Skip to content

Commit 7864e2e

Browse files
author
SqlRush
committed
Show marketplace sparse paths in CLI list
1 parent fbad8d4 commit 7864e2e

5 files changed

Lines changed: 63 additions & 21 deletions

File tree

cmd/claude/main.go

Lines changed: 54 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -318,13 +318,14 @@ type pluginCLIAvailableList struct {
318318
}
319319

320320
type pluginCLIMarketplaceEntry struct {
321-
Name string `json:"name"`
322-
Source string `json:"source,omitempty"`
323-
Repo string `json:"repo,omitempty"`
324-
URL string `json:"url,omitempty"`
325-
Path string `json:"path,omitempty"`
326-
Package string `json:"package,omitempty"`
327-
InstallLocation string `json:"installLocation,omitempty"`
321+
Name string `json:"name"`
322+
Source string `json:"source,omitempty"`
323+
Repo string `json:"repo,omitempty"`
324+
URL string `json:"url,omitempty"`
325+
Path string `json:"path,omitempty"`
326+
Package string `json:"package,omitempty"`
327+
SparsePaths []string `json:"sparsePaths,omitempty"`
328+
InstallLocation string `json:"installLocation,omitempty"`
328329
}
329330

330331
func runPluginCLI(ctx context.Context, state *bootstrap.State, args []string, stdout io.Writer, stderr io.Writer) int {
@@ -717,6 +718,9 @@ func runPluginMarketplaceListCLI(state *bootstrap.State, args []string, stdout i
717718
if source := pluginCLIMarketplaceSourceText(marketplace); source != "" {
718719
fmt.Fprintf(stdout, " Source: %s\n", source)
719720
}
721+
if len(marketplace.SparsePaths) > 0 {
722+
fmt.Fprintf(stdout, " Sparse paths: %s\n", strings.Join(marketplace.SparsePaths, ", "))
723+
}
720724
if marketplace.InstallLocation != "" {
721725
fmt.Fprintf(stdout, " Install location: %s\n", marketplace.InstallLocation)
722726
}
@@ -1184,7 +1188,11 @@ func pluginCLIMarketplaceEntries(settings contracts.Settings) []pluginCLIMarketp
11841188
switch sourceType {
11851189
case "github":
11861190
entry.Repo = pluginCLIStringFromMap(source, "repo")
1187-
case "git", "url":
1191+
entry.SparsePaths = pluginCLIStringSliceFromMap(source, "sparsePaths")
1192+
case "git":
1193+
entry.URL = pluginCLIStringFromMap(source, "url")
1194+
entry.SparsePaths = pluginCLIStringSliceFromMap(source, "sparsePaths")
1195+
case "url":
11881196
entry.URL = pluginCLIStringFromMap(source, "url")
11891197
case "directory", "file":
11901198
entry.Path = pluginCLIStringFromMap(source, "path")
@@ -1280,17 +1288,9 @@ func pluginCLINormalizeMarketplaceSourceArg(sourceType string, value string) (st
12801288
}
12811289

12821290
func compactPluginCLISparsePaths(paths []string) []any {
1291+
paths = compactPluginCLIStrings(paths)
12831292
out := make([]any, 0, len(paths))
1284-
seen := map[string]struct{}{}
12851293
for _, path := range paths {
1286-
path = strings.TrimSpace(path)
1287-
if path == "" {
1288-
continue
1289-
}
1290-
if _, ok := seen[path]; ok {
1291-
continue
1292-
}
1293-
seen[path] = struct{}{}
12941294
out = append(out, path)
12951295
}
12961296
return out
@@ -1353,6 +1353,43 @@ func pluginCLIStringFromMap(values map[string]any, key string) string {
13531353
return strings.TrimSpace(value)
13541354
}
13551355

1356+
func pluginCLIStringSliceFromMap(values map[string]any, key string) []string {
1357+
if len(values) == 0 {
1358+
return nil
1359+
}
1360+
switch raw := values[key].(type) {
1361+
case []string:
1362+
return compactPluginCLIStrings(raw)
1363+
case []any:
1364+
out := make([]string, 0, len(raw))
1365+
for _, item := range raw {
1366+
if value, ok := item.(string); ok {
1367+
out = append(out, value)
1368+
}
1369+
}
1370+
return compactPluginCLIStrings(out)
1371+
default:
1372+
return nil
1373+
}
1374+
}
1375+
1376+
func compactPluginCLIStrings(values []string) []string {
1377+
out := make([]string, 0, len(values))
1378+
seen := map[string]struct{}{}
1379+
for _, value := range values {
1380+
value = strings.TrimSpace(value)
1381+
if value == "" {
1382+
continue
1383+
}
1384+
if _, ok := seen[value]; ok {
1385+
continue
1386+
}
1387+
seen[value] = struct{}{}
1388+
out = append(out, value)
1389+
}
1390+
return out
1391+
}
1392+
13561393
func pluginCLIMCPServerNames(servers map[string]contracts.MCPServer) []string {
13571394
names := make([]string, 0, len(servers))
13581395
for name := range servers {

cmd/claude/main_test.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2782,7 +2782,7 @@ func TestRunPluginMarketplaceListCLI(t *testing.T) {
27822782
"installLocation": "project"
27832783
},
27842784
"remote": {"source": {"source": "url", "url": "https://example.com/catalog.json"}},
2785-
"github": {"source": {"source": "github", "repo": "owner/repo"}},
2785+
"github": {"source": {"source": "github", "repo": "owner/repo", "sparsePaths": [".claude-plugin", "plugins/demo"]}},
27862786
"npm-tools": {"source": {"source": "npm", "package": "@example/tools"}},
27872787
"file": {"source": {"source": "file", "path": %q}}
27882788
}
@@ -2817,6 +2817,10 @@ func TestRunPluginMarketplaceListCLI(t *testing.T) {
28172817
if byName["github"]["source"] != "github" || byName["github"]["repo"] != "owner/repo" {
28182818
t.Fatalf("github marketplace = %#v", byName["github"])
28192819
}
2820+
githubSparsePaths, ok := byName["github"]["sparsePaths"].([]any)
2821+
if !ok || len(githubSparsePaths) != 2 || githubSparsePaths[0] != ".claude-plugin" || githubSparsePaths[1] != "plugins/demo" {
2822+
t.Fatalf("github sparse paths = %#v", byName["github"]["sparsePaths"])
2823+
}
28202824
if byName["npm-tools"]["source"] != "npm" || byName["npm-tools"]["package"] != "@example/tools" {
28212825
t.Fatalf("npm marketplace = %#v", byName["npm-tools"])
28222826
}
@@ -2839,6 +2843,7 @@ func TestRunPluginMarketplaceListCLI(t *testing.T) {
28392843
"Source: URL (https://example.com/catalog.json)",
28402844
"- github",
28412845
"Source: GitHub (owner/repo)",
2846+
"Sparse paths: .claude-plugin, plugins/demo",
28422847
"- npm-tools",
28432848
"Source: NPM (@example/tools)",
28442849
} {

docs/cc-100-roadmap.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ M8 补充:CLI `plugin marketplace add` 现在支持官方 `--sparse <path>` re
5353

5454
M10 补充:plugin command/agent 的 allowed tool frontmatter 解析现在只在顶层逗号或空白处分隔,保留括号、方括号和引号内的逗号/空白,避免 `Bash(git commit -m "x,y")` 这类 tool pattern 被误拆。
5555

56-
M8 补充:CLI `plugin marketplace list` 现在可列出 settings 中已配置的 marketplace;`--json` 输出按名称排序的 source/repo/url/path/package/installLocation 结构,普通文本输出与无配置提示也已覆盖。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` 并覆盖基本参数冲突。
56+
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` 并覆盖基本参数冲突。
5757

5858
M10 补充:新增 `TaskOutput`/`AgentOutputTool``KillTask`/`TaskStop` 内置工具;`TaskOutput` 可列出当前 session 的 sidechain task,或按 task/sidechain ID 读取状态、summary、tail 输出和 agent metadata,`KillTask` 会通过 sidechain manager 写入 cancelled lifecycle summary。完整 AgentTool 执行循环、progress event streaming、resume command UI 和 worktree isolation 仍未完成。
5959

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ test/parity/ # golden tests against TS/official behavior
143143
- 本轮补充:`strictPluginOnlyCustomization` 已开始按 policy settings 生效,当前覆盖已实现的 `skills``mcp``hooks` 和 agent-metadata surfaces:锁定后 user/project/local skills 与 legacy commands 不加载,manual MCP server sources 不加载但 plugin/admin policy 仍保留,settings hooks 只保留 policy hooks 且 plugin hooks 继续运行;`agents` 锁定时非 plugin/bundled/admin prompt command 的 `agent``context: fork``effort` frontmatter metadata 会被清除。完整 forked agent runtime/UI edge cases 仍按缺口追踪。
144144
- 本轮补充:settings 文件读取现在有 path-keyed cache,按 size/mode/mtime 指纹复用内容并提供 `ResetSettingsCache`;新增 settings change detector,可对 settings 文件快照区分 created/modified/deleted,并在检测到变化时清空 settings 文件缓存;Runner turn-start 会在检测到 user/project/local settings 文件变化时重载 merged settings 并同步刷新 plugin MCP server app-state。完整后台 watcher 和 remote managed 非 daemon 后台 refresh 生命周期仍按缺口追踪。
145145
- 本轮补充:新增 settings JSON Schema generation,`SettingsJSONSchema()` 会从 `contracts.Settings` 反射生成 draft 2020-12 schema,补强 `$schema` const、permission mode enum、`strictPluginOnlyCustomization` union 和 login method enum;`/config show schema` 现在可展示 schema ID、draft、字段数和生成大小,完整 Zod message parity 仍按缺口追踪。
146-
- 本轮补充:settings validation 现在覆盖官方 marketplace source union 的基础语义,适用于 `extraKnownMarketplaces[].source`、`strictKnownMarketplaces[]` 和 `blockedMarketplaces[]`:校验 source discriminator、各 source 类型必填字段、URL source 绝对 URL、headers string record、github/git sparsePaths string array、settings-source plugins array/item path shape、settings-source name 安全/保留名,并继续覆盖 inline marketplace key/name 一致性,`extraKnownMarketplaces[].installLocation` 也会被约束为 `user|project|local`;`internal/plugins` 新增 marketplace policy resolver,blocked settings 优先、strict allowlist 非空时拒绝未列入来源,`/plugin marketplaces` 会展示 allow/block decision;本地 plugin manifest 可声明 `marketplace`/`marketplaceName`/`marketplace_name`/`source.name`,带 settings 的 plugin loader 会对这些来源执行 marketplace policy,`extraKnownMarketplaces.<name>.source.source=settings` 的 `plugins` 本地 root、`source=directory` 的本地 marketplace 目录、`source=file` catalog、`source=url` 远端 JSON catalog、`source=git` clone cache、`source=github` repo clone cache 以及 `source=npm` package cache 的 plugin roots 都会进入 plugin load path 并继承 marketplace 名称;URL catalog 支持配置 headers、大小限制、JSON 校验、写入 `marketplace-cache/catalogs` 并在后续请求失败时缓存回退;git/github source 会按 URL clone 到 `marketplace-cache/git`,后续加载 fetch/pull,支持 `ref` checkout 和 repo 内 `path` 指向单插件、插件目录或 catalog JSON,github `repo: owner/name` 会规范化为 GitHub clone URL;npm source 会用 `npm pack` 解包到 `marketplace-cache/npm`,失败时可从已有缓存回退,且支持 package 内 `path` 指向单插件、插件目录或 catalog JSON;`/plugin show/search` 会标注 blocked reason;运行时已安装插件发现会合并项目链 `.claude/plugins` 和用户级 `${CLAUDE_CONFIG_DIR}/plugins`,项目同名插件优先;headless `/plugin available [query]`、`/plugin marketplace plugins|search|show` 会浏览 configured marketplace 插件并标注 available/installed/update 状态;CLI `plugin list --json --available` 会输出 installed/available marketplace JSON,CLI `plugin marketplace list --json` 会列出已配置 marketplace 的 source/repo/url/path/package/installLocation 投影,CLI `plugin marketplace add/remove --scope user|project|local` 会按目标 scope 写入或删除 `extraKnownMarketplaces` 并复用 source validation,CLI `plugin marketplace update [name]` 会按全部或指定 marketplace 触发现有 cache 刷新路径,CLI `plugin install --scope project|user|local <plugin>` 与 headless `/plugin install [--scope project|user|local] <name>` 共享安装 API 并复制插件到目标 scope 的插件目录,不覆盖冲突路径并拒绝 symlink/non-regular 文件,未显式传 scope 时会尊重 marketplace `installLocation`;CLI `plugin update --scope project|user|local|all <plugin>` 与 headless `/plugin update [--scope project|user|local|all] [name]` 共享更新/目录替换 API,把目标 scope 已安装同名插件替换为最新 marketplace 副本,未显式传 scope 的命名更新同样会尊重 marketplace `installLocation`;CLI `plugin enable|disable [--scope user|project|local] <plugin>` 和 `plugin disable --all --scope ...` 共享 settings 文件写入 helper,按目标 scope 更新 `enabledPlugins`;headless install/update 后刷新 plugin MCP server app-state。完整 marketplace TUI/UI、后台自动更新和策略 lifecycle 仍按缺口追踪。
146+
- 本轮补充:settings validation 现在覆盖官方 marketplace source union 的基础语义,适用于 `extraKnownMarketplaces[].source`、`strictKnownMarketplaces[]` 和 `blockedMarketplaces[]`:校验 source discriminator、各 source 类型必填字段、URL source 绝对 URL、headers string record、github/git sparsePaths string array、settings-source plugins array/item path shape、settings-source name 安全/保留名,并继续覆盖 inline marketplace key/name 一致性,`extraKnownMarketplaces[].installLocation` 也会被约束为 `user|project|local`;`internal/plugins` 新增 marketplace policy resolver,blocked settings 优先、strict allowlist 非空时拒绝未列入来源,`/plugin marketplaces` 会展示 allow/block decision;本地 plugin manifest 可声明 `marketplace`/`marketplaceName`/`marketplace_name`/`source.name`,带 settings 的 plugin loader 会对这些来源执行 marketplace policy,`extraKnownMarketplaces.<name>.source.source=settings` 的 `plugins` 本地 root、`source=directory` 的本地 marketplace 目录、`source=file` catalog、`source=url` 远端 JSON catalog、`source=git` clone cache、`source=github` repo clone cache 以及 `source=npm` package cache 的 plugin roots 都会进入 plugin load path 并继承 marketplace 名称;URL catalog 支持配置 headers、大小限制、JSON 校验、写入 `marketplace-cache/catalogs` 并在后续请求失败时缓存回退;git/github source 会按 URL clone 到 `marketplace-cache/git`,后续加载 fetch/pull,支持 `ref` checkout 和 repo 内 `path` 指向单插件、插件目录或 catalog JSON,github `repo: owner/name` 会规范化为 GitHub clone URL;npm source 会用 `npm pack` 解包到 `marketplace-cache/npm`,失败时可从已有缓存回退,且支持 package 内 `path` 指向单插件、插件目录或 catalog JSON;`/plugin show/search` 会标注 blocked reason;运行时已安装插件发现会合并项目链 `.claude/plugins` 和用户级 `${CLAUDE_CONFIG_DIR}/plugins`,项目同名插件优先;headless `/plugin available [query]`、`/plugin marketplace plugins|search|show` 会浏览 configured marketplace 插件并标注 available/installed/update 状态;CLI `plugin list --json --available` 会输出 installed/available marketplace JSON,CLI `plugin marketplace list --json` 会列出已配置 marketplace 的 source/repo/url/path/package/sparsePaths/installLocation 投影,CLI `plugin marketplace add/remove --scope user|project|local` 会按目标 scope 写入或删除 `extraKnownMarketplaces` 并复用 source validation,CLI `plugin marketplace update [name]` 会按全部或指定 marketplace 触发现有 cache 刷新路径,CLI `plugin install --scope project|user|local <plugin>` 与 headless `/plugin install [--scope project|user|local] <name>` 共享安装 API 并复制插件到目标 scope 的插件目录,不覆盖冲突路径并拒绝 symlink/non-regular 文件,未显式传 scope 时会尊重 marketplace `installLocation`;CLI `plugin update --scope project|user|local|all <plugin>` 与 headless `/plugin update [--scope project|user|local|all] [name]` 共享更新/目录替换 API,把目标 scope 已安装同名插件替换为最新 marketplace 副本,未显式传 scope 的命名更新同样会尊重 marketplace `installLocation`;CLI `plugin enable|disable [--scope user|project|local] <plugin>` 和 `plugin disable --all --scope ...` 共享 settings 文件写入 helper,按目标 scope 更新 `enabledPlugins`;headless install/update 后刷新 plugin MCP server app-state。完整 marketplace TUI/UI、后台自动更新和策略 lifecycle 仍按缺口追踪。
147147

148148
### M4: Tool framework、permissions、sandbox
149149

0 commit comments

Comments
 (0)