Skip to content

Commit 53cb2bb

Browse files
author
SqlRush
committed
Report plugin CLI usage for unknown commands
1 parent ac0c53d commit 53cb2bb

4 files changed

Lines changed: 104 additions & 2 deletions

File tree

cmd/claude/main.go

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -335,9 +335,13 @@ func runPluginCLI(ctx context.Context, state *bootstrap.State, args []string, st
335335
_ = ctx
336336
if len(args) == 0 {
337337
fmt.Fprintln(stderr, "ccgo plugin: missing subcommand")
338+
fmt.Fprintln(stderr, pluginCLIUsage())
338339
return 2
339340
}
340341
switch strings.ToLower(strings.TrimSpace(args[0])) {
342+
case "help", "-h", "--help":
343+
fmt.Fprintln(stdout, pluginCLIUsage())
344+
return 0
341345
case "list", "ls":
342346
return runPluginListCLI(state, args[1:], stdout, stderr)
343347
case "install", "i":
@@ -355,11 +359,16 @@ func runPluginCLI(ctx context.Context, state *bootstrap.State, args []string, st
355359
case "marketplace", "marketplaces":
356360
return runPluginMarketplaceCLI(state, args[1:], stdout, stderr)
357361
default:
358-
fmt.Fprintf(stderr, "ccgo plugin: unsupported subcommand %s\n", args[0])
362+
fmt.Fprintf(stderr, "ccgo plugin: unknown subcommand %s\n", args[0])
363+
fmt.Fprintln(stderr, pluginCLIUsage())
359364
return 2
360365
}
361366
}
362367

368+
func pluginCLIUsage() string {
369+
return "Usage: claude plugin <list|install|update|uninstall|validate|enable|disable|marketplace>"
370+
}
371+
363372
func runPluginListCLI(state *bootstrap.State, args []string, stdout io.Writer, stderr io.Writer) int {
364373
flags := flag.NewFlagSet("claude plugin list", flag.ContinueOnError)
365374
flags.SetOutput(stderr)
@@ -665,9 +674,13 @@ func pluralWord(count int, singular string, plural string) string {
665674
func runPluginMarketplaceCLI(state *bootstrap.State, args []string, stdout io.Writer, stderr io.Writer) int {
666675
if len(args) == 0 {
667676
fmt.Fprintln(stderr, "ccgo plugin marketplace: missing subcommand")
677+
fmt.Fprintln(stderr, pluginMarketplaceCLIUsage())
668678
return 2
669679
}
670680
switch strings.ToLower(strings.TrimSpace(args[0])) {
681+
case "help", "-h", "--help":
682+
fmt.Fprintln(stdout, pluginMarketplaceCLIUsage())
683+
return 0
671684
case "add":
672685
return runPluginMarketplaceAddCLI(state, args[1:], stdout, stderr)
673686
case "list", "ls":
@@ -681,11 +694,16 @@ func runPluginMarketplaceCLI(state *bootstrap.State, args []string, stdout io.Wr
681694
case "show", "info":
682695
return runPluginMarketplaceShowCLI(state, args[1:], stdout, stderr)
683696
default:
684-
fmt.Fprintf(stderr, "ccgo plugin marketplace: unsupported subcommand %s\n", args[0])
697+
fmt.Fprintf(stderr, "ccgo plugin marketplace: unknown subcommand %s\n", args[0])
698+
fmt.Fprintln(stderr, pluginMarketplaceCLIUsage())
685699
return 2
686700
}
687701
}
688702

703+
func pluginMarketplaceCLIUsage() string {
704+
return "Usage: claude plugin marketplace <list|add|remove|update|plugins|show>"
705+
}
706+
689707
func runPluginMarketplaceListCLI(state *bootstrap.State, args []string, stdout io.Writer, stderr io.Writer) int {
690708
flags := flag.NewFlagSet("claude plugin marketplace list", flag.ContinueOnError)
691709
flags.SetOutput(stderr)

cmd/claude/main_test.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2198,6 +2198,87 @@ func TestRunPrintStreamJSONOutput(t *testing.T) {
21982198
}
21992199
}
22002200

2201+
func TestRunPluginCLIUsageForHelpAndUnknown(t *testing.T) {
2202+
configHome := t.TempDir()
2203+
t.Setenv("CLAUDE_CONFIG_DIR", configHome)
2204+
project := t.TempDir()
2205+
2206+
tests := []struct {
2207+
name string
2208+
args []string
2209+
wantCode int
2210+
wantStdout []string
2211+
wantStderr []string
2212+
notStderr []string
2213+
}{
2214+
{
2215+
name: "plugin help",
2216+
args: []string{"--cwd", project, "plugin", "help"},
2217+
wantCode: 0,
2218+
wantStdout: []string{"Usage: claude plugin <list|install|update|uninstall|validate|enable|disable|marketplace>"},
2219+
},
2220+
{
2221+
name: "plugin missing",
2222+
args: []string{"--cwd", project, "plugin"},
2223+
wantCode: 2,
2224+
wantStderr: []string{"ccgo plugin: missing subcommand", "Usage: claude plugin <list|install|update|uninstall|validate|enable|disable|marketplace>"},
2225+
notStderr: []string{"unsupported"},
2226+
},
2227+
{
2228+
name: "plugin unknown",
2229+
args: []string{"--cwd", project, "plugin", "nope"},
2230+
wantCode: 2,
2231+
wantStderr: []string{"ccgo plugin: unknown subcommand nope", "Usage: claude plugin <list|install|update|uninstall|validate|enable|disable|marketplace>"},
2232+
notStderr: []string{"unsupported"},
2233+
},
2234+
{
2235+
name: "marketplace help",
2236+
args: []string{"--cwd", project, "plugin", "marketplace", "help"},
2237+
wantCode: 0,
2238+
wantStdout: []string{"Usage: claude plugin marketplace <list|add|remove|update|plugins|show>"},
2239+
},
2240+
{
2241+
name: "marketplace missing",
2242+
args: []string{"--cwd", project, "plugin", "marketplace"},
2243+
wantCode: 2,
2244+
wantStderr: []string{"ccgo plugin marketplace: missing subcommand", "Usage: claude plugin marketplace <list|add|remove|update|plugins|show>"},
2245+
notStderr: []string{"unsupported"},
2246+
},
2247+
{
2248+
name: "marketplace unknown",
2249+
args: []string{"--cwd", project, "plugin", "marketplace", "nope"},
2250+
wantCode: 2,
2251+
wantStderr: []string{"ccgo plugin marketplace: unknown subcommand nope", "Usage: claude plugin marketplace <list|add|remove|update|plugins|show>"},
2252+
notStderr: []string{"unsupported"},
2253+
},
2254+
}
2255+
2256+
for _, tc := range tests {
2257+
t.Run(tc.name, func(t *testing.T) {
2258+
var stdout, stderr bytes.Buffer
2259+
code := run(tc.args, strings.NewReader(""), &stdout, &stderr)
2260+
if code != tc.wantCode {
2261+
t.Fatalf("exit=%d stdout=%q stderr=%q", code, stdout.String(), stderr.String())
2262+
}
2263+
for _, want := range tc.wantStdout {
2264+
if !strings.Contains(stdout.String(), want) {
2265+
t.Fatalf("stdout missing %q: %q", want, stdout.String())
2266+
}
2267+
}
2268+
for _, want := range tc.wantStderr {
2269+
if !strings.Contains(stderr.String(), want) {
2270+
t.Fatalf("stderr missing %q: %q", want, stderr.String())
2271+
}
2272+
}
2273+
for _, unwanted := range tc.notStderr {
2274+
if strings.Contains(stderr.String(), unwanted) {
2275+
t.Fatalf("stderr contains %q: %q", unwanted, stderr.String())
2276+
}
2277+
}
2278+
})
2279+
}
2280+
}
2281+
22012282
func TestRunPluginListJSONAvailable(t *testing.T) {
22022283
configHome := t.TempDir()
22032284
t.Setenv("CLAUDE_CONFIG_DIR", configHome)

docs/cc-100-roadmap.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ M8 补充:CLI `plugin list` 现在会对 installed plugin roots 做显式加
5151

5252
M8 补充:CLI `plugin marketplace add` 现在支持官方 `--sparse <path>` repeat 参数,会把 github/git marketplace source 写入 `sparsePaths`,并拒绝对 url/npm/directory/file source 使用 sparse。
5353

54+
M8 补充:CLI `plugin help|-h|--help``plugin marketplace help|-h|--help` 现在输出稳定 usage;缺少子命令或未知子命令会返回 exit 2 并显示 usage,不再落入 `unsupported subcommand` 占位文案。
55+
5456
M10 补充:plugin command/agent 的 allowed tool frontmatter 解析现在只在顶层逗号或空白处分隔,保留括号、方括号和引号内的逗号/空白,避免 `Bash(git commit -m "x,y")` 这类 tool pattern 被误拆。
5557

5658
M8 补充:CLI `plugin marketplace list` 现在可列出 settings 中已配置的 marketplace;`--json` 输出按名称排序的 source/repo/url/path/package/sparsePaths/installLocation 结构,普通文本输出与无配置提示也已覆盖,并会显示 github/git sparse paths。CLI `plugin marketplace add [--scope user|project|local] [--type ...] <name> <source>` 和 `plugin marketplace remove [--scope user|project|local] <name>` 现在复用 `internal/config` settings 文件写入 helper,可按目标 scope 写入/删除 `extraKnownMarketplaces`,并在写入前复用 marketplace source validation,且 `installLocation` 会校验为 `user|project|local`。CLI `plugin marketplace update [name]` 现在可按全部或指定 marketplace 触发现有 URL/git/github/npm/settings cache 加载刷新路径,命名 update 会用轻量 settings 读取避免启动时提前刷新全部 marketplace。运行时插件发现现在会合并项目链 `.claude/plugins` 和用户级 `${CLAUDE_CONFIG_DIR}/plugins`,项目同名插件优先。CLI `plugin install --scope project|user|local <plugin>` 与 headless `/plugin install [--scope project|user|local] <plugin>` 现在复用 `internal/plugins` 共享安装 API,把 marketplace 插件复制到目标 scope 的 `plugins/<safe-name>`,未显式传 scope 时会尊重 marketplace `installLocation` 默认值,并保留冲突检测、重复安装识别和 symlink/non-regular file 拒绝。CLI `plugin update --scope project|user|local|all <plugin>` 与 headless `/plugin update [--scope project|user|local|all] [plugin]` 现在复用 `internal/plugins` 共享更新 API,把目标 scope 已安装同名插件替换为最新 marketplace 副本,未显式传 scope 的命名更新同样会尊重 marketplace `installLocation`。CLI `plugin enable|disable [--scope user|project|local] <plugin>` 与 `plugin disable --all --scope ...` 现在复用 `internal/config` settings 文件写入 helper,按目标 scope 更新 `enabledPlugins` 并覆盖基本参数冲突。

docs/claude-code-go-rewrite-plan.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -911,6 +911,7 @@ test/parity/ # golden tests against TS/official behavior
911911
- 本轮补充:CLI `plugin list` 现在会显式展示 installed plugin root 的加载错误,坏 manifest 会作为 `failed to load` entry 出现在文本输出中,JSON 输出也会带 `errors`,不再被 `LoadPluginDirs` 静默跳过。
912912
- 本轮补充:CLI `plugin marketplace add` 现在支持官方 `--sparse <path>` repeat 参数,仅允许 github/git marketplace source 使用,并写入 settings source 的 `sparsePaths`
913913
- 本轮补充:CLI `plugin marketplace show|info [--json] <plugin>` 现在可查看 configured marketplace 中单个插件详情,支持 name、`name@marketplace``name@version` 精确匹配,并输出 marketplace、version、source、description、installed version/path 和 available/installed/update 状态。
914+
- 本轮补充:CLI `plugin help|-h|--help``plugin marketplace help|-h|--help` 现在输出稳定 usage;缺少子命令或未知子命令返回 exit 2 并展示 usage,不再输出 `unsupported subcommand` 占位文案。
914915

915916
- 本轮补充:bridge-safe 内置本地命令 `/summary``/release-notes``/files` 现在已注册并接入 no-query runner 路径;`/summary` 输出确定性的会话/历史消息计数、工具使用计数、估算 token 和最近用户/助手预览,`/files` 只读列出当前工作目录第一层条目而不读取文件内容,`/release-notes` 明确报告当前 Go runtime 未打包 release notes。完整 local-jsx UI surface 和其它本地命令 parity 仍需继续补。
916917

0 commit comments

Comments
 (0)