@curdx/flow 2.0.17 → 2.0.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,7 +6,7 @@
6
6
  },
7
7
  "metadata": {
8
8
  "description": "Claude Code Discipline Layer — spec-driven workflow + goal-backward verification + Karpathy 4 principles enforced via gates. Stops Claude from faking \"done\" on non-trivial features.",
9
- "version": "2.0.17"
9
+ "version": "2.0.19"
10
10
  },
11
11
  "plugins": [
12
12
  {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "curdx-flow",
3
- "version": "2.0.17",
3
+ "version": "2.0.19",
4
4
  "description": "Claude Code Discipline Layer — spec-driven workflow + goal-backward verification + Karpathy 4 principles enforced via gates. Stops Claude from faking \"done\" on non-trivial features.",
5
5
  "author": {
6
6
  "name": "wdx",
package/README.zh.md CHANGED
@@ -19,11 +19,11 @@ CurdX-Flow 是一个 Claude Code 插件,把 6 个验证过的 AI 工程工作
19
19
  ## 一览(v2)
20
20
 
21
21
  - **9 个命令** — 初始化 / 启动规格 / 规格 / 执行 / 验证 / 审查 / 调试 / fast / 帮助
22
- - **15 个内部代理** — 由命令调度,不再暴露 v1 的人格角色
22
+ - **15 个内部代理** — 由命令调度,按职能分工执行
23
23
  - **8 个可组合 Gate** — Karpathy / Verification / TDD / Coverage / Adversarial / Edge-Case / Security / DevEx
24
24
  - **4 种执行策略** — linear / subagent / stop-hook / wave(自动路由)
25
25
  - **10 个知识文档** — 规格驱动 / POC-First / 原子提交 / 执行策略 / ...
26
- - **5 个 hook 事件** — SessionStart / InstructionsLoaded / PostToolUseFailure / Stop / PreToolUse
26
+ - **4 个 hook 事件** — SessionStart / SessionStart(startup|clear|compact) / Stop / PreToolUse
27
27
  - **必需文档/推理工具 + 4 个推荐插件** — Context7 官方插件 / sequential-thinking MCP + pua / claude-mem / frontend-design / chrome-devtools-mcp
28
28
  - **优雅降级** — 依赖缺失时进入 fallback 模式并清晰告知
29
29
 
@@ -37,7 +37,7 @@ CurdX-Flow 是一个 Claude Code 插件,把 6 个验证过的 AI 工程工作
37
37
  | 无工程纪律(AI 跳步、幻觉) | Karpathy L1 + verification-gate + TDD-gate |
38
38
  | 最佳实践分散 | 6 个工作流蒸馏到一个元框架 |
39
39
  | 验证薄弱(无证据声称"完成") | 目标反向验证 + 禁用词检测 |
40
- | 单视角决策 | Party Mode(真正多代理,非角色扮演)+ 对抗审查 |
40
+ | 单视角决策 | 多代理协作 + 对抗审查 |
41
41
 
42
42
  ## 安装
43
43
 
@@ -91,7 +91,7 @@ claude --plugin-dir ./curdx-flow
91
91
 
92
92
  1. **Mind(L1 基线)** — Karpathy 4 原则 + 必用工具规则 + 三条红线
93
93
  2. **Spec(规格)** — 研究 → 需求 → 设计 → 任务(可伸缩,4 文件 lite 到 12 文件 enterprise)
94
- 3. **Agents(代理)** — 15 职能 + 9 人格(Mary / John / Winston / ...)
94
+ 3. **Agents(代理)** — 15 个内部代理,按命令和流程调度
95
95
  4. **Flow(流程)** — 4 种执行策略,自动路由
96
96
  5. **Memory(记忆)** — claude-mem(隐式)+ `.flow/STATE.md`(显式决策 D-NN)
97
97
 
@@ -126,7 +126,7 @@ CurdX-Flow 是蒸馏,不是原创。深深致谢:
126
126
  - [**andrej-karpathy-skills**](https://github.com/forrestchang/andrej-karpathy-skills) — 4 原则
127
127
  - [**smart-ralph**](https://github.com/Nibzard/smart-ralph) — 规格引擎 + stop-hook 循环
128
128
  - [**superpowers**](https://github.com/obra/superpowers) — Subagent 纪律 + TDD + 两阶段审查
129
- - [**BMAD-METHOD**](https://github.com/bmad-code-org/BMAD-METHOD) — 人格 + Party Mode + 对抗审查
129
+ - [**BMAD-METHOD**](https://github.com/bmad-code-org/BMAD-METHOD) — 协作流程 + 对抗审查
130
130
  - [**get-shit-done**](https://github.com/ryangentry/get-shit-done) — 波形执行 + 决策锁定 + 多源审计
131
131
  - [**gstack**](https://github.com/garrytan/gstack)(Garry Tan) — 规划审查 + DX 哲学
132
132
  - [**pua**](https://github.com/tanweai/pua) — 持续性 + 三条红线
package/cli/doctor.js CHANGED
@@ -4,6 +4,7 @@
4
4
 
5
5
  import fs from "node:fs/promises";
6
6
  import path from "node:path";
7
+
7
8
  import {
8
9
  color,
9
10
  log,
@@ -14,220 +15,102 @@ import {
14
15
  listMcps,
15
16
  ensureClaudeMemRuntimes,
16
17
  readUserMcpConfig,
17
- findDuplicateMcps,
18
- findPluginByRegistryEntry,
19
- hasMarketplace,
20
18
  } from "./utils.js";
21
- import { BUNDLED_MCPS, REQUIRED_PLUGINS, RECOMMENDED_PLUGINS } from "./registry.js";
19
+ import { buildDoctorReport } from "./lib/doctor-report.js";
22
20
 
23
21
  export async function doctor(args = []) {
24
22
  const verbose = args.includes("--verbose") || args.includes("-v");
23
+ const claudeVersionValue = claudeVersion();
24
+ const plugins = claudeVersionValue ? listPlugins() : [];
25
+ const marketplaces = claudeVersionValue ? listPluginMarketplaces() : [];
26
+ const mcps = claudeVersionValue ? listMcps() : [];
27
+ const userMcpConfig = claudeVersionValue ? readUserMcpConfig() : new Map();
28
+ const runtimeStatus = plugins.some((plugin) => plugin.name === "claude-mem" && plugin.status === "enabled")
29
+ ? ensureClaudeMemRuntimes()
30
+ : null;
25
31
 
26
32
  log.title("🏥 CurdX-Flow Health Check");
27
33
 
28
- let errors = 0;
29
- let warnings = 0;
30
-
31
- // ---------- claude CLI ----------
32
- const cv = claudeVersion();
33
- if (cv) {
34
- log.ok(`claude CLI ${color.dim(cv)}`);
35
- } else {
36
- log.err("claude CLI not found (install Claude Code)");
37
- errors++;
34
+ const report = buildDoctorReport({
35
+ claudeVersionValue,
36
+ nodeVersion: process.version,
37
+ plugins,
38
+ marketplaces,
39
+ mcps,
40
+ userMcpConfig,
41
+ runtimeStatus,
42
+ cwd: process.cwd(),
43
+ projectState: await readProjectState(),
44
+ });
45
+
46
+ renderLines(report.lines);
47
+ for (const section of report.sections) {
48
+ console.log(`\n${color.bold(section.title)}`);
49
+ renderLines(section.lines);
38
50
  }
39
51
 
40
- // ---------- Node ----------
41
- log.ok(`Node ${color.dim(process.version)}`);
42
-
43
- // ---------- curdx-flow plugin ----------
44
- const plugins = cv ? listPlugins() : [];
45
- const curdx = plugins.find((p) => p.name === "curdx-flow");
46
- if (curdx) {
47
- if (curdx.status === "enabled") {
48
- log.ok(`curdx-flow ${color.dim(`v${curdx.version} (enabled)`)}`);
49
- } else {
50
- log.err(`curdx-flow v${curdx.version} (${curdx.status})`);
51
- errors++;
52
- }
53
- } else {
54
- log.warn("curdx-flow not installed → run curdx-flow install");
55
- warnings++;
52
+ printSummary(report);
53
+ if (verbose && claudeVersionValue) {
54
+ printVerboseDetails();
56
55
  }
56
+ }
57
57
 
58
- const marketplaces = cv ? listPluginMarketplaces() : [];
59
-
60
- // ---------- Required plugins ----------
61
- console.log(`\n${color.bold("Required plugins:")}`);
62
- for (const r of REQUIRED_PLUGINS) {
63
- const p = findPluginByRegistryEntry(plugins, r);
64
- if (!hasMarketplace(marketplaces, r)) {
65
- log.warn(
66
- `${r.marketplaceId.padEnd(22)} marketplace missing ${color.dim(`(run: claude plugin marketplace add --scope ${r.scope} ${r.marketplaceSource})`)}`
67
- );
68
- warnings++;
69
- }
70
- if (p && p.status === "enabled") {
71
- log.ok(`${r.name.padEnd(22)} ${color.dim(`v${p.version || "unknown"}`)}`);
72
- } else if (p && p.status === "failed") {
73
- log.err(`${r.name.padEnd(22)} load failed`);
74
- errors++;
75
- } else {
76
- log.warn(
77
- `${r.name.padEnd(22)} not installed ${color.dim(`(run: claude plugin install --scope ${r.scope} ${r.installSpec})`)}`
78
- );
79
- warnings++;
58
+ async function readProjectState() {
59
+ const cwd = process.cwd();
60
+ const flowDir = path.join(cwd, ".flow");
61
+ try {
62
+ const stat = await fs.stat(flowDir);
63
+ if (!stat.isDirectory()) {
64
+ return { exists: false, activeSpec: null };
80
65
  }
81
- }
82
66
 
83
- // ---------- MCPs ----------
84
- console.log(`\n${color.bold("MCP Servers (required by L2 mandatory tools):")}`);
85
- const mcps = cv ? listMcps() : [];
86
- for (const expected of BUNDLED_MCPS) {
87
- const m = expected.name;
88
- // Beta.12 onward: the required MCPs are registered at user-level
89
- // (via `claude mcp add`), NOT plugin-bundled. Both still show up in
90
- // `claude mcp list`. Accept a standalone user-level registration
91
- // as the primary form, and warn if only a plugin-bundled copy exists
92
- // (because that's the legacy layout that beta.11 had).
93
- const userLevel = mcps.find((x) => x.name === m && x.plugin === null);
94
- const pluginLevel = mcps.find((x) => x.name === m && x.plugin !== null);
95
-
96
- if (userLevel) {
97
- log.ok(`${m.padEnd(22)} ${color.dim("user-level (standard)")}`);
98
- } else if (pluginLevel) {
99
- log.warn(
100
- `${m.padEnd(22)} registered via plugin:${pluginLevel.plugin} (legacy). ` +
101
- `Run ${color.cyan("npx @curdx/flow install --all")} to migrate to user-level.`
102
- );
103
- warnings++;
104
- } else {
105
- if (curdx) {
106
- log.warn(
107
- `${m.padEnd(22)} missing. Run: ${color.cyan(`claude mcp add --scope user ${m} -- ${expected.command} ${expected.args.join(" ")}`)}`
108
- );
109
- warnings++;
110
- } else {
111
- log.info(`${m.padEnd(22)} waiting for curdx-flow install`);
112
- }
67
+ try {
68
+ const activeSpec = await fs.readFile(path.join(flowDir, ".active-spec"), "utf-8");
69
+ return { exists: true, activeSpec: activeSpec.trim() };
70
+ } catch {
71
+ return { exists: true, activeSpec: null };
113
72
  }
73
+ } catch {
74
+ return { exists: false, activeSpec: null };
114
75
  }
76
+ }
115
77
 
116
- // ---------- Recommended plugins (single registry; see cli/registry.js) ----------
117
- console.log(`\n${color.bold("Recommended plugins:")}`);
118
- let claudeMemEnabled = false;
119
- for (const r of RECOMMENDED_PLUGINS) {
120
- const p = findPluginByRegistryEntry(plugins, r);
121
- if (!hasMarketplace(marketplaces, r)) {
122
- log.warn(
123
- `${r.marketplaceId.padEnd(22)} marketplace missing ${color.dim(`(run: claude plugin marketplace add --scope ${r.scope} ${r.marketplaceSource})`)}`
124
- );
125
- warnings++;
126
- }
127
- if (p && p.status === "enabled") {
128
- log.ok(`${r.name.padEnd(22)} ${color.dim(`v${p.version}`)}`);
129
- if (r.postInstall === "claude-mem-runtimes") claudeMemEnabled = true;
130
- } else if (p && p.status === "failed") {
131
- log.err(`${r.name.padEnd(22)} load failed`);
132
- errors++;
78
+ function renderLines(lines) {
79
+ for (const line of lines) {
80
+ if (line.level === "ok") {
81
+ log.ok(line.text);
82
+ } else if (line.level === "warn") {
83
+ log.warn(line.text);
84
+ } else if (line.level === "err") {
85
+ log.err(line.text);
133
86
  } else {
134
- log.warn(
135
- `${r.name.padEnd(22)} not installed ${color.dim(`(run: claude plugin install --scope ${r.scope} ${r.installSpec})`)}`
136
- );
137
- warnings++;
87
+ log.info(line.text);
138
88
  }
139
- }
140
-
141
- // ---------- Legacy plugin-bundled MCP residue (beta.11 and earlier) ----------
142
- // Beta.12 moved context7 + sequential-thinking to user-level registration.
143
- // If both a user-level and a plugin-bundled copy are visible, the user
144
- // was likely installed from an older beta.7-beta.11 that still had
145
- // mcpServers in plugin.json and a `claude mcp update` hasn't refreshed
146
- // the plugin cache yet. Point them at the migration command.
147
- if (cv) {
148
- const userCfg = readUserMcpConfig();
149
- const duplicates = findDuplicateMcps(mcps, userCfg);
150
- if (duplicates.length > 0) {
151
- console.log(`\n${color.bold("Legacy plugin-bundled MCPs still present:")}`);
152
- for (const d of duplicates) {
153
- log.warn(
154
- `${d.name.padEnd(22)} both user-level AND plugin:${d.pluginEntry.plugin} active`
155
- );
156
- console.log(
157
- color.dim(
158
- ` → Migration: ${color.cyan(`claude plugin update curdx-flow@curdx-flow-marketplace`)}\n` +
159
- ` then restart Claude Code. Beta.12+ removes plugin-bundled\n` +
160
- ` versions in favor of the user-level entry you already have.`
161
- )
162
- );
163
- warnings++;
164
- }
165
- }
166
- }
167
89
 
168
- // ---------- Runtime PATH guards (only if claude-mem is installed) ----------
169
- if (claudeMemEnabled) {
170
- console.log(`\n${color.bold("Runtime (claude-mem dependencies):")}`);
171
- const rt = ensureClaudeMemRuntimes();
172
- for (const [name, res] of Object.entries(rt)) {
173
- if (res.status === "ok") {
174
- log.ok(`${name.padEnd(22)} ${color.dim("visible on PATH")}`);
175
- } else if (res.status === "linked") {
176
- log.ok(
177
- `${name.padEnd(22)} ${color.dim(`auto-linked ${res.link} → ${res.path}`)}`
178
- );
179
- } else if (res.status === "missing") {
180
- log.warn(
181
- `${name.padEnd(22)} not installed ${color.dim("(claude-mem will auto-install on next Claude Code session)")}`
182
- );
183
- warnings++;
184
- } else if (res.status === "path-unwritable") {
185
- const dir = res.path.split("/").slice(0, -1).join("/");
186
- log.err(
187
- `${name.padEnd(22)} installed but not on PATH ${color.dim(`(add export PATH="${dir}:$PATH" to your shell rc)`)}`
188
- );
189
- errors++;
190
- }
90
+ for (const detail of line.details || []) {
91
+ console.log(color.dim(` → ${detail}`));
191
92
  }
192
93
  }
94
+ }
193
95
 
194
- // ---------- Project state ----------
195
- console.log(`\n${color.bold("Local project:")}`);
196
- const cwd = process.cwd();
197
- const flowDir = path.join(cwd, ".flow");
198
- try {
199
- const stat = await fs.stat(flowDir);
200
- if (stat.isDirectory()) {
201
- log.ok(`.flow/ ${color.dim(cwd)}`);
202
- // Check active spec
203
- try {
204
- const active = await fs.readFile(path.join(flowDir, ".active-spec"), "utf-8");
205
- log.info(`Active spec ${color.cyan(active.trim())}`);
206
- } catch {
207
- log.info("Active spec (none)");
208
- }
209
- }
210
- } catch {
211
- log.info(`.flow/ not a curdx-flow project (run: curdx-flow init)`);
212
- }
213
-
214
- // ---------- Summary ----------
96
+ function printSummary(report) {
215
97
  console.log();
216
- if (errors > 0) {
217
- console.log(color.red(`Summary: ${errors} error(s), ${warnings} warning(s)`));
98
+ if (report.errors > 0) {
99
+ console.log(color.red(`Summary: ${report.errors} error(s), ${report.warnings} warning(s)`));
218
100
  console.log(color.dim("Fix errors and re-run curdx-flow doctor"));
219
101
  process.exit(1);
220
- } else if (warnings > 0) {
221
- console.log(color.yellow(`Summary: ${warnings} warning(s). Usable, but worth addressing.`));
222
- } else {
223
- console.log(color.green("Summary: all healthy ✓"));
224
102
  }
225
103
 
226
- if (verbose && cv) {
227
- // Only call claude when it is actually on PATH; otherwise we spawn a
228
- // child that fails silently and print a blank block.
229
- console.log(`\n${color.bold("Details:")}`);
230
- console.log(color.dim(` Plugins raw:`));
231
- console.log(runSync("claude", ["plugin", "list"]).stdout);
104
+ if (report.warnings > 0) {
105
+ console.log(color.yellow(`Summary: ${report.warnings} warning(s). Usable, but worth addressing.`));
106
+ return;
232
107
  }
108
+
109
+ console.log(color.green("Summary: all healthy ✓"));
110
+ }
111
+
112
+ function printVerboseDetails() {
113
+ console.log(`\n${color.bold("Details:")}`);
114
+ console.log(color.dim(" Plugins raw:"));
115
+ console.log(runSync("claude", ["plugin", "list"]).stdout);
233
116
  }
@@ -1,34 +1,38 @@
1
- import { color, ensureClaudeMemRuntimes, listPlugins, log, multiselectClack, note, resultLastLine, run, text, writeConfig } from "./utils.js";
1
+ import {
2
+ color,
3
+ ensureClaudeMemRuntimes,
4
+ listPlugins,
5
+ log,
6
+ multiselectClack,
7
+ note,
8
+ resultLastLine,
9
+ text,
10
+ writeConfig,
11
+ } from "./utils.js";
2
12
  import { BUNDLED_MCPS, RECOMMENDED_PLUGINS, REQUIRED_PLUGINS } from "./registry.js";
3
13
  import { readUserMcpConfig } from "./utils.js";
4
14
  import {
5
- mcpAddArgs,
6
- mcpRemoveArgs,
7
- pluginInstallArgs,
8
- pluginMarketplaceAddArgs,
9
- } from "./lib/claude-commands.js";
15
+ addMcp,
16
+ addPluginMarketplace,
17
+ installPlugin,
18
+ removeMcp,
19
+ } from "./lib/claude-ops.js";
10
20
 
11
21
  const RECOMMENDED = RECOMMENDED_PLUGINS;
22
+ const CONTEXT7_COMMAND = "npx";
23
+ const CONTEXT7_ARGS = ["-y", "@upstash/context7-mcp"];
12
24
 
13
25
  export async function installRequiredPlugins({ yes, language, config }) {
14
26
  log.blank();
15
27
  log.info("Installing required Claude Code plugins...");
16
28
  for (const plugin of REQUIRED_PLUGINS) {
17
29
  console.log(` ${color.cyan("▸")} Installing ${color.bold(plugin.name)}...`);
18
- const ma = await run(
19
- "claude",
20
- pluginMarketplaceAddArgs(plugin),
21
- { silent: true }
22
- );
30
+ const ma = await addPluginMarketplace(plugin);
23
31
  if (ma.code !== 0 && !ma.stderr.includes("already")) {
24
32
  log.warn(` marketplace add warning: ${ma.stderr.trim().split("\n")[0]}`);
25
33
  }
26
34
 
27
- const ir = await run(
28
- "claude",
29
- pluginInstallArgs(plugin),
30
- { silent: true }
31
- );
35
+ const ir = await installPlugin(plugin);
32
36
  if (ir.code === 0) {
33
37
  console.log(` ${color.green("✓")} ${plugin.name} installed`);
34
38
 
@@ -65,11 +69,7 @@ export async function registerBundledMcps() {
65
69
  );
66
70
  continue;
67
71
  }
68
- const r = await run(
69
- "claude",
70
- mcpAddArgs(mcp),
71
- { silent: true }
72
- );
72
+ const r = await addMcp(mcp);
73
73
  if (r.code === 0) {
74
74
  log.ok(` ${mcp.name.padEnd(22)} ${color.dim("registered")}`);
75
75
  } else if (r.stderr.includes("already exists")) {
@@ -129,19 +129,13 @@ export async function installRecommendedPlugins({ all, yes, language }) {
129
129
  console.log(` ${color.cyan("▸")} Installing ${color.bold(rec.name)}...`);
130
130
 
131
131
  if (rec.marketplaceSource) {
132
- const ma = await run(
133
- "claude",
134
- pluginMarketplaceAddArgs(rec),
135
- { silent: true }
136
- );
132
+ const ma = await addPluginMarketplace(rec);
137
133
  if (ma.code !== 0 && !ma.stderr.includes("already")) {
138
134
  log.warn(` marketplace add warning: ${ma.stderr.trim().split("\n")[0]}`);
139
135
  }
140
136
  }
141
137
 
142
- const ir = await run("claude", pluginInstallArgs(rec), {
143
- silent: true,
144
- });
138
+ const ir = await installPlugin(rec);
145
139
  if (ir.code === 0) {
146
140
  console.log(` ${color.green("✓")} ${rec.name} installed`);
147
141
 
@@ -207,16 +201,7 @@ async function promptPluginConfig(plugin, language, config) {
207
201
  config.context7ApiKey = apiKey;
208
202
  writeConfig(config);
209
203
 
210
- const r = await run(
211
- "claude",
212
- mcpAddArgs({
213
- name: "context7",
214
- env: [`CONTEXT7_API_KEY=${apiKey}`],
215
- command: "npx",
216
- args: ["-y", "@upstash/context7-mcp"],
217
- }),
218
- { silent: true }
219
- );
204
+ const r = await addMcp(buildContext7Mcp(apiKey));
220
205
 
221
206
  if (r.code === 0) {
222
207
  log.ok(
@@ -225,17 +210,8 @@ async function promptPluginConfig(plugin, language, config) {
225
210
  : " Context7 API key configured"
226
211
  );
227
212
  } else if (r.stderr.includes("already exists")) {
228
- await run("claude", mcpRemoveArgs({ name: "context7" }), { silent: true });
229
- const r2 = await run(
230
- "claude",
231
- mcpAddArgs({
232
- name: "context7",
233
- env: [`CONTEXT7_API_KEY=${apiKey}`],
234
- command: "npx",
235
- args: ["-y", "@upstash/context7-mcp"],
236
- }),
237
- { silent: true }
238
- );
213
+ await removeMcp({ name: "context7" });
214
+ const r2 = await addMcp(buildContext7Mcp(apiKey));
239
215
  if (r2.code === 0) {
240
216
  log.ok(
241
217
  language === "zh"
@@ -252,8 +228,8 @@ async function promptPluginConfig(plugin, language, config) {
252
228
  log.info(
253
229
  color.dim(
254
230
  language === "zh"
255
- ? ` 手动运行: claude mcp add --scope user context7 --env CONTEXT7_API_KEY=${apiKey} -- npx -y @upstash/context7-mcp`
256
- : ` Run manually: claude mcp add --scope user context7 --env CONTEXT7_API_KEY=${apiKey} -- npx -y @upstash/context7-mcp`
231
+ ? ` 手动运行: claude mcp add --scope user context7 --env CONTEXT7_API_KEY=${apiKey} -- ${CONTEXT7_COMMAND} ${CONTEXT7_ARGS.join(" ")}`
232
+ : ` Run manually: claude mcp add --scope user context7 --env CONTEXT7_API_KEY=${apiKey} -- ${CONTEXT7_COMMAND} ${CONTEXT7_ARGS.join(" ")}`
257
233
  )
258
234
  );
259
235
  }
@@ -265,3 +241,12 @@ async function promptPluginConfig(plugin, language, config) {
265
241
  );
266
242
  }
267
243
  }
244
+
245
+ function buildContext7Mcp(apiKey) {
246
+ return {
247
+ name: "context7",
248
+ env: [`CONTEXT7_API_KEY=${apiKey}`],
249
+ command: CONTEXT7_COMMAND,
250
+ args: CONTEXT7_ARGS,
251
+ };
252
+ }
@@ -1,9 +1,9 @@
1
- import { color, log, resultOutput, run } from "./utils.js";
1
+ import { color, log, resultOutput } from "./utils.js";
2
2
  import {
3
- pluginInstallArgs,
4
- pluginMarketplaceAddArgs,
5
- pluginMarketplaceRemoveArgs,
6
- } from "./lib/claude-commands.js";
3
+ addPluginMarketplace,
4
+ installPlugin,
5
+ removePluginMarketplace,
6
+ } from "./lib/claude-ops.js";
7
7
 
8
8
  export async function addCurdxMarketplace({ marketplaceSource, marketplaceLabel, useOffline }) {
9
9
  log.blank();
@@ -12,17 +12,9 @@ export async function addCurdxMarketplace({ marketplaceSource, marketplaceLabel,
12
12
  // Remove any existing marketplace with the same name so we get a clean
13
13
  // rebind to the chosen source. Errors are non-fatal (marketplace may
14
14
  // simply not exist yet).
15
- await run(
16
- "claude",
17
- pluginMarketplaceRemoveArgs("curdx-flow-marketplace"),
18
- { silent: true }
19
- );
15
+ await removePluginMarketplace("curdx-flow-marketplace");
20
16
 
21
- const addRes = await run(
22
- "claude",
23
- pluginMarketplaceAddArgs({ scope: "user", marketplaceSource }),
24
- { silent: true }
25
- );
17
+ const addRes = await addPluginMarketplace({ scope: "user", marketplaceSource });
26
18
  if (addRes.code !== 0 && !addRes.stderr.includes("already")) {
27
19
  log.warn(`marketplace add output: ${resultOutput(addRes)}`);
28
20
  } else {
@@ -50,14 +42,10 @@ export async function installCurdxFlowPlugin({ prevCurdxFlow, shippedVersion })
50
42
  log.info(
51
43
  `curdx-flow already at v${already.version}, re-registering...`
52
44
  );
53
- const r = await run(
54
- "claude",
55
- pluginInstallArgs({
56
- scope: "user",
57
- installSpec: "curdx-flow@curdx-flow-marketplace",
58
- }),
59
- { silent: true }
60
- );
45
+ const r = await installPlugin({
46
+ scope: "user",
47
+ installSpec: "curdx-flow@curdx-flow-marketplace",
48
+ });
61
49
  if (r.code !== 0) {
62
50
  log.err(`Install failed: ${resultOutput(r)}`);
63
51
  process.exit(1);
@@ -67,14 +55,10 @@ export async function installCurdxFlowPlugin({ prevCurdxFlow, shippedVersion })
67
55
  log.info(
68
56
  `curdx-flow v${already.version} → v${shippedVersion}, installing...`
69
57
  );
70
- const r = await run(
71
- "claude",
72
- pluginInstallArgs({
73
- scope: "user",
74
- installSpec: "curdx-flow@curdx-flow-marketplace",
75
- }),
76
- { silent: true }
77
- );
58
+ const r = await installPlugin({
59
+ scope: "user",
60
+ installSpec: "curdx-flow@curdx-flow-marketplace",
61
+ });
78
62
  if (r.code !== 0) {
79
63
  log.err(`Install failed: ${resultOutput(r)}`);
80
64
  process.exit(1);
@@ -84,28 +68,20 @@ export async function installCurdxFlowPlugin({ prevCurdxFlow, shippedVersion })
84
68
  log.info(
85
69
  `curdx-flow v${already.version} detected, re-registering...`
86
70
  );
87
- const r = await run(
88
- "claude",
89
- pluginInstallArgs({
90
- scope: "user",
91
- installSpec: "curdx-flow@curdx-flow-marketplace",
92
- }),
93
- { silent: true }
94
- );
71
+ const r = await installPlugin({
72
+ scope: "user",
73
+ installSpec: "curdx-flow@curdx-flow-marketplace",
74
+ });
95
75
  if (r.code !== 0) {
96
76
  log.err(`Install failed: ${resultOutput(r)}`);
97
77
  process.exit(1);
98
78
  }
99
79
  log.ok("curdx-flow re-registered");
100
80
  } else {
101
- const r = await run(
102
- "claude",
103
- pluginInstallArgs({
104
- scope: "user",
105
- installSpec: "curdx-flow@curdx-flow-marketplace",
106
- }),
107
- { silent: true }
108
- );
81
+ const r = await installPlugin({
82
+ scope: "user",
83
+ installSpec: "curdx-flow@curdx-flow-marketplace",
84
+ });
109
85
  if (r.code !== 0) {
110
86
  log.err(`Install failed: ${resultOutput(r)}`);
111
87
  process.exit(1);
@@ -0,0 +1,47 @@
1
+ import { run } from "./process.js";
2
+ import {
3
+ mcpAddArgs,
4
+ mcpRemoveArgs,
5
+ pluginInstallArgs,
6
+ pluginMarketplaceAddArgs,
7
+ pluginMarketplaceRemoveArgs,
8
+ pluginMarketplaceUpdateArgs,
9
+ pluginUninstallArgs,
10
+ pluginUpdateArgs,
11
+ } from "./claude-commands.js";
12
+
13
+ async function runClaude(args, { runner = run, silent = true } = {}) {
14
+ return runner("claude", args, { silent });
15
+ }
16
+
17
+ export async function addPluginMarketplace(entry, options) {
18
+ return runClaude(pluginMarketplaceAddArgs(entry), options);
19
+ }
20
+
21
+ export async function updatePluginMarketplace(marketplaceId, options) {
22
+ return runClaude(pluginMarketplaceUpdateArgs(marketplaceId), options);
23
+ }
24
+
25
+ export async function removePluginMarketplace(marketplaceId, options) {
26
+ return runClaude(pluginMarketplaceRemoveArgs(marketplaceId), options);
27
+ }
28
+
29
+ export async function installPlugin(entry, options) {
30
+ return runClaude(pluginInstallArgs(entry), options);
31
+ }
32
+
33
+ export async function updatePlugin(spec, options) {
34
+ return runClaude(pluginUpdateArgs({ spec }), options);
35
+ }
36
+
37
+ export async function uninstallPlugin(entry, options) {
38
+ return runClaude(pluginUninstallArgs(entry), options);
39
+ }
40
+
41
+ export async function addMcp(entry, options) {
42
+ return runClaude(mcpAddArgs(entry), options);
43
+ }
44
+
45
+ export async function removeMcp(entry, options) {
46
+ return runClaude(mcpRemoveArgs(entry), options);
47
+ }