@curdx/flow 2.0.0-beta.8 → 2.0.0
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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +4 -20
- package/CHANGELOG.md +101 -0
- package/README.md +6 -3
- package/README.zh.md +18 -18
- package/bin/curdx-flow.js +30 -1
- package/cli/README.md +8 -6
- package/cli/doctor.js +95 -23
- package/cli/install.js +433 -59
- package/cli/protocols-body.md +21 -0
- package/cli/protocols.js +74 -36
- package/cli/registry.js +123 -0
- package/cli/uninstall.js +105 -14
- package/cli/upgrade.js +7 -11
- package/cli/utils.js +321 -61
- package/commands/implement.md +3 -3
- package/commands/init.md +14 -3
- package/commands/spec.md +1 -1
- package/commands/start.md +47 -17
- package/hooks/hooks.json +2 -14
- package/hooks/scripts/inject-karpathy.sh +8 -5
- package/hooks/scripts/quick-mode-guard.sh +12 -9
- package/hooks/scripts/session-start.sh +1 -1
- package/hooks/scripts/stop-watcher.sh +25 -15
- package/package.json +9 -4
- package/skills/brownfield-index/SKILL.md +62 -0
- package/skills/browser-qa/SKILL.md +50 -0
- package/skills/epic/SKILL.md +68 -0
- package/skills/security-audit/SKILL.md +50 -0
- package/skills/ui-sketch/SKILL.md +49 -0
- package/hooks/scripts/fail-tracker.sh +0 -31
package/cli/install.js
CHANGED
|
@@ -15,8 +15,20 @@ import {
|
|
|
15
15
|
listPlugins,
|
|
16
16
|
multiSelect,
|
|
17
17
|
ensureClaudeMemRuntimes,
|
|
18
|
+
select,
|
|
19
|
+
intro,
|
|
20
|
+
outro,
|
|
21
|
+
readConfig,
|
|
22
|
+
writeConfig,
|
|
23
|
+
text,
|
|
24
|
+
note,
|
|
25
|
+
multiselectClack,
|
|
26
|
+
runSync,
|
|
27
|
+
VERSION,
|
|
18
28
|
} from "./utils.js";
|
|
19
29
|
import { injectGlobalProtocols, GLOBAL_CLAUDE_MD } from "./protocols.js";
|
|
30
|
+
import { REQUIRED_PLUGINS, RECOMMENDED_PLUGINS, BUNDLED_MCPS } from "./registry.js";
|
|
31
|
+
import { readUserMcpConfig } from "./utils.js";
|
|
20
32
|
|
|
21
33
|
// When installed via npm, this CLI file lives at <pkg-root>/cli/install.js.
|
|
22
34
|
// The npm package bundles the full plugin body (.claude-plugin/, agents/,
|
|
@@ -28,38 +40,35 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
28
40
|
const PKG_ROOT = dirname(__dirname);
|
|
29
41
|
const LOCAL_MARKETPLACE_MANIFEST = join(PKG_ROOT, ".claude-plugin", "marketplace.json");
|
|
30
42
|
|
|
31
|
-
// Recommended plugins
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
installSpec: "pua@pua-skills",
|
|
37
|
-
hint: "no-give-up + three red lines",
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
name: "claude-mem",
|
|
41
|
-
marketplace: "thedotmack/claude-mem",
|
|
42
|
-
installSpec: "claude-mem@thedotmack",
|
|
43
|
-
hint: "automatic cross-session memory",
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
name: "frontend-design",
|
|
47
|
-
marketplace: null, // already in default marketplace claude-plugins-official
|
|
48
|
-
installSpec: "frontend-design@claude-plugins-official",
|
|
49
|
-
hint: "Anthropic official UI skill",
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
name: "chrome-devtools-mcp",
|
|
53
|
-
marketplace: "ChromeDevTools/chrome-devtools-mcp",
|
|
54
|
-
installSpec: "chrome-devtools-mcp@chrome-devtools-plugins",
|
|
55
|
-
hint: "Chrome DevTools + Puppeteer (Google official)",
|
|
56
|
-
},
|
|
57
|
-
];
|
|
43
|
+
// Recommended plugins: single source of truth is cli/registry.js.
|
|
44
|
+
// See registry.js for the rationale — this list used to drift across
|
|
45
|
+
// install/uninstall/upgrade/doctor, producing the chrome-devtools-mcp
|
|
46
|
+
// orphan-plugin bug (installable but uninstallable).
|
|
47
|
+
const RECOMMENDED = RECOMMENDED_PLUGINS;
|
|
58
48
|
|
|
59
49
|
export async function install(args = []) {
|
|
60
50
|
const all = args.includes("--all");
|
|
61
51
|
const noDeps = args.includes("--no-deps");
|
|
62
52
|
const forceOnline = args.includes("--online") || args.includes("--from-github");
|
|
53
|
+
const yes = args.includes("--yes") || args.includes("-y");
|
|
54
|
+
const skipSelfUpdate = args.includes("--skip-self-update");
|
|
55
|
+
|
|
56
|
+
// ---------- Step 0: Self-update check ----------
|
|
57
|
+
if (!skipSelfUpdate) {
|
|
58
|
+
const updateResult = await checkAndUpdateSelf();
|
|
59
|
+
if (updateResult.updated) {
|
|
60
|
+
// CLI was updated, re-exec with same args
|
|
61
|
+
log.info("Restarting with updated version...");
|
|
62
|
+
const { spawn } = await import("node:child_process");
|
|
63
|
+
const child = spawn("curdx-flow", ["install", ...args, "--skip-self-update"], {
|
|
64
|
+
stdio: "inherit",
|
|
65
|
+
shell: false,
|
|
66
|
+
});
|
|
67
|
+
return new Promise((resolve) => {
|
|
68
|
+
child.on("close", (code) => process.exit(code || 0));
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
63
72
|
|
|
64
73
|
// Default to offline install when the npm package includes the full plugin
|
|
65
74
|
// body (since 1.1.5). Fall back to GitHub only if the local manifest is
|
|
@@ -67,10 +76,48 @@ export async function install(args = []) {
|
|
|
67
76
|
// or the user explicitly passes --online.
|
|
68
77
|
const useOffline = !forceOnline && existsSync(LOCAL_MARKETPLACE_MANIFEST);
|
|
69
78
|
|
|
70
|
-
|
|
79
|
+
// Use @clack intro only in interactive TTY mode
|
|
80
|
+
if (process.stdout.isTTY && !yes) {
|
|
81
|
+
await intro("🚀 CurDX-Flow Installer");
|
|
82
|
+
} else {
|
|
83
|
+
log.title("🚀 CurDX-Flow Installer");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ---------- Step 0: Language selection ----------
|
|
87
|
+
const config = readConfig();
|
|
88
|
+
let language = config.language;
|
|
89
|
+
|
|
90
|
+
if (!language && !yes) {
|
|
91
|
+
log.blank();
|
|
92
|
+
language = await select({
|
|
93
|
+
message: "Choose your preferred language / 选择语言",
|
|
94
|
+
options: [
|
|
95
|
+
{
|
|
96
|
+
value: "en",
|
|
97
|
+
label: "English",
|
|
98
|
+
hint: "CLI output and documentation in English"
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
value: "zh",
|
|
102
|
+
label: "简体中文",
|
|
103
|
+
hint: "CLI 输出和文档使用简体中文"
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
initialValue: "en",
|
|
107
|
+
});
|
|
108
|
+
config.language = language;
|
|
109
|
+
writeConfig(config);
|
|
110
|
+
log.ok(`Language set to ${language === "zh" ? "简体中文" : "English"}`);
|
|
111
|
+
} else if (!language) {
|
|
112
|
+
// --yes mode, default to English
|
|
113
|
+
language = "en";
|
|
114
|
+
config.language = language;
|
|
115
|
+
writeConfig(config);
|
|
116
|
+
}
|
|
71
117
|
|
|
72
118
|
// ---------- Step 1: Check claude CLI ----------
|
|
73
|
-
log.
|
|
119
|
+
log.blank();
|
|
120
|
+
log.step(1, 5, "Checking claude CLI...");
|
|
74
121
|
const ver = claudeVersion();
|
|
75
122
|
if (!ver) {
|
|
76
123
|
log.err("claude CLI not found. Install Claude Code from https://code.claude.com first.");
|
|
@@ -78,13 +125,66 @@ export async function install(args = []) {
|
|
|
78
125
|
}
|
|
79
126
|
log.ok(`claude CLI found (${ver})`);
|
|
80
127
|
|
|
128
|
+
// Snapshot curdx-flow's pre-install version BEFORE Step 2 touches the
|
|
129
|
+
// marketplace. Step 2 does `claude plugin marketplace remove` + `add` to
|
|
130
|
+
// rebind to the current source, and the remove side-effect also drops
|
|
131
|
+
// any plugins installed from that marketplace. If we only call
|
|
132
|
+
// listPlugins() in Step 3, curdx-flow is already gone from the list and
|
|
133
|
+
// we can't tell a fresh install apart from an upgrade — the Step 3
|
|
134
|
+
// output then incorrectly says "installed" for both cases.
|
|
135
|
+
const prevCurdxFlow = listPlugins().find((p) => p.name === "curdx-flow");
|
|
136
|
+
|
|
137
|
+
// ---------- Step 1.5: Existing installation action menu ----------
|
|
138
|
+
if (prevCurdxFlow && !yes) {
|
|
139
|
+
log.blank();
|
|
140
|
+
const action = await select({
|
|
141
|
+
message: language === "zh"
|
|
142
|
+
? "检测到已安装的版本。如何继续?"
|
|
143
|
+
: "Existing installation detected. How would you like to proceed?",
|
|
144
|
+
options: [
|
|
145
|
+
{
|
|
146
|
+
value: "upgrade",
|
|
147
|
+
label: language === "zh" ? "升级到最新版本" : "Upgrade to latest version",
|
|
148
|
+
hint: language === "zh"
|
|
149
|
+
? `当前: v${prevCurdxFlow.version}`
|
|
150
|
+
: `Current: v${prevCurdxFlow.version}`
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
value: "reinstall",
|
|
154
|
+
label: language === "zh" ? "重新安装(保留配置)" : "Reinstall (preserve config)",
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
value: "reconfigure",
|
|
158
|
+
label: language === "zh" ? "重新配置插件" : "Reconfigure plugins",
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
value: "cancel",
|
|
162
|
+
label: language === "zh" ? "取消" : "Cancel",
|
|
163
|
+
},
|
|
164
|
+
],
|
|
165
|
+
initialValue: "upgrade",
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
if (action === "cancel") {
|
|
169
|
+
log.info(language === "zh" ? "安装已取消" : "Installation cancelled");
|
|
170
|
+
process.exit(0);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (action === "reconfigure") {
|
|
174
|
+
log.info(language === "zh"
|
|
175
|
+
? "重新配置模式:将重新提示插件选择和 API key 配置"
|
|
176
|
+
: "Reconfigure mode: will re-prompt for plugin selection and API key configuration");
|
|
177
|
+
// Continue with normal flow but force prompts
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
81
181
|
// ---------- Step 2: Add marketplace ----------
|
|
82
182
|
log.blank();
|
|
83
183
|
const marketplaceSource = useOffline ? PKG_ROOT : "curdx/curdx-flow";
|
|
84
184
|
const marketplaceLabel = useOffline
|
|
85
185
|
? `local npm package (${PKG_ROOT})`
|
|
86
186
|
: "GitHub curdx/curdx-flow";
|
|
87
|
-
log.step(2,
|
|
187
|
+
log.step(2, 5, `Adding curdx-flow marketplace from ${marketplaceLabel}...`);
|
|
88
188
|
|
|
89
189
|
// Remove any existing marketplace with the same name so we get a clean
|
|
90
190
|
// rebind to the chosen source. Errors are non-fatal (marketplace may
|
|
@@ -97,7 +197,7 @@ export async function install(args = []) {
|
|
|
97
197
|
|
|
98
198
|
const addRes = await run(
|
|
99
199
|
"claude",
|
|
100
|
-
["plugin", "marketplace", "add", marketplaceSource],
|
|
200
|
+
["plugin", "marketplace", "add", "--scope", "user", marketplaceSource],
|
|
101
201
|
{ silent: true }
|
|
102
202
|
);
|
|
103
203
|
if (addRes.code !== 0 && !addRes.stderr.includes("already")) {
|
|
@@ -111,7 +211,7 @@ export async function install(args = []) {
|
|
|
111
211
|
|
|
112
212
|
// ---------- Step 3: Install curdx-flow plugin ----------
|
|
113
213
|
log.blank();
|
|
114
|
-
log.step(3,
|
|
214
|
+
log.step(3, 5, "Installing curdx-flow plugin...");
|
|
115
215
|
// Read the version the marketplace is shipping so we can decide whether an
|
|
116
216
|
// already-installed plugin needs an update (same name but stale version
|
|
117
217
|
// previously silently skipped the upgrade — caused the beta.1 → beta.7 drift).
|
|
@@ -125,41 +225,157 @@ export async function install(args = []) {
|
|
|
125
225
|
// marketplace not local (online install) or unreadable — fall through
|
|
126
226
|
}
|
|
127
227
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
228
|
+
// Use the pre-Step-2 snapshot — by this point `claude plugin marketplace
|
|
229
|
+
// remove` has already evicted the plugin, so listPlugins() here would
|
|
230
|
+
// always return undefined for curdx-flow and we'd mis-report "installed"
|
|
231
|
+
// when we actually upgraded (the bug reported by @wdx's beta.14 log).
|
|
232
|
+
const already = prevCurdxFlow;
|
|
233
|
+
|
|
234
|
+
if (already && shippedVersion && already.version === shippedVersion) {
|
|
235
|
+
// Step 2 removes and re-adds the marketplace to rebind it to the current
|
|
236
|
+
// source. Claude Code removes plugins installed from that marketplace as
|
|
237
|
+
// part of `marketplace remove`, so even a same-version install must be
|
|
238
|
+
// re-registered here.
|
|
131
239
|
log.info(
|
|
132
|
-
`curdx-flow
|
|
240
|
+
`curdx-flow already at v${already.version}, re-registering...`
|
|
133
241
|
);
|
|
134
242
|
const r = await run(
|
|
135
243
|
"claude",
|
|
136
|
-
["plugin", "
|
|
244
|
+
["plugin", "install", "--scope", "user", "curdx-flow@curdx-flow-marketplace"],
|
|
137
245
|
{ silent: true }
|
|
138
246
|
);
|
|
139
247
|
if (r.code !== 0) {
|
|
140
|
-
log.
|
|
141
|
-
|
|
142
|
-
} else {
|
|
143
|
-
log.ok(`curdx-flow updated to v${shippedVersion}`);
|
|
248
|
+
log.err(`Install failed: ${r.stderr.trim() || r.stdout.trim()}`);
|
|
249
|
+
process.exit(1);
|
|
144
250
|
}
|
|
251
|
+
log.ok(`curdx-flow re-registered at v${shippedVersion}`);
|
|
252
|
+
} else if (already && shippedVersion) {
|
|
253
|
+
// Existing install, different version — frame as an upgrade.
|
|
254
|
+
log.info(
|
|
255
|
+
`curdx-flow v${already.version} → v${shippedVersion}, installing...`
|
|
256
|
+
);
|
|
257
|
+
const r = await run(
|
|
258
|
+
"claude",
|
|
259
|
+
["plugin", "install", "--scope", "user", "curdx-flow@curdx-flow-marketplace"],
|
|
260
|
+
{ silent: true }
|
|
261
|
+
);
|
|
262
|
+
if (r.code !== 0) {
|
|
263
|
+
log.err(`Install failed: ${r.stderr.trim() || r.stdout.trim()}`);
|
|
264
|
+
process.exit(1);
|
|
265
|
+
}
|
|
266
|
+
log.ok(`curdx-flow upgraded to v${shippedVersion}`);
|
|
145
267
|
} else if (already) {
|
|
146
|
-
|
|
268
|
+
// shippedVersion unknown (e.g. online install) — best we can do is report
|
|
269
|
+
// the previous version and let `claude plugin install` idempotently
|
|
270
|
+
// re-register.
|
|
271
|
+
log.info(
|
|
272
|
+
`curdx-flow v${already.version} detected, re-registering...`
|
|
273
|
+
);
|
|
274
|
+
const r = await run(
|
|
275
|
+
"claude",
|
|
276
|
+
["plugin", "install", "--scope", "user", "curdx-flow@curdx-flow-marketplace"],
|
|
277
|
+
{ silent: true }
|
|
278
|
+
);
|
|
279
|
+
if (r.code !== 0) {
|
|
280
|
+
log.err(`Install failed: ${r.stderr.trim() || r.stdout.trim()}`);
|
|
281
|
+
process.exit(1);
|
|
282
|
+
}
|
|
283
|
+
log.ok(`curdx-flow re-registered`);
|
|
147
284
|
} else {
|
|
148
285
|
const r = await run(
|
|
149
286
|
"claude",
|
|
150
|
-
["plugin", "install", "curdx-flow@curdx-flow-marketplace"],
|
|
287
|
+
["plugin", "install", "--scope", "user", "curdx-flow@curdx-flow-marketplace"],
|
|
151
288
|
{ silent: true }
|
|
152
289
|
);
|
|
153
290
|
if (r.code !== 0) {
|
|
154
291
|
log.err(`Install failed: ${r.stderr.trim() || r.stdout.trim()}`);
|
|
155
292
|
process.exit(1);
|
|
156
293
|
}
|
|
157
|
-
|
|
294
|
+
if (shippedVersion) {
|
|
295
|
+
log.ok(`curdx-flow v${shippedVersion} installed`);
|
|
296
|
+
} else {
|
|
297
|
+
log.ok("curdx-flow installed");
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// ---------- Step 3.5: Install required plugins + register user-level MCPs ----------
|
|
302
|
+
log.blank();
|
|
303
|
+
log.info("Installing required Claude Code plugins...");
|
|
304
|
+
for (const plugin of REQUIRED_PLUGINS) {
|
|
305
|
+
console.log(` ${color.cyan("▸")} Installing ${color.bold(plugin.name)}...`);
|
|
306
|
+
const ma = await run(
|
|
307
|
+
"claude",
|
|
308
|
+
["plugin", "marketplace", "add", "--scope", plugin.scope, plugin.marketplaceSource],
|
|
309
|
+
{ silent: true }
|
|
310
|
+
);
|
|
311
|
+
if (ma.code !== 0 && !ma.stderr.includes("already")) {
|
|
312
|
+
log.warn(` marketplace add warning: ${ma.stderr.trim().split("\n")[0]}`);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const ir = await run(
|
|
316
|
+
"claude",
|
|
317
|
+
["plugin", "install", "--scope", plugin.scope, plugin.installSpec],
|
|
318
|
+
{ silent: true }
|
|
319
|
+
);
|
|
320
|
+
if (ir.code === 0) {
|
|
321
|
+
console.log(` ${color.green("✓")} ${plugin.name} installed`);
|
|
322
|
+
|
|
323
|
+
// Post-install: API key configuration
|
|
324
|
+
if (plugin.requiresConfig && plugin.configType === "apiKey" && !yes) {
|
|
325
|
+
await promptPluginConfig(plugin, language, config);
|
|
326
|
+
}
|
|
327
|
+
} else {
|
|
328
|
+
console.log(
|
|
329
|
+
` ${color.red("✗")} ${plugin.name} install failed: ${ir.stderr.trim().split("\n").pop()}`
|
|
330
|
+
);
|
|
331
|
+
console.log(
|
|
332
|
+
color.dim(
|
|
333
|
+
` Run manually: claude plugin marketplace add --scope ${plugin.scope} ${plugin.marketplaceSource}`
|
|
334
|
+
)
|
|
335
|
+
);
|
|
336
|
+
console.log(
|
|
337
|
+
color.dim(
|
|
338
|
+
` Then: claude plugin install --scope ${plugin.scope} ${plugin.installSpec}`
|
|
339
|
+
)
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Beta.12: direct MCPs migrated from plugin.json bundling. See cli/registry.js
|
|
345
|
+
// for the rationale. Context7 now uses Upstash's official plugin instead.
|
|
346
|
+
log.blank();
|
|
347
|
+
log.info("Registering required MCP servers (user-level)...");
|
|
348
|
+
const existingUserMcps = readUserMcpConfig();
|
|
349
|
+
for (const mcp of BUNDLED_MCPS) {
|
|
350
|
+
if (mcp.preserveExisting && existingUserMcps.has(mcp.name)) {
|
|
351
|
+
const existing = existingUserMcps.get(mcp.name);
|
|
352
|
+
log.info(
|
|
353
|
+
` ${mcp.name.padEnd(22)} ${color.dim(`already registered (${(existing.args || []).join(" ")}) — preserving`)}`
|
|
354
|
+
);
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
const r = await run(
|
|
358
|
+
"claude",
|
|
359
|
+
["mcp", "add", "--scope", "user", mcp.name, "--", mcp.command, ...mcp.args],
|
|
360
|
+
{ silent: true }
|
|
361
|
+
);
|
|
362
|
+
if (r.code === 0) {
|
|
363
|
+
log.ok(` ${mcp.name.padEnd(22)} ${color.dim("registered")}`);
|
|
364
|
+
} else if (r.stderr.includes("already exists")) {
|
|
365
|
+
log.info(` ${mcp.name.padEnd(22)} ${color.dim("already exists — skipped")}`);
|
|
366
|
+
} else {
|
|
367
|
+
log.warn(
|
|
368
|
+
` ${mcp.name.padEnd(22)} registration failed: ${r.stderr.trim().split("\n").pop()}`
|
|
369
|
+
);
|
|
370
|
+
log.info(
|
|
371
|
+
` Run manually: claude mcp add --scope user ${mcp.name} -- ${mcp.command} ${mcp.args.join(" ")}`
|
|
372
|
+
);
|
|
373
|
+
}
|
|
158
374
|
}
|
|
159
375
|
|
|
160
376
|
// ---------- Step 4: Recommended plugins ----------
|
|
161
377
|
log.blank();
|
|
162
|
-
log.step(4,
|
|
378
|
+
log.step(4, 5, "Recommended plugins");
|
|
163
379
|
|
|
164
380
|
if (noDeps) {
|
|
165
381
|
log.info("Skipping recommended plugins (--no-deps)");
|
|
@@ -171,18 +387,32 @@ export async function install(args = []) {
|
|
|
171
387
|
if (all) {
|
|
172
388
|
toInstall = RECOMMENDED.map((r) => r.name);
|
|
173
389
|
log.info("--all mode: installing all recommended");
|
|
390
|
+
} else if (yes) {
|
|
391
|
+
// --yes mode: install all not-yet-installed plugins
|
|
392
|
+
const currentlyInstalled = new Set(listPlugins().map((p) => p.name));
|
|
393
|
+
toInstall = RECOMMENDED
|
|
394
|
+
.filter((r) => !currentlyInstalled.has(r.name))
|
|
395
|
+
.map((r) => r.name);
|
|
396
|
+
log.info(`--yes mode: installing ${toInstall.length} recommended plugins`);
|
|
174
397
|
} else {
|
|
175
398
|
const currentlyInstalled = new Set(listPlugins().map((p) => p.name));
|
|
176
|
-
const
|
|
177
|
-
label: `${color.bold(r.name)}${currentlyInstalled.has(r.name) ? color.green(" (installed)") : ""}`,
|
|
399
|
+
const options = RECOMMENDED.map((r) => ({
|
|
178
400
|
value: r.name,
|
|
401
|
+
label: `${r.name}${currentlyInstalled.has(r.name) ? " (installed)" : ""}`,
|
|
179
402
|
hint: r.hint,
|
|
180
403
|
}));
|
|
181
|
-
const
|
|
182
|
-
.
|
|
183
|
-
.
|
|
404
|
+
const initialValues = RECOMMENDED
|
|
405
|
+
.filter((r) => !currentlyInstalled.has(r.name))
|
|
406
|
+
.map((r) => r.name);
|
|
184
407
|
|
|
185
|
-
toInstall = await
|
|
408
|
+
toInstall = await multiselectClack({
|
|
409
|
+
message: language === "zh"
|
|
410
|
+
? "选择要安装的推荐插件(空格切换,回车确认)"
|
|
411
|
+
: "Select recommended plugins to install (space to toggle, enter to confirm)",
|
|
412
|
+
options,
|
|
413
|
+
initialValues,
|
|
414
|
+
required: false,
|
|
415
|
+
});
|
|
186
416
|
}
|
|
187
417
|
|
|
188
418
|
if (!toInstall || toInstall.length === 0) {
|
|
@@ -195,13 +425,13 @@ export async function install(args = []) {
|
|
|
195
425
|
for (const pluginName of toInstall) {
|
|
196
426
|
const rec = RECOMMENDED.find((r) => r.name === pluginName);
|
|
197
427
|
log.blank();
|
|
198
|
-
console.log(` ${color.cyan("
|
|
428
|
+
console.log(` ${color.cyan("▸")} Installing ${color.bold(rec.name)}...`);
|
|
199
429
|
|
|
200
430
|
// 1. Add marketplace (if needed)
|
|
201
|
-
if (rec.
|
|
431
|
+
if (rec.marketplaceSource) {
|
|
202
432
|
const ma = await run(
|
|
203
433
|
"claude",
|
|
204
|
-
["plugin", "marketplace", "add", rec.
|
|
434
|
+
["plugin", "marketplace", "add", "--scope", rec.scope, rec.marketplaceSource],
|
|
205
435
|
{ silent: true }
|
|
206
436
|
);
|
|
207
437
|
if (ma.code !== 0 && !ma.stderr.includes("already")) {
|
|
@@ -211,7 +441,7 @@ export async function install(args = []) {
|
|
|
211
441
|
}
|
|
212
442
|
|
|
213
443
|
// 2. Install
|
|
214
|
-
const ir = await run("claude", ["plugin", "install", rec.installSpec], {
|
|
444
|
+
const ir = await run("claude", ["plugin", "install", "--scope", rec.scope, rec.installSpec], {
|
|
215
445
|
silent: true,
|
|
216
446
|
});
|
|
217
447
|
if (ir.code === 0) {
|
|
@@ -220,7 +450,7 @@ export async function install(args = []) {
|
|
|
220
450
|
// 3. Post-install hook for claude-mem: its .mcp.json hard-codes `bun`,
|
|
221
451
|
// but ~/.bun/bin is not on PATH when Claude Code spawns the MCP server.
|
|
222
452
|
// Auto-create a PATH-visible symlink to fix it.
|
|
223
|
-
if (rec.
|
|
453
|
+
if (rec.postInstall === "claude-mem-runtimes") {
|
|
224
454
|
const r = ensureClaudeMemRuntimes();
|
|
225
455
|
for (const [name, res] of Object.entries(r)) {
|
|
226
456
|
if (res.status === "linked") {
|
|
@@ -245,7 +475,7 @@ export async function install(args = []) {
|
|
|
245
475
|
);
|
|
246
476
|
console.log(
|
|
247
477
|
color.dim(
|
|
248
|
-
` Run manually: claude plugin install ${rec.installSpec}`
|
|
478
|
+
` Run manually: claude plugin install --scope ${rec.scope} ${rec.installSpec}`
|
|
249
479
|
)
|
|
250
480
|
);
|
|
251
481
|
}
|
|
@@ -253,11 +483,13 @@ export async function install(args = []) {
|
|
|
253
483
|
|
|
254
484
|
// ---------- Step 5: inject global protocols ----------
|
|
255
485
|
log.blank();
|
|
256
|
-
|
|
486
|
+
log.step(5, 5, "Injecting global protocols into ~/.claude/CLAUDE.md...");
|
|
257
487
|
try {
|
|
258
488
|
const r = injectGlobalProtocols();
|
|
259
489
|
if (r.action === "created") {
|
|
260
490
|
log.ok(`Global protocols injected ${color.dim(`(${GLOBAL_CLAUDE_MD})`)}`);
|
|
491
|
+
} else if (r.action === "appended") {
|
|
492
|
+
log.ok(`Global protocols appended ${color.dim(`(${GLOBAL_CLAUDE_MD})`)}`);
|
|
261
493
|
} else if (r.action === "upgraded") {
|
|
262
494
|
log.ok(`Global protocols upgraded ${color.dim(`(${GLOBAL_CLAUDE_MD})`)}`);
|
|
263
495
|
} else {
|
|
@@ -270,13 +502,155 @@ export async function install(args = []) {
|
|
|
270
502
|
printNextSteps();
|
|
271
503
|
}
|
|
272
504
|
|
|
505
|
+
/**
|
|
506
|
+
* Check for CLI updates and auto-update if available
|
|
507
|
+
* Returns { updated: boolean, version?: string }
|
|
508
|
+
*/
|
|
509
|
+
async function checkAndUpdateSelf() {
|
|
510
|
+
try {
|
|
511
|
+
// Check if globally installed
|
|
512
|
+
const globalPath = runSync("npm", ["root", "-g"]).stdout.trim();
|
|
513
|
+
const installedPath = join(globalPath, "@curdx/flow");
|
|
514
|
+
|
|
515
|
+
if (!existsSync(installedPath)) {
|
|
516
|
+
// Not globally installed, skip update
|
|
517
|
+
return { updated: false };
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Check npm registry for latest version
|
|
521
|
+
log.info("Checking for CLI updates...");
|
|
522
|
+
const res = runSync("npm", ["view", "@curdx/flow", "version"]);
|
|
523
|
+
|
|
524
|
+
if (res.code !== 0) {
|
|
525
|
+
// Registry check failed, continue with current version
|
|
526
|
+
return { updated: false };
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
const latestVersion = res.stdout.trim();
|
|
530
|
+
const currentVersion = VERSION;
|
|
531
|
+
|
|
532
|
+
if (latestVersion === currentVersion) {
|
|
533
|
+
log.ok(`CLI is up to date (v${currentVersion})`);
|
|
534
|
+
return { updated: false };
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// Compare versions (simple string comparison works for semver)
|
|
538
|
+
if (latestVersion > currentVersion) {
|
|
539
|
+
log.info(`New version available: v${currentVersion} → v${latestVersion}`);
|
|
540
|
+
log.info("Updating CLI...");
|
|
541
|
+
|
|
542
|
+
const updateRes = await run("npm", ["install", "-g", "@curdx/flow@latest"], {
|
|
543
|
+
silent: false,
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
if (updateRes.code === 0) {
|
|
547
|
+
log.ok(`CLI updated to v${latestVersion}`);
|
|
548
|
+
return { updated: true, version: latestVersion };
|
|
549
|
+
} else {
|
|
550
|
+
log.warn("CLI update failed, continuing with current version");
|
|
551
|
+
return { updated: false };
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
return { updated: false };
|
|
556
|
+
} catch (err) {
|
|
557
|
+
// Update check failed, continue silently
|
|
558
|
+
return { updated: false };
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* Prompt for plugin-specific configuration (e.g., API keys)
|
|
564
|
+
*/
|
|
565
|
+
async function promptPluginConfig(plugin, language, config) {
|
|
566
|
+
if (plugin.name === "context7-plugin") {
|
|
567
|
+
log.blank();
|
|
568
|
+
await note(
|
|
569
|
+
language === "zh"
|
|
570
|
+
? "Context7 需要 API key 才能使用。\n获取 API key: https://console.upstash.com/context7"
|
|
571
|
+
: "Context7 requires an API key to function.\nGet your API key at: https://console.upstash.com/context7",
|
|
572
|
+
language === "zh" ? "配置 Context7" : "Configure Context7"
|
|
573
|
+
);
|
|
574
|
+
|
|
575
|
+
const apiKey = await text({
|
|
576
|
+
message: language === "zh"
|
|
577
|
+
? "输入你的 Context7 API key(或按 Enter 跳过)"
|
|
578
|
+
: "Enter your Context7 API key (or press Enter to skip)",
|
|
579
|
+
placeholder: "ctx7_...",
|
|
580
|
+
validate: (value) => {
|
|
581
|
+
if (!value) return; // Allow skip
|
|
582
|
+
if (!value.startsWith("ctx7_")) {
|
|
583
|
+
return language === "zh"
|
|
584
|
+
? "API key 应该以 ctx7_ 开头"
|
|
585
|
+
: "API key should start with ctx7_";
|
|
586
|
+
}
|
|
587
|
+
},
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
if (apiKey) {
|
|
591
|
+
// Save to config for future reference
|
|
592
|
+
config.context7ApiKey = apiKey;
|
|
593
|
+
writeConfig(config);
|
|
594
|
+
|
|
595
|
+
// Add to MCP config with environment variable
|
|
596
|
+
const r = await run(
|
|
597
|
+
"claude",
|
|
598
|
+
["mcp", "add", "--scope", "user", "context7", "--env", `CONTEXT7_API_KEY=${apiKey}`, "--", "npx", "-y", "@upstash/context7-mcp"],
|
|
599
|
+
{ silent: true }
|
|
600
|
+
);
|
|
601
|
+
|
|
602
|
+
if (r.code === 0) {
|
|
603
|
+
log.ok(
|
|
604
|
+
language === "zh"
|
|
605
|
+
? " Context7 API key 已配置"
|
|
606
|
+
: " Context7 API key configured"
|
|
607
|
+
);
|
|
608
|
+
} else if (r.stderr.includes("already exists")) {
|
|
609
|
+
// Update existing MCP server
|
|
610
|
+
await run("claude", ["mcp", "remove", "--scope", "user", "context7"], { silent: true });
|
|
611
|
+
const r2 = await run(
|
|
612
|
+
"claude",
|
|
613
|
+
["mcp", "add", "--scope", "user", "context7", "--env", `CONTEXT7_API_KEY=${apiKey}`, "--", "npx", "-y", "@upstash/context7-mcp"],
|
|
614
|
+
{ silent: true }
|
|
615
|
+
);
|
|
616
|
+
if (r2.code === 0) {
|
|
617
|
+
log.ok(
|
|
618
|
+
language === "zh"
|
|
619
|
+
? " Context7 API key 已更新"
|
|
620
|
+
: " Context7 API key updated"
|
|
621
|
+
);
|
|
622
|
+
}
|
|
623
|
+
} else {
|
|
624
|
+
log.warn(
|
|
625
|
+
language === "zh"
|
|
626
|
+
? ` Context7 MCP 配置失败: ${r.stderr.trim().split("\n").pop()}`
|
|
627
|
+
: ` Context7 MCP configuration failed: ${r.stderr.trim().split("\n").pop()}`
|
|
628
|
+
);
|
|
629
|
+
log.info(
|
|
630
|
+
color.dim(
|
|
631
|
+
language === "zh"
|
|
632
|
+
? ` 手动运行: claude mcp add --scope user context7 --env CONTEXT7_API_KEY=${apiKey} -- npx -y @upstash/context7-mcp`
|
|
633
|
+
: ` Run manually: claude mcp add --scope user context7 --env CONTEXT7_API_KEY=${apiKey} -- npx -y @upstash/context7-mcp`
|
|
634
|
+
)
|
|
635
|
+
);
|
|
636
|
+
}
|
|
637
|
+
} else {
|
|
638
|
+
log.info(
|
|
639
|
+
language === "zh"
|
|
640
|
+
? " 跳过 Context7 配置(稍后可运行 curdx-flow install 重新配置)"
|
|
641
|
+
: " Skipped Context7 configuration (run curdx-flow install later to reconfigure)"
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
273
647
|
function printNextSteps() {
|
|
274
648
|
// Detect whether the CLI is globally installed (curdx-flow on PATH) or
|
|
275
649
|
// the user ran us via npx. Tell them the right invocation each time.
|
|
276
650
|
const cliOnPath = has("curdx-flow");
|
|
277
651
|
const cliCmd = cliOnPath ? "curdx-flow" : "npx @curdx/flow";
|
|
278
652
|
|
|
279
|
-
console.log(`\n${color.bold("
|
|
653
|
+
console.log(`\n${color.bold(`${color.green("✓")} Install complete`)}\n`);
|
|
280
654
|
console.log(`${color.bold("Restart Claude Code")} so the plugin registers all its commands and hooks.\n`);
|
|
281
655
|
console.log(`${color.bold("Next steps")}:\n`);
|
|
282
656
|
console.log(` ${color.dim("# Verify health")}`);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
## Global Protocols (curdx-flow)
|
|
2
|
+
|
|
3
|
+
All operations MUST strictly follow these system constraints:
|
|
4
|
+
|
|
5
|
+
### Language separation
|
|
6
|
+
- **Tool / persistence layer = English**: commit messages, code, comments, file names, function names, PR descriptions, CLI log output, error messages thrown by code, and any artifact persisted to the repository or shown in a developer terminal.
|
|
7
|
+
- **Conversational layer = Simplified Chinese**: chat replies, explanations and reasoning shown directly to the human in a conversation interface (e.g. Claude Code chat).
|
|
8
|
+
|
|
9
|
+
Rationale: English in the persistence/tool layer aligns with developer-tool industry norms (npm/git/cargo are all English) and keeps the codebase internationally collaborable. Chinese in the conversational layer matches the user's language preference. Mixing the two (e.g. Chinese commit messages, Chinese CLI log output) is a violation.
|
|
10
|
+
|
|
11
|
+
### Discovery & reasoning
|
|
12
|
+
- **Library / framework / API questions**: query `context7` MCP first. Do not rely on training memory.
|
|
13
|
+
- **Planning / design / architecture review / epic decomposition**: use `sequential-thinking` MCP with at least 5 thoughts.
|
|
14
|
+
- **Cross-session memory**: query `claude-mem` MCP at task start when available.
|
|
15
|
+
|
|
16
|
+
### Three red lines (inherited from pua)
|
|
17
|
+
1. **Closed loop**: claiming "done"? Provide evidence (build output / passing tests / curl result).
|
|
18
|
+
2. **Fact-driven**: before saying "probably an env issue", verify it. Unverified attribution = blame-shifting.
|
|
19
|
+
3. **Exhaust everything**: before saying "I cannot", complete the systematic 4-stage debugging.
|
|
20
|
+
|
|
21
|
+
> Source: curdx-flow CLI installer (`curdx-flow install`). Remove with: `curdx-flow uninstall --purge`.
|