ruvector 0.2.18 → 0.2.20
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/README.md +20 -7
- package/bin/cli.js +1569 -1553
- package/bin/mcp-server.js +508 -892
- package/package.json +23 -13
- package/src/decompiler/index.js +407 -0
- package/src/decompiler/metrics.js +86 -0
- package/src/decompiler/module-splitter.js +498 -0
- package/src/decompiler/module-tree.js +142 -0
- package/src/decompiler/name-predictor.js +400 -0
- package/src/decompiler/npm-fetch.js +176 -0
- package/src/decompiler/reconstructor.js +499 -0
- package/src/decompiler/reference-tracker.js +285 -0
- package/src/decompiler/statement-parser.js +285 -0
- package/src/decompiler/style-improver.js +438 -0
- package/src/decompiler/subcategories.js +339 -0
- package/src/decompiler/validator.js +379 -0
- package/src/decompiler/witness.js +140 -0
package/bin/cli.js
CHANGED
|
@@ -9,52 +9,6 @@ const chalk = _chalk.default || _chalk;
|
|
|
9
9
|
const fs = require('fs');
|
|
10
10
|
const path = require('path');
|
|
11
11
|
|
|
12
|
-
// Load .env from current directory (if exists)
|
|
13
|
-
try {
|
|
14
|
-
const envPath = path.join(process.cwd(), '.env');
|
|
15
|
-
if (fs.existsSync(envPath)) {
|
|
16
|
-
const envContent = fs.readFileSync(envPath, 'utf8');
|
|
17
|
-
for (const line of envContent.split('\n')) {
|
|
18
|
-
const trimmed = line.trim();
|
|
19
|
-
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
20
|
-
const eqIdx = trimmed.indexOf('=');
|
|
21
|
-
if (eqIdx > 0) {
|
|
22
|
-
const key = trimmed.slice(0, eqIdx).trim();
|
|
23
|
-
let value = trimmed.slice(eqIdx + 1).trim();
|
|
24
|
-
// Strip surrounding quotes
|
|
25
|
-
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
26
|
-
value = value.slice(1, -1);
|
|
27
|
-
}
|
|
28
|
-
// Don't override existing env vars
|
|
29
|
-
if (!process.env[key]) {
|
|
30
|
-
process.env[key] = value;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
} catch {}
|
|
36
|
-
|
|
37
|
-
// Load global config from ~/.ruvector/config.json (if exists)
|
|
38
|
-
try {
|
|
39
|
-
const os = require('os');
|
|
40
|
-
const configPath = path.join(os.homedir(), '.ruvector', 'config.json');
|
|
41
|
-
if (fs.existsSync(configPath)) {
|
|
42
|
-
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
43
|
-
// Map config keys to env vars (don't override existing)
|
|
44
|
-
const configMap = {
|
|
45
|
-
brain_url: 'BRAIN_URL',
|
|
46
|
-
pi_key: 'PI',
|
|
47
|
-
edge_genesis_url: 'EDGE_GENESIS_URL',
|
|
48
|
-
edge_relay_url: 'EDGE_RELAY_URL',
|
|
49
|
-
};
|
|
50
|
-
for (const [configKey, envKey] of Object.entries(configMap)) {
|
|
51
|
-
if (config[configKey] && !process.env[envKey]) {
|
|
52
|
-
process.env[envKey] = config[configKey];
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
} catch {}
|
|
57
|
-
|
|
58
12
|
// Lazy load ora (spinner) - only needed for commands with progress indicators
|
|
59
13
|
let _oraModule = null;
|
|
60
14
|
function ora(text) {
|
|
@@ -92,7 +46,8 @@ function requireRuvector() {
|
|
|
92
46
|
}
|
|
93
47
|
|
|
94
48
|
// Lazy load GNN (optional - loaded on first use, not at startup)
|
|
95
|
-
|
|
49
|
+
// Saves ~6ms startup time by deferring require('@ruvector/gnn')
|
|
50
|
+
let _gnnModule = undefined; // undefined = not yet attempted, null = failed, object = loaded
|
|
96
51
|
let RuvectorLayer, TensorCompress, differentiableSearch, getCompressionLevel, hierarchicalForward;
|
|
97
52
|
let gnnAvailable = false;
|
|
98
53
|
|
|
@@ -108,88 +63,16 @@ function loadGnn() {
|
|
|
108
63
|
_gnnModule = gnn;
|
|
109
64
|
gnnAvailable = true;
|
|
110
65
|
return gnn;
|
|
111
|
-
} catch {
|
|
66
|
+
} catch (e) {
|
|
112
67
|
_gnnModule = null;
|
|
113
68
|
gnnAvailable = false;
|
|
114
69
|
return null;
|
|
115
70
|
}
|
|
116
71
|
}
|
|
117
72
|
|
|
118
|
-
// ── Proxy-aware fetch wrapper ───────────────────────────────────────────────
|
|
119
|
-
// Detects HTTP_PROXY / HTTPS_PROXY / ALL_PROXY env vars and routes traffic
|
|
120
|
-
// through the proxy when configured. Falls back to native fetch() when no
|
|
121
|
-
// proxy is set or the target matches NO_PROXY.
|
|
122
|
-
let _proxyDispatcherSet = false;
|
|
123
|
-
|
|
124
|
-
function getProxyUrl(targetUrl) {
|
|
125
|
-
const NO_PROXY = process.env.NO_PROXY || process.env.no_proxy || '';
|
|
126
|
-
if (NO_PROXY === '*') return null;
|
|
127
|
-
if (NO_PROXY) {
|
|
128
|
-
try {
|
|
129
|
-
const host = new URL(targetUrl).hostname.toLowerCase();
|
|
130
|
-
const patterns = NO_PROXY.split(',').map(p => p.trim().toLowerCase());
|
|
131
|
-
for (const p of patterns) {
|
|
132
|
-
if (!p) continue;
|
|
133
|
-
if (host === p || host.endsWith(p.startsWith('.') ? p : '.' + p)) return null;
|
|
134
|
-
}
|
|
135
|
-
} catch {}
|
|
136
|
-
}
|
|
137
|
-
return process.env.HTTPS_PROXY || process.env.https_proxy
|
|
138
|
-
|| process.env.HTTP_PROXY || process.env.http_proxy
|
|
139
|
-
|| process.env.ALL_PROXY || process.env.all_proxy
|
|
140
|
-
|| null;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
async function proxyFetch(url, opts) {
|
|
144
|
-
const target = typeof url === 'string' ? url : url.toString();
|
|
145
|
-
const proxy = getProxyUrl(target);
|
|
146
|
-
if (!proxy) return fetch(url, opts);
|
|
147
|
-
|
|
148
|
-
// Try undici ProxyAgent (bundled in Node 18+)
|
|
149
|
-
if (!_proxyDispatcherSet) {
|
|
150
|
-
try {
|
|
151
|
-
const undici = require('undici');
|
|
152
|
-
if (undici.ProxyAgent && undici.setGlobalDispatcher) {
|
|
153
|
-
undici.setGlobalDispatcher(new undici.ProxyAgent(proxy));
|
|
154
|
-
_proxyDispatcherSet = true;
|
|
155
|
-
}
|
|
156
|
-
} catch {}
|
|
157
|
-
}
|
|
158
|
-
if (_proxyDispatcherSet) return fetch(url, opts);
|
|
159
|
-
|
|
160
|
-
// Fallback: shell out to curl (which natively respects proxy env vars)
|
|
161
|
-
const { execFileSync } = require('child_process');
|
|
162
|
-
const args = ['-sS', '-L', '--max-time', '30', '-w', '\n%{http_code}'];
|
|
163
|
-
if (opts && opts.method) { args.push('-X', opts.method); }
|
|
164
|
-
if (opts && opts.headers) {
|
|
165
|
-
for (const [k, v] of Object.entries(opts.headers)) {
|
|
166
|
-
args.push('-H', `${k}: ${v}`);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
if (opts && opts.body) { args.push('-d', typeof opts.body === 'string' ? opts.body : JSON.stringify(opts.body)); }
|
|
170
|
-
args.push(target);
|
|
171
|
-
try {
|
|
172
|
-
const stdout = execFileSync('curl', args, { encoding: 'utf8', timeout: 35000 });
|
|
173
|
-
const lines = stdout.trimEnd().split('\n');
|
|
174
|
-
const statusCode = parseInt(lines.pop(), 10) || 200;
|
|
175
|
-
const body = lines.join('\n').trim();
|
|
176
|
-
const ok = statusCode >= 200 && statusCode < 300;
|
|
177
|
-
return {
|
|
178
|
-
ok,
|
|
179
|
-
status: statusCode,
|
|
180
|
-
statusText: ok ? 'OK' : `HTTP ${statusCode}`,
|
|
181
|
-
text: async () => body,
|
|
182
|
-
json: async () => body ? JSON.parse(body) : {},
|
|
183
|
-
headers: new Map(),
|
|
184
|
-
};
|
|
185
|
-
} catch (e) {
|
|
186
|
-
const msg = e.stderr ? e.stderr.toString().trim() : e.message;
|
|
187
|
-
throw new Error(`Proxy curl failed: ${msg}`);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
73
|
// Lazy load Attention (optional - loaded on first use, not at startup)
|
|
192
|
-
|
|
74
|
+
// Saves ~5ms startup time by deferring require('@ruvector/attention')
|
|
75
|
+
let _attentionModule = undefined; // undefined = not yet attempted
|
|
193
76
|
let DotProductAttention, MultiHeadAttention, HyperbolicAttention, FlashAttention, LinearAttention, MoEAttention;
|
|
194
77
|
let GraphRoPeAttention, EdgeFeaturedAttention, DualSpaceAttention, LocalGlobalAttention;
|
|
195
78
|
let benchmarkAttention, computeAttentionAsync, batchAttentionCompute, parallelAttentionCompute;
|
|
@@ -201,31 +84,36 @@ function loadAttention() {
|
|
|
201
84
|
if (_attentionModule !== undefined) return _attentionModule;
|
|
202
85
|
try {
|
|
203
86
|
const attention = require('@ruvector/attention');
|
|
87
|
+
// Core mechanisms
|
|
204
88
|
DotProductAttention = attention.DotProductAttention;
|
|
205
89
|
MultiHeadAttention = attention.MultiHeadAttention;
|
|
206
90
|
HyperbolicAttention = attention.HyperbolicAttention;
|
|
207
91
|
FlashAttention = attention.FlashAttention;
|
|
208
92
|
LinearAttention = attention.LinearAttention;
|
|
209
93
|
MoEAttention = attention.MoEAttention;
|
|
94
|
+
// Graph attention
|
|
210
95
|
GraphRoPeAttention = attention.GraphRoPeAttention;
|
|
211
96
|
EdgeFeaturedAttention = attention.EdgeFeaturedAttention;
|
|
212
97
|
DualSpaceAttention = attention.DualSpaceAttention;
|
|
213
98
|
LocalGlobalAttention = attention.LocalGlobalAttention;
|
|
99
|
+
// Utilities
|
|
214
100
|
benchmarkAttention = attention.benchmarkAttention;
|
|
215
101
|
computeAttentionAsync = attention.computeAttentionAsync;
|
|
216
102
|
batchAttentionCompute = attention.batchAttentionCompute;
|
|
217
103
|
parallelAttentionCompute = attention.parallelAttentionCompute;
|
|
104
|
+
// Hyperbolic math
|
|
218
105
|
expMap = attention.expMap;
|
|
219
106
|
logMap = attention.logMap;
|
|
220
107
|
mobiusAddition = attention.mobiusAddition;
|
|
221
108
|
poincareDistance = attention.poincareDistance;
|
|
222
109
|
projectToPoincareBall = attention.projectToPoincareBall;
|
|
223
|
-
|
|
224
|
-
|
|
110
|
+
// Meta
|
|
111
|
+
attentionInfo = attention.info;
|
|
112
|
+
attentionVersion = attention.version;
|
|
225
113
|
_attentionModule = attention;
|
|
226
114
|
attentionAvailable = true;
|
|
227
115
|
return attention;
|
|
228
|
-
} catch {
|
|
116
|
+
} catch (e) {
|
|
229
117
|
_attentionModule = null;
|
|
230
118
|
attentionAvailable = false;
|
|
231
119
|
return null;
|
|
@@ -256,13 +144,11 @@ program
|
|
|
256
144
|
try {
|
|
257
145
|
const dimension = parseInt(options.dimension);
|
|
258
146
|
const db = new VectorDB({
|
|
259
|
-
dimension,
|
|
147
|
+
dimensions: dimension,
|
|
260
148
|
metric: options.metric,
|
|
261
|
-
|
|
262
|
-
autoPersist: true
|
|
149
|
+
storagePath: dbPath,
|
|
263
150
|
});
|
|
264
151
|
|
|
265
|
-
db.save(dbPath);
|
|
266
152
|
spinner.succeed(chalk.green(`Database created: ${dbPath}`));
|
|
267
153
|
console.log(chalk.gray(` Dimension: ${dimension}`));
|
|
268
154
|
console.log(chalk.gray(` Metric: ${options.metric}`));
|
|
@@ -434,7 +320,7 @@ program
|
|
|
434
320
|
let spinner = ora('Creating database...').start();
|
|
435
321
|
|
|
436
322
|
try {
|
|
437
|
-
const db = new VectorDB({ dimension, metric: 'cosine' });
|
|
323
|
+
const db = new VectorDB({ dimensions: dimension, metric: 'cosine' });
|
|
438
324
|
spinner.succeed();
|
|
439
325
|
|
|
440
326
|
// Insert benchmark
|
|
@@ -478,10 +364,9 @@ program
|
|
|
478
364
|
console.log(chalk.gray(` Avg Latency: ${chalk.yellow(avgLatency)}ms`));
|
|
479
365
|
|
|
480
366
|
// Stats
|
|
481
|
-
const stats = db.stats();
|
|
482
367
|
console.log(chalk.cyan('\nFinal Stats:'));
|
|
483
|
-
console.log(chalk.white(` Vector Count: ${chalk.yellow(
|
|
484
|
-
console.log(chalk.white(` Dimension: ${chalk.yellow(
|
|
368
|
+
console.log(chalk.white(` Vector Count: ${chalk.yellow(numVectors)}`));
|
|
369
|
+
console.log(chalk.white(` Dimension: ${chalk.yellow(dimension)}`));
|
|
485
370
|
console.log(chalk.white(` Implementation: ${chalk.yellow(getImplementationType())}`));
|
|
486
371
|
|
|
487
372
|
} catch (error) {
|
|
@@ -496,13 +381,16 @@ program
|
|
|
496
381
|
.command('info')
|
|
497
382
|
.description('Show ruvector information')
|
|
498
383
|
.action(() => {
|
|
384
|
+
// Trigger lazy load of optional modules for availability check
|
|
385
|
+
loadGnn();
|
|
386
|
+
loadAttention();
|
|
387
|
+
|
|
499
388
|
console.log(chalk.cyan('\nruvector Information'));
|
|
500
389
|
console.log(chalk.white(` CLI Version: ${chalk.yellow(packageJson.version)}`));
|
|
501
390
|
|
|
502
391
|
// Try to load ruvector for implementation info
|
|
503
392
|
if (loadRuvector()) {
|
|
504
|
-
const
|
|
505
|
-
const version = versionInfo && versionInfo.version ? versionInfo.version : 'unknown';
|
|
393
|
+
const version = typeof getVersion === 'function' ? getVersion() : 'unknown';
|
|
506
394
|
const impl = typeof getImplementationType === 'function' ? getImplementationType() : 'native';
|
|
507
395
|
console.log(chalk.white(` Core Version: ${chalk.yellow(version)}`));
|
|
508
396
|
console.log(chalk.white(` Implementation: ${chalk.yellow(impl)}`));
|
|
@@ -510,7 +398,6 @@ program
|
|
|
510
398
|
console.log(chalk.white(` Core: ${chalk.gray('Not loaded (install @ruvector/core)')}`));
|
|
511
399
|
}
|
|
512
400
|
|
|
513
|
-
loadGnn();
|
|
514
401
|
console.log(chalk.white(` GNN Module: ${gnnAvailable ? chalk.green('Available') : chalk.gray('Not installed')}`));
|
|
515
402
|
console.log(chalk.white(` Node Version: ${chalk.yellow(process.version)}`));
|
|
516
403
|
console.log(chalk.white(` Platform: ${chalk.yellow(process.platform)}`));
|
|
@@ -534,8 +421,10 @@ program
|
|
|
534
421
|
.action(async (packages, options) => {
|
|
535
422
|
const { execSync } = require('child_process');
|
|
536
423
|
|
|
537
|
-
//
|
|
424
|
+
// Trigger lazy load to check availability
|
|
538
425
|
loadGnn();
|
|
426
|
+
|
|
427
|
+
// Available optional packages - all ruvector npm packages
|
|
539
428
|
const availablePackages = {
|
|
540
429
|
// Core packages
|
|
541
430
|
core: {
|
|
@@ -822,7 +711,7 @@ program
|
|
|
822
711
|
// GNN Commands
|
|
823
712
|
// =============================================================================
|
|
824
713
|
|
|
825
|
-
// Helper to check GNN availability
|
|
714
|
+
// Helper to check GNN availability (triggers lazy load)
|
|
826
715
|
function requireGnn() {
|
|
827
716
|
loadGnn();
|
|
828
717
|
if (!gnnAvailable) {
|
|
@@ -1050,7 +939,7 @@ gnnCmd
|
|
|
1050
939
|
// Attention Commands
|
|
1051
940
|
// =============================================================================
|
|
1052
941
|
|
|
1053
|
-
// Helper to require attention module
|
|
942
|
+
// Helper to require attention module (triggers lazy load)
|
|
1054
943
|
function requireAttention() {
|
|
1055
944
|
loadAttention();
|
|
1056
945
|
if (!attentionAvailable) {
|
|
@@ -1483,6 +1372,10 @@ program
|
|
|
1483
1372
|
.action(async (options) => {
|
|
1484
1373
|
const { execSync } = require('child_process');
|
|
1485
1374
|
|
|
1375
|
+
// Trigger lazy load of optional modules for availability check
|
|
1376
|
+
loadGnn();
|
|
1377
|
+
loadAttention();
|
|
1378
|
+
|
|
1486
1379
|
console.log(chalk.cyan('\n═══════════════════════════════════════════════════════════════'));
|
|
1487
1380
|
console.log(chalk.cyan(' RuVector Doctor'));
|
|
1488
1381
|
console.log(chalk.cyan('═══════════════════════════════════════════════════════════════\n'));
|
|
@@ -1569,7 +1462,6 @@ program
|
|
|
1569
1462
|
}
|
|
1570
1463
|
|
|
1571
1464
|
// Check @ruvector/gnn
|
|
1572
|
-
loadGnn();
|
|
1573
1465
|
if (gnnAvailable) {
|
|
1574
1466
|
console.log(chalk.green(` ✓ @ruvector/gnn installed`));
|
|
1575
1467
|
} else {
|
|
@@ -1577,7 +1469,6 @@ program
|
|
|
1577
1469
|
}
|
|
1578
1470
|
|
|
1579
1471
|
// Check @ruvector/attention
|
|
1580
|
-
loadAttention();
|
|
1581
1472
|
if (attentionAvailable) {
|
|
1582
1473
|
console.log(chalk.green(` ✓ @ruvector/attention installed`));
|
|
1583
1474
|
} else {
|
|
@@ -2643,7 +2534,7 @@ program
|
|
|
2643
2534
|
const spinner = ora('Creating demo database...').start();
|
|
2644
2535
|
|
|
2645
2536
|
try {
|
|
2646
|
-
const db = new VectorDB({
|
|
2537
|
+
const db = new VectorDB({ dimensions: 4, metric: 'cosine' });
|
|
2647
2538
|
|
|
2648
2539
|
spinner.text = 'Inserting vectors...';
|
|
2649
2540
|
db.insert('vec1', [1.0, 0.0, 0.0, 0.0], { label: 'x-axis' });
|
|
@@ -3551,29 +3442,15 @@ hooksCmd.command('init')
|
|
|
3551
3442
|
}
|
|
3552
3443
|
|
|
3553
3444
|
// MCP server configuration (unless --minimal or --no-mcp)
|
|
3554
|
-
// Note: We only use enabledMcpjsonServers (not mcpServers) to avoid
|
|
3555
|
-
// Claude Code regenerating .mcp.json and stripping user-added fields
|
|
3556
|
-
// like autoStart. See: https://github.com/ruvnet/RuVector/issues/250
|
|
3557
3445
|
if (!opts.minimal && opts.mcp !== false) {
|
|
3558
|
-
|
|
3559
|
-
//
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
}
|
|
3565
|
-
// Only add to enabledMcpjsonServers if the server is defined in .mcp.json
|
|
3566
|
-
if (mcpJson.mcpServers?.['claude-flow'] || mcpJson.mcpServers?.['ruvector']) {
|
|
3567
|
-
const serverName = mcpJson.mcpServers['ruvector'] ? 'ruvector' : 'claude-flow';
|
|
3568
|
-
if (!settings.enabledMcpjsonServers?.includes(serverName)) {
|
|
3569
|
-
settings.enabledMcpjsonServers = settings.enabledMcpjsonServers || [];
|
|
3570
|
-
settings.enabledMcpjsonServers.push(serverName);
|
|
3446
|
+
settings.mcpServers = settings.mcpServers || {};
|
|
3447
|
+
// Only add if not already configured
|
|
3448
|
+
if (!settings.mcpServers['claude-flow'] && !settings.enabledMcpjsonServers?.includes('claude-flow')) {
|
|
3449
|
+
settings.enabledMcpjsonServers = settings.enabledMcpjsonServers || [];
|
|
3450
|
+
if (!settings.enabledMcpjsonServers.includes('claude-flow')) {
|
|
3451
|
+
settings.enabledMcpjsonServers.push('claude-flow');
|
|
3571
3452
|
}
|
|
3572
3453
|
}
|
|
3573
|
-
// Remove stale mcpServers: {} that could trigger .mcp.json regeneration
|
|
3574
|
-
if (settings.mcpServers && Object.keys(settings.mcpServers).length === 0) {
|
|
3575
|
-
delete settings.mcpServers;
|
|
3576
|
-
}
|
|
3577
3454
|
console.log(chalk.blue(' ✓ MCP servers configured'));
|
|
3578
3455
|
}
|
|
3579
3456
|
|
|
@@ -4193,7 +4070,7 @@ hooksCmd.command('suggest-context').description('Suggest relevant context').acti
|
|
|
4193
4070
|
console.log(`RuVector Intelligence: ${stats.total_patterns} learned patterns, ${stats.total_errors} error fixes available. Use 'ruvector hooks route' for agent suggestions.`);
|
|
4194
4071
|
});
|
|
4195
4072
|
|
|
4196
|
-
hooksCmd.command('remember').description('Store in memory').
|
|
4073
|
+
hooksCmd.command('remember').description('Store in memory').requiredOption('-t, --type <type>', 'Memory type').option('--silent', 'Suppress output').option('--semantic', 'Use ONNX semantic embeddings (slower, better quality)').argument('<content...>', 'Content').action(async (content, opts) => {
|
|
4197
4074
|
const intel = new Intelligence();
|
|
4198
4075
|
let id;
|
|
4199
4076
|
if (opts.semantic) {
|
|
@@ -5783,16 +5660,6 @@ hooksCmd.command('doctor')
|
|
|
5783
5660
|
};
|
|
5784
5661
|
Object.values(settings.hooks || {}).forEach(checkCommands);
|
|
5785
5662
|
|
|
5786
|
-
// Remove empty mcpServers that can cause .mcp.json overwrites (#250)
|
|
5787
|
-
if (settings.mcpServers && Object.keys(settings.mcpServers).length === 0) {
|
|
5788
|
-
if (opts.fix) {
|
|
5789
|
-
delete settings.mcpServers;
|
|
5790
|
-
fixes.push('Removed empty mcpServers to prevent .mcp.json overwrites');
|
|
5791
|
-
} else {
|
|
5792
|
-
issues.push({ severity: 'warning', message: 'Empty mcpServers in settings.json can cause .mcp.json overwrites', fix: 'Will remove empty mcpServers' });
|
|
5793
|
-
}
|
|
5794
|
-
}
|
|
5795
|
-
|
|
5796
5663
|
// Save fixes
|
|
5797
5664
|
if (opts.fix && fixes.length > 0) {
|
|
5798
5665
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
@@ -7297,356 +7164,165 @@ rvfCmd.command('export <path>')
|
|
|
7297
7164
|
} catch (e) { console.error(chalk.red(e.message)); process.exit(1); }
|
|
7298
7165
|
});
|
|
7299
7166
|
|
|
7300
|
-
// RVF example
|
|
7301
|
-
const
|
|
7302
|
-
|
|
7303
|
-
{ name: '
|
|
7304
|
-
{ name: '
|
|
7305
|
-
{ name: '
|
|
7306
|
-
{ name: '
|
|
7307
|
-
{ name: '
|
|
7308
|
-
{ name: '
|
|
7309
|
-
{ name: '
|
|
7310
|
-
{ name: '
|
|
7311
|
-
{ name: '
|
|
7312
|
-
{ name: '
|
|
7313
|
-
{ name: '
|
|
7314
|
-
{ name: 'mcp_in_rvf',
|
|
7167
|
+
// RVF example download/list commands
|
|
7168
|
+
const RVF_EXAMPLES = [
|
|
7169
|
+
{ name: 'basic_store', size: '152 KB', desc: '1,000 vectors, dim 128, cosine metric' },
|
|
7170
|
+
{ name: 'semantic_search', size: '755 KB', desc: 'Semantic search with HNSW index' },
|
|
7171
|
+
{ name: 'rag_pipeline', size: '303 KB', desc: 'RAG pipeline with embeddings' },
|
|
7172
|
+
{ name: 'embedding_cache', size: '755 KB', desc: 'Cached embedding store' },
|
|
7173
|
+
{ name: 'quantization', size: '1.5 MB', desc: 'PQ-compressed vectors' },
|
|
7174
|
+
{ name: 'progressive_index', size: '2.5 MB', desc: 'Large-scale progressive HNSW index' },
|
|
7175
|
+
{ name: 'filtered_search', size: '255 KB', desc: 'Metadata-filtered vector search' },
|
|
7176
|
+
{ name: 'recommendation', size: '102 KB', desc: 'Recommendation engine vectors' },
|
|
7177
|
+
{ name: 'agent_memory', size: '32 KB', desc: 'AI agent episodic memory' },
|
|
7178
|
+
{ name: 'swarm_knowledge', size: '86 KB', desc: 'Multi-agent shared knowledge base' },
|
|
7179
|
+
{ name: 'experience_replay', size: '27 KB', desc: 'RL experience replay buffer' },
|
|
7180
|
+
{ name: 'tool_cache', size: '26 KB', desc: 'MCP tool call cache' },
|
|
7181
|
+
{ name: 'mcp_in_rvf', size: '32 KB', desc: 'MCP server embedded in RVF' },
|
|
7182
|
+
{ name: 'ruvbot', size: '51 KB', desc: 'Chatbot knowledge store' },
|
|
7183
|
+
{ name: 'claude_code_appliance', size: '17 KB', desc: 'Claude Code cognitive appliance' },
|
|
7184
|
+
{ name: 'lineage_parent', size: '52 KB', desc: 'COW parent file' },
|
|
7185
|
+
{ name: 'lineage_child', size: '26 KB', desc: 'COW child (derived) file' },
|
|
7186
|
+
{ name: 'self_booting', size: '31 KB', desc: 'Self-booting with KERNEL_SEG' },
|
|
7187
|
+
{ name: 'linux_microkernel', size: '15 KB', desc: 'Embedded Linux microkernel' },
|
|
7188
|
+
{ name: 'ebpf_accelerator', size: '153 KB', desc: 'eBPF distance accelerator' },
|
|
7189
|
+
{ name: 'browser_wasm', size: '14 KB', desc: 'Browser WASM module embedded' },
|
|
7190
|
+
{ name: 'tee_attestation', size: '102 KB', desc: 'TEE attestation with witnesses' },
|
|
7191
|
+
{ name: 'zero_knowledge', size: '52 KB', desc: 'ZK-proof witness chain' },
|
|
7192
|
+
{ name: 'sealed_engine', size: '208 KB', desc: 'Sealed inference engine' },
|
|
7193
|
+
{ name: 'access_control', size: '77 KB', desc: 'Permission-gated vectors' },
|
|
7194
|
+
{ name: 'financial_signals', size: '202 KB', desc: 'Financial signal vectors' },
|
|
7195
|
+
{ name: 'medical_imaging', size: '302 KB', desc: 'Medical imaging embeddings' },
|
|
7196
|
+
{ name: 'legal_discovery', size: '903 KB', desc: 'Legal document discovery' },
|
|
7197
|
+
{ name: 'multimodal_fusion', size: '804 KB', desc: 'Multi-modal embedding fusion' },
|
|
7198
|
+
{ name: 'hyperbolic_taxonomy', size: '23 KB', desc: 'Hyperbolic space taxonomy' },
|
|
7199
|
+
{ name: 'network_telemetry', size: '16 KB', desc: 'Network telemetry vectors' },
|
|
7200
|
+
{ name: 'postgres_bridge', size: '152 KB', desc: 'PostgreSQL bridge vectors' },
|
|
7201
|
+
{ name: 'ruvllm_inference', size: '133 KB', desc: 'RuvLLM inference cache' },
|
|
7202
|
+
{ name: 'serverless', size: '509 KB', desc: 'Serverless deployment bundle' },
|
|
7203
|
+
{ name: 'edge_iot', size: '27 KB', desc: 'Edge/IoT lightweight store' },
|
|
7204
|
+
{ name: 'dedup_detector', size: '153 KB', desc: 'Deduplication detector' },
|
|
7205
|
+
{ name: 'compacted', size: '77 KB', desc: 'Post-compaction example' },
|
|
7206
|
+
{ name: 'posix_fileops', size: '52 KB', desc: 'POSIX file operations test' },
|
|
7207
|
+
{ name: 'network_sync_a', size: '52 KB', desc: 'Network sync peer A' },
|
|
7208
|
+
{ name: 'network_sync_b', size: '52 KB', desc: 'Network sync peer B' },
|
|
7209
|
+
{ name: 'agent_handoff_a', size: '31 KB', desc: 'Agent handoff source' },
|
|
7210
|
+
{ name: 'agent_handoff_b', size: '11 KB', desc: 'Agent handoff target' },
|
|
7211
|
+
{ name: 'reasoning_parent', size: '5.6 KB', desc: 'Reasoning chain parent' },
|
|
7212
|
+
{ name: 'reasoning_child', size: '8.1 KB', desc: 'Reasoning chain child' },
|
|
7213
|
+
{ name: 'reasoning_grandchild', size: '162 B', desc: 'Minimal derived file' },
|
|
7315
7214
|
];
|
|
7316
7215
|
|
|
7317
|
-
const
|
|
7318
|
-
const GITHUB_RAW_BASE = 'https://raw.githubusercontent.com/ruvnet/ruvector/main/examples/rvf/output';
|
|
7319
|
-
|
|
7320
|
-
function getRvfCacheDir() {
|
|
7321
|
-
const os = require('os');
|
|
7322
|
-
return path.join(os.homedir(), '.ruvector', 'examples');
|
|
7323
|
-
}
|
|
7324
|
-
|
|
7325
|
-
async function getRvfManifest(opts = {}) {
|
|
7326
|
-
const cacheDir = getRvfCacheDir();
|
|
7327
|
-
const manifestPath = path.join(cacheDir, 'manifest.json');
|
|
7328
|
-
|
|
7329
|
-
// Check cache (1 hour TTL)
|
|
7330
|
-
if (!opts.refresh && fs.existsSync(manifestPath)) {
|
|
7331
|
-
try {
|
|
7332
|
-
const stat = fs.statSync(manifestPath);
|
|
7333
|
-
const age = Date.now() - stat.mtimeMs;
|
|
7334
|
-
if (age < 3600000) {
|
|
7335
|
-
return JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
7336
|
-
}
|
|
7337
|
-
} catch {}
|
|
7338
|
-
}
|
|
7339
|
-
|
|
7340
|
-
if (opts.offline) {
|
|
7341
|
-
// Offline mode - use cache even if stale
|
|
7342
|
-
if (fs.existsSync(manifestPath)) {
|
|
7343
|
-
return JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
7344
|
-
}
|
|
7345
|
-
return { examples: BUILTIN_RVF_CATALOG, base_url: GITHUB_RAW_BASE, version: 'builtin', offline: true };
|
|
7346
|
-
}
|
|
7347
|
-
|
|
7348
|
-
// Try GCS
|
|
7349
|
-
try {
|
|
7350
|
-
const resp = await proxyFetch(GCS_MANIFEST_URL, { signal: AbortSignal.timeout(15000) });
|
|
7351
|
-
if (resp.ok) {
|
|
7352
|
-
const manifest = await resp.json();
|
|
7353
|
-
fs.mkdirSync(cacheDir, { recursive: true });
|
|
7354
|
-
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
|
|
7355
|
-
return manifest;
|
|
7356
|
-
}
|
|
7357
|
-
} catch {}
|
|
7358
|
-
|
|
7359
|
-
// Fallback: stale cache
|
|
7360
|
-
if (fs.existsSync(manifestPath)) {
|
|
7361
|
-
try {
|
|
7362
|
-
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
7363
|
-
manifest._stale = true;
|
|
7364
|
-
return manifest;
|
|
7365
|
-
} catch {}
|
|
7366
|
-
}
|
|
7367
|
-
|
|
7368
|
-
// Final fallback: builtin catalog with GitHub URLs
|
|
7369
|
-
return { examples: BUILTIN_RVF_CATALOG, base_url: GITHUB_RAW_BASE, version: 'builtin' };
|
|
7370
|
-
}
|
|
7371
|
-
|
|
7372
|
-
function verifyRvfFile(filePath, expectedSha256) {
|
|
7373
|
-
if (!expectedSha256) return { verified: false, reason: 'No checksum available' };
|
|
7374
|
-
const crypto = require('crypto');
|
|
7375
|
-
const hash = crypto.createHash('sha256');
|
|
7376
|
-
const data = fs.readFileSync(filePath);
|
|
7377
|
-
hash.update(data);
|
|
7378
|
-
const actual = hash.digest('hex');
|
|
7379
|
-
return { verified: actual === expectedSha256, actual, expected: expectedSha256 };
|
|
7380
|
-
}
|
|
7216
|
+
const RVF_BASE_URL = 'https://raw.githubusercontent.com/ruvnet/ruvector/main/examples/rvf/output';
|
|
7381
7217
|
|
|
7382
7218
|
rvfCmd.command('examples')
|
|
7383
|
-
.description('List available example .rvf files
|
|
7384
|
-
.option('--category <cat>', 'Filter by category (core, ai, security, compute, lineage, industry, network, integration)')
|
|
7385
|
-
.option('--refresh', 'Force refresh manifest from server')
|
|
7386
|
-
.option('--offline', 'Use only cached data')
|
|
7219
|
+
.description('List available example .rvf files')
|
|
7387
7220
|
.option('--json', 'Output as JSON')
|
|
7388
|
-
.action(
|
|
7389
|
-
const manifest = await getRvfManifest({ refresh: opts.refresh, offline: opts.offline });
|
|
7390
|
-
let examples = manifest.examples || [];
|
|
7391
|
-
|
|
7392
|
-
if (opts.category) {
|
|
7393
|
-
examples = examples.filter(e => e.category === opts.category);
|
|
7394
|
-
}
|
|
7395
|
-
|
|
7221
|
+
.action((opts) => {
|
|
7396
7222
|
if (opts.json) {
|
|
7397
|
-
console.log(JSON.stringify(
|
|
7223
|
+
console.log(JSON.stringify(RVF_EXAMPLES, null, 2));
|
|
7398
7224
|
return;
|
|
7399
7225
|
}
|
|
7400
|
-
|
|
7401
|
-
console.log(chalk.
|
|
7402
|
-
|
|
7403
|
-
|
|
7404
|
-
|
|
7405
|
-
|
|
7406
|
-
|
|
7407
|
-
|
|
7408
|
-
const grouped = {};
|
|
7409
|
-
for (const ex of examples) {
|
|
7410
|
-
const cat = ex.category || 'other';
|
|
7411
|
-
if (!grouped[cat]) grouped[cat] = [];
|
|
7412
|
-
grouped[cat].push(ex);
|
|
7413
|
-
}
|
|
7414
|
-
|
|
7415
|
-
for (const [cat, items] of Object.entries(grouped).sort()) {
|
|
7416
|
-
const catDesc = manifest.categories ? manifest.categories[cat] || '' : '';
|
|
7417
|
-
console.log(chalk.bold.yellow(` ${cat} ${catDesc ? chalk.dim(`-- ${catDesc}`) : ''}`));
|
|
7418
|
-
for (const ex of items) {
|
|
7419
|
-
const name = chalk.green(ex.name.padEnd(28));
|
|
7420
|
-
const size = chalk.yellow((ex.size_human || '').padStart(8));
|
|
7421
|
-
console.log(` ${name} ${size} ${chalk.dim(ex.description || '')}`);
|
|
7422
|
-
}
|
|
7423
|
-
console.log();
|
|
7424
|
-
}
|
|
7425
|
-
|
|
7426
|
-
if (manifest.categories && !opts.category) {
|
|
7427
|
-
console.log(chalk.dim(` Categories: ${Object.keys(manifest.categories).join(', ')}\n`));
|
|
7226
|
+
console.log(chalk.bold.cyan('\nAvailable RVF Example Files (45 total)\n'));
|
|
7227
|
+
console.log(chalk.dim(`Download: npx ruvector rvf download <name>\n`));
|
|
7228
|
+
const maxName = Math.max(...RVF_EXAMPLES.map(e => e.name.length));
|
|
7229
|
+
const maxSize = Math.max(...RVF_EXAMPLES.map(e => e.size.length));
|
|
7230
|
+
for (const ex of RVF_EXAMPLES) {
|
|
7231
|
+
const name = chalk.green(ex.name.padEnd(maxName));
|
|
7232
|
+
const size = chalk.yellow(ex.size.padStart(maxSize));
|
|
7233
|
+
console.log(` ${name} ${size} ${chalk.dim(ex.desc)}`);
|
|
7428
7234
|
}
|
|
7235
|
+
console.log(chalk.dim(`\nFull catalog: https://github.com/ruvnet/ruvector/tree/main/examples/rvf/output\n`));
|
|
7429
7236
|
});
|
|
7430
7237
|
|
|
7431
7238
|
rvfCmd.command('download [names...]')
|
|
7432
|
-
.description('Download example .rvf files
|
|
7433
|
-
.option('-a, --all', 'Download all examples')
|
|
7434
|
-
.option('-c, --category <cat>', 'Download all examples in a category')
|
|
7239
|
+
.description('Download example .rvf files from GitHub')
|
|
7240
|
+
.option('-a, --all', 'Download all 45 examples (~11 MB)')
|
|
7435
7241
|
.option('-o, --output <dir>', 'Output directory', '.')
|
|
7436
|
-
.option('--verify', 'Re-verify cached files')
|
|
7437
|
-
.option('--no-cache', 'Skip cache, always download fresh')
|
|
7438
|
-
.option('--offline', 'Use only cached files')
|
|
7439
|
-
.option('--refresh', 'Refresh manifest before download')
|
|
7440
7242
|
.action(async (names, opts) => {
|
|
7441
|
-
const
|
|
7442
|
-
const
|
|
7443
|
-
const
|
|
7243
|
+
const https = require('https');
|
|
7244
|
+
const ALLOWED_REDIRECT_HOSTS = ['raw.githubusercontent.com', 'objects.githubusercontent.com', 'github.com'];
|
|
7245
|
+
const sanitizeFileName = (name) => {
|
|
7246
|
+
// Strip path separators and parent directory references
|
|
7247
|
+
const base = path.basename(name);
|
|
7248
|
+
// Only allow alphanumeric, underscores, hyphens, dots
|
|
7249
|
+
if (!/^[\w\-.]+$/.test(base)) throw new Error(`Invalid filename: ${base}`);
|
|
7250
|
+
return base;
|
|
7251
|
+
};
|
|
7252
|
+
const downloadFile = (url, dest) => new Promise((resolve, reject) => {
|
|
7253
|
+
const file = fs.createWriteStream(dest);
|
|
7254
|
+
https.get(url, (res) => {
|
|
7255
|
+
if (res.statusCode === 302 || res.statusCode === 301) {
|
|
7256
|
+
const redirectUrl = res.headers.location;
|
|
7257
|
+
try {
|
|
7258
|
+
const redirectHost = new URL(redirectUrl).hostname;
|
|
7259
|
+
if (!ALLOWED_REDIRECT_HOSTS.includes(redirectHost)) {
|
|
7260
|
+
file.close();
|
|
7261
|
+
reject(new Error(`Redirect to untrusted host: ${redirectHost}`));
|
|
7262
|
+
return;
|
|
7263
|
+
}
|
|
7264
|
+
} catch { file.close(); reject(new Error('Invalid redirect URL')); return; }
|
|
7265
|
+
https.get(redirectUrl, (res2) => { res2.pipe(file); file.on('finish', () => { file.close(); resolve(); }); }).on('error', reject);
|
|
7266
|
+
return;
|
|
7267
|
+
}
|
|
7268
|
+
if (res.statusCode !== 200) { file.close(); fs.unlinkSync(dest); reject(new Error(`HTTP ${res.statusCode}`)); return; }
|
|
7269
|
+
res.pipe(file);
|
|
7270
|
+
file.on('finish', () => { file.close(); resolve(); });
|
|
7271
|
+
}).on('error', reject);
|
|
7272
|
+
});
|
|
7444
7273
|
|
|
7445
7274
|
let toDownload = [];
|
|
7446
7275
|
if (opts.all) {
|
|
7447
|
-
toDownload =
|
|
7448
|
-
} else if (opts.category) {
|
|
7449
|
-
toDownload = examples.filter(e => e.category === opts.category);
|
|
7450
|
-
if (!toDownload.length) {
|
|
7451
|
-
console.error(chalk.red(`No examples in category '${opts.category}'`));
|
|
7452
|
-
process.exit(1);
|
|
7453
|
-
}
|
|
7276
|
+
toDownload = RVF_EXAMPLES.map(e => e.name);
|
|
7454
7277
|
} else if (names && names.length > 0) {
|
|
7455
|
-
|
|
7456
|
-
const cleanName = name.replace(/\.rvf$/, '');
|
|
7457
|
-
const found = examples.find(e => e.name === cleanName);
|
|
7458
|
-
if (found) {
|
|
7459
|
-
toDownload.push(found);
|
|
7460
|
-
} else {
|
|
7461
|
-
console.error(chalk.red(`Unknown example: ${cleanName}. Run 'npx ruvector rvf examples' to list.`));
|
|
7462
|
-
}
|
|
7463
|
-
}
|
|
7464
|
-
if (!toDownload.length) process.exit(1);
|
|
7278
|
+
toDownload = names;
|
|
7465
7279
|
} else {
|
|
7466
|
-
console.error(chalk.red('Specify example names
|
|
7280
|
+
console.error(chalk.red('Specify example names or use --all. Run `npx ruvector rvf examples` to list.'));
|
|
7467
7281
|
process.exit(1);
|
|
7468
7282
|
}
|
|
7469
7283
|
|
|
7470
7284
|
const outDir = path.resolve(opts.output);
|
|
7471
7285
|
if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
|
|
7472
|
-
const cacheDir = getRvfCacheDir();
|
|
7473
|
-
fs.mkdirSync(cacheDir, { recursive: true });
|
|
7474
7286
|
|
|
7475
7287
|
console.log(chalk.bold.cyan(`\nDownloading ${toDownload.length} .rvf file(s) to ${outDir}\n`));
|
|
7476
|
-
|
|
7477
|
-
const
|
|
7478
|
-
|
|
7479
|
-
|
|
7480
|
-
|
|
7481
|
-
|
|
7482
|
-
const doGet = (getUrl) => {
|
|
7483
|
-
const mod = getUrl.startsWith('https') ? https : require('http');
|
|
7484
|
-
mod.get(getUrl, (res) => {
|
|
7485
|
-
if (res.statusCode === 301 || res.statusCode === 302) {
|
|
7486
|
-
const loc = res.headers.location;
|
|
7487
|
-
try {
|
|
7488
|
-
const host = new URL(loc).hostname;
|
|
7489
|
-
if (!ALLOWED_REDIRECT_HOSTS.includes(host)) {
|
|
7490
|
-
reject(new Error(`Redirect to untrusted host: ${host}`));
|
|
7491
|
-
return;
|
|
7492
|
-
}
|
|
7493
|
-
} catch { reject(new Error('Invalid redirect URL')); return; }
|
|
7494
|
-
doGet(loc);
|
|
7495
|
-
return;
|
|
7496
|
-
}
|
|
7497
|
-
if (res.statusCode !== 200) {
|
|
7498
|
-
reject(new Error(`HTTP ${res.statusCode}`));
|
|
7499
|
-
return;
|
|
7500
|
-
}
|
|
7501
|
-
const file = fs.createWriteStream(dest);
|
|
7502
|
-
res.pipe(file);
|
|
7503
|
-
file.on('finish', () => { file.close(); resolve(); });
|
|
7504
|
-
file.on('error', reject);
|
|
7505
|
-
}).on('error', reject);
|
|
7506
|
-
};
|
|
7507
|
-
doGet(url);
|
|
7508
|
-
});
|
|
7509
|
-
|
|
7510
|
-
let ok = 0, cached = 0, fail = 0, verified = 0;
|
|
7511
|
-
|
|
7512
|
-
for (const ex of toDownload) {
|
|
7513
|
-
const fileName = `${ex.name}.rvf`;
|
|
7514
|
-
// Sanitize filename
|
|
7515
|
-
if (!/^[\w\-.]+$/.test(fileName)) {
|
|
7516
|
-
console.log(` ${chalk.red('SKIP')} ${fileName} (invalid filename)`);
|
|
7517
|
-
fail++;
|
|
7518
|
-
continue;
|
|
7519
|
-
}
|
|
7520
|
-
|
|
7521
|
-
const destPath = path.join(outDir, fileName);
|
|
7522
|
-
const cachePath = path.join(cacheDir, fileName);
|
|
7523
|
-
|
|
7524
|
-
// Path containment check
|
|
7525
|
-
if (!path.resolve(destPath).startsWith(path.resolve(outDir))) {
|
|
7526
|
-
console.log(` ${chalk.red('SKIP')} ${fileName} (path traversal)`);
|
|
7288
|
+
let ok = 0, fail = 0;
|
|
7289
|
+
for (const name of toDownload) {
|
|
7290
|
+
const rawName = name.endsWith('.rvf') ? name : `${name}.rvf`;
|
|
7291
|
+
let fileName;
|
|
7292
|
+
try { fileName = sanitizeFileName(rawName); } catch (e) {
|
|
7293
|
+
console.log(chalk.red(`SKIPPED: ${e.message}`));
|
|
7527
7294
|
fail++;
|
|
7528
7295
|
continue;
|
|
7529
7296
|
}
|
|
7530
|
-
|
|
7531
|
-
|
|
7532
|
-
|
|
7533
|
-
|
|
7534
|
-
|
|
7535
|
-
|
|
7536
|
-
if (check.verified) {
|
|
7537
|
-
// Copy from cache
|
|
7538
|
-
if (path.resolve(destPath) !== path.resolve(cachePath)) {
|
|
7539
|
-
fs.copyFileSync(cachePath, destPath);
|
|
7540
|
-
}
|
|
7541
|
-
console.log(` ${chalk.green('CACHED')} ${chalk.cyan(fileName)} ${chalk.dim(ex.size_human || '')}`);
|
|
7542
|
-
cached++;
|
|
7543
|
-
continue;
|
|
7544
|
-
} else {
|
|
7545
|
-
// Cache corrupted, re-download
|
|
7546
|
-
console.log(` ${chalk.yellow('STALE')} ${fileName} -- re-downloading`);
|
|
7547
|
-
}
|
|
7548
|
-
} else {
|
|
7549
|
-
// Copy from cache (no checksum to verify)
|
|
7550
|
-
if (path.resolve(destPath) !== path.resolve(cachePath)) {
|
|
7551
|
-
fs.copyFileSync(cachePath, destPath);
|
|
7552
|
-
}
|
|
7553
|
-
console.log(` ${chalk.green('CACHED')} ${chalk.cyan(fileName)} ${chalk.dim(ex.size_human || '')}`);
|
|
7554
|
-
cached++;
|
|
7297
|
+
// Validate against known examples when not using --all
|
|
7298
|
+
if (!opts.all) {
|
|
7299
|
+
const baseName = fileName.replace(/\.rvf$/, '');
|
|
7300
|
+
if (!RVF_EXAMPLES.some(e => e.name === baseName)) {
|
|
7301
|
+
console.log(chalk.red(`SKIPPED: Unknown example '${baseName}'. Run 'npx ruvector rvf examples' to list.`));
|
|
7302
|
+
fail++;
|
|
7555
7303
|
continue;
|
|
7556
7304
|
}
|
|
7557
7305
|
}
|
|
7558
|
-
|
|
7559
|
-
|
|
7560
|
-
|
|
7306
|
+
const url = `${RVF_BASE_URL}/${encodeURIComponent(fileName)}`;
|
|
7307
|
+
const dest = path.join(outDir, fileName);
|
|
7308
|
+
// Path containment check
|
|
7309
|
+
if (!path.resolve(dest).startsWith(path.resolve(outDir) + path.sep) && path.resolve(dest) !== path.resolve(outDir)) {
|
|
7310
|
+
console.log(chalk.red(`SKIPPED: Path traversal detected for '${fileName}'`));
|
|
7561
7311
|
fail++;
|
|
7562
7312
|
continue;
|
|
7563
7313
|
}
|
|
7564
|
-
|
|
7565
|
-
// Download
|
|
7566
|
-
const url = `${baseUrl}/${encodeURIComponent(fileName)}`;
|
|
7567
7314
|
try {
|
|
7568
|
-
|
|
7569
|
-
|
|
7570
|
-
|
|
7571
|
-
|
|
7572
|
-
const check = verifyRvfFile(cachePath, ex.sha256);
|
|
7573
|
-
if (check.verified) {
|
|
7574
|
-
verified++;
|
|
7575
|
-
console.log(` ${chalk.green('OK')} ${chalk.cyan(fileName)} ${chalk.dim(ex.size_human || '')} ${chalk.green('SHA-256 verified')}`);
|
|
7576
|
-
} else {
|
|
7577
|
-
console.log(` ${chalk.red('FAIL')} ${fileName} -- SHA-256 mismatch! Expected ${ex.sha256.slice(0, 12)}... got ${check.actual.slice(0, 12)}...`);
|
|
7578
|
-
fs.unlinkSync(cachePath);
|
|
7579
|
-
fail++;
|
|
7580
|
-
continue;
|
|
7581
|
-
}
|
|
7582
|
-
} else {
|
|
7583
|
-
console.log(` ${chalk.green('OK')} ${chalk.cyan(fileName)} ${chalk.dim(ex.size_human || '')} ${chalk.yellow('(no checksum)')}`);
|
|
7584
|
-
}
|
|
7585
|
-
|
|
7586
|
-
// Copy to output dir if different from cache
|
|
7587
|
-
if (path.resolve(destPath) !== path.resolve(cachePath)) {
|
|
7588
|
-
fs.copyFileSync(cachePath, destPath);
|
|
7589
|
-
}
|
|
7315
|
+
process.stdout.write(chalk.dim(` ${fileName} ... `));
|
|
7316
|
+
await downloadFile(url, dest);
|
|
7317
|
+
const stat = fs.statSync(dest);
|
|
7318
|
+
console.log(chalk.green(`OK (${(stat.size / 1024).toFixed(0)} KB)`));
|
|
7590
7319
|
ok++;
|
|
7591
7320
|
} catch (e) {
|
|
7592
|
-
console.log(
|
|
7321
|
+
console.log(chalk.red(`FAILED: ${e.message}`));
|
|
7593
7322
|
fail++;
|
|
7594
7323
|
}
|
|
7595
7324
|
}
|
|
7596
|
-
|
|
7597
|
-
console.log(chalk.bold(`\n Downloaded: ${ok}, Cached: ${cached}, Failed: ${fail}${verified ? `, Verified: ${verified}` : ''}\n`));
|
|
7598
|
-
});
|
|
7599
|
-
|
|
7600
|
-
// RVF cache management
|
|
7601
|
-
rvfCmd.command('cache <action>')
|
|
7602
|
-
.description('Manage local .rvf example cache (status, clear)')
|
|
7603
|
-
.action((action) => {
|
|
7604
|
-
const cacheDir = getRvfCacheDir();
|
|
7605
|
-
|
|
7606
|
-
switch (action) {
|
|
7607
|
-
case 'status': {
|
|
7608
|
-
if (!fs.existsSync(cacheDir)) {
|
|
7609
|
-
console.log(chalk.dim('\n No cache directory found.\n'));
|
|
7610
|
-
return;
|
|
7611
|
-
}
|
|
7612
|
-
const files = fs.readdirSync(cacheDir).filter(f => f.endsWith('.rvf'));
|
|
7613
|
-
const manifestExists = fs.existsSync(path.join(cacheDir, 'manifest.json'));
|
|
7614
|
-
let totalSize = 0;
|
|
7615
|
-
for (const f of files) {
|
|
7616
|
-
totalSize += fs.statSync(path.join(cacheDir, f)).size;
|
|
7617
|
-
}
|
|
7618
|
-
console.log(chalk.bold.cyan('\nRVF Cache Status\n'));
|
|
7619
|
-
console.log(` ${chalk.bold('Location:')} ${cacheDir}`);
|
|
7620
|
-
console.log(` ${chalk.bold('Files:')} ${files.length} .rvf files`);
|
|
7621
|
-
console.log(` ${chalk.bold('Size:')} ${(totalSize / (1024 * 1024)).toFixed(1)} MB`);
|
|
7622
|
-
console.log(` ${chalk.bold('Manifest:')} ${manifestExists ? chalk.green('cached') : chalk.dim('not cached')}`);
|
|
7623
|
-
if (manifestExists) {
|
|
7624
|
-
const stat = fs.statSync(path.join(cacheDir, 'manifest.json'));
|
|
7625
|
-
const age = Date.now() - stat.mtimeMs;
|
|
7626
|
-
const fresh = age < 3600000;
|
|
7627
|
-
console.log(` ${chalk.bold('Age:')} ${Math.floor(age / 60000)} min ${fresh ? chalk.green('(fresh)') : chalk.yellow('(stale)')}`);
|
|
7628
|
-
}
|
|
7629
|
-
console.log();
|
|
7630
|
-
break;
|
|
7631
|
-
}
|
|
7632
|
-
case 'clear': {
|
|
7633
|
-
if (!fs.existsSync(cacheDir)) {
|
|
7634
|
-
console.log(chalk.dim('\n No cache to clear.\n'));
|
|
7635
|
-
return;
|
|
7636
|
-
}
|
|
7637
|
-
const files = fs.readdirSync(cacheDir);
|
|
7638
|
-
let cleared = 0;
|
|
7639
|
-
for (const f of files) {
|
|
7640
|
-
fs.unlinkSync(path.join(cacheDir, f));
|
|
7641
|
-
cleared++;
|
|
7642
|
-
}
|
|
7643
|
-
console.log(chalk.green(`\n Cleared ${cleared} cached files from ${cacheDir}\n`));
|
|
7644
|
-
break;
|
|
7645
|
-
}
|
|
7646
|
-
default:
|
|
7647
|
-
console.error(chalk.red(`Unknown cache action: ${action}. Use: status, clear`));
|
|
7648
|
-
process.exit(1);
|
|
7649
|
-
}
|
|
7325
|
+
console.log(chalk.bold(`\nDone: ${ok} downloaded, ${fail} failed\n`));
|
|
7650
7326
|
});
|
|
7651
7327
|
|
|
7652
7328
|
// MCP Server command
|
|
@@ -7654,15 +7330,8 @@ const mcpCmd = program.command('mcp').description('MCP (Model Context Protocol)
|
|
|
7654
7330
|
|
|
7655
7331
|
mcpCmd.command('start')
|
|
7656
7332
|
.description('Start the RuVector MCP server')
|
|
7657
|
-
.
|
|
7658
|
-
|
|
7659
|
-
.option('--host <host>', 'Host to bind for SSE', '0.0.0.0')
|
|
7660
|
-
.action((opts) => {
|
|
7661
|
-
if (opts.transport === 'sse') {
|
|
7662
|
-
process.env.MCP_TRANSPORT = 'sse';
|
|
7663
|
-
process.env.MCP_PORT = opts.port;
|
|
7664
|
-
process.env.MCP_HOST = opts.host;
|
|
7665
|
-
}
|
|
7333
|
+
.action(() => {
|
|
7334
|
+
// Execute the mcp-server.js directly
|
|
7666
7335
|
const mcpServerPath = path.join(__dirname, 'mcp-server.js');
|
|
7667
7336
|
if (!fs.existsSync(mcpServerPath)) {
|
|
7668
7337
|
console.error(chalk.red('Error: MCP server not found at'), mcpServerPath);
|
|
@@ -7707,6 +7376,29 @@ mcpCmd.command('info')
|
|
|
7707
7376
|
console.log(chalk.dim(' rvlite_cypher - Execute Cypher graph query'));
|
|
7708
7377
|
console.log(chalk.dim(' rvlite_sparql - Execute SPARQL RDF query'));
|
|
7709
7378
|
|
|
7379
|
+
console.log(chalk.bold('\nBrain Tools (Shared Intelligence):'));
|
|
7380
|
+
console.log(chalk.dim(' brain_search - Semantic search shared knowledge'));
|
|
7381
|
+
console.log(chalk.dim(' brain_share - Share knowledge with brain'));
|
|
7382
|
+
console.log(chalk.dim(' brain_get - Get memory by ID'));
|
|
7383
|
+
console.log(chalk.dim(' brain_vote - Vote on quality'));
|
|
7384
|
+
console.log(chalk.dim(' brain_list - List memories'));
|
|
7385
|
+
console.log(chalk.dim(' brain_delete - Delete own contribution'));
|
|
7386
|
+
console.log(chalk.dim(' brain_status - System health'));
|
|
7387
|
+
console.log(chalk.dim(' brain_drift - Check knowledge drift'));
|
|
7388
|
+
console.log(chalk.dim(' brain_partition - MinCut knowledge topology'));
|
|
7389
|
+
console.log(chalk.dim(' brain_transfer - Domain transfer'));
|
|
7390
|
+
console.log(chalk.dim(' brain_sync - LoRA weight sync'));
|
|
7391
|
+
|
|
7392
|
+
console.log(chalk.bold('\nEdge Tools (Distributed Compute):'));
|
|
7393
|
+
console.log(chalk.dim(' edge_status - Network status'));
|
|
7394
|
+
console.log(chalk.dim(' edge_join - Join as compute node'));
|
|
7395
|
+
console.log(chalk.dim(' edge_balance - Check rUv balance'));
|
|
7396
|
+
console.log(chalk.dim(' edge_tasks - Available compute tasks'));
|
|
7397
|
+
|
|
7398
|
+
console.log(chalk.bold('\nIdentity Tools:'));
|
|
7399
|
+
console.log(chalk.dim(' identity_generate - Generate new pi key'));
|
|
7400
|
+
console.log(chalk.dim(' identity_show - Show current identity'));
|
|
7401
|
+
|
|
7710
7402
|
console.log(chalk.bold('\n📦 Resources:'));
|
|
7711
7403
|
console.log(chalk.dim(' ruvector://intelligence/stats - Current statistics'));
|
|
7712
7404
|
console.log(chalk.dim(' ruvector://intelligence/patterns - Learned patterns'));
|
|
@@ -7728,166 +7420,163 @@ mcpCmd.command('info')
|
|
|
7728
7420
|
console.log();
|
|
7729
7421
|
});
|
|
7730
7422
|
|
|
7731
|
-
// ============================================================================
|
|
7732
|
-
// MCP tools subcommand
|
|
7733
|
-
// ============================================================================
|
|
7734
|
-
|
|
7735
7423
|
mcpCmd.command('tools')
|
|
7736
|
-
.description('List all MCP tools
|
|
7737
|
-
.option('--group <
|
|
7738
|
-
.option('--json', '
|
|
7424
|
+
.description('List all MCP tools with descriptions (JSON output)')
|
|
7425
|
+
.option('--group <group>', 'Filter by group (hooks, workers, rvf, rvlite, brain, edge, identity)')
|
|
7426
|
+
.option('--json', 'JSON output')
|
|
7739
7427
|
.action((opts) => {
|
|
7740
|
-
const
|
|
7428
|
+
const tools = {
|
|
7741
7429
|
'hooks-core': [
|
|
7742
|
-
{ name: 'hooks_stats',
|
|
7743
|
-
{ name: 'hooks_route',
|
|
7744
|
-
{ name: 'hooks_remember',
|
|
7745
|
-
{ name: 'hooks_recall',
|
|
7746
|
-
{ name: 'hooks_init',
|
|
7747
|
-
{ name: 'hooks_pretrain',
|
|
7748
|
-
{ name: 'hooks_build_agents',
|
|
7749
|
-
{ name: 'hooks_verify',
|
|
7750
|
-
{ name: 'hooks_doctor',
|
|
7751
|
-
{ name: 'hooks_export',
|
|
7430
|
+
{ name: 'hooks_stats', desc: 'Intelligence statistics' },
|
|
7431
|
+
{ name: 'hooks_route', desc: 'Route task to best agent' },
|
|
7432
|
+
{ name: 'hooks_remember', desc: 'Store in vector memory' },
|
|
7433
|
+
{ name: 'hooks_recall', desc: 'Search vector memory' },
|
|
7434
|
+
{ name: 'hooks_init', desc: 'Initialize hooks in project' },
|
|
7435
|
+
{ name: 'hooks_pretrain', desc: 'Pretrain from repository' },
|
|
7436
|
+
{ name: 'hooks_build_agents', desc: 'Generate agent configs' },
|
|
7437
|
+
{ name: 'hooks_verify', desc: 'Verify hooks config' },
|
|
7438
|
+
{ name: 'hooks_doctor', desc: 'Diagnose setup issues' },
|
|
7439
|
+
{ name: 'hooks_export', desc: 'Export intelligence data' },
|
|
7440
|
+
{ name: 'hooks_capabilities', desc: 'Get engine capabilities' },
|
|
7441
|
+
{ name: 'hooks_import', desc: 'Import intelligence data' },
|
|
7442
|
+
{ name: 'hooks_swarm_recommend', desc: 'Recommend agent for task' },
|
|
7443
|
+
{ name: 'hooks_suggest_context', desc: 'Suggest relevant context' },
|
|
7752
7444
|
],
|
|
7753
7445
|
'hooks-trajectory': [
|
|
7754
|
-
{ name: '
|
|
7755
|
-
{ name: 'hooks_trajectory_step',
|
|
7756
|
-
{ name: 'hooks_trajectory_end',
|
|
7446
|
+
{ name: 'hooks_trajectory_begin', desc: 'Begin execution trajectory' },
|
|
7447
|
+
{ name: 'hooks_trajectory_step', desc: 'Add trajectory step' },
|
|
7448
|
+
{ name: 'hooks_trajectory_end', desc: 'End trajectory with score' },
|
|
7757
7449
|
],
|
|
7758
7450
|
'hooks-coedit': [
|
|
7759
|
-
{ name: '
|
|
7760
|
-
{ name: '
|
|
7761
|
-
{ name: 'hooks_pre_command', args: 'command, args?', desc: 'Pre-command analysis' },
|
|
7762
|
-
{ name: 'hooks_post_command', args: 'command, exit_code, output?', desc: 'Post-command learning' },
|
|
7763
|
-
{ name: 'hooks_pre_task', args: 'task, context?', desc: 'Pre-task routing' },
|
|
7764
|
-
{ name: 'hooks_post_task', args: 'task, result, duration?', desc: 'Post-task learning' },
|
|
7451
|
+
{ name: 'hooks_coedit_record', desc: 'Record co-edit pattern' },
|
|
7452
|
+
{ name: 'hooks_coedit_suggest', desc: 'Suggest related files' },
|
|
7765
7453
|
],
|
|
7766
7454
|
'hooks-errors': [
|
|
7767
|
-
{ name: '
|
|
7768
|
-
{ name: '
|
|
7769
|
-
{ name: '
|
|
7455
|
+
{ name: 'hooks_error_record', desc: 'Record error and fix' },
|
|
7456
|
+
{ name: 'hooks_error_suggest', desc: 'Suggest fixes for error' },
|
|
7457
|
+
{ name: 'hooks_force_learn', desc: 'Force learning cycle' },
|
|
7770
7458
|
],
|
|
7771
7459
|
'hooks-analysis': [
|
|
7772
|
-
{ name: '
|
|
7773
|
-
{ name: '
|
|
7774
|
-
{ name: '
|
|
7775
|
-
{ name: '
|
|
7776
|
-
{ name: '
|
|
7777
|
-
{ name: '
|
|
7460
|
+
{ name: 'hooks_ast_analyze', desc: 'Parse AST, extract symbols' },
|
|
7461
|
+
{ name: 'hooks_ast_complexity', desc: 'Cyclomatic complexity' },
|
|
7462
|
+
{ name: 'hooks_diff_analyze', desc: 'Semantic diff analysis' },
|
|
7463
|
+
{ name: 'hooks_diff_classify', desc: 'Classify change type' },
|
|
7464
|
+
{ name: 'hooks_diff_similar', desc: 'Find similar past commits' },
|
|
7465
|
+
{ name: 'hooks_coverage_route', desc: 'Coverage-aware routing' },
|
|
7466
|
+
{ name: 'hooks_coverage_suggest', desc: 'Suggest tests for files' },
|
|
7467
|
+
{ name: 'hooks_graph_mincut', desc: 'MinCut code boundaries' },
|
|
7468
|
+
{ name: 'hooks_graph_cluster', desc: 'Code community detection' },
|
|
7469
|
+
{ name: 'hooks_security_scan', desc: 'Security vulnerability scan' },
|
|
7470
|
+
{ name: 'hooks_rag_context', desc: 'RAG-enhanced context' },
|
|
7471
|
+
{ name: 'hooks_git_churn', desc: 'Git churn hot spots' },
|
|
7472
|
+
{ name: 'hooks_route_enhanced', desc: 'Enhanced routing w/ signals' },
|
|
7473
|
+
{ name: 'hooks_attention_info', desc: 'Attention mechanisms info' },
|
|
7474
|
+
{ name: 'hooks_gnn_info', desc: 'GNN capabilities info' },
|
|
7778
7475
|
],
|
|
7779
7476
|
'hooks-learning': [
|
|
7780
|
-
{ name: '
|
|
7781
|
-
{ name: '
|
|
7782
|
-
{ name: '
|
|
7477
|
+
{ name: 'hooks_learning_config', desc: 'Configure learning algorithms (9 algos)' },
|
|
7478
|
+
{ name: 'hooks_learning_stats', desc: 'Learning performance metrics' },
|
|
7479
|
+
{ name: 'hooks_learning_update', desc: 'Record learning experience' },
|
|
7480
|
+
{ name: 'hooks_learn', desc: 'Combined learn + recommend' },
|
|
7481
|
+
{ name: 'hooks_algorithms_list', desc: 'List all algorithms' },
|
|
7482
|
+
{ name: 'hooks_batch_learn', desc: 'Batch learning experiences' },
|
|
7783
7483
|
],
|
|
7784
7484
|
'hooks-compress': [
|
|
7785
|
-
{ name: '
|
|
7786
|
-
{ name: '
|
|
7787
|
-
{ name: '
|
|
7485
|
+
{ name: 'hooks_compress', desc: 'Compress pattern storage (10x savings)' },
|
|
7486
|
+
{ name: 'hooks_compress_stats', desc: 'Compression statistics' },
|
|
7487
|
+
{ name: 'hooks_compress_store', desc: 'Store compressed embedding' },
|
|
7488
|
+
{ name: 'hooks_compress_get', desc: 'Retrieve compressed embedding' },
|
|
7788
7489
|
],
|
|
7789
7490
|
'hooks-events': [
|
|
7790
|
-
{ name: '
|
|
7791
|
-
{ name: '
|
|
7792
|
-
{ name: 'hooks_notify', args: 'message, level?', desc: 'Send notification' },
|
|
7793
|
-
{ name: 'hooks_transfer', args: 'target, data', desc: 'Transfer context' },
|
|
7794
|
-
],
|
|
7795
|
-
'hooks-model': [
|
|
7796
|
-
{ name: 'hooks_model_route', args: 'task, complexity?', desc: 'Route to optimal model tier' },
|
|
7797
|
-
{ name: 'hooks_model_outcome', args: 'model, task, success, tokens?', desc: 'Record model outcome' },
|
|
7798
|
-
{ name: 'hooks_model_stats', args: '(none)', desc: 'Get model routing stats' },
|
|
7491
|
+
{ name: 'hooks_subscribe_snapshot', desc: 'Event state snapshot' },
|
|
7492
|
+
{ name: 'hooks_watch_status', desc: 'File watch status' },
|
|
7799
7493
|
],
|
|
7800
7494
|
'workers': [
|
|
7801
|
-
{ name: '
|
|
7802
|
-
{ name: 'workers_status',
|
|
7803
|
-
{ name: '
|
|
7804
|
-
{ name: '
|
|
7805
|
-
{ name: '
|
|
7806
|
-
{ name: '
|
|
7807
|
-
{ name: '
|
|
7808
|
-
{ name: '
|
|
7809
|
-
{ name: '
|
|
7810
|
-
{ name: '
|
|
7811
|
-
{ name: '
|
|
7812
|
-
{ name: '
|
|
7495
|
+
{ name: 'workers_dispatch', desc: 'Dispatch background worker' },
|
|
7496
|
+
{ name: 'workers_status', desc: 'Worker status' },
|
|
7497
|
+
{ name: 'workers_results', desc: 'Worker results' },
|
|
7498
|
+
{ name: 'workers_triggers', desc: 'List worker triggers' },
|
|
7499
|
+
{ name: 'workers_stats', desc: 'Worker statistics' },
|
|
7500
|
+
{ name: 'workers_presets', desc: 'Available presets' },
|
|
7501
|
+
{ name: 'workers_phases', desc: 'Worker phases' },
|
|
7502
|
+
{ name: 'workers_create', desc: 'Create custom worker' },
|
|
7503
|
+
{ name: 'workers_run', desc: 'Run worker' },
|
|
7504
|
+
{ name: 'workers_custom', desc: 'Custom worker' },
|
|
7505
|
+
{ name: 'workers_init_config', desc: 'Init config file' },
|
|
7506
|
+
{ name: 'workers_load_config', desc: 'Load config' },
|
|
7813
7507
|
],
|
|
7814
7508
|
'rvf': [
|
|
7815
|
-
{ name: 'rvf_create',
|
|
7816
|
-
{ name: 'rvf_open',
|
|
7817
|
-
{ name: 'rvf_ingest',
|
|
7818
|
-
{ name: 'rvf_query',
|
|
7819
|
-
{ name: 'rvf_delete',
|
|
7820
|
-
{ name: 'rvf_status',
|
|
7821
|
-
{ name: 'rvf_compact',
|
|
7822
|
-
{ name: 'rvf_derive',
|
|
7823
|
-
{ name: 'rvf_segments',
|
|
7824
|
-
{ name: 'rvf_examples',
|
|
7509
|
+
{ name: 'rvf_create', desc: 'Create .rvf vector store' },
|
|
7510
|
+
{ name: 'rvf_open', desc: 'Open existing store' },
|
|
7511
|
+
{ name: 'rvf_ingest', desc: 'Insert vectors' },
|
|
7512
|
+
{ name: 'rvf_query', desc: 'Query nearest neighbors' },
|
|
7513
|
+
{ name: 'rvf_delete', desc: 'Delete vectors by ID' },
|
|
7514
|
+
{ name: 'rvf_status', desc: 'Store status' },
|
|
7515
|
+
{ name: 'rvf_compact', desc: 'Compact store' },
|
|
7516
|
+
{ name: 'rvf_derive', desc: 'COW-branch child store' },
|
|
7517
|
+
{ name: 'rvf_segments', desc: 'List file segments' },
|
|
7518
|
+
{ name: 'rvf_examples', desc: 'Example .rvf files' },
|
|
7825
7519
|
],
|
|
7826
7520
|
'rvlite': [
|
|
7827
|
-
{ name: 'rvlite_sql',
|
|
7828
|
-
{ name: 'rvlite_cypher',
|
|
7829
|
-
{ name: 'rvlite_sparql',
|
|
7521
|
+
{ name: 'rvlite_sql', desc: 'SQL query over vector DB' },
|
|
7522
|
+
{ name: 'rvlite_cypher', desc: 'Cypher graph query' },
|
|
7523
|
+
{ name: 'rvlite_sparql', desc: 'SPARQL RDF query' },
|
|
7830
7524
|
],
|
|
7831
7525
|
'brain': [
|
|
7832
|
-
{ name: 'brain_search',
|
|
7833
|
-
{ name: 'brain_share',
|
|
7834
|
-
{ name: 'brain_get',
|
|
7835
|
-
{ name: 'brain_vote',
|
|
7836
|
-
{ name: 'brain_list',
|
|
7837
|
-
{ name: 'brain_delete',
|
|
7838
|
-
{ name: 'brain_status',
|
|
7839
|
-
{ name: 'brain_drift',
|
|
7840
|
-
{ name: 'brain_partition',
|
|
7841
|
-
{ name: 'brain_transfer',
|
|
7842
|
-
{ name: 'brain_sync',
|
|
7526
|
+
{ name: 'brain_search', desc: 'Semantic search shared knowledge' },
|
|
7527
|
+
{ name: 'brain_share', desc: 'Share knowledge' },
|
|
7528
|
+
{ name: 'brain_get', desc: 'Get memory by ID' },
|
|
7529
|
+
{ name: 'brain_vote', desc: 'Vote on quality' },
|
|
7530
|
+
{ name: 'brain_list', desc: 'List memories' },
|
|
7531
|
+
{ name: 'brain_delete', desc: 'Delete own contribution' },
|
|
7532
|
+
{ name: 'brain_status', desc: 'System health' },
|
|
7533
|
+
{ name: 'brain_drift', desc: 'Knowledge drift check' },
|
|
7534
|
+
{ name: 'brain_partition', desc: 'MinCut knowledge topology' },
|
|
7535
|
+
{ name: 'brain_transfer', desc: 'Domain transfer' },
|
|
7536
|
+
{ name: 'brain_sync', desc: 'LoRA weight sync' },
|
|
7843
7537
|
],
|
|
7844
7538
|
'edge': [
|
|
7845
|
-
{ name: 'edge_status',
|
|
7846
|
-
{ name: 'edge_join',
|
|
7847
|
-
{ name: 'edge_balance',
|
|
7848
|
-
{ name: 'edge_tasks',
|
|
7539
|
+
{ name: 'edge_status', desc: 'Network status' },
|
|
7540
|
+
{ name: 'edge_join', desc: 'Join as compute node' },
|
|
7541
|
+
{ name: 'edge_balance', desc: 'Check rUv balance' },
|
|
7542
|
+
{ name: 'edge_tasks', desc: 'Available compute tasks' },
|
|
7849
7543
|
],
|
|
7850
7544
|
'identity': [
|
|
7851
|
-
{ name: 'identity_generate',
|
|
7852
|
-
{ name: 'identity_show',
|
|
7545
|
+
{ name: 'identity_generate', desc: 'Generate new pi key' },
|
|
7546
|
+
{ name: 'identity_show', desc: 'Show current identity' },
|
|
7853
7547
|
],
|
|
7854
7548
|
};
|
|
7855
7549
|
|
|
7856
|
-
if
|
|
7857
|
-
|
|
7858
|
-
|
|
7859
|
-
|
|
7860
|
-
|
|
7861
|
-
|
|
7862
|
-
|
|
7863
|
-
|
|
7550
|
+
// Filter by group if specified
|
|
7551
|
+
let groups = Object.entries(tools);
|
|
7552
|
+
if (opts.group) {
|
|
7553
|
+
groups = groups.filter(([g]) => g === opts.group || g.startsWith(opts.group));
|
|
7554
|
+
}
|
|
7555
|
+
|
|
7556
|
+
if (opts.json || !process.stdout.isTTY) {
|
|
7557
|
+
const flat = groups.flatMap(([group, items]) => items.map(t => ({ ...t, group })));
|
|
7558
|
+
console.log(JSON.stringify(flat, null, 2));
|
|
7864
7559
|
return;
|
|
7865
7560
|
}
|
|
7866
7561
|
|
|
7867
|
-
console.log(chalk.bold.cyan('\nRuVector MCP Tools\n'));
|
|
7868
7562
|
let total = 0;
|
|
7869
|
-
|
|
7870
|
-
|
|
7871
|
-
|
|
7872
|
-
|
|
7873
|
-
console.log(` ${chalk.green(t.name.padEnd(28))} ${chalk.dim(t.args.padEnd(40))} ${t.desc}`);
|
|
7563
|
+
groups.forEach(([group, items]) => {
|
|
7564
|
+
console.log(chalk.bold.cyan(`\n${group} (${items.length} tools)`));
|
|
7565
|
+
items.forEach(t => {
|
|
7566
|
+
console.log(` ${chalk.bold(t.name.padEnd(28))} ${chalk.dim(t.desc)}`);
|
|
7874
7567
|
});
|
|
7875
|
-
|
|
7876
|
-
total += tools.length;
|
|
7568
|
+
total += items.length;
|
|
7877
7569
|
});
|
|
7878
|
-
console.log(chalk.bold(
|
|
7570
|
+
console.log(chalk.bold(`\nTotal: ${total} MCP tools\n`));
|
|
7879
7571
|
});
|
|
7880
7572
|
|
|
7881
|
-
// ============================================================================
|
|
7882
|
-
// MCP test subcommand
|
|
7883
|
-
// ============================================================================
|
|
7884
|
-
|
|
7885
7573
|
mcpCmd.command('test')
|
|
7886
7574
|
.description('Test MCP server setup and tool registration')
|
|
7887
7575
|
.action(() => {
|
|
7888
7576
|
console.log(chalk.bold.cyan('\nMCP Server Test Results'));
|
|
7889
7577
|
console.log(chalk.dim('-'.repeat(40)));
|
|
7890
7578
|
|
|
7579
|
+
// Test 1: server file exists
|
|
7891
7580
|
const mcpServerPath = path.join(__dirname, 'mcp-server.js');
|
|
7892
7581
|
if (fs.existsSync(mcpServerPath)) {
|
|
7893
7582
|
console.log(` ${chalk.green('PASS')} mcp-server.js exists`);
|
|
@@ -7896,6 +7585,7 @@ mcpCmd.command('test')
|
|
|
7896
7585
|
process.exit(1);
|
|
7897
7586
|
}
|
|
7898
7587
|
|
|
7588
|
+
// Test 2: syntax check
|
|
7899
7589
|
try {
|
|
7900
7590
|
const { execSync } = require('child_process');
|
|
7901
7591
|
execSync(`node -c ${mcpServerPath}`, { stdio: 'pipe' });
|
|
@@ -7905,6 +7595,7 @@ mcpCmd.command('test')
|
|
|
7905
7595
|
process.exit(1);
|
|
7906
7596
|
}
|
|
7907
7597
|
|
|
7598
|
+
// Test 3: MCP SDK available
|
|
7908
7599
|
try {
|
|
7909
7600
|
require('@modelcontextprotocol/sdk/server/index.js');
|
|
7910
7601
|
console.log(` ${chalk.green('PASS')} @modelcontextprotocol/sdk installed`);
|
|
@@ -7913,10 +7604,13 @@ mcpCmd.command('test')
|
|
|
7913
7604
|
process.exit(1);
|
|
7914
7605
|
}
|
|
7915
7606
|
|
|
7607
|
+
// Test 4: count tools by parsing TOOLS array entries (each has inputSchema)
|
|
7916
7608
|
try {
|
|
7917
7609
|
const src = fs.readFileSync(mcpServerPath, 'utf8');
|
|
7610
|
+
// Extract the TOOLS array section (from 'const TOOLS = [' to the matching '];')
|
|
7918
7611
|
const toolsStart = src.indexOf('const TOOLS = [');
|
|
7919
7612
|
const toolsSection = toolsStart >= 0 ? src.slice(toolsStart) : src;
|
|
7613
|
+
// Match tool names that are followed by inputSchema (real MCP tools only)
|
|
7920
7614
|
const toolDefs = toolsSection.match(/name:\s*'([a-z][a-z0-9_]*)'\s*,\s*\n\s*description:/g) || [];
|
|
7921
7615
|
const toolNames = toolDefs.map(m => m.match(/name:\s*'([a-z][a-z0-9_]*)'/)[1]);
|
|
7922
7616
|
const groups = {};
|
|
@@ -7933,6 +7627,7 @@ mcpCmd.command('test')
|
|
|
7933
7627
|
console.log(` ${chalk.yellow('WARN')} Could not parse tool count: ${e.message}`);
|
|
7934
7628
|
}
|
|
7935
7629
|
|
|
7630
|
+
// Test 5: version check
|
|
7936
7631
|
try {
|
|
7937
7632
|
const src = fs.readFileSync(mcpServerPath, 'utf8');
|
|
7938
7633
|
const verMatch = src.match(/version:\s*'([^']+)'/);
|
|
@@ -7948,1103 +7643,1424 @@ mcpCmd.command('test')
|
|
|
7948
7643
|
});
|
|
7949
7644
|
|
|
7950
7645
|
// ============================================================================
|
|
7951
|
-
// Brain Commands — Shared intelligence via pi
|
|
7646
|
+
// Brain Commands — Shared intelligence via @ruvector/pi-brain (lazy-loaded)
|
|
7952
7647
|
// ============================================================================
|
|
7953
7648
|
|
|
7954
|
-
|
|
7955
|
-
|
|
7956
|
-
|
|
7957
|
-
|
|
7958
|
-
}
|
|
7649
|
+
// Lazy load pi-brain (optional peer dependency)
|
|
7650
|
+
async function requirePiBrain() {
|
|
7651
|
+
try {
|
|
7652
|
+
return await import('@ruvector/pi-brain');
|
|
7653
|
+
} catch {
|
|
7654
|
+
console.error(chalk.red('Brain commands require @ruvector/pi-brain'));
|
|
7655
|
+
console.error(chalk.yellow(' npm install @ruvector/pi-brain'));
|
|
7656
|
+
process.exit(1);
|
|
7657
|
+
}
|
|
7959
7658
|
}
|
|
7960
7659
|
|
|
7961
|
-
|
|
7962
|
-
|
|
7963
|
-
|
|
7964
|
-
return h;
|
|
7660
|
+
// Determine output mode: JSON when --json flag or piped
|
|
7661
|
+
function isJsonOutput(opts) {
|
|
7662
|
+
return opts.json || !process.stdout.isTTY;
|
|
7965
7663
|
}
|
|
7966
7664
|
|
|
7967
|
-
|
|
7968
|
-
|
|
7969
|
-
|
|
7970
|
-
|
|
7971
|
-
|
|
7972
|
-
|
|
7973
|
-
}
|
|
7974
|
-
const fetchOpts = { headers: brainHeaders(config), signal: AbortSignal.timeout(30000) };
|
|
7975
|
-
if (opts.method) fetchOpts.method = opts.method;
|
|
7976
|
-
if (opts.body) { fetchOpts.method = opts.method || 'POST'; fetchOpts.body = JSON.stringify(opts.body); }
|
|
7977
|
-
const resp = await proxyFetch(url.toString(), fetchOpts);
|
|
7978
|
-
if (!resp.ok) {
|
|
7979
|
-
const errText = await resp.text().catch(() => resp.statusText);
|
|
7980
|
-
throw new Error(`${resp.status} ${errText}`);
|
|
7981
|
-
}
|
|
7982
|
-
if (resp.status === 204 || resp.headers.get('content-length') === '0') return {};
|
|
7983
|
-
return resp.json();
|
|
7665
|
+
// Create a PiBrainClient from command options
|
|
7666
|
+
async function makeBrainClient(opts) {
|
|
7667
|
+
const { PiBrainClient } = await requirePiBrain();
|
|
7668
|
+
return new PiBrainClient({
|
|
7669
|
+
url: opts.url || process.env.BRAIN_URL || 'https://pi.ruv.io',
|
|
7670
|
+
apiKey: process.env.PI || 'anonymous',
|
|
7671
|
+
});
|
|
7984
7672
|
}
|
|
7985
7673
|
|
|
7986
|
-
const brainCmd = program
|
|
7674
|
+
const brainCmd = program
|
|
7675
|
+
.command('brain')
|
|
7676
|
+
.description('Shared intelligence — search, share, and manage collective knowledge')
|
|
7677
|
+
.option('--url <url>', 'Brain server URL', process.env.BRAIN_URL || 'https://pi.ruv.io')
|
|
7678
|
+
.option('--json', 'Force JSON output');
|
|
7987
7679
|
|
|
7988
|
-
brainCmd
|
|
7989
|
-
.
|
|
7990
|
-
.
|
|
7680
|
+
brainCmd
|
|
7681
|
+
.command('search <query>')
|
|
7682
|
+
.description('Semantic search across collective knowledge')
|
|
7991
7683
|
.option('-l, --limit <n>', 'Max results', '10')
|
|
7992
|
-
.option('--
|
|
7993
|
-
.
|
|
7994
|
-
|
|
7995
|
-
|
|
7996
|
-
|
|
7997
|
-
|
|
7998
|
-
|
|
7999
|
-
|
|
8000
|
-
|
|
8001
|
-
|
|
8002
|
-
if (!results.length) { console.log(chalk.dim(' No results found.\n')); return; }
|
|
8003
|
-
results.forEach((r, i) => {
|
|
8004
|
-
console.log(` ${chalk.yellow(i + 1 + '.')} ${chalk.bold(r.title || r.id)}`);
|
|
8005
|
-
if (r.category) console.log(` ${chalk.dim('Category:')} ${r.category}`);
|
|
8006
|
-
if (r.score) console.log(` ${chalk.dim('Score:')} ${r.score.toFixed(3)}`);
|
|
8007
|
-
if (opts.verbose) {
|
|
8008
|
-
if (r.quality_score !== undefined) console.log(` ${chalk.dim('Quality:')} ${typeof r.quality_score === 'number' ? r.quality_score.toFixed(3) : r.quality_score}`);
|
|
8009
|
-
if (r.votes_up !== undefined || r.votes_down !== undefined) console.log(` ${chalk.dim('Votes:')} ${r.votes_up || 0}↑ ${r.votes_down || 0}↓`);
|
|
8010
|
-
if (r.witness_hash) console.log(` ${chalk.dim('Witness:')} ${r.witness_hash.slice(0, 12)}...`);
|
|
8011
|
-
if (r.contributor_id) console.log(` ${chalk.dim('Contributor:')} ${r.contributor_id}`);
|
|
8012
|
-
if (r.created_at) console.log(` ${chalk.dim('Created:')} ${r.created_at}`);
|
|
8013
|
-
if (r.tags && r.tags.length) console.log(` ${chalk.dim('Tags:')} ${r.tags.join(', ')}`);
|
|
8014
|
-
}
|
|
8015
|
-
console.log();
|
|
7684
|
+
.option('-c, --category <category>', 'Filter by category')
|
|
7685
|
+
.action(async (query, cmdOpts) => {
|
|
7686
|
+
const opts = brainCmd.opts();
|
|
7687
|
+
const spinner = ora('Searching brain...').start();
|
|
7688
|
+
try {
|
|
7689
|
+
const client = await makeBrainClient(opts);
|
|
7690
|
+
const result = await client.search({
|
|
7691
|
+
query,
|
|
7692
|
+
category: cmdOpts.category || undefined,
|
|
7693
|
+
limit: parseInt(cmdOpts.limit),
|
|
8016
7694
|
});
|
|
8017
|
-
|
|
7695
|
+
const items = Array.isArray(result) ? result : (result.memories || result.results || []);
|
|
7696
|
+
spinner.succeed(chalk.green(`Found ${items.length} result(s)`));
|
|
7697
|
+
if (isJsonOutput(opts)) {
|
|
7698
|
+
console.log(JSON.stringify(result, null, 2));
|
|
7699
|
+
} else {
|
|
7700
|
+
items.forEach((item, i) => {
|
|
7701
|
+
console.log(chalk.cyan(`\n ${i + 1}. ${item.title || item.id || 'Untitled'}`));
|
|
7702
|
+
if (item.category) console.log(chalk.gray(` Category: ${item.category}`));
|
|
7703
|
+
if (item.quality_score != null) console.log(chalk.gray(` Quality: ${item.quality_score}`));
|
|
7704
|
+
if (item.content) console.log(chalk.dim(` ${item.content.substring(0, 120)}${item.content.length > 120 ? '...' : ''}`));
|
|
7705
|
+
});
|
|
7706
|
+
}
|
|
7707
|
+
} catch (error) {
|
|
7708
|
+
spinner.fail(chalk.red('Search failed'));
|
|
7709
|
+
console.error(chalk.red(error.message));
|
|
7710
|
+
process.exit(1);
|
|
7711
|
+
}
|
|
8018
7712
|
});
|
|
8019
7713
|
|
|
8020
|
-
brainCmd
|
|
7714
|
+
brainCmd
|
|
7715
|
+
.command('share <title>')
|
|
8021
7716
|
.description('Share knowledge with the collective brain')
|
|
8022
|
-
.requiredOption('-c, --category <
|
|
7717
|
+
.requiredOption('-c, --category <category>', 'Category (architecture, pattern, solution, etc.)')
|
|
8023
7718
|
.option('-t, --tags <tags>', 'Comma-separated tags')
|
|
8024
|
-
.option('--content <
|
|
8025
|
-
.
|
|
8026
|
-
|
|
8027
|
-
|
|
8028
|
-
|
|
8029
|
-
|
|
8030
|
-
|
|
8031
|
-
|
|
8032
|
-
|
|
8033
|
-
|
|
8034
|
-
|
|
8035
|
-
|
|
7719
|
+
.option('--content <content>', 'Content body (reads from stdin if omitted)')
|
|
7720
|
+
.action(async (title, cmdOpts) => {
|
|
7721
|
+
const opts = brainCmd.opts();
|
|
7722
|
+
const spinner = ora('Sharing knowledge...').start();
|
|
7723
|
+
try {
|
|
7724
|
+
const client = await makeBrainClient(opts);
|
|
7725
|
+
let content = cmdOpts.content;
|
|
7726
|
+
if (!content && !process.stdin.isTTY) {
|
|
7727
|
+
const chunks = [];
|
|
7728
|
+
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
7729
|
+
content = Buffer.concat(chunks).toString('utf8').trim();
|
|
7730
|
+
}
|
|
7731
|
+
if (!content) {
|
|
7732
|
+
spinner.fail(chalk.red('No content provided. Use --content or pipe via stdin.'));
|
|
7733
|
+
process.exit(1);
|
|
7734
|
+
}
|
|
7735
|
+
const tags = cmdOpts.tags ? cmdOpts.tags.split(',').map(t => t.trim()) : [];
|
|
7736
|
+
const result = await client.share({ title, category: cmdOpts.category, content, tags });
|
|
7737
|
+
spinner.succeed(chalk.green('Knowledge shared'));
|
|
7738
|
+
if (isJsonOutput(opts)) {
|
|
7739
|
+
console.log(JSON.stringify(result, null, 2));
|
|
7740
|
+
} else {
|
|
7741
|
+
console.log(chalk.cyan(` ID: ${result.id || 'N/A'}`));
|
|
7742
|
+
console.log(chalk.gray(` Title: ${title}`));
|
|
7743
|
+
console.log(chalk.gray(` Category: ${cmdOpts.category}`));
|
|
7744
|
+
if (tags.length) console.log(chalk.gray(` Tags: ${tags.join(', ')}`));
|
|
7745
|
+
}
|
|
7746
|
+
} catch (error) {
|
|
7747
|
+
spinner.fail(chalk.red('Failed to share knowledge'));
|
|
7748
|
+
console.error(chalk.red(error.message));
|
|
7749
|
+
process.exit(1);
|
|
7750
|
+
}
|
|
8036
7751
|
});
|
|
8037
7752
|
|
|
8038
|
-
brainCmd
|
|
8039
|
-
.
|
|
8040
|
-
.
|
|
8041
|
-
.
|
|
8042
|
-
|
|
8043
|
-
|
|
8044
|
-
const config = getBrainConfig(opts);
|
|
7753
|
+
brainCmd
|
|
7754
|
+
.command('get <id>')
|
|
7755
|
+
.description('Retrieve a memory by ID with full provenance')
|
|
7756
|
+
.action(async (id) => {
|
|
7757
|
+
const opts = brainCmd.opts();
|
|
7758
|
+
const spinner = ora('Fetching memory...').start();
|
|
8045
7759
|
try {
|
|
8046
|
-
const
|
|
8047
|
-
|
|
8048
|
-
|
|
8049
|
-
if (
|
|
8050
|
-
|
|
8051
|
-
|
|
8052
|
-
|
|
8053
|
-
|
|
8054
|
-
});
|
|
8055
|
-
|
|
8056
|
-
|
|
8057
|
-
|
|
8058
|
-
|
|
8059
|
-
|
|
8060
|
-
.
|
|
8061
|
-
|
|
8062
|
-
|
|
7760
|
+
const client = await makeBrainClient(opts);
|
|
7761
|
+
const result = await client.get(id);
|
|
7762
|
+
spinner.succeed(chalk.green('Memory retrieved'));
|
|
7763
|
+
if (isJsonOutput(opts)) {
|
|
7764
|
+
console.log(JSON.stringify(result, null, 2));
|
|
7765
|
+
} else {
|
|
7766
|
+
console.log(chalk.cyan(` Title: ${result.title || 'N/A'}`));
|
|
7767
|
+
console.log(chalk.gray(` ID: ${result.id || id}`));
|
|
7768
|
+
console.log(chalk.gray(` Category: ${result.category || 'N/A'}`));
|
|
7769
|
+
console.log(chalk.gray(` Quality: ${result.quality_score != null ? result.quality_score : 'N/A'}`));
|
|
7770
|
+
console.log(chalk.gray(` Created: ${result.created_at || 'N/A'}`));
|
|
7771
|
+
if (result.tags && result.tags.length) console.log(chalk.gray(` Tags: ${result.tags.join(', ')}`));
|
|
7772
|
+
if (result.content) {
|
|
7773
|
+
console.log(chalk.white('\n Content:'));
|
|
7774
|
+
console.log(chalk.dim(` ${result.content}`));
|
|
7775
|
+
}
|
|
7776
|
+
}
|
|
7777
|
+
} catch (error) {
|
|
7778
|
+
spinner.fail(chalk.red('Failed to retrieve memory'));
|
|
7779
|
+
console.error(chalk.red(error.message));
|
|
7780
|
+
process.exit(1);
|
|
7781
|
+
}
|
|
7782
|
+
});
|
|
7783
|
+
|
|
7784
|
+
brainCmd
|
|
7785
|
+
.command('vote <id> <direction>')
|
|
7786
|
+
.description('Vote on a memory (up or down)')
|
|
7787
|
+
.action(async (id, direction) => {
|
|
7788
|
+
const opts = brainCmd.opts();
|
|
7789
|
+
if (!['up', 'down'].includes(direction)) {
|
|
7790
|
+
console.error(chalk.red('Direction must be "up" or "down"'));
|
|
7791
|
+
process.exit(1);
|
|
7792
|
+
}
|
|
7793
|
+
const spinner = ora(`Voting ${direction} on ${id}...`).start();
|
|
8063
7794
|
try {
|
|
8064
|
-
const
|
|
8065
|
-
|
|
8066
|
-
|
|
8067
|
-
|
|
7795
|
+
const client = await makeBrainClient(opts);
|
|
7796
|
+
const result = await client.vote(id, direction);
|
|
7797
|
+
spinner.succeed(chalk.green(`Voted ${direction} on memory ${id}`));
|
|
7798
|
+
if (isJsonOutput(opts)) {
|
|
7799
|
+
console.log(JSON.stringify(result, null, 2));
|
|
7800
|
+
}
|
|
7801
|
+
} catch (error) {
|
|
7802
|
+
spinner.fail(chalk.red('Vote failed'));
|
|
7803
|
+
console.error(chalk.red(error.message));
|
|
7804
|
+
process.exit(1);
|
|
7805
|
+
}
|
|
8068
7806
|
});
|
|
8069
7807
|
|
|
8070
|
-
brainCmd
|
|
8071
|
-
.
|
|
8072
|
-
.
|
|
7808
|
+
brainCmd
|
|
7809
|
+
.command('list')
|
|
7810
|
+
.description('List memories from the collective brain')
|
|
7811
|
+
.option('-c, --category <category>', 'Filter by category')
|
|
8073
7812
|
.option('-l, --limit <n>', 'Max results', '20')
|
|
8074
|
-
.
|
|
8075
|
-
|
|
8076
|
-
|
|
8077
|
-
|
|
8078
|
-
|
|
8079
|
-
|
|
8080
|
-
|
|
8081
|
-
|
|
8082
|
-
|
|
8083
|
-
const
|
|
8084
|
-
|
|
8085
|
-
|
|
8086
|
-
|
|
8087
|
-
|
|
8088
|
-
|
|
8089
|
-
|
|
8090
|
-
|
|
8091
|
-
|
|
8092
|
-
|
|
8093
|
-
}
|
|
8094
|
-
|
|
8095
|
-
|
|
7813
|
+
.action(async (cmdOpts) => {
|
|
7814
|
+
const opts = brainCmd.opts();
|
|
7815
|
+
const spinner = ora('Listing memories...').start();
|
|
7816
|
+
try {
|
|
7817
|
+
const client = await makeBrainClient(opts);
|
|
7818
|
+
const result = await client.list(
|
|
7819
|
+
cmdOpts.category || undefined,
|
|
7820
|
+
parseInt(cmdOpts.limit),
|
|
7821
|
+
);
|
|
7822
|
+
const items = Array.isArray(result) ? result : (result.memories || result.results || []);
|
|
7823
|
+
spinner.succeed(chalk.green(`${items.length} memor${items.length === 1 ? 'y' : 'ies'} found`));
|
|
7824
|
+
if (isJsonOutput(opts)) {
|
|
7825
|
+
console.log(JSON.stringify(result, null, 2));
|
|
7826
|
+
} else {
|
|
7827
|
+
items.forEach((item, i) => {
|
|
7828
|
+
console.log(chalk.cyan(` ${i + 1}. ${item.title || item.id || 'Untitled'}`) +
|
|
7829
|
+
(item.category ? chalk.gray(` [${item.category}]`) : '') +
|
|
7830
|
+
(item.quality_score != null ? chalk.yellow(` (q: ${item.quality_score})`) : ''));
|
|
7831
|
+
});
|
|
7832
|
+
}
|
|
7833
|
+
} catch (error) {
|
|
7834
|
+
spinner.fail(chalk.red('Failed to list memories'));
|
|
7835
|
+
console.error(chalk.red(error.message));
|
|
7836
|
+
process.exit(1);
|
|
7837
|
+
}
|
|
8096
7838
|
});
|
|
8097
7839
|
|
|
8098
|
-
brainCmd
|
|
8099
|
-
.
|
|
8100
|
-
.
|
|
8101
|
-
.
|
|
8102
|
-
|
|
8103
|
-
|
|
8104
|
-
const config = getBrainConfig(opts);
|
|
7840
|
+
brainCmd
|
|
7841
|
+
.command('delete <id>')
|
|
7842
|
+
.description('Delete a memory you contributed')
|
|
7843
|
+
.action(async (id) => {
|
|
7844
|
+
const opts = brainCmd.opts();
|
|
7845
|
+
const spinner = ora(`Deleting memory ${id}...`).start();
|
|
8105
7846
|
try {
|
|
8106
|
-
const
|
|
8107
|
-
|
|
8108
|
-
|
|
8109
|
-
|
|
7847
|
+
const client = await makeBrainClient(opts);
|
|
7848
|
+
const result = await client.delete(id);
|
|
7849
|
+
spinner.succeed(chalk.green(`Memory ${id} deleted`));
|
|
7850
|
+
if (isJsonOutput(opts)) {
|
|
7851
|
+
console.log(JSON.stringify(result, null, 2));
|
|
7852
|
+
}
|
|
7853
|
+
} catch (error) {
|
|
7854
|
+
spinner.fail(chalk.red('Failed to delete memory'));
|
|
7855
|
+
console.error(chalk.red(error.message));
|
|
7856
|
+
process.exit(1);
|
|
7857
|
+
}
|
|
8110
7858
|
});
|
|
8111
7859
|
|
|
8112
|
-
brainCmd
|
|
8113
|
-
.
|
|
8114
|
-
.
|
|
8115
|
-
.
|
|
8116
|
-
|
|
8117
|
-
|
|
8118
|
-
const config = getBrainConfig(opts);
|
|
8119
|
-
try {
|
|
8120
|
-
const status = await brainFetch(config, '/v1/status');
|
|
8121
|
-
if (opts.json || !process.stdout.isTTY) { console.log(JSON.stringify(status, null, 2)); return; }
|
|
8122
|
-
console.log(chalk.bold.cyan('\nBrain Status\n'));
|
|
8123
|
-
Object.entries(status).forEach(([k, v]) => {
|
|
8124
|
-
console.log(` ${chalk.bold(k + ':')} ${v}`);
|
|
8125
|
-
});
|
|
8126
|
-
// AGI subsystem fields
|
|
8127
|
-
if (status.sona_patterns !== undefined) {
|
|
8128
|
-
console.log(chalk.bold('\n AGI Subsystems'));
|
|
8129
|
-
if (status.sona_patterns !== undefined) console.log(` ${chalk.dim('SONA Patterns:')} ${status.sona_patterns} ${chalk.dim('Trajectories:')} ${status.sona_trajectories || 0}`);
|
|
8130
|
-
if (status.gwt_workspace_load !== undefined) console.log(` ${chalk.dim('GWT Load:')} ${status.gwt_workspace_load} ${chalk.dim('Avg Salience:')} ${status.gwt_avg_salience || 0}`);
|
|
8131
|
-
if (status.knowledge_velocity !== undefined) console.log(` ${chalk.dim('Temporal Velocity:')} ${status.knowledge_velocity}/hr ${chalk.dim('Deltas:')} ${status.temporal_deltas || 0}`);
|
|
8132
|
-
if (status.meta_avg_regret !== undefined) console.log(` ${chalk.dim('Meta Regret:')} ${status.meta_avg_regret} ${chalk.dim('Plateau:')} ${status.meta_plateau_status || 'unknown'}`);
|
|
8133
|
-
}
|
|
8134
|
-
if (status.midstream_scheduler_ticks !== undefined) {
|
|
8135
|
-
console.log(chalk.bold('\n Midstream'));
|
|
8136
|
-
console.log(` ${chalk.dim('Scheduler Ticks:')} ${status.midstream_scheduler_ticks}`);
|
|
8137
|
-
console.log(` ${chalk.dim('Attractor Categories:')} ${status.midstream_attractor_categories || 0}`);
|
|
8138
|
-
console.log(` ${chalk.dim('Strange-Loop:')} v${status.midstream_strange_loop_version || '?'}`);
|
|
8139
|
-
}
|
|
8140
|
-
console.log();
|
|
8141
|
-
} catch (e) { console.error(chalk.red(`Error: ${e.message}`)); process.exit(1); }
|
|
8142
|
-
});
|
|
8143
|
-
|
|
8144
|
-
brainCmd.command('drift')
|
|
8145
|
-
.description('Check if shared knowledge has drifted')
|
|
8146
|
-
.option('-d, --domain <domain>', 'Domain to check')
|
|
8147
|
-
.option('--url <url>', 'Brain server URL')
|
|
8148
|
-
.option('--key <key>', 'Pi key')
|
|
8149
|
-
.option('--json', 'Output as JSON')
|
|
8150
|
-
.action(async (opts) => {
|
|
8151
|
-
const config = getBrainConfig(opts);
|
|
8152
|
-
try {
|
|
8153
|
-
const report = await brainFetch(config, '/v1/drift', { params: { domain: opts.domain } });
|
|
8154
|
-
if (opts.json || !process.stdout.isTTY) { console.log(JSON.stringify(report, null, 2)); return; }
|
|
8155
|
-
console.log(chalk.bold.cyan('\nDrift Report\n'));
|
|
8156
|
-
console.log(` ${chalk.bold('Drifting:')} ${report.is_drifting ? chalk.red('Yes') : chalk.green('No')}`);
|
|
8157
|
-
if (report.cv) console.log(` ${chalk.bold('CV:')} ${report.cv}`);
|
|
8158
|
-
console.log();
|
|
8159
|
-
} catch (e) { console.error(chalk.red(`Error: ${e.message}`)); process.exit(1); }
|
|
8160
|
-
});
|
|
8161
|
-
|
|
8162
|
-
brainCmd.command('partition')
|
|
8163
|
-
.description('Get knowledge partitioned by mincut topology')
|
|
8164
|
-
.option('-d, --domain <domain>', 'Domain to partition')
|
|
8165
|
-
.option('--min-size <n>', 'Minimum cluster size', '3')
|
|
8166
|
-
.option('--url <url>', 'Brain server URL')
|
|
8167
|
-
.option('--key <key>', 'Pi key')
|
|
8168
|
-
.option('--json', 'Output as JSON')
|
|
8169
|
-
.action(async (opts) => {
|
|
8170
|
-
const config = getBrainConfig(opts);
|
|
7860
|
+
brainCmd
|
|
7861
|
+
.command('status')
|
|
7862
|
+
.description('Show brain system health and statistics')
|
|
7863
|
+
.action(async () => {
|
|
7864
|
+
const opts = brainCmd.opts();
|
|
7865
|
+
const spinner = ora('Fetching brain status...').start();
|
|
8171
7866
|
try {
|
|
8172
|
-
const
|
|
8173
|
-
|
|
8174
|
-
|
|
8175
|
-
if (
|
|
8176
|
-
|
|
8177
|
-
|
|
8178
|
-
|
|
7867
|
+
const client = await makeBrainClient(opts);
|
|
7868
|
+
const result = await client.status();
|
|
7869
|
+
spinner.succeed(chalk.green('Brain status'));
|
|
7870
|
+
if (isJsonOutput(opts)) {
|
|
7871
|
+
console.log(JSON.stringify(result, null, 2));
|
|
7872
|
+
} else {
|
|
7873
|
+
const s = result;
|
|
7874
|
+
console.log(chalk.cyan(` Memories: ${s.total_memories != null ? s.total_memories : 'N/A'}`));
|
|
7875
|
+
console.log(chalk.cyan(` Contributors: ${s.total_contributors != null ? s.total_contributors : 'N/A'}`));
|
|
7876
|
+
console.log(chalk.cyan(` Quality: ${s.avg_quality != null ? s.avg_quality : 'N/A'}`));
|
|
7877
|
+
console.log(chalk.cyan(` Drift: ${s.drift_status || s.drift || 'N/A'}`));
|
|
7878
|
+
if (s.uptime) console.log(chalk.gray(` Uptime: ${s.uptime}`));
|
|
8179
7879
|
}
|
|
8180
|
-
|
|
8181
|
-
|
|
7880
|
+
} catch (error) {
|
|
7881
|
+
spinner.fail(chalk.red('Failed to get brain status'));
|
|
7882
|
+
console.error(chalk.red(error.message));
|
|
7883
|
+
process.exit(1);
|
|
7884
|
+
}
|
|
8182
7885
|
});
|
|
8183
7886
|
|
|
8184
|
-
brainCmd
|
|
8185
|
-
.
|
|
8186
|
-
.
|
|
8187
|
-
.
|
|
8188
|
-
|
|
8189
|
-
|
|
8190
|
-
const config = getBrainConfig(opts);
|
|
7887
|
+
brainCmd
|
|
7888
|
+
.command('drift [domain]')
|
|
7889
|
+
.description('Check knowledge drift for a domain')
|
|
7890
|
+
.action(async (domain) => {
|
|
7891
|
+
const opts = brainCmd.opts();
|
|
7892
|
+
const spinner = ora('Checking drift...').start();
|
|
8191
7893
|
try {
|
|
8192
|
-
const
|
|
8193
|
-
|
|
8194
|
-
|
|
8195
|
-
|
|
7894
|
+
const client = await makeBrainClient(opts);
|
|
7895
|
+
const result = await client.drift(domain || undefined);
|
|
7896
|
+
spinner.succeed(chalk.green('Drift analysis complete'));
|
|
7897
|
+
if (isJsonOutput(opts)) {
|
|
7898
|
+
console.log(JSON.stringify(result, null, 2));
|
|
7899
|
+
} else {
|
|
7900
|
+
if (domain) console.log(chalk.gray(` Domain: ${domain}`));
|
|
7901
|
+
console.log(chalk.gray(` Drift: ${result.drift_score != null ? result.drift_score : JSON.stringify(result)}`));
|
|
7902
|
+
if (result.status) console.log(chalk.gray(` Status: ${result.status}`));
|
|
7903
|
+
}
|
|
7904
|
+
} catch (error) {
|
|
7905
|
+
spinner.fail(chalk.red('Drift check failed'));
|
|
7906
|
+
console.error(chalk.red(error.message));
|
|
7907
|
+
process.exit(1);
|
|
7908
|
+
}
|
|
8196
7909
|
});
|
|
8197
7910
|
|
|
8198
|
-
brainCmd
|
|
8199
|
-
.
|
|
8200
|
-
.
|
|
8201
|
-
.
|
|
8202
|
-
|
|
8203
|
-
|
|
8204
|
-
const config = getBrainConfig(opts);
|
|
7911
|
+
brainCmd
|
|
7912
|
+
.command('partition [domain]')
|
|
7913
|
+
.description('View knowledge topology / partitioning')
|
|
7914
|
+
.action(async (domain) => {
|
|
7915
|
+
const opts = brainCmd.opts();
|
|
7916
|
+
const spinner = ora('Fetching partition info...').start();
|
|
8205
7917
|
try {
|
|
8206
|
-
const
|
|
8207
|
-
|
|
8208
|
-
|
|
8209
|
-
|
|
8210
|
-
|
|
8211
|
-
|
|
8212
|
-
|
|
8213
|
-
|
|
8214
|
-
|
|
8215
|
-
|
|
8216
|
-
|
|
8217
|
-
|
|
8218
|
-
|
|
8219
|
-
|
|
8220
|
-
.option('--key <key>', 'Pi key')
|
|
8221
|
-
.option('--json', 'Output as JSON')
|
|
8222
|
-
.action(async (direction, opts) => {
|
|
8223
|
-
const config = getBrainConfig(opts);
|
|
8224
|
-
try {
|
|
8225
|
-
const result = await brainFetch(config, '/v1/lora/latest', { params: { direction: direction || 'both' } });
|
|
8226
|
-
if (opts.json || !process.stdout.isTTY) { console.log(JSON.stringify(result, null, 2)); return; }
|
|
8227
|
-
console.log(chalk.green(`Sync ${direction || 'both'}: ${result.status || 'OK'}`));
|
|
8228
|
-
} catch (e) { console.error(chalk.red(`Error: ${e.message}`)); process.exit(1); }
|
|
7918
|
+
const client = await makeBrainClient(opts);
|
|
7919
|
+
const result = await client.partition(domain || undefined);
|
|
7920
|
+
spinner.succeed(chalk.green('Partition data retrieved'));
|
|
7921
|
+
if (isJsonOutput(opts)) {
|
|
7922
|
+
console.log(JSON.stringify(result, null, 2));
|
|
7923
|
+
} else {
|
|
7924
|
+
if (domain) console.log(chalk.gray(` Domain: ${domain}`));
|
|
7925
|
+
console.log(chalk.dim(` ${JSON.stringify(result, null, 2)}`));
|
|
7926
|
+
}
|
|
7927
|
+
} catch (error) {
|
|
7928
|
+
spinner.fail(chalk.red('Partition query failed'));
|
|
7929
|
+
console.error(chalk.red(error.message));
|
|
7930
|
+
process.exit(1);
|
|
7931
|
+
}
|
|
8229
7932
|
});
|
|
8230
7933
|
|
|
8231
|
-
brainCmd
|
|
8232
|
-
.
|
|
8233
|
-
.
|
|
8234
|
-
.
|
|
8235
|
-
|
|
8236
|
-
|
|
8237
|
-
const config = getBrainConfig(opts);
|
|
7934
|
+
brainCmd
|
|
7935
|
+
.command('transfer <source> <target>')
|
|
7936
|
+
.description('Transfer knowledge between domains')
|
|
7937
|
+
.action(async (source, target) => {
|
|
7938
|
+
const opts = brainCmd.opts();
|
|
7939
|
+
const spinner = ora(`Transferring knowledge: ${source} -> ${target}...`).start();
|
|
8238
7940
|
try {
|
|
8239
|
-
|
|
8240
|
-
|
|
8241
|
-
|
|
8242
|
-
|
|
8243
|
-
|
|
8244
|
-
case 'get':
|
|
8245
|
-
if (!args[0]) { console.error(chalk.red('Usage: brain page get <slug>')); process.exit(1); }
|
|
8246
|
-
result = await brainFetch(config, `/v1/pages/${args[0]}`);
|
|
8247
|
-
break;
|
|
8248
|
-
case 'create':
|
|
8249
|
-
if (!args[0]) { console.error(chalk.red('Usage: brain page create <title> [--content <text>]')); process.exit(1); }
|
|
8250
|
-
result = await brainFetch(config, '/v1/pages', { body: { title: args[0], content: opts.content || '' } });
|
|
8251
|
-
break;
|
|
8252
|
-
case 'update':
|
|
8253
|
-
if (!args[0]) { console.error(chalk.red('Usage: brain page update <slug> [--content <text>]')); process.exit(1); }
|
|
8254
|
-
result = await brainFetch(config, `/v1/pages/${args[0]}/deltas`, { body: { content: opts.content || '' } });
|
|
8255
|
-
break;
|
|
8256
|
-
case 'delete':
|
|
8257
|
-
if (!args[0]) { console.error(chalk.red('Usage: brain page delete <slug>')); process.exit(1); }
|
|
8258
|
-
result = await brainFetch(config, `/v1/pages/${args[0]}`, { method: 'DELETE' }).catch(() => ({ error: 'Delete not available' }));
|
|
8259
|
-
break;
|
|
8260
|
-
default:
|
|
8261
|
-
console.error(chalk.red(`Unknown page action: ${action}. Use: list, get, create, update, delete`));
|
|
8262
|
-
process.exit(1);
|
|
8263
|
-
}
|
|
8264
|
-
if (opts.json || !process.stdout.isTTY) { console.log(JSON.stringify(result, null, 2)); return; }
|
|
8265
|
-
if (result.pages) {
|
|
8266
|
-
console.log(chalk.bold.cyan('\nBrainpedia Pages\n'));
|
|
8267
|
-
if (result.total_count !== undefined) console.log(chalk.dim(` ${result.total_count} total pages\n`));
|
|
8268
|
-
result.pages.forEach((p, i) => {
|
|
8269
|
-
const score = p.quality_score !== undefined ? chalk.dim(` (${(p.quality_score * 100).toFixed(0)}%)`) : '';
|
|
8270
|
-
const status = p.status ? chalk.dim(` [${p.status}]`) : '';
|
|
8271
|
-
console.log(` ${chalk.yellow(i + 1 + '.')} ${chalk.bold(p.title || p.slug || p.id)}${status}${score}`);
|
|
8272
|
-
if (p.category) console.log(` ${chalk.dim(p.category)}`);
|
|
8273
|
-
});
|
|
8274
|
-
} else if (result.memory && result.memory.title) {
|
|
8275
|
-
// Unwrap .memory wrapper from page detail response
|
|
8276
|
-
const page = result.memory;
|
|
8277
|
-
console.log(chalk.bold.cyan(`\n${page.title}\n`));
|
|
8278
|
-
if (result.status) console.log(` ${chalk.bold('Status:')} ${result.status}`);
|
|
8279
|
-
if (page.category) console.log(` ${chalk.bold('Category:')} ${page.category}`);
|
|
8280
|
-
if (page.quality_score !== undefined) console.log(` ${chalk.bold('Quality:')} ${(page.quality_score * 100).toFixed(0)}%`);
|
|
8281
|
-
if (result.delta_count !== undefined) console.log(` ${chalk.bold('Deltas:')} ${result.delta_count}`);
|
|
8282
|
-
if (result.evidence_count !== undefined) console.log(` ${chalk.bold('Evidence:')} ${result.evidence_count}`);
|
|
8283
|
-
if (page.tags && page.tags.length) console.log(` ${chalk.bold('Tags:')} ${page.tags.join(', ')}`);
|
|
8284
|
-
if (page.content) { console.log(); console.log(page.content); }
|
|
8285
|
-
} else if (result.title) {
|
|
8286
|
-
console.log(chalk.bold.cyan(`\n${result.title}\n`));
|
|
8287
|
-
if (result.content) console.log(result.content);
|
|
7941
|
+
const client = await makeBrainClient(opts);
|
|
7942
|
+
const result = await client.transfer(source, target);
|
|
7943
|
+
spinner.succeed(chalk.green('Transfer complete'));
|
|
7944
|
+
if (isJsonOutput(opts)) {
|
|
7945
|
+
console.log(JSON.stringify(result, null, 2));
|
|
8288
7946
|
} else {
|
|
7947
|
+
console.log(chalk.gray(` Source: ${source}`));
|
|
7948
|
+
console.log(chalk.gray(` Target: ${target}`));
|
|
7949
|
+
if (result.transferred != null) console.log(chalk.gray(` Transferred: ${result.transferred} items`));
|
|
7950
|
+
}
|
|
7951
|
+
} catch (error) {
|
|
7952
|
+
spinner.fail(chalk.red('Transfer failed'));
|
|
7953
|
+
console.error(chalk.red(error.message));
|
|
7954
|
+
process.exit(1);
|
|
7955
|
+
}
|
|
7956
|
+
});
|
|
7957
|
+
|
|
7958
|
+
brainCmd
|
|
7959
|
+
.command('sync [direction]')
|
|
7960
|
+
.description('Sync LoRA weights (pull, push, or both)')
|
|
7961
|
+
.action(async (direction) => {
|
|
7962
|
+
const opts = brainCmd.opts();
|
|
7963
|
+
const dir = direction || 'both';
|
|
7964
|
+
if (!['pull', 'push', 'both'].includes(dir)) {
|
|
7965
|
+
console.error(chalk.red('Direction must be "pull", "push", or "both"'));
|
|
7966
|
+
process.exit(1);
|
|
7967
|
+
}
|
|
7968
|
+
const spinner = ora(`Syncing LoRA weights (${dir})...`).start();
|
|
7969
|
+
try {
|
|
7970
|
+
await requirePiBrain();
|
|
7971
|
+
const url = opts.url || process.env.BRAIN_URL || 'https://pi.ruv.io';
|
|
7972
|
+
const apiKey = process.env.PI || 'anonymous';
|
|
7973
|
+
const headers = { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json' };
|
|
7974
|
+
const res = await fetch(`${url}/v1/lora/sync`, {
|
|
7975
|
+
method: 'POST',
|
|
7976
|
+
headers,
|
|
7977
|
+
body: JSON.stringify({ direction: dir }),
|
|
7978
|
+
});
|
|
7979
|
+
if (!res.ok) throw new Error(`Sync failed (${res.status})`);
|
|
7980
|
+
const result = await res.json();
|
|
7981
|
+
spinner.succeed(chalk.green(`LoRA sync complete (${dir})`));
|
|
7982
|
+
if (isJsonOutput(opts)) {
|
|
8289
7983
|
console.log(JSON.stringify(result, null, 2));
|
|
7984
|
+
} else {
|
|
7985
|
+
console.log(chalk.gray(` Direction: ${dir}`));
|
|
7986
|
+
console.log(chalk.dim(` ${JSON.stringify(result)}`));
|
|
8290
7987
|
}
|
|
8291
|
-
|
|
8292
|
-
|
|
7988
|
+
} catch (error) {
|
|
7989
|
+
spinner.fail(chalk.red('LoRA sync failed'));
|
|
7990
|
+
console.error(chalk.red(error.message));
|
|
7991
|
+
process.exit(1);
|
|
7992
|
+
}
|
|
8293
7993
|
});
|
|
8294
7994
|
|
|
8295
|
-
brainCmd
|
|
8296
|
-
.
|
|
8297
|
-
.
|
|
8298
|
-
.option('--
|
|
8299
|
-
.option('--
|
|
8300
|
-
.
|
|
8301
|
-
|
|
7995
|
+
brainCmd
|
|
7996
|
+
.command('page <action> [id]')
|
|
7997
|
+
.description('Brainpedia page management (list, get, create, update)')
|
|
7998
|
+
.option('--title <title>', 'Page title (for create/update)')
|
|
7999
|
+
.option('--content <content>', 'Page content (for create/update)')
|
|
8000
|
+
.option('-l, --limit <n>', 'Max results for list', '20')
|
|
8001
|
+
.action(async (action, id, cmdOpts) => {
|
|
8002
|
+
const opts = brainCmd.opts();
|
|
8003
|
+
const validActions = ['list', 'get', 'create', 'update'];
|
|
8004
|
+
if (!validActions.includes(action)) {
|
|
8005
|
+
console.error(chalk.red(`Action must be one of: ${validActions.join(', ')}`));
|
|
8006
|
+
process.exit(1);
|
|
8007
|
+
}
|
|
8008
|
+
if (['get', 'update'].includes(action) && !id) {
|
|
8009
|
+
console.error(chalk.red(`Page ID required for "${action}"`));
|
|
8010
|
+
process.exit(1);
|
|
8011
|
+
}
|
|
8012
|
+
const spinner = ora(`Brainpedia: ${action}...`).start();
|
|
8302
8013
|
try {
|
|
8014
|
+
const url = opts.url || process.env.BRAIN_URL || 'https://pi.ruv.io';
|
|
8015
|
+
const apiKey = process.env.PI || 'anonymous';
|
|
8016
|
+
const headers = { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json' };
|
|
8303
8017
|
let result;
|
|
8304
8018
|
switch (action) {
|
|
8305
|
-
case '
|
|
8306
|
-
|
|
8307
|
-
|
|
8308
|
-
|
|
8309
|
-
|
|
8310
|
-
result = await
|
|
8019
|
+
case 'list': {
|
|
8020
|
+
const params = new URLSearchParams();
|
|
8021
|
+
params.set('limit', cmdOpts.limit);
|
|
8022
|
+
const res = await fetch(`${url}/v1/pages?${params}`, { headers });
|
|
8023
|
+
if (!res.ok) throw new Error(`List failed (${res.status})`);
|
|
8024
|
+
result = await res.json();
|
|
8311
8025
|
break;
|
|
8312
|
-
|
|
8313
|
-
|
|
8026
|
+
}
|
|
8027
|
+
case 'get': {
|
|
8028
|
+
const res = await fetch(`${url}/v1/pages/${id}`, { headers });
|
|
8029
|
+
if (!res.ok) throw new Error(`Get failed (${res.status})`);
|
|
8030
|
+
result = await res.json();
|
|
8031
|
+
break;
|
|
8032
|
+
}
|
|
8033
|
+
case 'create': {
|
|
8034
|
+
if (!cmdOpts.title || !cmdOpts.content) {
|
|
8035
|
+
spinner.fail(chalk.red('--title and --content are required for create'));
|
|
8036
|
+
process.exit(1);
|
|
8037
|
+
}
|
|
8038
|
+
const res = await fetch(`${url}/v1/pages`, {
|
|
8039
|
+
method: 'POST', headers,
|
|
8040
|
+
body: JSON.stringify({ title: cmdOpts.title, content: cmdOpts.content }),
|
|
8041
|
+
});
|
|
8042
|
+
if (!res.ok) throw new Error(`Create failed (${res.status})`);
|
|
8043
|
+
result = await res.json();
|
|
8314
8044
|
break;
|
|
8315
|
-
|
|
8316
|
-
|
|
8317
|
-
|
|
8045
|
+
}
|
|
8046
|
+
case 'update': {
|
|
8047
|
+
const body = {};
|
|
8048
|
+
if (cmdOpts.title) body.title = cmdOpts.title;
|
|
8049
|
+
if (cmdOpts.content) body.content = cmdOpts.content;
|
|
8050
|
+
const res = await fetch(`${url}/v1/pages/${id}`, {
|
|
8051
|
+
method: 'PUT', headers,
|
|
8052
|
+
body: JSON.stringify(body),
|
|
8053
|
+
});
|
|
8054
|
+
if (!res.ok) throw new Error(`Update failed (${res.status})`);
|
|
8055
|
+
result = await res.json();
|
|
8318
8056
|
break;
|
|
8319
|
-
|
|
8320
|
-
console.error(chalk.red(`Unknown node action: ${action}. Use: publish, list, status`));
|
|
8321
|
-
process.exit(1);
|
|
8322
|
-
}
|
|
8323
|
-
if (opts.json || !process.stdout.isTTY) { console.log(JSON.stringify(result, null, 2)); return; }
|
|
8324
|
-
if (result.nodes) {
|
|
8325
|
-
console.log(chalk.bold.cyan('\nWASM Compute Nodes\n'));
|
|
8326
|
-
result.nodes.forEach((n, i) => console.log(` ${chalk.yellow(i + 1 + '.')} ${chalk.bold(n.name || n.id)} ${chalk.dim(n.status || '')}`));
|
|
8327
|
-
} else if (result.id) {
|
|
8328
|
-
console.log(chalk.green(`Published node: ${result.id}`));
|
|
8329
|
-
} else {
|
|
8330
|
-
console.log(JSON.stringify(result, null, 2));
|
|
8057
|
+
}
|
|
8331
8058
|
}
|
|
8332
|
-
|
|
8333
|
-
|
|
8334
|
-
|
|
8335
|
-
|
|
8336
|
-
|
|
8337
|
-
const
|
|
8338
|
-
|
|
8339
|
-
|
|
8340
|
-
|
|
8341
|
-
const headers = {};
|
|
8342
|
-
if (config.key) headers['Authorization'] = `Bearer ${config.key}`;
|
|
8343
|
-
const resp = await proxyFetch(url, { headers, signal: AbortSignal.timeout(30000) });
|
|
8344
|
-
if (!resp.ok) throw new Error(`${resp.status} ${resp.statusText}`);
|
|
8345
|
-
if (resp.status === 204 || resp.headers.get('content-length') === '0') return {};
|
|
8346
|
-
return resp.json();
|
|
8347
|
-
}
|
|
8348
|
-
|
|
8349
|
-
agiCmd.command('status')
|
|
8350
|
-
.description('Combined AGI + midstream diagnostics from π.ruv.io')
|
|
8351
|
-
.option('--url <url>', 'Brain server URL')
|
|
8352
|
-
.option('--key <key>', 'Pi key')
|
|
8353
|
-
.option('--json', 'Output as JSON')
|
|
8354
|
-
.action(async (opts) => {
|
|
8355
|
-
const config = getBrainConfig(opts);
|
|
8356
|
-
try {
|
|
8357
|
-
const status = await fetchBrainEndpoint(config, '/v1/status');
|
|
8358
|
-
if (opts.json || !process.stdout.isTTY) { console.log(JSON.stringify(status, null, 2)); return; }
|
|
8359
|
-
console.log(chalk.bold.cyan('\n π.ruv.io AGI Diagnostics\n'));
|
|
8360
|
-
console.log(chalk.bold(' SONA'));
|
|
8361
|
-
console.log(` Patterns: ${status.sona_patterns || 0} Trajectories: ${status.sona_trajectories || 0}`);
|
|
8362
|
-
console.log(` Background ticks: ${status.sona_background_ticks || 0}`);
|
|
8363
|
-
console.log(chalk.bold('\n GWT Attention'));
|
|
8364
|
-
console.log(` Workspace load: ${status.gwt_workspace_load || 0}`);
|
|
8365
|
-
console.log(` Avg salience: ${status.gwt_avg_salience || 0}`);
|
|
8366
|
-
console.log(chalk.bold('\n Temporal'));
|
|
8367
|
-
console.log(` Total deltas: ${status.temporal_deltas || 0}`);
|
|
8368
|
-
console.log(` Velocity: ${status.knowledge_velocity || 0}/hr`);
|
|
8369
|
-
console.log(` Trend: ${status.temporal_trend || 'unknown'}`);
|
|
8370
|
-
console.log(chalk.bold('\n Meta-Learning'));
|
|
8371
|
-
console.log(` Avg regret: ${status.meta_avg_regret || 0}`);
|
|
8372
|
-
console.log(` Plateau: ${status.meta_plateau_status || 'unknown'}`);
|
|
8373
|
-
console.log(chalk.bold('\n Midstream'));
|
|
8374
|
-
console.log(` Scheduler ticks: ${status.midstream_scheduler_ticks || 0}`);
|
|
8375
|
-
console.log(` Attractor categories: ${status.midstream_attractor_categories || 0}`);
|
|
8376
|
-
console.log(` Strange-loop: v${status.midstream_strange_loop_version || '?'}`);
|
|
8377
|
-
console.log();
|
|
8378
|
-
} catch (e) { console.error(chalk.red(`Error: ${e.message}`)); process.exit(1); }
|
|
8379
|
-
});
|
|
8380
|
-
|
|
8381
|
-
agiCmd.command('sona')
|
|
8382
|
-
.description('SONA learning engine — patterns, trajectories, background ticks')
|
|
8383
|
-
.option('--url <url>', 'Brain server URL')
|
|
8384
|
-
.option('--key <key>', 'Pi key')
|
|
8385
|
-
.option('--json', 'Output as JSON')
|
|
8386
|
-
.action(async (opts) => {
|
|
8387
|
-
const config = getBrainConfig(opts);
|
|
8388
|
-
try {
|
|
8389
|
-
const data = await fetchBrainEndpoint(config, '/v1/sona/stats');
|
|
8390
|
-
if (opts.json || !process.stdout.isTTY) { console.log(JSON.stringify(data, null, 2)); return; }
|
|
8391
|
-
console.log(chalk.bold.cyan('\n SONA Learning Engine\n'));
|
|
8392
|
-
Object.entries(data).forEach(([k, v]) => console.log(` ${chalk.bold(k + ':')} ${v}`));
|
|
8393
|
-
console.log();
|
|
8394
|
-
} catch (e) { console.error(chalk.red(`Error: ${e.message}`)); process.exit(1); }
|
|
8395
|
-
});
|
|
8396
|
-
|
|
8397
|
-
agiCmd.command('temporal')
|
|
8398
|
-
.description('Temporal delta tracking — velocity, trend, total deltas')
|
|
8399
|
-
.option('--url <url>', 'Brain server URL')
|
|
8400
|
-
.option('--key <key>', 'Pi key')
|
|
8401
|
-
.option('--json', 'Output as JSON')
|
|
8402
|
-
.action(async (opts) => {
|
|
8403
|
-
const config = getBrainConfig(opts);
|
|
8404
|
-
try {
|
|
8405
|
-
const data = await fetchBrainEndpoint(config, '/v1/temporal');
|
|
8406
|
-
if (opts.json || !process.stdout.isTTY) { console.log(JSON.stringify(data, null, 2)); return; }
|
|
8407
|
-
console.log(chalk.bold.cyan('\n Temporal Delta Tracking\n'));
|
|
8408
|
-
Object.entries(data).forEach(([k, v]) => console.log(` ${chalk.bold(k + ':')} ${v}`));
|
|
8409
|
-
console.log();
|
|
8410
|
-
} catch (e) { console.error(chalk.red(`Error: ${e.message}`)); process.exit(1); }
|
|
8411
|
-
});
|
|
8412
|
-
|
|
8413
|
-
agiCmd.command('explore')
|
|
8414
|
-
.description('Meta-learning exploration — curiosity, regret, plateau, Pareto')
|
|
8415
|
-
.option('--url <url>', 'Brain server URL')
|
|
8416
|
-
.option('--key <key>', 'Pi key')
|
|
8417
|
-
.option('--json', 'Output as JSON')
|
|
8418
|
-
.action(async (opts) => {
|
|
8419
|
-
const config = getBrainConfig(opts);
|
|
8420
|
-
try {
|
|
8421
|
-
const data = await fetchBrainEndpoint(config, '/v1/explore');
|
|
8422
|
-
if (opts.json || !process.stdout.isTTY) { console.log(JSON.stringify(data, null, 2)); return; }
|
|
8423
|
-
console.log(chalk.bold.cyan('\n Meta-Learning Exploration\n'));
|
|
8424
|
-
Object.entries(data).forEach(([k, v]) => console.log(` ${chalk.bold(k + ':')} ${v}`));
|
|
8425
|
-
console.log();
|
|
8426
|
-
} catch (e) { console.error(chalk.red(`Error: ${e.message}`)); process.exit(1); }
|
|
8427
|
-
});
|
|
8428
|
-
|
|
8429
|
-
agiCmd.command('midstream')
|
|
8430
|
-
.description('Midstream platform — scheduler, attractor, solver, strange-loop')
|
|
8431
|
-
.option('--url <url>', 'Brain server URL')
|
|
8432
|
-
.option('--key <key>', 'Pi key')
|
|
8433
|
-
.option('--json', 'Output as JSON')
|
|
8434
|
-
.action(async (opts) => {
|
|
8435
|
-
const config = getBrainConfig(opts);
|
|
8436
|
-
try {
|
|
8437
|
-
const data = await fetchBrainEndpoint(config, '/v1/midstream');
|
|
8438
|
-
if (opts.json || !process.stdout.isTTY) { console.log(JSON.stringify(data, null, 2)); return; }
|
|
8439
|
-
console.log(chalk.bold.cyan('\n Midstream Platform\n'));
|
|
8440
|
-
Object.entries(data).forEach(([k, v]) => {
|
|
8441
|
-
if (typeof v === 'object' && v !== null) {
|
|
8442
|
-
console.log(` ${chalk.bold(k + ':')}`);
|
|
8443
|
-
Object.entries(v).forEach(([sk, sv]) => console.log(` ${chalk.dim(sk + ':')} ${sv}`));
|
|
8059
|
+
spinner.succeed(chalk.green(`Brainpedia ${action} complete`));
|
|
8060
|
+
if (isJsonOutput(opts)) {
|
|
8061
|
+
console.log(JSON.stringify(result, null, 2));
|
|
8062
|
+
} else {
|
|
8063
|
+
if (action === 'list') {
|
|
8064
|
+
const pages = Array.isArray(result) ? result : (result.pages || []);
|
|
8065
|
+
pages.forEach((p, i) => {
|
|
8066
|
+
console.log(chalk.cyan(` ${i + 1}. ${p.title || p.id || 'Untitled'}`));
|
|
8067
|
+
});
|
|
8444
8068
|
} else {
|
|
8445
|
-
console.log(` ${
|
|
8446
|
-
}
|
|
8447
|
-
});
|
|
8448
|
-
console.log();
|
|
8449
|
-
} catch (e) { console.error(chalk.red(`Error: ${e.message}`)); process.exit(1); }
|
|
8450
|
-
});
|
|
8451
|
-
|
|
8452
|
-
agiCmd.command('flags')
|
|
8453
|
-
.description('Show feature flag state from backend')
|
|
8454
|
-
.option('--url <url>', 'Brain server URL')
|
|
8455
|
-
.option('--key <key>', 'Pi key')
|
|
8456
|
-
.option('--json', 'Output as JSON')
|
|
8457
|
-
.action(async (opts) => {
|
|
8458
|
-
const config = getBrainConfig(opts);
|
|
8459
|
-
try {
|
|
8460
|
-
const status = await fetchBrainEndpoint(config, '/v1/status');
|
|
8461
|
-
const flags = {};
|
|
8462
|
-
for (const [k, v] of Object.entries(status)) {
|
|
8463
|
-
if (typeof v === 'boolean' || k.startsWith('rvf_') || k.endsWith('_enabled')) {
|
|
8464
|
-
flags[k] = v;
|
|
8069
|
+
console.log(chalk.dim(` ${JSON.stringify(result, null, 2)}`));
|
|
8465
8070
|
}
|
|
8466
8071
|
}
|
|
8467
|
-
|
|
8468
|
-
|
|
8469
|
-
|
|
8470
|
-
|
|
8471
|
-
|
|
8472
|
-
});
|
|
8473
|
-
console.log();
|
|
8474
|
-
} catch (e) { console.error(chalk.red(`Error: ${e.message}`)); process.exit(1); }
|
|
8072
|
+
} catch (error) {
|
|
8073
|
+
spinner.fail(chalk.red(`Brainpedia ${action} failed`));
|
|
8074
|
+
console.error(chalk.red(error.message));
|
|
8075
|
+
process.exit(1);
|
|
8076
|
+
}
|
|
8475
8077
|
});
|
|
8476
8078
|
|
|
8477
8079
|
// ============================================================================
|
|
8478
|
-
//
|
|
8080
|
+
// Edge commands — distributed compute network
|
|
8479
8081
|
// ============================================================================
|
|
8480
8082
|
|
|
8481
|
-
const
|
|
8083
|
+
const EDGE_GENESIS_URL = 'https://edge-net-genesis-875130704813.us-central1.run.app';
|
|
8084
|
+
const EDGE_DASHBOARD_URL = 'https://edge-net-dashboard-875130704813.us-central1.run.app';
|
|
8482
8085
|
|
|
8483
|
-
|
|
8484
|
-
.
|
|
8485
|
-
.
|
|
8486
|
-
.option('--
|
|
8487
|
-
.option('--json', 'Output as JSON')
|
|
8488
|
-
.action(async (opts) => {
|
|
8489
|
-
const config = getBrainConfig(opts);
|
|
8490
|
-
try {
|
|
8491
|
-
const data = await fetchBrainEndpoint(config, '/v1/midstream');
|
|
8492
|
-
if (opts.json || !process.stdout.isTTY) { console.log(JSON.stringify(data, null, 2)); return; }
|
|
8493
|
-
console.log(chalk.bold.cyan('\n Midstream Platform Status\n'));
|
|
8494
|
-
Object.entries(data).forEach(([k, v]) => {
|
|
8495
|
-
if (typeof v === 'object' && v !== null) {
|
|
8496
|
-
console.log(` ${chalk.bold(k + ':')}`);
|
|
8497
|
-
Object.entries(v).forEach(([sk, sv]) => console.log(` ${chalk.dim(sk + ':')} ${sv}`));
|
|
8498
|
-
} else {
|
|
8499
|
-
console.log(` ${chalk.bold(k + ':')} ${v}`);
|
|
8500
|
-
}
|
|
8501
|
-
});
|
|
8502
|
-
console.log();
|
|
8503
|
-
} catch (e) { console.error(chalk.red(`Error: ${e.message}`)); process.exit(1); }
|
|
8504
|
-
});
|
|
8086
|
+
const edgeCmd = program
|
|
8087
|
+
.command('edge')
|
|
8088
|
+
.description('Distributed edge compute network — status, tasks, and rUv balance')
|
|
8089
|
+
.option('--json', 'Force JSON output');
|
|
8505
8090
|
|
|
8506
|
-
|
|
8507
|
-
.
|
|
8508
|
-
.
|
|
8509
|
-
.
|
|
8510
|
-
|
|
8511
|
-
|
|
8512
|
-
|
|
8513
|
-
|
|
8514
|
-
|
|
8515
|
-
const
|
|
8516
|
-
|
|
8517
|
-
|
|
8518
|
-
|
|
8519
|
-
if (!entry) { console.log(chalk.yellow(` No attractor data for category: ${category}`)); return; }
|
|
8520
|
-
console.log(chalk.bold.cyan(`\n Attractor: ${category}\n`));
|
|
8521
|
-
Object.entries(entry).forEach(([k, v]) => console.log(` ${chalk.bold(k + ':')} ${v}`));
|
|
8091
|
+
edgeCmd
|
|
8092
|
+
.command('status')
|
|
8093
|
+
.description('Query edge network status from genesis node')
|
|
8094
|
+
.action(async () => {
|
|
8095
|
+
const opts = edgeCmd.opts();
|
|
8096
|
+
const spinner = ora('Querying edge network...').start();
|
|
8097
|
+
try {
|
|
8098
|
+
const res = await fetch(`${EDGE_GENESIS_URL}/status`);
|
|
8099
|
+
if (!res.ok) throw new Error(`Genesis returned ${res.status}`);
|
|
8100
|
+
const result = await res.json();
|
|
8101
|
+
spinner.succeed(chalk.green('Edge network status'));
|
|
8102
|
+
if (isJsonOutput(opts)) {
|
|
8103
|
+
console.log(JSON.stringify(result, null, 2));
|
|
8522
8104
|
} else {
|
|
8523
|
-
|
|
8524
|
-
console.log(chalk.
|
|
8525
|
-
|
|
8526
|
-
|
|
8527
|
-
|
|
8528
|
-
});
|
|
8529
|
-
} else {
|
|
8530
|
-
console.log(chalk.dim(` ${typeof attractors === 'number' ? attractors + ' categories tracked' : 'No attractor data available'}`));
|
|
8531
|
-
}
|
|
8105
|
+
console.log(chalk.cyan(` Nodes: ${result.total_nodes != null ? result.total_nodes : 'N/A'}`));
|
|
8106
|
+
console.log(chalk.cyan(` Active: ${result.active_nodes != null ? result.active_nodes : 'N/A'}`));
|
|
8107
|
+
console.log(chalk.cyan(` rUv Supply: ${result.ruv_supply != null ? result.ruv_supply : 'N/A'}`));
|
|
8108
|
+
console.log(chalk.cyan(` Phase: ${result.phase || result.sunset_phase || 'N/A'}`));
|
|
8109
|
+
if (result.uptime) console.log(chalk.gray(` Uptime: ${result.uptime}`));
|
|
8532
8110
|
}
|
|
8533
|
-
|
|
8534
|
-
|
|
8111
|
+
} catch (error) {
|
|
8112
|
+
spinner.fail(chalk.red('Failed to reach edge network'));
|
|
8113
|
+
console.error(chalk.red(error.message));
|
|
8114
|
+
process.exit(1);
|
|
8115
|
+
}
|
|
8535
8116
|
});
|
|
8536
8117
|
|
|
8537
|
-
|
|
8538
|
-
.
|
|
8539
|
-
.
|
|
8540
|
-
.
|
|
8541
|
-
|
|
8542
|
-
|
|
8543
|
-
|
|
8544
|
-
|
|
8545
|
-
|
|
8546
|
-
|
|
8547
|
-
|
|
8548
|
-
|
|
8549
|
-
|
|
8550
|
-
|
|
8118
|
+
edgeCmd
|
|
8119
|
+
.command('join')
|
|
8120
|
+
.description('Join the edge compute network as a node')
|
|
8121
|
+
.action(async () => {
|
|
8122
|
+
console.log(chalk.bold.cyan('\n Edge-Net: Join as Compute Node\n'));
|
|
8123
|
+
console.log(chalk.white(' The edge compute network currently runs in-browser via WASM + Web Workers.'));
|
|
8124
|
+
console.log(chalk.white(' To join as a compute node, open the dashboard in your browser:\n'));
|
|
8125
|
+
console.log(chalk.yellow(` ${EDGE_DASHBOARD_URL}\n`));
|
|
8126
|
+
console.log(chalk.gray(' Node.js headless join is planned for a future release.'));
|
|
8127
|
+
console.log(chalk.gray(' Set PI=<your-key> to use your identity when joining.\n'));
|
|
8128
|
+
});
|
|
8129
|
+
|
|
8130
|
+
edgeCmd
|
|
8131
|
+
.command('balance [nodeId]')
|
|
8132
|
+
.description('Check rUv balance for a node')
|
|
8133
|
+
.action(async (nodeId) => {
|
|
8134
|
+
const opts = edgeCmd.opts();
|
|
8135
|
+
const id = nodeId || process.env.PI || 'anonymous';
|
|
8136
|
+
const spinner = ora('Fetching rUv balance...').start();
|
|
8137
|
+
try {
|
|
8138
|
+
const res = await fetch(`${EDGE_GENESIS_URL}/balance/${encodeURIComponent(id)}`);
|
|
8139
|
+
if (!res.ok) throw new Error(`Balance query failed (${res.status})`);
|
|
8140
|
+
const result = await res.json();
|
|
8141
|
+
spinner.succeed(chalk.green('rUv balance'));
|
|
8142
|
+
if (isJsonOutput(opts)) {
|
|
8143
|
+
console.log(JSON.stringify(result, null, 2));
|
|
8551
8144
|
} else {
|
|
8552
|
-
console.log(
|
|
8145
|
+
console.log(chalk.cyan(` Node: ${id}`));
|
|
8146
|
+
console.log(chalk.cyan(` Balance: ${result.balance != null ? result.balance : JSON.stringify(result)} rUv`));
|
|
8553
8147
|
}
|
|
8554
|
-
|
|
8555
|
-
|
|
8556
|
-
|
|
8557
|
-
|
|
8558
|
-
midstreamCmd.command('benchmark')
|
|
8559
|
-
.description('Run latency benchmark against brain backend')
|
|
8560
|
-
.option('--url <url>', 'Brain server URL')
|
|
8561
|
-
.option('--key <key>', 'Pi key')
|
|
8562
|
-
.option('-n, --concurrent <n>', 'Concurrent search requests', '20')
|
|
8563
|
-
.option('--json', 'Output as JSON')
|
|
8564
|
-
.action(async (opts) => {
|
|
8565
|
-
const config = getBrainConfig(opts);
|
|
8566
|
-
const baseUrl = config.url || 'https://pi.ruv.io';
|
|
8567
|
-
const headers = {};
|
|
8568
|
-
if (config.key) headers['Authorization'] = `Bearer ${config.key}`;
|
|
8569
|
-
const concurrentN = Math.min(parseInt(opts.concurrent) || 20, 100);
|
|
8570
|
-
|
|
8571
|
-
async function timeRequest(url, label) {
|
|
8572
|
-
const start = performance.now();
|
|
8573
|
-
const resp = await proxyFetch(url, { headers, signal: AbortSignal.timeout(30000) });
|
|
8574
|
-
const elapsed = performance.now() - start;
|
|
8575
|
-
return { label, status: resp.status, elapsed };
|
|
8576
|
-
}
|
|
8577
|
-
|
|
8578
|
-
function percentile(sorted, p) {
|
|
8579
|
-
const idx = Math.ceil(sorted.length * p / 100) - 1;
|
|
8580
|
-
return sorted[Math.max(0, idx)];
|
|
8148
|
+
} catch (error) {
|
|
8149
|
+
spinner.fail(chalk.red('Failed to fetch balance'));
|
|
8150
|
+
console.error(chalk.red(error.message));
|
|
8151
|
+
process.exit(1);
|
|
8581
8152
|
}
|
|
8153
|
+
});
|
|
8582
8154
|
|
|
8583
|
-
|
|
8584
|
-
|
|
8585
|
-
|
|
8586
|
-
|
|
8587
|
-
|
|
8588
|
-
|
|
8589
|
-
|
|
8590
|
-
|
|
8591
|
-
|
|
8592
|
-
|
|
8593
|
-
|
|
8594
|
-
|
|
8595
|
-
|
|
8596
|
-
|
|
8597
|
-
|
|
8598
|
-
|
|
8599
|
-
|
|
8155
|
+
edgeCmd
|
|
8156
|
+
.command('tasks')
|
|
8157
|
+
.description('List available distributed compute tasks')
|
|
8158
|
+
.action(async () => {
|
|
8159
|
+
const opts = edgeCmd.opts();
|
|
8160
|
+
const spinner = ora('Fetching compute tasks...').start();
|
|
8161
|
+
try {
|
|
8162
|
+
const res = await fetch(`${EDGE_GENESIS_URL}/tasks`);
|
|
8163
|
+
if (!res.ok) throw new Error(`Tasks query failed (${res.status})`);
|
|
8164
|
+
const result = await res.json();
|
|
8165
|
+
const tasks = Array.isArray(result) ? result : (result.tasks || []);
|
|
8166
|
+
spinner.succeed(chalk.green(`${tasks.length} task(s) available`));
|
|
8167
|
+
if (isJsonOutput(opts)) {
|
|
8168
|
+
console.log(JSON.stringify(result, null, 2));
|
|
8169
|
+
} else {
|
|
8170
|
+
if (tasks.length === 0) {
|
|
8171
|
+
console.log(chalk.gray(' No compute tasks currently available.'));
|
|
8172
|
+
} else {
|
|
8173
|
+
tasks.forEach((task, i) => {
|
|
8174
|
+
console.log(chalk.cyan(` ${i + 1}. ${task.name || task.id || 'Task'}`) +
|
|
8175
|
+
(task.reward ? chalk.yellow(` (${task.reward} rUv)`) : '') +
|
|
8176
|
+
(task.status ? chalk.gray(` [${task.status}]`) : ''));
|
|
8177
|
+
});
|
|
8600
8178
|
}
|
|
8601
|
-
sequential[ep.label] = { avg: times.reduce((a, b) => a + b, 0) / times.length, min: Math.min(...times), max: Math.max(...times) };
|
|
8602
|
-
}
|
|
8603
|
-
|
|
8604
|
-
// Concurrent search test
|
|
8605
|
-
const concurrentTimes = [];
|
|
8606
|
-
const promises = [];
|
|
8607
|
-
for (let i = 0; i < concurrentN; i++) {
|
|
8608
|
-
promises.push(timeRequest(baseUrl + '/v1/memories/search?q=test&limit=3', 'concurrent'));
|
|
8609
8179
|
}
|
|
8610
|
-
|
|
8611
|
-
|
|
8612
|
-
|
|
8613
|
-
|
|
8614
|
-
|
|
8615
|
-
|
|
8616
|
-
const benchResult = { sequential, concurrent: { count: concurrentN, p50, p90, p99 } };
|
|
8617
|
-
|
|
8618
|
-
if (opts.json || !process.stdout.isTTY) { console.log(JSON.stringify(benchResult, null, 2)); return; }
|
|
8619
|
-
|
|
8620
|
-
console.log(chalk.bold(' Sequential (3 rounds each):'));
|
|
8621
|
-
for (const [label, data] of Object.entries(sequential)) {
|
|
8622
|
-
console.log(` ${chalk.yellow(label.padEnd(12))} avg: ${data.avg.toFixed(1)}ms min: ${data.min.toFixed(1)}ms max: ${data.max.toFixed(1)}ms`);
|
|
8623
|
-
}
|
|
8624
|
-
console.log(chalk.bold(`\n Concurrent (${concurrentN}x search):`));
|
|
8625
|
-
console.log(` p50: ${p50.toFixed(1)}ms p90: ${p90.toFixed(1)}ms p99: ${p99.toFixed(1)}ms`);
|
|
8626
|
-
console.log();
|
|
8627
|
-
} catch (e) { console.error(chalk.red(`Error: ${e.message}`)); process.exit(1); }
|
|
8628
|
-
});
|
|
8629
|
-
|
|
8630
|
-
// ============================================================================
|
|
8631
|
-
// Edge Commands — Distributed compute via @ruvector/edge-net
|
|
8632
|
-
// ============================================================================
|
|
8633
|
-
|
|
8634
|
-
const edgeCmd = program.command('edge').description('Distributed P2P compute network — status, join, balance, tasks');
|
|
8635
|
-
|
|
8636
|
-
const EDGE_GENESIS = 'https://edge-net-genesis-875130704813.us-central1.run.app';
|
|
8637
|
-
|
|
8638
|
-
edgeCmd.command('status')
|
|
8639
|
-
.description('Show edge compute network status')
|
|
8640
|
-
.option('--json', 'Output as JSON')
|
|
8641
|
-
.action(async (opts) => {
|
|
8642
|
-
try {
|
|
8643
|
-
const resp = await proxyFetch(`${EDGE_GENESIS}/status`, { signal: AbortSignal.timeout(30000) });
|
|
8644
|
-
const data = await resp.json();
|
|
8645
|
-
if (opts.json || !process.stdout.isTTY) { console.log(JSON.stringify(data, null, 2)); return; }
|
|
8646
|
-
console.log(chalk.bold.cyan('\nEdge Network Status\n'));
|
|
8647
|
-
Object.entries(data).forEach(([k, v]) => console.log(` ${chalk.bold(k + ':')} ${v}`));
|
|
8648
|
-
console.log();
|
|
8649
|
-
} catch (e) { console.error(chalk.red(`Error: ${e.message}`)); }
|
|
8650
|
-
});
|
|
8651
|
-
|
|
8652
|
-
edgeCmd.command('join')
|
|
8653
|
-
.description('Join the edge compute network as a compute node')
|
|
8654
|
-
.option('--contribution <level>', 'Contribution level 0.0-1.0', '0.3')
|
|
8655
|
-
.action(async (opts) => {
|
|
8656
|
-
const piKey = process.env.PI;
|
|
8657
|
-
if (!piKey) { console.error(chalk.red('Set PI environment variable first. Run: npx ruvector identity generate')); process.exit(1); }
|
|
8658
|
-
try {
|
|
8659
|
-
const resp = await proxyFetch(`${EDGE_GENESIS}/join`, {
|
|
8660
|
-
method: 'POST',
|
|
8661
|
-
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${piKey}` },
|
|
8662
|
-
body: JSON.stringify({ contribution: parseFloat(opts.contribution), pi_key: piKey }),
|
|
8663
|
-
signal: AbortSignal.timeout(30000)
|
|
8664
|
-
});
|
|
8665
|
-
const data = await resp.json();
|
|
8666
|
-
console.log(chalk.green(`Joined edge network: ${data.node_id || 'OK'}`));
|
|
8667
|
-
} catch (e) { console.error(chalk.red(`Error: ${e.message}`)); }
|
|
8180
|
+
} catch (error) {
|
|
8181
|
+
spinner.fail(chalk.red('Failed to fetch tasks'));
|
|
8182
|
+
console.error(chalk.red(error.message));
|
|
8183
|
+
process.exit(1);
|
|
8184
|
+
}
|
|
8668
8185
|
});
|
|
8669
8186
|
|
|
8670
|
-
edgeCmd
|
|
8671
|
-
.
|
|
8672
|
-
.
|
|
8673
|
-
.action(async (
|
|
8674
|
-
|
|
8675
|
-
if (!piKey) { console.error(chalk.red('Set PI environment variable first.')); process.exit(1); }
|
|
8676
|
-
try {
|
|
8677
|
-
const pseudonym = require('crypto').createHash('shake256', { outputLength: 16 }).update(piKey).digest('hex');
|
|
8678
|
-
const resp = await proxyFetch(`${EDGE_GENESIS}/balance/${pseudonym}`, { headers: { 'Authorization': `Bearer ${piKey}` }, signal: AbortSignal.timeout(30000) });
|
|
8679
|
-
if (!resp.ok) { console.error(chalk.red(`Edge network returned ${resp.status} ${resp.statusText}`)); process.exit(1); }
|
|
8680
|
-
const data = await resp.json();
|
|
8681
|
-
if (opts.json || !process.stdout.isTTY) { console.log(JSON.stringify(data, null, 2)); return; }
|
|
8682
|
-
console.log(chalk.bold.cyan('\nrUv Balance\n'));
|
|
8683
|
-
console.log(` ${chalk.bold('Balance:')} ${data.balance || 0} rUv`);
|
|
8684
|
-
console.log();
|
|
8685
|
-
} catch (e) { console.error(chalk.red(`Error: ${e.message}`)); }
|
|
8686
|
-
});
|
|
8687
|
-
|
|
8688
|
-
edgeCmd.command('tasks')
|
|
8689
|
-
.description('List available distributed compute tasks')
|
|
8690
|
-
.option('-l, --limit <n>', 'Max tasks', '20')
|
|
8691
|
-
.option('--json', 'Output as JSON')
|
|
8692
|
-
.action(async (opts) => {
|
|
8693
|
-
try {
|
|
8694
|
-
const resp = await proxyFetch(`${EDGE_GENESIS}/tasks?limit=${opts.limit}`, { signal: AbortSignal.timeout(30000) });
|
|
8695
|
-
const data = await resp.json();
|
|
8696
|
-
if (opts.json || !process.stdout.isTTY) { console.log(JSON.stringify(data, null, 2)); return; }
|
|
8697
|
-
console.log(chalk.bold.cyan('\nEdge Compute Tasks\n'));
|
|
8698
|
-
const tasks = Array.isArray(data) ? data : data.tasks || [];
|
|
8699
|
-
if (!tasks.length) { console.log(chalk.dim(' No tasks available.\n')); return; }
|
|
8700
|
-
tasks.forEach((t, i) => console.log(` ${chalk.yellow(i + 1 + '.')} ${t.name || t.id} ${chalk.dim(`[${t.status || 'pending'}]`)}`));
|
|
8701
|
-
console.log();
|
|
8702
|
-
} catch (e) { console.error(chalk.red(`Error: ${e.message}`)); }
|
|
8703
|
-
});
|
|
8704
|
-
|
|
8705
|
-
edgeCmd.command('dashboard')
|
|
8706
|
-
.description('Open edge-net dashboard in browser')
|
|
8707
|
-
.action(() => {
|
|
8708
|
-
const url = 'https://edge-net-dashboard-875130704813.us-central1.run.app';
|
|
8709
|
-
console.log(chalk.cyan(`Dashboard: ${url}`));
|
|
8187
|
+
edgeCmd
|
|
8188
|
+
.command('dashboard')
|
|
8189
|
+
.description('Open the edge-net dashboard in your default browser')
|
|
8190
|
+
.action(async () => {
|
|
8191
|
+
console.log(chalk.cyan(`\n Opening edge-net dashboard: ${EDGE_DASHBOARD_URL}\n`));
|
|
8710
8192
|
try {
|
|
8711
8193
|
const { execSync } = require('child_process');
|
|
8712
|
-
const
|
|
8713
|
-
|
|
8714
|
-
|
|
8194
|
+
const platform = process.platform;
|
|
8195
|
+
if (platform === 'darwin') {
|
|
8196
|
+
execSync(`open "${EDGE_DASHBOARD_URL}"`);
|
|
8197
|
+
} else if (platform === 'win32') {
|
|
8198
|
+
execSync(`start "" "${EDGE_DASHBOARD_URL}"`);
|
|
8199
|
+
} else {
|
|
8200
|
+
execSync(`xdg-open "${EDGE_DASHBOARD_URL}" 2>/dev/null || echo "Open manually: ${EDGE_DASHBOARD_URL}"`);
|
|
8201
|
+
}
|
|
8202
|
+
} catch {
|
|
8203
|
+
console.log(chalk.yellow(` Could not open browser. Visit manually:`));
|
|
8204
|
+
console.log(chalk.white(` ${EDGE_DASHBOARD_URL}\n`));
|
|
8205
|
+
}
|
|
8715
8206
|
});
|
|
8716
8207
|
|
|
8717
8208
|
// ============================================================================
|
|
8718
|
-
// Identity
|
|
8209
|
+
// Identity commands — manage your pi key
|
|
8719
8210
|
// ============================================================================
|
|
8720
8211
|
|
|
8721
|
-
const identityCmd = program
|
|
8722
|
-
|
|
8723
|
-
|
|
8724
|
-
.
|
|
8725
|
-
|
|
8212
|
+
const identityCmd = program
|
|
8213
|
+
.command('identity')
|
|
8214
|
+
.description('Manage your pi identity key for brain, edge, and MCP')
|
|
8215
|
+
.option('--json', 'Force JSON output');
|
|
8216
|
+
|
|
8217
|
+
identityCmd
|
|
8218
|
+
.command('generate')
|
|
8219
|
+
.description('Generate a new pi identity key (64 hex chars)')
|
|
8220
|
+
.option('--save', 'Save the key to ~/.ruvector/pi-key')
|
|
8221
|
+
.action(async (cmdOpts) => {
|
|
8222
|
+
const opts = identityCmd.opts();
|
|
8726
8223
|
const crypto = require('crypto');
|
|
8727
8224
|
const key = crypto.randomBytes(32).toString('hex');
|
|
8728
|
-
|
|
8729
|
-
|
|
8730
|
-
|
|
8731
|
-
|
|
8732
|
-
|
|
8733
|
-
|
|
8734
|
-
|
|
8735
|
-
|
|
8736
|
-
|
|
8225
|
+
let pseudonym;
|
|
8226
|
+
try {
|
|
8227
|
+
const hash = crypto.createHash('shake256', { outputLength: 16 });
|
|
8228
|
+
hash.update(key);
|
|
8229
|
+
pseudonym = hash.digest('hex');
|
|
8230
|
+
} catch {
|
|
8231
|
+
pseudonym = crypto.createHash('sha256').update(key).digest('hex').substring(0, 32);
|
|
8232
|
+
}
|
|
8233
|
+
if (isJsonOutput(opts)) {
|
|
8234
|
+
console.log(JSON.stringify({ key, pseudonym }, null, 2));
|
|
8235
|
+
} else {
|
|
8236
|
+
console.log(chalk.bold.cyan('\n New Pi Identity Generated\n'));
|
|
8237
|
+
console.log(chalk.white(` Key: ${key}`));
|
|
8238
|
+
console.log(chalk.gray(` Pseudonym: ${pseudonym}`));
|
|
8239
|
+
console.log(chalk.yellow('\n Save this key! Set it as PI environment variable:'));
|
|
8240
|
+
console.log(chalk.dim(` export PI=${key}\n`));
|
|
8241
|
+
}
|
|
8242
|
+
if (cmdOpts.save) {
|
|
8243
|
+
const keyDir = path.join(require('os').homedir(), '.ruvector');
|
|
8244
|
+
const keyPath = path.join(keyDir, 'pi-key');
|
|
8245
|
+
if (!fs.existsSync(keyDir)) fs.mkdirSync(keyDir, { recursive: true, mode: 0o700 });
|
|
8246
|
+
fs.writeFileSync(keyPath, key, { mode: 0o600 });
|
|
8247
|
+
console.log(chalk.green(` Key saved to ${keyPath}`));
|
|
8248
|
+
}
|
|
8737
8249
|
});
|
|
8738
8250
|
|
|
8739
|
-
identityCmd
|
|
8740
|
-
.
|
|
8741
|
-
.
|
|
8742
|
-
.action((
|
|
8743
|
-
const
|
|
8744
|
-
|
|
8745
|
-
|
|
8746
|
-
|
|
8251
|
+
identityCmd
|
|
8252
|
+
.command('show')
|
|
8253
|
+
.description('Show current identity pseudonym derived from your pi key')
|
|
8254
|
+
.action(async () => {
|
|
8255
|
+
const opts = identityCmd.opts();
|
|
8256
|
+
const crypto = require('crypto');
|
|
8257
|
+
let key = process.env.PI;
|
|
8258
|
+
if (!key) {
|
|
8259
|
+
const keyPath = path.join(require('os').homedir(), '.ruvector', 'pi-key');
|
|
8260
|
+
if (fs.existsSync(keyPath)) {
|
|
8261
|
+
key = fs.readFileSync(keyPath, 'utf8').trim();
|
|
8262
|
+
}
|
|
8263
|
+
}
|
|
8264
|
+
if (!key) {
|
|
8265
|
+
console.error(chalk.red('No pi key found. Set PI env var or run: ruvector identity generate --save'));
|
|
8747
8266
|
process.exit(1);
|
|
8748
8267
|
}
|
|
8749
|
-
|
|
8750
|
-
|
|
8751
|
-
|
|
8752
|
-
|
|
8753
|
-
|
|
8754
|
-
|
|
8755
|
-
|
|
8756
|
-
|
|
8757
|
-
|
|
8758
|
-
|
|
8268
|
+
let pseudonym;
|
|
8269
|
+
try {
|
|
8270
|
+
const hash = crypto.createHash('shake256', { outputLength: 16 });
|
|
8271
|
+
hash.update(key);
|
|
8272
|
+
pseudonym = hash.digest('hex');
|
|
8273
|
+
} catch {
|
|
8274
|
+
pseudonym = crypto.createHash('sha256').update(key).digest('hex').substring(0, 32);
|
|
8275
|
+
}
|
|
8276
|
+
if (isJsonOutput(opts)) {
|
|
8277
|
+
console.log(JSON.stringify({
|
|
8278
|
+
pseudonym,
|
|
8279
|
+
key_preview: key.substring(0, 8) + '...' + key.substring(key.length - 8),
|
|
8280
|
+
source: process.env.PI ? 'PI env var' : '~/.ruvector/pi-key',
|
|
8281
|
+
}, null, 2));
|
|
8282
|
+
} else {
|
|
8283
|
+
console.log(chalk.bold.cyan('\n Pi Identity\n'));
|
|
8284
|
+
console.log(chalk.white(` Pseudonym: ${pseudonym}`));
|
|
8285
|
+
console.log(chalk.gray(` Key: ${key.substring(0, 8)}...${key.substring(key.length - 8)}`));
|
|
8286
|
+
console.log(chalk.gray(` Source: ${process.env.PI ? 'PI env var' : '~/.ruvector/pi-key'}\n`));
|
|
8759
8287
|
}
|
|
8760
|
-
console.log(chalk.bold.cyan('\nPi Identity\n'));
|
|
8761
|
-
console.log(` ${chalk.bold('Key:')} ${piKey.slice(0, 8)}...${piKey.slice(-8)}`);
|
|
8762
|
-
console.log(` ${chalk.bold('Pseudonym:')} ${chalk.green(pseudonym)}`);
|
|
8763
|
-
console.log(` ${chalk.bold('MCP Token:')} ${chalk.dim(mcpToken)}`);
|
|
8764
|
-
console.log(` ${chalk.bold('Edge Key:')} ${chalk.dim(edgeKey)}`);
|
|
8765
|
-
console.log();
|
|
8766
8288
|
});
|
|
8767
8289
|
|
|
8768
|
-
identityCmd
|
|
8769
|
-
.
|
|
8770
|
-
.
|
|
8771
|
-
|
|
8772
|
-
|
|
8290
|
+
identityCmd
|
|
8291
|
+
.command('export')
|
|
8292
|
+
.description('Export your pi key encrypted with a passphrase')
|
|
8293
|
+
.option('-o, --output <file>', 'Output file', 'pi-key.enc')
|
|
8294
|
+
.action(async (cmdOpts) => {
|
|
8295
|
+
const opts = identityCmd.opts();
|
|
8773
8296
|
const crypto = require('crypto');
|
|
8774
|
-
|
|
8775
|
-
|
|
8776
|
-
|
|
8777
|
-
|
|
8778
|
-
|
|
8297
|
+
let key = process.env.PI;
|
|
8298
|
+
if (!key) {
|
|
8299
|
+
const keyPath = path.join(require('os').homedir(), '.ruvector', 'pi-key');
|
|
8300
|
+
if (fs.existsSync(keyPath)) {
|
|
8301
|
+
key = fs.readFileSync(keyPath, 'utf8').trim();
|
|
8302
|
+
}
|
|
8303
|
+
}
|
|
8304
|
+
if (!key) {
|
|
8305
|
+
console.error(chalk.red('No pi key found. Set PI env var or run: ruvector identity generate --save'));
|
|
8306
|
+
process.exit(1);
|
|
8307
|
+
}
|
|
8308
|
+
let passphrase = process.env.PI_PASSPHRASE;
|
|
8309
|
+
if (!passphrase) {
|
|
8310
|
+
passphrase = crypto.randomBytes(16).toString('hex');
|
|
8311
|
+
console.log(chalk.yellow(`\n Generated passphrase (save this!): ${passphrase}`));
|
|
8312
|
+
}
|
|
8313
|
+
const salt = crypto.randomBytes(16);
|
|
8314
|
+
const derivedKey = crypto.pbkdf2Sync(passphrase, salt, 100000, 32, 'sha256');
|
|
8315
|
+
const iv = crypto.randomBytes(12);
|
|
8316
|
+
const cipher = crypto.createCipheriv('aes-256-gcm', derivedKey, iv);
|
|
8317
|
+
let encrypted = cipher.update(key, 'utf8', 'hex');
|
|
8779
8318
|
encrypted += cipher.final('hex');
|
|
8780
|
-
const
|
|
8781
|
-
const
|
|
8782
|
-
|
|
8783
|
-
|
|
8784
|
-
|
|
8785
|
-
|
|
8319
|
+
const authTag = cipher.getAuthTag().toString('hex');
|
|
8320
|
+
const payload = JSON.stringify({
|
|
8321
|
+
version: 1,
|
|
8322
|
+
algorithm: 'aes-256-gcm',
|
|
8323
|
+
salt: salt.toString('hex'),
|
|
8324
|
+
iv: iv.toString('hex'),
|
|
8325
|
+
authTag,
|
|
8326
|
+
data: encrypted,
|
|
8327
|
+
});
|
|
8328
|
+
const outFile = path.resolve(cmdOpts.output);
|
|
8329
|
+
fs.writeFileSync(outFile, payload, { mode: 0o600 });
|
|
8330
|
+
if (isJsonOutput(opts)) {
|
|
8331
|
+
console.log(JSON.stringify({ file: outFile, algorithm: 'aes-256-gcm' }, null, 2));
|
|
8332
|
+
} else {
|
|
8333
|
+
console.log(chalk.green(`\n Key exported to: ${outFile}`));
|
|
8334
|
+
console.log(chalk.gray(' Encrypted with AES-256-GCM + PBKDF2'));
|
|
8335
|
+
console.log(chalk.yellow(' Keep your passphrase safe -- it cannot be recovered.\n'));
|
|
8336
|
+
}
|
|
8786
8337
|
});
|
|
8787
8338
|
|
|
8788
|
-
identityCmd
|
|
8789
|
-
.
|
|
8790
|
-
.
|
|
8791
|
-
.
|
|
8339
|
+
identityCmd
|
|
8340
|
+
.command('import <file>')
|
|
8341
|
+
.description('Import a pi key from an encrypted backup')
|
|
8342
|
+
.option('--save', 'Save the imported key to ~/.ruvector/pi-key')
|
|
8343
|
+
.action(async (file, cmdOpts) => {
|
|
8344
|
+
const opts = identityCmd.opts();
|
|
8345
|
+
const crypto = require('crypto');
|
|
8346
|
+
const filePath = path.resolve(file);
|
|
8347
|
+
if (!fs.existsSync(filePath)) {
|
|
8348
|
+
console.error(chalk.red(`File not found: ${filePath}`));
|
|
8349
|
+
process.exit(1);
|
|
8350
|
+
}
|
|
8351
|
+
const payload = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
8352
|
+
if (payload.version !== 1 || payload.algorithm !== 'aes-256-gcm') {
|
|
8353
|
+
console.error(chalk.red('Unsupported key file format'));
|
|
8354
|
+
process.exit(1);
|
|
8355
|
+
}
|
|
8356
|
+
const passphrase = process.env.PI_PASSPHRASE;
|
|
8357
|
+
if (!passphrase) {
|
|
8358
|
+
console.error(chalk.red('Set PI_PASSPHRASE env var to decrypt the key'));
|
|
8359
|
+
process.exit(1);
|
|
8360
|
+
}
|
|
8792
8361
|
try {
|
|
8793
|
-
const
|
|
8794
|
-
const
|
|
8795
|
-
const
|
|
8796
|
-
const
|
|
8797
|
-
decipher.
|
|
8798
|
-
|
|
8362
|
+
const salt = Buffer.from(payload.salt, 'hex');
|
|
8363
|
+
const iv = Buffer.from(payload.iv, 'hex');
|
|
8364
|
+
const authTag = Buffer.from(payload.authTag, 'hex');
|
|
8365
|
+
const derivedKey = crypto.pbkdf2Sync(passphrase, salt, 100000, 32, 'sha256');
|
|
8366
|
+
const decipher = crypto.createDecipheriv('aes-256-gcm', derivedKey, iv);
|
|
8367
|
+
decipher.setAuthTag(authTag);
|
|
8368
|
+
let decrypted = decipher.update(payload.data, 'hex', 'utf8');
|
|
8799
8369
|
decrypted += decipher.final('utf8');
|
|
8800
|
-
|
|
8801
|
-
|
|
8802
|
-
|
|
8370
|
+
if (!/^[0-9a-f]{64}$/i.test(decrypted)) {
|
|
8371
|
+
throw new Error('Decrypted value is not a valid pi key');
|
|
8372
|
+
}
|
|
8373
|
+
if (isJsonOutput(opts)) {
|
|
8374
|
+
console.log(JSON.stringify({
|
|
8375
|
+
key: decrypted,
|
|
8376
|
+
source: filePath,
|
|
8377
|
+
saved: !!cmdOpts.save,
|
|
8378
|
+
}, null, 2));
|
|
8379
|
+
} else {
|
|
8380
|
+
console.log(chalk.green('\n Key imported successfully'));
|
|
8381
|
+
console.log(chalk.white(` Key: ${decrypted.substring(0, 8)}...${decrypted.substring(decrypted.length - 8)}`));
|
|
8382
|
+
}
|
|
8383
|
+
if (cmdOpts.save) {
|
|
8384
|
+
const keyDir = path.join(require('os').homedir(), '.ruvector');
|
|
8385
|
+
const keyPath = path.join(keyDir, 'pi-key');
|
|
8386
|
+
if (!fs.existsSync(keyDir)) fs.mkdirSync(keyDir, { recursive: true, mode: 0o700 });
|
|
8387
|
+
fs.writeFileSync(keyPath, decrypted, { mode: 0o600 });
|
|
8388
|
+
console.log(chalk.green(` Key saved to ${keyPath}`));
|
|
8389
|
+
}
|
|
8390
|
+
if (!isJsonOutput(opts)) {
|
|
8391
|
+
console.log(chalk.yellow('\n Set as environment variable:'));
|
|
8392
|
+
console.log(chalk.dim(` export PI=${decrypted}\n`));
|
|
8393
|
+
}
|
|
8394
|
+
} catch (error) {
|
|
8395
|
+
console.error(chalk.red('Failed to decrypt key -- wrong passphrase?'));
|
|
8396
|
+
console.error(chalk.red(error.message));
|
|
8397
|
+
process.exit(1);
|
|
8398
|
+
}
|
|
8803
8399
|
});
|
|
8804
8400
|
|
|
8805
|
-
//
|
|
8806
|
-
// LLM Commands
|
|
8807
|
-
//
|
|
8808
|
-
|
|
8809
|
-
const llmCmd = program.command('llm').description('LLM embeddings and inference via @ruvector/ruvllm');
|
|
8401
|
+
// =============================================================================
|
|
8402
|
+
// LLM Commands - LLM orchestration (lazy: @ruvector/ruvllm)
|
|
8403
|
+
// =============================================================================
|
|
8810
8404
|
|
|
8811
|
-
|
|
8812
|
-
try { return require('@ruvector/ruvllm'); } catch {
|
|
8813
|
-
console.error(chalk.red('LLM commands require @ruvector/ruvllm'));
|
|
8814
|
-
console.error(chalk.yellow(' npm install @ruvector/ruvllm'));
|
|
8815
|
-
process.exit(1);
|
|
8816
|
-
}
|
|
8817
|
-
}
|
|
8405
|
+
const llmCmd = program.command('llm').description('LLM orchestration with SONA adaptive learning');
|
|
8818
8406
|
|
|
8819
|
-
llmCmd.command('embed
|
|
8820
|
-
.description('Generate text embeddings')
|
|
8821
|
-
.
|
|
8822
|
-
.option('--
|
|
8823
|
-
.
|
|
8824
|
-
|
|
8407
|
+
llmCmd.command('embed')
|
|
8408
|
+
.description('Generate text embeddings via RuvLLM')
|
|
8409
|
+
.argument('<text>', 'Text to embed')
|
|
8410
|
+
.option('-m, --model <model>', 'Model name', 'minilm')
|
|
8411
|
+
.option('--json', 'JSON output')
|
|
8412
|
+
.action(async (text, opts) => {
|
|
8413
|
+
let ruvllm;
|
|
8825
8414
|
try {
|
|
8826
|
-
|
|
8827
|
-
|
|
8828
|
-
console.
|
|
8829
|
-
console.
|
|
8830
|
-
console.
|
|
8831
|
-
console.
|
|
8832
|
-
|
|
8415
|
+
ruvllm = require('@ruvector/ruvllm');
|
|
8416
|
+
} catch (e) {
|
|
8417
|
+
console.error(chalk.red('LLM commands require @ruvector/ruvllm'));
|
|
8418
|
+
console.error(chalk.yellow(' npm install @ruvector/ruvllm'));
|
|
8419
|
+
console.error(chalk.dim(' or: npx ruvector install ruvllm'));
|
|
8420
|
+
console.error(chalk.dim('\n Tip: use "ruvector embed text <text>" for built-in ONNX embeddings'));
|
|
8421
|
+
process.exit(1);
|
|
8422
|
+
}
|
|
8423
|
+
const spinner = ora('Generating embedding...').start();
|
|
8424
|
+
try {
|
|
8425
|
+
const { performance } = require('perf_hooks');
|
|
8426
|
+
const start = performance.now();
|
|
8427
|
+
const llm = new ruvllm.RuvLLM({ embeddingDim: 384 });
|
|
8428
|
+
const embedding = llm.embed(text);
|
|
8429
|
+
const elapsed = performance.now() - start;
|
|
8430
|
+
spinner.stop();
|
|
8431
|
+
if (opts.json) {
|
|
8432
|
+
console.log(JSON.stringify({ text, model: opts.model, dimension: embedding.length, embedding: Array.from(embedding), timeMs: +elapsed.toFixed(2) }, null, 2));
|
|
8433
|
+
return;
|
|
8434
|
+
}
|
|
8435
|
+
console.log(chalk.bold.cyan('\n RuvLLM Embedding\n'));
|
|
8436
|
+
console.log(chalk.dim(` Text: "${text.length > 60 ? text.slice(0, 60) + '...' : text}"`));
|
|
8437
|
+
console.log(chalk.dim(` Model: ${opts.model}`));
|
|
8438
|
+
console.log(chalk.dim(` Dimension: ${embedding.length}`));
|
|
8439
|
+
console.log(chalk.dim(` Time: ${elapsed.toFixed(1)}ms`));
|
|
8440
|
+
console.log(chalk.dim(` First 5 values: [${Array.from(embedding).slice(0, 5).map(v => v.toFixed(4)).join(', ')}...]`));
|
|
8441
|
+
console.log('');
|
|
8442
|
+
} catch (err) {
|
|
8443
|
+
spinner.fail('Embedding generation failed');
|
|
8444
|
+
console.error(chalk.red(` ${err.message}`));
|
|
8445
|
+
process.exit(1);
|
|
8446
|
+
}
|
|
8833
8447
|
});
|
|
8834
8448
|
|
|
8835
8449
|
llmCmd.command('models')
|
|
8836
8450
|
.description('List available LLM models')
|
|
8837
|
-
.
|
|
8838
|
-
|
|
8451
|
+
.option('--json', 'JSON output')
|
|
8452
|
+
.action(async (opts) => {
|
|
8453
|
+
let ruvllm;
|
|
8839
8454
|
try {
|
|
8840
|
-
|
|
8841
|
-
|
|
8842
|
-
|
|
8843
|
-
|
|
8844
|
-
|
|
8845
|
-
|
|
8455
|
+
ruvllm = require('@ruvector/ruvllm');
|
|
8456
|
+
} catch (e) {
|
|
8457
|
+
console.error(chalk.red('LLM commands require @ruvector/ruvllm'));
|
|
8458
|
+
console.error(chalk.yellow(' npm install @ruvector/ruvllm'));
|
|
8459
|
+
console.error(chalk.dim(' or: npx ruvector install ruvllm'));
|
|
8460
|
+
process.exit(1);
|
|
8461
|
+
}
|
|
8462
|
+
const models = typeof ruvllm.listModels === 'function' ? ruvllm.listModels() :
|
|
8463
|
+
(ruvllm.RUVLTRA_MODELS ? Object.values(ruvllm.RUVLTRA_MODELS) : []);
|
|
8464
|
+
if (opts.json) {
|
|
8465
|
+
console.log(JSON.stringify(models, null, 2));
|
|
8466
|
+
return;
|
|
8467
|
+
}
|
|
8468
|
+
console.log(chalk.bold.cyan('\n RuvLLM Available Models\n'));
|
|
8469
|
+
if (!models || models.length === 0) {
|
|
8470
|
+
console.log(chalk.yellow(' No models registered in the registry.'));
|
|
8471
|
+
console.log(chalk.dim(' Upgrade @ruvector/ruvllm for model registry support:'));
|
|
8472
|
+
console.log(chalk.dim(' npm install @ruvector/ruvllm@latest\n'));
|
|
8473
|
+
} else {
|
|
8474
|
+
for (const m of models) {
|
|
8475
|
+
console.log(chalk.white(` ${chalk.bold(m.id)}`));
|
|
8476
|
+
console.log(chalk.dim(` ${m.name} - ${m.size} (${m.quantization})`));
|
|
8477
|
+
console.log(chalk.dim(` Use case: ${m.useCase}`));
|
|
8478
|
+
console.log(chalk.dim(` Context: ${m.contextLength} tokens`));
|
|
8479
|
+
console.log('');
|
|
8846
8480
|
}
|
|
8847
|
-
}
|
|
8481
|
+
}
|
|
8482
|
+
console.log(chalk.dim(` Total: ${models ? models.length : 0} models\n`));
|
|
8848
8483
|
});
|
|
8849
8484
|
|
|
8850
8485
|
llmCmd.command('benchmark')
|
|
8851
8486
|
.description('Benchmark LLM inference performance')
|
|
8852
8487
|
.option('-n, --iterations <n>', 'Number of iterations', '100')
|
|
8853
|
-
.
|
|
8854
|
-
|
|
8855
|
-
|
|
8856
|
-
|
|
8857
|
-
|
|
8858
|
-
|
|
8488
|
+
.option('--json', 'JSON output')
|
|
8489
|
+
.action(async (opts) => {
|
|
8490
|
+
let ruvllm;
|
|
8491
|
+
try {
|
|
8492
|
+
ruvllm = require('@ruvector/ruvllm');
|
|
8493
|
+
} catch (e) {
|
|
8494
|
+
console.error(chalk.red('LLM commands require @ruvector/ruvllm'));
|
|
8495
|
+
console.error(chalk.yellow(' npm install @ruvector/ruvllm'));
|
|
8496
|
+
console.error(chalk.dim(' or: npx ruvector install ruvllm'));
|
|
8497
|
+
process.exit(1);
|
|
8498
|
+
}
|
|
8499
|
+
const iterations = parseInt(opts.iterations, 10) || 100;
|
|
8500
|
+
const spinner = ora(`Running ${iterations} embedding iterations...`).start();
|
|
8501
|
+
try {
|
|
8502
|
+
const { performance } = require('perf_hooks');
|
|
8503
|
+
const llm = new ruvllm.RuvLLM({ embeddingDim: 384 });
|
|
8504
|
+
const testText = 'The quick brown fox jumps over the lazy dog';
|
|
8505
|
+
for (let i = 0; i < 5; i++) llm.embed(testText);
|
|
8506
|
+
const times = [];
|
|
8859
8507
|
const start = performance.now();
|
|
8860
|
-
|
|
8861
|
-
|
|
8862
|
-
|
|
8863
|
-
|
|
8864
|
-
|
|
8865
|
-
|
|
8866
|
-
|
|
8867
|
-
|
|
8868
|
-
|
|
8869
|
-
|
|
8870
|
-
|
|
8508
|
+
for (let i = 0; i < iterations; i++) {
|
|
8509
|
+
const t0 = performance.now();
|
|
8510
|
+
llm.embed(testText);
|
|
8511
|
+
times.push(performance.now() - t0);
|
|
8512
|
+
}
|
|
8513
|
+
const totalMs = performance.now() - start;
|
|
8514
|
+
times.sort((a, b) => a - b);
|
|
8515
|
+
const avgMs = totalMs / iterations;
|
|
8516
|
+
const p50 = times[Math.floor(iterations * 0.5)];
|
|
8517
|
+
const p95 = times[Math.floor(iterations * 0.95)];
|
|
8518
|
+
const p99 = times[Math.floor(iterations * 0.99)];
|
|
8519
|
+
const opsPerSec = (iterations / totalMs) * 1000;
|
|
8520
|
+
spinner.stop();
|
|
8521
|
+
if (opts.json) {
|
|
8522
|
+
console.log(JSON.stringify({ iterations, totalMs: +totalMs.toFixed(2), avgMs: +avgMs.toFixed(3), p50: +p50.toFixed(3), p95: +p95.toFixed(3), p99: +p99.toFixed(3), opsPerSec: +opsPerSec.toFixed(1) }, null, 2));
|
|
8523
|
+
return;
|
|
8524
|
+
}
|
|
8525
|
+
console.log(chalk.bold.cyan('\n RuvLLM Benchmark Results\n'));
|
|
8526
|
+
console.log(chalk.white(` Iterations: ${iterations}`));
|
|
8527
|
+
console.log(chalk.white(` Total time: ${totalMs.toFixed(1)}ms`));
|
|
8528
|
+
console.log(chalk.white(` Avg latency: ${avgMs.toFixed(3)}ms`));
|
|
8529
|
+
console.log(chalk.white(` P50: ${p50.toFixed(3)}ms`));
|
|
8530
|
+
console.log(chalk.white(` P95: ${p95.toFixed(3)}ms`));
|
|
8531
|
+
console.log(chalk.white(` P99: ${p99.toFixed(3)}ms`));
|
|
8532
|
+
console.log(chalk.green(` Throughput: ${opsPerSec.toFixed(1)} ops/sec`));
|
|
8533
|
+
console.log('');
|
|
8534
|
+
} catch (err) {
|
|
8535
|
+
spinner.fail('Benchmark failed');
|
|
8536
|
+
console.error(chalk.red(` ${err.message}`));
|
|
8537
|
+
process.exit(1);
|
|
8538
|
+
}
|
|
8871
8539
|
});
|
|
8872
8540
|
|
|
8873
8541
|
llmCmd.command('info')
|
|
8874
|
-
.description('Show
|
|
8875
|
-
.
|
|
8876
|
-
|
|
8877
|
-
|
|
8878
|
-
|
|
8879
|
-
|
|
8880
|
-
|
|
8542
|
+
.description('Show LLM module information')
|
|
8543
|
+
.option('--json', 'JSON output')
|
|
8544
|
+
.action(async (opts) => {
|
|
8545
|
+
let ruvllm;
|
|
8546
|
+
try {
|
|
8547
|
+
ruvllm = require('@ruvector/ruvllm');
|
|
8548
|
+
} catch (e) {
|
|
8549
|
+
console.error(chalk.red('LLM commands require @ruvector/ruvllm'));
|
|
8550
|
+
console.error(chalk.yellow(' npm install @ruvector/ruvllm'));
|
|
8551
|
+
console.error(chalk.dim(' or: npx ruvector install ruvllm'));
|
|
8552
|
+
process.exit(1);
|
|
8553
|
+
}
|
|
8554
|
+
const version = typeof ruvllm.version === 'function' ? ruvllm.version() : (ruvllm.version || 'unknown');
|
|
8555
|
+
const hasSIMD = ruvllm.hasSimdSupport ? ruvllm.hasSimdSupport() : false;
|
|
8556
|
+
const models = ruvllm.listModels ? ruvllm.listModels() : [];
|
|
8557
|
+
const info = {
|
|
8558
|
+
package: '@ruvector/ruvllm',
|
|
8559
|
+
version,
|
|
8560
|
+
simd: hasSIMD,
|
|
8561
|
+
availableModels: models.length,
|
|
8562
|
+
features: ['SONA adaptive learning', 'HNSW memory', 'FastGRNN routing', 'SIMD inference', 'LoRA adapters', 'Session management', 'Federated learning']
|
|
8563
|
+
};
|
|
8564
|
+
if (opts.json) {
|
|
8565
|
+
console.log(JSON.stringify(info, null, 2));
|
|
8566
|
+
return;
|
|
8567
|
+
}
|
|
8568
|
+
console.log(chalk.bold.cyan('\n RuvLLM Module Information\n'));
|
|
8569
|
+
console.log(chalk.white(` Package: ${info.package}`));
|
|
8570
|
+
console.log(chalk.white(` Version: ${info.version}`));
|
|
8571
|
+
console.log(chalk.white(` SIMD: ${hasSIMD ? chalk.green('Available') : chalk.yellow('Not available')}`));
|
|
8572
|
+
console.log(chalk.white(` Models: ${info.availableModels} registered`));
|
|
8573
|
+
console.log(chalk.white('\n Features:'));
|
|
8574
|
+
for (const f of info.features) {
|
|
8575
|
+
console.log(chalk.dim(` - ${f}`));
|
|
8576
|
+
}
|
|
8577
|
+
console.log('');
|
|
8881
8578
|
});
|
|
8882
8579
|
|
|
8883
|
-
//
|
|
8884
|
-
// SONA Commands
|
|
8885
|
-
//
|
|
8886
|
-
|
|
8887
|
-
const sonaCmd = program.command('sona').description('SONA adaptive learning — status, patterns, train, export');
|
|
8888
|
-
|
|
8889
|
-
function loadSona() {
|
|
8890
|
-
try { return require('@ruvector/sona'); } catch {
|
|
8891
|
-
console.error(chalk.red('SONA commands require @ruvector/sona'));
|
|
8892
|
-
console.error(chalk.yellow(' npm install @ruvector/sona'));
|
|
8893
|
-
process.exit(1);
|
|
8894
|
-
}
|
|
8895
|
-
}
|
|
8580
|
+
// =============================================================================
|
|
8581
|
+
// SONA Commands - Self-Optimizing Neural Architecture (bundled dep)
|
|
8582
|
+
// =============================================================================
|
|
8896
8583
|
|
|
8897
|
-
const
|
|
8898
|
-
function createSonaEngine(sona) {
|
|
8899
|
-
if (sona.SonaEngine) return new sona.SonaEngine(SONA_DEFAULT_DIM);
|
|
8900
|
-
if (sona.SonaCoordinator) return new sona.SonaCoordinator(SONA_DEFAULT_DIM);
|
|
8901
|
-
throw new Error('No SONA engine class found');
|
|
8902
|
-
}
|
|
8903
|
-
function parseSonaResult(val) {
|
|
8904
|
-
if (typeof val === 'string') { try { return JSON.parse(val); } catch { return val; } }
|
|
8905
|
-
return val;
|
|
8906
|
-
}
|
|
8584
|
+
const sonaCmd = program.command('sona').description('SONA Self-Optimizing Neural Architecture');
|
|
8907
8585
|
|
|
8908
8586
|
sonaCmd.command('status')
|
|
8909
|
-
.description('Show SONA learning
|
|
8910
|
-
.option('--json', '
|
|
8911
|
-
.action((opts) => {
|
|
8912
|
-
const sona = loadSona();
|
|
8587
|
+
.description('Show SONA learning status')
|
|
8588
|
+
.option('--json', 'JSON output')
|
|
8589
|
+
.action(async (opts) => {
|
|
8913
8590
|
try {
|
|
8914
|
-
const
|
|
8915
|
-
const
|
|
8916
|
-
|
|
8917
|
-
console.log(
|
|
8918
|
-
|
|
8919
|
-
console.log();
|
|
8920
|
-
|
|
8591
|
+
const { SonaEngine } = require('@ruvector/sona');
|
|
8592
|
+
const engine = new SonaEngine();
|
|
8593
|
+
const stats = engine.stats();
|
|
8594
|
+
if (opts.json) { console.log(JSON.stringify(stats, null, 2)); return; }
|
|
8595
|
+
console.log(chalk.bold.cyan('\n SONA Learning Status\n'));
|
|
8596
|
+
console.log(chalk.white(` Trajectories: ${stats.trajectoryCount || stats.trajectory_count || 0}`));
|
|
8597
|
+
console.log(chalk.white(` Patterns: ${stats.patternCount || stats.pattern_count || 0}`));
|
|
8598
|
+
console.log(chalk.white(` Energy: ${typeof stats.energy === 'number' ? stats.energy.toFixed(4) : stats.energy || 'N/A'}`));
|
|
8599
|
+
console.log(chalk.white(` State: ${stats.state || 'initialized'}`));
|
|
8600
|
+
console.log('');
|
|
8601
|
+
} catch (e) {
|
|
8602
|
+
try {
|
|
8603
|
+
const { SonaCoordinator } = require('@ruvector/ruvllm');
|
|
8604
|
+
const sona = new SonaCoordinator();
|
|
8605
|
+
const stats = sona.stats();
|
|
8606
|
+
if (opts.json) { console.log(JSON.stringify(stats, null, 2)); return; }
|
|
8607
|
+
console.log(chalk.bold.cyan('\n SONA Learning Status (JS fallback)\n'));
|
|
8608
|
+
console.log(chalk.white(` Signals received: ${stats.signalsReceived}`));
|
|
8609
|
+
console.log(chalk.white(` Trajectories buffered: ${stats.trajectoriesBuffered}`));
|
|
8610
|
+
console.log(chalk.white(` Total patterns: ${stats.patterns.totalPatterns}`));
|
|
8611
|
+
console.log(chalk.white(` Avg success rate: ${(stats.patterns.avgSuccessRate * 100).toFixed(1)}%`));
|
|
8612
|
+
console.log(chalk.white(` EWC tasks learned: ${stats.ewc.tasksLearned}`));
|
|
8613
|
+
console.log('');
|
|
8614
|
+
} catch (e2) {
|
|
8615
|
+
console.error(chalk.red('SONA native binding not available for this platform.'));
|
|
8616
|
+
console.error(chalk.yellow(' Install @ruvector/ruvllm for JS fallback: npm install @ruvector/ruvllm'));
|
|
8617
|
+
console.error(chalk.dim(` Native error: ${e.message}`));
|
|
8618
|
+
process.exit(1);
|
|
8619
|
+
}
|
|
8620
|
+
}
|
|
8921
8621
|
});
|
|
8922
8622
|
|
|
8923
|
-
sonaCmd.command('patterns
|
|
8623
|
+
sonaCmd.command('patterns')
|
|
8924
8624
|
.description('Search learned patterns')
|
|
8925
|
-
.
|
|
8926
|
-
.option('--
|
|
8927
|
-
.
|
|
8928
|
-
|
|
8929
|
-
|
|
8930
|
-
|
|
8931
|
-
const
|
|
8932
|
-
|
|
8933
|
-
|
|
8934
|
-
if (
|
|
8935
|
-
|
|
8936
|
-
|
|
8937
|
-
|
|
8625
|
+
.argument('<query>', 'Search query')
|
|
8626
|
+
.option('-k, --top-k <n>', 'Number of results', '5')
|
|
8627
|
+
.option('--json', 'JSON output')
|
|
8628
|
+
.action(async (query, opts) => {
|
|
8629
|
+
const topK = parseInt(opts.topK, 10) || 5;
|
|
8630
|
+
try {
|
|
8631
|
+
const { SonaEngine } = require('@ruvector/sona');
|
|
8632
|
+
const engine = new SonaEngine();
|
|
8633
|
+
const results = engine.searchPatterns ? engine.searchPatterns(query, topK) : [];
|
|
8634
|
+
if (opts.json) { console.log(JSON.stringify(results, null, 2)); return; }
|
|
8635
|
+
console.log(chalk.bold.cyan(`\n SONA Pattern Search: "${query}"\n`));
|
|
8636
|
+
if (!results || results.length === 0) {
|
|
8637
|
+
console.log(chalk.yellow(' No patterns found. Record some training data first.'));
|
|
8638
|
+
} else {
|
|
8639
|
+
for (let i = 0; i < results.length; i++) {
|
|
8640
|
+
const r = results[i];
|
|
8641
|
+
console.log(chalk.white(` ${i + 1}. ${chalk.bold(r.type || r.id || 'pattern')}`));
|
|
8642
|
+
console.log(chalk.dim(` Score: ${typeof r.score === 'number' ? r.score.toFixed(4) : r.score || 'N/A'}`));
|
|
8643
|
+
if (r.metadata) console.log(chalk.dim(` Metadata: ${JSON.stringify(r.metadata)}`));
|
|
8644
|
+
console.log('');
|
|
8645
|
+
}
|
|
8646
|
+
}
|
|
8647
|
+
} catch (e) {
|
|
8648
|
+
try {
|
|
8649
|
+
const { SonaCoordinator } = require('@ruvector/ruvllm');
|
|
8650
|
+
const sona = new SonaCoordinator();
|
|
8651
|
+
const bank = sona.getReasoningBank();
|
|
8652
|
+
const embedding = new Float64Array(64);
|
|
8653
|
+
for (let i = 0; i < query.length && i < 64; i++) embedding[i] = query.charCodeAt(i) / 255;
|
|
8654
|
+
const results = bank.findSimilar(Array.from(embedding), topK);
|
|
8655
|
+
if (opts.json) { console.log(JSON.stringify(results, null, 2)); return; }
|
|
8656
|
+
console.log(chalk.bold.cyan(`\n SONA Pattern Search (JS): "${query}"\n`));
|
|
8657
|
+
if (!results || results.length === 0) {
|
|
8658
|
+
console.log(chalk.yellow(' No patterns found. Record some training data first.'));
|
|
8659
|
+
} else {
|
|
8660
|
+
for (let i = 0; i < results.length; i++) {
|
|
8661
|
+
const r = results[i];
|
|
8662
|
+
console.log(chalk.white(` ${i + 1}. ${chalk.bold(r.type || r.id)}`));
|
|
8663
|
+
console.log(chalk.dim(` Success rate: ${(r.successRate * 100).toFixed(1)}%`));
|
|
8664
|
+
console.log('');
|
|
8665
|
+
}
|
|
8666
|
+
}
|
|
8667
|
+
} catch (e2) {
|
|
8668
|
+
console.error(chalk.red('SONA not available.'));
|
|
8669
|
+
console.error(chalk.dim(` Native error: ${e.message}`));
|
|
8670
|
+
process.exit(1);
|
|
8671
|
+
}
|
|
8672
|
+
}
|
|
8938
8673
|
});
|
|
8939
8674
|
|
|
8940
|
-
sonaCmd.command('train
|
|
8675
|
+
sonaCmd.command('train')
|
|
8941
8676
|
.description('Record a training trajectory')
|
|
8942
|
-
.
|
|
8943
|
-
.
|
|
8944
|
-
|
|
8677
|
+
.argument('<data>', 'Training data JSON file or JSON string')
|
|
8678
|
+
.option('--json', 'JSON output')
|
|
8679
|
+
.action(async (data, opts) => {
|
|
8680
|
+
let trajectoryData;
|
|
8945
8681
|
try {
|
|
8946
|
-
|
|
8947
|
-
|
|
8948
|
-
|
|
8949
|
-
console.
|
|
8950
|
-
|
|
8682
|
+
if (fs.existsSync(data)) { trajectoryData = JSON.parse(fs.readFileSync(data, 'utf8')); }
|
|
8683
|
+
else { trajectoryData = JSON.parse(data); }
|
|
8684
|
+
} catch (e) {
|
|
8685
|
+
console.error(chalk.red('Invalid training data. Provide a JSON file path or JSON string.'));
|
|
8686
|
+
console.error(chalk.dim(` Error: ${e.message}`));
|
|
8687
|
+
console.error(chalk.dim('\n Example: ruvector sona train \'{"steps":[{"type":"query","input":"test","output":"result","confidence":0.9}],"outcome":"success"}\''));
|
|
8688
|
+
process.exit(1);
|
|
8689
|
+
}
|
|
8690
|
+
const spinner = ora('Recording trajectory...').start();
|
|
8691
|
+
try {
|
|
8692
|
+
const { SonaEngine } = require('@ruvector/sona');
|
|
8693
|
+
const engine = new SonaEngine();
|
|
8694
|
+
const result = engine.recordTrajectory ? engine.recordTrajectory(JSON.stringify(trajectoryData)) : { recorded: true };
|
|
8695
|
+
spinner.succeed('Trajectory recorded');
|
|
8696
|
+
if (opts.json) { console.log(JSON.stringify(result, null, 2)); return; }
|
|
8697
|
+
console.log(chalk.dim(` Steps: ${trajectoryData.steps ? trajectoryData.steps.length : 'N/A'}`));
|
|
8698
|
+
console.log(chalk.dim(` Outcome: ${trajectoryData.outcome || 'N/A'}`));
|
|
8699
|
+
console.log('');
|
|
8700
|
+
} catch (e) {
|
|
8701
|
+
try {
|
|
8702
|
+
const { SonaCoordinator, TrajectoryBuilder } = require('@ruvector/ruvllm');
|
|
8703
|
+
const sona = new SonaCoordinator();
|
|
8704
|
+
const builder = new TrajectoryBuilder();
|
|
8705
|
+
if (trajectoryData.steps && Array.isArray(trajectoryData.steps)) {
|
|
8706
|
+
for (const step of trajectoryData.steps) { builder.startStep(step.type || 'query', step.input || ''); builder.endStep(step.output || '', step.confidence || 0.5); }
|
|
8707
|
+
}
|
|
8708
|
+
const trajectory = builder.complete(trajectoryData.outcome || 'success');
|
|
8709
|
+
sona.recordTrajectory(trajectory);
|
|
8710
|
+
spinner.succeed('Trajectory recorded (JS fallback)');
|
|
8711
|
+
if (opts.json) { console.log(JSON.stringify({ id: trajectory.id, steps: trajectory.steps.length, outcome: trajectory.outcome }, null, 2)); return; }
|
|
8712
|
+
console.log(chalk.dim(` ID: ${trajectory.id}`));
|
|
8713
|
+
console.log(chalk.dim(` Steps: ${trajectory.steps.length}`));
|
|
8714
|
+
console.log(chalk.dim(` Outcome: ${trajectory.outcome}`));
|
|
8715
|
+
console.log('');
|
|
8716
|
+
} catch (e2) { spinner.fail('Failed to record trajectory'); console.error(chalk.red(` ${e.message}`)); process.exit(1); }
|
|
8717
|
+
}
|
|
8951
8718
|
});
|
|
8952
8719
|
|
|
8953
8720
|
sonaCmd.command('export')
|
|
8954
|
-
.description('Export SONA learned weights
|
|
8955
|
-
.option('-o, --output <
|
|
8956
|
-
.
|
|
8957
|
-
|
|
8958
|
-
|
|
8959
|
-
|
|
8960
|
-
const
|
|
8961
|
-
|
|
8962
|
-
|
|
8963
|
-
|
|
8721
|
+
.description('Export SONA learned weights')
|
|
8722
|
+
.option('-o, --output <path>', 'Output file', 'sona-export.json')
|
|
8723
|
+
.option('--json', 'JSON output')
|
|
8724
|
+
.action(async (opts) => {
|
|
8725
|
+
const spinner = ora('Exporting SONA state...').start();
|
|
8726
|
+
try {
|
|
8727
|
+
const { SonaEngine } = require('@ruvector/sona');
|
|
8728
|
+
const engine = new SonaEngine();
|
|
8729
|
+
const exported = engine.export ? engine.export() : JSON.stringify(engine.stats());
|
|
8730
|
+
const outputData = typeof exported === 'string' ? exported : JSON.stringify(exported, null, 2);
|
|
8731
|
+
if (opts.json) { spinner.stop(); console.log(outputData); return; }
|
|
8732
|
+
fs.writeFileSync(opts.output, outputData);
|
|
8733
|
+
spinner.succeed(`Exported to ${opts.output}`);
|
|
8734
|
+
console.log(chalk.dim(` Size: ${Buffer.byteLength(outputData)} bytes`));
|
|
8735
|
+
console.log('');
|
|
8736
|
+
} catch (e) {
|
|
8737
|
+
try {
|
|
8738
|
+
const { SonaCoordinator } = require('@ruvector/ruvllm');
|
|
8739
|
+
const sona = new SonaCoordinator();
|
|
8740
|
+
const exportData = JSON.stringify({ stats: sona.stats(), reasoningBank: sona.getReasoningBank().stats(), exportedAt: new Date().toISOString() }, null, 2);
|
|
8741
|
+
if (opts.json) { spinner.stop(); console.log(exportData); return; }
|
|
8742
|
+
fs.writeFileSync(opts.output, exportData);
|
|
8743
|
+
spinner.succeed(`Exported to ${opts.output} (JS fallback)`);
|
|
8744
|
+
console.log(chalk.dim(` Size: ${Buffer.byteLength(exportData)} bytes`));
|
|
8745
|
+
console.log('');
|
|
8746
|
+
} catch (e2) { spinner.fail('Export failed'); console.error(chalk.red(` ${e.message}`)); process.exit(1); }
|
|
8747
|
+
}
|
|
8964
8748
|
});
|
|
8965
8749
|
|
|
8966
8750
|
sonaCmd.command('stats')
|
|
8967
|
-
.description('Show detailed
|
|
8968
|
-
.option('--json', '
|
|
8969
|
-
.action((opts) => {
|
|
8970
|
-
const sona = loadSona();
|
|
8751
|
+
.description('Show detailed learning statistics')
|
|
8752
|
+
.option('--json', 'JSON output')
|
|
8753
|
+
.action(async (opts) => {
|
|
8971
8754
|
try {
|
|
8972
|
-
const
|
|
8973
|
-
const
|
|
8974
|
-
|
|
8975
|
-
console.log(
|
|
8976
|
-
|
|
8977
|
-
|
|
8978
|
-
|
|
8755
|
+
const { SonaEngine } = require('@ruvector/sona');
|
|
8756
|
+
const engine = new SonaEngine();
|
|
8757
|
+
const stats = engine.stats();
|
|
8758
|
+
if (opts.json) { console.log(JSON.stringify(stats, null, 2)); return; }
|
|
8759
|
+
console.log(chalk.bold.cyan('\n SONA Detailed Statistics\n'));
|
|
8760
|
+
for (const [key, value] of Object.entries(stats)) {
|
|
8761
|
+
const label = key.replace(/([A-Z])/g, ' $1').replace(/_/g, ' ').trim();
|
|
8762
|
+
const displayLabel = label.charAt(0).toUpperCase() + label.slice(1);
|
|
8763
|
+
if (typeof value === 'object' && value !== null) {
|
|
8764
|
+
console.log(chalk.white(` ${displayLabel}:`));
|
|
8765
|
+
for (const [k, v] of Object.entries(value)) console.log(chalk.dim(` ${k}: ${typeof v === 'number' ? v.toFixed(4) : v}`));
|
|
8766
|
+
} else { console.log(chalk.white(` ${displayLabel}: ${typeof value === 'number' ? value.toFixed(4) : value}`)); }
|
|
8767
|
+
}
|
|
8768
|
+
console.log('');
|
|
8769
|
+
} catch (e) {
|
|
8770
|
+
try {
|
|
8771
|
+
const { SonaCoordinator } = require('@ruvector/ruvllm');
|
|
8772
|
+
const stats = new SonaCoordinator().stats();
|
|
8773
|
+
if (opts.json) { console.log(JSON.stringify(stats, null, 2)); return; }
|
|
8774
|
+
console.log(chalk.bold.cyan('\n SONA Detailed Statistics (JS)\n'));
|
|
8775
|
+
console.log(chalk.white(` Signals received: ${stats.signalsReceived}`));
|
|
8776
|
+
console.log(chalk.white(` Trajectories buffered: ${stats.trajectoriesBuffered}`));
|
|
8777
|
+
console.log(chalk.white('\n Reasoning Bank:'));
|
|
8778
|
+
console.log(chalk.dim(` Total patterns: ${stats.patterns.totalPatterns}`));
|
|
8779
|
+
console.log(chalk.dim(` Avg success rate: ${(stats.patterns.avgSuccessRate * 100).toFixed(1)}%`));
|
|
8780
|
+
if (stats.patterns.byType) { for (const [type, count] of Object.entries(stats.patterns.byType)) console.log(chalk.dim(` ${type}: ${count}`)); }
|
|
8781
|
+
console.log(chalk.white('\n EWC++ (Memory Protection):'));
|
|
8782
|
+
console.log(chalk.dim(` Tasks learned: ${stats.ewc.tasksLearned}`));
|
|
8783
|
+
console.log(chalk.dim(` Lambda: ${stats.ewc.lambda}`));
|
|
8784
|
+
console.log(chalk.dim(` Forgetting rate: ${(stats.ewc.estimatedForgettingRate * 100).toFixed(2)}%`));
|
|
8785
|
+
console.log('');
|
|
8786
|
+
} catch (e2) { console.error(chalk.red('SONA not available.')); console.error(chalk.dim(` Native error: ${e.message}`)); process.exit(1); }
|
|
8787
|
+
}
|
|
8979
8788
|
});
|
|
8980
8789
|
|
|
8981
8790
|
sonaCmd.command('info')
|
|
8982
|
-
.description('Show SONA module
|
|
8983
|
-
.
|
|
8984
|
-
|
|
8985
|
-
|
|
8986
|
-
|
|
8987
|
-
|
|
8988
|
-
|
|
8791
|
+
.description('Show SONA module information')
|
|
8792
|
+
.option('--json', 'JSON output')
|
|
8793
|
+
.action(async (opts) => {
|
|
8794
|
+
let nativeAvailable = false, nativeVersion = 'N/A';
|
|
8795
|
+
try { const sona = require('@ruvector/sona'); nativeAvailable = !!sona.SonaEngine; nativeVersion = sona.version || '0.1.4'; } catch (e) { /* not available */ }
|
|
8796
|
+
let jsAvailable = false, jsVersion = 'N/A';
|
|
8797
|
+
try { const ruvllm = require('@ruvector/ruvllm'); jsAvailable = !!ruvllm.SonaCoordinator; jsVersion = typeof ruvllm.version === 'function' ? ruvllm.version() : (ruvllm.version || 'unknown'); } catch (e) { /* not available */ }
|
|
8798
|
+
const info = { package: '@ruvector/sona', nativeBinding: nativeAvailable, nativeVersion, jsFallback: jsAvailable, jsFallbackVersion: jsVersion, features: ['LoRA adaptive weights', 'EWC++ memory protection', 'ReasoningBank pattern storage', 'Trajectory tracking', 'Continual learning', 'Sub-millisecond overhead'] };
|
|
8799
|
+
if (opts.json) { console.log(JSON.stringify(info, null, 2)); return; }
|
|
8800
|
+
console.log(chalk.bold.cyan('\n SONA Module Information\n'));
|
|
8801
|
+
console.log(chalk.white(` Package: ${info.package}`));
|
|
8802
|
+
console.log(chalk.white(` Native binding: ${nativeAvailable ? chalk.green('Available') + chalk.dim(` (v${nativeVersion})`) : chalk.yellow('Not available')}`));
|
|
8803
|
+
console.log(chalk.white(` JS fallback: ${jsAvailable ? chalk.green('Available') + chalk.dim(` (ruvllm v${jsVersion})`) : chalk.yellow('Not available')}`));
|
|
8804
|
+
console.log(chalk.white('\n Features:'));
|
|
8805
|
+
for (const f of info.features) console.log(chalk.dim(` - ${f}`));
|
|
8806
|
+
console.log('');
|
|
8989
8807
|
});
|
|
8990
8808
|
|
|
8991
|
-
//
|
|
8992
|
-
// Route Commands
|
|
8993
|
-
//
|
|
8994
|
-
|
|
8995
|
-
const routeCmd = program.command('route').description('Semantic routing — classify inputs to routes via HNSW + SIMD');
|
|
8996
|
-
|
|
8997
|
-
function requireRouter() {
|
|
8998
|
-
try { return require('@ruvector/router'); } catch {
|
|
8999
|
-
console.error(chalk.red('Route commands require @ruvector/router'));
|
|
9000
|
-
console.error(chalk.yellow(' npm install @ruvector/router'));
|
|
9001
|
-
process.exit(1);
|
|
9002
|
-
}
|
|
9003
|
-
}
|
|
8809
|
+
// =============================================================================
|
|
8810
|
+
// Route Commands - Semantic routing (lazy: @ruvector/router)
|
|
8811
|
+
// =============================================================================
|
|
9004
8812
|
|
|
9005
|
-
routeCmd.command('
|
|
9006
|
-
|
|
9007
|
-
|
|
9008
|
-
.
|
|
9009
|
-
|
|
9010
|
-
|
|
9011
|
-
|
|
9012
|
-
|
|
9013
|
-
|
|
9014
|
-
|
|
9015
|
-
|
|
9016
|
-
console.
|
|
9017
|
-
console.
|
|
9018
|
-
|
|
8813
|
+
const routeCmd = program.command('route').description('Semantic routing for AI agents');
|
|
8814
|
+
|
|
8815
|
+
routeCmd.command('classify')
|
|
8816
|
+
.description('Classify input to a route')
|
|
8817
|
+
.argument('<input>', 'Input text to classify')
|
|
8818
|
+
.option('-r, --routes <json>', 'Routes definition JSON file')
|
|
8819
|
+
.option('-k, --top-k <n>', 'Number of results', '3')
|
|
8820
|
+
.option('--json', 'JSON output')
|
|
8821
|
+
.action(async (input, opts) => {
|
|
8822
|
+
let router;
|
|
8823
|
+
try { router = require('@ruvector/router'); } catch (e) {
|
|
8824
|
+
console.error(chalk.red('Route commands require @ruvector/router'));
|
|
8825
|
+
console.error(chalk.yellow(' npm install @ruvector/router'));
|
|
8826
|
+
console.error(chalk.dim(' or: npx ruvector install router'));
|
|
8827
|
+
process.exit(1);
|
|
8828
|
+
}
|
|
8829
|
+
const topK = parseInt(opts.topK, 10) || 3;
|
|
8830
|
+
const spinner = ora('Classifying input...').start();
|
|
8831
|
+
try {
|
|
8832
|
+
let routesDef = null;
|
|
8833
|
+
if (opts.routes) {
|
|
8834
|
+
if (!fs.existsSync(opts.routes)) { spinner.fail(`Routes file not found: ${opts.routes}`); process.exit(1); }
|
|
8835
|
+
routesDef = JSON.parse(fs.readFileSync(opts.routes, 'utf8'));
|
|
8836
|
+
}
|
|
8837
|
+
const sr = new router.SemanticRouter({ dimension: routesDef ? (routesDef.dimension || 384) : 384 });
|
|
8838
|
+
if (routesDef && routesDef.intents) { for (const intent of routesDef.intents) sr.addIntent(intent); }
|
|
8839
|
+
const embedding = new Float32Array(sr._dimension || 384);
|
|
8840
|
+
for (let i = 0; i < input.length && i < embedding.length; i++) embedding[i] = input.charCodeAt(i) / 255;
|
|
8841
|
+
let norm = 0;
|
|
8842
|
+
for (let i = 0; i < embedding.length; i++) norm += embedding[i] * embedding[i];
|
|
8843
|
+
norm = Math.sqrt(norm) || 1;
|
|
8844
|
+
for (let i = 0; i < embedding.length; i++) embedding[i] /= norm;
|
|
8845
|
+
const results = sr.routeWithEmbedding(embedding, topK);
|
|
8846
|
+
spinner.stop();
|
|
8847
|
+
if (opts.json) { console.log(JSON.stringify({ input, results }, null, 2)); return; }
|
|
8848
|
+
console.log(chalk.bold.cyan(`\n Route Classification: "${input.length > 50 ? input.slice(0, 50) + '...' : input}"\n`));
|
|
8849
|
+
if (!results || results.length === 0) {
|
|
8850
|
+
console.log(chalk.yellow(' No matching routes found.'));
|
|
8851
|
+
if (!routesDef) { console.log(chalk.dim(' Provide a routes file with -r/--routes to define intents.')); console.log(chalk.dim(' Example: ruvector route classify "hello" -r routes.json')); }
|
|
8852
|
+
} else {
|
|
8853
|
+
for (let i = 0; i < results.length; i++) {
|
|
8854
|
+
const r = results[i];
|
|
8855
|
+
console.log(chalk.white(` ${i + 1}. ${chalk.bold(r.intent)}`));
|
|
8856
|
+
console.log(chalk.dim(` Score: ${r.score.toFixed(4)}`));
|
|
8857
|
+
if (r.metadata && Object.keys(r.metadata).length > 0) console.log(chalk.dim(` Metadata: ${JSON.stringify(r.metadata)}`));
|
|
8858
|
+
}
|
|
8859
|
+
}
|
|
8860
|
+
console.log('');
|
|
8861
|
+
} catch (err) { spinner.fail('Classification failed'); console.error(chalk.red(` ${err.message}`)); process.exit(1); }
|
|
9019
8862
|
});
|
|
9020
8863
|
|
|
9021
8864
|
routeCmd.command('benchmark')
|
|
9022
8865
|
.description('Benchmark routing throughput')
|
|
9023
8866
|
.option('-n, --iterations <n>', 'Number of iterations', '1000')
|
|
9024
|
-
.
|
|
9025
|
-
|
|
9026
|
-
|
|
9027
|
-
|
|
9028
|
-
|
|
9029
|
-
|
|
9030
|
-
|
|
9031
|
-
|
|
9032
|
-
|
|
9033
|
-
|
|
9034
|
-
|
|
9035
|
-
|
|
9036
|
-
|
|
9037
|
-
|
|
9038
|
-
|
|
8867
|
+
.option('--json', 'JSON output')
|
|
8868
|
+
.action(async (opts) => {
|
|
8869
|
+
let router;
|
|
8870
|
+
try { router = require('@ruvector/router'); } catch (e) {
|
|
8871
|
+
console.error(chalk.red('Route commands require @ruvector/router'));
|
|
8872
|
+
console.error(chalk.yellow(' npm install @ruvector/router'));
|
|
8873
|
+
console.error(chalk.dim(' or: npx ruvector install router'));
|
|
8874
|
+
process.exit(1);
|
|
8875
|
+
}
|
|
8876
|
+
const iterations = parseInt(opts.iterations, 10) || 1000;
|
|
8877
|
+
const spinner = ora(`Benchmarking ${iterations} route classifications...`).start();
|
|
8878
|
+
try {
|
|
8879
|
+
const { performance } = require('perf_hooks');
|
|
8880
|
+
const dim = 128;
|
|
8881
|
+
const sr = new router.SemanticRouter({ dimension: dim, threshold: 0.3 });
|
|
8882
|
+
const intentNames = ['greeting', 'farewell', 'question', 'command', 'feedback'];
|
|
8883
|
+
for (const name of intentNames) { const emb = new Float32Array(dim); for (let i = 0; i < dim; i++) emb[i] = Math.random() - 0.5; sr.addIntent({ name, utterances: [`example ${name}`], embedding: emb }); }
|
|
8884
|
+
const warmupEmb = new Float32Array(dim); for (let i = 0; i < dim; i++) warmupEmb[i] = Math.random() - 0.5;
|
|
8885
|
+
for (let i = 0; i < 50; i++) sr.routeWithEmbedding(warmupEmb, 3);
|
|
8886
|
+
const times = [], start = performance.now();
|
|
8887
|
+
for (let i = 0; i < iterations; i++) { const emb = new Float32Array(dim); for (let j = 0; j < dim; j++) emb[j] = Math.random() - 0.5; const t0 = performance.now(); sr.routeWithEmbedding(emb, 3); times.push(performance.now() - t0); }
|
|
8888
|
+
const totalMs = performance.now() - start;
|
|
8889
|
+
times.sort((a, b) => a - b);
|
|
8890
|
+
const avgMs = totalMs / iterations, p50 = times[Math.floor(iterations * 0.5)], p95 = times[Math.floor(iterations * 0.95)], p99 = times[Math.floor(iterations * 0.99)], opsPerSec = (iterations / totalMs) * 1000;
|
|
8891
|
+
spinner.stop();
|
|
8892
|
+
if (opts.json) { console.log(JSON.stringify({ iterations, dimension: dim, intents: intentNames.length, totalMs: +totalMs.toFixed(2), avgMs: +avgMs.toFixed(3), p50: +p50.toFixed(3), p95: +p95.toFixed(3), p99: +p99.toFixed(3), opsPerSec: +opsPerSec.toFixed(1) }, null, 2)); return; }
|
|
8893
|
+
console.log(chalk.bold.cyan('\n Router Benchmark Results\n'));
|
|
8894
|
+
console.log(chalk.white(` Iterations: ${iterations}`));
|
|
8895
|
+
console.log(chalk.white(` Dimension: ${dim}`));
|
|
8896
|
+
console.log(chalk.white(` Intents: ${intentNames.length}`));
|
|
8897
|
+
console.log(chalk.white(` Total time: ${totalMs.toFixed(1)}ms`));
|
|
8898
|
+
console.log(chalk.white(` Avg latency: ${avgMs.toFixed(3)}ms`));
|
|
8899
|
+
console.log(chalk.white(` P50: ${p50.toFixed(3)}ms`));
|
|
8900
|
+
console.log(chalk.white(` P95: ${p95.toFixed(3)}ms`));
|
|
8901
|
+
console.log(chalk.white(` P99: ${p99.toFixed(3)}ms`));
|
|
8902
|
+
console.log(chalk.green(` Throughput: ${opsPerSec.toFixed(1)} ops/sec`));
|
|
8903
|
+
console.log('');
|
|
8904
|
+
} catch (err) { spinner.fail('Benchmark failed'); console.error(chalk.red(` ${err.message}`)); process.exit(1); }
|
|
9039
8905
|
});
|
|
9040
8906
|
|
|
9041
8907
|
routeCmd.command('info')
|
|
9042
8908
|
.description('Show router module information')
|
|
9043
|
-
.
|
|
9044
|
-
|
|
9045
|
-
|
|
9046
|
-
|
|
9047
|
-
|
|
8909
|
+
.option('--json', 'JSON output')
|
|
8910
|
+
.action(async (opts) => {
|
|
8911
|
+
let routerAvailable = false, routerVersion = 'N/A', metrics = {};
|
|
8912
|
+
try { const router = require('@ruvector/router'); routerAvailable = true; routerVersion = router.version || '0.1.28'; const sr = new router.SemanticRouter({ dimension: 16 }); metrics = { distanceMetrics: Object.keys(router.DistanceMetric || {}), hasSemanticRouter: true }; } catch (e) { /* Not available */ }
|
|
8913
|
+
const info = { package: '@ruvector/router', available: routerAvailable, version: routerVersion, backend: 'Rust NAPI (HNSW + SIMD)', features: ['Semantic intent matching', 'HNSW indexing', 'SIMD-accelerated search', 'Multiple distance metrics', 'Save/load state', 'Async embedding support'], ...metrics };
|
|
8914
|
+
if (opts.json) { console.log(JSON.stringify(info, null, 2)); return; }
|
|
8915
|
+
console.log(chalk.bold.cyan('\n Semantic Router Information\n'));
|
|
8916
|
+
console.log(chalk.white(` Package: ${info.package}`));
|
|
8917
|
+
console.log(chalk.white(` Status: ${routerAvailable ? chalk.green('Available') + chalk.dim(` (v${routerVersion})`) : chalk.yellow('Not installed')}`));
|
|
8918
|
+
console.log(chalk.white(` Backend: ${info.backend}`));
|
|
8919
|
+
if (info.distanceMetrics) console.log(chalk.white(` Metrics: ${info.distanceMetrics.join(', ')}`));
|
|
8920
|
+
console.log(chalk.white('\n Features:'));
|
|
8921
|
+
for (const f of info.features) console.log(chalk.dim(` - ${f}`));
|
|
8922
|
+
if (!routerAvailable) console.log(chalk.yellow('\n Install: npm install @ruvector/router'));
|
|
8923
|
+
console.log('');
|
|
8924
|
+
});
|
|
8925
|
+
|
|
8926
|
+
// ── Decompile Command ──────────────────────────────────────────────────────
|
|
8927
|
+
const decompileCmd = program
|
|
8928
|
+
.command('decompile [target]')
|
|
8929
|
+
.description('Decompile npm packages, local JS files, or URLs into modules')
|
|
8930
|
+
.option('-o, --output <dir>', 'Output directory')
|
|
8931
|
+
.option('-f, --format <type>', 'Output format: modules, single, json', 'modules')
|
|
8932
|
+
.option('-c, --confidence <n>', 'Minimum confidence threshold (0-1)', '0.3')
|
|
8933
|
+
.option('--no-witness', 'Skip witness chain generation')
|
|
8934
|
+
.option('--json', 'JSON output to stdout (for piping)')
|
|
8935
|
+
.option('-q, --quiet', 'Suppress progress output')
|
|
8936
|
+
.option('--version-pkg <ver>', 'Package version (alternative to @version syntax)')
|
|
8937
|
+
.option('--diff <version>', 'Compare against another version')
|
|
8938
|
+
.action(async (target, opts) => {
|
|
8939
|
+
if (!target) {
|
|
8940
|
+
console.log(chalk.cyan('\nUsage:'));
|
|
8941
|
+
console.log(chalk.white(' ruvector decompile <package> Decompile npm package'));
|
|
8942
|
+
console.log(chalk.white(' ruvector decompile <pkg>@<ver> Specific version'));
|
|
8943
|
+
console.log(chalk.white(' ruvector decompile ./bundle.js Local file'));
|
|
8944
|
+
console.log(chalk.white(' ruvector decompile https://unpkg.com/x URL'));
|
|
8945
|
+
console.log(chalk.dim('\nOptions:'));
|
|
8946
|
+
console.log(chalk.dim(' -o, --output <dir> Output directory'));
|
|
8947
|
+
console.log(chalk.dim(' -f, --format <type> modules | single | json'));
|
|
8948
|
+
console.log(chalk.dim(' -c, --confidence <n> Min confidence (0-1, default: 0.3)'));
|
|
8949
|
+
console.log(chalk.dim(' --no-witness Skip witness chain'));
|
|
8950
|
+
console.log(chalk.dim(' --json JSON to stdout'));
|
|
8951
|
+
console.log(chalk.dim(' --diff <version> Diff against another version'));
|
|
8952
|
+
console.log('');
|
|
8953
|
+
return;
|
|
8954
|
+
}
|
|
8955
|
+
|
|
8956
|
+
const decompiler = require('../src/decompiler/index.js');
|
|
8957
|
+
const { parseTarget } = decompiler;
|
|
8958
|
+
const parsed = parseTarget(target);
|
|
8959
|
+
const minConfidence = parseFloat(opts.confidence);
|
|
8960
|
+
const decompileOpts = { minConfidence, witness: opts.witness !== false, useRust: true };
|
|
8961
|
+
const quiet = opts.quiet || opts.json;
|
|
8962
|
+
let spinner = null;
|
|
8963
|
+
|
|
8964
|
+
if (!quiet) {
|
|
8965
|
+
spinner = ora('Analyzing target...').start();
|
|
8966
|
+
}
|
|
8967
|
+
|
|
8968
|
+
try {
|
|
8969
|
+
let result;
|
|
8970
|
+
|
|
8971
|
+
if (parsed.type === 'npm') {
|
|
8972
|
+
const version = opts.versionPkg || parsed.version;
|
|
8973
|
+
if (!quiet) spinner.text = `Fetching ${parsed.name}${version ? '@' + version : ''}...`;
|
|
8974
|
+
result = await decompiler.decompilePackage(parsed.name, version, decompileOpts);
|
|
8975
|
+
if (!quiet) spinner.text = `Decompiled ${result.packageInfo.name}@${result.packageInfo.version}`;
|
|
8976
|
+
} else if (parsed.type === 'file') {
|
|
8977
|
+
if (!quiet) spinner.text = `Reading ${parsed.path}...`;
|
|
8978
|
+
result = decompiler.decompileFile(parsed.path, { ...decompileOpts, filePath: parsed.path });
|
|
8979
|
+
} else if (parsed.type === 'url') {
|
|
8980
|
+
if (!quiet) spinner.text = `Fetching ${parsed.url}...`;
|
|
8981
|
+
result = await decompiler.decompileUrl(parsed.url, decompileOpts);
|
|
8982
|
+
}
|
|
8983
|
+
|
|
8984
|
+
if (!quiet) spinner.succeed(chalk.green('Decompilation complete'));
|
|
8985
|
+
|
|
8986
|
+
// Handle --diff flag
|
|
8987
|
+
if (opts.diff && parsed.type === 'npm') {
|
|
8988
|
+
if (!quiet) {
|
|
8989
|
+
const diffSpinner = ora(`Fetching ${parsed.name}@${opts.diff} for diff...`).start();
|
|
8990
|
+
try {
|
|
8991
|
+
const other = await decompiler.decompilePackage(parsed.name, opts.diff, decompileOpts);
|
|
8992
|
+
diffSpinner.succeed('Diff complete');
|
|
8993
|
+
const resultNames = new Set(result.modules.map((m) => m.name));
|
|
8994
|
+
const otherNames = new Set(other.modules.map((m) => m.name));
|
|
8995
|
+
const added = [...resultNames].filter((n) => !otherNames.has(n));
|
|
8996
|
+
const removed = [...otherNames].filter((n) => !resultNames.has(n));
|
|
8997
|
+
const common = [...resultNames].filter((n) => otherNames.has(n));
|
|
8998
|
+
|
|
8999
|
+
console.log(chalk.bold.cyan('\n Version Diff'));
|
|
9000
|
+
console.log(chalk.white(` ${opts.diff} -> ${result.packageInfo.version}`));
|
|
9001
|
+
if (added.length) console.log(chalk.green(` Added: ${added.join(', ')}`));
|
|
9002
|
+
if (removed.length) console.log(chalk.red(` Removed: ${removed.join(', ')}`));
|
|
9003
|
+
console.log(chalk.dim(` Common: ${common.length} modules`));
|
|
9004
|
+
console.log('');
|
|
9005
|
+
} catch (err) {
|
|
9006
|
+
diffSpinner.fail(`Diff failed: ${err.message}`);
|
|
9007
|
+
}
|
|
9008
|
+
}
|
|
9009
|
+
}
|
|
9010
|
+
|
|
9011
|
+
// Output
|
|
9012
|
+
if (opts.json) {
|
|
9013
|
+
const jsonOut = {
|
|
9014
|
+
modules: result.modules.map((m) => ({
|
|
9015
|
+
name: m.name, fragments: m.fragments, confidence: m.confidence,
|
|
9016
|
+
contentLength: m.content.length,
|
|
9017
|
+
})),
|
|
9018
|
+
metrics: result.metrics,
|
|
9019
|
+
witness: result.witness ? { root: result.witness.root, chain_length: result.witness.chain.length } : null,
|
|
9020
|
+
packageInfo: result.packageInfo || null,
|
|
9021
|
+
};
|
|
9022
|
+
console.log(JSON.stringify(jsonOut, null, 2));
|
|
9023
|
+
return;
|
|
9024
|
+
}
|
|
9025
|
+
|
|
9026
|
+
// Determine output directory
|
|
9027
|
+
let outputDir = opts.output;
|
|
9028
|
+
if (!outputDir) {
|
|
9029
|
+
const baseName = result.packageInfo
|
|
9030
|
+
? `${result.packageInfo.name.replace('/', '-')}@${result.packageInfo.version}`
|
|
9031
|
+
: path.basename(target, '.js');
|
|
9032
|
+
outputDir = path.join(process.cwd(), 'decompiled', baseName);
|
|
9033
|
+
}
|
|
9034
|
+
|
|
9035
|
+
decompiler.writeOutput(result, outputDir, opts.format);
|
|
9036
|
+
|
|
9037
|
+
console.log(chalk.bold.cyan('\n Decompilation Summary'));
|
|
9038
|
+
console.log(chalk.white(` Modules: ${result.modules.length}`));
|
|
9039
|
+
console.log(chalk.white(` Source size: ${(result.metrics.source.sizeBytes / 1024).toFixed(1)} KB`));
|
|
9040
|
+
console.log(chalk.white(` Functions: ${result.metrics.source.functions}`));
|
|
9041
|
+
console.log(chalk.white(` Classes: ${result.metrics.source.classes}`));
|
|
9042
|
+
if (result.witness) {
|
|
9043
|
+
const wRoot = result.witness.root || result.witness.chain_root || '';
|
|
9044
|
+
console.log(chalk.white(` Witness root: ${wRoot.slice(0, 16)}...`));
|
|
9045
|
+
}
|
|
9046
|
+
console.log(chalk.green(` Output: ${outputDir}`));
|
|
9047
|
+
console.log('');
|
|
9048
|
+
|
|
9049
|
+
if (result.modules.length > 0) {
|
|
9050
|
+
console.log(chalk.dim(' Detected modules:'));
|
|
9051
|
+
for (const mod of result.modules) {
|
|
9052
|
+
const conf = (mod.confidence * 100).toFixed(0);
|
|
9053
|
+
console.log(chalk.dim(` ${mod.name} (${mod.fragments} fragments, ${conf}% confidence)`));
|
|
9054
|
+
}
|
|
9055
|
+
console.log('');
|
|
9056
|
+
}
|
|
9057
|
+
} catch (err) {
|
|
9058
|
+
if (spinner) spinner.fail(chalk.red('Decompilation failed'));
|
|
9059
|
+
console.error(chalk.red(` ${err.message}`));
|
|
9060
|
+
process.exit(1);
|
|
9061
|
+
}
|
|
9048
9062
|
});
|
|
9049
9063
|
|
|
9050
9064
|
program.parse();
|
|
9065
|
+
|
|
9066
|
+
|