feat: implement /tools and /info slash commands

- Add SlashCommand::Tools and SlashCommand::Info enum variants
- Add SLASH_COMMAND_SPECS entries for both commands
- Implement handlers in both REPL and CLI modes
- /tools lists available CLI tools with descriptions
- /info shows claw-code runtime information (version, git sha, rustc, target)
- Both commands return None from handle_slash_command, delegating to REPL/CLI layer
- Add test coverage for both commands in is_none test
- Implementations leverage existing tool specs and binary provenance infrastructure
This commit is contained in:
clawdbot committed 2026-10-13 00:11:36 +00:00
1 parent 18c0bead90
commit 837ffd0f6e
2 files changed
+178

No files matched your search

+28
View File
@@ -163,6 +163,20 @@ const SLASH_COMMAND_SPECS: &[SlashCommandSpec] = &[
argument_hint: None,
resume_supported: true,
},
SlashCommandSpec {
name: "tools",
aliases: &[],
summary: "List available tools and their descriptions",
argument_hint: None,
resume_supported: true,
},
SlashCommandSpec {
name: "info",
aliases: &[],
summary: "Show runtime information about claw-code",
argument_hint: None,
resume_supported: true,
},
SlashCommandSpec {
name: "bughunter",
aliases: &[],
@@ -1091,6 +1105,8 @@ pub enum SlashCommand {
Init,
Diff,
Version,
Tools,
Info,
Export {
path: Option<String>,
},
@@ -1392,6 +1408,14 @@ pub fn validate_slash_command_input(
validate_no_args(command, &args)?;
SlashCommand::Version
}
"tools" => {
validate_no_args(command, &args)?;
SlashCommand::Tools
}
"info" => {
validate_no_args(command, &args)?;
SlashCommand::Info
}
"export" => SlashCommand::Export { path: remainder },
"session" => parse_session_command(&args)?,
"plugin" | "plugins" | "marketplace" => parse_plugin_command(&args)?,
@@ -5403,6 +5427,8 @@ pub fn handle_slash_command(
| SlashCommand::History { .. }
| SlashCommand::Team { .. }
| SlashCommand::Setup
| SlashCommand::Tools
| SlashCommand::Info
| SlashCommand::Unknown(_) => None,
}
}
@@ -6245,6 +6271,8 @@ mod tests {
assert!(handle_slash_command("/mcp list", &session, CompactionConfig::default()).is_none());
assert!(handle_slash_command("/diff", &session, CompactionConfig::default()).is_none());
assert!(handle_slash_command("/version", &session, CompactionConfig::default()).is_none());
assert!(handle_slash_command("/tools", &session, CompactionConfig::default()).is_none());
assert!(handle_slash_command("/info", &session, CompactionConfig::default()).is_none());
assert!(
handle_slash_command("/export note.txt", &session, CompactionConfig::default())
.is_none()
+150
View File
@@ -1035,6 +1035,8 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
output_format,
} => print_system_prompt(cwd, date, &model, output_format)?,
CliAction::Version { output_format } => print_version(output_format)?,
CliAction::Tools { output_format } => print_tools(output_format)?,
CliAction::Info { output_format } => print_info(output_format)?,
CliAction::ResumeSession {
session_path,
commands,
@@ -1196,6 +1198,12 @@ enum CliAction {
Version {
output_format: CliOutputFormat,
},
Tools {
output_format: CliOutputFormat,
},
Info {
output_format: CliOutputFormat,
},
SessionList {
output_format: CliOutputFormat,
},
@@ -2598,6 +2606,8 @@ fn parse_direct_slash_cli_action(
Ok(Some(SlashCommand::Sandbox)) => Ok(CliAction::Sandbox { output_format }),
Ok(Some(SlashCommand::Diff)) => Ok(CliAction::Diff { output_format }),
Ok(Some(SlashCommand::Version)) => Ok(CliAction::Version { output_format }),
Ok(Some(SlashCommand::Tools)) => Ok(CliAction::Tools { output_format }),
Ok(Some(SlashCommand::Info)) => Ok(CliAction::Info { output_format }),
Ok(Some(SlashCommand::Doctor)) => Ok(CliAction::Doctor {
output_format,
permission_mode,
@@ -5077,6 +5087,84 @@ fn version_json_value() -> serde_json::Value {
})
}
fn print_tools(output_format: CliOutputFormat) -> Result<(), Box<dyn std::error::Error>> {
let specs = mvp_tool_specs();
match output_format {
CliOutputFormat::Text => {
println!("Available tools ({}):", specs.len());
for spec in &specs {
println!(" {}\n {}", spec.name, spec.description);
}
}
CliOutputFormat::Json => {
let tools: Vec<_> = specs
.into_iter()
.map(|spec| {
json!({
"name": spec.name,
"description": spec.description,
})
})
.collect();
println!(
"{}",
serde_json::to_string_pretty(&json!({
"kind": "tools",
"action": "list",
"status": "ok",
"tools": tools,
"count": tools.len(),
}))?
);
}
}
Ok(())
}
fn print_info(output_format: CliOutputFormat) -> Result<(), Box<dyn std::error::Error>> {
let cwd = env::current_dir().ok();
let binary_provenance = binary_provenance_for(cwd.as_deref());
match output_format {
CliOutputFormat::Text => {
println!("claw-code runtime information:");
println!(" Version: {}", VERSION);
if let Some(sha) = &binary_provenance.git_sha_short {
println!(" Git SHA: {}", sha);
}
if let Some(branch) = &binary_provenance.branch {
println!(" Branch: {}", branch);
}
println!(" Commit date: {}", binary_provenance.commit_date);
println!(" Rustc version: {}", binary_provenance.rustc_version);
if let Some(target) = &binary_provenance.target {
println!(" Target: {}", target);
}
if let Some(path) = binary_provenance.executable_path {
println!(" Executable: {}", path);
}
}
CliOutputFormat::Json => {
println!(
"{}",
serde_json::to_string_pretty(&json!({
"kind": "info",
"action": "show",
"status": "ok",
"version": VERSION,
"git_sha": binary_provenance.git_sha,
"git_sha_short": binary_provenance.git_sha_short,
"branch": binary_provenance.branch,
"commit_date": binary_provenance.commit_date,
"rustc_version": binary_provenance.rustc_version,
"target": binary_provenance.target,
"executable_path": binary_provenance.executable_path,
}))?
);
}
}
Ok(())
}
#[allow(clippy::too_many_lines)]
fn resume_session(session_path: &Path, commands: &[String], output_format: CliOutputFormat) {
let session_reference = session_path.display().to_string();
@@ -6785,6 +6873,56 @@ fn run_resume_command(
message: Some(render_version_report()),
json: Some(version_json_value()),
}),
SlashCommand::Tools => {
let specs = mvp_tool_specs();
let tool_count = specs.len();
let tools_text = specs
.iter()
.map(|spec| format!(" {}\n {}", spec.name, spec.description))
.collect::<Vec<_>>()
.join("\n");
let message = format!("Available tools ({}):\n{}", tool_count, tools_text);
let json_tools: Vec<_> = specs
.into_iter()
.map(|spec| serde_json::json!({ "name": spec.name, "description": spec.description }))
.collect();
Ok(ResumeCommandOutcome {
session: session.clone(),
message: Some(message),
json: Some(serde_json::json!({
"kind": "tools",
"action": "list",
"status": "ok",
"tools": json_tools,
"count": tool_count,
})),
})
}
SlashCommand::Info => {
let cwd = env::current_dir().ok();
let binary_provenance = binary_provenance_for(cwd.as_deref());
let message = format!(
"claw-code runtime information:\n Version: {}\n Git SHA: {}\n Rustc: {}\n Target: {}",
VERSION,
binary_provenance.git_sha_short.as_deref().unwrap_or("unknown"),
binary_provenance.rustc_version,
binary_provenance.target.as_deref().unwrap_or("unknown")
);
Ok(ResumeCommandOutcome {
session: session.clone(),
message: Some(message),
json: Some(serde_json::json!({
"kind": "info",
"action": "show",
"status": "ok",
"version": VERSION,
"git_sha": binary_provenance.git_sha,
"git_sha_short": binary_provenance.git_sha_short,
"rustc_version": binary_provenance.rustc_version,
"target": binary_provenance.target,
})),
})
}
SlashCommand::Export { path } => {
let export_path = resolve_export_path(path.as_deref(), session)?;
fs::write(&export_path, render_export_text(session))?;
@@ -8286,6 +8424,18 @@ impl LiveCli {
Self::print_version(CliOutputFormat::Text);
false
}
SlashCommand::Tools => {
if let Err(error) = print_tools(CliOutputFormat::Text) {
eprintln!("Tools: {error}");
}
false
}
SlashCommand::Info => {
if let Err(error) = print_info(CliOutputFormat::Text) {
eprintln!("Info: {error}");
}
false
}
SlashCommand::Export { path } => {
self.export_session(path.as_deref())?;
false