Skip to content

Commit 1d958fd

Browse files
committed
feat(chat): add /changedir (/cd) slash command to change workdir mid-session
Allows changing the working directory without restarting the session: /changedir /path/to/project /cd ~/project/wezterm /cd (goes to $HOME) After changing directory, the user is prompted to run /code init to reinitialize code intelligence for the new directory.
1 parent 25f7c8a commit 1d958fd

3 files changed

Lines changed: 72 additions & 12 deletions

File tree

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
use std::path::PathBuf;
2+
3+
use clap::Args;
4+
use crossterm::{
5+
execute,
6+
style,
7+
};
8+
9+
use crate::cli::chat::{
10+
ChatError,
11+
ChatSession,
12+
ChatState,
13+
};
14+
use crate::theme::StyledText;
15+
16+
#[derive(Debug, PartialEq, Args)]
17+
/// Arguments for the changedir command.
18+
pub struct ChangedirArgs {
19+
/// The directory to switch to. Defaults to $HOME if not provided.
20+
pub path: Option<PathBuf>,
21+
}
22+
23+
impl ChangedirArgs {
24+
pub async fn execute(self, session: &mut ChatSession) -> Result<ChatState, ChatError> {
25+
let target = match self.path {
26+
Some(p) => p,
27+
None => dirs::home_dir().ok_or_else(|| ChatError::Custom("Could not determine home directory".into()))?,
28+
};
29+
30+
let target = if target.is_relative() {
31+
std::env::current_dir()
32+
.map_err(|e| ChatError::Custom(e.to_string().into()))?
33+
.join(&target)
34+
} else {
35+
target
36+
};
37+
38+
std::env::set_current_dir(&target)
39+
.map_err(|e| ChatError::Custom(format!("Failed to change directory: {e}").into()))?;
40+
41+
execute!(
42+
session.stderr,
43+
StyledText::success_fg(),
44+
style::Print(format!("Working directory changed to: {}\n", target.display())),
45+
style::Print("Run /code init to reinitialize code intelligence for this directory.\n"),
46+
StyledText::reset(),
47+
)?;
48+
49+
Ok(ChatState::PromptUser {
50+
skip_printing_tools: true,
51+
})
52+
}
53+
}

crates/chat-cli/src/cli/chat/cli/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::theme::StyledText;
22
pub mod changelog;
3+
pub mod changedir;
34
pub mod checkpoint;
45
pub mod clear;
56
pub mod compact;
@@ -23,6 +24,7 @@ pub mod tools;
2324
pub mod usage;
2425

2526
use changelog::ChangelogArgs;
27+
use changedir::ChangedirArgs;
2628
use clap::Parser;
2729
use clear::ClearArgs;
2830
use compact::CompactArgs;
@@ -65,6 +67,9 @@ pub enum SlashCommand {
6567
Quit,
6668
/// Clear the conversation history
6769
Clear(ClearArgs),
70+
/// Change the working directory for this session
71+
#[command(name = "changedir", alias = "cd")]
72+
Changedir(ChangedirArgs),
6873
/// Manage agents
6974
#[command(subcommand)]
7075
Agent(AgentSubcommand),
@@ -134,6 +139,7 @@ impl SlashCommand {
134139
match self {
135140
Self::Quit => Ok(ChatState::Exit),
136141
Self::Clear(args) => args.execute(session).await,
142+
Self::Changedir(args) => args.execute(session).await,
137143
Self::Agent(subcommand) => subcommand.execute(os, session).await,
138144
Self::Profile => {
139145
use crossterm::{
@@ -203,6 +209,7 @@ impl SlashCommand {
203209
match self {
204210
Self::Quit => "quit",
205211
Self::Clear(_) => "clear",
212+
Self::Changedir(_) => "changedir",
206213
Self::Agent(_) => "agent",
207214
Self::Profile => "profile",
208215
Self::Context(_) => "context",

crates/chat-cli/src/cli/mod.rs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ mod test {
378378
});
379379

380380
assert_eq!(Cli::parse_from([CHAT_BINARY_NAME, "chat", "-vv"]), Cli {
381-
subcommand: Some(RootSubcommand::Chat(ChatArgs {
381+
subcommand: Some(RootSubcommand::Chat(ChatArgs { workdir: None,
382382
resume: false,
383383
input: None,
384384
agent: None,
@@ -418,7 +418,7 @@ mod test {
418418
fn test_chat_with_context_profile() {
419419
assert_parse!(
420420
["chat", "--profile", "my-profile"],
421-
RootSubcommand::Chat(ChatArgs {
421+
RootSubcommand::Chat(ChatArgs { workdir: None,
422422
resume: false,
423423
input: None,
424424
agent: Some("my-profile".to_string()),
@@ -435,7 +435,7 @@ mod test {
435435
fn test_chat_with_context_profile_and_input() {
436436
assert_parse!(
437437
["chat", "--profile", "my-profile", "Hello"],
438-
RootSubcommand::Chat(ChatArgs {
438+
RootSubcommand::Chat(ChatArgs { workdir: None,
439439
resume: false,
440440
input: Some("Hello".to_string()),
441441
agent: Some("my-profile".to_string()),
@@ -452,7 +452,7 @@ mod test {
452452
fn test_chat_with_context_profile_and_accept_all() {
453453
assert_parse!(
454454
["chat", "--profile", "my-profile", "--trust-all-tools"],
455-
RootSubcommand::Chat(ChatArgs {
455+
RootSubcommand::Chat(ChatArgs { workdir: None,
456456
resume: false,
457457
input: None,
458458
agent: Some("my-profile".to_string()),
@@ -469,7 +469,7 @@ mod test {
469469
fn test_chat_with_no_interactive_and_resume() {
470470
assert_parse!(
471471
["chat", "--no-interactive", "--resume"],
472-
RootSubcommand::Chat(ChatArgs {
472+
RootSubcommand::Chat(ChatArgs { workdir: None,
473473
resume: true,
474474
input: None,
475475
agent: None,
@@ -482,7 +482,7 @@ mod test {
482482
);
483483
assert_parse!(
484484
["chat", "--non-interactive", "-r"],
485-
RootSubcommand::Chat(ChatArgs {
485+
RootSubcommand::Chat(ChatArgs { workdir: None,
486486
resume: true,
487487
input: None,
488488
agent: None,
@@ -499,7 +499,7 @@ mod test {
499499
fn test_chat_with_tool_trust_all() {
500500
assert_parse!(
501501
["chat", "--trust-all-tools"],
502-
RootSubcommand::Chat(ChatArgs {
502+
RootSubcommand::Chat(ChatArgs { workdir: None,
503503
resume: false,
504504
input: None,
505505
agent: None,
@@ -516,7 +516,7 @@ mod test {
516516
fn test_chat_with_tool_trust_none() {
517517
assert_parse!(
518518
["chat", "--trust-tools="],
519-
RootSubcommand::Chat(ChatArgs {
519+
RootSubcommand::Chat(ChatArgs { workdir: None,
520520
resume: false,
521521
input: None,
522522
agent: None,
@@ -533,7 +533,7 @@ mod test {
533533
fn test_chat_with_tool_trust_some() {
534534
assert_parse!(
535535
["chat", "--trust-tools=fs_read,fs_write"],
536-
RootSubcommand::Chat(ChatArgs {
536+
RootSubcommand::Chat(ChatArgs { workdir: None,
537537
resume: false,
538538
input: None,
539539
agent: None,
@@ -550,7 +550,7 @@ mod test {
550550
fn test_chat_with_different_wrap_modes() {
551551
assert_parse!(
552552
["chat", "-w", "never"],
553-
RootSubcommand::Chat(ChatArgs {
553+
RootSubcommand::Chat(ChatArgs { workdir: None,
554554
resume: false,
555555
input: None,
556556
agent: None,
@@ -563,7 +563,7 @@ mod test {
563563
);
564564
assert_parse!(
565565
["chat", "--wrap", "always"],
566-
RootSubcommand::Chat(ChatArgs {
566+
RootSubcommand::Chat(ChatArgs { workdir: None,
567567
resume: false,
568568
input: None,
569569
agent: None,
@@ -576,7 +576,7 @@ mod test {
576576
);
577577
assert_parse!(
578578
["chat", "--wrap", "auto"],
579-
RootSubcommand::Chat(ChatArgs {
579+
RootSubcommand::Chat(ChatArgs { workdir: None,
580580
resume: false,
581581
input: None,
582582
agent: None,

0 commit comments

Comments
 (0)