Merge a6c32edcf6 into 08106b0c37
This commit is contained in:
commit
353844b4e0
|
|
@ -1131,6 +1131,9 @@ pub enum SlashCommand {
|
||||||
SecurityReview,
|
SecurityReview,
|
||||||
Keybindings,
|
Keybindings,
|
||||||
PrivacySettings,
|
PrivacySettings,
|
||||||
|
Workspace {
|
||||||
|
path: Option<String>,
|
||||||
|
},
|
||||||
Plan {
|
Plan {
|
||||||
mode: Option<String>,
|
mode: Option<String>,
|
||||||
},
|
},
|
||||||
|
|
@ -1272,6 +1275,7 @@ impl SlashCommand {
|
||||||
Self::SecurityReview => "/security-review",
|
Self::SecurityReview => "/security-review",
|
||||||
Self::Keybindings => "/keybindings",
|
Self::Keybindings => "/keybindings",
|
||||||
Self::PrivacySettings => "/privacy-settings",
|
Self::PrivacySettings => "/privacy-settings",
|
||||||
|
Self::Workspace { .. } => "/workspace",
|
||||||
Self::Plan { .. } => "/plan",
|
Self::Plan { .. } => "/plan",
|
||||||
Self::Review { .. } => "/review",
|
Self::Review { .. } => "/review",
|
||||||
Self::Tasks { .. } => "/tasks",
|
Self::Tasks { .. } => "/tasks",
|
||||||
|
|
@ -1405,6 +1409,9 @@ pub fn validate_slash_command_input(
|
||||||
validate_no_args(command, &args)?;
|
validate_no_args(command, &args)?;
|
||||||
SlashCommand::Setup
|
SlashCommand::Setup
|
||||||
}
|
}
|
||||||
|
"workspace" | "cwd" => SlashCommand::Workspace {
|
||||||
|
path: optional_single_arg(command, &args, "[path]")?,
|
||||||
|
},
|
||||||
"login" | "logout" => {
|
"login" | "logout" => {
|
||||||
return Err(command_error(
|
return Err(command_error(
|
||||||
"This auth flow was removed. Set ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN instead.",
|
"This auth flow was removed. Set ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN instead.",
|
||||||
|
|
@ -5342,6 +5349,7 @@ pub fn handle_slash_command(
|
||||||
| SlashCommand::Cost
|
| SlashCommand::Cost
|
||||||
| SlashCommand::Resume { .. }
|
| SlashCommand::Resume { .. }
|
||||||
| SlashCommand::Config { .. }
|
| SlashCommand::Config { .. }
|
||||||
|
| SlashCommand::Workspace { .. }
|
||||||
| SlashCommand::Mcp { .. }
|
| SlashCommand::Mcp { .. }
|
||||||
| SlashCommand::Memory
|
| SlashCommand::Memory
|
||||||
| SlashCommand::Init
|
| SlashCommand::Init
|
||||||
|
|
@ -6119,6 +6127,24 @@ mod tests {
|
||||||
assert_eq!(suggest_slash_commands("zzz", 3), Vec::<String>::new());
|
assert_eq!(suggest_slash_commands("zzz", 3), Vec::<String>::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parses_workspace_slash_command_and_alias() {
|
||||||
|
let workspace = validate_slash_command_input("/workspace")
|
||||||
|
.expect("workspace should parse")
|
||||||
|
.expect("workspace should be a slash command");
|
||||||
|
assert_eq!(workspace, SlashCommand::Workspace { path: None });
|
||||||
|
|
||||||
|
let cwd = validate_slash_command_input("/cwd src")
|
||||||
|
.expect("cwd alias should parse")
|
||||||
|
.expect("cwd alias should be a slash command");
|
||||||
|
assert_eq!(
|
||||||
|
cwd,
|
||||||
|
SlashCommand::Workspace {
|
||||||
|
path: Some("src".to_string()),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn compacts_sessions_via_slash_command() {
|
fn compacts_sessions_via_slash_command() {
|
||||||
let mut session = Session::new();
|
let mut session = Session::new();
|
||||||
|
|
|
||||||
|
|
@ -6654,6 +6654,49 @@ fn run_resume_command(
|
||||||
message: Some(render_memory_report()?),
|
message: Some(render_memory_report()?),
|
||||||
json: Some(render_memory_json()?),
|
json: Some(render_memory_json()?),
|
||||||
}),
|
}),
|
||||||
|
SlashCommand::Workspace { path } => {
|
||||||
|
let cwd_before = env::current_dir()?;
|
||||||
|
let workspace_root = session
|
||||||
|
.workspace_root()
|
||||||
|
.map(Path::to_path_buf)
|
||||||
|
.unwrap_or_else(|| cwd_before.clone());
|
||||||
|
let workspace_root = canonicalize_or_clone(&workspace_root);
|
||||||
|
let changed = if let Some(path) = path.as_deref() {
|
||||||
|
let requested_path = Path::new(path);
|
||||||
|
let resolved_path = if requested_path.is_absolute() {
|
||||||
|
requested_path.to_path_buf()
|
||||||
|
} else {
|
||||||
|
cwd_before.join(requested_path)
|
||||||
|
};
|
||||||
|
let resolved_path = canonicalize_or_clone(&resolved_path);
|
||||||
|
if !resolved_path.starts_with(&workspace_root) {
|
||||||
|
return Err(format!(
|
||||||
|
"workspace_change_outside_root: `{}` is outside the current workspace root `{}`.\nUse `claw --cwd {}` to start a new session there.",
|
||||||
|
resolved_path.display(),
|
||||||
|
workspace_root.display(),
|
||||||
|
resolved_path.display(),
|
||||||
|
)
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
env::set_current_dir(&resolved_path)?;
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
let cwd_after = env::current_dir()?;
|
||||||
|
let message =
|
||||||
|
render_workspace_report(&cwd_after, &workspace_root, &session.session_id, changed);
|
||||||
|
Ok(ResumeCommandOutcome {
|
||||||
|
session: session.clone(),
|
||||||
|
message: Some(message),
|
||||||
|
json: Some(workspace_report_json(
|
||||||
|
&cwd_after,
|
||||||
|
&workspace_root,
|
||||||
|
&session.session_id,
|
||||||
|
changed,
|
||||||
|
)),
|
||||||
|
})
|
||||||
|
}
|
||||||
SlashCommand::Init => {
|
SlashCommand::Init => {
|
||||||
// #142: run the init once, then render both text + structured JSON
|
// #142: run the init once, then render both text + structured JSON
|
||||||
// from the same InitReport so both surfaces stay in sync.
|
// from the same InitReport so both surfaces stay in sync.
|
||||||
|
|
@ -8122,6 +8165,7 @@ impl LiveCli {
|
||||||
Self::print_config(section.as_deref())?;
|
Self::print_config(section.as_deref())?;
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
SlashCommand::Workspace { path } => self.handle_workspace_command(path.as_deref())?,
|
||||||
SlashCommand::Mcp { action, target } => {
|
SlashCommand::Mcp { action, target } => {
|
||||||
let args = match (action.as_deref(), target.as_deref()) {
|
let args = match (action.as_deref(), target.as_deref()) {
|
||||||
(None, None) => None,
|
(None, None) => None,
|
||||||
|
|
@ -8280,6 +8324,51 @@ impl LiveCli {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_workspace_command(
|
||||||
|
&mut self,
|
||||||
|
target: Option<&str>,
|
||||||
|
) -> Result<bool, Box<dyn std::error::Error>> {
|
||||||
|
let current_dir = env::current_dir()?;
|
||||||
|
let workspace_root = self
|
||||||
|
.runtime
|
||||||
|
.session()
|
||||||
|
.workspace_root()
|
||||||
|
.map(Path::to_path_buf)
|
||||||
|
.unwrap_or_else(|| current_dir.clone());
|
||||||
|
let workspace_root = canonicalize_or_clone(&workspace_root);
|
||||||
|
|
||||||
|
if let Some(target) = target {
|
||||||
|
let requested_path = Path::new(target);
|
||||||
|
let resolved_path = if requested_path.is_absolute() {
|
||||||
|
requested_path.to_path_buf()
|
||||||
|
} else {
|
||||||
|
current_dir.join(requested_path)
|
||||||
|
};
|
||||||
|
let resolved_path = canonicalize_or_clone(&resolved_path);
|
||||||
|
if !resolved_path.starts_with(&workspace_root) {
|
||||||
|
return Err(format!(
|
||||||
|
"workspace_change_outside_root: `{}` is outside the current workspace root `{}`.\nUse `claw --cwd {}` to start a new session there.",
|
||||||
|
resolved_path.display(),
|
||||||
|
workspace_root.display(),
|
||||||
|
resolved_path.display(),
|
||||||
|
)
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
env::set_current_dir(&resolved_path)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"{}",
|
||||||
|
render_workspace_report(
|
||||||
|
&env::current_dir()?,
|
||||||
|
&workspace_root,
|
||||||
|
&self.session.id,
|
||||||
|
target.is_some(),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
Ok(target.is_some())
|
||||||
|
}
|
||||||
|
|
||||||
fn record_prompt_history(&mut self, prompt: &str) {
|
fn record_prompt_history(&mut self, prompt: &str) {
|
||||||
let timestamp_ms = std::time::SystemTime::now()
|
let timestamp_ms = std::time::SystemTime::now()
|
||||||
.duration_since(UNIX_EPOCH)
|
.duration_since(UNIX_EPOCH)
|
||||||
|
|
@ -9111,6 +9200,41 @@ fn new_cli_session() -> Result<Session, Box<dyn std::error::Error>> {
|
||||||
Ok(Session::new().with_workspace_root(env::current_dir()?))
|
Ok(Session::new().with_workspace_root(env::current_dir()?))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn canonicalize_or_clone(path: &Path) -> PathBuf {
|
||||||
|
fs::canonicalize(path).unwrap_or_else(|_| path.to_path_buf())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_workspace_report(
|
||||||
|
cwd: &Path,
|
||||||
|
workspace_root: &Path,
|
||||||
|
session_id: &str,
|
||||||
|
changed: bool,
|
||||||
|
) -> String {
|
||||||
|
let action = if changed { "change" } else { "show" };
|
||||||
|
format!(
|
||||||
|
"Workspace\n Action {action}\n Session {session_id}\n Workspace root {}\n Current directory {}",
|
||||||
|
workspace_root.display(),
|
||||||
|
cwd.display(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn workspace_report_json(
|
||||||
|
cwd: &Path,
|
||||||
|
workspace_root: &Path,
|
||||||
|
session_id: &str,
|
||||||
|
changed: bool,
|
||||||
|
) -> Value {
|
||||||
|
serde_json::json!({
|
||||||
|
"kind": "workspace",
|
||||||
|
"action": if changed { "change" } else { "show" },
|
||||||
|
"status": "ok",
|
||||||
|
"session_id": session_id,
|
||||||
|
"workspace_root": workspace_root.display().to_string(),
|
||||||
|
"current_directory": cwd.display().to_string(),
|
||||||
|
"changed": changed,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn create_managed_session_handle(
|
fn create_managed_session_handle(
|
||||||
session_id: &str,
|
session_id: &str,
|
||||||
) -> Result<SessionHandle, Box<dyn std::error::Error>> {
|
) -> Result<SessionHandle, Box<dyn std::error::Error>> {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue