skillhub/cli/src/commands/help.ts
dongmucat f7ab8f4db7 feat(cli): add --scope option to install command
- Distinguish user vs project install scope via explicit --scope flag
- Interactive mode prompts for scope when --scope/--agent/--dir not provided
- Non-interactive bare install preserves existing behavior (backward compatible)
- Mutual exclusion: --dir cannot be combined with --scope or --agent
- Symmetric fallback: --scope user falls back to ~/.agents/skills,
  --scope project falls back to <cwd>/.agents/skills
- Strict TTY check requires both stdin and stdout TTY plus no --json
- Scope-aware candidate generation avoids root.startsWith(cwd) misjudgement
  when cwd === home or paths overlap
- Correct gemini-cli (.gemini/skills) and kiro-cli (.kiro/skills) paths
  in install path tables across README and guide docs
- Note CLI fallback uses .agents/skills (with s) in skill protocol doc
2026-05-15 14:43:25 +08:00

98 lines
3.7 KiB
TypeScript

import { printResult } from '../shared/output'
export const commands = {
help: {
summary: 'Show available commands',
usage: 'skillhub help [command] [--json]',
examples: ['skillhub help', 'skillhub help install', 'skillhub help --json']
},
version: {
summary: 'Show installed CLI version',
usage: 'skillhub version [--json]',
examples: ['skillhub version', 'skillhub version --json']
},
login: {
summary: 'Save registry and token',
usage: 'skillhub login [--token <token>] [--registry <url>] [--json]',
examples: ['skillhub login --token sk_xxx', 'skillhub login --registry https://skillhub.example.com']
},
logout: {
summary: 'Remove local token',
usage: 'skillhub logout [--registry <url>] [--json]',
examples: ['skillhub logout']
},
whoami: {
summary: 'Verify current token',
usage: 'skillhub whoami [--token <token>] [--registry <url>] [--json]',
examples: ['skillhub whoami', 'skillhub whoami --json']
},
search: {
summary: 'Search published skills',
usage: 'skillhub search [query] [--limit <n>] [--registry <url>] [--json]',
examples: ['skillhub search', 'skillhub search pdf']
},
install: {
summary: 'Install a skill locally',
usage: 'skillhub install <slug> [--scope <user|project>] [--namespace <slug>] [--version <v>] [--agent <profile>] [--dir <path>] [--force] [--json]',
examples: [
'skillhub install pdf-parser',
'skillhub install pdf-parser --scope user',
'skillhub install pdf-parser --scope project --agent codex'
]
},
list: {
summary: 'List local installs',
usage: 'skillhub list [--agent <profile>] [--dir <path>] [--registry <url>] [--json]',
examples: ['skillhub list', 'skillhub list --agent codex']
},
remove: {
summary: 'Remove local or remote skill',
usage: 'skillhub remove <slug> [--agent <profile>] [--all] [--remote] [--hard] [--namespace <slug>] [--json]',
examples: ['skillhub remove pdf-parser', 'skillhub remove pdf-parser --remote --hard']
},
doctor: {
summary: 'Scan project and merge into local inventory (preserves entries outside scan scope)',
usage: 'skillhub doctor [--json]',
examples: ['skillhub doctor', 'skillhub doctor --json']
},
publish: {
summary: 'Publish a local skill package',
usage: 'skillhub publish <path> [--namespace <slug>] [--visibility <public|namespace-only|private>] [--registry <url>] [--json]',
examples: ['skillhub publish ./my-skill', 'skillhub publish ./my-skill --namespace myspace']
},
update: {
summary: 'Check or update CLI itself',
usage: 'skillhub update [--check] [--json]',
examples: ['skillhub update --check', 'skillhub update']
}
} as const
export function formatCommandList(): string {
return Object.entries(commands).map(([name, detail]) => `${name.padEnd(10)} ${detail.summary}`).join('\n')
}
export async function helpCommand(args: string[]): Promise<string> {
const json = args.includes('--json')
const topic = args.find(arg => !arg.startsWith('--'))
if (json) {
if (topic) {
// TODO: unknown topic returns undefined and crashes on detail.usage; see help-command.test.ts
const detail = commands[topic as keyof typeof commands]
return printResult({ ok: true, command: topic, ...detail }, true)
}
return printResult({
ok: true,
commands: Object.entries(commands).map(([name, detail]) => ({ name, description: detail.summary }))
}, true)
}
if (topic) {
const detail = commands[topic as keyof typeof commands]
return [
`${topic} - ${detail.summary}`,
`Usage: ${detail.usage}`,
'Examples:',
...detail.examples.map(example => ` ${example}`)
].join('\n')
}
return formatCommandList()
}