diff --git a/crates/skills/src/commands.rs b/crates/skills/src/commands.rs index 60a0a8ff6..3572ab419 100644 --- a/crates/skills/src/commands.rs +++ b/crates/skills/src/commands.rs @@ -15,9 +15,40 @@ use crate::util::{self, pad_end, utf16_len, utf16_prefix}; use crate::{Flow, R}; /// JS: skills.mjs#run(args) +/// JS: skills.mjs#SUBCOMMAND_HELP (#708). Static help for a sub-command, so +/// `install --help` never enters an operational path. +const SUBCOMMAND_HELP: &[(&str, &str)] = &[ + ( + "install", + "Usage: impeccable install [options]\n\nInstall compiled Impeccable skills into project or user-level harness folders.\n\nOptions:\n -y, --yes Accept detected defaults without prompting\n --providers= Comma-separated harnesses to install\n --scope= Install scope: project or global\n --project Install into the current project\n --user, --global Install at the user level\n --no-hooks Install skills without provider hook manifests\n --force Replace an existing installation\n -h, --help Show this help message", + ), + ( + "link", + "Usage: impeccable link [options]\n\nLink Impeccable skills from a local checkout or submodule.\n\nOptions:\n --source= Source checkout (default: .impeccable)\n --providers= Comma-separated harnesses to link\n -y, --yes Accept detected defaults without prompting\n --force Replace existing skill folders with links\n -h, --help Show this help message", + ), + ( + "update", + "Usage: impeccable update [options]\n\nUpdate an existing Impeccable skill installation.\n\nOptions:\n -y, --yes Accept detected defaults without prompting\n --scope= Update scope: project or global\n --project Update the current project installation\n --user, --global Update the user-level installation\n --no-hooks Update skills without changing hook manifests\n --force Replace installed skill files\n -h, --help Show this help message", + ), + ( + "check", + "Usage: impeccable check [options]\n\nCheck whether installed Impeccable skills are up to date.\n\nOptions:\n -h, --help Show this help message", + ), +]; + +fn subcommand_help(sub: &str) -> Option<&'static str> { + SUBCOMMAND_HELP.iter().find(|(k, _)| *k == sub).map(|(_, v)| *v) +} + pub fn run(args: &[String], io: &mut Io) -> R<()> { let sub = args.first().map(String::as_str).unwrap_or(""); let rest: Vec = args.iter().skip(1).cloned().collect(); + if let Some(help) = subcommand_help(sub) { + if rest.iter().any(|a| a == "--help" || a == "-h") { + out(io, help); + return Ok(()); + } + } match sub { "" | "help" | "--help" | "-h" => show_help(io), "install" => install(&rest, io), diff --git a/tests/oracle/cases/skills.mjs b/tests/oracle/cases/skills.mjs new file mode 100644 index 000000000..e15db0cbb --- /dev/null +++ b/tests/oracle/cases/skills.mjs @@ -0,0 +1,17 @@ +/** + * The installer verbs' static help (`install`, `link`, `update`, `check`). + * + * Only the help paths are pinned here: every other installer path writes into + * harness directories or reaches the network, which the corpus keeps out. The + * help text must render before any operational path runs (#708), through both + * the top-level verb and the legacy `skills` namespace. + */ +export default [ + { id: 'skills-install-help', verb: 'install', args: ['--help'] }, + { id: 'skills-install-help-short', verb: 'install', args: ['-h'] }, + { id: 'skills-link-help', verb: 'link', args: ['--help'] }, + { id: 'skills-update-help', verb: 'update', args: ['--help'] }, + { id: 'skills-check-help', verb: 'check', args: ['--help'] }, + { id: 'skills-namespace-install-help', verb: 'skills', args: ['install', '--help'] }, + { id: 'skills-namespace-check-help-short', verb: 'skills', args: ['check', '-h'] }, +]; diff --git a/tests/oracle/golden/skills-check-help.json b/tests/oracle/golden/skills-check-help.json new file mode 100644 index 000000000..81fa2d10b --- /dev/null +++ b/tests/oracle/golden/skills-check-help.json @@ -0,0 +1,7 @@ +{ + "stdout": "Usage: impeccable check [options]\n\nCheck whether installed Impeccable skills are up to date.\n\nOptions:\n -h, --help Show this help message\n", + "stderr": "", + "exit": 0, + "signal": null, + "files": {} +} diff --git a/tests/oracle/golden/skills-install-help-short.json b/tests/oracle/golden/skills-install-help-short.json new file mode 100644 index 000000000..70262f7d9 --- /dev/null +++ b/tests/oracle/golden/skills-install-help-short.json @@ -0,0 +1,7 @@ +{ + "stdout": "Usage: impeccable install [options]\n\nInstall compiled Impeccable skills into project or user-level harness folders.\n\nOptions:\n -y, --yes Accept detected defaults without prompting\n --providers= Comma-separated harnesses to install\n --scope= Install scope: project or global\n --project Install into the current project\n --user, --global Install at the user level\n --no-hooks Install skills without provider hook manifests\n --force Replace an existing installation\n -h, --help Show this help message\n", + "stderr": "", + "exit": 0, + "signal": null, + "files": {} +} diff --git a/tests/oracle/golden/skills-install-help.json b/tests/oracle/golden/skills-install-help.json new file mode 100644 index 000000000..70262f7d9 --- /dev/null +++ b/tests/oracle/golden/skills-install-help.json @@ -0,0 +1,7 @@ +{ + "stdout": "Usage: impeccable install [options]\n\nInstall compiled Impeccable skills into project or user-level harness folders.\n\nOptions:\n -y, --yes Accept detected defaults without prompting\n --providers= Comma-separated harnesses to install\n --scope= Install scope: project or global\n --project Install into the current project\n --user, --global Install at the user level\n --no-hooks Install skills without provider hook manifests\n --force Replace an existing installation\n -h, --help Show this help message\n", + "stderr": "", + "exit": 0, + "signal": null, + "files": {} +} diff --git a/tests/oracle/golden/skills-link-help.json b/tests/oracle/golden/skills-link-help.json new file mode 100644 index 000000000..a11ed1a46 --- /dev/null +++ b/tests/oracle/golden/skills-link-help.json @@ -0,0 +1,7 @@ +{ + "stdout": "Usage: impeccable link [options]\n\nLink Impeccable skills from a local checkout or submodule.\n\nOptions:\n --source= Source checkout (default: .impeccable)\n --providers= Comma-separated harnesses to link\n -y, --yes Accept detected defaults without prompting\n --force Replace existing skill folders with links\n -h, --help Show this help message\n", + "stderr": "", + "exit": 0, + "signal": null, + "files": {} +} diff --git a/tests/oracle/golden/skills-namespace-check-help-short.json b/tests/oracle/golden/skills-namespace-check-help-short.json new file mode 100644 index 000000000..81fa2d10b --- /dev/null +++ b/tests/oracle/golden/skills-namespace-check-help-short.json @@ -0,0 +1,7 @@ +{ + "stdout": "Usage: impeccable check [options]\n\nCheck whether installed Impeccable skills are up to date.\n\nOptions:\n -h, --help Show this help message\n", + "stderr": "", + "exit": 0, + "signal": null, + "files": {} +} diff --git a/tests/oracle/golden/skills-namespace-install-help.json b/tests/oracle/golden/skills-namespace-install-help.json new file mode 100644 index 000000000..70262f7d9 --- /dev/null +++ b/tests/oracle/golden/skills-namespace-install-help.json @@ -0,0 +1,7 @@ +{ + "stdout": "Usage: impeccable install [options]\n\nInstall compiled Impeccable skills into project or user-level harness folders.\n\nOptions:\n -y, --yes Accept detected defaults without prompting\n --providers= Comma-separated harnesses to install\n --scope= Install scope: project or global\n --project Install into the current project\n --user, --global Install at the user level\n --no-hooks Install skills without provider hook manifests\n --force Replace an existing installation\n -h, --help Show this help message\n", + "stderr": "", + "exit": 0, + "signal": null, + "files": {} +} diff --git a/tests/oracle/golden/skills-update-help.json b/tests/oracle/golden/skills-update-help.json new file mode 100644 index 000000000..cd1b16cae --- /dev/null +++ b/tests/oracle/golden/skills-update-help.json @@ -0,0 +1,7 @@ +{ + "stdout": "Usage: impeccable update [options]\n\nUpdate an existing Impeccable skill installation.\n\nOptions:\n -y, --yes Accept detected defaults without prompting\n --scope= Update scope: project or global\n --project Update the current project installation\n --user, --global Update the user-level installation\n --no-hooks Update skills without changing hook manifests\n --force Replace installed skill files\n -h, --help Show this help message\n", + "stderr": "", + "exit": 0, + "signal": null, + "files": {} +}