diff --git a/rust/crates/commands/src/lib.rs b/rust/crates/commands/src/lib.rs index a1e70069..0e2e7cd0 100644 --- a/rust/crates/commands/src/lib.rs +++ b/rust/crates/commands/src/lib.rs @@ -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, }, @@ -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() diff --git a/rust/crates/rusty-claude-cli/src/main.rs b/rust/crates/rusty-claude-cli/src/main.rs index cda89fa1..ba226956 100644 --- a/rust/crates/rusty-claude-cli/src/main.rs +++ b/rust/crates/rusty-claude-cli/src/main.rs @@ -1035,6 +1035,8 @@ fn run() -> Result<(), Box> { 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> { + 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> { + 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::>() + .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