@curdx/flow 2.0.21 → 2.1.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 +1 -1
- package/CHANGELOG.md +74 -3
- package/README.zh.md +11 -5
- package/agent-preamble/preamble.md +1 -4
- package/cli/install-bundled-mcps.js +37 -0
- package/cli/install-companions.js +16 -252
- package/cli/install-context7-config.js +97 -0
- package/cli/install-recommended-plugins.js +104 -0
- package/cli/install-required-plugins.js +44 -0
- package/cli/protocols-body.md +3 -2
- package/hooks/scripts/session-start.sh +6 -3
- package/package.json +1 -2
- package/schemas/config.schema.json +1 -1
- package/schemas/spec-state.schema.json +1 -1
- package/skills/debug/SKILL.md +104 -0
- package/{commands/help.md → skills/help/SKILL.md} +14 -5
- package/{commands/implement.md → skills/implement/SKILL.md} +10 -151
- package/skills/implement/references/wave-execution.md +162 -0
- package/{commands/review.md → skills/review/SKILL.md} +36 -2
- package/skills/verify/SKILL.md +98 -0
- package/commands/debug.md +0 -199
- package/commands/verify.md +0 -142
- /package/{commands/fast.md → skills/fast/SKILL.md} +0 -0
- /package/{commands/init.md → skills/init/SKILL.md} +0 -0
- /package/{commands/spec.md → skills/spec/SKILL.md} +0 -0
- /package/{commands/start.md → skills/start/SKILL.md} +0 -0
|
@@ -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
|
|
9
|
+
"version": "2.1.0"
|
|
10
10
|
},
|
|
11
11
|
"plugins": [
|
|
12
12
|
{
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "curdx-flow",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
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/CHANGELOG.md
CHANGED
|
@@ -9,9 +9,80 @@ All notable changes to CurdX-Flow will be documented here.
|
|
|
9
9
|
- `cli/install-workflow.js` + `cli/install-self-update.js` — after `npm install -g @curdx/flow@latest`, the self-update restart spawned a bare `curdx-flow`, which fails under `npx` (PATH not guaranteed) or when the global bin dir isn't on PATH, surfacing as `sh: curdx-flow: command not found`. `checkAndUpdateSelf` now returns the absolute entry path of the freshly installed package, and the restart re-launches with `process.execPath` + that absolute script path. No PATH dependency, no shell involvement. Two new unit tests assert the spawn contract.
|
|
10
10
|
- `README.zh.md` — corrected the `smart-ralph` credit URL. The previously listed `Nibzard/smart-ralph` 404s; the repo matching the described feature set ("Spec-driven + Ralph Wiggum loop + Claude Code plugin") is `tzachbon/smart-ralph`.
|
|
11
11
|
|
|
12
|
-
## [
|
|
13
|
-
|
|
14
|
-
###
|
|
12
|
+
## [2.1.0] - 2026-04-23
|
|
13
|
+
|
|
14
|
+
### Changed (commands → skills migration; zero user-visible change)
|
|
15
|
+
|
|
16
|
+
All 9 slash workflows migrate from `commands/*.md` to
|
|
17
|
+
`skills/<name>/SKILL.md`, per Anthropic's recommended plugin layout.
|
|
18
|
+
`/curdx-flow:<name>` still resolves to the same slash paths with the
|
|
19
|
+
same arguments and output. File history is preserved via `git mv`.
|
|
20
|
+
|
|
21
|
+
- `skills/implement/SKILL.md` — progressive-disclosure split to
|
|
22
|
+
`skills/implement/references/wave-execution.md`; body reduced
|
|
23
|
+
from 381 to 240 lines.
|
|
24
|
+
- `/curdx-flow:verify` and `/curdx-flow:debug` — route via
|
|
25
|
+
Anthropic's native `context: fork + agent:` mechanism instead of
|
|
26
|
+
hand-rolled `Task()` dispatch prose. `agents/flow-verifier.md`
|
|
27
|
+
and `agents/flow-debugger.md` are unchanged.
|
|
28
|
+
- `/curdx-flow:review --devex` — new optional flag; wires the
|
|
29
|
+
previously caller-less `gates/devex-gate.md` into the reviewer
|
|
30
|
+
dispatch.
|
|
31
|
+
- `cli/install-companions.js` — split by concern into four modules
|
|
32
|
+
(`install-required-plugins.js`, `install-bundled-mcps.js`,
|
|
33
|
+
`install-recommended-plugins.js`, `install-context7-config.js`).
|
|
34
|
+
`install-companions.js` becomes a re-export barrel; callers in
|
|
35
|
+
`cli/install.js` and `cli/install-workflow.js` unchanged.
|
|
36
|
+
- `package.json.files` — drops `"commands/"` (directory removed);
|
|
37
|
+
`"skills/"` retained.
|
|
38
|
+
- `.gitignore` — anchors the `references/` pattern to the repo root
|
|
39
|
+
(`/references/`) so `skills/<name>/references/` progressive-disclosure
|
|
40
|
+
directories ship with the plugin.
|
|
41
|
+
|
|
42
|
+
### Added (internal test coverage)
|
|
43
|
+
|
|
44
|
+
- `test/skill-surface-parity.test.js` — snapshot of 14 unqualified
|
|
45
|
+
slash names (9 migrated + 5 pre-existing); pins the public
|
|
46
|
+
command surface across every refactor stage.
|
|
47
|
+
- `test/schema-drift.test.js` — strict equality between
|
|
48
|
+
`schemas/*.json` mode enum and documented v2 modes.
|
|
49
|
+
- `test/registry-session-start-parity.test.js` — strict equality
|
|
50
|
+
between `hooks/scripts/session-start.sh` plugin list and
|
|
51
|
+
`cli/registry.js` RECOMMENDED_PLUGINS.
|
|
52
|
+
- `test/language-rule-scan.test.js` — no CJK characters outside
|
|
53
|
+
`cli/**` and an explicit small allow-list.
|
|
54
|
+
- `test/install-required-plugins.test.js`,
|
|
55
|
+
`test/install-bundled-mcps.test.js`,
|
|
56
|
+
`test/install-recommended-plugins.test.js`,
|
|
57
|
+
`test/install-context7-config.test.js` — module-import smoke
|
|
58
|
+
tests for the four split install modules.
|
|
59
|
+
- `test/pack-tarball-smoke.test.js` — `npm pack --dry-run`
|
|
60
|
+
assertions that `commands/` is absent, 14 SKILL.md files and
|
|
61
|
+
`skills/implement/references/` ship, and every top-level
|
|
62
|
+
infrastructure directory is present in the tarball (Acceptance
|
|
63
|
+
#18 from `docs/refactor-plan.md`).
|
|
64
|
+
|
|
65
|
+
Total test count: 85 → 104, all green.
|
|
66
|
+
|
|
67
|
+
### Fixed (drift cleanup)
|
|
68
|
+
|
|
69
|
+
- `schemas/spec-state.schema.json`, `schemas/config.schema.json` —
|
|
70
|
+
remove unreachable `sketch` and `autonomous` mode enum values.
|
|
71
|
+
Both were retired on the user-facing surface in v2 but never
|
|
72
|
+
cleaned from the schemas (see `MIGRATION.md`).
|
|
73
|
+
- `hooks/scripts/session-start.sh` — adds `chrome-devtools-mcp` to
|
|
74
|
+
the dependency-check list, closing the beta.8 drift that
|
|
75
|
+
`cli/registry.js` was supposed to prevent.
|
|
76
|
+
- `agent-preamble/preamble.md` — removes an empty
|
|
77
|
+
`| Task | Guideline |` table header left behind by a prior edit.
|
|
78
|
+
- `README.zh.md` — masthead paragraph rewritten from the pre-v2
|
|
79
|
+
"meta-framework" positioning to the current "discipline layer"
|
|
80
|
+
framing, parallel to `README.md`.
|
|
81
|
+
- `cli/protocols-body.md` — explicit carve-out paragraph allowing
|
|
82
|
+
`cli/` installer menus to be bilingual; every other layer stays
|
|
83
|
+
English for AI / agent adaptation.
|
|
84
|
+
|
|
85
|
+
### Fixed (hooks — previously pending)
|
|
15
86
|
|
|
16
87
|
- `hooks/hooks.json` + `hooks/scripts/inject-karpathy.sh` — migrated the L1 baseline injector from the `InstructionsLoaded` event to `SessionStart` with `matcher: "startup|clear|compact"`. Per the Claude Code hooks docs (code.claude.com/docs/en/hooks), `InstructionsLoaded` is an observability-only event: its hook schema has no `hookSpecificOutput` field, so the injector's `{"hookSpecificOutput":{"hookEventName":"InstructionsLoaded","additionalContext":…}}` payload was rejected at session boot with `Hook JSON output validation failed — (root): Invalid input`, producing the `SessionStart:startup hook error` banner on every Claude Code launch. The `startup|clear|compact` matcher preserves the original "baseline survives compaction" intent. Also updated `docs/architecture.md` and `README.zh.md` hook-event inventories.
|
|
17
88
|
|
package/README.zh.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# CurdX-Flow
|
|
2
2
|
|
|
3
|
-
>
|
|
4
|
-
>
|
|
3
|
+
> **让 Claude Code 不再假装"完成"。**
|
|
4
|
+
> 规格驱动工作流 + 目标反向验证 + Karpathy 4 原则。
|
|
5
5
|
|
|
6
6
|
[](https://www.npmjs.com/package/@curdx/flow)
|
|
7
7
|
[](./LICENSE)
|
|
@@ -10,11 +10,17 @@
|
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
-
##
|
|
13
|
+
## 为什么存在
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
Claude Code 会漂——声称功能跑通但从未跑过测试、用训练数据里陈旧的库 API、把三个问题的代码量塞进六问。
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
CurdX-Flow 是 Claude Code 之上的一层薄**纪律层**,只做三件事,拒绝做更多:
|
|
18
|
+
|
|
19
|
+
1. **一套规格工作流**(研究 → 需求 → 设计 → 任务),用于超出一次性 vibe-code 尺度的特性。
|
|
20
|
+
2. **目标反向验证**——扫描实现是否对齐每一条 FR / AC / AD,揪出桩代码、假完成、未被测试覆盖的验收标准。
|
|
21
|
+
3. **Karpathy 4 原则**(先思考再编码 / 简单优先 / 外科式修改 / 目标驱动)通过常开 hook 和 gate 强制执行。
|
|
22
|
+
|
|
23
|
+
不是框架。不是方法论库。就是一层纪律。
|
|
18
24
|
|
|
19
25
|
## 一览(v2)
|
|
20
26
|
|
|
@@ -74,10 +74,7 @@ Do NOT query context7 for:
|
|
|
74
74
|
|
|
75
75
|
### Structured thinking → sequential-thinking MCP
|
|
76
76
|
|
|
77
|
-
Use `sequential-thinking` proportional to **decision complexity**, not a fixed quota.
|
|
78
|
-
|
|
79
|
-
| Task | Guideline |
|
|
80
|
-
|------|-----------|
|
|
77
|
+
Use `sequential-thinking` proportional to **decision complexity**, not a fixed quota. Any thought count is a **ceiling for genuinely hard cases**, not a floor to hit.
|
|
81
78
|
|
|
82
79
|
**Principle**: running 8 thoughts to pick between Vue and React for a Todo is waste. Running 1 thought to architect a distributed queue is irresponsible. Match effort to stakes.
|
|
83
80
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Register MCP servers that curdx-flow depends on at user level via
|
|
3
|
+
* `claude mcp add`. Source-of-truth list: cli/registry.js BUNDLED_MCPS.
|
|
4
|
+
* Preserves existing user-level entries when mcp.preserveExisting is set.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { addMcp } from "./lib/claude-ops.js";
|
|
8
|
+
import { BUNDLED_MCPS } from "./registry.js";
|
|
9
|
+
import { color, log, readUserMcpConfig, resultLastLine } from "./utils.js";
|
|
10
|
+
|
|
11
|
+
export async function registerBundledMcps() {
|
|
12
|
+
log.blank();
|
|
13
|
+
log.info("Registering required MCP servers (user-level)...");
|
|
14
|
+
const existingUserMcps = readUserMcpConfig();
|
|
15
|
+
for (const mcp of BUNDLED_MCPS) {
|
|
16
|
+
if (mcp.preserveExisting && existingUserMcps.has(mcp.name)) {
|
|
17
|
+
const existing = existingUserMcps.get(mcp.name);
|
|
18
|
+
log.info(
|
|
19
|
+
` ${mcp.name.padEnd(22)} ${color.dim(`already registered (${(existing.args || []).join(" ")}) — preserving`)}`
|
|
20
|
+
);
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
const r = await addMcp(mcp);
|
|
24
|
+
if (r.code === 0) {
|
|
25
|
+
log.ok(` ${mcp.name.padEnd(22)} ${color.dim("registered")}`);
|
|
26
|
+
} else if (r.stderr.includes("already exists")) {
|
|
27
|
+
log.info(` ${mcp.name.padEnd(22)} ${color.dim("already exists — skipped")}`);
|
|
28
|
+
} else {
|
|
29
|
+
log.warn(
|
|
30
|
+
` ${mcp.name.padEnd(22)} registration failed: ${resultLastLine(r)}`
|
|
31
|
+
);
|
|
32
|
+
log.info(
|
|
33
|
+
` Run manually: claude mcp add --scope user ${mcp.name} -- ${mcp.command} ${mcp.args.join(" ")}`
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -1,252 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
installPlugin,
|
|
18
|
-
removeMcp,
|
|
19
|
-
} from "./lib/claude-ops.js";
|
|
20
|
-
|
|
21
|
-
const RECOMMENDED = RECOMMENDED_PLUGINS;
|
|
22
|
-
const CONTEXT7_COMMAND = "npx";
|
|
23
|
-
const CONTEXT7_ARGS = ["-y", "@upstash/context7-mcp"];
|
|
24
|
-
|
|
25
|
-
export async function installRequiredPlugins({ yes, language, config }) {
|
|
26
|
-
log.blank();
|
|
27
|
-
log.info("Installing required Claude Code plugins...");
|
|
28
|
-
for (const plugin of REQUIRED_PLUGINS) {
|
|
29
|
-
console.log(` ${color.cyan("▸")} Installing ${color.bold(plugin.name)}...`);
|
|
30
|
-
const ma = await addPluginMarketplace(plugin);
|
|
31
|
-
if (ma.code !== 0 && !ma.stderr.includes("already")) {
|
|
32
|
-
log.warn(` marketplace add warning: ${ma.stderr.trim().split("\n")[0]}`);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const ir = await installPlugin(plugin);
|
|
36
|
-
if (ir.code === 0) {
|
|
37
|
-
console.log(` ${color.green("✓")} ${plugin.name} installed`);
|
|
38
|
-
|
|
39
|
-
if (plugin.requiresConfig && plugin.configType === "apiKey" && !yes) {
|
|
40
|
-
await promptPluginConfig(plugin, language, config);
|
|
41
|
-
}
|
|
42
|
-
} else {
|
|
43
|
-
console.log(
|
|
44
|
-
` ${color.red("✗")} ${plugin.name} install failed: ${resultLastLine(ir)}`
|
|
45
|
-
);
|
|
46
|
-
console.log(
|
|
47
|
-
color.dim(
|
|
48
|
-
` Run manually: claude plugin marketplace add --scope ${plugin.scope} ${plugin.marketplaceSource}`
|
|
49
|
-
)
|
|
50
|
-
);
|
|
51
|
-
console.log(
|
|
52
|
-
color.dim(
|
|
53
|
-
` Then: claude plugin install --scope ${plugin.scope} ${plugin.installSpec}`
|
|
54
|
-
)
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export async function registerBundledMcps() {
|
|
61
|
-
log.blank();
|
|
62
|
-
log.info("Registering required MCP servers (user-level)...");
|
|
63
|
-
const existingUserMcps = readUserMcpConfig();
|
|
64
|
-
for (const mcp of BUNDLED_MCPS) {
|
|
65
|
-
if (mcp.preserveExisting && existingUserMcps.has(mcp.name)) {
|
|
66
|
-
const existing = existingUserMcps.get(mcp.name);
|
|
67
|
-
log.info(
|
|
68
|
-
` ${mcp.name.padEnd(22)} ${color.dim(`already registered (${(existing.args || []).join(" ")}) — preserving`)}`
|
|
69
|
-
);
|
|
70
|
-
continue;
|
|
71
|
-
}
|
|
72
|
-
const r = await addMcp(mcp);
|
|
73
|
-
if (r.code === 0) {
|
|
74
|
-
log.ok(` ${mcp.name.padEnd(22)} ${color.dim("registered")}`);
|
|
75
|
-
} else if (r.stderr.includes("already exists")) {
|
|
76
|
-
log.info(` ${mcp.name.padEnd(22)} ${color.dim("already exists — skipped")}`);
|
|
77
|
-
} else {
|
|
78
|
-
log.warn(
|
|
79
|
-
` ${mcp.name.padEnd(22)} registration failed: ${resultLastLine(r)}`
|
|
80
|
-
);
|
|
81
|
-
log.info(
|
|
82
|
-
` Run manually: claude mcp add --scope user ${mcp.name} -- ${mcp.command} ${mcp.args.join(" ")}`
|
|
83
|
-
);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export async function installRecommendedPlugins({ all, yes, language }) {
|
|
89
|
-
log.blank();
|
|
90
|
-
log.step(4, 5, "Recommended plugins");
|
|
91
|
-
|
|
92
|
-
let toInstall;
|
|
93
|
-
if (all) {
|
|
94
|
-
toInstall = RECOMMENDED.map((r) => r.name);
|
|
95
|
-
log.info("--all mode: installing all recommended");
|
|
96
|
-
} else if (yes) {
|
|
97
|
-
const currentlyInstalled = new Set(listPlugins().map((p) => p.name));
|
|
98
|
-
toInstall = RECOMMENDED
|
|
99
|
-
.filter((r) => !currentlyInstalled.has(r.name))
|
|
100
|
-
.map((r) => r.name);
|
|
101
|
-
log.info(`--yes mode: installing ${toInstall.length} recommended plugins`);
|
|
102
|
-
} else {
|
|
103
|
-
const currentlyInstalled = new Set(listPlugins().map((p) => p.name));
|
|
104
|
-
const options = RECOMMENDED.map((r) => ({
|
|
105
|
-
value: r.name,
|
|
106
|
-
label: `${r.name}${currentlyInstalled.has(r.name) ? " (installed)" : ""}`,
|
|
107
|
-
hint: r.hint,
|
|
108
|
-
}));
|
|
109
|
-
const initialValues = RECOMMENDED.map((r) => r.name);
|
|
110
|
-
|
|
111
|
-
toInstall = await multiselectClack({
|
|
112
|
-
message: language === "zh"
|
|
113
|
-
? "选择要安装的推荐插件(空格切换,回车确认)"
|
|
114
|
-
: "Select recommended plugins to install (space to toggle, enter to confirm)",
|
|
115
|
-
options,
|
|
116
|
-
initialValues,
|
|
117
|
-
required: false,
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if (!toInstall || toInstall.length === 0) {
|
|
122
|
-
log.info("No recommended plugins selected, skipping");
|
|
123
|
-
return false;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
for (const pluginName of toInstall) {
|
|
127
|
-
const rec = RECOMMENDED.find((r) => r.name === pluginName);
|
|
128
|
-
log.blank();
|
|
129
|
-
console.log(` ${color.cyan("▸")} Installing ${color.bold(rec.name)}...`);
|
|
130
|
-
|
|
131
|
-
if (rec.marketplaceSource) {
|
|
132
|
-
const ma = await addPluginMarketplace(rec);
|
|
133
|
-
if (ma.code !== 0 && !ma.stderr.includes("already")) {
|
|
134
|
-
log.warn(` marketplace add warning: ${ma.stderr.trim().split("\n")[0]}`);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const ir = await installPlugin(rec);
|
|
139
|
-
if (ir.code === 0) {
|
|
140
|
-
console.log(` ${color.green("✓")} ${rec.name} installed`);
|
|
141
|
-
|
|
142
|
-
if (rec.postInstall === "claude-mem-runtimes") {
|
|
143
|
-
const r = ensureClaudeMemRuntimes();
|
|
144
|
-
for (const [name, res] of Object.entries(r)) {
|
|
145
|
-
if (res.status === "linked") {
|
|
146
|
-
console.log(
|
|
147
|
-
` ${color.green("✓")} ${name} → PATH symlink created ${color.dim(`(${res.link} → ${res.path})`)}`
|
|
148
|
-
);
|
|
149
|
-
} else if (res.status === "missing") {
|
|
150
|
-
console.log(
|
|
151
|
-
` ${color.yellow("⚠")} ${name} not installed ${color.dim("(claude-mem will auto-install on first run; or run: curdx-flow doctor)")}`
|
|
152
|
-
);
|
|
153
|
-
} else if (res.status === "path-unwritable") {
|
|
154
|
-
console.log(
|
|
155
|
-
` ${color.yellow("⚠")} ${name} installed ${color.dim(`(${res.path}) but no writable PATH location — add export PATH=\"${res.path.split("/").slice(0,-1).join("/")}:$PATH\" to your shell rc`)}`
|
|
156
|
-
);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
} else {
|
|
161
|
-
console.log(
|
|
162
|
-
` ${color.red("✗")} ${rec.name} install failed: ${resultLastLine(ir)}`
|
|
163
|
-
);
|
|
164
|
-
console.log(
|
|
165
|
-
color.dim(
|
|
166
|
-
` Run manually: claude plugin install --scope ${rec.scope} ${rec.installSpec}`
|
|
167
|
-
)
|
|
168
|
-
);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
return true;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
async function promptPluginConfig(plugin, language, config) {
|
|
175
|
-
if (plugin.name !== "context7-plugin") return;
|
|
176
|
-
|
|
177
|
-
log.blank();
|
|
178
|
-
await note(
|
|
179
|
-
language === "zh"
|
|
180
|
-
? "Context7 需要 API key 才能使用。\n获取 API key: https://console.upstash.com/context7"
|
|
181
|
-
: "Context7 requires an API key to function.\nGet your API key at: https://console.upstash.com/context7",
|
|
182
|
-
language === "zh" ? "配置 Context7" : "Configure Context7"
|
|
183
|
-
);
|
|
184
|
-
|
|
185
|
-
const apiKey = await text({
|
|
186
|
-
message: language === "zh"
|
|
187
|
-
? "输入你的 Context7 API key(或按 Enter 跳过)"
|
|
188
|
-
: "Enter your Context7 API key (or press Enter to skip)",
|
|
189
|
-
placeholder: "ctx7sk-...",
|
|
190
|
-
validate: (value) => {
|
|
191
|
-
if (!value) return;
|
|
192
|
-
if (!value.startsWith("ctx7sk-") && !value.startsWith("ctx7_")) {
|
|
193
|
-
return language === "zh"
|
|
194
|
-
? "API key 应该以 ctx7sk- 或 ctx7_ 开头"
|
|
195
|
-
: "API key should start with ctx7sk- or ctx7_";
|
|
196
|
-
}
|
|
197
|
-
},
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
if (apiKey) {
|
|
201
|
-
config.context7ApiKey = apiKey;
|
|
202
|
-
writeConfig(config);
|
|
203
|
-
|
|
204
|
-
const r = await addMcp(buildContext7Mcp(apiKey));
|
|
205
|
-
|
|
206
|
-
if (r.code === 0) {
|
|
207
|
-
log.ok(
|
|
208
|
-
language === "zh"
|
|
209
|
-
? " Context7 API key 已配置"
|
|
210
|
-
: " Context7 API key configured"
|
|
211
|
-
);
|
|
212
|
-
} else if (r.stderr.includes("already exists")) {
|
|
213
|
-
await removeMcp({ name: "context7" });
|
|
214
|
-
const r2 = await addMcp(buildContext7Mcp(apiKey));
|
|
215
|
-
if (r2.code === 0) {
|
|
216
|
-
log.ok(
|
|
217
|
-
language === "zh"
|
|
218
|
-
? " Context7 API key 已更新"
|
|
219
|
-
: " Context7 API key updated"
|
|
220
|
-
);
|
|
221
|
-
}
|
|
222
|
-
} else {
|
|
223
|
-
log.warn(
|
|
224
|
-
language === "zh"
|
|
225
|
-
? ` Context7 MCP 配置失败: ${resultLastLine(r)}`
|
|
226
|
-
: ` Context7 MCP configuration failed: ${resultLastLine(r)}`
|
|
227
|
-
);
|
|
228
|
-
log.info(
|
|
229
|
-
color.dim(
|
|
230
|
-
language === "zh"
|
|
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(" ")}`
|
|
233
|
-
)
|
|
234
|
-
);
|
|
235
|
-
}
|
|
236
|
-
} else {
|
|
237
|
-
log.info(
|
|
238
|
-
language === "zh"
|
|
239
|
-
? " 跳过 Context7 配置(稍后可运行 curdx-flow install 重新配置)"
|
|
240
|
-
: " Skipped Context7 configuration (run curdx-flow install later to reconfigure)"
|
|
241
|
-
);
|
|
242
|
-
}
|
|
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
|
+
/**
|
|
2
|
+
* Re-export barrel preserving the pre-Stage-7 import surface. New callers
|
|
3
|
+
* should import directly from the concern-specific modules below instead
|
|
4
|
+
* of going through this barrel. Kept in place so existing imports in
|
|
5
|
+
* cli/install.js and cli/install-workflow.js keep working unchanged.
|
|
6
|
+
*
|
|
7
|
+
* Concern split (see docs/refactor-plan.md Stage 7):
|
|
8
|
+
* install-required-plugins.js — required Claude Code companion plugins
|
|
9
|
+
* install-bundled-mcps.js — user-level MCP registration
|
|
10
|
+
* install-recommended-plugins.js — optional recommended plugins
|
|
11
|
+
* install-context7-config.js — context7 API-key prompt (private to required)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export { installRequiredPlugins } from "./install-required-plugins.js";
|
|
15
|
+
export { registerBundledMcps } from "./install-bundled-mcps.js";
|
|
16
|
+
export { installRecommendedPlugins } from "./install-recommended-plugins.js";
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context7 API-key configuration during install. Private to the
|
|
3
|
+
* required-plugins concern; not re-exported from install-companions.js.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { addMcp, removeMcp } from "./lib/claude-ops.js";
|
|
7
|
+
import {
|
|
8
|
+
color,
|
|
9
|
+
log,
|
|
10
|
+
note,
|
|
11
|
+
resultLastLine,
|
|
12
|
+
text,
|
|
13
|
+
writeConfig,
|
|
14
|
+
} from "./utils.js";
|
|
15
|
+
|
|
16
|
+
const CONTEXT7_COMMAND = "npx";
|
|
17
|
+
const CONTEXT7_ARGS = ["-y", "@upstash/context7-mcp"];
|
|
18
|
+
|
|
19
|
+
export async function installContext7Config(plugin, language, config) {
|
|
20
|
+
if (plugin.name !== "context7-plugin") return;
|
|
21
|
+
|
|
22
|
+
log.blank();
|
|
23
|
+
await note(
|
|
24
|
+
language === "zh"
|
|
25
|
+
? "Context7 需要 API key 才能使用。\n获取 API key: https://console.upstash.com/context7"
|
|
26
|
+
: "Context7 requires an API key to function.\nGet your API key at: https://console.upstash.com/context7",
|
|
27
|
+
language === "zh" ? "配置 Context7" : "Configure Context7"
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
const apiKey = await text({
|
|
31
|
+
message: language === "zh"
|
|
32
|
+
? "输入你的 Context7 API key(或按 Enter 跳过)"
|
|
33
|
+
: "Enter your Context7 API key (or press Enter to skip)",
|
|
34
|
+
placeholder: "ctx7sk-...",
|
|
35
|
+
validate: (value) => {
|
|
36
|
+
if (!value) return;
|
|
37
|
+
if (!value.startsWith("ctx7sk-") && !value.startsWith("ctx7_")) {
|
|
38
|
+
return language === "zh"
|
|
39
|
+
? "API key 应该以 ctx7sk- 或 ctx7_ 开头"
|
|
40
|
+
: "API key should start with ctx7sk- or ctx7_";
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
if (apiKey) {
|
|
46
|
+
config.context7ApiKey = apiKey;
|
|
47
|
+
writeConfig(config);
|
|
48
|
+
|
|
49
|
+
const r = await addMcp(buildContext7Mcp(apiKey));
|
|
50
|
+
|
|
51
|
+
if (r.code === 0) {
|
|
52
|
+
log.ok(
|
|
53
|
+
language === "zh"
|
|
54
|
+
? " Context7 API key 已配置"
|
|
55
|
+
: " Context7 API key configured"
|
|
56
|
+
);
|
|
57
|
+
} else if (r.stderr.includes("already exists")) {
|
|
58
|
+
await removeMcp({ name: "context7" });
|
|
59
|
+
const r2 = await addMcp(buildContext7Mcp(apiKey));
|
|
60
|
+
if (r2.code === 0) {
|
|
61
|
+
log.ok(
|
|
62
|
+
language === "zh"
|
|
63
|
+
? " Context7 API key 已更新"
|
|
64
|
+
: " Context7 API key updated"
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
log.warn(
|
|
69
|
+
language === "zh"
|
|
70
|
+
? ` Context7 MCP 配置失败: ${resultLastLine(r)}`
|
|
71
|
+
: ` Context7 MCP configuration failed: ${resultLastLine(r)}`
|
|
72
|
+
);
|
|
73
|
+
log.info(
|
|
74
|
+
color.dim(
|
|
75
|
+
language === "zh"
|
|
76
|
+
? ` 手动运行: claude mcp add --scope user context7 --env CONTEXT7_API_KEY=${apiKey} -- ${CONTEXT7_COMMAND} ${CONTEXT7_ARGS.join(" ")}`
|
|
77
|
+
: ` Run manually: claude mcp add --scope user context7 --env CONTEXT7_API_KEY=${apiKey} -- ${CONTEXT7_COMMAND} ${CONTEXT7_ARGS.join(" ")}`
|
|
78
|
+
)
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
} else {
|
|
82
|
+
log.info(
|
|
83
|
+
language === "zh"
|
|
84
|
+
? " 跳过 Context7 配置(稍后可运行 curdx-flow install 重新配置)"
|
|
85
|
+
: " Skipped Context7 configuration (run curdx-flow install later to reconfigure)"
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function buildContext7Mcp(apiKey) {
|
|
91
|
+
return {
|
|
92
|
+
name: "context7",
|
|
93
|
+
env: [`CONTEXT7_API_KEY=${apiKey}`],
|
|
94
|
+
command: CONTEXT7_COMMAND,
|
|
95
|
+
args: CONTEXT7_ARGS,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Install optional recommended companion plugins. List source-of-truth:
|
|
3
|
+
* cli/registry.js RECOMMENDED_PLUGINS. Handles --all / --yes / interactive
|
|
4
|
+
* multiselect and the claude-mem bun/uv PATH symlink post-install step.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { addPluginMarketplace, installPlugin } from "./lib/claude-ops.js";
|
|
8
|
+
import { RECOMMENDED_PLUGINS } from "./registry.js";
|
|
9
|
+
import {
|
|
10
|
+
color,
|
|
11
|
+
ensureClaudeMemRuntimes,
|
|
12
|
+
listPlugins,
|
|
13
|
+
log,
|
|
14
|
+
multiselectClack,
|
|
15
|
+
resultLastLine,
|
|
16
|
+
} from "./utils.js";
|
|
17
|
+
|
|
18
|
+
const RECOMMENDED = RECOMMENDED_PLUGINS;
|
|
19
|
+
|
|
20
|
+
export async function installRecommendedPlugins({ all, yes, language }) {
|
|
21
|
+
log.blank();
|
|
22
|
+
log.step(4, 5, "Recommended plugins");
|
|
23
|
+
|
|
24
|
+
let toInstall;
|
|
25
|
+
if (all) {
|
|
26
|
+
toInstall = RECOMMENDED.map((r) => r.name);
|
|
27
|
+
log.info("--all mode: installing all recommended");
|
|
28
|
+
} else if (yes) {
|
|
29
|
+
const currentlyInstalled = new Set(listPlugins().map((p) => p.name));
|
|
30
|
+
toInstall = RECOMMENDED
|
|
31
|
+
.filter((r) => !currentlyInstalled.has(r.name))
|
|
32
|
+
.map((r) => r.name);
|
|
33
|
+
log.info(`--yes mode: installing ${toInstall.length} recommended plugins`);
|
|
34
|
+
} else {
|
|
35
|
+
const currentlyInstalled = new Set(listPlugins().map((p) => p.name));
|
|
36
|
+
const options = RECOMMENDED.map((r) => ({
|
|
37
|
+
value: r.name,
|
|
38
|
+
label: `${r.name}${currentlyInstalled.has(r.name) ? " (installed)" : ""}`,
|
|
39
|
+
hint: r.hint,
|
|
40
|
+
}));
|
|
41
|
+
const initialValues = RECOMMENDED.map((r) => r.name);
|
|
42
|
+
|
|
43
|
+
toInstall = await multiselectClack({
|
|
44
|
+
message: language === "zh"
|
|
45
|
+
? "选择要安装的推荐插件(空格切换,回车确认)"
|
|
46
|
+
: "Select recommended plugins to install (space to toggle, enter to confirm)",
|
|
47
|
+
options,
|
|
48
|
+
initialValues,
|
|
49
|
+
required: false,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!toInstall || toInstall.length === 0) {
|
|
54
|
+
log.info("No recommended plugins selected, skipping");
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
for (const pluginName of toInstall) {
|
|
59
|
+
const rec = RECOMMENDED.find((r) => r.name === pluginName);
|
|
60
|
+
log.blank();
|
|
61
|
+
console.log(` ${color.cyan("▸")} Installing ${color.bold(rec.name)}...`);
|
|
62
|
+
|
|
63
|
+
if (rec.marketplaceSource) {
|
|
64
|
+
const ma = await addPluginMarketplace(rec);
|
|
65
|
+
if (ma.code !== 0 && !ma.stderr.includes("already")) {
|
|
66
|
+
log.warn(` marketplace add warning: ${ma.stderr.trim().split("\n")[0]}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const ir = await installPlugin(rec);
|
|
71
|
+
if (ir.code === 0) {
|
|
72
|
+
console.log(` ${color.green("✓")} ${rec.name} installed`);
|
|
73
|
+
|
|
74
|
+
if (rec.postInstall === "claude-mem-runtimes") {
|
|
75
|
+
const r = ensureClaudeMemRuntimes();
|
|
76
|
+
for (const [name, res] of Object.entries(r)) {
|
|
77
|
+
if (res.status === "linked") {
|
|
78
|
+
console.log(
|
|
79
|
+
` ${color.green("✓")} ${name} → PATH symlink created ${color.dim(`(${res.link} → ${res.path})`)}`
|
|
80
|
+
);
|
|
81
|
+
} else if (res.status === "missing") {
|
|
82
|
+
console.log(
|
|
83
|
+
` ${color.yellow("⚠")} ${name} not installed ${color.dim("(claude-mem will auto-install on first run; or run: curdx-flow doctor)")}`
|
|
84
|
+
);
|
|
85
|
+
} else if (res.status === "path-unwritable") {
|
|
86
|
+
console.log(
|
|
87
|
+
` ${color.yellow("⚠")} ${name} installed ${color.dim(`(${res.path}) but no writable PATH location — add export PATH="${res.path.split("/").slice(0,-1).join("/")}:$PATH" to your shell rc`)}`
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
console.log(
|
|
94
|
+
` ${color.red("✗")} ${rec.name} install failed: ${resultLastLine(ir)}`
|
|
95
|
+
);
|
|
96
|
+
console.log(
|
|
97
|
+
color.dim(
|
|
98
|
+
` Run manually: claude plugin install --scope ${rec.scope} ${rec.installSpec}`
|
|
99
|
+
)
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return true;
|
|
104
|
+
}
|