Skip to content

Commit afa974e

Browse files
author
SqlRush
committed
Scope plugin toggles in CLI
1 parent 8d0c873 commit afa974e

6 files changed

Lines changed: 82 additions & 18 deletions

File tree

cmd/claude/main.go

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -510,24 +510,25 @@ func runPluginSetEnabledCLI(state *bootstrap.State, action string, args []string
510510
if scope == "" {
511511
scope = "user"
512512
}
513-
if scope != "user" {
514-
fmt.Fprintf(stderr, "ccgo plugin %s: scope %q is not supported yet; use user\n", action, scope)
513+
settingsPath, err := pluginCLISettingsPathForScope(state, scope)
514+
if err != nil {
515+
fmt.Fprintf(stderr, "ccgo plugin %s: %v\n", action, err)
515516
return 2
516517
}
517518
if all {
518519
if flags.NArg() > 0 {
519520
fmt.Fprintln(stderr, "ccgo plugin disable: cannot use --all with a specific plugin")
520521
return 2
521522
}
522-
return runPluginDisableAllCLI(state, stdout, stderr)
523+
return runPluginDisableAllCLI(state, settingsPath, stdout, stderr)
523524
}
524525
if flags.NArg() != 1 {
525-
fmt.Fprintf(stderr, "ccgo plugin %s: usage: claude plugin %s [--scope user] <plugin>\n", action, action)
526+
fmt.Fprintf(stderr, "ccgo plugin %s: usage: claude plugin %s [--scope user|project|local] <plugin>\n", action, action)
526527
return 2
527528
}
528529
name := strings.TrimSpace(flags.Arg(0))
529530
enabled := action == "enable"
530-
if err := config.SetUserPluginEnabled(name, enabled); err != nil {
531+
if err := config.SetPluginEnabledInSettingsFile(settingsPath, name, enabled); err != nil {
531532
fmt.Fprintf(stderr, "ccgo plugin %s: %v\n", action, err)
532533
return 1
533534
}
@@ -539,7 +540,7 @@ func runPluginSetEnabledCLI(state *bootstrap.State, action string, args []string
539540
return 0
540541
}
541542

542-
func runPluginDisableAllCLI(state *bootstrap.State, stdout io.Writer, stderr io.Writer) int {
543+
func runPluginDisableAllCLI(state *bootstrap.State, settingsPath string, stdout io.Writer, stderr io.Writer) int {
543544
settings, err := pluginCLISettingsFromFiles(state.CWD())
544545
if err != nil {
545546
fmt.Fprintf(stderr, "ccgo plugin disable: %v\n", err)
@@ -556,14 +557,27 @@ func runPluginDisableAllCLI(state *bootstrap.State, stdout io.Writer, stderr io.
556557
fmt.Fprintln(stdout, "No enabled plugins to disable")
557558
return 0
558559
}
559-
if err := config.SetUserPluginsEnabled(states); err != nil {
560+
if err := config.SetPluginsEnabledInSettingsFile(settingsPath, states); err != nil {
560561
fmt.Fprintf(stderr, "ccgo plugin disable: %v\n", err)
561562
return 1
562563
}
563564
fmt.Fprintf(stdout, "Disabled %d %s\n", len(states), pluralWord(len(states), "plugin", "plugins"))
564565
return 0
565566
}
566567

568+
func pluginCLISettingsPathForScope(state *bootstrap.State, scope string) (string, error) {
569+
switch strings.ToLower(strings.TrimSpace(scope)) {
570+
case "", "user":
571+
return config.UserSettingsPath(), nil
572+
case "project":
573+
return config.ProjectSettingsPath(state.CWD()), nil
574+
case "local":
575+
return config.LocalSettingsPath(state.CWD()), nil
576+
default:
577+
return "", fmt.Errorf("scope %q is not supported; use user, project, or local", scope)
578+
}
579+
}
580+
567581
func pluralWord(count int, singular string, plural string) string {
568582
if count == 1 {
569583
return singular

cmd/claude/main_test.go

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2371,8 +2371,40 @@ func TestRunPluginEnableDisableCLI(t *testing.T) {
23712371
stdout.Reset()
23722372
stderr.Reset()
23732373
code = run([]string{"--cwd", project, "plugin", "enable", "--scope", "project", "demo"}, strings.NewReader(""), &stdout, &stderr)
2374-
if code != 2 || !strings.Contains(stderr.String(), `scope "project" is not supported yet`) {
2375-
t.Fatalf("project scope exit=%d stdout=%q stderr=%q", code, stdout.String(), stderr.String())
2374+
if code != 0 {
2375+
t.Fatalf("project scope enable exit=%d stdout=%q stderr=%q", code, stdout.String(), stderr.String())
2376+
}
2377+
if !strings.Contains(stdout.String(), "Plugin demo enabled.") {
2378+
t.Fatalf("project enable stdout = %q", stdout.String())
2379+
}
2380+
projectSettings := readTestSettingsJSON(t, filepath.Join(project, ".claude", "settings.json"))
2381+
if enabled := projectSettings["enabledPlugins"].(map[string]any)["demo"]; enabled != true {
2382+
t.Fatalf("project enabled plugin state = %#v", projectSettings["enabledPlugins"])
2383+
}
2384+
2385+
stdout.Reset()
2386+
stderr.Reset()
2387+
code = run([]string{"--cwd", project, "plugin", "disable", "--scope", "project", "--all"}, strings.NewReader(""), &stdout, &stderr)
2388+
if code != 0 {
2389+
t.Fatalf("project disable all exit=%d stdout=%q stderr=%q", code, stdout.String(), stderr.String())
2390+
}
2391+
if !strings.Contains(stdout.String(), "Disabled 1 plugin") {
2392+
t.Fatalf("project disable all stdout = %q", stdout.String())
2393+
}
2394+
projectSettings = readTestSettingsJSON(t, filepath.Join(project, ".claude", "settings.json"))
2395+
if enabled := projectSettings["enabledPlugins"].(map[string]any)["demo"]; enabled != false {
2396+
t.Fatalf("project disabled plugin state = %#v", projectSettings["enabledPlugins"])
2397+
}
2398+
2399+
stdout.Reset()
2400+
stderr.Reset()
2401+
code = run([]string{"--cwd", project, "plugin", "enable", "--scope", "local", "market/plugin"}, strings.NewReader(""), &stdout, &stderr)
2402+
if code != 0 {
2403+
t.Fatalf("local scope enable exit=%d stdout=%q stderr=%q", code, stdout.String(), stderr.String())
2404+
}
2405+
localSettings := readTestSettingsJSON(t, filepath.Join(project, ".claude", "settings.local.json"))
2406+
if enabled := localSettings["enabledPlugins"].(map[string]any)["market/plugin"]; enabled != true {
2407+
t.Fatalf("local enabled plugin state = %#v", localSettings["enabledPlugins"])
23762408
}
23772409

23782410
stdout.Reset()

docs/cc-100-roadmap.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ M4 补充:tool executor 会围绕 `PreToolUse`、`PostToolUse`、`PermissionDe
3737

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

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

4242
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 仍未完成。
4343

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 一致性;`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;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` 会写入或删除用户级 `extraKnownMarketplaces` 并复用 source validation,CLI `plugin marketplace update [name]` 会按全部或指定 marketplace 触发现有 cache 刷新路径,CLI `plugin install --scope project <plugin>` 与 headless `/plugin install <name>` 共享安装 API 并复制插件到项目 `.claude/plugins/<safe-name>`,不覆盖冲突路径并拒绝 symlink/non-regular 文件;CLI `plugin update --scope project <plugin>` 与 headless `/plugin update [name]` 共享更新/目录替换 API,把已安装同名项目插件替换为最新 marketplace 副本;CLI `plugin enable|disable [--scope user] <plugin>` 和 `plugin disable --all` 与 headless `/plugin enable|disable` 共享用户 settings 写入 helper;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 一致性;`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;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` 会写入或删除用户级 `extraKnownMarketplaces` 并复用 source validation,CLI `plugin marketplace update [name]` 会按全部或指定 marketplace 触发现有 cache 刷新路径,CLI `plugin install --scope project <plugin>` 与 headless `/plugin install <name>` 共享安装 API 并复制插件到项目 `.claude/plugins/<safe-name>`,不覆盖冲突路径并拒绝 symlink/non-regular 文件;CLI `plugin update --scope project <plugin>` 与 headless `/plugin update [name]` 共享更新/目录替换 API,把已安装同名项目插件替换为最新 marketplace 副本;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)