Skip to content

Commit 8272d39

Browse files
committed
Adding directories
1 parent a31db65 commit 8272d39

File tree

10 files changed

+145
-7
lines changed

10 files changed

+145
-7
lines changed

apps/desktop/src/components/codegen/CodegenPage.svelte

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,9 @@
145145
const selectedPermissionMode = $derived(
146146
selectedBranch ? uiState.lane(selectedBranch.stackId).permissionMode.current : 'default'
147147
);
148+
const laneState = $derived(
149+
selectedBranch?.stackId ? uiState.lane(selectedBranch.stackId) : undefined
150+
);
148151
149152
const prompt = $derived(
150153
selectedBranch ? uiState.lane(selectedBranch.stackId).prompt.current : ''
@@ -202,6 +205,22 @@
202205
if (!selectedBranch) return;
203206
if (!prompt) return;
204207
208+
// Handle /add-dir command
209+
if (prompt.startsWith('/add-dir ')) {
210+
const path = prompt.slice('/add-dir '.length).trim();
211+
if (path) {
212+
const isValid = await claudeCodeService.verifyPath({ projectId, path });
213+
if (isValid) {
214+
laneState?.addedDirs.add(path);
215+
chipToasts.success(`Added directory: ${path}`);
216+
} else {
217+
chipToasts.error(`Invalid directory path: ${path}`);
218+
}
219+
}
220+
setPrompt('');
221+
return;
222+
}
223+
205224
if (prompt.startsWith('/')) {
206225
chipToasts.warning('Slash commands are not yet supported');
207226
setPrompt('');
@@ -225,7 +244,8 @@
225244
thinkingLevel: selectedThinkingLevel,
226245
model: selectedModel,
227246
permissionMode: selectedPermissionMode,
228-
disabledMcpServers: uiState.lane(selectedBranch.stackId).disabledMcpServers.current
247+
disabledMcpServers: uiState.lane(selectedBranch.stackId).disabledMcpServers.current,
248+
addDirs: laneState?.addedDirs.current || []
229249
},
230250
{ properties: analyticsProperties }
231251
);
@@ -693,8 +713,9 @@
693713
{/snippet}
694714

695715
{#snippet rightSidebar(events: ClaudeMessage[])}
716+
{@const addedDirs = laneState?.addedDirs.current || []}
696717
<div class="right-sidebar" bind:this={rightSidebarRef}>
697-
{#if !branchChanges || !selectedBranch || (branchChanges.response && branchChanges.response.changes.length === 0 && getTodos(events).length === 0)}
718+
{#if !branchChanges || !selectedBranch || (branchChanges.response && branchChanges.response.changes.length === 0 && getTodos(events).length === 0 && addedDirs.length === 0)}
698719
<div class="right-sidebar__placeholder">
699720
<EmptyStatePlaceholder
700721
image={filesAndChecksSvg}
@@ -713,7 +734,7 @@
713734
<ReduxResult result={branchChanges.result} {projectId}>
714735
{#snippet children({ changes }, { projectId })}
715736
<Drawer
716-
bottomBorder={todos.length > 0}
737+
bottomBorder={todos.length > 0 || addedDirs.length > 0}
717738
grow
718739
defaultCollapsed={todos.length > 0}
719740
notFoldable
@@ -763,6 +784,35 @@
763784
</div>
764785
</Drawer>
765786
{/if}
787+
788+
{#if addedDirs.length > 0}
789+
<Drawer defaultCollapsed={false} noshrink>
790+
{#snippet header()}
791+
<h4 class="text-14 text-semibold truncate">Added Directories</h4>
792+
<Badge>{addedDirs.length}</Badge>
793+
{/snippet}
794+
795+
<div class="right-sidebar-list right-sidebar-list--small-gap">
796+
{#each addedDirs as dir}
797+
<div class="added-dir-item">
798+
<span class="text-13 grow-1">{dir}</span>
799+
<Button
800+
kind="ghost"
801+
icon="bin"
802+
shrinkable
803+
onclick={() => {
804+
if (selectedBranch) {
805+
uiState.lane(selectedBranch.stackId).addedDirs.remove(dir);
806+
chipToasts.success(`Removed directory: ${dir}`);
807+
}
808+
}}
809+
tooltip="Remove directory"
810+
/>
811+
</div>
812+
{/each}
813+
</div>
814+
</Drawer>
815+
{/if}
766816
{/if}
767817

768818
<Resizer
@@ -1188,4 +1238,14 @@
11881238
width: 9px;
11891239
height: 9px;
11901240
}
1241+
1242+
.added-dir-item {
1243+
display: flex;
1244+
align-items: center;
1245+
justify-content: space-between;
1246+
}
1247+
1248+
.right-sidebar-list--small-gap {
1249+
gap: 4px;
1250+
}
11911251
</style>

apps/desktop/src/lib/codegen/claude.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,10 @@ export class ClaudeCodeService {
115115
get subAgents() {
116116
return this.api.endpoints.getSubAgents.useQuery;
117117
}
118+
119+
get verifyPath() {
120+
return this.api.endpoints.verifyPath.mutate;
121+
}
118122
}
119123

120124
function injectEndpoints(api: ClientState['backendApi']) {
@@ -130,6 +134,7 @@ function injectEndpoints(api: ClientState['backendApi']) {
130134
model: ModelType;
131135
permissionMode: PermissionMode;
132136
disabledMcpServers: string[];
137+
addDirs: string[];
133138
}
134139
>({
135140
extraOptions: {
@@ -298,6 +303,19 @@ function injectEndpoints(api: ClientState['backendApi']) {
298303
getSubAgents: build.query<SubAgent[], { projectId: string }>({
299304
extraOptions: { command: 'claude_get_sub_agents' },
300305
query: (args) => args
306+
}),
307+
verifyPath: build.mutation<
308+
boolean,
309+
{
310+
projectId: string;
311+
path: string;
312+
}
313+
>({
314+
extraOptions: {
315+
command: 'claude_verify_path',
316+
actionName: 'Verify Path'
317+
},
318+
query: (args) => args
301319
})
302320
})
303321
});

apps/desktop/src/lib/state/uiState.svelte.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ export type StackState = {
3535
permissionMode: PermissionMode;
3636
// A list of mcp server names that should be disabled
3737
disabledMcpServers: string[];
38+
// A list of added directories for Claude Code
39+
addedDirs: string[];
3840
};
3941

4042
type BranchesSelection = {
@@ -156,7 +158,8 @@ export class UiState {
156158
prompt: '',
157159
// I _know_ we have a permission mode called 'default', but acceptEdits is a much more sensible default.
158160
permissionMode: 'acceptEdits',
159-
disabledMcpServers: []
161+
disabledMcpServers: [],
162+
addedDirs: []
160163
});
161164

162165
/** Properties scoped to a specific project. */

apps/desktop/src/lib/testing/mockUiState.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ const MOCK_STACK_UI_STATE: StackState = {
1717
newCommitMessage: { title: '', description: '' },
1818
prompt: '',
1919
permissionMode: 'default',
20-
disabledMcpServers: []
20+
disabledMcpServers: [],
21+
addedDirs: []
2122
};
2223

2324
const MOCK_PROJECT_UI_STATE: ProjectUiState = {

crates/but-api/src/commands/claude.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,3 +194,32 @@ pub async fn claude_get_sub_agents(
194194
let sub_agents = but_claude::claude_sub_agents::read_claude_sub_agents(&project.path).await;
195195
Ok(sub_agents)
196196
}
197+
198+
#[tauri::command(async)]
199+
#[instrument(err(Debug))]
200+
pub async fn claude_verify_path(project_id: ProjectId, path: String) -> Result<bool, Error> {
201+
let project = gitbutler_project::get(project_id)?;
202+
203+
// Check if it's an absolute path first
204+
let path = if std::path::Path::new(&path).is_absolute() {
205+
std::path::PathBuf::from(&path)
206+
} else {
207+
// If relative, make it relative to project path
208+
project.path.join(&path)
209+
};
210+
211+
// Check if the path exists and is a directory
212+
match tokio::fs::try_exists(&path).await {
213+
Ok(exists) => {
214+
if exists {
215+
match tokio::fs::metadata(&path).await {
216+
Ok(metadata) => Ok(metadata.is_dir()),
217+
Err(_) => Ok(false),
218+
}
219+
} else {
220+
Ok(false)
221+
}
222+
}
223+
Err(_) => Ok(false),
224+
}
225+
}

crates/but-claude/src/bridge.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,7 @@ async fn spawn_command(
377377
command.args(["--model", user_params.model.to_cli_string()]);
378378
}
379379

380-
command.args(["-p", "--verbose"]);
380+
command.args(["--verbose"]);
381381

382382
if app_settings.claude.dangerously_allow_all_permissions {
383383
command.arg("--dangerously-skip-permissions");
@@ -413,6 +413,13 @@ async fn spawn_command(
413413

414414
command.args(["--append-system-prompt", SYSTEM_PROMPT]);
415415

416+
if !user_params.add_dirs.is_empty() {
417+
command.arg("--add-dir");
418+
command.args(user_params.add_dirs);
419+
}
420+
421+
command.arg("-p");
422+
416423
command.arg(format_message(
417424
&user_params.message,
418425
user_params.thinking_level,

crates/but-claude/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,4 +164,5 @@ pub struct ClaudeUserParams {
164164
pub model: ModelType,
165165
pub permission_mode: PermissionMode,
166166
pub disabled_mcp_servers: Vec<String>,
167+
pub add_dirs: Vec<String>,
167168
}

crates/but-server/src/lib.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,22 @@ async fn handle_command(
508508
Err(e) => Err(e),
509509
}
510510
}
511+
"claude_verify_path" => {
512+
#[derive(Debug, Deserialize)]
513+
#[serde(rename_all = "camelCase")]
514+
pub struct Params {
515+
pub project_id: ProjectId,
516+
pub path: String,
517+
}
518+
let params = serde_json::from_value::<Params>(request.params).to_error();
519+
match params {
520+
Ok(params) => {
521+
let result = claude::claude_verify_path(params.project_id, params.path).await;
522+
result.map(|r| json!(r))
523+
}
524+
Err(e) => Err(e),
525+
}
526+
}
511527

512528
_ => Err(anyhow::anyhow!("Command {} not found!", command).into()),
513529
};

crates/gitbutler-tauri/src/claude.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ pub async fn claude_send_message(
2323
model: ModelType,
2424
permission_mode: PermissionMode,
2525
disabled_mcp_servers: Vec<String>,
26+
add_dirs: Vec<String>,
2627
) -> Result<(), Error> {
2728
claude::claude_send_message(
2829
&app,
@@ -35,6 +36,7 @@ pub async fn claude_send_message(
3536
model,
3637
permission_mode,
3738
disabled_mcp_servers,
39+
add_dirs,
3840
},
3941
},
4042
)

crates/gitbutler-tauri/src/main.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,8 @@ fn main() {
358358
but_api::claude::claude_write_prompt_templates,
359359
but_api::claude::claude_get_prompt_templates_path,
360360
but_api::claude::claude_get_mcp_config,
361-
but_api::claude::claude_get_sub_agents
361+
but_api::claude::claude_get_sub_agents,
362+
but_api::claude::claude_verify_path
362363
])
363364
.menu(move |handle| menu::build(handle, &app_settings_for_menu))
364365
.on_window_event(|window, event| match event {

0 commit comments

Comments
 (0)