diff --git a/ROADMAP.md b/ROADMAP.md index e2a76a17..fafa2200 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -7601,3 +7601,5 @@ Original filing (2026-04-18): the session emitted `SessionStart hook (completed) 716. **Resume-path error JSON used legacy `{type:"error", error:...}` shape instead of standard `{kind, action, status:"error", error_kind, exit_code}` envelope — 5 error paths affected** — dogfooded 2026-05-26 on `76c8d480`. Session load failure, unsupported command, unsupported resumed command, SlashCommand parse error, and broad-cwd abort all emitted the old two-key shape. Fix: aligned all 5 to `{kind, action:"resume"|"abort", status:"error", error_kind, error, exit_code}`. Updated `resumed_stub_command_emits_not_implemented_json` test to assert `status:"error"` + `kind:"unsupported_command"`. Source: Jobdori dogfood on `76c8d480`, 2026-05-26. 717. **`claw agents show ` missing — `handle_agents_slash_command_json` only accepted `list`; `show/info/describe` was unimplemented unlike skills which had parity** — dogfooded 2026-05-26 on `6a007344`. `claw agents show claw-code --output-format json` returned `unknown_agents_subcommand` error. Fix: added `show/info/describe` and `list ` arms to both `handle_agents_slash_command` and `handle_agents_slash_command_json`, mirroring the skills handler; renamed `render_agents_report_json` → `render_agents_report_json_with_action`; not-found path returns `{kind:"agents", action:"show", status:"error", error_kind:"agent_not_found", requested:""}` + Ok; added `classify_error_kind` branch for `agent_not_found`. Updated 2 tests. Source: Jobdori dogfood on `6a007344`, 2026-05-26. + +718. **`claw plugins show ` unimplemented — unlike `agents show` and `skills show`, `/plugins show` returned `unknown_plugins_action` error** — dogfooded 2026-05-26 on `8d80f2ff`. Fix: added `show/info/describe` arm to `handle_plugins_slash_command` that filters installed plugins by name; `print_plugins` JSON path filters `payload.plugins` when action is `show/info/describe` and emits `{kind:"plugin", action:"show", status:"error", error_kind:"plugin_not_found", requested:""}` for missing names. Updated error message in catch-all to name `show` as supported. Source: Jobdori dogfood on `8d80f2ff`, 2026-05-26. diff --git a/rust/crates/commands/src/lib.rs b/rust/crates/commands/src/lib.rs index 64620001..476ac569 100644 --- a/rust/crates/commands/src/lib.rs +++ b/rust/crates/commands/src/lib.rs @@ -2301,8 +2301,28 @@ pub fn handle_plugins_slash_command( reload_runtime: true, }) } + Some("show" | "info" | "describe") => { + // Show a named plugin by filtering the installed registry. + // Without a target, shows all (same as list). + let report = manager.installed_plugin_registry_report()?; + let plugins: Vec<_> = if let Some(name) = target { + let needle = name.to_lowercase(); + report + .summaries() + .into_iter() + .filter(|p| p.metadata.id.to_lowercase() == needle) + .collect() + } else { + report.summaries().into_iter().collect() + }; + let failures = report.failures(); + Ok(PluginsCommandResult { + message: render_plugins_report_with_failures(&plugins, failures), + reload_runtime: false, + }) + } Some(other) => Err(PluginError::CommandFailed(format!( - "unknown_plugins_action: '{other}' is not a supported /plugins action. Use list, install, enable, disable, uninstall, or update." + "unknown_plugins_action: '{other}' is not a supported /plugins action. Use list, show, install, enable, disable, uninstall, or update." ))), } } diff --git a/rust/crates/rusty-claude-cli/src/main.rs b/rust/crates/rusty-claude-cli/src/main.rs index f01b644b..7c577fc8 100644 --- a/rust/crates/rusty-claude-cli/src/main.rs +++ b/rust/crates/rusty-claude-cli/src/main.rs @@ -5999,28 +5999,65 @@ impl LiveCli { CliOutputFormat::Text => println!("{}", payload.message), CliOutputFormat::Json => { let action_str = action.unwrap_or("list"); - let enabled_count = payload - .plugins + // For show/info/describe, filter to the named plugin. + let is_show_action = matches!(action_str, "show" | "info" | "describe"); + let filtered_plugins: Vec<_> = if is_show_action { + if let Some(name) = target { + let needle = name.to_lowercase(); + payload + .plugins + .iter() + .filter(|p| { + p.get("id") + .and_then(|v| v.as_str()) + .map(|id| id.to_lowercase() == needle) + .unwrap_or(false) + }) + .cloned() + .collect() + } else { + payload.plugins.clone() + } + } else { + payload.plugins.clone() + }; + // Return not-found error for show with missing target. + if is_show_action { + if let Some(name) = target { + if filtered_plugins.is_empty() { + let obj = json!({ + "kind": "plugin", + "action": action_str, + "status": "error", + "error_kind": "plugin_not_found", + "requested": name, + }); + println!("{}", serde_json::to_string_pretty(&obj)?); + return Ok(()); + } + } + } + let enabled_count = filtered_plugins .iter() .filter(|p| p.get("enabled").and_then(|v| v.as_bool()).unwrap_or(false)) .count(); - let disabled_count = payload.plugins.len().saturating_sub(enabled_count); + let disabled_count = filtered_plugins.len().saturating_sub(enabled_count); let mut obj = json!({ "kind": "plugin", "action": action_str, "status": payload.status, "summary": { - "total": payload.plugins.len(), + "total": filtered_plugins.len(), "enabled": enabled_count, "disabled": disabled_count, "load_failures": payload.load_failures.len(), }, "config_load_error": payload.config_load_error, - "plugins": payload.plugins, + "plugins": filtered_plugins, "load_failures": payload.load_failures, }); - // Only include operation-result fields for mutating actions - if action_str != "list" { + // Only include operation-result fields for mutating actions (not list/show) + if action_str != "list" && !is_show_action { obj["target"] = json!(target); obj["reload_runtime"] = json!(payload.reload_runtime); obj["message"] = json!(payload.message);