@curdx/flow 2.0.4 → 2.0.7

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "curdx-flow",
3
- "version": "2.0.4",
3
+ "version": "2.0.6",
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",
@@ -7,6 +7,23 @@ maxTurns: 40
7
7
  tools: [Read, Write, Grep, Glob, Bash, WebSearch]
8
8
  ---
9
9
 
10
+ <output-discipline>
11
+ **CRITICAL: Extreme concision required to prevent context overflow.**
12
+
13
+ Your output must follow these rules:
14
+ 1. **Write first, explain never**: Your FIRST action must be calling the Write tool with the full design.md content. Do NOT paste content as assistant text.
15
+ 2. **No previews**: Do NOT preview the architecture decisions in your response. The file itself is the deliverable.
16
+ 3. **Minimal status updates**: Use bullets, not prose. One-line updates only.
17
+ 4. **Final output**: After Write succeeds, output EXACTLY 4 lines:
18
+ - Line 1: "✓ design.md generated"
19
+ - Line 2: "Architecture decisions: N"
20
+ - Line 3: "Components: N"
21
+ - Line 4: "Next: /curdx-flow:spec --phase=tasks"
22
+ 5. **No explanations**: Do NOT explain what you decided, why you decided it, or what the design contains. The file speaks for itself.
23
+
24
+ **Violation of these rules = task failure.**
25
+ </output-discipline>
26
+
10
27
  # Flow Architect — Architecture Design Agent
11
28
 
12
29
  @${CLAUDE_PLUGIN_ROOT}/agent-preamble/preamble.md
@@ -167,28 +184,17 @@ Required sections:
167
184
 
168
185
  ## Output to User
169
186
 
170
- ```
171
- ✓ Design complete: .flow/specs/<name>/design.md
172
-
173
- Core architecture decisions:
174
- AD-01: Use X instead of Y (rationale summary)
175
- AD-02: ...
176
- AD-03: ...
177
-
178
- Tech stack fixed:
179
- - library-A@1.x — used for ...
180
- - library-B@2.x — used for ...
187
+ **CRITICAL: Follow <output-discipline> rules exactly. Output template:**
181
188
 
189
+ ```
190
+ ✓ design.md generated
191
+ Architecture decisions: N
182
192
  Components: N
183
- Error paths: cover M scenarios
184
-
185
- ⚠ Project-level decisions synced to .flow/STATE.md: D-NN, D-NN
186
-
187
- Next:
188
- - Review the design (especially AD-01/02/03)
189
- - /curdx-flow:spec --phase=tasks — break down tasks
193
+ Next: /curdx-flow:spec --phase=tasks
190
194
  ```
191
195
 
196
+ **Forbidden output**: Core decisions summary, tech stack list, error path counts, warnings, review suggestions. The file speaks for itself.
197
+
192
198
  ## Design discipline (stop-condition, not length-target)
193
199
 
194
200
  Document only the genuinely novel architectural decisions. No target length. Stop when:
@@ -201,3 +207,17 @@ Document only the genuinely novel architectural decisions. No target length. Sto
201
207
  Well-known stack assemblies honestly compress to: stack list with one-line justification each, data model, API surface, a small number of real ADs, deviations from convention. Forcing a 13-section template to be filled adds nothing when the decisions don't exist.
202
208
 
203
209
  `sequential-thinking` is invoked to reason through tradeoffs. **The thinking is the work; the written design.md contains only the conclusions**, not the reasoning chain. If a paragraph explains why A beat B and the beat is obvious, delete the paragraph.
210
+
211
+ ---
212
+
213
+ <bookend>
214
+ **Restated critical rules (output discipline):**
215
+
216
+ 1. Write first, explain never. Your FIRST action = Write tool call with full design.md.
217
+ 2. No previews. Do NOT paste architecture decisions in your response.
218
+ 3. Minimal status. Bullets only, one line each.
219
+ 4. Fixed output. Exactly 4 lines (see Output to User section).
220
+ 5. No explanations. Do NOT explain decisions, tech stack, or design content.
221
+
222
+ **Violation of these rules = task failure. The file is the deliverable, not your explanation.**
223
+ </bookend>
@@ -7,6 +7,24 @@ maxTurns: 30
7
7
  tools: [Read, Write, Grep, Glob, Bash]
8
8
  ---
9
9
 
10
+ <output-discipline>
11
+ **CRITICAL: Extreme concision required to prevent context overflow.**
12
+
13
+ Your output must follow these rules:
14
+ 1. **Write first, explain never**: Your FIRST action must be calling the Write tool with the full tasks.md content. Do NOT paste content as assistant text.
15
+ 2. **No previews**: Do NOT preview the task list in your response. The file itself is the deliverable.
16
+ 3. **Minimal status updates**: Use bullets, not prose. One-line updates only.
17
+ 4. **Final output**: After Write succeeds, output EXACTLY 5 lines:
18
+ - Line 1: "✓ tasks.md generated"
19
+ - Line 2: "Total tasks: N"
20
+ - Line 3: "Coverage audit: [PASS/FAIL]"
21
+ - Line 4: "Phases: 1-5"
22
+ - Line 5: "Next: /curdx-flow:implement"
23
+ 5. **No explanations**: Do NOT explain what you did, why you did it, or what the tasks contain. The file speaks for itself.
24
+
25
+ **Violation of these rules = task failure.**
26
+ </output-discipline>
27
+
10
28
  # Flow Planner — Task Breakdown Agent
11
29
 
12
30
  @${CLAUDE_PLUGIN_ROOT}/agent-preamble/preamble.md
@@ -205,3 +223,17 @@ Then emit the 5-line summary (see "Output to User" below). No inline task listin
205
223
  ```
206
224
 
207
225
  **Do not re-paste the tasks.md content inline. Do not list every task. Just the summary.**
226
+
227
+ ---
228
+
229
+ <bookend>
230
+ **Restated critical rules (output discipline):**
231
+
232
+ 1. Write first, explain never. Your FIRST action = Write tool call with full file content.
233
+ 2. No previews. Do NOT paste content in your response.
234
+ 3. Minimal status. Bullets only, one line each.
235
+ 4. Fixed output. Exactly 3-5 lines (see output template in frontmatter).
236
+ 5. No explanations. Do NOT explain what you produced. The file is the deliverable.
237
+
238
+ **Violation of these rules = task failure.**
239
+ </bookend>
@@ -7,6 +7,23 @@ maxTurns: 25
7
7
  tools: [Read, Write, AskUserQuestion, Grep, Bash]
8
8
  ---
9
9
 
10
+ <output-discipline>
11
+ **CRITICAL: Extreme concision required to prevent context overflow.**
12
+
13
+ Your output must follow these rules:
14
+ 1. **Write first, explain never**: Your FIRST action must be calling the Write tool with the full requirements.md content. Do NOT paste content as assistant text.
15
+ 2. **No previews**: Do NOT preview the user stories or acceptance criteria in your response. The file itself is the deliverable.
16
+ 3. **Minimal status updates**: Use bullets, not prose. One-line updates only.
17
+ 4. **Final output**: After Write succeeds, output EXACTLY 4 lines:
18
+ - Line 1: "✓ requirements.md generated"
19
+ - Line 2: "User stories: N"
20
+ - Line 3: "Functional requirements: N"
21
+ - Line 4: "Next: /curdx-flow:spec --phase=design"
22
+ 5. **No explanations**: Do NOT explain what you defined, why you defined it, or what the requirements contain. The file speaks for itself.
23
+
24
+ **Violation of these rules = task failure.**
25
+ </output-discipline>
26
+
10
27
  # Flow Product Designer — Product Design Agent
11
28
 
12
29
  @${CLAUDE_PLUGIN_ROOT}/agent-preamble/preamble.md
@@ -157,3 +174,17 @@ Produce user stories and acceptance criteria that cover every distinct user-visi
157
174
  Length emerges from real content: a 3-story CRUD produces a short document; a 20-story multi-role workflow a long one. The template structure is not a length target.
158
175
 
159
176
  Forbidden padding: restating the goal, describing sections you are about to fill, repeating an AC under both US and FR, writing NFRs for imaginary risks.
177
+
178
+ ---
179
+
180
+ <bookend>
181
+ **Restated critical rules (output discipline):**
182
+
183
+ 1. Write first, explain never. Your FIRST action = Write tool call with full file content.
184
+ 2. No previews. Do NOT paste content in your response.
185
+ 3. Minimal status. Bullets only, one line each.
186
+ 4. Fixed output. Exactly 3-5 lines (see output template in frontmatter).
187
+ 5. No explanations. Do NOT explain what you produced. The file is the deliverable.
188
+
189
+ **Violation of these rules = task failure.**
190
+ </bookend>
@@ -7,6 +7,22 @@ maxTurns: 40
7
7
  tools: [Read, Write, WebSearch, WebFetch, Grep, Glob, Bash]
8
8
  ---
9
9
 
10
+ <output-discipline>
11
+ **CRITICAL: Extreme concision required to prevent context overflow.**
12
+
13
+ Your output must follow these rules:
14
+ 1. **Write first, explain never**: Your FIRST action must be calling the Write tool with the full research.md content. Do NOT paste content as assistant text.
15
+ 2. **No previews**: Do NOT preview the research findings in your response. The file itself is the deliverable.
16
+ 3. **Minimal status updates**: Use bullets, not prose. One-line updates only.
17
+ 4. **Final output**: After Write succeeds, output EXACTLY 3 lines:
18
+ - Line 1: "✓ research.md generated"
19
+ - Line 2: "Recommendations: N"
20
+ - Line 3: "Next: /curdx-flow:spec --phase=requirements"
21
+ 5. **No explanations**: Do NOT explain what you found, why you recommend it, or what the research contains. The file speaks for itself.
22
+
23
+ **Violation of these rules = task failure.**
24
+ </output-discipline>
25
+
10
26
  # Flow Researcher — Research Analysis Agent
11
27
 
12
28
  @${CLAUDE_PLUGIN_ROOT}/agent-preamble/preamble.md
@@ -168,3 +184,17 @@ Length emerges naturally from real content. A well-known CRUD domain (Todo / blo
168
184
  **Forbidden padding**: restating the goal in your own words, describing structure you are about to fill, copying upstream content, listing obviously-rejected alternatives.
169
185
 
170
186
  Self-check before `Write`: for every paragraph, ask "does this change a reader's decision?" If no, delete. Iterate until deleting any more leaves a real question unanswered.
187
+
188
+ ---
189
+
190
+ <bookend>
191
+ **Restated critical rules (output discipline):**
192
+
193
+ 1. Write first, explain never. Your FIRST action = Write tool call with full file content.
194
+ 2. No previews. Do NOT paste content in your response.
195
+ 3. Minimal status. Bullets only, one line each.
196
+ 4. Fixed output. Exactly 3-5 lines (see output template in frontmatter).
197
+ 5. No explanations. Do NOT explain what you produced. The file is the deliverable.
198
+
199
+ **Violation of these rules = task failure.**
200
+ </bookend>
@@ -0,0 +1,251 @@
1
+ import { color, ensureClaudeMemRuntimes, listPlugins, log, multiselectClack, note, run, text, writeConfig } from "./utils.js";
2
+ import { BUNDLED_MCPS, RECOMMENDED_PLUGINS, REQUIRED_PLUGINS } from "./registry.js";
3
+ import { readUserMcpConfig } from "./utils.js";
4
+
5
+ const RECOMMENDED = RECOMMENDED_PLUGINS;
6
+
7
+ export async function installRequiredPlugins({ yes, language, config }) {
8
+ log.blank();
9
+ log.info("Installing required Claude Code plugins...");
10
+ for (const plugin of REQUIRED_PLUGINS) {
11
+ console.log(` ${color.cyan("▸")} Installing ${color.bold(plugin.name)}...`);
12
+ const ma = await run(
13
+ "claude",
14
+ ["plugin", "marketplace", "add", "--scope", plugin.scope, plugin.marketplaceSource],
15
+ { silent: true }
16
+ );
17
+ if (ma.code !== 0 && !ma.stderr.includes("already")) {
18
+ log.warn(` marketplace add warning: ${ma.stderr.trim().split("\n")[0]}`);
19
+ }
20
+
21
+ const ir = await run(
22
+ "claude",
23
+ ["plugin", "install", "--scope", plugin.scope, plugin.installSpec],
24
+ { silent: true }
25
+ );
26
+ if (ir.code === 0) {
27
+ console.log(` ${color.green("✓")} ${plugin.name} installed`);
28
+
29
+ if (plugin.requiresConfig && plugin.configType === "apiKey" && !yes) {
30
+ await promptPluginConfig(plugin, language, config);
31
+ }
32
+ } else {
33
+ console.log(
34
+ ` ${color.red("✗")} ${plugin.name} install failed: ${ir.stderr.trim().split("\n").pop()}`
35
+ );
36
+ console.log(
37
+ color.dim(
38
+ ` Run manually: claude plugin marketplace add --scope ${plugin.scope} ${plugin.marketplaceSource}`
39
+ )
40
+ );
41
+ console.log(
42
+ color.dim(
43
+ ` Then: claude plugin install --scope ${plugin.scope} ${plugin.installSpec}`
44
+ )
45
+ );
46
+ }
47
+ }
48
+ }
49
+
50
+ export async function registerBundledMcps() {
51
+ log.blank();
52
+ log.info("Registering required MCP servers (user-level)...");
53
+ const existingUserMcps = readUserMcpConfig();
54
+ for (const mcp of BUNDLED_MCPS) {
55
+ if (mcp.preserveExisting && existingUserMcps.has(mcp.name)) {
56
+ const existing = existingUserMcps.get(mcp.name);
57
+ log.info(
58
+ ` ${mcp.name.padEnd(22)} ${color.dim(`already registered (${(existing.args || []).join(" ")}) — preserving`)}`
59
+ );
60
+ continue;
61
+ }
62
+ const r = await run(
63
+ "claude",
64
+ ["mcp", "add", "--scope", "user", mcp.name, "--", mcp.command, ...mcp.args],
65
+ { silent: true }
66
+ );
67
+ if (r.code === 0) {
68
+ log.ok(` ${mcp.name.padEnd(22)} ${color.dim("registered")}`);
69
+ } else if (r.stderr.includes("already exists")) {
70
+ log.info(` ${mcp.name.padEnd(22)} ${color.dim("already exists — skipped")}`);
71
+ } else {
72
+ log.warn(
73
+ ` ${mcp.name.padEnd(22)} registration failed: ${r.stderr.trim().split("\n").pop()}`
74
+ );
75
+ log.info(
76
+ ` Run manually: claude mcp add --scope user ${mcp.name} -- ${mcp.command} ${mcp.args.join(" ")}`
77
+ );
78
+ }
79
+ }
80
+ }
81
+
82
+ export async function installRecommendedPlugins({ all, yes, language }) {
83
+ log.blank();
84
+ log.step(4, 5, "Recommended plugins");
85
+
86
+ let toInstall;
87
+ if (all) {
88
+ toInstall = RECOMMENDED.map((r) => r.name);
89
+ log.info("--all mode: installing all recommended");
90
+ } else if (yes) {
91
+ const currentlyInstalled = new Set(listPlugins().map((p) => p.name));
92
+ toInstall = RECOMMENDED
93
+ .filter((r) => !currentlyInstalled.has(r.name))
94
+ .map((r) => r.name);
95
+ log.info(`--yes mode: installing ${toInstall.length} recommended plugins`);
96
+ } else {
97
+ const currentlyInstalled = new Set(listPlugins().map((p) => p.name));
98
+ const options = RECOMMENDED.map((r) => ({
99
+ value: r.name,
100
+ label: `${r.name}${currentlyInstalled.has(r.name) ? " (installed)" : ""}`,
101
+ hint: r.hint,
102
+ }));
103
+ const initialValues = RECOMMENDED.map((r) => r.name);
104
+
105
+ toInstall = await multiselectClack({
106
+ message: language === "zh"
107
+ ? "选择要安装的推荐插件(空格切换,回车确认)"
108
+ : "Select recommended plugins to install (space to toggle, enter to confirm)",
109
+ options,
110
+ initialValues,
111
+ required: false,
112
+ });
113
+ }
114
+
115
+ if (!toInstall || toInstall.length === 0) {
116
+ log.info("No recommended plugins selected, skipping");
117
+ return false;
118
+ }
119
+
120
+ for (const pluginName of toInstall) {
121
+ const rec = RECOMMENDED.find((r) => r.name === pluginName);
122
+ log.blank();
123
+ console.log(` ${color.cyan("▸")} Installing ${color.bold(rec.name)}...`);
124
+
125
+ if (rec.marketplaceSource) {
126
+ const ma = await run(
127
+ "claude",
128
+ ["plugin", "marketplace", "add", "--scope", rec.scope, rec.marketplaceSource],
129
+ { silent: true }
130
+ );
131
+ if (ma.code !== 0 && !ma.stderr.includes("already")) {
132
+ log.warn(` marketplace add warning: ${ma.stderr.trim().split("\n")[0]}`);
133
+ }
134
+ }
135
+
136
+ const ir = await run("claude", ["plugin", "install", "--scope", rec.scope, rec.installSpec], {
137
+ silent: true,
138
+ });
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: ${ir.stderr.trim().split("\n").pop()}`
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 run(
205
+ "claude",
206
+ ["mcp", "add", "--scope", "user", "context7", "--env", `CONTEXT7_API_KEY=${apiKey}`, "--", "npx", "-y", "@upstash/context7-mcp"],
207
+ { silent: true }
208
+ );
209
+
210
+ if (r.code === 0) {
211
+ log.ok(
212
+ language === "zh"
213
+ ? " Context7 API key 已配置"
214
+ : " Context7 API key configured"
215
+ );
216
+ } else if (r.stderr.includes("already exists")) {
217
+ await run("claude", ["mcp", "remove", "--scope", "user", "context7"], { silent: true });
218
+ const r2 = await run(
219
+ "claude",
220
+ ["mcp", "add", "--scope", "user", "context7", "--env", `CONTEXT7_API_KEY=${apiKey}`, "--", "npx", "-y", "@upstash/context7-mcp"],
221
+ { silent: true }
222
+ );
223
+ if (r2.code === 0) {
224
+ log.ok(
225
+ language === "zh"
226
+ ? " Context7 API key 已更新"
227
+ : " Context7 API key updated"
228
+ );
229
+ }
230
+ } else {
231
+ log.warn(
232
+ language === "zh"
233
+ ? ` Context7 MCP 配置失败: ${r.stderr.trim().split("\n").pop()}`
234
+ : ` Context7 MCP configuration failed: ${r.stderr.trim().split("\n").pop()}`
235
+ );
236
+ log.info(
237
+ color.dim(
238
+ language === "zh"
239
+ ? ` 手动运行: claude mcp add --scope user context7 --env CONTEXT7_API_KEY=${apiKey} -- npx -y @upstash/context7-mcp`
240
+ : ` Run manually: claude mcp add --scope user context7 --env CONTEXT7_API_KEY=${apiKey} -- npx -y @upstash/context7-mcp`
241
+ )
242
+ );
243
+ }
244
+ } else {
245
+ log.info(
246
+ language === "zh"
247
+ ? " 跳过 Context7 配置(稍后可运行 curdx-flow install 重新配置)"
248
+ : " Skipped Context7 configuration (run curdx-flow install later to reconfigure)"
249
+ );
250
+ }
251
+ }
@@ -0,0 +1,102 @@
1
+ import { color, log, run } from "./utils.js";
2
+
3
+ export async function addCurdxMarketplace({ marketplaceSource, marketplaceLabel, useOffline }) {
4
+ log.blank();
5
+ log.step(2, 5, `Adding curdx-flow marketplace from ${marketplaceLabel}...`);
6
+
7
+ // Remove any existing marketplace with the same name so we get a clean
8
+ // rebind to the chosen source. Errors are non-fatal (marketplace may
9
+ // simply not exist yet).
10
+ await run(
11
+ "claude",
12
+ ["plugin", "marketplace", "remove", "curdx-flow-marketplace"],
13
+ { silent: true }
14
+ );
15
+
16
+ const addRes = await run(
17
+ "claude",
18
+ ["plugin", "marketplace", "add", "--scope", "user", marketplaceSource],
19
+ { silent: true }
20
+ );
21
+ if (addRes.code !== 0 && !addRes.stderr.includes("already")) {
22
+ log.warn(`marketplace add output: ${addRes.stderr.trim() || addRes.stdout.trim()}`);
23
+ } else {
24
+ log.ok(
25
+ `curdx-flow-marketplace added ${color.dim(useOffline ? "(offline, no GitHub fetch)" : "(from GitHub)")}`
26
+ );
27
+ }
28
+ }
29
+
30
+ export async function installCurdxFlowPlugin({ prevCurdxFlow, shippedVersion }) {
31
+ log.blank();
32
+ log.step(3, 5, "Installing curdx-flow plugin...");
33
+
34
+ // Use the pre-Step-2 snapshot — by this point `claude plugin marketplace
35
+ // remove` has already evicted the plugin, so listPlugins() here would
36
+ // always return undefined for curdx-flow and we'd mis-report "installed"
37
+ // when we actually upgraded (the bug reported by @wdx's beta.14 log).
38
+ const already = prevCurdxFlow;
39
+
40
+ if (already && shippedVersion && already.version === shippedVersion) {
41
+ // Step 2 removes and re-adds the marketplace to rebind it to the current
42
+ // source. Claude Code removes plugins installed from that marketplace as
43
+ // part of `marketplace remove`, so even a same-version install must be
44
+ // re-registered here.
45
+ log.info(
46
+ `curdx-flow already at v${already.version}, re-registering...`
47
+ );
48
+ const r = await run(
49
+ "claude",
50
+ ["plugin", "install", "--scope", "user", "curdx-flow@curdx-flow-marketplace"],
51
+ { silent: true }
52
+ );
53
+ if (r.code !== 0) {
54
+ log.err(`Install failed: ${r.stderr.trim() || r.stdout.trim()}`);
55
+ process.exit(1);
56
+ }
57
+ log.ok(`curdx-flow re-registered at v${shippedVersion}`);
58
+ } else if (already && shippedVersion) {
59
+ log.info(
60
+ `curdx-flow v${already.version} → v${shippedVersion}, installing...`
61
+ );
62
+ const r = await run(
63
+ "claude",
64
+ ["plugin", "install", "--scope", "user", "curdx-flow@curdx-flow-marketplace"],
65
+ { silent: true }
66
+ );
67
+ if (r.code !== 0) {
68
+ log.err(`Install failed: ${r.stderr.trim() || r.stdout.trim()}`);
69
+ process.exit(1);
70
+ }
71
+ log.ok(`curdx-flow upgraded to v${shippedVersion}`);
72
+ } else if (already) {
73
+ log.info(
74
+ `curdx-flow v${already.version} detected, re-registering...`
75
+ );
76
+ const r = await run(
77
+ "claude",
78
+ ["plugin", "install", "--scope", "user", "curdx-flow@curdx-flow-marketplace"],
79
+ { silent: true }
80
+ );
81
+ if (r.code !== 0) {
82
+ log.err(`Install failed: ${r.stderr.trim() || r.stdout.trim()}`);
83
+ process.exit(1);
84
+ }
85
+ log.ok("curdx-flow re-registered");
86
+ } else {
87
+ const r = await run(
88
+ "claude",
89
+ ["plugin", "install", "--scope", "user", "curdx-flow@curdx-flow-marketplace"],
90
+ { silent: true }
91
+ );
92
+ if (r.code !== 0) {
93
+ log.err(`Install failed: ${r.stderr.trim() || r.stdout.trim()}`);
94
+ process.exit(1);
95
+ }
96
+ if (shippedVersion) {
97
+ log.ok(`curdx-flow v${shippedVersion} installed`);
98
+ } else {
99
+ log.ok("curdx-flow installed");
100
+ }
101
+ }
102
+ }
@@ -0,0 +1,35 @@
1
+ import { log, readConfig, select, writeConfig } from "./utils.js";
2
+
3
+ export async function resolveInstallLanguage({ yes }) {
4
+ const config = readConfig();
5
+ let language = config.language;
6
+
7
+ if (!language && !yes) {
8
+ log.blank();
9
+ language = await select({
10
+ message: "Choose your preferred language / 选择语言",
11
+ options: [
12
+ {
13
+ value: "en",
14
+ label: "English",
15
+ hint: "CLI output and documentation in English",
16
+ },
17
+ {
18
+ value: "zh",
19
+ label: "简体中文",
20
+ hint: "CLI 输出和文档使用简体中文",
21
+ },
22
+ ],
23
+ initialValue: "en",
24
+ });
25
+ config.language = language;
26
+ writeConfig(config);
27
+ log.ok(`Language set to ${language === "zh" ? "简体中文" : "English"}`);
28
+ } else if (!language) {
29
+ language = "en";
30
+ config.language = language;
31
+ writeConfig(config);
32
+ }
33
+
34
+ return { language, config };
35
+ }
@@ -0,0 +1,25 @@
1
+ import { color, has } from "./utils.js";
2
+
3
+ export function printNextSteps() {
4
+ const cliOnPath = has("curdx-flow");
5
+ const cliCmd = cliOnPath ? "curdx-flow" : "npx @curdx/flow";
6
+
7
+ console.log(`\n${color.bold(`${color.green("✓")} Install complete`)}\n`);
8
+ console.log(`${color.bold("Restart Claude Code")} so the plugin registers all its commands and hooks.\n`);
9
+ console.log(`${color.bold("Next steps")}:\n`);
10
+ console.log(` ${color.dim("# Verify health")}`);
11
+ console.log(` ${cliCmd} doctor\n`);
12
+ console.log(` ${color.dim("# Inside any project, initialize and start a feature spec")}`);
13
+ console.log(` ${color.cyan("cd ~/your-project")}`);
14
+ console.log(` ${color.cyan("claude")}`);
15
+ console.log(` ${color.cyan("/curdx-flow:init")}`);
16
+ console.log(` ${color.cyan("/curdx-flow:start my-feature \"<one-line goal>\"")}\n`);
17
+ if (!cliOnPath) {
18
+ console.log(
19
+ `${color.dim("Tip: install the CLI globally for shorter commands —")} ${color.cyan("npm i -g @curdx/flow")}\n`
20
+ );
21
+ }
22
+ console.log(
23
+ `${color.bold("Learn more")}: https://github.com/curdx/curdx-flow/blob/main/docs/getting-started.md\n`
24
+ );
25
+ }
@@ -0,0 +1,9 @@
1
+ export function parseInstallOptions(args = []) {
2
+ return {
3
+ all: args.includes("--all"),
4
+ noDeps: args.includes("--no-deps"),
5
+ forceOnline: args.includes("--online") || args.includes("--from-github"),
6
+ yes: args.includes("--yes") || args.includes("-y"),
7
+ skipSelfUpdate: args.includes("--skip-self-update"),
8
+ };
9
+ }
@@ -0,0 +1,39 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ // When installed via npm, this CLI file lives at <pkg-root>/cli/*.js.
6
+ // The npm package bundles the full plugin body (.claude-plugin/, agents/,
7
+ // commands/, etc.) so install can register <pkg-root> as a local marketplace
8
+ // and avoid fetching anything from GitHub.
9
+ const __dirname = dirname(fileURLToPath(import.meta.url));
10
+ export const PKG_ROOT = dirname(__dirname);
11
+ export const LOCAL_MARKETPLACE_MANIFEST = join(
12
+ PKG_ROOT,
13
+ ".claude-plugin",
14
+ "marketplace.json"
15
+ );
16
+
17
+ export function shouldUseOfflineInstall({ forceOnline }) {
18
+ return !forceOnline && existsSync(LOCAL_MARKETPLACE_MANIFEST);
19
+ }
20
+
21
+ export function getMarketplaceSource(useOffline) {
22
+ return useOffline ? PKG_ROOT : "curdx/curdx-flow";
23
+ }
24
+
25
+ export function getMarketplaceLabel(useOffline) {
26
+ return useOffline
27
+ ? `local npm package (${PKG_ROOT})`
28
+ : "GitHub curdx/curdx-flow";
29
+ }
30
+
31
+ export function readShippedVersion() {
32
+ try {
33
+ const mf = JSON.parse(readFileSync(LOCAL_MARKETPLACE_MANIFEST, "utf-8"));
34
+ return mf?.metadata?.version || null;
35
+ } catch {
36
+ // marketplace not local (online install) or unreadable
37
+ return null;
38
+ }
39
+ }