claude-flow 3.32.2 → 3.32.9

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 (50) hide show
  1. package/.claude/helpers/statusline.cjs +93 -39
  2. package/.claude-plugin/hooks/hooks.json +3 -1
  3. package/.claude-plugin/scripts/ruflo-hook.cjs +2 -2
  4. package/.claude-plugin/scripts/ruflo-hook.sh +17 -17
  5. package/package.json +9 -3
  6. package/v3/@claude-flow/cli/catalog-manifest.json +2 -2
  7. package/v3/@claude-flow/cli/dist/src/auth/client.d.ts +89 -0
  8. package/v3/@claude-flow/cli/dist/src/auth/client.js +242 -0
  9. package/v3/@claude-flow/cli/dist/src/auth/constants.d.ts +7 -0
  10. package/v3/@claude-flow/cli/dist/src/auth/constants.js +7 -0
  11. package/v3/@claude-flow/cli/dist/src/auth/scopes.d.ts +14 -0
  12. package/v3/@claude-flow/cli/dist/src/auth/scopes.js +21 -0
  13. package/v3/@claude-flow/cli/dist/src/auth/security-bridge.d.ts +36 -0
  14. package/v3/@claude-flow/cli/dist/src/auth/security-bridge.js +42 -0
  15. package/v3/@claude-flow/cli/dist/src/auth/session.d.ts +20 -0
  16. package/v3/@claude-flow/cli/dist/src/auth/session.js +32 -0
  17. package/v3/@claude-flow/cli/dist/src/auth/state.d.ts +19 -0
  18. package/v3/@claude-flow/cli/dist/src/auth/state.js +53 -0
  19. package/v3/@claude-flow/cli/dist/src/auth/types.d.ts +27 -0
  20. package/v3/@claude-flow/cli/dist/src/auth/types.js +11 -0
  21. package/v3/@claude-flow/cli/dist/src/commands/auth.d.ts +15 -0
  22. package/v3/@claude-flow/cli/dist/src/commands/auth.js +244 -0
  23. package/v3/@claude-flow/cli/dist/src/commands/doctor.js +478 -24
  24. package/v3/@claude-flow/cli/dist/src/commands/hooks.js +43 -1
  25. package/v3/@claude-flow/cli/dist/src/commands/index.js +2 -0
  26. package/v3/@claude-flow/cli/dist/src/commands/proxy-lifecycle.d.ts +20 -0
  27. package/v3/@claude-flow/cli/dist/src/commands/proxy-lifecycle.js +277 -0
  28. package/v3/@claude-flow/cli/dist/src/commands/proxy.js +97 -4
  29. package/v3/@claude-flow/cli/dist/src/funnel/message-transport.d.ts +11 -4
  30. package/v3/@claude-flow/cli/dist/src/funnel/message-transport.js +11 -4
  31. package/v3/@claude-flow/cli/dist/src/mcp-tools/memory-tools.js +11 -3
  32. package/v3/@claude-flow/cli/dist/src/memory/graph-edge-writer.d.ts +9 -0
  33. package/v3/@claude-flow/cli/dist/src/memory/graph-edge-writer.js +13 -0
  34. package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.js +32 -5
  35. package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.js +72 -0
  36. package/v3/@claude-flow/cli/dist/src/proxy/install.d.ts +29 -0
  37. package/v3/@claude-flow/cli/dist/src/proxy/install.js +135 -0
  38. package/v3/@claude-flow/cli/dist/src/proxy/lifecycle.d.ts +61 -0
  39. package/v3/@claude-flow/cli/dist/src/proxy/lifecycle.js +249 -0
  40. package/v3/@claude-flow/cli/dist/src/proxy/paths.d.ts +34 -0
  41. package/v3/@claude-flow/cli/dist/src/proxy/paths.js +70 -0
  42. package/v3/@claude-flow/cli/dist/src/proxy/release.d.ts +47 -0
  43. package/v3/@claude-flow/cli/dist/src/proxy/release.js +138 -0
  44. package/v3/@claude-flow/cli/dist/src/proxy/token-bridge.d.ts +5 -0
  45. package/v3/@claude-flow/cli/dist/src/proxy/token-bridge.js +61 -0
  46. package/v3/@claude-flow/cli/dist/src/proxy/verify.d.ts +44 -0
  47. package/v3/@claude-flow/cli/dist/src/proxy/verify.js +68 -0
  48. package/v3/@claude-flow/cli/package.json +6 -4
  49. package/.claude/.proven-config-version +0 -1
  50. package/.claude/proven-config.json +0 -42
@@ -0,0 +1,20 @@
1
+ /**
2
+ * `ruflo proxy install|start|stop|status|logs|update|uninstall` (ADR-307) —
3
+ * the full lifecycle command set, kept in its own file so
4
+ * src/commands/proxy.ts (the ADR-313/314/315 consent subcommands) stays
5
+ * under the repo's 500-line-per-file convention. Merged into one
6
+ * `proxyCommand` in proxy.ts.
7
+ */
8
+ import type { Command } from '../types.js';
9
+ import { type ProxyStatus } from '../proxy/lifecycle.js';
10
+ /** Pinned and reviewed; later upgrades remain explicit commands. */
11
+ export declare const DEFAULT_PROXY_RELEASE = "0.4.0";
12
+ /**
13
+ * Human-oriented next steps for `ruflo proxy` and `ruflo proxy status`.
14
+ * Keep this independent of the command framework so the state-specific
15
+ * guidance has a small, direct regression-test surface.
16
+ */
17
+ export declare function proxyConsoleGuidance(status: ProxyStatus): string[];
18
+ export declare function printProxyConsoleGuidance(status: ProxyStatus): void;
19
+ export declare const proxyLifecycleSubcommands: Command[];
20
+ //# sourceMappingURL=proxy-lifecycle.d.ts.map
@@ -0,0 +1,277 @@
1
+ /**
2
+ * `ruflo proxy install|start|stop|status|logs|update|uninstall` (ADR-307) —
3
+ * the full lifecycle command set, kept in its own file so
4
+ * src/commands/proxy.ts (the ADR-313/314/315 consent subcommands) stays
5
+ * under the repo's 500-line-per-file convention. Merged into one
6
+ * `proxyCommand` in proxy.ts.
7
+ */
8
+ import { output } from '../output.js';
9
+ import { hasConsent, recordConsent, revokeConsent } from '../funnel/index.js';
10
+ import { installProxy, uninstallProxy } from '../proxy/install.js';
11
+ import { startForeground, startBackground, stopProxy, getProxyStatus, readProxyLogTail, watchProxyLog, ProxyNotInstalledError, ProxyAlreadyRunningError, } from '../proxy/lifecycle.js';
12
+ import { proxyTokenPath } from '../proxy/paths.js';
13
+ import { removeInjectedToken, startTokenRefreshPump } from '../proxy/token-bridge.js';
14
+ /** Pinned and reviewed; later upgrades remain explicit commands. */
15
+ export const DEFAULT_PROXY_RELEASE = '0.4.0';
16
+ const PROXY_COMMAND = 'npx ruflo@latest proxy';
17
+ const AUTH_COMMAND = 'npx ruflo@latest auth';
18
+ /**
19
+ * Human-oriented next steps for `ruflo proxy` and `ruflo proxy status`.
20
+ * Keep this independent of the command framework so the state-specific
21
+ * guidance has a small, direct regression-test surface.
22
+ */
23
+ export function proxyConsoleGuidance(status) {
24
+ if (!status.installed) {
25
+ return [
26
+ '',
27
+ 'Next step',
28
+ ` ${PROXY_COMMAND} install --yes Install signed Meta-Proxy v${DEFAULT_PROXY_RELEASE}`,
29
+ '',
30
+ 'After installation, start it in the background:',
31
+ ` ${PROXY_COMMAND} start --service`,
32
+ ];
33
+ }
34
+ if (!status.running) {
35
+ return [
36
+ '',
37
+ 'Next step',
38
+ ` ${PROXY_COMMAND} start --service Start the proxy in the background`,
39
+ '',
40
+ 'Foreground mode (shows live logs; Ctrl+C stops it):',
41
+ ` ${PROXY_COMMAND} start`,
42
+ '',
43
+ 'Optional: enable Cognitum cloud routing (local-only is the default):',
44
+ ` ${AUTH_COMMAND} login`,
45
+ ` ${PROXY_COMMAND} config --cloud --yes`,
46
+ ];
47
+ }
48
+ return [
49
+ '',
50
+ 'Meta Proxy is ready.',
51
+ ` Logs: ${PROXY_COMMAND} logs`,
52
+ ` Stop: ${PROXY_COMMAND} stop`,
53
+ '',
54
+ 'Optional: enable Cognitum cloud routing (local-only is the default):',
55
+ ` ${AUTH_COMMAND} login`,
56
+ ` ${PROXY_COMMAND} config --cloud --yes`,
57
+ ];
58
+ }
59
+ export function printProxyConsoleGuidance(status) {
60
+ for (const line of proxyConsoleGuidance(status))
61
+ output.writeln(line);
62
+ }
63
+ const INSTALL_DISCLOSURE = [
64
+ 'Installing the Meta LLM Proxy (ADR-304/307).',
65
+ '',
66
+ 'This downloads a separately-released Rust binary (Ed25519-signature and',
67
+ 'checksum verified before anything is written to disk) and runs it as a',
68
+ 'local process bound to 127.0.0.1 only. The proxy routes to LOCAL',
69
+ 'backends by default — no prompt leaves this machine. Cloud routing is a',
70
+ 'separate, explicit opt-in (`ruflo proxy config --cloud`), never enabled',
71
+ 'by install alone.',
72
+ '',
73
+ 'Uninstall anytime: ruflo proxy uninstall',
74
+ ].join('\n');
75
+ const installSub = {
76
+ name: 'install',
77
+ description: 'Download, verify, and install the meta-proxy binary (ADR-307)',
78
+ options: [
79
+ // NOT named 'version' — index.ts:107 globally intercepts any --version/-v
80
+ // anywhere in argv (`if (flags.version || flags.V) { showVersion(); return; }`)
81
+ // BEFORE subcommand dispatch, regardless of which command defines it. A
82
+ // subcommand-local --version is silently swallowed by the CLI's own
83
+ // `ruflo --version` handling — confirmed the hard way in E2E testing.
84
+ { name: 'release', description: `Release version to install (default: ${DEFAULT_PROXY_RELEASE})`, type: 'string' },
85
+ { name: 'yes', description: 'Skip the confirmation prompt', type: 'boolean', default: false },
86
+ ],
87
+ action: async (ctx) => {
88
+ // Resolve the reviewed default before the consent gate. An explicit empty
89
+ // override still fails below without recording a consent receipt.
90
+ const version = typeof ctx.flags.release === 'string' ? ctx.flags.release : DEFAULT_PROXY_RELEASE;
91
+ if (!version) {
92
+ output.printError('ruflo proxy install requires --release <x.y.z> — there is no version-discovery ' +
93
+ 'endpoint yet (see the plan doc for the tracked follow-up). Find the latest at ' +
94
+ 'the release channel and pass it explicitly.');
95
+ return { success: false, exitCode: 1 };
96
+ }
97
+ if (!hasConsent('proxy-install')) {
98
+ output.writeln(INSTALL_DISCLOSURE);
99
+ output.writeln('');
100
+ const confirmed = Boolean(ctx.flags.yes);
101
+ if (!confirmed) {
102
+ output.writeln(`Re-run with --yes to confirm: ruflo proxy install --yes (installs ${version})`);
103
+ return { success: true, data: { confirmed: false } };
104
+ }
105
+ recordConsent('proxy-install', true, 'proxy-install');
106
+ }
107
+ try {
108
+ const spinner = output.createSpinner({ text: `Installing meta-proxy ${version}...`, spinner: 'dots' });
109
+ spinner.start();
110
+ const result = await installProxy({ version, log: (line) => spinner.setText(line) });
111
+ spinner.succeed(`meta-proxy ${version} installed`);
112
+ output.writeln(` binary: ${result.binaryPath}`);
113
+ output.writeln(` sha256: ${result.sha256}`);
114
+ return { success: true, data: result };
115
+ }
116
+ catch (e) {
117
+ const message = e instanceof Error ? e.message : String(e);
118
+ output.printError('Install failed', message);
119
+ return { success: false, message, exitCode: 1 };
120
+ }
121
+ },
122
+ };
123
+ const updateSub = {
124
+ name: 'update',
125
+ description: 'Re-verify and replace the installed binary with a specific version (never automatic)',
126
+ options: [{ name: 'release', description: 'Release version to install', type: 'string', required: true }],
127
+ action: async (ctx) => {
128
+ const version = typeof ctx.flags.release === 'string' ? ctx.flags.release : undefined;
129
+ if (!version) {
130
+ output.printError('ruflo proxy update requires --release <x.y.z>');
131
+ return { success: false, exitCode: 1 };
132
+ }
133
+ try {
134
+ const spinner = output.createSpinner({ text: `Updating meta-proxy to ${version}...`, spinner: 'dots' });
135
+ spinner.start();
136
+ const result = await installProxy({ version, log: (line) => spinner.setText(line) });
137
+ spinner.succeed(`meta-proxy updated to ${version}`);
138
+ output.writeln(` binary: ${result.binaryPath}`);
139
+ return { success: true, data: result };
140
+ }
141
+ catch (e) {
142
+ const message = e instanceof Error ? e.message : String(e);
143
+ output.printError('Update failed', message);
144
+ return { success: false, message, exitCode: 1 };
145
+ }
146
+ },
147
+ };
148
+ const startSub = {
149
+ name: 'start',
150
+ description: 'Start meta-proxy (foreground by default; --service to detach)',
151
+ options: [
152
+ { name: 'service', description: 'Run detached (background), survives terminal close but not a reboot — full OS-service registration is not yet implemented', type: 'boolean', default: false },
153
+ ],
154
+ action: async (ctx) => {
155
+ const service = Boolean(ctx.flags.service);
156
+ try {
157
+ if (service) {
158
+ const { pid } = await startBackground();
159
+ output.printSuccess(`meta-proxy started in the background (pid ${pid})`);
160
+ output.writeln(' Note: survives this terminal closing, but not a reboot — OS-service registration is not yet implemented.');
161
+ output.writeln(' Logs: ruflo proxy logs');
162
+ return { success: true, data: { pid } };
163
+ }
164
+ output.writeln('Starting meta-proxy in the foreground — press Ctrl+C to stop.');
165
+ await startTokenRefreshPump();
166
+ await startForeground(); // never returns normally
167
+ return { success: true };
168
+ }
169
+ catch (e) {
170
+ if (e instanceof ProxyNotInstalledError || e instanceof ProxyAlreadyRunningError) {
171
+ output.printError(e.message);
172
+ return { success: false, message: e.message, exitCode: 1 };
173
+ }
174
+ const message = e instanceof Error ? e.message : String(e);
175
+ output.printError('Failed to start meta-proxy', message);
176
+ return { success: false, message, exitCode: 1 };
177
+ }
178
+ },
179
+ };
180
+ const superviseSub = {
181
+ name: 'supervise',
182
+ description: 'Internal detached supervisor for token refresh and meta-proxy lifecycle',
183
+ action: async () => {
184
+ await startTokenRefreshPump();
185
+ await startForeground(true);
186
+ return { success: true };
187
+ },
188
+ };
189
+ const stopSub = {
190
+ name: 'stop',
191
+ description: 'Stop a running meta-proxy process',
192
+ action: async () => {
193
+ const result = await stopProxy();
194
+ removeInjectedToken();
195
+ if (!result.wasRunning) {
196
+ output.writeln('meta-proxy was not running.');
197
+ return { success: true, data: result };
198
+ }
199
+ output.printSuccess(`meta-proxy stopped (was pid ${result.pid})`);
200
+ return { success: true, data: result };
201
+ },
202
+ };
203
+ const statusSub = {
204
+ name: 'status',
205
+ description: 'Show meta-proxy install + process status',
206
+ options: [{ name: 'json', description: 'Machine-readable output', type: 'boolean', default: false }],
207
+ action: async (ctx) => {
208
+ const status = getProxyStatus();
209
+ if (ctx.flags.json) {
210
+ output.printJson(status);
211
+ return { success: true, data: status };
212
+ }
213
+ output.writeln('Meta Proxy');
214
+ output.writeln(` Installation: ${status.installed ? 'ready' : 'not installed'}`);
215
+ output.writeln(` Process: ${status.running ? `running (pid ${status.pid})` : 'not running'}`);
216
+ if (status.stalePidFile)
217
+ output.writeln(' (a stale PID file was found and will be cleared on next start)');
218
+ printProxyConsoleGuidance(status);
219
+ return { success: true, data: status };
220
+ },
221
+ };
222
+ const logsSub = {
223
+ name: 'logs',
224
+ description: 'Show meta-proxy --service logs',
225
+ options: [
226
+ { name: 'follow', short: 'f', description: 'Stream new log lines as they arrive', type: 'boolean', default: false },
227
+ ],
228
+ action: async (ctx) => {
229
+ if (ctx.flags.follow) {
230
+ output.writeln('Following meta-proxy logs — press Ctrl+C to stop.');
231
+ try {
232
+ const watcher = watchProxyLog((line) => output.writeln(line));
233
+ await new Promise((resolve) => {
234
+ process.on('SIGINT', () => {
235
+ watcher.close();
236
+ resolve();
237
+ });
238
+ });
239
+ return { success: true };
240
+ }
241
+ catch (e) {
242
+ const message = e instanceof Error ? e.message : String(e);
243
+ output.printError(message);
244
+ return { success: false, message, exitCode: 1 };
245
+ }
246
+ }
247
+ const tail = readProxyLogTail();
248
+ if (!tail) {
249
+ output.writeln('No log content yet — meta-proxy has never been started in --service mode.');
250
+ return { success: true, data: { empty: true } };
251
+ }
252
+ output.writeln(tail);
253
+ return { success: true };
254
+ },
255
+ };
256
+ const uninstallSub = {
257
+ name: 'uninstall',
258
+ description: 'Stop the proxy (if running), remove the binary, token, and consent receipt',
259
+ action: async () => {
260
+ const status = getProxyStatus();
261
+ if (status.running) {
262
+ await stopProxy();
263
+ output.writeln('Stopped the running meta-proxy process.');
264
+ }
265
+ const removed = await uninstallProxy();
266
+ const { existsSync, unlinkSync } = await import('node:fs');
267
+ const tokenPath = proxyTokenPath();
268
+ if (existsSync(tokenPath))
269
+ unlinkSync(tokenPath);
270
+ removeInjectedToken();
271
+ revokeConsent('proxy-install', 'proxy-uninstall');
272
+ output.printSuccess(removed ? 'meta-proxy uninstalled.' : 'Nothing was installed — cleaned up any leftover state.');
273
+ return { success: true, data: { removed } };
274
+ },
275
+ };
276
+ export const proxyLifecycleSubcommands = [installSub, updateSub, startSub, superviseSub, stopSub, statusSub, logsSub, uninstallSub];
277
+ //# sourceMappingURL=proxy-lifecycle.js.map
@@ -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 writeConsentMirrorLine(field, value) {
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} = ${value}`;
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: sponsorStatusSub.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
@@ -5,16 +5,23 @@
5
5
  *
6
6
  * Design discipline:
7
7
  * 1. **Never blocks the render.** The statusline reads only the local
8
- * cache. This module refreshes the cache in the background; a fresh
9
- * install shows the in-code fallback pool until the first refresh
10
- * lands (usually seconds after CLI startup).
8
+ * cache. This module refreshes the cache in the background; per
9
+ * ADR-311 "zero local promo content" there is NO in-code fallback
10
+ * pool — a genuinely fresh install (empty cache) shows nothing on
11
+ * the promo/disclosure row until the first background refresh lands
12
+ * (see hook-handler.cjs's SessionStart-triggered detached
13
+ * `hooks refresh-funnel`, usually within seconds of CLI startup, but
14
+ * contingent on network reachability to the messages endpoint). This
15
+ * comment previously (incorrectly) described an in-code fallback
16
+ * pool; there has never been one since ADR-311 — see disclosure.ts's
17
+ * own "fail-closed" doc comment for the authoritative statement.
11
18
  * 2. **Content pipeline stays authoritative.** Every message the server
12
19
  * returns is validated by `isValidMessage()` from `messages.ts` —
13
20
  * same schema, same host allowlist, same control-char strip, same
14
21
  * 80-column cap. A tampered or accidentally-broken remote feed can
15
22
  * pollute nothing.
16
23
  * 3. **Fail silent.** Any network/parse/validation failure leaves the
17
- * previously-cached (or in-code) pool intact.
24
+ * previously-cached pool intact (empty, if nothing has landed yet).
18
25
  * 4. **Bounded cache size.** ≤ 128 KiB and ≤ 200 messages — matches
19
26
  * ADR-309's bounded-local-queue discipline.
20
27
  * 5. **Kill switch.** `RUFLO_FUNNEL_MESSAGES=0` (or `RUFLO_FUNNEL=0`)
@@ -5,16 +5,23 @@
5
5
  *
6
6
  * Design discipline:
7
7
  * 1. **Never blocks the render.** The statusline reads only the local
8
- * cache. This module refreshes the cache in the background; a fresh
9
- * install shows the in-code fallback pool until the first refresh
10
- * lands (usually seconds after CLI startup).
8
+ * cache. This module refreshes the cache in the background; per
9
+ * ADR-311 "zero local promo content" there is NO in-code fallback
10
+ * pool — a genuinely fresh install (empty cache) shows nothing on
11
+ * the promo/disclosure row until the first background refresh lands
12
+ * (see hook-handler.cjs's SessionStart-triggered detached
13
+ * `hooks refresh-funnel`, usually within seconds of CLI startup, but
14
+ * contingent on network reachability to the messages endpoint). This
15
+ * comment previously (incorrectly) described an in-code fallback
16
+ * pool; there has never been one since ADR-311 — see disclosure.ts's
17
+ * own "fail-closed" doc comment for the authoritative statement.
11
18
  * 2. **Content pipeline stays authoritative.** Every message the server
12
19
  * returns is validated by `isValidMessage()` from `messages.ts` —
13
20
  * same schema, same host allowlist, same control-char strip, same
14
21
  * 80-column cap. A tampered or accidentally-broken remote feed can
15
22
  * pollute nothing.
16
23
  * 3. **Fail silent.** Any network/parse/validation failure leaves the
17
- * previously-cached (or in-code) pool intact.
24
+ * previously-cached pool intact (empty, if nothing has landed yet).
18
25
  * 4. **Bounded cache size.** ≤ 128 KiB and ≤ 200 messages — matches
19
26
  * ADR-309's bounded-local-queue discipline.
20
27
  * 5. **Kill switch.** `RUFLO_FUNNEL_MESSAGES=0` (or `RUFLO_FUNNEL=0`)
@@ -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: "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
- const namespace = input.namespace || 'default';
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.
@@ -90,6 +90,15 @@ export declare function countGraphEdges(dbPath?: string): Promise<number>;
90
90
  * #2431 fix: also explicitly closes the prior handle so file locks
91
91
  * release immediately — better-sqlite3 holds an OS-level file handle
92
92
  * which the prior sql.js implementation did not.
93
+ *
94
+ * #2736-followup fix: `close()` alone only checkpoints the WAL back into
95
+ * the main file as a best-effort PASSIVE checkpoint when SQLite considers
96
+ * this the last connection — which is not guaranteed to fully flush under
97
+ * contention, and was observed leaving writes invisible to a same-process
98
+ * sql.js reader that re-reads the raw file immediately after close() on
99
+ * Linux CI runners (not reproduced on Windows). Force a blocking TRUNCATE
100
+ * checkpoint before close so cross-engine readers always see committed
101
+ * writes deterministically, regardless of platform/timing.
93
102
  */
94
103
  export declare function _resetBridgeDb(): void;
95
104
  //# sourceMappingURL=graph-edge-writer.d.ts.map
@@ -203,9 +203,22 @@ export async function countGraphEdges(dbPath) {
203
203
  * #2431 fix: also explicitly closes the prior handle so file locks
204
204
  * release immediately — better-sqlite3 holds an OS-level file handle
205
205
  * which the prior sql.js implementation did not.
206
+ *
207
+ * #2736-followup fix: `close()` alone only checkpoints the WAL back into
208
+ * the main file as a best-effort PASSIVE checkpoint when SQLite considers
209
+ * this the last connection — which is not guaranteed to fully flush under
210
+ * contention, and was observed leaving writes invisible to a same-process
211
+ * sql.js reader that re-reads the raw file immediately after close() on
212
+ * Linux CI runners (not reproduced on Windows). Force a blocking TRUNCATE
213
+ * checkpoint before close so cross-engine readers always see committed
214
+ * writes deterministically, regardless of platform/timing.
206
215
  */
207
216
  export function _resetBridgeDb() {
208
217
  if (_db) {
218
+ try {
219
+ _db.pragma('wal_checkpoint(TRUNCATE)');
220
+ }
221
+ catch { /* best-effort */ }
209
222
  try {
210
223
  _db.close();
211
224
  }
@@ -23,6 +23,28 @@ import { createRequire } from 'node:module';
23
23
  let registryPromise = null;
24
24
  let registryInstance = null;
25
25
  let bridgeAvailable = null;
26
+ // #2735 — the native-bridge -> sql.js whole-image fallback used to be
27
+ // completely silent: a registry init failure cached `bridgeAvailable =
28
+ // false` for the rest of the process, and a per-operation bridge exception
29
+ // swallowed itself with a bare `catch { return null; }` — in both cases the
30
+ // caller fell back to memory-initializer.ts's unsafe whole-image sql.js
31
+ // path with zero diagnostic trail. When that path corrupted a database, the
32
+ // demotion that caused it was structurally unknowable after the fact. Log
33
+ // once per distinct reason (not once per call — a hot per-op catch could
34
+ // otherwise spam stderr on every single memory operation) so the first
35
+ // occurrence is attributable. Stderr only — never stdout, which callers may
36
+ // be parsing as JSON.
37
+ const loggedDemotions = new Set();
38
+ function logDemotionOnce(where, reason) {
39
+ const dedupeKey = `${where}:${reason}`;
40
+ if (loggedDemotions.has(dedupeKey))
41
+ return;
42
+ loggedDemotions.add(dedupeKey);
43
+ try {
44
+ process.stderr.write(`[memory-bridge] demoted to sql.js fallback in ${where}: ${reason}\n`);
45
+ }
46
+ catch { /* stderr unavailable — nothing more we can do */ }
47
+ }
26
48
  /**
27
49
  * Resolve database path with path traversal protection.
28
50
  * Only allows paths within or below the project's working directory,
@@ -350,9 +372,10 @@ async function getRegistry(dbPath) {
350
372
  bridgeAvailable = true;
351
373
  return registry;
352
374
  }
353
- catch {
375
+ catch (err) {
354
376
  bridgeAvailable = false;
355
377
  registryPromise = null;
378
+ logDemotionOnce('getRegistry (process-wide, cached for the rest of this process)', err instanceof Error ? err.message : String(err));
356
379
  return null;
357
380
  }
358
381
  })();
@@ -737,7 +760,8 @@ export async function bridgeStoreEntry(options) {
737
760
  attested: true,
738
761
  };
739
762
  }
740
- catch {
763
+ catch (err) {
764
+ logDemotionOnce('bridgeStoreEntry', err instanceof Error ? err.message : String(err));
741
765
  return null;
742
766
  }
743
767
  }
@@ -1017,7 +1041,8 @@ export async function bridgeGetEntry(options) {
1017
1041
  await cacheSet(registry, cacheKey, entry);
1018
1042
  return { success: true, found: true, cacheHit: false, entry };
1019
1043
  }
1020
- catch {
1044
+ catch (err) {
1045
+ logDemotionOnce('bridgeGetEntry', err instanceof Error ? err.message : String(err));
1021
1046
  return null;
1022
1047
  }
1023
1048
  }
@@ -1077,7 +1102,8 @@ export async function bridgeDeleteEntry(options) {
1077
1102
  guarded: true,
1078
1103
  };
1079
1104
  }
1080
- catch {
1105
+ catch (err) {
1106
+ logDemotionOnce('bridgeDeleteEntry', err instanceof Error ? err.message : String(err));
1081
1107
  return null;
1082
1108
  }
1083
1109
  }
@@ -1128,7 +1154,8 @@ export async function bridgePurgeNamespace(options) {
1128
1154
  guarded: true,
1129
1155
  };
1130
1156
  }
1131
- catch {
1157
+ catch (err) {
1158
+ logDemotionOnce('bridgePurgeNamespace', err instanceof Error ? err.message : String(err));
1132
1159
  return null;
1133
1160
  }
1134
1161
  }