mirror of
https://github.com/instructkr/claw-code.git
synced 2026-04-07 00:24:50 +08:00
Honor JSON output for skills and MCP inventory commands
The skills and mcp inventory handlers were still emitting prose tables even when the global --output-format json flag was set. This wires structured JSON renderers into the command handlers and CLI dispatch so direct invocations and resumed slash-command execution both return machine-readable payloads while preserving existing text output in the REPL path. Constraint: Must preserve existing text output and help behavior for interactive slash commands Rejected: Parse existing prose tables into JSON at the CLI edge | brittle and loses structured fields Confidence: high Scope-risk: narrow Reversibility: clean Directive: Keep text and JSON variants driven by the same command parsing branches so --output-format stays deterministic across entry points Tested: cargo test -p commands Tested: cargo test -p rusty-claude-cli Not-tested: Manual invocation against a live user skills registry or external MCP services
This commit is contained in:
@@ -31,9 +31,10 @@ use api::{
|
||||
};
|
||||
|
||||
use commands::{
|
||||
handle_agents_slash_command, handle_mcp_slash_command, handle_plugins_slash_command,
|
||||
handle_skills_slash_command, render_slash_command_help, resume_supported_slash_commands,
|
||||
slash_command_specs, validate_slash_command_input, SlashCommand,
|
||||
handle_agents_slash_command, handle_mcp_slash_command, handle_mcp_slash_command_json,
|
||||
handle_plugins_slash_command, handle_skills_slash_command, handle_skills_slash_command_json,
|
||||
render_slash_command_help, resume_supported_slash_commands, slash_command_specs,
|
||||
validate_slash_command_input, SlashCommand,
|
||||
};
|
||||
use compat_harness::{extract_manifest, UpstreamPaths};
|
||||
use init::initialize_repo;
|
||||
@@ -111,8 +112,14 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
||||
CliAction::DumpManifests => dump_manifests(),
|
||||
CliAction::BootstrapPlan => print_bootstrap_plan(),
|
||||
CliAction::Agents { args } => LiveCli::print_agents(args.as_deref())?,
|
||||
CliAction::Mcp { args } => LiveCli::print_mcp(args.as_deref())?,
|
||||
CliAction::Skills { args } => LiveCli::print_skills(args.as_deref())?,
|
||||
CliAction::Mcp {
|
||||
args,
|
||||
output_format,
|
||||
} => LiveCli::print_mcp(args.as_deref(), output_format)?,
|
||||
CliAction::Skills {
|
||||
args,
|
||||
output_format,
|
||||
} => LiveCli::print_skills(args.as_deref(), output_format)?,
|
||||
CliAction::PrintSystemPrompt { cwd, date } => print_system_prompt(cwd, date),
|
||||
CliAction::Version => print_version(),
|
||||
CliAction::ResumeSession {
|
||||
@@ -156,9 +163,11 @@ enum CliAction {
|
||||
},
|
||||
Mcp {
|
||||
args: Option<String>,
|
||||
output_format: CliOutputFormat,
|
||||
},
|
||||
Skills {
|
||||
args: Option<String>,
|
||||
output_format: CliOutputFormat,
|
||||
},
|
||||
PrintSystemPrompt {
|
||||
cwd: PathBuf,
|
||||
@@ -370,9 +379,11 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
|
||||
}),
|
||||
"mcp" => Ok(CliAction::Mcp {
|
||||
args: join_optional_args(&rest[1..]),
|
||||
output_format,
|
||||
}),
|
||||
"skills" => Ok(CliAction::Skills {
|
||||
args: join_optional_args(&rest[1..]),
|
||||
output_format,
|
||||
}),
|
||||
"system-prompt" => parse_system_prompt_args(&rest[1..]),
|
||||
"login" => Ok(CliAction::Login),
|
||||
@@ -391,7 +402,7 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
|
||||
permission_mode,
|
||||
})
|
||||
}
|
||||
other if other.starts_with('/') => parse_direct_slash_cli_action(&rest),
|
||||
other if other.starts_with('/') => parse_direct_slash_cli_action(&rest, output_format),
|
||||
_other => Ok(CliAction::Prompt {
|
||||
prompt: rest.join(" "),
|
||||
model,
|
||||
@@ -479,7 +490,10 @@ fn join_optional_args(args: &[String]) -> Option<String> {
|
||||
(!trimmed.is_empty()).then(|| trimmed.to_string())
|
||||
}
|
||||
|
||||
fn parse_direct_slash_cli_action(rest: &[String]) -> Result<CliAction, String> {
|
||||
fn parse_direct_slash_cli_action(
|
||||
rest: &[String],
|
||||
output_format: CliOutputFormat,
|
||||
) -> Result<CliAction, String> {
|
||||
let raw = rest.join(" ");
|
||||
match SlashCommand::parse(&raw) {
|
||||
Ok(Some(SlashCommand::Help)) => Ok(CliAction::Help),
|
||||
@@ -491,8 +505,12 @@ fn parse_direct_slash_cli_action(rest: &[String]) -> Result<CliAction, String> {
|
||||
(Some(action), Some(target)) => Some(format!("{action} {target}")),
|
||||
(None, Some(target)) => Some(target),
|
||||
},
|
||||
output_format,
|
||||
}),
|
||||
Ok(Some(SlashCommand::Skills { args })) => Ok(CliAction::Skills {
|
||||
args,
|
||||
output_format,
|
||||
}),
|
||||
Ok(Some(SlashCommand::Skills { args })) => Ok(CliAction::Skills { args }),
|
||||
Ok(Some(SlashCommand::Unknown(name))) => Err(format_unknown_direct_slash_command(&name)),
|
||||
Ok(Some(command)) => Err({
|
||||
let _ = command;
|
||||
@@ -1844,7 +1862,13 @@ fn run_resume_command(
|
||||
};
|
||||
Ok(ResumeCommandOutcome {
|
||||
session: session.clone(),
|
||||
message: Some(handle_mcp_slash_command(args.as_deref(), &cwd)?),
|
||||
message: Some(match output_format {
|
||||
CliOutputFormat::Text => json!({
|
||||
"kind": "mcp",
|
||||
"message": handle_mcp_slash_command(args.as_deref(), &cwd)?,
|
||||
}),
|
||||
CliOutputFormat::Json => handle_mcp_slash_command_json(args.as_deref(), &cwd)?,
|
||||
}),
|
||||
})
|
||||
}
|
||||
SlashCommand::Memory => Ok(ResumeCommandOutcome {
|
||||
@@ -1888,7 +1912,15 @@ fn run_resume_command(
|
||||
let cwd = env::current_dir()?;
|
||||
Ok(ResumeCommandOutcome {
|
||||
session: session.clone(),
|
||||
message: Some(handle_skills_slash_command(args.as_deref(), &cwd)?),
|
||||
message: Some(match output_format {
|
||||
CliOutputFormat::Text => json!({
|
||||
"kind": "skills",
|
||||
"message": handle_skills_slash_command(args.as_deref(), &cwd)?,
|
||||
}),
|
||||
CliOutputFormat::Json => {
|
||||
handle_skills_slash_command_json(args.as_deref(), &cwd)?
|
||||
}
|
||||
}),
|
||||
})
|
||||
}
|
||||
SlashCommand::Doctor => Ok(ResumeCommandOutcome {
|
||||
@@ -2777,7 +2809,7 @@ impl LiveCli {
|
||||
(Some(action), Some(target)) => Some(format!("{action} {target}")),
|
||||
(None, Some(target)) => Some(target.to_string()),
|
||||
};
|
||||
Self::print_mcp(args.as_deref())?;
|
||||
Self::print_mcp(args.as_deref(), CliOutputFormat::Text)?;
|
||||
false
|
||||
}
|
||||
SlashCommand::Memory => {
|
||||
@@ -2811,7 +2843,7 @@ impl LiveCli {
|
||||
false
|
||||
}
|
||||
SlashCommand::Skills { args } => {
|
||||
Self::print_skills(args.as_deref())?;
|
||||
Self::print_skills(args.as_deref(), CliOutputFormat::Text)?;
|
||||
false
|
||||
}
|
||||
SlashCommand::Doctor => {
|
||||
@@ -3095,15 +3127,33 @@ impl LiveCli {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_mcp(args: Option<&str>) -> Result<(), Box<dyn std::error::Error>> {
|
||||
fn print_mcp(
|
||||
args: Option<&str>,
|
||||
output_format: CliOutputFormat,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let cwd = env::current_dir()?;
|
||||
println!("{}", handle_mcp_slash_command(args, &cwd)?);
|
||||
match output_format {
|
||||
CliOutputFormat::Text => println!("{}", handle_mcp_slash_command(args, &cwd)?),
|
||||
CliOutputFormat::Json => println!(
|
||||
"{}",
|
||||
serialize_json_output(&handle_mcp_slash_command_json(args, &cwd)?)?
|
||||
),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_skills(args: Option<&str>) -> Result<(), Box<dyn std::error::Error>> {
|
||||
fn print_skills(
|
||||
args: Option<&str>,
|
||||
output_format: CliOutputFormat,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let cwd = env::current_dir()?;
|
||||
println!("{}", handle_skills_slash_command(args, &cwd)?);
|
||||
match output_format {
|
||||
CliOutputFormat::Text => println!("{}", handle_skills_slash_command(args, &cwd)?),
|
||||
CliOutputFormat::Json => println!(
|
||||
"{}",
|
||||
serialize_json_output(&handle_skills_slash_command_json(args, &cwd)?)?
|
||||
),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -6498,11 +6548,17 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
parse_args(&["mcp".to_string()]).expect("mcp should parse"),
|
||||
CliAction::Mcp { args: None }
|
||||
CliAction::Mcp {
|
||||
args: None,
|
||||
output_format: CliOutputFormat::Text,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
parse_args(&["skills".to_string()]).expect("skills should parse"),
|
||||
CliAction::Skills { args: None }
|
||||
CliAction::Skills {
|
||||
args: None,
|
||||
output_format: CliOutputFormat::Text,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
parse_args(&["agents".to_string(), "--help".to_string()])
|
||||
@@ -6557,6 +6613,30 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_json_output_for_mcp_and_skills_commands() {
|
||||
assert_eq!(
|
||||
parse_args(&["--output-format=json".to_string(), "mcp".to_string()])
|
||||
.expect("json mcp should parse"),
|
||||
CliAction::Mcp {
|
||||
args: None,
|
||||
output_format: CliOutputFormat::Json,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
parse_args(&[
|
||||
"--output-format=json".to_string(),
|
||||
"/skills".to_string(),
|
||||
"help".to_string(),
|
||||
])
|
||||
.expect("json /skills help should parse"),
|
||||
CliAction::Skills {
|
||||
args: Some("help".to_string()),
|
||||
output_format: CliOutputFormat::Json,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_word_slash_command_names_return_guidance_instead_of_hitting_prompt_mode() {
|
||||
let error = parse_args(&["cost".to_string()]).expect_err("cost should return guidance");
|
||||
@@ -6591,18 +6671,23 @@ mod tests {
|
||||
parse_args(&["/mcp".to_string(), "show".to_string(), "demo".to_string()])
|
||||
.expect("/mcp show demo should parse"),
|
||||
CliAction::Mcp {
|
||||
args: Some("show demo".to_string())
|
||||
args: Some("show demo".to_string()),
|
||||
output_format: CliOutputFormat::Text,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
parse_args(&["/skills".to_string()]).expect("/skills should parse"),
|
||||
CliAction::Skills { args: None }
|
||||
CliAction::Skills {
|
||||
args: None,
|
||||
output_format: CliOutputFormat::Text,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
parse_args(&["/skills".to_string(), "help".to_string()])
|
||||
.expect("/skills help should parse"),
|
||||
CliAction::Skills {
|
||||
args: Some("help".to_string())
|
||||
args: Some("help".to_string()),
|
||||
output_format: CliOutputFormat::Text,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -6613,7 +6698,8 @@ mod tests {
|
||||
])
|
||||
.expect("/skills install should parse"),
|
||||
CliAction::Skills {
|
||||
args: Some("install ./fixtures/help-skill".to_string())
|
||||
args: Some("install ./fixtures/help-skill".to_string()),
|
||||
output_format: CliOutputFormat::Text,
|
||||
}
|
||||
);
|
||||
let error = parse_args(&["/status".to_string()])
|
||||
|
||||
Reference in New Issue
Block a user