claude-flow 3.63.4 → 3.63.5
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/proven-config.json +1 -1
- package/.claude-plugin/marketplace.json +3 -3
- package/README.md +1 -1
- package/package.json +2 -2
- package/v3/@claude-flow/cli/dist/src/commands/hooks.js +1 -1
- package/v3/@claude-flow/cli/dist/src/commands/status.js +2 -1
- package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.js +1 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/http-fetch-tools.d.ts +1 -1
- package/v3/@claude-flow/cli/dist/src/mcp-tools/http-fetch-tools.js +95 -9
- package/v3/@claude-flow/cli/dist/src/mcp-tools/system-tools.js +1 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/task-tools.js +3 -1
- package/v3/@claude-flow/cli/dist/src/mcp-tools/workflow-tools.js +3 -1
- package/v3/@claude-flow/cli/dist/src/mods/claude-installs.js +108 -3
- package/v3/@claude-flow/cli/package.json +2 -2
|
@@ -57,10 +57,10 @@
|
|
|
57
57
|
"source": "git-subdir",
|
|
58
58
|
"url": "https://github.com/proffesor-for-testing/agentic-qe.git",
|
|
59
59
|
"path": "plugins/agentic-qe-fleet",
|
|
60
|
-
"ref": "v3.15.
|
|
61
|
-
"sha": "
|
|
60
|
+
"ref": "v3.15.15",
|
|
61
|
+
"sha": "1d0cc76632e8663ca2dffeaf3e000ef7baf3d481"
|
|
62
62
|
},
|
|
63
|
-
"description": "Agentic QE (external: maintained in proffesor-for-testing/agentic-qe, pinned to v3.15.
|
|
63
|
+
"description": "Agentic QE (external: maintained in proffesor-for-testing/agentic-qe, pinned to v3.15.15): a quality-engineering fleet with 11 QE agents, 9 skills, 9 commands, the agentic-qe MCP server pinned to the plugin version, and the aqe-mod guard that refuses destructive operations on AQE learning data"
|
|
64
64
|
},
|
|
65
65
|
{
|
|
66
66
|
"name": "ruflo-docs",
|
package/README.md
CHANGED
|
@@ -244,7 +244,7 @@ This adds slash commands and agent definitions. `ruflo-core` (installed above) d
|
|
|
244
244
|
| [**ruflo-browser**](plugins/ruflo-browser/README.md) | Automate browser testing with Playwright |
|
|
245
245
|
| [**ruflo-jujutsu**](plugins/ruflo-jujutsu/README.md) | Analyze git diffs, score risk, suggest reviewers |
|
|
246
246
|
| [**ruflo-docs**](plugins/ruflo-docs/README.md) | Generate and maintain documentation automatically |
|
|
247
|
-
| [**agentic-qe-fleet**](https://github.com/proffesor-for-testing/agentic-qe/tree/v3.15.
|
|
247
|
+
| [**agentic-qe-fleet**](https://github.com/proffesor-for-testing/agentic-qe/tree/v3.15.15/plugins/agentic-qe-fleet) | External, maintained by [Agentic QE](https://github.com/proffesor-for-testing/agentic-qe): 11 QE agents, test generation, coverage, chaos and TDD skills, the `agentic-qe` MCP server, and a guard for AQE learning data (`/plugin install agentic-qe-fleet@ruflo`). Install it from one marketplace only, `agentic-qe` or `ruflo`: enabled from both, its mod and commands load twice |
|
|
248
248
|
|
|
249
249
|
#### Security & Compliance
|
|
250
250
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-flow",
|
|
3
|
-
"version": "3.63.
|
|
3
|
+
"version": "3.63.5",
|
|
4
4
|
"workspaces": [
|
|
5
5
|
"v3/@claude-flow/codex",
|
|
6
6
|
"v3/@claude-flow/mcp",
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
"@claude-flow/cli-core": "3.7.5",
|
|
73
73
|
"@claude-flow/codex": "3.0.5",
|
|
74
74
|
"@claude-flow/mcp": "3.1.4",
|
|
75
|
-
"@claude-flow/neural": "3.0.
|
|
75
|
+
"@claude-flow/neural": "3.0.1",
|
|
76
76
|
"@claude-flow/plugin-agent-federation": "1.0.3",
|
|
77
77
|
"@claude-flow/security": "3.0.5",
|
|
78
78
|
"@claude-flow/shared": "3.0.6",
|
|
@@ -2489,7 +2489,7 @@ const workerStatusCommand = {
|
|
|
2489
2489
|
});
|
|
2490
2490
|
if (result.summary) {
|
|
2491
2491
|
output.writeln();
|
|
2492
|
-
output.writeln(`Total: ${result.summary.total} | Running: ${output.highlight(String(result.summary.running))} | Completed: ${output.success(String(result.summary.completed))} | Failed: ${output.error(String(result.summary.failed))}`);
|
|
2492
|
+
output.writeln(`Total: ${result.summary.total} | Pending: ${String(result.summary.pending ?? 0)} | Running: ${output.highlight(String(result.summary.running))} | Completed: ${output.success(String(result.summary.completed))} | Failed: ${output.error(String(result.summary.failed))}`);
|
|
2493
2493
|
}
|
|
2494
2494
|
}
|
|
2495
2495
|
else {
|
|
@@ -152,7 +152,7 @@ async function getSystemStatus() {
|
|
|
152
152
|
backend: 'none',
|
|
153
153
|
performance: { searchTime: 0, cacheHitRate: 0 }
|
|
154
154
|
},
|
|
155
|
-
tasks: { total: 0, pending: 0, running: 0, completed: 0, failed: 0 },
|
|
155
|
+
tasks: { total: 0, pending: 0, running: 0, completed: 0, failed: 0, cancelled: 0 },
|
|
156
156
|
performance: {
|
|
157
157
|
cpuUsage: 0,
|
|
158
158
|
memoryUsage: 0,
|
|
@@ -217,6 +217,7 @@ function displayStatus(status) {
|
|
|
217
217
|
{ status: 'Running', count: status.tasks.running },
|
|
218
218
|
{ status: 'Completed', count: status.tasks.completed },
|
|
219
219
|
{ status: 'Failed', count: status.tasks.failed },
|
|
220
|
+
{ status: 'Cancelled', count: status.tasks.cancelled },
|
|
220
221
|
{ status: output.bold('Total'), count: status.tasks.total }
|
|
221
222
|
]
|
|
222
223
|
});
|
|
@@ -4817,6 +4817,7 @@ export const hooksWorkerStatus = {
|
|
|
4817
4817
|
workers,
|
|
4818
4818
|
summary: {
|
|
4819
4819
|
total: workers.length,
|
|
4820
|
+
pending: workers.filter(w => w.status === 'pending').length,
|
|
4820
4821
|
running: workers.filter(w => w.status === 'running').length,
|
|
4821
4822
|
completed: workers.filter(w => w.status === 'completed').length,
|
|
4822
4823
|
failed: workers.filter(w => w.status === 'failed').length,
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* - DEFAULT-REFUSES Authorization / Cookie / X-Auth-* (CLAUDE_FLOW_HTTP_FETCH_ALLOW_AUTH=1 opt-in)
|
|
16
16
|
* - hard timeout 30s default, 60s ceiling
|
|
17
17
|
* - response truncated to 256KB default, 1MB ceiling
|
|
18
|
-
* -
|
|
18
|
+
* - redirects followed manually, max 10, every hop re-validated by the same allowlist
|
|
19
19
|
*
|
|
20
20
|
* @module @claude-flow/cli/mcp-tools/http-fetch
|
|
21
21
|
*/
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* - DEFAULT-REFUSES Authorization / Cookie / X-Auth-* (CLAUDE_FLOW_HTTP_FETCH_ALLOW_AUTH=1 opt-in)
|
|
16
16
|
* - hard timeout 30s default, 60s ceiling
|
|
17
17
|
* - response truncated to 256KB default, 1MB ceiling
|
|
18
|
-
* -
|
|
18
|
+
* - redirects followed manually, max 10, every hop re-validated by the same allowlist
|
|
19
19
|
*
|
|
20
20
|
* @module @claude-flow/cli/mcp-tools/http-fetch
|
|
21
21
|
*/
|
|
@@ -23,6 +23,8 @@ const DEFAULT_TIMEOUT_MS = 30_000;
|
|
|
23
23
|
const MAX_TIMEOUT_MS = 60_000;
|
|
24
24
|
const DEFAULT_MAX_RESPONSE_BYTES = 256 * 1024; // 256 KB
|
|
25
25
|
const MAX_RESPONSE_BYTES = 1024 * 1024; // 1 MB
|
|
26
|
+
const MAX_REDIRECTS = 10;
|
|
27
|
+
const REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);
|
|
26
28
|
const DEFAULT_USER_AGENT = 'ruflo-http-fetch/1.0';
|
|
27
29
|
const FORBIDDEN_HEADER_PREFIXES = ['x-auth-', 'x-api-key'];
|
|
28
30
|
const FORBIDDEN_HEADERS_EXACT = ['authorization', 'cookie', 'set-cookie', 'proxy-authorization'];
|
|
@@ -230,13 +232,97 @@ export async function httpFetchExecute(input) {
|
|
|
230
232
|
const controller = new AbortController();
|
|
231
233
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
232
234
|
try {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
235
|
+
let currentUrl = parsed;
|
|
236
|
+
let currentMethod = method;
|
|
237
|
+
let currentBody = body;
|
|
238
|
+
let response;
|
|
239
|
+
let redirects = 0;
|
|
240
|
+
// Do not let fetch follow redirects on its own: only the initial URL was
|
|
241
|
+
// validated, so an automatic follow could reach loopback/RFC1918 or switch
|
|
242
|
+
// protocols after passing the allowlist. Every hop is validated with the
|
|
243
|
+
// same rules; sensitive headers are dropped when the origin changes.
|
|
244
|
+
while (true) {
|
|
245
|
+
response = await fetch(currentUrl.toString(), {
|
|
246
|
+
method: currentMethod,
|
|
247
|
+
headers,
|
|
248
|
+
body: currentMethod === 'GET' || currentMethod === 'HEAD' ? undefined : currentBody,
|
|
249
|
+
signal: controller.signal,
|
|
250
|
+
redirect: 'manual',
|
|
251
|
+
});
|
|
252
|
+
if (!REDIRECT_STATUSES.has(response.status))
|
|
253
|
+
break;
|
|
254
|
+
const location = response.headers.get('location');
|
|
255
|
+
if (!location)
|
|
256
|
+
break;
|
|
257
|
+
if (redirects++ >= MAX_REDIRECTS) {
|
|
258
|
+
try {
|
|
259
|
+
await response.body?.cancel();
|
|
260
|
+
}
|
|
261
|
+
catch { /* ignore */ }
|
|
262
|
+
return {
|
|
263
|
+
success: false,
|
|
264
|
+
status: response.status,
|
|
265
|
+
statusText: response.statusText,
|
|
266
|
+
headers: {},
|
|
267
|
+
body: '',
|
|
268
|
+
bodyTruncated: false,
|
|
269
|
+
bytesRead: 0,
|
|
270
|
+
durationMs: Date.now() - startedAt,
|
|
271
|
+
url: currentUrl.toString(),
|
|
272
|
+
method,
|
|
273
|
+
error: `too many redirects (max ${MAX_REDIRECTS})`,
|
|
274
|
+
errorCode: 'REDIRECT_LIMIT',
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
let nextUrl;
|
|
278
|
+
try {
|
|
279
|
+
nextUrl = validateUrl(new URL(location, currentUrl).toString());
|
|
280
|
+
}
|
|
281
|
+
catch (e) {
|
|
282
|
+
try {
|
|
283
|
+
await response.body?.cancel();
|
|
284
|
+
}
|
|
285
|
+
catch { /* ignore */ }
|
|
286
|
+
const err = e;
|
|
287
|
+
return {
|
|
288
|
+
success: false,
|
|
289
|
+
status: response.status,
|
|
290
|
+
statusText: response.statusText,
|
|
291
|
+
headers: {},
|
|
292
|
+
body: '',
|
|
293
|
+
bodyTruncated: false,
|
|
294
|
+
bytesRead: 0,
|
|
295
|
+
durationMs: Date.now() - startedAt,
|
|
296
|
+
url: currentUrl.toString(),
|
|
297
|
+
method,
|
|
298
|
+
error: `redirect blocked: ${err.message}`,
|
|
299
|
+
errorCode: err.code ?? 'REDIRECT_BLOCKED',
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
// The redirect response body is not returned; release the connection
|
|
303
|
+
// before issuing the next request. A body-less 3xx is also fine.
|
|
304
|
+
try {
|
|
305
|
+
await response.body?.cancel();
|
|
306
|
+
}
|
|
307
|
+
catch { /* ignore */ }
|
|
308
|
+
if (nextUrl.origin !== currentUrl.origin) {
|
|
309
|
+
for (const key of Object.keys(headers)) {
|
|
310
|
+
const lower = key.toLowerCase();
|
|
311
|
+
if (lower === 'authorization' || lower === 'cookie' || lower === 'proxy-authorization') {
|
|
312
|
+
delete headers[key];
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
// Match fetch's redirect method rules: 303 becomes GET except for
|
|
317
|
+
// GET/HEAD, while 301/302 only rewrite POST to GET. 307/308 preserve
|
|
318
|
+
// both method and body.
|
|
319
|
+
if ((response.status === 303 && currentMethod !== 'GET' && currentMethod !== 'HEAD') ||
|
|
320
|
+
((response.status === 301 || response.status === 302) && currentMethod === 'POST')) {
|
|
321
|
+
currentMethod = 'GET';
|
|
322
|
+
currentBody = undefined;
|
|
323
|
+
}
|
|
324
|
+
currentUrl = nextUrl;
|
|
325
|
+
}
|
|
240
326
|
// Drain the body with byte budget. Use array-buffer first to bound size
|
|
241
327
|
// deterministically before decoding to UTF-8.
|
|
242
328
|
const reader = response.body?.getReader();
|
|
@@ -286,7 +372,7 @@ export async function httpFetchExecute(input) {
|
|
|
286
372
|
bodyTruncated,
|
|
287
373
|
bytesRead,
|
|
288
374
|
durationMs: Date.now() - startedAt,
|
|
289
|
-
url:
|
|
375
|
+
url: currentUrl.toString(),
|
|
290
376
|
method,
|
|
291
377
|
};
|
|
292
378
|
}
|
|
@@ -633,6 +633,7 @@ export const systemTools = [
|
|
|
633
633
|
running: tasks.filter(t => t.status === 'in_progress').length,
|
|
634
634
|
completed: tasks.filter(t => t.status === 'completed').length,
|
|
635
635
|
failed: tasks.filter(t => t.status === 'failed').length,
|
|
636
|
+
cancelled: tasks.filter(t => t.status === 'cancelled').length,
|
|
636
637
|
};
|
|
637
638
|
},
|
|
638
639
|
},
|
|
@@ -208,7 +208,9 @@ export const taskTools = [
|
|
|
208
208
|
// Apply filters
|
|
209
209
|
if (input.status) {
|
|
210
210
|
// Support comma-separated status values. 'all' (sent by `status tasks`) means no status filter.
|
|
211
|
-
|
|
211
|
+
// Each value goes through statusOf, the reader task_update uses, so "done", "canceled",
|
|
212
|
+
// "running" or "Completed" match the stored status rather than matching nothing.
|
|
213
|
+
const statuses = input.status.split(',').map(s => s.trim()).map(s => statusOf(s) ?? s);
|
|
212
214
|
if (!statuses.includes('all')) {
|
|
213
215
|
tasks = tasks.filter(t => statuses.includes(t.status));
|
|
214
216
|
}
|
|
@@ -20,7 +20,9 @@ function parseCondition(when) {
|
|
|
20
20
|
if (expression === 'true' || expression === 'false') {
|
|
21
21
|
return { constant: expression === 'true' };
|
|
22
22
|
}
|
|
23
|
-
|
|
23
|
+
// Variable names follow the `{{…}}` interpolation syntax, so a condition can
|
|
24
|
+
// read a task step's output, stored as `<stepId>.output` (`step-1.output`).
|
|
25
|
+
const match = expression.match(/^([a-zA-Z_][\w.-]*)\s*===?\s*(?:'([^']*)'|"([^"]*)"|(-?\d+(?:\.\d+)?)|(true|false))$/);
|
|
24
26
|
if (!match) {
|
|
25
27
|
return new Error('condition config.when must compare a variable to a quoted string, number, or boolean using == or ===');
|
|
26
28
|
}
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
* (a 2.1.107 npm-global beside a current native one, say) silently decides.
|
|
8
8
|
*/
|
|
9
9
|
import { execFileSync } from 'node:child_process';
|
|
10
|
-
import { existsSync, realpathSync, statSync } from 'node:fs';
|
|
11
|
-
import { delimiter, join } from 'node:path';
|
|
10
|
+
import { closeSync, existsSync, fstatSync, openSync, readSync, realpathSync, statSync } from 'node:fs';
|
|
11
|
+
import { delimiter, dirname, join, resolve } from 'node:path';
|
|
12
12
|
export const MODS_DEFAULT_ON = '2.1.287';
|
|
13
13
|
export const MODS_FIRST = '2.1.277';
|
|
14
14
|
const parse = (v) => v.split('.').map((n) => Number(n));
|
|
@@ -52,6 +52,98 @@ export const defaultVersionOf = (path) => {
|
|
|
52
52
|
return null;
|
|
53
53
|
}
|
|
54
54
|
};
|
|
55
|
+
/** Known npm/cmd-shim Node launcher forms (legacy basedir and basedir_win).
|
|
56
|
+
* Match the whole template, not comments or arbitrary shell code. Unknown npm
|
|
57
|
+
* versions/formats simply keep the independent probe result. Never execute it.
|
|
58
|
+
*/
|
|
59
|
+
const npmCmdShim = (entry, modern) => [
|
|
60
|
+
'@ECHO off', 'GOTO start', ':find_dp0', 'SET dp0=%~dp0', 'EXIT /b', ':start',
|
|
61
|
+
'SETLOCAL', 'CALL :find_dp0', '', 'IF EXIST "%dp0%\\node.exe" (',
|
|
62
|
+
' SET "_prog=%dp0%\\node.exe"', ') ELSE (', ' SET "_prog=node"',
|
|
63
|
+
...(!modern ? [' SET PATHEXT=%PATHEXT:;.JS;=;%'] : []), ')', '',
|
|
64
|
+
'endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & '
|
|
65
|
+
+ (modern ? 'set PATHEXT=%PATHEXT:;.JS;=;% & ' : '')
|
|
66
|
+
+ `"%_prog%" "%dp0%\\${entry.replaceAll('/', '\\')}" %*`, '',
|
|
67
|
+
].join('\r\n');
|
|
68
|
+
const npmPosixShim = (entry, modern) => [
|
|
69
|
+
'#!/bin/sh', 'basedir=$(dirname "$(echo "$0" | sed -e \'s,\\\\,/,g\')")',
|
|
70
|
+
...(modern ? ['basedir_win="$basedir"'] : []), '',
|
|
71
|
+
modern ? 'case `uname -a` in' : 'case `uname` in',
|
|
72
|
+
...(modern ? [
|
|
73
|
+
' *CYGWIN*|*MINGW*|*MSYS*)', ' if command -v cygpath > /dev/null 2>&1; then',
|
|
74
|
+
' basedir_win=`cygpath -w "$basedir"`', ' fi', ' ;;', ' *WSL2*)',
|
|
75
|
+
' if command -v wslpath > /dev/null 2>&1; then',
|
|
76
|
+
' basedir_win="$(wslpath -w "$basedir" 2> /dev/null)"',
|
|
77
|
+
' if [ $? -ne 0 ] || [ -z "$basedir_win" ]; then',
|
|
78
|
+
' echo "Error: wslpath failed to convert path. WSL environment may be misconfigured." >&2',
|
|
79
|
+
' exit 1', ' fi', ' fi', ' ;;',
|
|
80
|
+
] : [
|
|
81
|
+
' *CYGWIN*|*MINGW*|*MSYS*)', ' if command -v cygpath > /dev/null 2>&1; then',
|
|
82
|
+
' basedir=`cygpath -w "$basedir"`', ' fi', ' ;;',
|
|
83
|
+
]), 'esac', '',
|
|
84
|
+
...(modern ? [
|
|
85
|
+
'PROG_EXE="$basedir/node.exe"', 'if ! [ -x "$PROG_EXE" ]; then',
|
|
86
|
+
' PROG_EXE="$basedir/node"', ' if ! [ -x "$PROG_EXE" ]; then',
|
|
87
|
+
' PROG_EXE=node', ' if ! [ -x "$PROG_EXE" ]; then', ' PROG_EXE=node.exe',
|
|
88
|
+
' fi', ' fi', 'fi', '', `exec "$PROG_EXE" "$basedir_win/${entry}" "$@"`,
|
|
89
|
+
] : [
|
|
90
|
+
'if [ -x "$basedir/node" ]; then', ` exec "$basedir/node" "$basedir/${entry}" "$@"`,
|
|
91
|
+
'else ', ` exec node "$basedir/${entry}" "$@"`, 'fi',
|
|
92
|
+
]), '',
|
|
93
|
+
].join('\n');
|
|
94
|
+
// A no-shebang/native bin uses npm's direct-exec forms, not a Node wrapper.
|
|
95
|
+
const npmDirectCmdShim = (entry) => [
|
|
96
|
+
'@ECHO off', 'GOTO start', ':find_dp0', 'SET dp0=%~dp0', 'EXIT /b', ':start',
|
|
97
|
+
'SETLOCAL', 'CALL :find_dp0', `"%dp0%\\${entry.replaceAll('/', '\\')}" %*`, '',
|
|
98
|
+
].join('\r\n');
|
|
99
|
+
const npmDirectPosixShim = (entry, modern) => {
|
|
100
|
+
const nodeShim = npmPosixShim(entry, modern);
|
|
101
|
+
const preamble = nodeShim.slice(0, nodeShim.indexOf(modern ? 'PROG_EXE=' : 'if [ -x'));
|
|
102
|
+
return preamble + `exec "$basedir/${entry}" "$@"\n`;
|
|
103
|
+
};
|
|
104
|
+
function npmShimTarget(path, cmd) {
|
|
105
|
+
let fd;
|
|
106
|
+
try {
|
|
107
|
+
fd = openSync(path, 'r');
|
|
108
|
+
const stat = fstatSync(fd);
|
|
109
|
+
if (!stat.isFile() || stat.size > 64 * 1024)
|
|
110
|
+
return null;
|
|
111
|
+
// Also cap the actual read if a file grows after stat.
|
|
112
|
+
const bytes = Buffer.alloc(64 * 1024 + 1);
|
|
113
|
+
let size = 0;
|
|
114
|
+
while (size < bytes.length) {
|
|
115
|
+
const n = readSync(fd, bytes, size, bytes.length - size, null);
|
|
116
|
+
if (!n)
|
|
117
|
+
break;
|
|
118
|
+
size += n;
|
|
119
|
+
}
|
|
120
|
+
if (size > 64 * 1024)
|
|
121
|
+
return null;
|
|
122
|
+
const text = bytes.subarray(0, size).toString('utf8').replace(/\r\n/g, '\n').trimEnd();
|
|
123
|
+
const targets = cmd
|
|
124
|
+
? [...text.matchAll(/"%dp0%\\([^"\r\n]+)" +%\*$/gm)].map(m => m[1].replaceAll('\\', '/'))
|
|
125
|
+
: [...text.matchAll(/^\s*exec (?:[^\r\n]* )?"\$basedir(?:_win)?\/([^"\r\n]+)" +"\$@"$/gm)].map(m => m[1]);
|
|
126
|
+
const entry = targets[0];
|
|
127
|
+
if (!entry || targets.some(t => t !== entry) || !/^[a-zA-Z0-9@._ /-]+$/.test(entry) || entry.startsWith('/'))
|
|
128
|
+
return null;
|
|
129
|
+
const template = cmd ? npmCmdShim : npmPosixShim;
|
|
130
|
+
const normalize = (value) => value.replace(/\r\n/g, '\n').trimEnd();
|
|
131
|
+
const node = [false, true].some(modern => normalize(template(entry, modern)) === text);
|
|
132
|
+
const direct = cmd ? normalize(npmDirectCmdShim(entry)) === text
|
|
133
|
+
: [false, true].some(modern => normalize(npmDirectPosixShim(entry, modern)) === text);
|
|
134
|
+
if (!node && !direct)
|
|
135
|
+
return null;
|
|
136
|
+
const target = realpathSync(resolve(dirname(path), entry));
|
|
137
|
+
return statSync(target).isFile() ? { path: target, mode: node ? 'node' : 'direct' } : null;
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
finally {
|
|
143
|
+
if (fd !== undefined)
|
|
144
|
+
closeSync(fd);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
55
147
|
/** Every `claude` on PATH, in PATH order (the first is what runs), plus ~/.local/bin. */
|
|
56
148
|
export function findClaudeInstalls(env, home, versionOf = defaultVersionOf) {
|
|
57
149
|
const names = process.platform === 'win32' ? ['claude.exe', 'claude.cmd', 'claude'] : ['claude'];
|
|
@@ -59,6 +151,7 @@ export function findClaudeInstalls(env, home, versionOf = defaultVersionOf) {
|
|
|
59
151
|
const seen = new Set();
|
|
60
152
|
const installs = [];
|
|
61
153
|
for (const dir of dirs) {
|
|
154
|
+
let cmd;
|
|
62
155
|
for (const name of names) {
|
|
63
156
|
const path = join(dir, name);
|
|
64
157
|
let real;
|
|
@@ -73,7 +166,19 @@ export function findClaudeInstalls(env, home, versionOf = defaultVersionOf) {
|
|
|
73
166
|
if (seen.has(real))
|
|
74
167
|
continue;
|
|
75
168
|
seen.add(real);
|
|
76
|
-
|
|
169
|
+
let version = versionOf(path);
|
|
170
|
+
if (process.platform === 'win32' && name === 'claude' && version === null && cmd?.version) {
|
|
171
|
+
const target = npmShimTarget(path, false);
|
|
172
|
+
const cmdTarget = npmShimTarget(cmd.path, true);
|
|
173
|
+
if (target && cmdTarget && target.path === cmdTarget.path && target.mode === cmdTarget.mode)
|
|
174
|
+
version = cmd.version;
|
|
175
|
+
}
|
|
176
|
+
const install = { path, version };
|
|
177
|
+
installs.push(install);
|
|
178
|
+
// Only a .cmd actually probed in this directory can supply a version.
|
|
179
|
+
// A realpath-deduplicated alias in another directory proves nothing here.
|
|
180
|
+
if (name === 'claude.cmd')
|
|
181
|
+
cmd = install;
|
|
77
182
|
}
|
|
78
183
|
}
|
|
79
184
|
return installs;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claude-flow/cli",
|
|
3
|
-
"version": "3.63.
|
|
3
|
+
"version": "3.63.5",
|
|
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",
|
|
@@ -103,7 +103,7 @@
|
|
|
103
103
|
"@claude-flow/cli-core": "3.7.5",
|
|
104
104
|
"@claude-flow/codex": "3.0.5",
|
|
105
105
|
"@claude-flow/mcp": "3.1.4",
|
|
106
|
-
"@claude-flow/neural": "3.0.
|
|
106
|
+
"@claude-flow/neural": "3.0.1",
|
|
107
107
|
"@claude-flow/plugin-agent-federation": "1.0.3",
|
|
108
108
|
"@claude-flow/security": "3.0.5",
|
|
109
109
|
"@claude-flow/shared": "3.0.6",
|