Skip to content

Commit 1b68c7c

Browse files
author
SqlRush
committed
Add marketplace plugin show CLI
1 parent d5900a4 commit 1b68c7c

4 files changed

Lines changed: 155 additions & 0 deletions

File tree

cmd/claude/main.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -678,6 +678,8 @@ func runPluginMarketplaceCLI(state *bootstrap.State, args []string, stdout io.Wr
678678
return runPluginMarketplaceUpdateCLI(state, args[1:], stdout, stderr)
679679
case "plugins", "available", "browse", "discover", "search", "find":
680680
return runPluginMarketplacePluginsCLI(state, args[1:], stdout, stderr)
681+
case "show", "info":
682+
return runPluginMarketplaceShowCLI(state, args[1:], stdout, stderr)
681683
default:
682684
fmt.Fprintf(stderr, "ccgo plugin marketplace: unsupported subcommand %s\n", args[0])
683685
return 2
@@ -946,6 +948,82 @@ func runPluginMarketplacePluginsCLI(state *bootstrap.State, args []string, stdou
946948
return 0
947949
}
948950

951+
func runPluginMarketplaceShowCLI(state *bootstrap.State, args []string, stdout io.Writer, stderr io.Writer) int {
952+
flags := flag.NewFlagSet("claude plugin marketplace show", flag.ContinueOnError)
953+
flags.SetOutput(stderr)
954+
jsonOutput := flags.Bool("json", false, "output JSON")
955+
if err := flags.Parse(args); err != nil {
956+
if err == flag.ErrHelp {
957+
return 0
958+
}
959+
return 2
960+
}
961+
name := strings.TrimSpace(strings.Join(flags.Args(), " "))
962+
if name == "" {
963+
fmt.Fprintln(stderr, "ccgo plugin marketplace show: usage: claude plugin marketplace show [--json] <plugin>")
964+
return 2
965+
}
966+
runner, err := state.ConversationRunner()
967+
if err != nil {
968+
fmt.Fprintf(stderr, "ccgo plugin marketplace show: %v\n", err)
969+
return 1
970+
}
971+
settings := runnerMergedSettings(runner)
972+
marketplacePlugins := pluginpkg.LoadMarketplacePluginDirsWithSettings(settings)
973+
installedPlugins := pluginpkg.LoadPluginDirs(pluginpkg.InstalledPluginDirs(runner.WorkingDirectory))
974+
entries := pluginCLIMarketplacePluginEntries(marketplacePlugins, installedPlugins, "")
975+
entry, ok := pluginCLIMarketplaceFindEntry(entries, name)
976+
if !ok {
977+
fmt.Fprintf(stderr, "ccgo plugin marketplace show: plugin %q not found in configured marketplaces\n", name)
978+
return 1
979+
}
980+
if *jsonOutput {
981+
encoder := json.NewEncoder(stdout)
982+
encoder.SetIndent("", " ")
983+
if err := encoder.Encode(entry); err != nil {
984+
fmt.Fprintf(stderr, "ccgo plugin marketplace show: %v\n", err)
985+
return 1
986+
}
987+
return 0
988+
}
989+
writePluginMarketplaceShowResult(stdout, entry)
990+
return 0
991+
}
992+
993+
func writePluginMarketplaceShowResult(stdout io.Writer, plugin pluginCLIAvailableEntry) {
994+
status := plugin.Status
995+
if status == "" {
996+
status = "available"
997+
}
998+
lines := []string{
999+
"Marketplace plugin",
1000+
"Name: " + plugin.Name,
1001+
}
1002+
if plugin.PluginID != "" {
1003+
lines = append(lines, "Plugin ID: "+plugin.PluginID)
1004+
}
1005+
if plugin.MarketplaceName != "" {
1006+
lines = append(lines, "Marketplace: "+plugin.MarketplaceName)
1007+
}
1008+
if plugin.Version != "" {
1009+
lines = append(lines, "Version: "+plugin.Version)
1010+
}
1011+
lines = append(lines, "Status: "+status)
1012+
if plugin.InstalledVersion != "" {
1013+
lines = append(lines, "Installed version: "+plugin.InstalledVersion)
1014+
}
1015+
if plugin.InstallPath != "" {
1016+
lines = append(lines, "Installed path: "+plugin.InstallPath)
1017+
}
1018+
if plugin.Source != "" {
1019+
lines = append(lines, "Source: "+plugin.Source)
1020+
}
1021+
if description := strings.TrimSpace(plugin.Description); description != "" {
1022+
lines = append(lines, "Description: "+description)
1023+
}
1024+
fmt.Fprintln(stdout, strings.Join(lines, "\n"))
1025+
}
1026+
9491027
func writePluginInstallResult(stdout io.Writer, result pluginpkg.PluginInstallResult) {
9501028
lines := []string{
9511029
"Plugin installed",
@@ -1258,6 +1336,37 @@ func pluginCLIMarketplacePluginEntries(marketplacePlugins []pluginpkg.LoadedPlug
12581336
return out
12591337
}
12601338

1339+
func pluginCLIMarketplaceFindEntry(entries []pluginCLIAvailableEntry, name string) (pluginCLIAvailableEntry, bool) {
1340+
name = strings.TrimSpace(name)
1341+
if name == "" {
1342+
return pluginCLIAvailableEntry{}, false
1343+
}
1344+
for _, entry := range entries {
1345+
for _, candidate := range pluginCLIMarketplaceEntryNames(entry) {
1346+
if strings.EqualFold(candidate, name) {
1347+
return entry, true
1348+
}
1349+
}
1350+
}
1351+
return pluginCLIAvailableEntry{}, false
1352+
}
1353+
1354+
func pluginCLIMarketplaceEntryNames(entry pluginCLIAvailableEntry) []string {
1355+
var names []string
1356+
for _, candidate := range []string{
1357+
entry.PluginID,
1358+
entry.Name,
1359+
entry.Name + "@" + entry.MarketplaceName,
1360+
entry.Name + "@" + entry.Version,
1361+
} {
1362+
candidate = strings.TrimSpace(strings.Trim(candidate, "@"))
1363+
if candidate != "" {
1364+
names = append(names, candidate)
1365+
}
1366+
}
1367+
return names
1368+
}
1369+
12611370
func pluginCLIMarketplaceEntryMatches(entry pluginCLIAvailableEntry, query string) bool {
12621371
fields := []string{
12631372
entry.PluginID,

cmd/claude/main_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2352,6 +2352,49 @@ func TestRunPluginListJSONAvailable(t *testing.T) {
23522352
if len(marketplacePayload) != 1 || marketplacePayload[0]["pluginId"] != "lint tool@team" || marketplacePayload[0]["status"] != "available" {
23532353
t.Fatalf("marketplace json = %#v", marketplacePayload)
23542354
}
2355+
2356+
stdout.Reset()
2357+
stderr.Reset()
2358+
code = run([]string{"--cwd", project, "plugin", "marketplace", "show", "market", "demo"}, strings.NewReader(""), &stdout, &stderr)
2359+
if code != 0 {
2360+
t.Fatalf("marketplace show exit=%d stdout=%q stderr=%q", code, stdout.String(), stderr.String())
2361+
}
2362+
for _, want := range []string{
2363+
"Marketplace plugin",
2364+
"Name: market demo",
2365+
"Plugin ID: market demo@team",
2366+
"Marketplace: team",
2367+
"Version: 2.0.0",
2368+
"Status: update available",
2369+
"Installed version: 1.0.0",
2370+
"Installed path: " + expectedInstalledDir,
2371+
"Description: Deploy marketplace plugin",
2372+
} {
2373+
if !strings.Contains(stdout.String(), want) {
2374+
t.Fatalf("marketplace show missing %q: %q", want, stdout.String())
2375+
}
2376+
}
2377+
2378+
stdout.Reset()
2379+
stderr.Reset()
2380+
code = run([]string{"--cwd", project, "plugin", "marketplace", "info", "--json", "lint tool@team"}, strings.NewReader(""), &stdout, &stderr)
2381+
if code != 0 {
2382+
t.Fatalf("marketplace info json exit=%d stdout=%q stderr=%q", code, stdout.String(), stderr.String())
2383+
}
2384+
var marketplaceInfo map[string]any
2385+
if err := json.Unmarshal(stdout.Bytes(), &marketplaceInfo); err != nil {
2386+
t.Fatalf("invalid marketplace info json stdout %q: %v", stdout.String(), err)
2387+
}
2388+
if marketplaceInfo["pluginId"] != "lint tool@team" || marketplaceInfo["name"] != "lint tool" || marketplaceInfo["status"] != "available" {
2389+
t.Fatalf("marketplace info json = %#v", marketplaceInfo)
2390+
}
2391+
2392+
stdout.Reset()
2393+
stderr.Reset()
2394+
code = run([]string{"--cwd", project, "plugin", "marketplace", "show", "missing"}, strings.NewReader(""), &stdout, &stderr)
2395+
if code != 1 || !strings.Contains(stderr.String(), `plugin "missing" not found`) {
2396+
t.Fatalf("marketplace missing exit=%d stdout=%q stderr=%q", code, stdout.String(), stderr.String())
2397+
}
23552398
}
23562399

23572400
func TestRunPluginValidateCLI(t *testing.T) {

docs/cc-100-roadmap.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,8 @@ M8/CLI 补充:`claude plugin marketplace refresh` 与 `reload` 现在作为 `u
325325

326326
M8/CLI 补充:`claude plugin marketplace plugins|available|browse|discover|search|find [--json] [query]` 现在可直接浏览 configured marketplace 插件,文本与 JSON 输出都会标注 marketplace、version、description 和 available/installed/update-available 状态,复用当前 settings marketplace loader 和本地 installed plugin detection。
327327

328+
M8/CLI 补充:`claude plugin marketplace show|info [--json] <plugin>` 现在可查看 configured marketplace 中单个插件详情,支持 name、`name@marketplace``name@version` 精确匹配,并在文本/JSON 中输出 marketplace、version、source、description、installed version/path 和 available/installed/update 状态。
329+
328330
M8 补充:headless `/plugin list|status` 现在会按 `enabledPlugins` 的 true/false 统计 enabled 数量并列出 enabled/disabled/configured 状态明细,方便验证本地 plugin lifecycle 状态。
329331

330332
M8 补充:`enabledPlugins` 的 disabled 状态现在会过滤本地 plugin manifest 产生的 slash command、Skill tool prompt、agent/MCP/output-style/status/stream-init metadata,`/plugin disable <name>` 写入配置后不再只影响展示层;marketplace 下载/安装、版本解析、动态 refresh lifecycle 和完整 UI 管理仍待补齐。

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -905,6 +905,7 @@ test/parity/ # golden tests against TS/official behavior
905905
- 本轮补充:CLI `plugin uninstall|remove|rm [--scope user|project|local] [--keep-data] <plugin>` 现在复用共享安全卸载 API,默认 user scope,支持 remove/rm alias 和 `--keep-data` 兼容参数。
906906
- 本轮补充:CLI `plugin list` 现在会显式展示 installed plugin root 的加载错误,坏 manifest 会作为 `failed to load` entry 出现在文本输出中,JSON 输出也会带 `errors`,不再被 `LoadPluginDirs` 静默跳过。
907907
- 本轮补充:CLI `plugin marketplace add` 现在支持官方 `--sparse <path>` repeat 参数,仅允许 github/git marketplace source 使用,并写入 settings source 的 `sparsePaths`
908+
- 本轮补充:CLI `plugin marketplace show|info [--json] <plugin>` 现在可查看 configured marketplace 中单个插件详情,支持 name、`name@marketplace``name@version` 精确匹配,并输出 marketplace、version、source、description、installed version/path 和 available/installed/update 状态。
908909

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

0 commit comments

Comments
 (0)