claude-flow 3.59.12 → 3.59.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/helpers/hook-handler.cjs +228 -11
- package/.claude/proven-config.json +1 -1
- package/package.json +1 -1
- package/v3/@claude-flow/cli/catalog-manifest.json +2 -2
- package/v3/@claude-flow/cli/dist/src/mcp-tools/claims-tools.d.ts +1 -1
- package/v3/@claude-flow/cli/dist/src/mcp-tools/claims-tools.js +4 -3
- package/v3/@claude-flow/cli/package.json +1 -1
|
@@ -24,9 +24,10 @@ const helpersDir = __dirname;
|
|
|
24
24
|
const helperRoot = path.resolve(helpersDir, '..', '..');
|
|
25
25
|
|
|
26
26
|
// Resolve an installed @claude-flow/cli (or ruflo) bin — mirrors
|
|
27
|
-
// statusline-generator.ts's resolveCliBin() candidate list
|
|
28
|
-
//
|
|
29
|
-
//
|
|
27
|
+
// statusline-generator.ts's resolveCliBin() candidate list, then looks for a
|
|
28
|
+
// global install (resolveGlobalCliBin() below, #3368). Used only to spawn the
|
|
29
|
+
// detached funnel-refresh helper below; failures are silent (no candidate
|
|
30
|
+
// found just means the refresh falls back to npx).
|
|
30
31
|
//
|
|
31
32
|
// Verifies dist/src/index.js exists alongside bin/cli.js, not just the bin
|
|
32
33
|
// itself — Claude Code's own plugin marketplace mechanism installs by
|
|
@@ -39,7 +40,17 @@ const helperRoot = path.resolve(helpersDir, '..', '..');
|
|
|
39
40
|
// first every time and spawnDetachedFunnelRefresh() below had no fallback,
|
|
40
41
|
// so the promo/disclosure row could never populate for any marketplace
|
|
41
42
|
// install, on any OS.
|
|
43
|
+
// session-restore asks up to three times (first-run enable, then the two
|
|
44
|
+
// refreshes). The answer cannot change inside one short-lived hook process,
|
|
45
|
+
// so resolve once: `undefined` means "not looked up yet", null means "no
|
|
46
|
+
// local or global CLI — use npx".
|
|
47
|
+
let cliBinCache;
|
|
42
48
|
function resolveCliBinForHook() {
|
|
49
|
+
if (cliBinCache === undefined) cliBinCache = resolveCliBinUncached();
|
|
50
|
+
return cliBinCache;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function resolveCliBinUncached() {
|
|
43
54
|
try {
|
|
44
55
|
const home = os.homedir();
|
|
45
56
|
const candidates = [
|
|
@@ -53,16 +64,219 @@ function resolveCliBinForHook() {
|
|
|
53
64
|
path.join(helpersDir, '..', '..', 'bin', 'cli.js'),
|
|
54
65
|
];
|
|
55
66
|
for (const p of candidates) {
|
|
56
|
-
|
|
57
|
-
if (fs.existsSync(p) && fs.existsSync(path.join(path.dirname(p), '..', 'dist', 'src', 'index.js'))) {
|
|
58
|
-
return p;
|
|
59
|
-
}
|
|
60
|
-
} catch (e) { /* try next candidate */ }
|
|
67
|
+
if (isRunnableCli(p)) return p;
|
|
61
68
|
}
|
|
69
|
+
return resolveGlobalCliBin();
|
|
62
70
|
} catch (e) { /* ignore */ }
|
|
63
71
|
return null;
|
|
64
72
|
}
|
|
65
73
|
|
|
74
|
+
function isRunnableCli(p) {
|
|
75
|
+
try {
|
|
76
|
+
return fs.existsSync(p) && fs.existsSync(path.join(path.dirname(p), '..', 'dist', 'src', 'index.js'));
|
|
77
|
+
} catch (e) { return false; }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// #3368: a global install — the README's `npm install -g ruflo@latest` — keeps
|
|
81
|
+
// the CLI at <prefix>/lib/node_modules/ruflo/node_modules/@claude-flow/cli,
|
|
82
|
+
// which none of the project-relative candidates above cover, so on those
|
|
83
|
+
// machines every refresh fell back to npx: a second, separately cached copy
|
|
84
|
+
// of the CLI that follows the registry's latest rather than the version the
|
|
85
|
+
// user pinned (the same npx-cache skew as #3306), and that cannot start
|
|
86
|
+
// offline on a cold cache.
|
|
87
|
+
//
|
|
88
|
+
// Three lookups, fs calls only (no child process), reached only after every
|
|
89
|
+
// candidate above missed:
|
|
90
|
+
// 1. npm's global prefix, resolved the way npm resolves it
|
|
91
|
+
// (npmGlobalPrefix() below). First because it does not depend on PATH:
|
|
92
|
+
// a hook inherits the host process's PATH, which need not contain the
|
|
93
|
+
// prefix's bin/ even when the user's interactive shell does (observed:
|
|
94
|
+
// the Claude Code process spawning hooks lacked ~/.npm-global/bin while
|
|
95
|
+
// a login zsh on the same machine had it).
|
|
96
|
+
// 2. `ruflo`, then `claude-flow`, on PATH — the order plugins/ruflo-core/
|
|
97
|
+
// scripts/ruflo-hook.cjs uses — for globals npm did not install (yarn
|
|
98
|
+
// classic, bun). Only a symlinked bin counts, and only when it lands in
|
|
99
|
+
// a package that declares one of the three known names, so a wrapper
|
|
100
|
+
// script or a version-manager shim (Volta) is never mistaken for an
|
|
101
|
+
// install. The <dir>/node_modules/<name> layout npm uses on Windows,
|
|
102
|
+
// where ruflo.cmd sits beside node_modules\ruflo (what resolveNpmShim()
|
|
103
|
+
// in ruflo-hook.cjs maps), is only consulted on win32: on POSIX every
|
|
104
|
+
// manager symlinks, and probing it there would follow a Windows install
|
|
105
|
+
// exposed to WSL through the appended Windows PATH.
|
|
106
|
+
// Relative PATH entries (`.` or an empty entry) are skipped, so a bare
|
|
107
|
+
// `./ruflo` in the working directory is never picked up.
|
|
108
|
+
// 3. ~/.npm-global, npm's documented no-sudo prefix, when 1 and 2 both
|
|
109
|
+
// missed: the prefix setting may live where this hook's node never
|
|
110
|
+
// reads it, and a wrapper script on PATH is skipped by 2.
|
|
111
|
+
// `env`/`platform`/`execPath` are parameters so any layout is testable
|
|
112
|
+
// from any OS.
|
|
113
|
+
function resolveGlobalCliBin(env = process.env, platform = process.platform, execPath = process.execPath) {
|
|
114
|
+
const prefix = npmGlobalPrefix(env, platform, execPath);
|
|
115
|
+
if (prefix) {
|
|
116
|
+
const hit = cliUnderPrefix(prefix, platform);
|
|
117
|
+
if (hit) return hit;
|
|
118
|
+
}
|
|
119
|
+
const dirs = String(env.PATH || env.Path || '').split(platform === 'win32' ? ';' : ':');
|
|
120
|
+
const exts = platform === 'win32' ? ['.cmd', ''] : [''];
|
|
121
|
+
for (const name of ['ruflo', 'claude-flow']) {
|
|
122
|
+
for (const dir of dirs) {
|
|
123
|
+
if (!dir || !path.isAbsolute(dir)) continue;
|
|
124
|
+
for (const ext of exts) {
|
|
125
|
+
const shim = path.join(dir, name + ext);
|
|
126
|
+
if (!fs.existsSync(shim)) continue;
|
|
127
|
+
const roots = [];
|
|
128
|
+
try {
|
|
129
|
+
if (fs.lstatSync(shim).isSymbolicLink()) {
|
|
130
|
+
const root = path.dirname(path.dirname(fs.realpathSync(shim)));
|
|
131
|
+
if (isKnownPackageRoot(root)) roots.push(root);
|
|
132
|
+
}
|
|
133
|
+
} catch (e) { /* unreadable link — fall back to the layout roots */ }
|
|
134
|
+
if (platform === 'win32') {
|
|
135
|
+
roots.push(path.join(dir, 'node_modules', name));
|
|
136
|
+
if (name === 'claude-flow') roots.push(path.join(dir, 'node_modules', '@claude-flow', 'cli'));
|
|
137
|
+
}
|
|
138
|
+
for (const root of roots) {
|
|
139
|
+
const hit = cliInPackage(root);
|
|
140
|
+
if (hit) return hit;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// Last resort: ~/.npm-global, the prefix npm's own docs recommend for
|
|
146
|
+
// global installs without sudo ("Resolving EACCES permissions errors").
|
|
147
|
+
// The setting that points npm there can live where this hook's node never
|
|
148
|
+
// looks (another node's builtin npmrc, or a wrapper script that sets it
|
|
149
|
+
// per call), and a `ruflo` wrapper script on PATH is skipped above by
|
|
150
|
+
// design. Without this, such a machine fell back to npx @latest on every
|
|
151
|
+
// hook. Checked only after the configured prefix and PATH both miss, so it
|
|
152
|
+
// can never override either.
|
|
153
|
+
const home = env.HOME || env.USERPROFILE;
|
|
154
|
+
if (home && path.isAbsolute(home)) {
|
|
155
|
+
const hit = cliUnderPrefix(path.join(home, '.npm-global'), platform);
|
|
156
|
+
if (hit) return hit;
|
|
157
|
+
}
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// The CLI of a global install under an npm prefix: <prefix>/lib/node_modules
|
|
162
|
+
// on POSIX, <prefix>\node_modules on Windows, trying the three published
|
|
163
|
+
// package names in order.
|
|
164
|
+
function cliUnderPrefix(prefix, platform) {
|
|
165
|
+
const modules = platform === 'win32'
|
|
166
|
+
? path.join(prefix, 'node_modules')
|
|
167
|
+
: path.join(prefix, 'lib', 'node_modules');
|
|
168
|
+
for (const pkg of ['ruflo', 'claude-flow', path.join('@claude-flow', 'cli')]) {
|
|
169
|
+
const hit = cliInPackage(path.join(modules, pkg));
|
|
170
|
+
if (hit) return hit;
|
|
171
|
+
}
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// A bin on PATH only counts as an install when the package it points into
|
|
176
|
+
// declares one of the three published names. This is a narrower check than
|
|
177
|
+
// resolveNpmShim() in ruflo-hook.cjs, which never reads `name`: that one takes
|
|
178
|
+
// the package name from the shim's own filename, then validates package.json's
|
|
179
|
+
// `bin` entry and that its realpath stays inside the package. Here the bin has
|
|
180
|
+
// already been resolved to a package root, so the manifest `name` is what
|
|
181
|
+
// identifies it.
|
|
182
|
+
function isKnownPackageRoot(root) {
|
|
183
|
+
try {
|
|
184
|
+
const name = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf-8')).name;
|
|
185
|
+
return name === 'ruflo' || name === 'claude-flow' || name === '@claude-flow/cli';
|
|
186
|
+
} catch (e) { return false; }
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Map an installed ruflo / claude-flow / @claude-flow/cli package root to the
|
|
190
|
+
// CLI it runs: <root>/bin/cli.js (@claude-flow/cli itself),
|
|
191
|
+
// <root>/v3/@claude-flow/cli/bin/cli.js (the claude-flow umbrella bundles
|
|
192
|
+
// it), or @claude-flow/cli in the node_modules npm nests inside <root> —
|
|
193
|
+
// or, for the flat layout yarn classic and bun use, in the node_modules
|
|
194
|
+
// folder that holds <root> itself. That is the dependency edge
|
|
195
|
+
// ruflo/bin/ruflo.js's findCliPath() follows. The search stops at that one
|
|
196
|
+
// enclosing node_modules: walking further up would reach unrelated trees,
|
|
197
|
+
// such as a stray ~/node_modules left by an `npm i` in a home directory.
|
|
198
|
+
// Every hit must still pass isRunnableCli().
|
|
199
|
+
function cliInPackage(root) {
|
|
200
|
+
if (!fs.existsSync(root)) return null;
|
|
201
|
+
const candidates = [
|
|
202
|
+
path.join(root, 'bin', 'cli.js'),
|
|
203
|
+
path.join(root, 'v3', '@claude-flow', 'cli', 'bin', 'cli.js'),
|
|
204
|
+
path.join(root, 'node_modules', '@claude-flow', 'cli', 'bin', 'cli.js'),
|
|
205
|
+
];
|
|
206
|
+
for (let dir = path.dirname(root), i = 0; i < 4 && dir !== path.dirname(dir); i++, dir = path.dirname(dir)) {
|
|
207
|
+
if (path.basename(dir) === 'node_modules') {
|
|
208
|
+
candidates.push(path.join(dir, '@claude-flow', 'cli', 'bin', 'cli.js'));
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return candidates.find(isRunnableCli) || null;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// npm's global prefix without spawning npm (`npm prefix -g` measured 70-80 ms
|
|
216
|
+
// per call, paid on every SessionStart that gets this far). Same precedence
|
|
217
|
+
// npm applies:
|
|
218
|
+
// 1. npm_config_prefix in the environment (npm accepts either case);
|
|
219
|
+
// 2. `prefix=` in the user npmrc ($npm_config_userconfig, else ~/.npmrc),
|
|
220
|
+
// where `npm config set prefix ~/.npm-global` (the npm docs' fix for
|
|
221
|
+
// EACCES on global installs) writes it;
|
|
222
|
+
// 3. `prefix=` in npm's builtin npmrc (<npm package>/npmrc), found through
|
|
223
|
+
// the `npm` link beside node: Homebrew writes `prefix = /opt/homebrew`
|
|
224
|
+
// there (its process.execPath is the Cellar path, so step 4 alone would
|
|
225
|
+
// be wrong), and the Node.js Windows installer ships
|
|
226
|
+
// `prefix=${APPDATA}\npm`;
|
|
227
|
+
// 4. npm's default: $PREFIX, else the directory above node's bin/ (POSIX)
|
|
228
|
+
// or node.exe's own directory (Windows).
|
|
229
|
+
// Values follow npm's ini handling for the cases that decide a path: a
|
|
230
|
+
// quoted value is taken literally, an unquoted `;`/`#` starts a comment,
|
|
231
|
+
// keys below a `[section]` header are ignored, `${VAR}` expands when the
|
|
232
|
+
// variable is set (and `${VAR?}` to empty when it is not) but otherwise
|
|
233
|
+
// stays literal like npm — which leaves the value relative, so the
|
|
234
|
+
// isAbsolute() check below rejects it instead of pointing at the drive root.
|
|
235
|
+
// Not emulated: escaped characters and npm's other ini corner cases; each of
|
|
236
|
+
// those produces a miss, never a wrong CLI. The npmrc is read into memory,
|
|
237
|
+
// but only the value of its last top-level `prefix =` line is kept: no other
|
|
238
|
+
// line (an `_authToken`, say) is returned, stored or logged.
|
|
239
|
+
function npmGlobalPrefix(env = process.env, platform = process.platform, execPath = process.execPath) {
|
|
240
|
+
const home = (platform === 'win32' ? env.USERPROFILE : env.HOME) || os.homedir();
|
|
241
|
+
const readPrefix = (file) => {
|
|
242
|
+
let value = null;
|
|
243
|
+
try {
|
|
244
|
+
for (const line of fs.readFileSync(file, 'utf-8').split(/\r?\n/)) {
|
|
245
|
+
if (/^\s*\[/.test(line)) break; // only the top-level section applies
|
|
246
|
+
const m = /^\s*prefix\s*=\s*(.*?)\s*$/.exec(line);
|
|
247
|
+
if (m) value = m[1]; // last one wins, as in npm's ini parser
|
|
248
|
+
}
|
|
249
|
+
} catch (e) { /* no such npmrc */ }
|
|
250
|
+
return value;
|
|
251
|
+
};
|
|
252
|
+
const nodeDir = path.dirname(execPath);
|
|
253
|
+
// npm's builtin npmrc sits in npm's own package dir. On Windows that is
|
|
254
|
+
// node_modules\npm beside node.exe; elsewhere follow the `npm` link beside
|
|
255
|
+
// node (<npm>/bin/npm-cli.js) — on Homebrew it points out of the node keg
|
|
256
|
+
// to /opt/homebrew/lib/node_modules/npm.
|
|
257
|
+
let builtinRc = path.join(nodeDir, 'node_modules', 'npm', 'npmrc');
|
|
258
|
+
if (platform !== 'win32') {
|
|
259
|
+
try {
|
|
260
|
+
builtinRc = path.join(path.dirname(path.dirname(fs.realpathSync(path.join(nodeDir, 'npm')))), 'npmrc');
|
|
261
|
+
} catch (e) { /* no npm beside node */ }
|
|
262
|
+
}
|
|
263
|
+
const raw = env.npm_config_prefix || env.NPM_CONFIG_PREFIX
|
|
264
|
+
|| readPrefix(env.npm_config_userconfig || env.NPM_CONFIG_USERCONFIG || path.join(home, '.npmrc'))
|
|
265
|
+
|| readPrefix(builtinRc)
|
|
266
|
+
|| env.PREFIX
|
|
267
|
+
|| (platform === 'win32' ? nodeDir : path.dirname(nodeDir));
|
|
268
|
+
let value = String(raw).trim();
|
|
269
|
+
const quoted = /^(["'])([\s\S]*)\1$/.exec(value);
|
|
270
|
+
if (quoted) value = quoted[2];
|
|
271
|
+
else value = value.replace(/(^|[^\\])[;#].*$/, '$1').trim();
|
|
272
|
+
value = value
|
|
273
|
+
.replace(/\$\{([^}?]+)(\?)?\}/g, (whole, name, optional) => (
|
|
274
|
+
env[name] !== undefined ? env[name] : (optional ? '' : whole)
|
|
275
|
+
))
|
|
276
|
+
.replace(/^~(?=$|[\\/])/, home);
|
|
277
|
+
return path.isAbsolute(value) ? path.resolve(value) : null;
|
|
278
|
+
}
|
|
279
|
+
|
|
66
280
|
// Fire-and-forget doesn't work when the CALLER is itself a short-lived
|
|
67
281
|
// subprocess (confirmed live: two consecutive statusline renders 5s apart
|
|
68
282
|
// both saw an empty funnel-messages-cache, because the async HTTPS fetch
|
|
@@ -74,8 +288,8 @@ function resolveCliBinForHook() {
|
|
|
74
288
|
// chance to write the cache. Never awaited here — must not add to
|
|
75
289
|
// SessionStart's own timeout budget.
|
|
76
290
|
//
|
|
77
|
-
// No usable local candidate (resolveCliBinForHook() returned null)
|
|
78
|
-
// back to npx: this call is detached/unref'd, so a slower npx cold-start
|
|
291
|
+
// No usable local or global candidate (resolveCliBinForHook() returned null)
|
|
292
|
+
// falls back to npx: this call is detached/unref'd, so a slower npx cold-start
|
|
79
293
|
// costs nothing perceptible — unlike the statusline's own synchronous
|
|
80
294
|
// render path, where local-first exists purely for per-render latency.
|
|
81
295
|
// `--prefer-offline` avoids a registry round trip for the tarball when
|
|
@@ -1060,4 +1274,7 @@ if (require.main === module) {
|
|
|
1060
1274
|
// Which sibling helpers loaded (all CommonJS, shipped as .cjs — #3555).
|
|
1061
1275
|
const loadedHelpers = { router: !!router, session: !!session, memory: !!memory, intelligence: !!intelligence };
|
|
1062
1276
|
|
|
1063
|
-
|
|
1277
|
+
// resolveGlobalCliBin/npmGlobalPrefix are exported for
|
|
1278
|
+
// hook-handler-global-cli.test.ts; resolveCliBinForHook for anyone checking
|
|
1279
|
+
// what a given machine resolves to without running a hook (#3368).
|
|
1280
|
+
module.exports = { runWithTimeout, INTELLIGENCE_TIMEOUT_MS, loadedHelpers, ownedByMod, MOD_OWNABLE_EVENTS, resolveCliBinForHook, resolveGlobalCliBin, npmGlobalPrefix, cliSpawnArgs, helperRoot, scanSessionStartSettingsForRisk, srFormatRiskFindingsForAutomaticBoot, srScanSettingsForRisk, srScanCommandStringForRisk, srScanAllowRuleForRisk, SR_DANGEROUS_COMMAND_WORDS, SR_SHELL_EXEC_WORDS, SR_DOWNLOADER_WORDS, SR_STANDALONE_RISK_PATTERNS };
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @module @claude-flow/cli/mcp-tools/claims
|
|
8
8
|
*/
|
|
9
|
-
import type
|
|
9
|
+
import { type MCPTool } from './types.js';
|
|
10
10
|
export declare const claimsTools: MCPTool[];
|
|
11
11
|
export default claimsTools;
|
|
12
12
|
//# sourceMappingURL=claims-tools.d.ts.map
|
|
@@ -6,19 +6,20 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @module @claude-flow/cli/mcp-tools/claims
|
|
8
8
|
*/
|
|
9
|
+
import { getProjectCwd } from './types.js';
|
|
9
10
|
import { readClaimsStore } from '../services/claims-store.js';
|
|
10
11
|
import { validateIdentifier, validateText } from './validate-input.js';
|
|
11
12
|
// File-based persistence
|
|
12
13
|
import { existsSync, writeFileSync, mkdirSync } from 'fs';
|
|
13
|
-
import {
|
|
14
|
+
import { resolve } from 'path';
|
|
14
15
|
const CLAIMS_DIR = '.claude-flow/claims';
|
|
15
16
|
const CLAIMS_FILE = 'claims.json';
|
|
16
17
|
const DEFAULT_MAX_CLAIMS = 5;
|
|
17
18
|
function getClaimsPath() {
|
|
18
|
-
return resolve(
|
|
19
|
+
return resolve(getProjectCwd(), CLAIMS_DIR, CLAIMS_FILE);
|
|
19
20
|
}
|
|
20
21
|
function ensureClaimsDir() {
|
|
21
|
-
const dir = resolve(CLAIMS_DIR);
|
|
22
|
+
const dir = resolve(getProjectCwd(), CLAIMS_DIR);
|
|
22
23
|
if (!existsSync(dir)) {
|
|
23
24
|
mkdirSync(dir, { recursive: true });
|
|
24
25
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claude-flow/cli",
|
|
3
|
-
"version": "3.59.
|
|
3
|
+
"version": "3.59.13",
|
|
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",
|