claude-flow 3.32.1 → 3.32.8
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/statusline.cjs +24 -17
- package/.claude-plugin/scripts/ruflo-hook.cjs +2 -2
- package/.claude-plugin/scripts/ruflo-hook.sh +17 -2
- package/package.json +1 -1
- package/v3/@claude-flow/cli/catalog-manifest.json +2 -2
- package/v3/@claude-flow/cli/dist/src/auth/client.d.ts +89 -0
- package/v3/@claude-flow/cli/dist/src/auth/client.js +242 -0
- package/v3/@claude-flow/cli/dist/src/auth/constants.d.ts +7 -0
- package/v3/@claude-flow/cli/dist/src/auth/constants.js +7 -0
- package/v3/@claude-flow/cli/dist/src/auth/scopes.d.ts +14 -0
- package/v3/@claude-flow/cli/dist/src/auth/scopes.js +21 -0
- package/v3/@claude-flow/cli/dist/src/auth/security-bridge.d.ts +36 -0
- package/v3/@claude-flow/cli/dist/src/auth/security-bridge.js +42 -0
- package/v3/@claude-flow/cli/dist/src/auth/session.d.ts +20 -0
- package/v3/@claude-flow/cli/dist/src/auth/session.js +32 -0
- package/v3/@claude-flow/cli/dist/src/auth/state.d.ts +19 -0
- package/v3/@claude-flow/cli/dist/src/auth/state.js +53 -0
- package/v3/@claude-flow/cli/dist/src/auth/types.d.ts +27 -0
- package/v3/@claude-flow/cli/dist/src/auth/types.js +11 -0
- package/v3/@claude-flow/cli/dist/src/commands/auth.d.ts +15 -0
- package/v3/@claude-flow/cli/dist/src/commands/auth.js +244 -0
- package/v3/@claude-flow/cli/dist/src/commands/doctor.js +211 -4
- package/v3/@claude-flow/cli/dist/src/commands/hooks.js +20 -9
- package/v3/@claude-flow/cli/dist/src/commands/index.js +2 -0
- package/v3/@claude-flow/cli/dist/src/commands/proxy-lifecycle.d.ts +20 -0
- package/v3/@claude-flow/cli/dist/src/commands/proxy-lifecycle.js +277 -0
- package/v3/@claude-flow/cli/dist/src/commands/proxy.js +97 -4
- package/v3/@claude-flow/cli/dist/src/commands/security.js +16 -11
- package/v3/@claude-flow/cli/dist/src/funnel/insights.d.ts +1 -0
- package/v3/@claude-flow/cli/dist/src/funnel/insights.js +4 -4
- package/v3/@claude-flow/cli/dist/src/funnel/local-signals.d.ts +6 -1
- package/v3/@claude-flow/cli/dist/src/funnel/local-signals.js +29 -20
- package/v3/@claude-flow/cli/dist/src/init/executor.js +2 -2
- package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.js +13 -1
- package/v3/@claude-flow/cli/dist/src/mcp-tools/memory-tools.js +11 -3
- package/v3/@claude-flow/cli/dist/src/proxy/install.d.ts +29 -0
- package/v3/@claude-flow/cli/dist/src/proxy/install.js +135 -0
- package/v3/@claude-flow/cli/dist/src/proxy/lifecycle.d.ts +61 -0
- package/v3/@claude-flow/cli/dist/src/proxy/lifecycle.js +249 -0
- package/v3/@claude-flow/cli/dist/src/proxy/paths.d.ts +34 -0
- package/v3/@claude-flow/cli/dist/src/proxy/paths.js +70 -0
- package/v3/@claude-flow/cli/dist/src/proxy/release.d.ts +47 -0
- package/v3/@claude-flow/cli/dist/src/proxy/release.js +138 -0
- package/v3/@claude-flow/cli/dist/src/proxy/token-bridge.d.ts +5 -0
- package/v3/@claude-flow/cli/dist/src/proxy/token-bridge.js +61 -0
- package/v3/@claude-flow/cli/dist/src/proxy/verify.d.ts +44 -0
- package/v3/@claude-flow/cli/dist/src/proxy/verify.js +68 -0
- package/v3/@claude-flow/cli/dist/src/security/builtin-aidefence.d.ts +34 -0
- package/v3/@claude-flow/cli/dist/src/security/builtin-aidefence.js +86 -0
- package/v3/@claude-flow/cli/dist/src/services/fable-harness.d.ts +1 -0
- package/v3/@claude-flow/cli/package.json +2 -2
|
@@ -23,6 +23,8 @@ import { clearRateLimitStatus, readRateLimitStatus } from '../funnel/rate-limit-
|
|
|
23
23
|
import { clearQuotaLowStatus, readQuotaLowStatus } from '../funnel/power-saver-notifier.js';
|
|
24
24
|
import { getInstalledCliVersion } from '../init/helper-refresh.js';
|
|
25
25
|
import * as path from 'path';
|
|
26
|
+
import { proxyLifecycleSubcommands, printProxyConsoleGuidance } from './proxy-lifecycle.js';
|
|
27
|
+
import { getProxyStatus } from '../proxy/lifecycle.js';
|
|
26
28
|
const PROXY_CONFIG_FILE = 'proxy-config.toml';
|
|
27
29
|
/**
|
|
28
30
|
* Minimal hand-rolled TOML writer — this config has exactly one boolean
|
|
@@ -40,12 +42,12 @@ function readProxyConfigRaw() {
|
|
|
40
42
|
return '';
|
|
41
43
|
}
|
|
42
44
|
}
|
|
43
|
-
function
|
|
45
|
+
function writeConfigLine(field, rawValue) {
|
|
44
46
|
const dir = funnelStateDir();
|
|
45
47
|
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
46
48
|
const target = path.join(dir, PROXY_CONFIG_FILE);
|
|
47
49
|
const raw = readProxyConfigRaw();
|
|
48
|
-
const line = `${field} = ${
|
|
50
|
+
const line = `${field} = ${rawValue}`;
|
|
49
51
|
const pattern = new RegExp(`^${field}\\s*=.*$`, 'm');
|
|
50
52
|
let next;
|
|
51
53
|
if (pattern.test(raw)) {
|
|
@@ -58,6 +60,9 @@ function writeConsentMirrorLine(field, value) {
|
|
|
58
60
|
fs.writeFileSync(tmp, next, { encoding: 'utf-8', mode: 0o600 });
|
|
59
61
|
fs.renameSync(tmp, target);
|
|
60
62
|
}
|
|
63
|
+
function writeConsentMirrorLine(field, value) {
|
|
64
|
+
writeConfigLine(field, String(value));
|
|
65
|
+
}
|
|
61
66
|
function writeSponsoredConsentMirror(granted) {
|
|
62
67
|
writeConsentMirrorLine('sponsored_consent_granted', granted);
|
|
63
68
|
}
|
|
@@ -67,6 +72,78 @@ function writePowerSaverConsentMirror(granted) {
|
|
|
67
72
|
function writeTrainingShareConsentMirror(granted) {
|
|
68
73
|
writeConsentMirrorLine('training_share_consent_granted', granted);
|
|
69
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* `default_data_plane` — the ADR-304 cloud-routing toggle. Values confirmed
|
|
77
|
+
* against meta-proxy's actual `DataPlane` enum (`src/config.rs`,
|
|
78
|
+
* `#[serde(rename_all = "snake_case")]`): "local" | "cloud" | "sponsored" |
|
|
79
|
+
* "passthrough" (the last two are not written by this command — sponsored
|
|
80
|
+
* is ADR-313's own consent flag, passthrough is the proxy's own default).
|
|
81
|
+
*/
|
|
82
|
+
function readDataPlane() {
|
|
83
|
+
const raw = readProxyConfigRaw();
|
|
84
|
+
const match = raw.match(/^default_data_plane\s*=\s*"([^"]*)"/m);
|
|
85
|
+
return match ? match[1] : 'passthrough'; // matches the Rust struct's own default
|
|
86
|
+
}
|
|
87
|
+
function writeDataPlane(plane) {
|
|
88
|
+
writeConfigLine('default_data_plane', `"${plane}"`);
|
|
89
|
+
}
|
|
90
|
+
const CLOUD_ROUTING_DISCLOSURE = [
|
|
91
|
+
'Enabling cloud routing.',
|
|
92
|
+
'',
|
|
93
|
+
'With cloud routing ON, prompts for cloud-tier requests are sent to',
|
|
94
|
+
'api.cognitum.one and forwarded to the selected provider',
|
|
95
|
+
'(Claude / GPT / Gemini / DeepSeek / OpenRouter).',
|
|
96
|
+
'',
|
|
97
|
+
'Requests routed to local backends never leave this machine.',
|
|
98
|
+
'',
|
|
99
|
+
'Disable anytime: ruflo proxy config --local-only',
|
|
100
|
+
].join('\n');
|
|
101
|
+
const configSub = {
|
|
102
|
+
name: 'config',
|
|
103
|
+
description: 'Toggle cloud routing (ADR-304) — local backends only by default',
|
|
104
|
+
options: [
|
|
105
|
+
{ name: 'cloud', description: 'Enable cloud routing (requires cloud-routing consent)', type: 'boolean', default: false },
|
|
106
|
+
{ name: 'local-only', description: 'Disable cloud routing, revert to local-only routing', type: 'boolean', default: false },
|
|
107
|
+
{ name: 'yes', description: 'Skip the confirmation prompt', type: 'boolean', default: false },
|
|
108
|
+
],
|
|
109
|
+
action: async (ctx) => {
|
|
110
|
+
const wantCloud = Boolean(ctx.flags.cloud);
|
|
111
|
+
const wantLocalOnly = Boolean(ctx.flags.localOnly ?? ctx.flags['local-only']);
|
|
112
|
+
if (wantCloud && wantLocalOnly) {
|
|
113
|
+
output.printError('Pass either --cloud or --local-only, not both.');
|
|
114
|
+
return { success: false, exitCode: 1 };
|
|
115
|
+
}
|
|
116
|
+
if (!wantCloud && !wantLocalOnly) {
|
|
117
|
+
const plane = readDataPlane();
|
|
118
|
+
output.writeln(`Current data plane: ${plane}`);
|
|
119
|
+
output.writeln(plane === 'cloud'
|
|
120
|
+
? 'Cloud routing is ON — cloud-tier requests go to api.cognitum.one.'
|
|
121
|
+
: 'Cloud routing is OFF — requests never leave this machine (or use your own Claude subscription on Passthrough).');
|
|
122
|
+
return { success: true, data: { plane } };
|
|
123
|
+
}
|
|
124
|
+
if (wantLocalOnly) {
|
|
125
|
+
writeDataPlane('local');
|
|
126
|
+
revokeConsent('cloud-routing', 'proxy-config-local-only');
|
|
127
|
+
output.printSuccess('Cloud routing disabled — reverted to local-only routing.');
|
|
128
|
+
return { success: true, data: { plane: 'local' } };
|
|
129
|
+
}
|
|
130
|
+
// wantCloud
|
|
131
|
+
if (!hasConsent('cloud-routing')) {
|
|
132
|
+
output.writeln(CLOUD_ROUTING_DISCLOSURE);
|
|
133
|
+
output.writeln('');
|
|
134
|
+
if (!ctx.flags.yes) {
|
|
135
|
+
output.writeln('Re-run with --yes to confirm: ruflo proxy config --cloud --yes');
|
|
136
|
+
return { success: true, data: { confirmed: false } };
|
|
137
|
+
}
|
|
138
|
+
recordConsent('cloud-routing', true, 'proxy-config-cloud');
|
|
139
|
+
}
|
|
140
|
+
writeDataPlane('cloud');
|
|
141
|
+
output.printSuccess('Cloud routing enabled.');
|
|
142
|
+
output.writeln(' Requests routed to local backends still never leave this machine.');
|
|
143
|
+
output.writeln(' Disable anytime: ruflo proxy config --local-only');
|
|
144
|
+
return { success: true, data: { plane: 'cloud' } };
|
|
145
|
+
},
|
|
146
|
+
};
|
|
70
147
|
const SPONSOR_DISCLOSURE = [
|
|
71
148
|
'Enabling sponsored downtime mode.',
|
|
72
149
|
'',
|
|
@@ -292,19 +369,35 @@ const trainingShareStatusSub = {
|
|
|
292
369
|
};
|
|
293
370
|
export const proxyCommand = {
|
|
294
371
|
name: 'proxy',
|
|
295
|
-
description: 'Meta LLM Proxy — sponsored downtime + power saver + training-data sharing (ADR-304/307/313/314/315)',
|
|
372
|
+
description: 'Meta LLM Proxy — install/lifecycle + sponsored downtime + power saver + training-data sharing (ADR-304/307/313/314/315)',
|
|
296
373
|
subcommands: [
|
|
374
|
+
...proxyLifecycleSubcommands,
|
|
375
|
+
configSub,
|
|
297
376
|
sponsorEnableSub, sponsorDisableSub, sponsorStatusSub, sponsorClearSub,
|
|
298
377
|
powerSaverEnableSub, powerSaverDisableSub, powerSaverStatusSub, powerSaverClearSub,
|
|
299
378
|
trainingShareEnableSub, trainingShareDisableSub, trainingShareStatusSub,
|
|
300
379
|
],
|
|
301
380
|
examples: [
|
|
381
|
+
{ command: 'ruflo proxy install --yes', description: 'Install the signed Meta-Proxy v0.4.0 binary' },
|
|
382
|
+
{ command: 'ruflo proxy start', description: 'Start meta-proxy in the foreground' },
|
|
383
|
+
{ command: 'ruflo proxy status', description: 'Show install + process status' },
|
|
384
|
+
{ command: 'ruflo proxy config --cloud --yes', description: 'Enable cloud routing (ADR-304)' },
|
|
385
|
+
{ command: 'ruflo proxy config --local-only', description: 'Revert to local-only routing' },
|
|
302
386
|
{ command: 'ruflo proxy sponsor-status', description: 'Show current sponsored-mode state' },
|
|
303
387
|
{ command: 'ruflo proxy sponsor-enable --yes', description: 'Opt into sponsored downtime capacity' },
|
|
304
388
|
{ command: 'ruflo proxy power-saver-enable --yes', description: 'Opt into power saver mode' },
|
|
305
389
|
{ command: 'ruflo proxy training-share-enable --yes', description: 'Opt into training-data sharing (ADR-315)' },
|
|
306
390
|
],
|
|
307
|
-
action:
|
|
391
|
+
action: async () => {
|
|
392
|
+
const status = getProxyStatus();
|
|
393
|
+
output.writeln('Meta Proxy');
|
|
394
|
+
output.writeln(` Installation: ${status.installed ? 'ready' : 'not installed'}`);
|
|
395
|
+
output.writeln(` Process: ${status.running ? `running (pid ${status.pid})` : 'not running'}`);
|
|
396
|
+
if (status.stalePidFile)
|
|
397
|
+
output.writeln(' (a stale PID file was found and will be cleared on next start)');
|
|
398
|
+
printProxyConsoleGuidance(status);
|
|
399
|
+
return { success: true, data: status };
|
|
400
|
+
},
|
|
308
401
|
};
|
|
309
402
|
export default proxyCommand;
|
|
310
403
|
//# sourceMappingURL=proxy.js.map
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { output } from '../output.js';
|
|
8
8
|
import { execSync } from 'node:child_process';
|
|
9
|
+
import { createBuiltinAIDefence } from '../security/builtin-aidefence.js';
|
|
9
10
|
// Scan subcommand
|
|
10
11
|
const scanCommand = {
|
|
11
12
|
name: 'scan',
|
|
@@ -861,16 +862,17 @@ const defendCommand = {
|
|
|
861
862
|
output.writeln(output.bold('🛡️ AIDefence - AI Manipulation Defense System'));
|
|
862
863
|
output.writeln(output.dim('─'.repeat(55)));
|
|
863
864
|
// Dynamic import of aidefence (allows package to be optional)
|
|
864
|
-
let
|
|
865
|
+
let defender;
|
|
865
866
|
try {
|
|
866
867
|
const aidefence = await import('@claude-flow/aidefence');
|
|
867
|
-
|
|
868
|
+
defender = aidefence.createAIDefence({ enableLearning });
|
|
868
869
|
}
|
|
869
870
|
catch {
|
|
870
|
-
|
|
871
|
-
|
|
871
|
+
// Keep cold npx startup lean (#2561): the full learning engine remains
|
|
872
|
+
// user-installable, while the CLI always ships a deterministic scanner.
|
|
873
|
+
defender = createBuiltinAIDefence();
|
|
874
|
+
output.writeln(output.dim('Using built-in defense engine (install @claude-flow/aidefence for adaptive learning)'));
|
|
872
875
|
}
|
|
873
|
-
const defender = createAIDefence({ enableLearning });
|
|
874
876
|
// Show stats mode
|
|
875
877
|
if (showStats) {
|
|
876
878
|
const stats = await defender.getStats();
|
|
@@ -893,8 +895,8 @@ const defendCommand = {
|
|
|
893
895
|
output.writeln(output.dim(`Reading file: ${filePath}`));
|
|
894
896
|
}
|
|
895
897
|
catch (err) {
|
|
896
|
-
output.
|
|
897
|
-
return { success: false, message: 'File not found' };
|
|
898
|
+
output.printError(`Failed to read file: ${filePath}`);
|
|
899
|
+
return { success: false, exitCode: 2, message: 'File not found' };
|
|
898
900
|
}
|
|
899
901
|
}
|
|
900
902
|
if (!textToScan) {
|
|
@@ -914,8 +916,9 @@ const defendCommand = {
|
|
|
914
916
|
spinner.start();
|
|
915
917
|
// Perform scan
|
|
916
918
|
const startTime = performance.now();
|
|
917
|
-
const
|
|
918
|
-
|
|
919
|
+
const quickResult = quickMode ? defender.quickScan(textToScan) : undefined;
|
|
920
|
+
const result = quickResult
|
|
921
|
+
? { ...quickResult, threats: [], piiFound: false, detectionTimeMs: 0, inputHash: '', safe: !quickResult.threat }
|
|
919
922
|
: await defender.detect(textToScan);
|
|
920
923
|
const scanTime = performance.now() - startTime;
|
|
921
924
|
spinner.stop();
|
|
@@ -927,7 +930,8 @@ const defendCommand = {
|
|
|
927
930
|
piiFound: result.piiFound,
|
|
928
931
|
detectionTimeMs: scanTime,
|
|
929
932
|
}, null, 2));
|
|
930
|
-
|
|
933
|
+
const safe = result.safe && !result.piiFound;
|
|
934
|
+
return { success: safe, exitCode: safe ? 0 : 1 };
|
|
931
935
|
}
|
|
932
936
|
// Text output
|
|
933
937
|
output.writeln();
|
|
@@ -969,7 +973,8 @@ const defendCommand = {
|
|
|
969
973
|
}
|
|
970
974
|
}
|
|
971
975
|
output.writeln(output.dim(`Detection time: ${scanTime.toFixed(3)}ms`));
|
|
972
|
-
|
|
976
|
+
const safe = result.safe && !result.piiFound;
|
|
977
|
+
return { success: safe, exitCode: safe ? 0 : 1 };
|
|
973
978
|
},
|
|
974
979
|
};
|
|
975
980
|
// Main security command
|
|
@@ -42,11 +42,11 @@ function securityInsight(ctx) {
|
|
|
42
42
|
const s = ctx.security;
|
|
43
43
|
if (!s)
|
|
44
44
|
return null;
|
|
45
|
-
const
|
|
46
|
-
if (
|
|
45
|
+
const findings = Math.max(0, s.findings ?? 0);
|
|
46
|
+
if (s.status === 'ISSUES' || findings > 0) {
|
|
47
47
|
return {
|
|
48
|
-
id: 'insight-
|
|
49
|
-
text: `⚠ ${
|
|
48
|
+
id: 'insight-security-findings',
|
|
49
|
+
text: `⚠ ${findings} security finding${findings === 1 ? '' : 's'} — Review the latest ruflo security scan`,
|
|
50
50
|
priority: 90,
|
|
51
51
|
};
|
|
52
52
|
}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
export interface SecurityStatus {
|
|
2
|
-
status: 'CLEAN' | '
|
|
2
|
+
status: 'CLEAN' | 'ISSUES' | 'PENDING';
|
|
3
|
+
/** Generic code-pattern findings from `ruflo security scan` (not CVEs). */
|
|
4
|
+
findings: number;
|
|
5
|
+
scannedAt?: string;
|
|
6
|
+
/** @deprecated Retained as a zero-valued compatibility field. */
|
|
3
7
|
cvesFixed: number;
|
|
8
|
+
/** @deprecated Retained as a zero-valued compatibility field. */
|
|
4
9
|
totalCves: number;
|
|
5
10
|
}
|
|
6
11
|
export declare function getSecurityStatus(cwd?: string): SecurityStatus;
|
|
@@ -11,30 +11,39 @@ import * as fs from 'fs';
|
|
|
11
11
|
import * as path from 'path';
|
|
12
12
|
import { execSync } from 'child_process';
|
|
13
13
|
export function getSecurityStatus(cwd = process.cwd()) {
|
|
14
|
+
const empty = {
|
|
15
|
+
status: 'PENDING', findings: 0, cvesFixed: 0, totalCves: 0,
|
|
16
|
+
};
|
|
14
17
|
const scanResultsPath = path.join(cwd, '.claude', 'security-scans');
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
if (!fs.existsSync(scanResultsPath))
|
|
19
|
+
return empty;
|
|
20
|
+
try {
|
|
21
|
+
const files = fs.readdirSync(scanResultsPath).filter((f) => f.endsWith('.json'));
|
|
22
|
+
if (files.length === 0)
|
|
23
|
+
return empty;
|
|
24
|
+
let newest = files[0];
|
|
25
|
+
let newestMtime = -1;
|
|
26
|
+
for (const f of files) {
|
|
27
|
+
const st = fs.statSync(path.join(scanResultsPath, f));
|
|
28
|
+
if (st.mtimeMs > newestMtime) {
|
|
29
|
+
newestMtime = st.mtimeMs;
|
|
30
|
+
newest = f;
|
|
31
|
+
}
|
|
24
32
|
}
|
|
33
|
+
const scan = JSON.parse(fs.readFileSync(path.join(scanResultsPath, newest), 'utf-8'));
|
|
34
|
+
const rawFindings = scan.summary?.total ?? scan.totalFindings ?? scan.findings?.length ?? 0;
|
|
35
|
+
const findings = Math.max(0, Number.isFinite(rawFindings) ? rawFindings : 0);
|
|
36
|
+
return {
|
|
37
|
+
status: findings > 0 ? 'ISSUES' : 'CLEAN',
|
|
38
|
+
findings,
|
|
39
|
+
scannedAt: typeof scan.timestamp === 'string' ? scan.timestamp : undefined,
|
|
40
|
+
cvesFixed: 0,
|
|
41
|
+
totalCves: 0,
|
|
42
|
+
};
|
|
25
43
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
try {
|
|
29
|
-
const audits = fs.readdirSync(auditPath).filter((f) => f.includes('audit'));
|
|
30
|
-
cvesFixed = Math.min(totalCves, Math.max(cvesFixed, audits.length));
|
|
31
|
-
}
|
|
32
|
-
catch {
|
|
33
|
-
// Ignore
|
|
34
|
-
}
|
|
44
|
+
catch {
|
|
45
|
+
return empty;
|
|
35
46
|
}
|
|
36
|
-
const status = cvesFixed >= totalCves ? 'CLEAN' : cvesFixed > 0 ? 'IN_PROGRESS' : 'PENDING';
|
|
37
|
-
return { status, cvesFixed, totalCves };
|
|
38
47
|
}
|
|
39
48
|
export function getSwarmStatus() {
|
|
40
49
|
let activeAgents = 0;
|
|
@@ -619,7 +619,7 @@ export async function executeUpgrade(targetDir, upgradeSettings = false) {
|
|
|
619
619
|
initialized: new Date().toISOString(),
|
|
620
620
|
status: 'PENDING',
|
|
621
621
|
cvesFixed: 0,
|
|
622
|
-
totalCves:
|
|
622
|
+
totalCves: 0,
|
|
623
623
|
lastScan: null,
|
|
624
624
|
_note: 'Run: npx @claude-flow/cli@latest security scan'
|
|
625
625
|
};
|
|
@@ -1541,7 +1541,7 @@ async function writeInitialMetrics(targetDir, options, result) {
|
|
|
1541
1541
|
initialized: new Date().toISOString(),
|
|
1542
1542
|
status: 'PENDING',
|
|
1543
1543
|
cvesFixed: 0,
|
|
1544
|
-
totalCves:
|
|
1544
|
+
totalCves: 0,
|
|
1545
1545
|
lastScan: null,
|
|
1546
1546
|
_note: 'Run: npx @claude-flow/cli@latest security scan'
|
|
1547
1547
|
};
|
|
@@ -2010,8 +2010,9 @@ export const hooksSessionEnd = {
|
|
|
2010
2010
|
}
|
|
2011
2011
|
// Phase 5: Wire ReflexionMemory session end + NightlyLearner consolidation via bridge
|
|
2012
2012
|
let sessionPersistence = null;
|
|
2013
|
+
let bridge = null;
|
|
2013
2014
|
try {
|
|
2014
|
-
|
|
2015
|
+
bridge = await import('../memory/memory-bridge.js');
|
|
2015
2016
|
const result = await bridge.bridgeSessionEnd({
|
|
2016
2017
|
sessionId,
|
|
2017
2018
|
summary: saveState ? 'Session ended with state saved' : 'Session ended',
|
|
@@ -2028,6 +2029,17 @@ export const hooksSessionEnd = {
|
|
|
2028
2029
|
catch {
|
|
2029
2030
|
// Bridge not available
|
|
2030
2031
|
}
|
|
2032
|
+
finally {
|
|
2033
|
+
// Release AgentDB/ONNX resources after one-shot session persistence.
|
|
2034
|
+
// A partially initialized native pool can otherwise keep Node alive
|
|
2035
|
+
// after the command has completed all logical work (#2691).
|
|
2036
|
+
try {
|
|
2037
|
+
await bridge?.shutdownBridge();
|
|
2038
|
+
}
|
|
2039
|
+
catch {
|
|
2040
|
+
// Cleanup is best-effort and must not fail session-end.
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2031
2043
|
return {
|
|
2032
2044
|
sessionId,
|
|
2033
2045
|
duration: 3600000, // 1 hour in ms
|
|
@@ -389,7 +389,7 @@ export const memoryTools = [
|
|
|
389
389
|
type: 'object',
|
|
390
390
|
properties: {
|
|
391
391
|
query: { type: 'string', description: 'Search query (semantic similarity)' },
|
|
392
|
-
namespace: { type: 'string', description: 'Namespace to search (default:
|
|
392
|
+
namespace: { type: 'string', description: 'Namespace to search (default: all namespaces — omit to search across every namespace)' },
|
|
393
393
|
limit: { type: 'number', description: 'Maximum results (default: 10)' },
|
|
394
394
|
threshold: { type: 'number', description: 'Minimum similarity threshold 0-1 (default: 0.3)' },
|
|
395
395
|
smart: { type: 'boolean', description: 'Enable SmartRetrieval pipeline — query expansion, RRF fusion, recency boost, MMR diversity (default: false)' },
|
|
@@ -400,10 +400,18 @@ export const memoryTools = [
|
|
|
400
400
|
await ensureInitialized();
|
|
401
401
|
const { searchEntries } = await getMemoryFunctions();
|
|
402
402
|
const query = input.query;
|
|
403
|
-
|
|
403
|
+
// #2646 (3rd occurrence of #1123/#1131 shape): do NOT coerce an omitted
|
|
404
|
+
// namespace to the literal string 'default' here. Both searchEntries()
|
|
405
|
+
// and bridgeSearchEntries() already resolve an omitted/undefined
|
|
406
|
+
// namespace to 'all' (fan out across every namespace) — but 'default'
|
|
407
|
+
// is truthy, so passing it defeats that fallback and silently scopes
|
|
408
|
+
// the search to a namespace that is usually empty. Leave namespace
|
|
409
|
+
// undefined when not provided so the underlying `|| 'all'` fallback
|
|
410
|
+
// in the search layer actually fires.
|
|
411
|
+
const namespace = input.namespace;
|
|
404
412
|
const limit = input.limit ?? 10;
|
|
405
413
|
const threshold = input.threshold ?? 0.3;
|
|
406
|
-
validateMemoryInput(undefined, undefined, query);
|
|
414
|
+
validateMemoryInput(undefined, undefined, query, namespace);
|
|
407
415
|
const startTime = performance.now();
|
|
408
416
|
try {
|
|
409
417
|
// #1846: feature-detect smartSearch on the resolved memory package.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ruflo proxy install`/`update` orchestration (ADR-307): download -> verify
|
|
3
|
+
* -> extract -> place -> record. The per-user bearer token
|
|
4
|
+
* (`~/.ruflo/proxy-token`) is NOT generated here — confirmed empirically
|
|
5
|
+
* (2026-07-16) that the meta-proxy binary itself creates it on first launch
|
|
6
|
+
* (`load_or_create_token()`), so this module's job ends at a verified binary
|
|
7
|
+
* on disk plus an install manifest doctor can check against.
|
|
8
|
+
*
|
|
9
|
+
* @module proxy/install
|
|
10
|
+
*/
|
|
11
|
+
export declare class ExtractionError extends Error {
|
|
12
|
+
constructor(message: string);
|
|
13
|
+
}
|
|
14
|
+
export interface InstallOptions {
|
|
15
|
+
version: string;
|
|
16
|
+
log?: (line: string) => void;
|
|
17
|
+
}
|
|
18
|
+
export interface InstallResult {
|
|
19
|
+
version: string;
|
|
20
|
+
binaryPath: string;
|
|
21
|
+
sha256: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Full install pipeline. Refuses (throws) on any verification failure —
|
|
25
|
+
* never writes a partially-verified binary into the live install path.
|
|
26
|
+
*/
|
|
27
|
+
export declare function installProxy(opts: InstallOptions): Promise<InstallResult>;
|
|
28
|
+
export declare function uninstallProxy(): Promise<boolean>;
|
|
29
|
+
//# sourceMappingURL=install.d.ts.map
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ruflo proxy install`/`update` orchestration (ADR-307): download -> verify
|
|
3
|
+
* -> extract -> place -> record. The per-user bearer token
|
|
4
|
+
* (`~/.ruflo/proxy-token`) is NOT generated here — confirmed empirically
|
|
5
|
+
* (2026-07-16) that the meta-proxy binary itself creates it on first launch
|
|
6
|
+
* (`load_or_create_token()`), so this module's job ends at a verified binary
|
|
7
|
+
* on disk plus an install manifest doctor can check against.
|
|
8
|
+
*
|
|
9
|
+
* @module proxy/install
|
|
10
|
+
*/
|
|
11
|
+
import * as fs from 'node:fs';
|
|
12
|
+
import * as os from 'node:os';
|
|
13
|
+
import * as path from 'node:path';
|
|
14
|
+
import { fetchReleaseAssets, detectTargetTriple, releaseArchiveExtension, releaseAssetFilename } from './release.js';
|
|
15
|
+
import { verifyRelease, sha256Hex, PROXY_RELEASE_PUBKEY_PEM } from './verify.js';
|
|
16
|
+
import { proxyBinaryPath, proxyInstallManifestPath } from './paths.js';
|
|
17
|
+
export class ExtractionError extends Error {
|
|
18
|
+
constructor(message) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.name = 'ExtractionError';
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function binaryNameInArchive() {
|
|
24
|
+
return process.platform === 'win32' ? 'meta-proxy.exe' : 'meta-proxy';
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Extracts the archive via the OS's own tools — `tar` for `.tar.gz`
|
|
28
|
+
* (present on macOS/Linux/Windows 10+), PowerShell `Expand-Archive`
|
|
29
|
+
* specifically for `.zip` on Windows (not tar's bsdtar zip support — not
|
|
30
|
+
* reliable enough to lean on). Zero new archive-parsing dependency, matching
|
|
31
|
+
* this repo's existing taste for shelling out over adding a parser dep.
|
|
32
|
+
*/
|
|
33
|
+
async function extractArchive(archivePath, extractDir, ext) {
|
|
34
|
+
const { SafeExecutor } = await import('@claude-flow/security');
|
|
35
|
+
fs.mkdirSync(extractDir, { recursive: true });
|
|
36
|
+
if (ext === 'tar.gz') {
|
|
37
|
+
const exec = new SafeExecutor({ allowedCommands: ['tar'], timeout: 60_000 });
|
|
38
|
+
const result = await exec.execute('tar', ['xzf', archivePath, '-C', extractDir]);
|
|
39
|
+
if (result.exitCode !== 0) {
|
|
40
|
+
throw new ExtractionError(`tar extraction failed (exit ${result.exitCode}): ${result.stderr || result.stdout}`);
|
|
41
|
+
}
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
// .zip — PowerShell Expand-Archive, single-quoted literal paths (doubling
|
|
45
|
+
// any embedded single quote per PowerShell string-literal escaping) passed
|
|
46
|
+
// as ONE argv element to -Command. shell:false means no OS shell ever
|
|
47
|
+
// tokenizes this string — only powershell.exe's own parser does.
|
|
48
|
+
const escape = (p) => p.replace(/'/g, "''");
|
|
49
|
+
const command = `Expand-Archive -LiteralPath '${escape(archivePath)}' -DestinationPath '${escape(extractDir)}' -Force`;
|
|
50
|
+
const exec = new SafeExecutor({ allowedCommands: ['powershell', 'powershell.exe'], timeout: 60_000 });
|
|
51
|
+
const result = await exec.execute('powershell', ['-NoProfile', '-NonInteractive', '-Command', command]);
|
|
52
|
+
if (result.exitCode !== 0) {
|
|
53
|
+
throw new ExtractionError(`Expand-Archive failed (exit ${result.exitCode}): ${result.stderr || result.stdout}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Full install pipeline. Refuses (throws) on any verification failure —
|
|
58
|
+
* never writes a partially-verified binary into the live install path.
|
|
59
|
+
*/
|
|
60
|
+
export async function installProxy(opts) {
|
|
61
|
+
const log = opts.log ?? (() => { });
|
|
62
|
+
const triple = detectTargetTriple();
|
|
63
|
+
const archiveFilename = releaseAssetFilename(opts.version, triple);
|
|
64
|
+
const workDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ruflo-proxy-install-'));
|
|
65
|
+
try {
|
|
66
|
+
log(`Fetching meta-proxy ${opts.version} (${triple})...`);
|
|
67
|
+
const assets = await fetchReleaseAssets(opts.version, triple, workDir, log);
|
|
68
|
+
log('Verifying release signature and checksum...');
|
|
69
|
+
const { sha256 } = verifyRelease({
|
|
70
|
+
sumsBytes: assets.sumsBytes,
|
|
71
|
+
sigBase64: assets.sigBase64,
|
|
72
|
+
assetBytes: assets.archiveBytes,
|
|
73
|
+
assetFilename: assets.archiveFilename,
|
|
74
|
+
});
|
|
75
|
+
log(`Verified — sha256 ${sha256.slice(0, 16)}…`);
|
|
76
|
+
// fetchReleaseAssets's dev (gh) path already wrote the archive to workDir
|
|
77
|
+
// under archiveFilename; ensure it's there regardless of source so
|
|
78
|
+
// extraction always has a real file to operate on.
|
|
79
|
+
const archivePath = path.join(workDir, archiveFilename);
|
|
80
|
+
if (!fs.existsSync(archivePath)) {
|
|
81
|
+
fs.writeFileSync(archivePath, assets.archiveBytes);
|
|
82
|
+
}
|
|
83
|
+
const extractDir = path.join(workDir, 'extracted');
|
|
84
|
+
await extractArchive(archivePath, extractDir, releaseArchiveExtension(triple));
|
|
85
|
+
const extractedBinaryPath = path.join(extractDir, binaryNameInArchive());
|
|
86
|
+
if (!fs.existsSync(extractedBinaryPath)) {
|
|
87
|
+
throw new ExtractionError(`archive did not contain the expected binary at its root: ${binaryNameInArchive()}`);
|
|
88
|
+
}
|
|
89
|
+
// Defense in depth: confirm the extracted binary genuinely resolves
|
|
90
|
+
// inside extractDir (catches a symlink swap or similar), even though
|
|
91
|
+
// we only ever read one specific expected relative path, never an
|
|
92
|
+
// archive-listed one (so "zip slip" via arbitrary archive paths isn't
|
|
93
|
+
// reachable here in the first place).
|
|
94
|
+
const { PathValidator } = await import('@claude-flow/security');
|
|
95
|
+
const validator = new PathValidator({ allowedPrefixes: [extractDir] });
|
|
96
|
+
const validation = await validator.validate(extractedBinaryPath);
|
|
97
|
+
if (!validation.isValid) {
|
|
98
|
+
throw new ExtractionError(`extracted binary path failed validation: ${validation.errors.join('; ') || 'unknown'}`);
|
|
99
|
+
}
|
|
100
|
+
const finalPath = proxyBinaryPath();
|
|
101
|
+
fs.mkdirSync(path.dirname(finalPath), { recursive: true, mode: 0o700 });
|
|
102
|
+
const tmp = `${finalPath}.tmp`;
|
|
103
|
+
fs.copyFileSync(extractedBinaryPath, tmp);
|
|
104
|
+
fs.chmodSync(tmp, 0o755);
|
|
105
|
+
fs.renameSync(tmp, finalPath);
|
|
106
|
+
const liveSha = sha256Hex(fs.readFileSync(finalPath));
|
|
107
|
+
const manifest = {
|
|
108
|
+
version: opts.version,
|
|
109
|
+
sha256: liveSha,
|
|
110
|
+
verifiedAt: new Date().toISOString(),
|
|
111
|
+
pubkeyFingerprint: sha256Hex(Buffer.from(PROXY_RELEASE_PUBKEY_PEM)).slice(0, 16),
|
|
112
|
+
};
|
|
113
|
+
fs.mkdirSync(path.dirname(proxyInstallManifestPath()), { recursive: true });
|
|
114
|
+
fs.writeFileSync(proxyInstallManifestPath(), JSON.stringify(manifest, null, 2), { mode: 0o600 });
|
|
115
|
+
log(`meta-proxy ${opts.version} installed at ${finalPath}`);
|
|
116
|
+
return { version: opts.version, binaryPath: finalPath, sha256: liveSha };
|
|
117
|
+
}
|
|
118
|
+
finally {
|
|
119
|
+
fs.rmSync(workDir, { recursive: true, force: true });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
export async function uninstallProxy() {
|
|
123
|
+
const binPath = proxyBinaryPath();
|
|
124
|
+
const existed = fs.existsSync(binPath);
|
|
125
|
+
if (existed)
|
|
126
|
+
fs.unlinkSync(binPath);
|
|
127
|
+
try {
|
|
128
|
+
fs.unlinkSync(proxyInstallManifestPath());
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
/* absent — fine */
|
|
132
|
+
}
|
|
133
|
+
return existed;
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=install.js.map
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* meta-proxy process lifecycle (ADR-307) — start/stop/status/logs.
|
|
3
|
+
*
|
|
4
|
+
* Adapts daemon.ts's proven pattern (PID file, O_EXCL lockfile for atomic
|
|
5
|
+
* check-then-start, signal-0 liveness, SIGTERM->1000ms->SIGKILL) to a
|
|
6
|
+
* native binary instead of a forked Node process. The binary itself takes
|
|
7
|
+
* no CLI flags — confirmed empirically (2026-07-16): `meta-proxy.exe` has
|
|
8
|
+
* no `--version`/`--help`, and any invocation just starts the server reading
|
|
9
|
+
* its own config file — so `spawn()` here passes zero arguments, always.
|
|
10
|
+
*
|
|
11
|
+
* Foreground `start` (the ADR-307 default) uses `stdio: 'inherit'` and
|
|
12
|
+
* blocks directly — simplest and safest, no log-file redirection needed.
|
|
13
|
+
* `start --service` needs REAL file-descriptor redirection
|
|
14
|
+
* (`stdio: ['ignore', fd, fd]`) + `detached: true` + `unref()` via
|
|
15
|
+
* `child_process.spawn()` directly — `SafeExecutor.executeStreaming()`
|
|
16
|
+
* buffers output in-process, which is wrong for a process meant to outlive
|
|
17
|
+
* the `ruflo` invocation that started it.
|
|
18
|
+
*
|
|
19
|
+
* @module proxy/lifecycle
|
|
20
|
+
*/
|
|
21
|
+
import * as fs from 'node:fs';
|
|
22
|
+
export declare class ProxyNotInstalledError extends Error {
|
|
23
|
+
constructor();
|
|
24
|
+
}
|
|
25
|
+
export declare class ProxyAlreadyRunningError extends Error {
|
|
26
|
+
readonly pid: number;
|
|
27
|
+
constructor(pid: number);
|
|
28
|
+
}
|
|
29
|
+
export interface ProxyStatus {
|
|
30
|
+
installed: boolean;
|
|
31
|
+
running: boolean;
|
|
32
|
+
pid: number | null;
|
|
33
|
+
stalePidFile: boolean;
|
|
34
|
+
}
|
|
35
|
+
export declare function getProxyStatus(): ProxyStatus;
|
|
36
|
+
/**
|
|
37
|
+
* Foreground start (ADR-307 default) — blocks the caller until the process
|
|
38
|
+
* exits or is interrupted. `stdio: 'inherit'` passes the proxy's own output
|
|
39
|
+
* straight through to the terminal; signals (Ctrl+C) propagate naturally to
|
|
40
|
+
* the child, no manual forwarding needed.
|
|
41
|
+
*/
|
|
42
|
+
export declare function startForeground(supervised?: boolean): Promise<never>;
|
|
43
|
+
/**
|
|
44
|
+
* Background/`--service` start — detaches so the process outlives this
|
|
45
|
+
* `ruflo` invocation, redirecting stdout/stderr to a real log file (not
|
|
46
|
+
* buffered in-process). Returns once the child's PID is confirmed written,
|
|
47
|
+
* without waiting for the process to exit.
|
|
48
|
+
*/
|
|
49
|
+
export declare function startBackground(): Promise<{
|
|
50
|
+
pid: number;
|
|
51
|
+
}>;
|
|
52
|
+
export interface StopResult {
|
|
53
|
+
wasRunning: boolean;
|
|
54
|
+
pid: number | null;
|
|
55
|
+
}
|
|
56
|
+
/** SIGTERM -> 1000ms -> SIGKILL if still alive, mirroring daemon.ts's killBackgroundDaemon. */
|
|
57
|
+
export declare function stopProxy(): Promise<StopResult>;
|
|
58
|
+
export declare function readProxyLogTail(maxBytes?: number): string;
|
|
59
|
+
/** Streams new log lines as they're appended, starting from the current end of file. */
|
|
60
|
+
export declare function watchProxyLog(onLine: (line: string) => void): fs.FSWatcher;
|
|
61
|
+
//# sourceMappingURL=lifecycle.d.ts.map
|