@claude-flow/cli 3.27.0 → 3.27.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.
@@ -1 +1 @@
1
- 3.25.6
1
+ 3.23.0
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "manifest": {
3
- "version": "3.27.0",
3
+ "version": "3.27.2",
4
4
  "files": {
5
5
  "auto-memory-hook.mjs": "68be7e9a9eba7bf9c4e8a230db7bf61a243b965639f8504842799d6c6ca28762",
6
- "hook-handler.cjs": "2a18a761f0dc4839df8b6eeadf8f0ceb6badcc5bf14414c91555711a5e9d4bdc",
6
+ "hook-handler.cjs": "2e4927ff158c0079a67a6f0ef0b99ac748d600d33798b0a5fbd5a6a82225ec03",
7
7
  "intelligence.cjs": "bd1f8e4b034944aee1df0391dc47ac2e8cc4b3aa542c65407a59d49620bbf76b",
8
- "statusline.cjs": "8abc67b7512e66fe5f8fe6cc5f099f827de6f7e2023a493f920090248d326e01"
8
+ "statusline.cjs": "f553f12a34e7df405a64501cdedb684374ef92670279380bc95f625fc68b2d1b"
9
9
  }
10
10
  },
11
- "signature": "f0oa4b6TIdjXxPc8TFjFEXODgr4+gNsdQXc54NszbvhpR066pXyaCgxrYTsN9iRwFFY1dp6RtaUCuaMpN4NqDg==",
11
+ "signature": "40Y8+mFRgH7LZZ5TeeDM6aIUcAKrNaN82nyJJzFyepx/HM1chZeK2yEUIbtPhyE0PdyoO4W1OuR3mlxLHgBjCA==",
12
12
  "algorithm": "ed25519"
13
13
  }
@@ -23,6 +23,18 @@ const helpersDir = __dirname;
23
23
  // statusline-generator.ts's resolveCliBin() candidate list. Used only to
24
24
  // spawn the detached funnel-refresh helper below; failures are silent (no
25
25
  // candidate found just means the refresh never fires this session).
26
+ //
27
+ // Verifies dist/src/index.js exists alongside bin/cli.js, not just the bin
28
+ // itself — Claude Code's own plugin marketplace mechanism installs by
29
+ // `git clone`/`git pull` with no build step, so `~/.claude/plugins/
30
+ // marketplaces/ruflo` is a SOURCE-ONLY checkout by construction: bin/cli.js
31
+ // is present on disk but importing dist/src/index.js throws
32
+ // ERR_MODULE_NOT_FOUND on every real command (confirmed live — only
33
+ // `--version` happens to survive it, since it reads package.json directly).
34
+ // Without this check, resolveCliBinForHook() picked that doomed candidate
35
+ // first every time and spawnDetachedFunnelRefresh() below had no fallback,
36
+ // so the promo/disclosure row could never populate for any marketplace
37
+ // install, on any OS.
26
38
  function resolveCliBinForHook() {
27
39
  try {
28
40
  const home = os.homedir();
@@ -38,7 +50,11 @@ function resolveCliBinForHook() {
38
50
  path.join(helpersDir, '..', '..', 'bin', 'cli.js'),
39
51
  ];
40
52
  for (const p of candidates) {
41
- if (fs.existsSync(p)) return p;
53
+ try {
54
+ if (fs.existsSync(p) && fs.existsSync(path.join(path.dirname(p), '..', 'dist', 'src', 'index.js'))) {
55
+ return p;
56
+ }
57
+ } catch (e) { /* try next candidate */ }
42
58
  }
43
59
  } catch (e) { /* ignore */ }
44
60
  return null;
@@ -54,12 +70,22 @@ function resolveCliBinForHook() {
54
70
  // session-restore's own process has already exited, so it actually gets a
55
71
  // chance to write the cache. Never awaited here — must not add to
56
72
  // SessionStart's own timeout budget.
57
- function spawnDetachedFunnelRefresh() {
73
+ //
74
+ // No usable local candidate (resolveCliBinForHook() returned null) falls
75
+ // back to npx: this call is detached/unref'd, so a slower npx cold-start
76
+ // costs nothing perceptible — unlike the statusline's own synchronous
77
+ // render path, where local-first exists purely for per-render latency.
78
+ // `--prefer-offline` avoids a registry round trip for the tarball when
79
+ // already cached while still resolving the current `@latest` version.
80
+ function spawnDetachedHookRefresh(subcommand) {
58
81
  try {
59
- const cliBin = resolveCliBinForHook();
60
- if (!cliBin) return;
61
82
  const { spawn } = require('child_process');
62
- const child = spawn(process.execPath, [cliBin, 'hooks', 'refresh-funnel', '--quiet'], {
83
+ const cliBin = resolveCliBinForHook();
84
+ const cmd = process.platform === 'win32' ? 'npx.cmd' : 'npx';
85
+ const spawnArgs = cliBin
86
+ ? [process.execPath, [cliBin, 'hooks', subcommand, '--quiet']]
87
+ : [cmd, ['--prefer-offline', '@claude-flow/cli', 'hooks', subcommand, '--quiet']];
88
+ const child = spawn(spawnArgs[0], spawnArgs[1], {
63
89
  detached: true,
64
90
  stdio: 'ignore',
65
91
  env: process.env,
@@ -68,24 +94,18 @@ function spawnDetachedFunnelRefresh() {
68
94
  } catch (e) { /* best-effort only */ }
69
95
  }
70
96
 
71
- // Same detached/unref'd pattern as spawnDetachedFunnelRefresh() above, for
97
+ function spawnDetachedFunnelRefresh() {
98
+ spawnDetachedHookRefresh('refresh-funnel');
99
+ }
100
+
101
+ // Same fallback-aware pattern as spawnDetachedFunnelRefresh() above, for
72
102
  // ADR-316's co-pilot advisor tip. Safe to call on EVERY session-restore:
73
103
  // refresh-advisor's own action checks consent + a 24h TTL BEFORE spending
74
104
  // anything, so an unconsented or already-fresh install is a fast no-op file
75
105
  // read, never a network call. Never awaited here — must not add to
76
106
  // SessionStart's own timeout budget.
77
107
  function spawnDetachedAdvisorRefresh() {
78
- try {
79
- const cliBin = resolveCliBinForHook();
80
- if (!cliBin) return;
81
- const { spawn } = require('child_process');
82
- const child = spawn(process.execPath, [cliBin, 'hooks', 'refresh-advisor', '--quiet'], {
83
- detached: true,
84
- stdio: 'ignore',
85
- env: process.env,
86
- });
87
- child.unref();
88
- } catch (e) { /* best-effort only */ }
108
+ spawnDetachedHookRefresh('refresh-advisor');
89
109
  }
90
110
 
91
111
  // Safe require with stdout suppression - the helper modules have CLI
@@ -116,7 +116,20 @@ function resolveCliBinCandidates() {
116
116
  }
117
117
  } catch { /* ignore */ }
118
118
  } catch { /* ignore */ }
119
- return candidates.filter((p) => { try { return fs.existsSync(p); } catch { return false; } });
119
+ return candidates.filter((p) => {
120
+ try {
121
+ if (!fs.existsSync(p)) return false;
122
+ // A candidate's bin/cli.js can exist on disk while its compiled
123
+ // dist/ never got built (Claude Code's own plugin marketplace just
124
+ // git-clones the repo — no install/build step — so every marketplace
125
+ // install is a source-only checkout by construction). Importing
126
+ // dist/src/index.js from bin/cli.js then throws MODULE_NOT_FOUND on
127
+ // every real command; only --version happens to survive it. Check
128
+ // for the compiled entrypoint too so a doomed candidate is skipped
129
+ // up front instead of wasting a spawn-and-fail on every render.
130
+ return fs.existsSync(path.join(path.dirname(p), '..', 'dist', 'src', 'index.js'));
131
+ } catch { return false; }
132
+ });
120
133
  }
121
134
 
122
135
  // Return { fresh, promoFresh, data }. 'fresh' is true only if within the TTL
@@ -585,6 +598,22 @@ function getCostFromStdin() {
585
598
  }
586
599
 
587
600
  // Read package version from the first package.json we find.
601
+ // Compares dotted-numeric version strings (e.g. "3.27.1" vs "3.27.10").
602
+ // Returns >0 if a>b, <0 if a<b, 0 if equal-as-far-as-parseable. Deliberately
603
+ // simple (no prerelease/build-metadata handling) — this only orders local
604
+ // package.json versions against each other, never anything untrusted from
605
+ // a payload, so a full semver implementation would be dead weight here.
606
+ function compareVersions(a, b) {
607
+ const pa = String(a).split('.').map((n) => parseInt(n, 10));
608
+ const pb = String(b).split('.').map((n) => parseInt(n, 10));
609
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
610
+ const na = Number.isFinite(pa[i]) ? pa[i] : 0;
611
+ const nb = Number.isFinite(pb[i]) ? pb[i] : 0;
612
+ if (na !== nb) return na - nb;
613
+ }
614
+ return 0;
615
+ }
616
+
588
617
  function getPkgVersion() {
589
618
  let ver = '3.6';
590
619
  try {
@@ -616,11 +645,24 @@ function getPkgVersion() {
616
645
  );
617
646
  }
618
647
  } catch { /* ignore */ }
648
+ // Pick the HIGHEST version among every candidate that exists, not the
649
+ // first one found. The marketplace plugin path is probed first (list
650
+ // order above), but Claude Code's own plugin marketplace mechanism
651
+ // syncs on its own git-pull cadence, independent of npm publishes — a
652
+ // freshly-published npm version can sit alongside a stale marketplace
653
+ // checkout for a while (observed live: marketplace one release behind
654
+ // right after a publish). Taking the first EXISTING candidate meant the
655
+ // header could show a stale version even when a newer install (e.g.
656
+ // node_modules/@claude-flow/cli from a plain npm install) was sitting right there.
657
+ let found = false;
619
658
  for (const p of pkgPaths) {
620
659
  if (!fs.existsSync(p)) continue;
621
660
  try {
622
661
  const pkg = JSON.parse(fs.readFileSync(p, 'utf-8'));
623
- if (pkg && typeof pkg.version === 'string' && pkg.version.length > 0) { ver = pkg.version; break; }
662
+ if (pkg && typeof pkg.version === 'string' && pkg.version.length > 0) {
663
+ if (!found || compareVersions(pkg.version, ver) > 0) ver = pkg.version;
664
+ found = true;
665
+ }
624
666
  } catch { /* ignore */ }
625
667
  }
626
668
  } catch { /* ignore */ }
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "generation": 1,
4
- "generatedAt": "2026-07-14T03:21:11.654Z",
5
- "gitSha": "87212ddb",
4
+ "generatedAt": "2026-07-14T04:02:10.768Z",
5
+ "gitSha": "dbcfe15f",
6
6
  "catalog": {
7
7
  "agents": 164,
8
8
  "tools": 387,
@@ -139,7 +139,20 @@ function resolveCliBinCandidates() {
139
139
  }
140
140
  } catch { /* ignore */ }
141
141
  } catch { /* ignore */ }
142
- return candidates.filter((p) => { try { return fs.existsSync(p); } catch { return false; } });
142
+ return candidates.filter((p) => {
143
+ try {
144
+ if (!fs.existsSync(p)) return false;
145
+ // A candidate's bin/cli.js can exist on disk while its compiled
146
+ // dist/ never got built (Claude Code's own plugin marketplace just
147
+ // git-clones the repo — no install/build step — so every marketplace
148
+ // install is a source-only checkout by construction). Importing
149
+ // dist/src/index.js from bin/cli.js then throws MODULE_NOT_FOUND on
150
+ // every real command; only --version happens to survive it. Check
151
+ // for the compiled entrypoint too so a doomed candidate is skipped
152
+ // up front instead of wasting a spawn-and-fail on every render.
153
+ return fs.existsSync(path.join(path.dirname(p), '..', 'dist', 'src', 'index.js'));
154
+ } catch { return false; }
155
+ });
143
156
  }
144
157
 
145
158
  // Return { fresh, promoFresh, data }. 'fresh' is true only if within the TTL
@@ -608,6 +621,22 @@ function getCostFromStdin() {
608
621
  }
609
622
 
610
623
  // Read package version from the first package.json we find.
624
+ // Compares dotted-numeric version strings (e.g. "3.27.1" vs "3.27.10").
625
+ // Returns >0 if a>b, <0 if a<b, 0 if equal-as-far-as-parseable. Deliberately
626
+ // simple (no prerelease/build-metadata handling) — this only orders local
627
+ // package.json versions against each other, never anything untrusted from
628
+ // a payload, so a full semver implementation would be dead weight here.
629
+ function compareVersions(a, b) {
630
+ const pa = String(a).split('.').map((n) => parseInt(n, 10));
631
+ const pb = String(b).split('.').map((n) => parseInt(n, 10));
632
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
633
+ const na = Number.isFinite(pa[i]) ? pa[i] : 0;
634
+ const nb = Number.isFinite(pb[i]) ? pb[i] : 0;
635
+ if (na !== nb) return na - nb;
636
+ }
637
+ return 0;
638
+ }
639
+
611
640
  function getPkgVersion() {
612
641
  let ver = '3.6';
613
642
  try {
@@ -639,11 +668,24 @@ function getPkgVersion() {
639
668
  );
640
669
  }
641
670
  } catch { /* ignore */ }
671
+ // Pick the HIGHEST version among every candidate that exists, not the
672
+ // first one found. The marketplace plugin path is probed first (list
673
+ // order above), but Claude Code's own plugin marketplace mechanism
674
+ // syncs on its own git-pull cadence, independent of npm publishes — a
675
+ // freshly-published npm version can sit alongside a stale marketplace
676
+ // checkout for a while (observed live: marketplace one release behind
677
+ // right after a publish). Taking the first EXISTING candidate meant the
678
+ // header could show a stale version even when a newer install (e.g.
679
+ // node_modules/@claude-flow/cli from a plain npm install) was sitting right there.
680
+ let found = false;
642
681
  for (const p of pkgPaths) {
643
682
  if (!fs.existsSync(p)) continue;
644
683
  try {
645
684
  const pkg = JSON.parse(fs.readFileSync(p, 'utf-8'));
646
- if (pkg && typeof pkg.version === 'string' && pkg.version.length > 0) { ver = pkg.version; break; }
685
+ if (pkg && typeof pkg.version === 'string' && pkg.version.length > 0) {
686
+ if (!found || compareVersions(pkg.version, ver) > 0) ver = pkg.version;
687
+ found = true;
688
+ }
647
689
  } catch { /* ignore */ }
648
690
  }
649
691
  } catch { /* ignore */ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claude-flow/cli",
3
- "version": "3.27.0",
3
+ "version": "3.27.2",
4
4
  "type": "module",
5
5
  "description": "Ruflo CLI - Enterprise AI agent orchestration with 60+ specialized agents, swarm coordination, MCP server, self-learning hooks, and vector memory for Claude Code",
6
6
  "main": "dist/src/index.js",