@curdx/flow 3.2.0 → 3.3.1
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/CHANGELOG.md +22 -0
- package/README.md +22 -0
- package/dist/index.mjs +363 -106
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@curdx/flow` are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/) and the project follows [Semantic Versioning](https://semver.org/).
|
|
4
4
|
|
|
5
|
+
## 3.3.1 — 2026-04-27
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- **Silent stalls between phases** — added spinners to the previously-silent windows where flow shells out to `claude plugin list --json` and `claude mcp list` (the latter performs an MCP server health check and can take 5-15 seconds). Affected sites: `install` (state-derivation between marketplace refresh and the multiselect), `update` and `uninstall` (state-derivation at flow entry), and the post-flow CLAUDE.md sync (after install/update/uninstall busts the cache, sync re-queries state). Each now shows `Checking installed state… (claude plugin list / mcp list)` with a result line so the run no longer feels frozen.
|
|
10
|
+
- **CLAUDE.md sync feedback** — replaced the post-hoc `p.log.info` line with a live spinner that converts to a final status line on completion, matching the marketplace-refresh and per-item install UX.
|
|
11
|
+
|
|
12
|
+
## 3.3.0 — 2026-04-27
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- **CLAUDE.md sync** — every successful `install` / `update` / `uninstall` now rewrites a small managed block in `~/.claude/CLAUDE.md` so Claude Code has session-start knowledge of which tools are installed and when to use each. The block lives between `<!-- BEGIN @curdx/flow v1 -->` / `<!-- END @curdx/flow v1 -->` markers; everything outside is preserved verbatim. Uninstalling all managed items removes the block entirely.
|
|
17
|
+
- **`Pkg.whenToUse` and `Pkg.slashNamespace`** — two new optional registry fields. `whenToUse` is the English trigger fragment shown in the CLAUDE.md "Available tools/plugins" list (e.g. "auto-fires on 2+ failures..."). `slashNamespace` is the slash invocation pattern (e.g. `/pua:*`) — only set on plugins that expose one. Both populated for the six bundled items, sourced from each upstream's own documentation.
|
|
18
|
+
- **Conditional Rules section** — the block's `Rules:` lines are emitted only for currently-installed tools, so the block never advises Claude to use a tool that isn't there. The "plan first" rule names whichever planners (`sequential-thinking`, `claude-mem`) are installed.
|
|
19
|
+
- **`--no-claude-md` flag and `CURDX_FLOW_NO_CLAUDE_MD` env var** — opt out of the CLAUDE.md sync (CI, locked-down filesystems, or users who prefer to manage CLAUDE.md by hand).
|
|
20
|
+
|
|
21
|
+
### Notes
|
|
22
|
+
|
|
23
|
+
- Sync is **safe by default**: writes are atomic (tmp + `fs.rename`), partial CLAUDE.md changes are impossible, and a failed sync prints a warning but never aborts a successful install.
|
|
24
|
+
- Forward-compatible: the BEGIN/END regex matches any `v\d+` suffix, so a future `v2` block format will silently replace any pre-existing `v1` block.
|
|
25
|
+
- Block content is always English regardless of `--lang`. CLAUDE.md's audience is Claude itself; English keeps instructions stable and avoids diff churn from alternating language runs.
|
|
26
|
+
|
|
5
27
|
## 3.2.0 — 2026-04-26
|
|
6
28
|
|
|
7
29
|
### Added
|
package/README.md
CHANGED
|
@@ -34,6 +34,28 @@ npx @curdx/flow --lang en # override language
|
|
|
34
34
|
| `sequential-thinking` | mcp | `@modelcontextprotocol/server-sequential-thinking` |
|
|
35
35
|
| `context7` | mcp | HTTP — `https://mcp.context7.com/mcp` (optional API key) |
|
|
36
36
|
|
|
37
|
+
## What it writes to your filesystem
|
|
38
|
+
|
|
39
|
+
After every successful `install` / `update` / `uninstall`, flow keeps a short managed block in your global `~/.claude/CLAUDE.md` so Claude Code knows at session start which tools are installed and when to use them. The block looks like:
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
<!-- BEGIN @curdx/flow v1 -->
|
|
43
|
+
## Tool Usage
|
|
44
|
+
|
|
45
|
+
Available tools/plugins:
|
|
46
|
+
- pua (v3.0.0) — `/pua:*` — auto-fires on 2+ failures or user frustration; ...
|
|
47
|
+
- ...
|
|
48
|
+
|
|
49
|
+
Rules:
|
|
50
|
+
- Do not call every tool by default; ...
|
|
51
|
+
- ...
|
|
52
|
+
|
|
53
|
+
Run `npx @curdx/flow` to install / update / uninstall.
|
|
54
|
+
<!-- END @curdx/flow v1 -->
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Anything outside the BEGIN/END markers is preserved verbatim — flow only ever rewrites or removes the block itself. Uninstalling all managed items removes the block entirely. Pass `--no-claude-md` (or set `CURDX_FLOW_NO_CLAUDE_MD=1`) to opt out.
|
|
58
|
+
|
|
37
59
|
## Requirements
|
|
38
60
|
|
|
39
61
|
- Node.js >= 20.12
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import * as
|
|
4
|
+
import * as p9 from "@clack/prompts";
|
|
5
5
|
import { defineCommand, runMain } from "citty";
|
|
6
6
|
|
|
7
7
|
// src/ui/language.ts
|
|
@@ -66,7 +66,15 @@ var messages = {
|
|
|
66
66
|
"chrome.prereqNode": "\u9700\u8981 Node.js >= 20.19\uFF0C\u5F53\u524D\u7248\u672C {current}",
|
|
67
67
|
"chrome.prereqChrome": "\u9700\u8981\u672C\u673A\u5DF2\u5B89\u88C5 Chrome\uFF08chrome-devtools-mcp \u4F1A\u8C03\u7528\u672C\u5730\u6D4F\u89C8\u5668\uFF09",
|
|
68
68
|
"reinstall.uninstalling": "\u5148\u5378\u8F7D\u65E7\u7248\u672C\u2026",
|
|
69
|
-
"reinstall.installing": "\u5B89\u88C5\u65B0\u7248\u672C\u2026"
|
|
69
|
+
"reinstall.installing": "\u5B89\u88C5\u65B0\u7248\u672C\u2026",
|
|
70
|
+
"state.checking": "\u68C0\u67E5\u5DF2\u5B89\u88C5\u72B6\u6001\u2026\uFF08claude plugin list / mcp list\uFF09",
|
|
71
|
+
"state.checked": "\u5DF2\u68C0\u67E5 {count} \u9879",
|
|
72
|
+
"claudeMd.syncing": "\u540C\u6B65 ~/.claude/CLAUDE.md \u2026",
|
|
73
|
+
"claudeMd.synced": "CLAUDE.md \u5DF2\u66F4\u65B0\uFF08{path}\uFF09",
|
|
74
|
+
"claudeMd.unchanged": "CLAUDE.md \u5DF2\u662F\u6700\u65B0",
|
|
75
|
+
"claudeMd.removed": "\u5DF2\u4ECE CLAUDE.md \u79FB\u9664 @curdx/flow \u533A\u5757",
|
|
76
|
+
"claudeMd.skipped": "\u5DF2\u8DF3\u8FC7 CLAUDE.md \u540C\u6B65\uFF08--no-claude-md\uFF09",
|
|
77
|
+
"claudeMd.failed": "CLAUDE.md \u540C\u6B65\u5931\u8D25\uFF1A{error}"
|
|
70
78
|
};
|
|
71
79
|
var zh_default = messages;
|
|
72
80
|
|
|
@@ -129,7 +137,15 @@ var messages2 = {
|
|
|
129
137
|
"chrome.prereqNode": "Requires Node.js >= 20.19 (current: {current})",
|
|
130
138
|
"chrome.prereqChrome": "Requires Chrome installed locally (chrome-devtools-mcp drives the local browser)",
|
|
131
139
|
"reinstall.uninstalling": "Uninstalling old version\u2026",
|
|
132
|
-
"reinstall.installing": "Installing new version\u2026"
|
|
140
|
+
"reinstall.installing": "Installing new version\u2026",
|
|
141
|
+
"state.checking": "Checking installed state\u2026 (claude plugin list / mcp list)",
|
|
142
|
+
"state.checked": "Checked {count} item(s)",
|
|
143
|
+
"claudeMd.syncing": "Syncing ~/.claude/CLAUDE.md\u2026",
|
|
144
|
+
"claudeMd.synced": "CLAUDE.md updated ({path})",
|
|
145
|
+
"claudeMd.unchanged": "CLAUDE.md already up to date",
|
|
146
|
+
"claudeMd.removed": "Removed @curdx/flow block from CLAUDE.md",
|
|
147
|
+
"claudeMd.skipped": "Skipped CLAUDE.md sync (--no-claude-md)",
|
|
148
|
+
"claudeMd.failed": "CLAUDE.md sync failed: {error}"
|
|
133
149
|
};
|
|
134
150
|
var en_default = messages2;
|
|
135
151
|
|
|
@@ -178,10 +194,10 @@ async function initLanguage(override) {
|
|
|
178
194
|
}
|
|
179
195
|
|
|
180
196
|
// src/ui/menu.ts
|
|
181
|
-
import * as
|
|
197
|
+
import * as p8 from "@clack/prompts";
|
|
182
198
|
|
|
183
199
|
// src/flows/install.ts
|
|
184
|
-
import * as
|
|
200
|
+
import * as p4 from "@clack/prompts";
|
|
185
201
|
import pc from "picocolors";
|
|
186
202
|
|
|
187
203
|
// src/runner/state.ts
|
|
@@ -200,15 +216,15 @@ async function run(cmd, args) {
|
|
|
200
216
|
stderr: result.stderr
|
|
201
217
|
};
|
|
202
218
|
}
|
|
203
|
-
async function runStreaming(cmd, args,
|
|
204
|
-
|
|
219
|
+
async function runStreaming(cmd, args, log5) {
|
|
220
|
+
log5.message(`$ ${cmd} ${args.join(" ")}`);
|
|
205
221
|
const proc = x(cmd, args, { throwOnError: false });
|
|
206
222
|
let stdout = "";
|
|
207
223
|
for await (const line of proc) {
|
|
208
224
|
const trimmed = line.replace(/\r?\n$/, "");
|
|
209
225
|
if (trimmed.length > 0) {
|
|
210
226
|
stdout += trimmed + "\n";
|
|
211
|
-
|
|
227
|
+
log5.message(trimmed);
|
|
212
228
|
}
|
|
213
229
|
}
|
|
214
230
|
const finished = await proc;
|
|
@@ -251,15 +267,15 @@ async function listPlugins(force = false) {
|
|
|
251
267
|
}
|
|
252
268
|
try {
|
|
253
269
|
const arr = JSON.parse(res.stdout);
|
|
254
|
-
pluginCache = arr.map((
|
|
255
|
-
const [name =
|
|
270
|
+
pluginCache = arr.map((p10) => {
|
|
271
|
+
const [name = p10.id, marketplace = ""] = p10.id.split("@");
|
|
256
272
|
return {
|
|
257
|
-
id:
|
|
273
|
+
id: p10.id,
|
|
258
274
|
name,
|
|
259
275
|
marketplace,
|
|
260
|
-
version:
|
|
261
|
-
scope:
|
|
262
|
-
enabled:
|
|
276
|
+
version: p10.version,
|
|
277
|
+
scope: p10.scope,
|
|
278
|
+
enabled: p10.enabled
|
|
263
279
|
};
|
|
264
280
|
});
|
|
265
281
|
} catch {
|
|
@@ -305,7 +321,7 @@ async function listMcp(force = false) {
|
|
|
305
321
|
}
|
|
306
322
|
async function isPluginInstalled(id) {
|
|
307
323
|
const list = await listPlugins();
|
|
308
|
-
return list.some((
|
|
324
|
+
return list.some((p10) => p10.id === id);
|
|
309
325
|
}
|
|
310
326
|
async function isMarketplaceAdded(name) {
|
|
311
327
|
const list = await listMarketplaces();
|
|
@@ -317,7 +333,7 @@ async function isMcpInstalled(name) {
|
|
|
317
333
|
}
|
|
318
334
|
async function findPlugin(id) {
|
|
319
335
|
const list = await listPlugins();
|
|
320
|
-
return list.find((
|
|
336
|
+
return list.find((p10) => p10.id === id);
|
|
321
337
|
}
|
|
322
338
|
var marketplaceJsonCache = /* @__PURE__ */ new Map();
|
|
323
339
|
function marketplaceDir(name) {
|
|
@@ -339,7 +355,7 @@ async function readMarketplaceJson(name) {
|
|
|
339
355
|
async function getMarketplacePluginVersion(marketplaceName, pluginName) {
|
|
340
356
|
const m = await readMarketplaceJson(marketplaceName);
|
|
341
357
|
if (!m?.plugins) return null;
|
|
342
|
-
const entry = m.plugins.find((
|
|
358
|
+
const entry = m.plugins.find((p10) => p10.name === pluginName);
|
|
343
359
|
return entry?.version ?? null;
|
|
344
360
|
}
|
|
345
361
|
var REFRESH_TTL_MS = 60 * 60 * 1e3;
|
|
@@ -399,11 +415,13 @@ var pua = {
|
|
|
399
415
|
name: "pua",
|
|
400
416
|
description: "tanweai/pua \u2014 Chinese Claude Code skills bundle",
|
|
401
417
|
type: "plugin",
|
|
418
|
+
slashNamespace: "/pua:*",
|
|
419
|
+
whenToUse: "auto-fires on 2+ failures or user frustration; sub-modes p7 / p9 / pro / loop. Skip on first-attempt failures or when a known fix is executing.",
|
|
402
420
|
marketplaces: () => [MARKETPLACE_NAME],
|
|
403
421
|
isInstalled: () => isPluginInstalled(PLUGIN_ID),
|
|
404
422
|
installedVersion: async () => {
|
|
405
|
-
const
|
|
406
|
-
const v =
|
|
423
|
+
const p10 = await findPlugin(PLUGIN_ID);
|
|
424
|
+
const v = p10?.version;
|
|
407
425
|
return v && v !== "unknown" ? v : null;
|
|
408
426
|
},
|
|
409
427
|
latestVersion: () => getMarketplacePluginVersion(MARKETPLACE_NAME, PLUGIN_NAME),
|
|
@@ -426,11 +444,13 @@ var claudeMem = {
|
|
|
426
444
|
name: "claude-mem",
|
|
427
445
|
description: "thedotmack/claude-mem \u2014 persistent cross-session memory for Claude Code",
|
|
428
446
|
type: "plugin",
|
|
447
|
+
slashNamespace: "/claude-mem:*",
|
|
448
|
+
whenToUse: 'for cross-session memory search ("did we solve this before?"), phased planning (`make-plan`), or phased execution (`do`).',
|
|
429
449
|
marketplaces: () => [MARKETPLACE_NAME2],
|
|
430
450
|
isInstalled: () => isPluginInstalled(PLUGIN_ID2),
|
|
431
451
|
installedVersion: async () => {
|
|
432
|
-
const
|
|
433
|
-
const v =
|
|
452
|
+
const p10 = await findPlugin(PLUGIN_ID2);
|
|
453
|
+
const v = p10?.version;
|
|
434
454
|
return v && v !== "unknown" ? v : null;
|
|
435
455
|
},
|
|
436
456
|
latestVersion: () => getMarketplacePluginVersion(MARKETPLACE_NAME2, PLUGIN_NAME2),
|
|
@@ -461,6 +481,7 @@ var chromeDevtoolsMcp = {
|
|
|
461
481
|
name: "chrome-devtools-mcp",
|
|
462
482
|
description: "ChromeDevTools/chrome-devtools-mcp \u2014 drive a real Chrome from Claude Code",
|
|
463
483
|
type: "plugin",
|
|
484
|
+
whenToUse: "when debugging code that runs in a browser: perf traces, network / console inspection, DOM / CSS issues. Prefer snapshot over screenshot.",
|
|
464
485
|
prereqCheck: async (t2) => {
|
|
465
486
|
const major = Number(process.versions.node.split(".")[0] ?? "0");
|
|
466
487
|
const minor = Number(process.versions.node.split(".")[1] ?? "0");
|
|
@@ -489,6 +510,7 @@ var frontendDesign = {
|
|
|
489
510
|
name: "frontend-design",
|
|
490
511
|
description: "Anthropic official \u2014 UI/frontend design helpers",
|
|
491
512
|
type: "plugin",
|
|
513
|
+
whenToUse: "auto-fires when building UI / web components / pages. Best where visual personality matters (landing, marketing, portfolio).",
|
|
492
514
|
isInstalled: () => isPluginInstalled(PLUGIN_ID4),
|
|
493
515
|
install: (ctx) => installPluginById(PLUGIN_ID4, ctx),
|
|
494
516
|
uninstall: (ctx) => uninstallPluginById(PLUGIN_ID4, ctx),
|
|
@@ -503,6 +525,7 @@ var sequentialThinking = {
|
|
|
503
525
|
name: "sequential-thinking",
|
|
504
526
|
description: "modelcontextprotocol/server-sequential-thinking \u2014 structured reasoning helper",
|
|
505
527
|
type: "mcp",
|
|
528
|
+
whenToUse: "for complex multi-step problems where assumptions may shift (architecture comparison, risk-assessed migrations, prod-only debugging). Skip for simple queries.",
|
|
506
529
|
isInstalled: () => isMcpInstalled(MCP_NAME),
|
|
507
530
|
install: async (ctx) => {
|
|
508
531
|
const r = await runStreaming(
|
|
@@ -543,6 +566,7 @@ var context7 = {
|
|
|
543
566
|
name: "context7",
|
|
544
567
|
description: "upstash/context7 \u2014 up-to-date docs from any library (HTTP MCP, optional API key)",
|
|
545
568
|
type: "mcp",
|
|
569
|
+
whenToUse: "for any library / SDK / framework / API / Claude Code docs lookup. Use instead of web search.",
|
|
546
570
|
isInstalled: () => isMcpInstalled(MCP_NAME2),
|
|
547
571
|
configPrompts: async ({ t: t2 }) => {
|
|
548
572
|
p2.note(`${t2("context7.dashboardHint")}
|
|
@@ -595,7 +619,187 @@ var PKGS = [
|
|
|
595
619
|
context7_default
|
|
596
620
|
];
|
|
597
621
|
function findPkg(id) {
|
|
598
|
-
return PKGS.find((
|
|
622
|
+
return PKGS.find((p10) => p10.id === id);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// src/runner/claudeMd.ts
|
|
626
|
+
import { promises as fs2 } from "fs";
|
|
627
|
+
import path2 from "path";
|
|
628
|
+
import os2 from "os";
|
|
629
|
+
import * as p3 from "@clack/prompts";
|
|
630
|
+
var BEGIN_MARKER = "<!-- BEGIN @curdx/flow v1 -->";
|
|
631
|
+
var END_MARKER = "<!-- END @curdx/flow v1 -->";
|
|
632
|
+
var BLOCK_RE = /<!-- BEGIN @curdx\/flow v\d+[^>]*-->[\s\S]*?<!-- END @curdx\/flow v\d+ -->/;
|
|
633
|
+
function claudeMdPath() {
|
|
634
|
+
return path2.join(os2.homedir(), ".claude", "CLAUDE.md");
|
|
635
|
+
}
|
|
636
|
+
function renderItemLine(item) {
|
|
637
|
+
let line = `- ${item.name}`;
|
|
638
|
+
if (item.version) line += ` (v${item.version})`;
|
|
639
|
+
if (item.slashNamespace) line += ` \u2014 \`${item.slashNamespace}\``;
|
|
640
|
+
if (item.whenToUse) line += ` \u2014 ${item.whenToUse}`;
|
|
641
|
+
return line;
|
|
642
|
+
}
|
|
643
|
+
var ALWAYS_ON_RULES = [
|
|
644
|
+
"Do not call every tool by default; pick by the trigger condition above.",
|
|
645
|
+
"For first-attempt failures or simple edits, skip extra tools."
|
|
646
|
+
];
|
|
647
|
+
function buildConditionalRules(installedIds) {
|
|
648
|
+
const out = [];
|
|
649
|
+
const planners = [];
|
|
650
|
+
if (installedIds.has("sequential-thinking")) planners.push("sequential-thinking");
|
|
651
|
+
if (installedIds.has("claude-mem")) planners.push("claude-mem `make-plan`");
|
|
652
|
+
if (planners.length > 0) {
|
|
653
|
+
out.push(`For complex / risky changes, plan first (${planners.join(" or ")}).`);
|
|
654
|
+
}
|
|
655
|
+
if (installedIds.has("context7")) {
|
|
656
|
+
out.push("For library / SDK lookups, prefer context7 over web search.");
|
|
657
|
+
}
|
|
658
|
+
if (installedIds.has("chrome-devtools-mcp")) {
|
|
659
|
+
out.push("For browser-rendered behavior, verify in chrome-devtools-mcp instead of guessing.");
|
|
660
|
+
}
|
|
661
|
+
return out;
|
|
662
|
+
}
|
|
663
|
+
function renderBlock(items) {
|
|
664
|
+
const installedIds = new Set(items.map((i) => i.id));
|
|
665
|
+
const rules = [...ALWAYS_ON_RULES, ...buildConditionalRules(installedIds)];
|
|
666
|
+
return [
|
|
667
|
+
BEGIN_MARKER,
|
|
668
|
+
"## Tool Usage",
|
|
669
|
+
"",
|
|
670
|
+
"Available tools/plugins:",
|
|
671
|
+
...items.map(renderItemLine),
|
|
672
|
+
"",
|
|
673
|
+
"Rules:",
|
|
674
|
+
...rules.map((r) => `- ${r}`),
|
|
675
|
+
"",
|
|
676
|
+
"Run `npx @curdx/flow` to install / update / uninstall.",
|
|
677
|
+
END_MARKER
|
|
678
|
+
].join("\n");
|
|
679
|
+
}
|
|
680
|
+
function withEol(s, eol) {
|
|
681
|
+
return eol === "\n" ? s : s.split("\n").join(eol);
|
|
682
|
+
}
|
|
683
|
+
function ensureSingleTrailingNewline(s, eol) {
|
|
684
|
+
if (s.length === 0) return s;
|
|
685
|
+
return s.replace(/[\r\n]+$/, "") + eol;
|
|
686
|
+
}
|
|
687
|
+
function upsertBlock(existing, blockBody, eol) {
|
|
688
|
+
const block = withEol(blockBody, eol);
|
|
689
|
+
if (BLOCK_RE.test(existing)) {
|
|
690
|
+
return existing.replace(BLOCK_RE, block);
|
|
691
|
+
}
|
|
692
|
+
if (existing.length === 0) {
|
|
693
|
+
return block + eol;
|
|
694
|
+
}
|
|
695
|
+
const trimmed = existing.replace(/[\r\n\s]+$/, "");
|
|
696
|
+
return trimmed + eol + eol + block + eol;
|
|
697
|
+
}
|
|
698
|
+
function removeBlock(existing, eol) {
|
|
699
|
+
if (!BLOCK_RE.test(existing)) return existing;
|
|
700
|
+
let next = existing.replace(BLOCK_RE, "");
|
|
701
|
+
const tripleEol = new RegExp(`(?:\\r?\\n){3,}`, "g");
|
|
702
|
+
next = next.replace(tripleEol, eol + eol);
|
|
703
|
+
if (next.replace(/[\s\r\n]/g, "").length === 0) return "";
|
|
704
|
+
return ensureSingleTrailingNewline(next, eol);
|
|
705
|
+
}
|
|
706
|
+
async function pkgToItem(pkg) {
|
|
707
|
+
let version;
|
|
708
|
+
if (pkg.installedVersion) {
|
|
709
|
+
const v = await pkg.installedVersion();
|
|
710
|
+
if (v) version = v;
|
|
711
|
+
}
|
|
712
|
+
return {
|
|
713
|
+
id: pkg.id,
|
|
714
|
+
name: pkg.name,
|
|
715
|
+
type: pkg.type,
|
|
716
|
+
version,
|
|
717
|
+
whenToUse: pkg.whenToUse,
|
|
718
|
+
slashNamespace: pkg.slashNamespace
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
async function collectInstalledItems() {
|
|
722
|
+
await Promise.all([listPlugins(true), listMcp(true)]);
|
|
723
|
+
const items = [];
|
|
724
|
+
for (const pkg of PKGS) {
|
|
725
|
+
if (await pkg.isInstalled()) {
|
|
726
|
+
items.push(await pkgToItem(pkg));
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
items.sort((a, b) => {
|
|
730
|
+
if (a.type !== b.type) return a.type === "plugin" ? -1 : 1;
|
|
731
|
+
return a.name.localeCompare(b.name);
|
|
732
|
+
});
|
|
733
|
+
return items;
|
|
734
|
+
}
|
|
735
|
+
async function syncClaudeMd(opts) {
|
|
736
|
+
const file = claudeMdPath();
|
|
737
|
+
if (opts?.skip) return { status: "skipped", path: file };
|
|
738
|
+
try {
|
|
739
|
+
const items = await collectInstalledItems();
|
|
740
|
+
let existing = "";
|
|
741
|
+
let existed = true;
|
|
742
|
+
try {
|
|
743
|
+
existing = await fs2.readFile(file, "utf8");
|
|
744
|
+
} catch (err) {
|
|
745
|
+
if (err.code === "ENOENT") {
|
|
746
|
+
existed = false;
|
|
747
|
+
} else {
|
|
748
|
+
throw err;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
const eol = existing.includes("\r\n") ? "\r\n" : "\n";
|
|
752
|
+
const hadBlock = BLOCK_RE.test(existing);
|
|
753
|
+
let next;
|
|
754
|
+
if (items.length === 0) {
|
|
755
|
+
if (!hadBlock) {
|
|
756
|
+
return { status: "unchanged", path: file };
|
|
757
|
+
}
|
|
758
|
+
next = removeBlock(existing, eol);
|
|
759
|
+
} else {
|
|
760
|
+
next = upsertBlock(existing, renderBlock(items), eol);
|
|
761
|
+
}
|
|
762
|
+
if (next === existing) {
|
|
763
|
+
return { status: "unchanged", path: file };
|
|
764
|
+
}
|
|
765
|
+
await fs2.mkdir(path2.dirname(file), { recursive: true });
|
|
766
|
+
const tmp = `${file}.tmp.${process.pid}`;
|
|
767
|
+
await fs2.writeFile(tmp, next, "utf8");
|
|
768
|
+
await fs2.rename(tmp, file);
|
|
769
|
+
if (!existed) return { status: "created", path: file };
|
|
770
|
+
if (hadBlock && items.length === 0) return { status: "removed", path: file };
|
|
771
|
+
return { status: "updated", path: file };
|
|
772
|
+
} catch (err) {
|
|
773
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
774
|
+
return { status: "failed", path: file, error: msg };
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
async function syncFromState(opts) {
|
|
778
|
+
if (opts?.skip) {
|
|
779
|
+
p3.log.info(t("claudeMd.skipped"));
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
782
|
+
const sp = p3.spinner();
|
|
783
|
+
sp.start(t("claudeMd.syncing"));
|
|
784
|
+
const r = await syncClaudeMd();
|
|
785
|
+
switch (r.status) {
|
|
786
|
+
case "skipped":
|
|
787
|
+
sp.stop(t("claudeMd.skipped"));
|
|
788
|
+
return;
|
|
789
|
+
case "unchanged":
|
|
790
|
+
sp.stop(t("claudeMd.unchanged"));
|
|
791
|
+
return;
|
|
792
|
+
case "created":
|
|
793
|
+
case "updated":
|
|
794
|
+
sp.stop(t("claudeMd.synced", { path: r.path }));
|
|
795
|
+
return;
|
|
796
|
+
case "removed":
|
|
797
|
+
sp.stop(t("claudeMd.removed"));
|
|
798
|
+
return;
|
|
799
|
+
case "failed":
|
|
800
|
+
sp.stop(t("claudeMd.failed", { error: r.error ?? "unknown" }));
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
599
803
|
}
|
|
600
804
|
|
|
601
805
|
// src/flows/install.ts
|
|
@@ -634,13 +838,13 @@ async function selectInteractive(states) {
|
|
|
634
838
|
const s = states.get(pkg.id);
|
|
635
839
|
return s.kind === "not_installed" || s.kind === "update_available";
|
|
636
840
|
}).map((pkg) => pkg.id);
|
|
637
|
-
const picked = await
|
|
841
|
+
const picked = await p4.multiselect({
|
|
638
842
|
message: t("install.selectPrompt"),
|
|
639
843
|
options,
|
|
640
844
|
initialValues,
|
|
641
845
|
required: false
|
|
642
846
|
});
|
|
643
|
-
if (
|
|
847
|
+
if (p4.isCancel(picked)) return null;
|
|
644
848
|
return picked.map((id) => findPkg(id)).filter((x2) => Boolean(x2));
|
|
645
849
|
}
|
|
646
850
|
function selectFromIds(opts) {
|
|
@@ -650,7 +854,7 @@ function selectFromIds(opts) {
|
|
|
650
854
|
for (const id of opts.ids) {
|
|
651
855
|
const pkg = findPkg(id);
|
|
652
856
|
if (pkg) found.push(pkg);
|
|
653
|
-
else
|
|
857
|
+
else p4.log.warn(`Unknown id: ${id}`);
|
|
654
858
|
}
|
|
655
859
|
return found;
|
|
656
860
|
}
|
|
@@ -662,11 +866,11 @@ async function runOne(pkg, state, opts) {
|
|
|
662
866
|
mode = "update";
|
|
663
867
|
} else {
|
|
664
868
|
if (!opts.yes) {
|
|
665
|
-
const ans = await
|
|
869
|
+
const ans = await p4.confirm({
|
|
666
870
|
message: t("install.confirmReinstall", { name: pkg.name }),
|
|
667
871
|
initialValue: false
|
|
668
872
|
});
|
|
669
|
-
if (
|
|
873
|
+
if (p4.isCancel(ans) || ans === false) {
|
|
670
874
|
return { id: pkg.id, status: "skip", message: t("install.skippedReinstall", { name: pkg.name }) };
|
|
671
875
|
}
|
|
672
876
|
}
|
|
@@ -675,7 +879,7 @@ async function runOne(pkg, state, opts) {
|
|
|
675
879
|
if (pkg.prereqCheck) {
|
|
676
880
|
const r = await pkg.prereqCheck(t);
|
|
677
881
|
if (!r.ok) {
|
|
678
|
-
|
|
882
|
+
p4.log.warn(t("install.prereqFail", { name: pkg.name, reason: r.reason }));
|
|
679
883
|
return { id: pkg.id, status: "skip", message: r.reason };
|
|
680
884
|
}
|
|
681
885
|
}
|
|
@@ -690,30 +894,30 @@ async function runOne(pkg, state, opts) {
|
|
|
690
894
|
if (mode === "update" && state.kind === "update_available") {
|
|
691
895
|
titleVars["version"] = state.latest;
|
|
692
896
|
}
|
|
693
|
-
const
|
|
897
|
+
const log5 = p4.taskLog({ title: t(titleKey, titleVars) });
|
|
694
898
|
try {
|
|
695
899
|
if (mode === "reinstall") {
|
|
696
|
-
|
|
697
|
-
await pkg.uninstall({ log:
|
|
698
|
-
|
|
699
|
-
await pkg.install({ log:
|
|
900
|
+
log5.message(t("reinstall.uninstalling"));
|
|
901
|
+
await pkg.uninstall({ log: log5, config, t });
|
|
902
|
+
log5.message(t("reinstall.installing"));
|
|
903
|
+
await pkg.install({ log: log5, config, t });
|
|
700
904
|
} else if (mode === "update") {
|
|
701
905
|
if (pkg.update) {
|
|
702
|
-
await pkg.update({ log:
|
|
906
|
+
await pkg.update({ log: log5, config, t });
|
|
703
907
|
} else {
|
|
704
|
-
|
|
705
|
-
await pkg.uninstall({ log:
|
|
706
|
-
|
|
707
|
-
await pkg.install({ log:
|
|
908
|
+
log5.message(t("reinstall.uninstalling"));
|
|
909
|
+
await pkg.uninstall({ log: log5, config, t });
|
|
910
|
+
log5.message(t("reinstall.installing"));
|
|
911
|
+
await pkg.install({ log: log5, config, t });
|
|
708
912
|
}
|
|
709
913
|
} else {
|
|
710
|
-
await pkg.install({ log:
|
|
914
|
+
await pkg.install({ log: log5, config, t });
|
|
711
915
|
}
|
|
712
|
-
|
|
916
|
+
log5.success(t("install.success", { name: pkg.name }));
|
|
713
917
|
return { id: pkg.id, status: "ok" };
|
|
714
918
|
} catch (err) {
|
|
715
919
|
const msg = err instanceof Error ? err.message : String(err);
|
|
716
|
-
|
|
920
|
+
log5.error(`${t("install.failed", { name: pkg.name })}
|
|
717
921
|
${msg}`);
|
|
718
922
|
return { id: pkg.id, status: "fail", message: msg };
|
|
719
923
|
}
|
|
@@ -731,7 +935,7 @@ function summarize(results) {
|
|
|
731
935
|
...skip.map((r) => ` ${pc.yellow("-")} ${r.id}${r.message ? pc.dim(` (${r.message})`) : ""}`),
|
|
732
936
|
...fail.map((r) => ` ${pc.red("\u2717")} ${r.id}${r.message ? pc.dim(` (${r.message.split("\n")[0]})`) : ""}`)
|
|
733
937
|
];
|
|
734
|
-
|
|
938
|
+
p4.note(lines.join("\n"), t("install.summaryTitle"));
|
|
735
939
|
}
|
|
736
940
|
async function maybeRefreshMarketplaces(opts) {
|
|
737
941
|
if (opts.noRefresh) return;
|
|
@@ -740,7 +944,7 @@ async function maybeRefreshMarketplaces(opts) {
|
|
|
740
944
|
if (pkg.marketplaces) for (const n of pkg.marketplaces()) names.add(n);
|
|
741
945
|
}
|
|
742
946
|
if (names.size === 0) return;
|
|
743
|
-
const sp =
|
|
947
|
+
const sp = p4.spinner();
|
|
744
948
|
sp.start(t("marketplace.refreshing"));
|
|
745
949
|
const refreshed = await refreshMarketplaces([...names]);
|
|
746
950
|
sp.stop(
|
|
@@ -752,28 +956,35 @@ async function installFlow(opts = {}) {
|
|
|
752
956
|
const explicit = opts.all || opts.ids && opts.ids.length > 0;
|
|
753
957
|
const candidates = explicit ? selectFromIds(opts) : [...PKGS];
|
|
754
958
|
if (candidates.length === 0) {
|
|
755
|
-
|
|
959
|
+
p4.log.info(t("install.nothingSelected"));
|
|
756
960
|
return;
|
|
757
961
|
}
|
|
758
962
|
const stateMap = /* @__PURE__ */ new Map();
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
963
|
+
const sp = p4.spinner();
|
|
964
|
+
sp.start(t("state.checking"));
|
|
965
|
+
try {
|
|
966
|
+
await Promise.all([listPlugins(), listMcp()]);
|
|
967
|
+
await Promise.all(
|
|
968
|
+
candidates.map(async (pkg) => {
|
|
969
|
+
stateMap.set(pkg.id, await deriveState(pkg));
|
|
970
|
+
})
|
|
971
|
+
);
|
|
972
|
+
} finally {
|
|
973
|
+
sp.stop(t("state.checked", { count: candidates.length }));
|
|
974
|
+
}
|
|
764
975
|
let targets;
|
|
765
976
|
if (explicit) {
|
|
766
977
|
targets = candidates;
|
|
767
978
|
} else {
|
|
768
979
|
const picked = await selectInteractive(stateMap);
|
|
769
980
|
if (picked === null) {
|
|
770
|
-
|
|
981
|
+
p4.cancel(t("app.cancelled"));
|
|
771
982
|
return;
|
|
772
983
|
}
|
|
773
984
|
targets = picked;
|
|
774
985
|
}
|
|
775
986
|
if (targets.length === 0) {
|
|
776
|
-
|
|
987
|
+
p4.log.info(t("install.nothingSelected"));
|
|
777
988
|
return;
|
|
778
989
|
}
|
|
779
990
|
const results = [];
|
|
@@ -782,38 +993,52 @@ async function installFlow(opts = {}) {
|
|
|
782
993
|
results.push(await runOne(pkg, state, opts));
|
|
783
994
|
}
|
|
784
995
|
summarize(results);
|
|
996
|
+
await syncFromState({ skip: opts.noClaudeMd });
|
|
785
997
|
}
|
|
786
998
|
|
|
787
999
|
// src/flows/uninstall.ts
|
|
788
|
-
import * as
|
|
1000
|
+
import * as p5 from "@clack/prompts";
|
|
789
1001
|
import pc2 from "picocolors";
|
|
790
1002
|
async function getInstalled() {
|
|
791
1003
|
const states = await Promise.all(PKGS.map(async (pkg) => ({ pkg, installed: await pkg.isInstalled() })));
|
|
792
1004
|
return states.filter((s) => s.installed).map((s) => s.pkg);
|
|
793
1005
|
}
|
|
1006
|
+
async function probeInstalled() {
|
|
1007
|
+
const sp = p5.spinner();
|
|
1008
|
+
sp.start(t("state.checking"));
|
|
1009
|
+
try {
|
|
1010
|
+
await Promise.all([listPlugins(), listMcp()]);
|
|
1011
|
+
const installed = await getInstalled();
|
|
1012
|
+
sp.stop(t("state.checked", { count: installed.length }));
|
|
1013
|
+
return installed;
|
|
1014
|
+
} catch (err) {
|
|
1015
|
+
sp.stop(t("state.checked", { count: 0 }));
|
|
1016
|
+
throw err;
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
794
1019
|
async function uninstallFlow(opts = {}) {
|
|
795
|
-
const installed = await
|
|
1020
|
+
const installed = await probeInstalled();
|
|
796
1021
|
let targets;
|
|
797
1022
|
if (opts.ids && opts.ids.length > 0) {
|
|
798
1023
|
targets = [];
|
|
799
1024
|
for (const id of opts.ids) {
|
|
800
1025
|
const pkg = findPkg(id);
|
|
801
1026
|
if (!pkg) {
|
|
802
|
-
|
|
1027
|
+
p5.log.warn(`Unknown id: ${id}`);
|
|
803
1028
|
continue;
|
|
804
1029
|
}
|
|
805
1030
|
if (!installed.some((x2) => x2.id === pkg.id)) {
|
|
806
|
-
|
|
1031
|
+
p5.log.warn(`${pkg.name}: ${t("pkg.notInstalled")}`);
|
|
807
1032
|
continue;
|
|
808
1033
|
}
|
|
809
1034
|
targets.push(pkg);
|
|
810
1035
|
}
|
|
811
1036
|
} else {
|
|
812
1037
|
if (installed.length === 0) {
|
|
813
|
-
|
|
1038
|
+
p5.log.info(t("uninstall.noneInstalled"));
|
|
814
1039
|
return;
|
|
815
1040
|
}
|
|
816
|
-
const picked = await
|
|
1041
|
+
const picked = await p5.multiselect({
|
|
817
1042
|
message: t("uninstall.selectPrompt"),
|
|
818
1043
|
options: installed.map((pkg) => ({
|
|
819
1044
|
value: pkg.id,
|
|
@@ -822,62 +1047,76 @@ async function uninstallFlow(opts = {}) {
|
|
|
822
1047
|
})),
|
|
823
1048
|
required: false
|
|
824
1049
|
});
|
|
825
|
-
if (
|
|
826
|
-
|
|
1050
|
+
if (p5.isCancel(picked)) {
|
|
1051
|
+
p5.cancel(t("app.cancelled"));
|
|
827
1052
|
return;
|
|
828
1053
|
}
|
|
829
1054
|
targets = picked.map((id) => findPkg(id)).filter((x2) => Boolean(x2));
|
|
830
1055
|
}
|
|
831
1056
|
if (targets.length === 0) {
|
|
832
|
-
|
|
1057
|
+
p5.log.info(t("install.nothingSelected"));
|
|
833
1058
|
return;
|
|
834
1059
|
}
|
|
835
1060
|
if (!opts.yes) {
|
|
836
|
-
const ok2 = await
|
|
1061
|
+
const ok2 = await p5.confirm({
|
|
837
1062
|
message: t("uninstall.confirm", { count: targets.length }),
|
|
838
1063
|
initialValue: false
|
|
839
1064
|
});
|
|
840
|
-
if (
|
|
841
|
-
|
|
1065
|
+
if (p5.isCancel(ok2) || ok2 === false) {
|
|
1066
|
+
p5.cancel(t("app.cancelled"));
|
|
842
1067
|
return;
|
|
843
1068
|
}
|
|
844
1069
|
}
|
|
845
1070
|
const results = [];
|
|
846
1071
|
for (const pkg of targets) {
|
|
847
|
-
const
|
|
1072
|
+
const log5 = p5.taskLog({ title: t("uninstall.starting", { name: pkg.name }) });
|
|
848
1073
|
try {
|
|
849
|
-
await pkg.uninstall({ log:
|
|
850
|
-
|
|
1074
|
+
await pkg.uninstall({ log: log5, config: {}, t });
|
|
1075
|
+
log5.success(t("uninstall.success", { name: pkg.name }));
|
|
851
1076
|
results.push({ id: pkg.id, status: "ok" });
|
|
852
1077
|
} catch (err) {
|
|
853
1078
|
const msg = err instanceof Error ? err.message : String(err);
|
|
854
|
-
|
|
1079
|
+
log5.error(`${t("uninstall.failed", { name: pkg.name })}
|
|
855
1080
|
${msg}`);
|
|
856
1081
|
results.push({ id: pkg.id, status: "fail", message: msg });
|
|
857
1082
|
}
|
|
858
1083
|
}
|
|
859
1084
|
const ok = results.filter((r) => r.status === "ok").length;
|
|
860
1085
|
const fail = results.filter((r) => r.status === "fail").length;
|
|
861
|
-
|
|
1086
|
+
p5.note(
|
|
862
1087
|
[
|
|
863
1088
|
pc2.green(t("install.summaryOk", { count: ok })),
|
|
864
1089
|
pc2.red(t("install.summaryFail", { count: fail }))
|
|
865
1090
|
].join("\n"),
|
|
866
1091
|
t("install.summaryTitle")
|
|
867
1092
|
);
|
|
1093
|
+
await syncFromState({ skip: opts.noClaudeMd });
|
|
868
1094
|
}
|
|
869
1095
|
|
|
870
1096
|
// src/flows/update.ts
|
|
871
|
-
import * as
|
|
1097
|
+
import * as p6 from "@clack/prompts";
|
|
872
1098
|
import pc3 from "picocolors";
|
|
873
1099
|
async function getInstalled2() {
|
|
874
1100
|
const states = await Promise.all(PKGS.map(async (pkg) => ({ pkg, installed: await pkg.isInstalled() })));
|
|
875
1101
|
return states.filter((s) => s.installed).map((s) => s.pkg);
|
|
876
1102
|
}
|
|
1103
|
+
async function probeInstalled2() {
|
|
1104
|
+
const sp = p6.spinner();
|
|
1105
|
+
sp.start(t("state.checking"));
|
|
1106
|
+
try {
|
|
1107
|
+
await Promise.all([listPlugins(), listMcp()]);
|
|
1108
|
+
const installed = await getInstalled2();
|
|
1109
|
+
sp.stop(t("state.checked", { count: installed.length }));
|
|
1110
|
+
return installed;
|
|
1111
|
+
} catch (err) {
|
|
1112
|
+
sp.stop(t("state.checked", { count: 0 }));
|
|
1113
|
+
throw err;
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
877
1116
|
async function updateFlow(opts = {}) {
|
|
878
|
-
const installed = await
|
|
1117
|
+
const installed = await probeInstalled2();
|
|
879
1118
|
if (installed.length === 0) {
|
|
880
|
-
|
|
1119
|
+
p6.log.info(t("update.noneInstalled"));
|
|
881
1120
|
return;
|
|
882
1121
|
}
|
|
883
1122
|
let targets;
|
|
@@ -888,17 +1127,17 @@ async function updateFlow(opts = {}) {
|
|
|
888
1127
|
for (const id of opts.ids) {
|
|
889
1128
|
const pkg = findPkg(id);
|
|
890
1129
|
if (!pkg) {
|
|
891
|
-
|
|
1130
|
+
p6.log.warn(`Unknown id: ${id}`);
|
|
892
1131
|
continue;
|
|
893
1132
|
}
|
|
894
1133
|
if (!installed.some((x2) => x2.id === pkg.id)) {
|
|
895
|
-
|
|
1134
|
+
p6.log.warn(`${pkg.name}: ${t("pkg.notInstalled")}`);
|
|
896
1135
|
continue;
|
|
897
1136
|
}
|
|
898
1137
|
targets.push(pkg);
|
|
899
1138
|
}
|
|
900
1139
|
} else {
|
|
901
|
-
const picked = await
|
|
1140
|
+
const picked = await p6.multiselect({
|
|
902
1141
|
message: t("update.selectPrompt"),
|
|
903
1142
|
options: installed.map((pkg) => ({
|
|
904
1143
|
value: pkg.id,
|
|
@@ -907,41 +1146,41 @@ async function updateFlow(opts = {}) {
|
|
|
907
1146
|
})),
|
|
908
1147
|
required: false
|
|
909
1148
|
});
|
|
910
|
-
if (
|
|
911
|
-
|
|
1149
|
+
if (p6.isCancel(picked)) {
|
|
1150
|
+
p6.cancel(t("app.cancelled"));
|
|
912
1151
|
return;
|
|
913
1152
|
}
|
|
914
1153
|
targets = picked.map((id) => findPkg(id)).filter((x2) => Boolean(x2));
|
|
915
1154
|
}
|
|
916
1155
|
if (targets.length === 0) {
|
|
917
|
-
|
|
1156
|
+
p6.log.info(t("install.nothingSelected"));
|
|
918
1157
|
return;
|
|
919
1158
|
}
|
|
920
1159
|
const results = [];
|
|
921
1160
|
for (const pkg of targets) {
|
|
922
1161
|
if (pkg.id === "sequential-thinking") {
|
|
923
|
-
|
|
1162
|
+
p6.log.info(t("update.mcpAutoNote", { name: pkg.name }));
|
|
924
1163
|
results.push({ id: pkg.id, status: "noop" });
|
|
925
1164
|
continue;
|
|
926
1165
|
}
|
|
927
1166
|
if (pkg.id === "context7") {
|
|
928
|
-
|
|
1167
|
+
p6.log.info(t("update.context7Note"));
|
|
929
1168
|
results.push({ id: pkg.id, status: "noop" });
|
|
930
1169
|
continue;
|
|
931
1170
|
}
|
|
932
|
-
const
|
|
1171
|
+
const log5 = p6.taskLog({ title: t("update.starting", { name: pkg.name }) });
|
|
933
1172
|
try {
|
|
934
1173
|
if (pkg.update) {
|
|
935
|
-
await pkg.update({ log:
|
|
1174
|
+
await pkg.update({ log: log5, config: {}, t });
|
|
936
1175
|
} else {
|
|
937
|
-
await pkg.uninstall({ log:
|
|
938
|
-
await pkg.install({ log:
|
|
1176
|
+
await pkg.uninstall({ log: log5, config: {}, t });
|
|
1177
|
+
await pkg.install({ log: log5, config: {}, t });
|
|
939
1178
|
}
|
|
940
|
-
|
|
1179
|
+
log5.success(t("update.success", { name: pkg.name }));
|
|
941
1180
|
results.push({ id: pkg.id, status: "ok" });
|
|
942
1181
|
} catch (err) {
|
|
943
1182
|
const msg = err instanceof Error ? err.message : String(err);
|
|
944
|
-
|
|
1183
|
+
log5.error(`${t("update.failed", { name: pkg.name })}
|
|
945
1184
|
${msg}`);
|
|
946
1185
|
results.push({ id: pkg.id, status: "fail", message: msg });
|
|
947
1186
|
}
|
|
@@ -949,7 +1188,7 @@ ${msg}`);
|
|
|
949
1188
|
const ok = results.filter((r) => r.status === "ok").length;
|
|
950
1189
|
const fail = results.filter((r) => r.status === "fail").length;
|
|
951
1190
|
const noop = results.filter((r) => r.status === "noop").length;
|
|
952
|
-
|
|
1191
|
+
p6.note(
|
|
953
1192
|
[
|
|
954
1193
|
pc3.green(t("install.summaryOk", { count: ok })),
|
|
955
1194
|
pc3.red(t("install.summaryFail", { count: fail })),
|
|
@@ -957,10 +1196,11 @@ ${msg}`);
|
|
|
957
1196
|
].join("\n"),
|
|
958
1197
|
t("install.summaryTitle")
|
|
959
1198
|
);
|
|
1199
|
+
await syncFromState({ skip: opts.noClaudeMd });
|
|
960
1200
|
}
|
|
961
1201
|
|
|
962
1202
|
// src/flows/status.ts
|
|
963
|
-
import * as
|
|
1203
|
+
import * as p7 from "@clack/prompts";
|
|
964
1204
|
import pc4 from "picocolors";
|
|
965
1205
|
async function statusFlow(opts = {}) {
|
|
966
1206
|
const states = await Promise.all(
|
|
@@ -993,12 +1233,12 @@ async function statusFlow(opts = {}) {
|
|
|
993
1233
|
const rows = states.map(
|
|
994
1234
|
(s) => `${s.name.padEnd(nameW)} ${s.type.padEnd(typeW)} ${s.installed ? pc4.green(`\u2713 ${t("pkg.installed")}`) : pc4.yellow(`\u2717 ${t("pkg.notInstalled")}`)}`
|
|
995
1235
|
);
|
|
996
|
-
|
|
1236
|
+
p7.note([header, sep, ...rows].join("\n"), t("status.title"));
|
|
997
1237
|
}
|
|
998
1238
|
|
|
999
1239
|
// src/ui/menu.ts
|
|
1000
1240
|
async function mainMenu() {
|
|
1001
|
-
const action = await
|
|
1241
|
+
const action = await p8.select({
|
|
1002
1242
|
message: t("menu.title"),
|
|
1003
1243
|
options: [
|
|
1004
1244
|
{ value: "install", label: t("menu.install") },
|
|
@@ -1008,8 +1248,8 @@ async function mainMenu() {
|
|
|
1008
1248
|
{ value: "exit", label: t("menu.exit") }
|
|
1009
1249
|
]
|
|
1010
1250
|
});
|
|
1011
|
-
if (
|
|
1012
|
-
|
|
1251
|
+
if (p8.isCancel(action) || action === "exit") {
|
|
1252
|
+
p8.cancel(t("app.cancelled"));
|
|
1013
1253
|
return;
|
|
1014
1254
|
}
|
|
1015
1255
|
switch (action) {
|
|
@@ -1032,8 +1272,16 @@ async function mainMenu() {
|
|
|
1032
1272
|
function parseLang(v) {
|
|
1033
1273
|
return v === "zh" || v === "en" ? v : void 0;
|
|
1034
1274
|
}
|
|
1275
|
+
function noClaudeMdFromArgs(args) {
|
|
1276
|
+
if (args["no-claude-md"]) return true;
|
|
1277
|
+
return Boolean(process.env["CURDX_FLOW_NO_CLAUDE_MD"]);
|
|
1278
|
+
}
|
|
1035
1279
|
var sharedArgs = {
|
|
1036
|
-
lang: { type: "string", description: "Override language: zh or en" }
|
|
1280
|
+
lang: { type: "string", description: "Override language: zh or en" },
|
|
1281
|
+
"no-claude-md": {
|
|
1282
|
+
type: "boolean",
|
|
1283
|
+
description: "Skip syncing the @curdx/flow block in ~/.claude/CLAUDE.md"
|
|
1284
|
+
}
|
|
1037
1285
|
};
|
|
1038
1286
|
var installCmd = defineCommand({
|
|
1039
1287
|
meta: { name: "install", description: "Install, reinstall, or update plugins / MCP servers" },
|
|
@@ -1046,15 +1294,16 @@ var installCmd = defineCommand({
|
|
|
1046
1294
|
},
|
|
1047
1295
|
async run({ args }) {
|
|
1048
1296
|
await initLanguage(parseLang(args.lang));
|
|
1049
|
-
|
|
1297
|
+
p9.intro(t("app.intro"));
|
|
1050
1298
|
const ids = collectPositional(args);
|
|
1051
1299
|
await installFlow({
|
|
1052
1300
|
ids,
|
|
1053
1301
|
all: Boolean(args.all),
|
|
1054
1302
|
yes: Boolean(args.yes),
|
|
1055
|
-
noRefresh: Boolean(args["no-refresh"])
|
|
1303
|
+
noRefresh: Boolean(args["no-refresh"]),
|
|
1304
|
+
noClaudeMd: noClaudeMdFromArgs(args)
|
|
1056
1305
|
});
|
|
1057
|
-
|
|
1306
|
+
p9.outro(t("app.outro"));
|
|
1058
1307
|
}
|
|
1059
1308
|
});
|
|
1060
1309
|
var uninstallCmd = defineCommand({
|
|
@@ -1066,10 +1315,14 @@ var uninstallCmd = defineCommand({
|
|
|
1066
1315
|
},
|
|
1067
1316
|
async run({ args }) {
|
|
1068
1317
|
await initLanguage(parseLang(args.lang));
|
|
1069
|
-
|
|
1318
|
+
p9.intro(t("app.intro"));
|
|
1070
1319
|
const ids = collectPositional(args);
|
|
1071
|
-
await uninstallFlow({
|
|
1072
|
-
|
|
1320
|
+
await uninstallFlow({
|
|
1321
|
+
ids,
|
|
1322
|
+
yes: Boolean(args.yes),
|
|
1323
|
+
noClaudeMd: noClaudeMdFromArgs(args)
|
|
1324
|
+
});
|
|
1325
|
+
p9.outro(t("app.outro"));
|
|
1073
1326
|
}
|
|
1074
1327
|
});
|
|
1075
1328
|
var updateCmd = defineCommand({
|
|
@@ -1081,10 +1334,14 @@ var updateCmd = defineCommand({
|
|
|
1081
1334
|
},
|
|
1082
1335
|
async run({ args }) {
|
|
1083
1336
|
await initLanguage(parseLang(args.lang));
|
|
1084
|
-
|
|
1337
|
+
p9.intro(t("app.intro"));
|
|
1085
1338
|
const ids = collectPositional(args);
|
|
1086
|
-
await updateFlow({
|
|
1087
|
-
|
|
1339
|
+
await updateFlow({
|
|
1340
|
+
ids,
|
|
1341
|
+
all: Boolean(args.all),
|
|
1342
|
+
noClaudeMd: noClaudeMdFromArgs(args)
|
|
1343
|
+
});
|
|
1344
|
+
p9.outro(t("app.outro"));
|
|
1088
1345
|
}
|
|
1089
1346
|
});
|
|
1090
1347
|
var statusCmd = defineCommand({
|
|
@@ -1095,16 +1352,16 @@ var statusCmd = defineCommand({
|
|
|
1095
1352
|
},
|
|
1096
1353
|
async run({ args }) {
|
|
1097
1354
|
await initLanguage(parseLang(args.lang));
|
|
1098
|
-
if (!args.json)
|
|
1355
|
+
if (!args.json) p9.intro(t("app.intro"));
|
|
1099
1356
|
await statusFlow({ json: Boolean(args.json) });
|
|
1100
|
-
if (!args.json)
|
|
1357
|
+
if (!args.json) p9.outro(t("app.outro"));
|
|
1101
1358
|
}
|
|
1102
1359
|
});
|
|
1103
1360
|
var SUBCOMMANDS = /* @__PURE__ */ new Set(["install", "uninstall", "update", "status"]);
|
|
1104
1361
|
var root = defineCommand({
|
|
1105
1362
|
meta: {
|
|
1106
1363
|
name: "@curdx/flow",
|
|
1107
|
-
version: "3.
|
|
1364
|
+
version: "3.3.1",
|
|
1108
1365
|
description: "Interactive installer for Claude Code plugins and MCP servers"
|
|
1109
1366
|
},
|
|
1110
1367
|
args: sharedArgs,
|
|
@@ -1139,9 +1396,9 @@ async function runInteractive(argv2) {
|
|
|
1139
1396
|
else if (argv2[i]?.startsWith("--lang=")) lang = parseLang(argv2[i].slice("--lang=".length));
|
|
1140
1397
|
}
|
|
1141
1398
|
await initLanguage(lang);
|
|
1142
|
-
|
|
1399
|
+
p9.intro(t("app.intro"));
|
|
1143
1400
|
await mainMenu();
|
|
1144
|
-
|
|
1401
|
+
p9.outro(t("app.outro"));
|
|
1145
1402
|
}
|
|
1146
1403
|
var argv = process.argv.slice(2);
|
|
1147
1404
|
var first = firstNonFlag(argv);
|