@curdx/flow 7.1.0 → 7.1.2

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +16 -1
  2. package/dist/index.mjs +218 -112
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -2,7 +2,22 @@
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
- ## 7.1.0 — 2026-05-04
5
+ ## 7.1.2 — 2026-05-04
6
+
7
+ ### Added
8
+
9
+ - **Auto-detect-and-install Bun when installing `claude-mem`.** The `claude-mem` plugin's runtime hooks shell out to `node scripts/bun-runner.js` which requires Bun on `PATH` or `~/.bun/bin/bun(.exe)` — Windows users without Bun previously hit `Error: Bun not found` at every Claude Code session start. The installer now runs `ensureBun()` as a `prereqCheck` before installing `claude-mem`: detects Bun via `which`/`where` plus the standard fallback paths (mirrors `bun-runner.js`'s discovery order), and if missing prompts `Auto-install Bun now? (default: No)`. On accept, runs the official installer (`curl -fsSL https://bun.sh/install | bash` on macOS/Linux, `powershell -c "irm bun.sh/install.ps1 | iex"` on Windows). On decline, the installer surfaces a `skip` row for `claude-mem` and continues with the rest of the bundle — other packages are unaffected. Existing users with Bun already installed (e.g. all macOS users who manually ran the installer earlier) see zero behavior change.
10
+ - New module: `src/runner/ensureBun.ts` (~75 LoC) — exports `findBun()` and `ensureBun(t)`.
11
+ - `src/registry/plugins/claude-mem.ts` declares `prereqCheck: (t) => ensureBun(t)` (one-line wire-in via the existing `Pkg.prereqCheck` contract that `chrome-devtools-mcp` already uses).
12
+ - i18n: 9 new `bun.*` keys in `src/i18n/{zh,en}.ts`.
13
+
14
+ ## 7.1.1 — 2026-05-04
15
+
16
+ ### Fixed
17
+
18
+ - **Windows Chrome detection in the installer's `chrome-devtools-mcp` pre-req check.** `src/registry/plugins/chrome-devtools-mcp.ts`'s `checkChrome()` previously ran `test -x /Applications/...` plus `which google-chrome` / `which chromium` on every platform — none of which work on Windows (`test` is a Unix builtin, `which` isn't on `cmd`/PowerShell PATH by default, and the macOS app-bundle path doesn't exist). Result: the installer rejected Windows machines with Chrome installed, reporting "需要本机已安装 Chrome / Requires Chrome installed locally". Detection is now per-platform, mirroring `GoogleChrome/chrome-launcher`'s `chrome-finder` strategy: on `win32`, scan `LOCALAPPDATA` / `PROGRAMFILES` / `PROGRAMFILES(X86)` for `Google\Chrome\Application\chrome.exe` and `Google\Chrome SxS\Application\chrome.exe` (Canary) via `fs.existsSync`; on `darwin`, check the canonical app-bundle path; on Linux, the existing `which` lookup. A `CHROME_PATH` env var now overrides on all platforms (matches chrome-launcher / Lighthouse convention).
19
+
20
+
6
21
 
7
22
  ### Added
8
23
 
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 p9 from "@clack/prompts";
4
+ import * as p10 from "@clack/prompts";
5
5
  import { defineCommand, runMain } from "citty";
6
6
 
7
7
  // src/ui/language.ts
@@ -66,6 +66,15 @@ var messages = {
66
66
  "context7.dashboardHint": "\u53EF\u5728 https://context7.com/dashboard \u521B\u5EFA API Key",
67
67
  "chrome.prereqNode": "\u9700\u8981 Node.js >= 20.19\uFF0C\u5F53\u524D\u7248\u672C {current}",
68
68
  "chrome.prereqChrome": "\u9700\u8981\u672C\u673A\u5DF2\u5B89\u88C5 Chrome\uFF08chrome-devtools-mcp \u4F1A\u8C03\u7528\u672C\u5730\u6D4F\u89C8\u5668\uFF09",
69
+ "bun.missing": "\u672A\u68C0\u6D4B\u5230 Bun\uFF0Cclaude-mem \u7684\u540E\u53F0 worker \u4F9D\u8D56 Bun \u8FD0\u884C\u3002",
70
+ "bun.installerSource": "Bun \u5B89\u88C5\u811A\u672C\u6765\u6E90\uFF1Ahttps://bun.sh\uFF08macOS/Linux \u7528 curl\uFF0CWindows \u7528 powershell irm\uFF09\u3002",
71
+ "bun.confirmInstall": "\u662F\u5426\u73B0\u5728\u81EA\u52A8\u5B89\u88C5 Bun\uFF1F\uFF08\u9ED8\u8BA4\u5426\u2014\u2014\u9009\u5426\u5C06\u8DF3\u8FC7 claude-mem\uFF0C\u5176\u4ED6\u63D2\u4EF6\u7EE7\u7EED\u5B89\u88C5\uFF09",
72
+ "bun.declined": "\u5DF2\u8DF3\u8FC7 Bun \u5B89\u88C5\uFF1Bclaude-mem \u9700\u8981 Bun\uFF0C\u672C\u6B21\u4E0D\u5B89\u88C5\u3002\u624B\u52A8\u88C5\u597D Bun \u540E\u53EF\u91CD\u8DD1 install\u3002",
73
+ "bun.installing": "\u6B63\u5728\u4E0B\u8F7D\u5E76\u5B89\u88C5 Bun\uFF08\u9996\u6B21\u7EA6 60MB\uFF09\u2026",
74
+ "bun.installed": "Bun \u5B89\u88C5\u5B8C\u6210\u3002",
75
+ "bun.installFailedTitle": "Bun \u5B89\u88C5\u5931\u8D25",
76
+ "bun.installFailedReason": "Bun \u5B89\u88C5\u5931\u8D25\uFF1A{error}",
77
+ "bun.installedButNotFound": "Bun \u5B89\u88C5\u811A\u672C\u58F0\u660E\u6210\u529F\uFF0C\u4F46\u672A\u5728\u5DF2\u77E5\u8DEF\u5F84\u627E\u5230 bun \u53EF\u6267\u884C\u6587\u4EF6\u2014\u2014\u8BF7\u624B\u52A8\u786E\u8BA4\u540E\u91CD\u8DD1\u3002",
69
78
  "reinstall.uninstalling": "\u5148\u5378\u8F7D\u65E7\u7248\u672C\u2026",
70
79
  "reinstall.installing": "\u5B89\u88C5\u65B0\u7248\u672C\u2026",
71
80
  "state.checking": "\u68C0\u67E5\u5DF2\u5B89\u88C5\u72B6\u6001\u2026\uFF08claude plugin list / mcp list\uFF09",
@@ -138,6 +147,15 @@ var messages2 = {
138
147
  "context7.dashboardHint": "Get a key at https://context7.com/dashboard",
139
148
  "chrome.prereqNode": "Requires Node.js >= 20.19 (current: {current})",
140
149
  "chrome.prereqChrome": "Requires Chrome installed locally (chrome-devtools-mcp drives the local browser)",
150
+ "bun.missing": "Bun runtime not found \u2014 claude-mem's background worker requires Bun.",
151
+ "bun.installerSource": "Bun installer source: https://bun.sh (curl on macOS/Linux, PowerShell irm on Windows).",
152
+ "bun.confirmInstall": "Auto-install Bun now? (default: No \u2014 declining will skip claude-mem; other packages continue)",
153
+ "bun.declined": "Bun install declined; claude-mem requires Bun and will be skipped this run. Install Bun manually and re-run install.",
154
+ "bun.installing": "Downloading and installing Bun (~60 MB on first run)\u2026",
155
+ "bun.installed": "Bun installed.",
156
+ "bun.installFailedTitle": "Bun install failed",
157
+ "bun.installFailedReason": "Bun install failed: {error}",
158
+ "bun.installedButNotFound": "Bun installer reported success but bun was not found at any known path \u2014 verify manually and re-run.",
141
159
  "reinstall.uninstalling": "Uninstalling old version\u2026",
142
160
  "reinstall.installing": "Installing new version\u2026",
143
161
  "state.checking": "Checking installed state\u2026 (claude plugin list / mcp list)",
@@ -196,10 +214,10 @@ async function initLanguage(override) {
196
214
  }
197
215
 
198
216
  // src/ui/menu.ts
199
- import * as p8 from "@clack/prompts";
217
+ import * as p9 from "@clack/prompts";
200
218
 
201
219
  // src/flows/install.ts
202
- import * as p4 from "@clack/prompts";
220
+ import * as p5 from "@clack/prompts";
203
221
  import pc from "picocolors";
204
222
 
205
223
  // src/runner/state.ts
@@ -218,15 +236,15 @@ async function run(cmd, args) {
218
236
  stderr: result.stderr
219
237
  };
220
238
  }
221
- async function runStreaming(cmd, args, log5) {
222
- log5.message(`$ ${cmd} ${args.join(" ")}`);
239
+ async function runStreaming(cmd, args, log6) {
240
+ log6.message(`$ ${cmd} ${args.join(" ")}`);
223
241
  const proc = x(cmd, args, { throwOnError: false });
224
242
  let stdout = "";
225
243
  for await (const line of proc) {
226
244
  const trimmed = line.replace(/\r?\n$/, "");
227
245
  if (trimmed.length > 0) {
228
246
  stdout += trimmed + "\n";
229
- log5.message(trimmed);
247
+ log6.message(trimmed);
230
248
  }
231
249
  }
232
250
  const finished = await proc;
@@ -269,15 +287,15 @@ async function listPlugins(force = false) {
269
287
  }
270
288
  try {
271
289
  const arr = JSON.parse(res.stdout);
272
- pluginCache = arr.map((p10) => {
273
- const [name = p10.id, marketplace = ""] = p10.id.split("@");
290
+ pluginCache = arr.map((p11) => {
291
+ const [name = p11.id, marketplace = ""] = p11.id.split("@");
274
292
  return {
275
- id: p10.id,
293
+ id: p11.id,
276
294
  name,
277
295
  marketplace,
278
- version: p10.version,
279
- scope: p10.scope,
280
- enabled: p10.enabled
296
+ version: p11.version,
297
+ scope: p11.scope,
298
+ enabled: p11.enabled
281
299
  };
282
300
  });
283
301
  } catch {
@@ -323,7 +341,7 @@ async function listMcp(force = false) {
323
341
  }
324
342
  async function isPluginInstalled(id) {
325
343
  const list = await listPlugins();
326
- return list.some((p10) => p10.id === id);
344
+ return list.some((p11) => p11.id === id);
327
345
  }
328
346
  async function isMarketplaceAdded(name) {
329
347
  const list = await listMarketplaces();
@@ -335,7 +353,7 @@ async function isMcpInstalled(name) {
335
353
  }
336
354
  async function findPlugin(id) {
337
355
  const list = await listPlugins();
338
- return list.find((p10) => p10.id === id);
356
+ return list.find((p11) => p11.id === id);
339
357
  }
340
358
  var marketplaceJsonCache = /* @__PURE__ */ new Map();
341
359
  function marketplaceDir(name) {
@@ -357,7 +375,7 @@ async function readMarketplaceJson(name) {
357
375
  async function getMarketplacePluginVersion(marketplaceName, pluginName) {
358
376
  const m = await readMarketplaceJson(marketplaceName);
359
377
  if (!m?.plugins) return null;
360
- const entry = m.plugins.find((p10) => p10.name === pluginName);
378
+ const entry = m.plugins.find((p11) => p11.name === pluginName);
361
379
  return entry?.version ?? null;
362
380
  }
363
381
  var REFRESH_TTL_MS = 60 * 60 * 1e3;
@@ -478,8 +496,8 @@ var pua = {
478
496
  marketplaces: () => [MARKETPLACE_NAME],
479
497
  isInstalled: () => isPluginInstalled(PLUGIN_ID),
480
498
  installedVersion: async () => {
481
- const p10 = await findPlugin(PLUGIN_ID);
482
- const v = p10?.version;
499
+ const p11 = await findPlugin(PLUGIN_ID);
500
+ const v = p11?.version;
483
501
  return v && v !== "unknown" ? v : null;
484
502
  },
485
503
  latestVersion: () => getMarketplacePluginVersion(MARKETPLACE_NAME, PLUGIN_NAME),
@@ -492,6 +510,72 @@ var pua = {
492
510
  };
493
511
  var pua_default = pua;
494
512
 
513
+ // src/runner/ensureBun.ts
514
+ import { existsSync } from "fs";
515
+ import { homedir } from "os";
516
+ import path2 from "path";
517
+ import * as p2 from "@clack/prompts";
518
+ var IS_WINDOWS = process.platform === "win32";
519
+ function fallbackPaths() {
520
+ const home = homedir();
521
+ if (IS_WINDOWS) return [path2.join(home, ".bun", "bin", "bun.exe")];
522
+ return [
523
+ path2.join(home, ".bun", "bin", "bun"),
524
+ "/usr/local/bin/bun",
525
+ "/opt/homebrew/bin/bun",
526
+ "/home/linuxbrew/.linuxbrew/bin/bun"
527
+ ];
528
+ }
529
+ async function findBun() {
530
+ const probe = IS_WINDOWS ? await run("where", ["bun"]) : await run("which", ["bun"]);
531
+ if (probe.exitCode === 0 && probe.stdout.trim()) {
532
+ const lines = probe.stdout.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
533
+ if (IS_WINDOWS) {
534
+ const cmd = lines.find((l) => l.toLowerCase().endsWith("bun.cmd") || l.toLowerCase().endsWith("bun.exe"));
535
+ if (cmd) return cmd;
536
+ }
537
+ if (lines[0]) return lines[0];
538
+ }
539
+ for (const candidate of fallbackPaths()) {
540
+ if (existsSync(candidate)) return candidate;
541
+ }
542
+ return null;
543
+ }
544
+ async function runInstaller() {
545
+ const result = IS_WINDOWS ? await run("powershell", ["-NoProfile", "-Command", "irm bun.sh/install.ps1 | iex"]) : await run("bash", ["-lc", "curl -fsSL https://bun.sh/install | bash"]);
546
+ if (result.exitCode !== 0) {
547
+ const detail = (result.stderr || result.stdout || "").trim().slice(0, 500);
548
+ return { ok: false, error: detail || `exit ${result.exitCode}` };
549
+ }
550
+ return { ok: true };
551
+ }
552
+ async function ensureBun(t2) {
553
+ const found = await findBun();
554
+ if (found) return { ok: true };
555
+ p2.log.warn(t2("bun.missing"));
556
+ p2.log.info(t2("bun.installerSource"));
557
+ const ans = await p2.confirm({
558
+ message: t2("bun.confirmInstall"),
559
+ initialValue: false
560
+ });
561
+ if (p2.isCancel(ans) || ans === false) {
562
+ return { ok: false, reason: t2("bun.declined") };
563
+ }
564
+ const sp = p2.spinner();
565
+ sp.start(t2("bun.installing"));
566
+ const result = await runInstaller();
567
+ if (!result.ok) {
568
+ sp.stop(t2("bun.installFailedTitle"));
569
+ return { ok: false, reason: t2("bun.installFailedReason", { error: result.error }) };
570
+ }
571
+ sp.stop(t2("bun.installed"));
572
+ const reFound = await findBun();
573
+ if (!reFound) {
574
+ return { ok: false, reason: t2("bun.installedButNotFound") };
575
+ }
576
+ return { ok: true };
577
+ }
578
+
495
579
  // src/registry/plugins/claude-mem.ts
496
580
  var PLUGIN_ID2 = "claude-mem@thedotmack";
497
581
  var PLUGIN_NAME2 = "claude-mem";
@@ -505,10 +589,11 @@ var claudeMem = {
505
589
  slashNamespace: "/claude-mem:*",
506
590
  whenToUse: 'for cross-session memory search ("did we solve this before?"), phased planning (`make-plan`), or phased execution (`do`).',
507
591
  marketplaces: () => [MARKETPLACE_NAME2],
592
+ prereqCheck: (t2) => ensureBun(t2),
508
593
  isInstalled: () => isPluginInstalled(PLUGIN_ID2),
509
594
  installedVersion: async () => {
510
- const p10 = await findPlugin(PLUGIN_ID2);
511
- const v = p10?.version;
595
+ const p11 = await findPlugin(PLUGIN_ID2);
596
+ const v = p11?.version;
512
597
  return v && v !== "unknown" ? v : null;
513
598
  },
514
599
  latestVersion: () => getMarketplacePluginVersion(MARKETPLACE_NAME2, PLUGIN_NAME2),
@@ -522,17 +607,38 @@ var claudeMem = {
522
607
  var claude_mem_default = claudeMem;
523
608
 
524
609
  // src/registry/plugins/chrome-devtools-mcp.ts
610
+ import { existsSync as existsSync2 } from "fs";
611
+ import path3 from "path";
525
612
  var PLUGIN_ID3 = "chrome-devtools-mcp@chrome-devtools-plugins";
526
613
  var MARKETPLACE_NAME3 = "chrome-devtools-plugins";
527
614
  var MARKETPLACE_SOURCE3 = "ChromeDevTools/chrome-devtools-mcp";
528
615
  async function checkChrome() {
529
- const macPath = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
530
- const [stat, viaPath, viaPathChromium] = await Promise.all([
531
- run("test", ["-x", macPath]),
616
+ if (process.env.CHROME_PATH && existsSync2(process.env.CHROME_PATH)) return true;
617
+ if (process.platform === "win32") {
618
+ const suffixes = [
619
+ path3.join("Google", "Chrome SxS", "Application", "chrome.exe"),
620
+ path3.join("Google", "Chrome", "Application", "chrome.exe")
621
+ ];
622
+ const prefixes = [
623
+ process.env.LOCALAPPDATA,
624
+ process.env.PROGRAMFILES,
625
+ process.env["PROGRAMFILES(X86)"]
626
+ ].filter((p11) => Boolean(p11));
627
+ for (const prefix of prefixes) {
628
+ for (const suffix of suffixes) {
629
+ if (existsSync2(path3.join(prefix, suffix))) return true;
630
+ }
631
+ }
632
+ return false;
633
+ }
634
+ if (process.platform === "darwin") {
635
+ return existsSync2("/Applications/Google Chrome.app/Contents/MacOS/Google Chrome");
636
+ }
637
+ const [viaPath, viaPathChromium] = await Promise.all([
532
638
  run("which", ["google-chrome"]),
533
639
  run("which", ["chromium"])
534
640
  ]);
535
- return stat.exitCode === 0 || viaPath.exitCode === 0 || viaPathChromium.exitCode === 0;
641
+ return viaPath.exitCode === 0 || viaPathChromium.exitCode === 0;
536
642
  }
537
643
  var chromeDevtoolsMcp = {
538
644
  id: "chrome-devtools-mcp",
@@ -592,8 +698,8 @@ var curdxFlow = {
592
698
  marketplaces: () => [MARKETPLACE_NAME4],
593
699
  isInstalled: () => isPluginInstalled(PLUGIN_ID5),
594
700
  installedVersion: async () => {
595
- const p10 = await findPlugin(PLUGIN_ID5);
596
- const v = p10?.version;
701
+ const p11 = await findPlugin(PLUGIN_ID5);
702
+ const v = p11?.version;
597
703
  return v && v !== "unknown" ? v : null;
598
704
  },
599
705
  latestVersion: () => getMarketplacePluginVersion(MARKETPLACE_NAME4, PLUGIN_NAME3),
@@ -646,7 +752,7 @@ var sequentialThinking = {
646
752
  var sequential_thinking_default = sequentialThinking;
647
753
 
648
754
  // src/registry/mcps/context7.ts
649
- import * as p2 from "@clack/prompts";
755
+ import * as p3 from "@clack/prompts";
650
756
  var MCP_NAME2 = "context7";
651
757
  var URL = "https://mcp.context7.com/mcp";
652
758
  var context7 = {
@@ -657,14 +763,14 @@ var context7 = {
657
763
  whenToUse: "for any library / SDK / framework / API / Claude Code docs lookup. Use instead of web search.",
658
764
  isInstalled: () => isMcpInstalled(MCP_NAME2),
659
765
  configPrompts: async ({ t: t2 }) => {
660
- p2.note(`${t2("context7.dashboardHint")}
766
+ p3.note(`${t2("context7.dashboardHint")}
661
767
  ${t2("context7.keyWarning")}`, "context7");
662
- const key = await p2.text({
768
+ const key = await p3.text({
663
769
  message: t2("context7.askKey"),
664
770
  placeholder: t2("context7.keyPlaceholder"),
665
771
  defaultValue: ""
666
772
  });
667
- if (p2.isCancel(key)) return null;
773
+ if (p3.isCancel(key)) return null;
668
774
  const trimmed = String(key ?? "").trim();
669
775
  const out = {};
670
776
  if (trimmed) out["CONTEXT7_API_KEY"] = trimmed;
@@ -708,19 +814,19 @@ var PKGS = [
708
814
  context7_default
709
815
  ];
710
816
  function findPkg(id) {
711
- return PKGS.find((p10) => p10.id === id);
817
+ return PKGS.find((p11) => p11.id === id);
712
818
  }
713
819
 
714
820
  // src/runner/claudeMd.ts
715
821
  import { promises as fs2 } from "fs";
716
- import path2 from "path";
822
+ import path4 from "path";
717
823
  import os2 from "os";
718
- import * as p3 from "@clack/prompts";
824
+ import * as p4 from "@clack/prompts";
719
825
  var BEGIN_MARKER = "<!-- BEGIN @curdx/flow v1 -->";
720
826
  var END_MARKER = "<!-- END @curdx/flow v1 -->";
721
827
  var BLOCK_RE = /<!-- BEGIN @curdx\/flow v\d+[^>]*-->[\s\S]*?<!-- END @curdx\/flow v\d+ -->/;
722
828
  function claudeMdPath() {
723
- return path2.join(os2.homedir(), ".claude", "CLAUDE.md");
829
+ return path4.join(os2.homedir(), ".claude", "CLAUDE.md");
724
830
  }
725
831
  function buildCombinationPatterns(ids) {
726
832
  const has = (k) => ids.has(k);
@@ -896,7 +1002,7 @@ async function syncClaudeMd(opts) {
896
1002
  if (next === existing) {
897
1003
  return { status: "unchanged", path: file };
898
1004
  }
899
- await fs2.mkdir(path2.dirname(file), { recursive: true });
1005
+ await fs2.mkdir(path4.dirname(file), { recursive: true });
900
1006
  const tmp = `${file}.tmp.${process.pid}`;
901
1007
  await fs2.writeFile(tmp, next, "utf8");
902
1008
  await fs2.rename(tmp, file);
@@ -910,10 +1016,10 @@ async function syncClaudeMd(opts) {
910
1016
  }
911
1017
  async function syncFromState(opts) {
912
1018
  if (opts?.skip) {
913
- p3.log.info(t("claudeMd.skipped"));
1019
+ p4.log.info(t("claudeMd.skipped"));
914
1020
  return;
915
1021
  }
916
- const sp = p3.spinner();
1022
+ const sp = p4.spinner();
917
1023
  sp.start(t("claudeMd.syncing"));
918
1024
  const r = await syncClaudeMd();
919
1025
  switch (r.status) {
@@ -968,7 +1074,7 @@ async function selectInteractive(states) {
968
1074
  const optionalPkgs = PKGS.filter((pkg) => !pkg.required);
969
1075
  if (requiredPkgs.length > 0) {
970
1076
  const lines = requiredPkgs.map((pkg) => ` ${stateLabel(pkg, states.get(pkg.id))}`);
971
- p4.note(lines.join("\n"), t("install.requiredHeader"));
1077
+ p5.note(lines.join("\n"), t("install.requiredHeader"));
972
1078
  }
973
1079
  const options = optionalPkgs.map((pkg) => {
974
1080
  const s = states.get(pkg.id);
@@ -978,13 +1084,13 @@ async function selectInteractive(states) {
978
1084
  const s = states.get(pkg.id);
979
1085
  return s.kind === "not_installed" || s.kind === "update_available";
980
1086
  }).map((pkg) => pkg.id);
981
- const picked = await p4.multiselect({
1087
+ const picked = await p5.multiselect({
982
1088
  message: t("install.selectPrompt"),
983
1089
  options,
984
1090
  initialValues,
985
1091
  required: false
986
1092
  });
987
- if (p4.isCancel(picked)) return null;
1093
+ if (p5.isCancel(picked)) return null;
988
1094
  const userPicked = picked.map((id) => findPkg(id)).filter((x2) => Boolean(x2));
989
1095
  const requiredAuto = requiredPkgs.filter((pkg) => states.get(pkg.id)?.kind !== "up_to_date");
990
1096
  return [...requiredAuto, ...userPicked];
@@ -996,7 +1102,7 @@ function selectFromIds(opts) {
996
1102
  for (const id of opts.ids) {
997
1103
  const pkg = findPkg(id);
998
1104
  if (pkg) found.push(pkg);
999
- else p4.log.warn(`Unknown id: ${id}`);
1105
+ else p5.log.warn(`Unknown id: ${id}`);
1000
1106
  }
1001
1107
  return found;
1002
1108
  }
@@ -1008,11 +1114,11 @@ async function runOne(pkg, state, opts) {
1008
1114
  mode = "update";
1009
1115
  } else {
1010
1116
  if (!opts.yes) {
1011
- const ans = await p4.confirm({
1117
+ const ans = await p5.confirm({
1012
1118
  message: t("install.confirmReinstall", { name: pkg.name }),
1013
1119
  initialValue: false
1014
1120
  });
1015
- if (p4.isCancel(ans) || ans === false) {
1121
+ if (p5.isCancel(ans) || ans === false) {
1016
1122
  return { id: pkg.id, status: "skip", message: t("install.skippedReinstall", { name: pkg.name }) };
1017
1123
  }
1018
1124
  }
@@ -1021,7 +1127,7 @@ async function runOne(pkg, state, opts) {
1021
1127
  if (pkg.prereqCheck) {
1022
1128
  const r = await pkg.prereqCheck(t);
1023
1129
  if (!r.ok) {
1024
- p4.log.warn(t("install.prereqFail", { name: pkg.name, reason: r.reason }));
1130
+ p5.log.warn(t("install.prereqFail", { name: pkg.name, reason: r.reason }));
1025
1131
  return { id: pkg.id, status: "skip", message: r.reason };
1026
1132
  }
1027
1133
  }
@@ -1036,30 +1142,30 @@ async function runOne(pkg, state, opts) {
1036
1142
  if (mode === "update" && state.kind === "update_available") {
1037
1143
  titleVars["version"] = state.latest;
1038
1144
  }
1039
- const log5 = p4.taskLog({ title: t(titleKey, titleVars) });
1145
+ const log6 = p5.taskLog({ title: t(titleKey, titleVars) });
1040
1146
  try {
1041
1147
  if (mode === "reinstall") {
1042
- log5.message(t("reinstall.uninstalling"));
1043
- await pkg.uninstall({ log: log5, config, t });
1044
- log5.message(t("reinstall.installing"));
1045
- await pkg.install({ log: log5, config, t });
1148
+ log6.message(t("reinstall.uninstalling"));
1149
+ await pkg.uninstall({ log: log6, config, t });
1150
+ log6.message(t("reinstall.installing"));
1151
+ await pkg.install({ log: log6, config, t });
1046
1152
  } else if (mode === "update") {
1047
1153
  if (pkg.update) {
1048
- await pkg.update({ log: log5, config, t });
1154
+ await pkg.update({ log: log6, config, t });
1049
1155
  } else {
1050
- log5.message(t("reinstall.uninstalling"));
1051
- await pkg.uninstall({ log: log5, config, t });
1052
- log5.message(t("reinstall.installing"));
1053
- await pkg.install({ log: log5, config, t });
1156
+ log6.message(t("reinstall.uninstalling"));
1157
+ await pkg.uninstall({ log: log6, config, t });
1158
+ log6.message(t("reinstall.installing"));
1159
+ await pkg.install({ log: log6, config, t });
1054
1160
  }
1055
1161
  } else {
1056
- await pkg.install({ log: log5, config, t });
1162
+ await pkg.install({ log: log6, config, t });
1057
1163
  }
1058
- log5.success(t("install.success", { name: pkg.name }));
1164
+ log6.success(t("install.success", { name: pkg.name }));
1059
1165
  return { id: pkg.id, status: "ok" };
1060
1166
  } catch (err) {
1061
1167
  const msg = err instanceof Error ? err.message : String(err);
1062
- log5.error(`${t("install.failed", { name: pkg.name })}
1168
+ log6.error(`${t("install.failed", { name: pkg.name })}
1063
1169
  ${msg}`);
1064
1170
  return { id: pkg.id, status: "fail", message: msg };
1065
1171
  }
@@ -1077,7 +1183,7 @@ function summarize(results) {
1077
1183
  ...skip.map((r) => ` ${pc.yellow("-")} ${r.id}${r.message ? pc.dim(` (${r.message})`) : ""}`),
1078
1184
  ...fail.map((r) => ` ${pc.red("\u2717")} ${r.id}${r.message ? pc.dim(` (${r.message.split("\n")[0]})`) : ""}`)
1079
1185
  ];
1080
- p4.note(lines.join("\n"), t("install.summaryTitle"));
1186
+ p5.note(lines.join("\n"), t("install.summaryTitle"));
1081
1187
  }
1082
1188
  async function maybeRefreshMarketplaces(opts) {
1083
1189
  if (opts.noRefresh) return;
@@ -1086,7 +1192,7 @@ async function maybeRefreshMarketplaces(opts) {
1086
1192
  if (pkg.marketplaces) for (const n of pkg.marketplaces()) names.add(n);
1087
1193
  }
1088
1194
  if (names.size === 0) return;
1089
- const sp = p4.spinner();
1195
+ const sp = p5.spinner();
1090
1196
  sp.start(t("marketplace.refreshing"));
1091
1197
  const refreshed = await refreshMarketplaces([...names]);
1092
1198
  sp.stop(
@@ -1100,7 +1206,7 @@ async function installFlow(opts = {}) {
1100
1206
  const explicit = opts.all || opts.ids && opts.ids.length > 0;
1101
1207
  const candidates = explicit ? selectFromIds(opts) : [...PKGS];
1102
1208
  if (candidates.length === 0) {
1103
- p4.log.info(t("install.nothingSelected"));
1209
+ p5.log.info(t("install.nothingSelected"));
1104
1210
  return;
1105
1211
  }
1106
1212
  if (opts.ids && opts.ids.length > 0) {
@@ -1111,7 +1217,7 @@ async function installFlow(opts = {}) {
1111
1217
  }
1112
1218
  }
1113
1219
  const stateMap = /* @__PURE__ */ new Map();
1114
- const sp = p4.spinner();
1220
+ const sp = p5.spinner();
1115
1221
  sp.start(t("state.checking"));
1116
1222
  try {
1117
1223
  await Promise.all([listPlugins(), listMcp()]);
@@ -1139,13 +1245,13 @@ async function installFlow(opts = {}) {
1139
1245
  const picked = await selectInteractive(stateMap);
1140
1246
  if (picked === null) {
1141
1247
  userCancelled = true;
1142
- p4.cancel(t("app.cancelled"));
1248
+ p5.cancel(t("app.cancelled"));
1143
1249
  return;
1144
1250
  }
1145
1251
  targets = picked;
1146
1252
  }
1147
1253
  if (targets.length === 0) {
1148
- p4.log.info(t("install.nothingSelected"));
1254
+ p5.log.info(t("install.nothingSelected"));
1149
1255
  return;
1150
1256
  }
1151
1257
  const results = [];
@@ -1162,14 +1268,14 @@ async function installFlow(opts = {}) {
1162
1268
  }
1163
1269
 
1164
1270
  // src/flows/uninstall.ts
1165
- import * as p5 from "@clack/prompts";
1271
+ import * as p6 from "@clack/prompts";
1166
1272
  import pc2 from "picocolors";
1167
1273
  async function getInstalled() {
1168
1274
  const states = await Promise.all(PKGS.map(async (pkg) => ({ pkg, installed: await pkg.isInstalled() })));
1169
1275
  return states.filter((s) => s.installed).map((s) => s.pkg);
1170
1276
  }
1171
1277
  async function probeInstalled() {
1172
- const sp = p5.spinner();
1278
+ const sp = p6.spinner();
1173
1279
  sp.start(t("state.checking"));
1174
1280
  try {
1175
1281
  await Promise.all([listPlugins(), listMcp()]);
@@ -1191,21 +1297,21 @@ async function uninstallFlow(opts = {}) {
1191
1297
  for (const id of opts.ids) {
1192
1298
  const pkg = findPkg(id);
1193
1299
  if (!pkg) {
1194
- p5.log.warn(`Unknown id: ${id}`);
1300
+ p6.log.warn(`Unknown id: ${id}`);
1195
1301
  continue;
1196
1302
  }
1197
1303
  if (!installed.some((x2) => x2.id === pkg.id)) {
1198
- p5.log.warn(`${pkg.name}: ${t("pkg.notInstalled")}`);
1304
+ p6.log.warn(`${pkg.name}: ${t("pkg.notInstalled")}`);
1199
1305
  continue;
1200
1306
  }
1201
1307
  targets.push(pkg);
1202
1308
  }
1203
1309
  } else {
1204
1310
  if (installed.length === 0) {
1205
- p5.log.info(t("uninstall.noneInstalled"));
1311
+ p6.log.info(t("uninstall.noneInstalled"));
1206
1312
  return;
1207
1313
  }
1208
- const picked = await p5.multiselect({
1314
+ const picked = await p6.multiselect({
1209
1315
  message: t("uninstall.selectPrompt"),
1210
1316
  options: installed.map((pkg) => ({
1211
1317
  value: pkg.id,
@@ -1214,45 +1320,45 @@ async function uninstallFlow(opts = {}) {
1214
1320
  })),
1215
1321
  required: false
1216
1322
  });
1217
- if (p5.isCancel(picked)) {
1323
+ if (p6.isCancel(picked)) {
1218
1324
  userCancelled = true;
1219
- p5.cancel(t("app.cancelled"));
1325
+ p6.cancel(t("app.cancelled"));
1220
1326
  return;
1221
1327
  }
1222
1328
  targets = picked.map((id) => findPkg(id)).filter((x2) => Boolean(x2));
1223
1329
  }
1224
1330
  if (targets.length === 0) {
1225
- p5.log.info(t("install.nothingSelected"));
1331
+ p6.log.info(t("install.nothingSelected"));
1226
1332
  return;
1227
1333
  }
1228
1334
  if (!opts.yes) {
1229
- const ok2 = await p5.confirm({
1335
+ const ok2 = await p6.confirm({
1230
1336
  message: t("uninstall.confirm", { count: targets.length }),
1231
1337
  initialValue: false
1232
1338
  });
1233
- if (p5.isCancel(ok2) || ok2 === false) {
1339
+ if (p6.isCancel(ok2) || ok2 === false) {
1234
1340
  userCancelled = true;
1235
- p5.cancel(t("app.cancelled"));
1341
+ p6.cancel(t("app.cancelled"));
1236
1342
  return;
1237
1343
  }
1238
1344
  }
1239
1345
  const results = [];
1240
1346
  for (const pkg of targets) {
1241
- const log5 = p5.taskLog({ title: t("uninstall.starting", { name: pkg.name }) });
1347
+ const log6 = p6.taskLog({ title: t("uninstall.starting", { name: pkg.name }) });
1242
1348
  try {
1243
- await pkg.uninstall({ log: log5, config: {}, t });
1244
- log5.success(t("uninstall.success", { name: pkg.name }));
1349
+ await pkg.uninstall({ log: log6, config: {}, t });
1350
+ log6.success(t("uninstall.success", { name: pkg.name }));
1245
1351
  results.push({ id: pkg.id, status: "ok" });
1246
1352
  } catch (err) {
1247
1353
  const msg = err instanceof Error ? err.message : String(err);
1248
- log5.error(`${t("uninstall.failed", { name: pkg.name })}
1354
+ log6.error(`${t("uninstall.failed", { name: pkg.name })}
1249
1355
  ${msg}`);
1250
1356
  results.push({ id: pkg.id, status: "fail", message: msg });
1251
1357
  }
1252
1358
  }
1253
1359
  const ok = results.filter((r) => r.status === "ok").length;
1254
1360
  const fail = results.filter((r) => r.status === "fail").length;
1255
- p5.note(
1361
+ p6.note(
1256
1362
  [
1257
1363
  pc2.green(t("install.summaryOk", { count: ok })),
1258
1364
  pc2.red(t("install.summaryFail", { count: fail }))
@@ -1267,14 +1373,14 @@ ${msg}`);
1267
1373
  }
1268
1374
 
1269
1375
  // src/flows/update.ts
1270
- import * as p6 from "@clack/prompts";
1376
+ import * as p7 from "@clack/prompts";
1271
1377
  import pc3 from "picocolors";
1272
1378
  async function getInstalled2() {
1273
1379
  const states = await Promise.all(PKGS.map(async (pkg) => ({ pkg, installed: await pkg.isInstalled() })));
1274
1380
  return states.filter((s) => s.installed).map((s) => s.pkg);
1275
1381
  }
1276
1382
  async function probeInstalled2() {
1277
- const sp = p6.spinner();
1383
+ const sp = p7.spinner();
1278
1384
  sp.start(t("state.checking"));
1279
1385
  try {
1280
1386
  await Promise.all([listPlugins(), listMcp()]);
@@ -1291,7 +1397,7 @@ async function updateFlow(opts = {}) {
1291
1397
  try {
1292
1398
  const installed = await probeInstalled2();
1293
1399
  if (installed.length === 0) {
1294
- p6.log.info(t("update.noneInstalled"));
1400
+ p7.log.info(t("update.noneInstalled"));
1295
1401
  return;
1296
1402
  }
1297
1403
  let targets;
@@ -1302,17 +1408,17 @@ async function updateFlow(opts = {}) {
1302
1408
  for (const id of opts.ids) {
1303
1409
  const pkg = findPkg(id);
1304
1410
  if (!pkg) {
1305
- p6.log.warn(`Unknown id: ${id}`);
1411
+ p7.log.warn(`Unknown id: ${id}`);
1306
1412
  continue;
1307
1413
  }
1308
1414
  if (!installed.some((x2) => x2.id === pkg.id)) {
1309
- p6.log.warn(`${pkg.name}: ${t("pkg.notInstalled")}`);
1415
+ p7.log.warn(`${pkg.name}: ${t("pkg.notInstalled")}`);
1310
1416
  continue;
1311
1417
  }
1312
1418
  targets.push(pkg);
1313
1419
  }
1314
1420
  } else {
1315
- const picked = await p6.multiselect({
1421
+ const picked = await p7.multiselect({
1316
1422
  message: t("update.selectPrompt"),
1317
1423
  options: installed.map((pkg) => ({
1318
1424
  value: pkg.id,
@@ -1321,42 +1427,42 @@ async function updateFlow(opts = {}) {
1321
1427
  })),
1322
1428
  required: false
1323
1429
  });
1324
- if (p6.isCancel(picked)) {
1430
+ if (p7.isCancel(picked)) {
1325
1431
  userCancelled = true;
1326
- p6.cancel(t("app.cancelled"));
1432
+ p7.cancel(t("app.cancelled"));
1327
1433
  return;
1328
1434
  }
1329
1435
  targets = picked.map((id) => findPkg(id)).filter((x2) => Boolean(x2));
1330
1436
  }
1331
1437
  if (targets.length === 0) {
1332
- p6.log.info(t("install.nothingSelected"));
1438
+ p7.log.info(t("install.nothingSelected"));
1333
1439
  return;
1334
1440
  }
1335
1441
  const results = [];
1336
1442
  for (const pkg of targets) {
1337
1443
  if (pkg.id === "sequential-thinking") {
1338
- p6.log.info(t("update.mcpAutoNote", { name: pkg.name }));
1444
+ p7.log.info(t("update.mcpAutoNote", { name: pkg.name }));
1339
1445
  results.push({ id: pkg.id, status: "noop" });
1340
1446
  continue;
1341
1447
  }
1342
1448
  if (pkg.id === "context7") {
1343
- p6.log.info(t("update.context7Note"));
1449
+ p7.log.info(t("update.context7Note"));
1344
1450
  results.push({ id: pkg.id, status: "noop" });
1345
1451
  continue;
1346
1452
  }
1347
- const log5 = p6.taskLog({ title: t("update.starting", { name: pkg.name }) });
1453
+ const log6 = p7.taskLog({ title: t("update.starting", { name: pkg.name }) });
1348
1454
  try {
1349
1455
  if (pkg.update) {
1350
- await pkg.update({ log: log5, config: {}, t });
1456
+ await pkg.update({ log: log6, config: {}, t });
1351
1457
  } else {
1352
- await pkg.uninstall({ log: log5, config: {}, t });
1353
- await pkg.install({ log: log5, config: {}, t });
1458
+ await pkg.uninstall({ log: log6, config: {}, t });
1459
+ await pkg.install({ log: log6, config: {}, t });
1354
1460
  }
1355
- log5.success(t("update.success", { name: pkg.name }));
1461
+ log6.success(t("update.success", { name: pkg.name }));
1356
1462
  results.push({ id: pkg.id, status: "ok" });
1357
1463
  } catch (err) {
1358
1464
  const msg = err instanceof Error ? err.message : String(err);
1359
- log5.error(`${t("update.failed", { name: pkg.name })}
1465
+ log6.error(`${t("update.failed", { name: pkg.name })}
1360
1466
  ${msg}`);
1361
1467
  results.push({ id: pkg.id, status: "fail", message: msg });
1362
1468
  }
@@ -1364,7 +1470,7 @@ ${msg}`);
1364
1470
  const ok = results.filter((r) => r.status === "ok").length;
1365
1471
  const fail = results.filter((r) => r.status === "fail").length;
1366
1472
  const noop = results.filter((r) => r.status === "noop").length;
1367
- p6.note(
1473
+ p7.note(
1368
1474
  [
1369
1475
  pc3.green(t("install.summaryOk", { count: ok })),
1370
1476
  pc3.red(t("install.summaryFail", { count: fail })),
@@ -1380,7 +1486,7 @@ ${msg}`);
1380
1486
  }
1381
1487
 
1382
1488
  // src/flows/status.ts
1383
- import * as p7 from "@clack/prompts";
1489
+ import * as p8 from "@clack/prompts";
1384
1490
  import pc4 from "picocolors";
1385
1491
  async function statusFlow(opts = {}) {
1386
1492
  const states = await Promise.all(
@@ -1413,12 +1519,12 @@ async function statusFlow(opts = {}) {
1413
1519
  const rows = states.map(
1414
1520
  (s) => `${s.name.padEnd(nameW)} ${s.type.padEnd(typeW)} ${s.installed ? pc4.green(`\u2713 ${t("pkg.installed")}`) : pc4.yellow(`\u2717 ${t("pkg.notInstalled")}`)}`
1415
1521
  );
1416
- p7.note([header, sep, ...rows].join("\n"), t("status.title"));
1522
+ p8.note([header, sep, ...rows].join("\n"), t("status.title"));
1417
1523
  }
1418
1524
 
1419
1525
  // src/ui/menu.ts
1420
1526
  async function mainMenu() {
1421
- const action = await p8.select({
1527
+ const action = await p9.select({
1422
1528
  message: t("menu.title"),
1423
1529
  options: [
1424
1530
  { value: "install", label: t("menu.install") },
@@ -1428,8 +1534,8 @@ async function mainMenu() {
1428
1534
  { value: "exit", label: t("menu.exit") }
1429
1535
  ]
1430
1536
  });
1431
- if (p8.isCancel(action) || action === "exit") {
1432
- p8.cancel(t("app.cancelled"));
1537
+ if (p9.isCancel(action) || action === "exit") {
1538
+ p9.cancel(t("app.cancelled"));
1433
1539
  return;
1434
1540
  }
1435
1541
  switch (action) {
@@ -1474,7 +1580,7 @@ var installCmd = defineCommand({
1474
1580
  },
1475
1581
  async run({ args }) {
1476
1582
  await initLanguage(parseLang(args.lang));
1477
- p9.intro(t("app.intro"));
1583
+ p10.intro(t("app.intro"));
1478
1584
  const ids = collectPositional(args);
1479
1585
  await installFlow({
1480
1586
  ids,
@@ -1483,7 +1589,7 @@ var installCmd = defineCommand({
1483
1589
  noRefresh: Boolean(args["no-refresh"]),
1484
1590
  noClaudeMd: noClaudeMdFromArgs(args)
1485
1591
  });
1486
- p9.outro(t("app.outro"));
1592
+ p10.outro(t("app.outro"));
1487
1593
  }
1488
1594
  });
1489
1595
  var uninstallCmd = defineCommand({
@@ -1495,14 +1601,14 @@ var uninstallCmd = defineCommand({
1495
1601
  },
1496
1602
  async run({ args }) {
1497
1603
  await initLanguage(parseLang(args.lang));
1498
- p9.intro(t("app.intro"));
1604
+ p10.intro(t("app.intro"));
1499
1605
  const ids = collectPositional(args);
1500
1606
  await uninstallFlow({
1501
1607
  ids,
1502
1608
  yes: Boolean(args.yes),
1503
1609
  noClaudeMd: noClaudeMdFromArgs(args)
1504
1610
  });
1505
- p9.outro(t("app.outro"));
1611
+ p10.outro(t("app.outro"));
1506
1612
  }
1507
1613
  });
1508
1614
  var updateCmd = defineCommand({
@@ -1514,14 +1620,14 @@ var updateCmd = defineCommand({
1514
1620
  },
1515
1621
  async run({ args }) {
1516
1622
  await initLanguage(parseLang(args.lang));
1517
- p9.intro(t("app.intro"));
1623
+ p10.intro(t("app.intro"));
1518
1624
  const ids = collectPositional(args);
1519
1625
  await updateFlow({
1520
1626
  ids,
1521
1627
  all: Boolean(args.all),
1522
1628
  noClaudeMd: noClaudeMdFromArgs(args)
1523
1629
  });
1524
- p9.outro(t("app.outro"));
1630
+ p10.outro(t("app.outro"));
1525
1631
  }
1526
1632
  });
1527
1633
  var statusCmd = defineCommand({
@@ -1532,9 +1638,9 @@ var statusCmd = defineCommand({
1532
1638
  },
1533
1639
  async run({ args }) {
1534
1640
  await initLanguage(parseLang(args.lang));
1535
- if (!args.json) p9.intro(t("app.intro"));
1641
+ if (!args.json) p10.intro(t("app.intro"));
1536
1642
  await statusFlow({ json: Boolean(args.json) });
1537
- if (!args.json) p9.outro(t("app.outro"));
1643
+ if (!args.json) p10.outro(t("app.outro"));
1538
1644
  }
1539
1645
  });
1540
1646
  var SUBCOMMANDS = /* @__PURE__ */ new Set(["install", "uninstall", "update", "status"]);
@@ -1576,9 +1682,9 @@ async function runInteractive(argv2) {
1576
1682
  else if (argv2[i]?.startsWith("--lang=")) lang = parseLang(argv2[i].slice("--lang=".length));
1577
1683
  }
1578
1684
  await initLanguage(lang);
1579
- p9.intro(t("app.intro"));
1685
+ p10.intro(t("app.intro"));
1580
1686
  await mainMenu();
1581
- p9.outro(t("app.outro"));
1687
+ p10.outro(t("app.outro"));
1582
1688
  }
1583
1689
  var argv = process.argv.slice(2);
1584
1690
  var first = firstNonFlag(argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@curdx/flow",
3
- "version": "7.1.0",
3
+ "version": "7.1.2",
4
4
  "description": "Interactive installer for Claude Code plugins and MCP servers",
5
5
  "type": "module",
6
6
  "bin": "./dist/index.mjs",