ruvector 0.2.5 → 0.2.7
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/bin/cli.js +133 -67
- package/bin/mcp-server.js +143 -31
- package/package.json +1 -3
package/bin/cli.js
CHANGED
|
@@ -115,6 +115,76 @@ function loadGnn() {
|
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
117
|
|
|
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'];
|
|
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 body = stdout.trim();
|
|
174
|
+
return {
|
|
175
|
+
ok: true,
|
|
176
|
+
status: 200,
|
|
177
|
+
statusText: 'OK',
|
|
178
|
+
text: async () => body,
|
|
179
|
+
json: async () => JSON.parse(body),
|
|
180
|
+
headers: new Map(),
|
|
181
|
+
};
|
|
182
|
+
} catch (e) {
|
|
183
|
+
const msg = e.stderr ? e.stderr.toString().trim() : e.message;
|
|
184
|
+
throw new Error(`Proxy curl failed: ${msg}`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
118
188
|
// Lazy load Attention (optional - loaded on first use, not at startup)
|
|
119
189
|
let _attentionModule = undefined;
|
|
120
190
|
let DotProductAttention, MultiHeadAttention, HyperbolicAttention, FlashAttention, LinearAttention, MoEAttention;
|
|
@@ -7250,7 +7320,7 @@ async function getRvfManifest(opts = {}) {
|
|
|
7250
7320
|
|
|
7251
7321
|
// Try GCS
|
|
7252
7322
|
try {
|
|
7253
|
-
const resp = await
|
|
7323
|
+
const resp = await proxyFetch(GCS_MANIFEST_URL, { signal: AbortSignal.timeout(15000) });
|
|
7254
7324
|
if (resp.ok) {
|
|
7255
7325
|
const manifest = await resp.json();
|
|
7256
7326
|
fs.mkdirSync(cacheDir, { recursive: true });
|
|
@@ -7851,19 +7921,9 @@ mcpCmd.command('test')
|
|
|
7851
7921
|
});
|
|
7852
7922
|
|
|
7853
7923
|
// ============================================================================
|
|
7854
|
-
// Brain Commands — Shared intelligence via
|
|
7924
|
+
// Brain Commands — Shared intelligence via pi.ruv.io REST API (direct fetch)
|
|
7855
7925
|
// ============================================================================
|
|
7856
7926
|
|
|
7857
|
-
async function requirePiBrain() {
|
|
7858
|
-
try {
|
|
7859
|
-
return require('@ruvector/pi-brain');
|
|
7860
|
-
} catch {
|
|
7861
|
-
console.error(chalk.red('Brain commands require @ruvector/pi-brain'));
|
|
7862
|
-
console.error(chalk.yellow(' npm install @ruvector/pi-brain'));
|
|
7863
|
-
process.exit(1);
|
|
7864
|
-
}
|
|
7865
|
-
}
|
|
7866
|
-
|
|
7867
7927
|
function getBrainConfig(opts) {
|
|
7868
7928
|
return {
|
|
7869
7929
|
url: opts.url || process.env.BRAIN_URL || 'https://pi.ruv.io',
|
|
@@ -7871,6 +7931,30 @@ function getBrainConfig(opts) {
|
|
|
7871
7931
|
};
|
|
7872
7932
|
}
|
|
7873
7933
|
|
|
7934
|
+
function brainHeaders(config) {
|
|
7935
|
+
const h = { 'Content-Type': 'application/json' };
|
|
7936
|
+
if (config.key) h['Authorization'] = `Bearer ${config.key}`;
|
|
7937
|
+
return h;
|
|
7938
|
+
}
|
|
7939
|
+
|
|
7940
|
+
async function brainFetch(config, endpoint, opts = {}) {
|
|
7941
|
+
const url = new URL(endpoint, config.url);
|
|
7942
|
+
if (opts.params) {
|
|
7943
|
+
for (const [k, v] of Object.entries(opts.params)) {
|
|
7944
|
+
if (v !== undefined && v !== null) url.searchParams.set(k, String(v));
|
|
7945
|
+
}
|
|
7946
|
+
}
|
|
7947
|
+
const fetchOpts = { headers: brainHeaders(config), signal: AbortSignal.timeout(30000) };
|
|
7948
|
+
if (opts.method) fetchOpts.method = opts.method;
|
|
7949
|
+
if (opts.body) { fetchOpts.method = opts.method || 'POST'; fetchOpts.body = JSON.stringify(opts.body); }
|
|
7950
|
+
const resp = await proxyFetch(url.toString(), fetchOpts);
|
|
7951
|
+
if (!resp.ok) {
|
|
7952
|
+
const errText = await resp.text().catch(() => resp.statusText);
|
|
7953
|
+
throw new Error(`${resp.status} ${errText}`);
|
|
7954
|
+
}
|
|
7955
|
+
return resp.json();
|
|
7956
|
+
}
|
|
7957
|
+
|
|
7874
7958
|
const brainCmd = program.command('brain').description('Shared intelligence — search, share, and manage collective knowledge');
|
|
7875
7959
|
|
|
7876
7960
|
brainCmd.command('search <query>')
|
|
@@ -7882,11 +7966,9 @@ brainCmd.command('search <query>')
|
|
|
7882
7966
|
.option('--json', 'Output as JSON')
|
|
7883
7967
|
.option('--verbose', 'Show detailed scoring and metadata per result')
|
|
7884
7968
|
.action(async (query, opts) => {
|
|
7885
|
-
const piBrain = await requirePiBrain();
|
|
7886
7969
|
const config = getBrainConfig(opts);
|
|
7887
7970
|
try {
|
|
7888
|
-
const
|
|
7889
|
-
const results = await client.search(query, { category: opts.category, limit: parseInt(opts.limit) });
|
|
7971
|
+
const results = await brainFetch(config, '/v1/memories/search', { params: { q: query, category: opts.category, limit: opts.limit } });
|
|
7890
7972
|
if (opts.json || !process.stdout.isTTY) { console.log(JSON.stringify(results, null, 2)); return; }
|
|
7891
7973
|
console.log(chalk.bold.cyan(`\nBrain Search: "${query}"\n`));
|
|
7892
7974
|
if (!results.length) { console.log(chalk.dim(' No results found.\n')); return; }
|
|
@@ -7916,11 +7998,9 @@ brainCmd.command('share <title>')
|
|
|
7916
7998
|
.option('--url <url>', 'Brain server URL')
|
|
7917
7999
|
.option('--key <key>', 'Pi key')
|
|
7918
8000
|
.action(async (title, opts) => {
|
|
7919
|
-
const piBrain = await requirePiBrain();
|
|
7920
8001
|
const config = getBrainConfig(opts);
|
|
7921
8002
|
try {
|
|
7922
|
-
const
|
|
7923
|
-
const result = await client.share({ title, content: opts.content || title, category: opts.category, tags: opts.tags ? opts.tags.split(',').map(t => t.trim()) : [], code_snippet: opts.code });
|
|
8003
|
+
const result = await brainFetch(config, '/v1/memories', { body: { title, content: opts.content || title, category: opts.category, tags: opts.tags ? opts.tags.split(',').map(t => t.trim()) : [], code_snippet: opts.code } });
|
|
7924
8004
|
console.log(chalk.green(`Shared: ${result.id || 'OK'}`));
|
|
7925
8005
|
} catch (e) { console.error(chalk.red(`Error: ${e.message}`)); process.exit(1); }
|
|
7926
8006
|
});
|
|
@@ -7931,11 +8011,9 @@ brainCmd.command('get <id>')
|
|
|
7931
8011
|
.option('--key <key>', 'Pi key')
|
|
7932
8012
|
.option('--json', 'Output as JSON')
|
|
7933
8013
|
.action(async (id, opts) => {
|
|
7934
|
-
const piBrain = await requirePiBrain();
|
|
7935
8014
|
const config = getBrainConfig(opts);
|
|
7936
8015
|
try {
|
|
7937
|
-
const
|
|
7938
|
-
const result = await client.get(id);
|
|
8016
|
+
const result = await brainFetch(config, `/v1/memories/${id}`);
|
|
7939
8017
|
if (opts.json || !process.stdout.isTTY) { console.log(JSON.stringify(result, null, 2)); return; }
|
|
7940
8018
|
console.log(chalk.bold.cyan(`\nMemory: ${id}\n`));
|
|
7941
8019
|
if (result.title) console.log(` ${chalk.bold('Title:')} ${result.title}`);
|
|
@@ -7950,11 +8028,9 @@ brainCmd.command('vote <id> <direction>')
|
|
|
7950
8028
|
.option('--url <url>', 'Brain server URL')
|
|
7951
8029
|
.option('--key <key>', 'Pi key')
|
|
7952
8030
|
.action(async (id, direction, opts) => {
|
|
7953
|
-
const piBrain = await requirePiBrain();
|
|
7954
8031
|
const config = getBrainConfig(opts);
|
|
7955
8032
|
try {
|
|
7956
|
-
|
|
7957
|
-
await client.vote(id, direction);
|
|
8033
|
+
await brainFetch(config, `/v1/memories/${id}/vote`, { body: { direction } });
|
|
7958
8034
|
console.log(chalk.green(`Voted ${direction} on ${id}`));
|
|
7959
8035
|
} catch (e) { console.error(chalk.red(`Error: ${e.message}`)); process.exit(1); }
|
|
7960
8036
|
});
|
|
@@ -7966,16 +8042,21 @@ brainCmd.command('list')
|
|
|
7966
8042
|
.option('--url <url>', 'Brain server URL')
|
|
7967
8043
|
.option('--key <key>', 'Pi key')
|
|
7968
8044
|
.option('--json', 'Output as JSON')
|
|
8045
|
+
.option('--offset <n>', 'Skip first N results (pagination)', '0')
|
|
8046
|
+
.option('--sort <field>', 'Sort by: updated_at, quality, votes', 'updated_at')
|
|
8047
|
+
.option('--tags <tags>', 'Filter by tags (comma-separated)')
|
|
7969
8048
|
.action(async (opts) => {
|
|
7970
|
-
const piBrain = await requirePiBrain();
|
|
7971
8049
|
const config = getBrainConfig(opts);
|
|
7972
8050
|
try {
|
|
7973
|
-
const
|
|
7974
|
-
|
|
7975
|
-
if (opts.json || !process.stdout.isTTY) { console.log(JSON.stringify(results, null, 2)); return; }
|
|
8051
|
+
const data = await brainFetch(config, '/v1/memories/list', { params: { category: opts.category, limit: opts.limit, offset: opts.offset, sort: opts.sort, tags: opts.tags } });
|
|
8052
|
+
if (opts.json || !process.stdout.isTTY) { console.log(JSON.stringify(data, null, 2)); return; }
|
|
7976
8053
|
console.log(chalk.bold.cyan('\nShared Brain Memories\n'));
|
|
7977
|
-
|
|
7978
|
-
|
|
8054
|
+
const memories = Array.isArray(data) ? data : data.memories || [];
|
|
8055
|
+
if (data.total_count !== undefined) {
|
|
8056
|
+
console.log(chalk.dim(` Showing ${memories.length} of ${data.total_count} (offset ${data.offset || 0})\n`));
|
|
8057
|
+
}
|
|
8058
|
+
if (!memories.length) { console.log(chalk.dim(' No memories found.\n')); return; }
|
|
8059
|
+
memories.forEach((r, i) => {
|
|
7979
8060
|
console.log(` ${chalk.yellow(i + 1 + '.')} ${chalk.bold(r.title || r.id)} ${chalk.dim(`[${r.category || 'unknown'}]`)}`);
|
|
7980
8061
|
});
|
|
7981
8062
|
console.log();
|
|
@@ -7987,11 +8068,9 @@ brainCmd.command('delete <id>')
|
|
|
7987
8068
|
.option('--url <url>', 'Brain server URL')
|
|
7988
8069
|
.option('--key <key>', 'Pi key')
|
|
7989
8070
|
.action(async (id, opts) => {
|
|
7990
|
-
const piBrain = await requirePiBrain();
|
|
7991
8071
|
const config = getBrainConfig(opts);
|
|
7992
8072
|
try {
|
|
7993
|
-
|
|
7994
|
-
await client.delete(id);
|
|
8073
|
+
await brainFetch(config, `/v1/memories/${id}`, { method: 'DELETE' });
|
|
7995
8074
|
console.log(chalk.green(`Deleted: ${id}`));
|
|
7996
8075
|
} catch (e) { console.error(chalk.red(`Error: ${e.message}`)); process.exit(1); }
|
|
7997
8076
|
});
|
|
@@ -8002,11 +8081,9 @@ brainCmd.command('status')
|
|
|
8002
8081
|
.option('--key <key>', 'Pi key')
|
|
8003
8082
|
.option('--json', 'Output as JSON')
|
|
8004
8083
|
.action(async (opts) => {
|
|
8005
|
-
const piBrain = await requirePiBrain();
|
|
8006
8084
|
const config = getBrainConfig(opts);
|
|
8007
8085
|
try {
|
|
8008
|
-
const
|
|
8009
|
-
const status = await client.status();
|
|
8086
|
+
const status = await brainFetch(config, '/v1/status');
|
|
8010
8087
|
if (opts.json || !process.stdout.isTTY) { console.log(JSON.stringify(status, null, 2)); return; }
|
|
8011
8088
|
console.log(chalk.bold.cyan('\nBrain Status\n'));
|
|
8012
8089
|
Object.entries(status).forEach(([k, v]) => {
|
|
@@ -8037,11 +8114,9 @@ brainCmd.command('drift')
|
|
|
8037
8114
|
.option('--key <key>', 'Pi key')
|
|
8038
8115
|
.option('--json', 'Output as JSON')
|
|
8039
8116
|
.action(async (opts) => {
|
|
8040
|
-
const piBrain = await requirePiBrain();
|
|
8041
8117
|
const config = getBrainConfig(opts);
|
|
8042
8118
|
try {
|
|
8043
|
-
const
|
|
8044
|
-
const report = await client.drift({ domain: opts.domain });
|
|
8119
|
+
const report = await brainFetch(config, '/v1/drift', { params: { domain: opts.domain } });
|
|
8045
8120
|
if (opts.json || !process.stdout.isTTY) { console.log(JSON.stringify(report, null, 2)); return; }
|
|
8046
8121
|
console.log(chalk.bold.cyan('\nDrift Report\n'));
|
|
8047
8122
|
console.log(` ${chalk.bold('Drifting:')} ${report.is_drifting ? chalk.red('Yes') : chalk.green('No')}`);
|
|
@@ -8058,11 +8133,9 @@ brainCmd.command('partition')
|
|
|
8058
8133
|
.option('--key <key>', 'Pi key')
|
|
8059
8134
|
.option('--json', 'Output as JSON')
|
|
8060
8135
|
.action(async (opts) => {
|
|
8061
|
-
const piBrain = await requirePiBrain();
|
|
8062
8136
|
const config = getBrainConfig(opts);
|
|
8063
8137
|
try {
|
|
8064
|
-
const
|
|
8065
|
-
const result = await client.partition({ domain: opts.domain, min_cluster_size: parseInt(opts.minSize) });
|
|
8138
|
+
const result = await brainFetch(config, '/v1/partition', { params: { domain: opts.domain, min_cluster_size: opts.minSize } });
|
|
8066
8139
|
if (opts.json || !process.stdout.isTTY) { console.log(JSON.stringify(result, null, 2)); return; }
|
|
8067
8140
|
console.log(chalk.bold.cyan('\nKnowledge Partitions\n'));
|
|
8068
8141
|
if (result.clusters) {
|
|
@@ -8080,11 +8153,9 @@ brainCmd.command('transfer <source> <target>')
|
|
|
8080
8153
|
.option('--key <key>', 'Pi key')
|
|
8081
8154
|
.option('--json', 'Output as JSON')
|
|
8082
8155
|
.action(async (source, target, opts) => {
|
|
8083
|
-
const piBrain = await requirePiBrain();
|
|
8084
8156
|
const config = getBrainConfig(opts);
|
|
8085
8157
|
try {
|
|
8086
|
-
const
|
|
8087
|
-
const result = await client.transfer(source, target);
|
|
8158
|
+
const result = await brainFetch(config, '/v1/transfer', { body: { source_domain: source, target_domain: target } });
|
|
8088
8159
|
if (opts.json || !process.stdout.isTTY) { console.log(JSON.stringify(result, null, 2)); return; }
|
|
8089
8160
|
console.log(chalk.green(`Transfer ${source} -> ${target}: ${result.status || 'OK'}`));
|
|
8090
8161
|
} catch (e) { console.error(chalk.red(`Error: ${e.message}`)); process.exit(1); }
|
|
@@ -8095,11 +8166,9 @@ brainCmd.command('sync [direction]')
|
|
|
8095
8166
|
.option('--url <url>', 'Brain server URL')
|
|
8096
8167
|
.option('--key <key>', 'Pi key')
|
|
8097
8168
|
.action(async (direction, opts) => {
|
|
8098
|
-
const piBrain = await requirePiBrain();
|
|
8099
8169
|
const config = getBrainConfig(opts);
|
|
8100
8170
|
try {
|
|
8101
|
-
const
|
|
8102
|
-
const result = await client.sync(direction || 'both');
|
|
8171
|
+
const result = await brainFetch(config, '/v1/lora/latest', { params: { direction: direction || 'both' } });
|
|
8103
8172
|
console.log(chalk.green(`Sync ${direction || 'both'}: ${result.status || 'OK'}`));
|
|
8104
8173
|
} catch (e) { console.error(chalk.red(`Error: ${e.message}`)); process.exit(1); }
|
|
8105
8174
|
});
|
|
@@ -8110,30 +8179,28 @@ brainCmd.command('page <action> [args...]')
|
|
|
8110
8179
|
.option('--key <key>', 'Pi key')
|
|
8111
8180
|
.option('--json', 'Output as JSON')
|
|
8112
8181
|
.action(async (action, args, opts) => {
|
|
8113
|
-
const piBrain = await requirePiBrain();
|
|
8114
8182
|
const config = getBrainConfig(opts);
|
|
8115
8183
|
try {
|
|
8116
|
-
const client = new piBrain.PiBrainClient(config);
|
|
8117
8184
|
let result;
|
|
8118
8185
|
switch (action) {
|
|
8119
8186
|
case 'list':
|
|
8120
|
-
result = await
|
|
8187
|
+
result = await brainFetch(config, '/v1/pages', { params: { limit: 20 } }).catch(() => ({ pages: [], message: 'Brainpedia endpoint not available' }));
|
|
8121
8188
|
break;
|
|
8122
8189
|
case 'get':
|
|
8123
8190
|
if (!args[0]) { console.error(chalk.red('Usage: brain page get <slug>')); process.exit(1); }
|
|
8124
|
-
result = await
|
|
8191
|
+
result = await brainFetch(config, `/v1/pages/${args[0]}`);
|
|
8125
8192
|
break;
|
|
8126
8193
|
case 'create':
|
|
8127
8194
|
if (!args[0]) { console.error(chalk.red('Usage: brain page create <title> [--content <text>]')); process.exit(1); }
|
|
8128
|
-
result = await
|
|
8195
|
+
result = await brainFetch(config, '/v1/pages', { body: { title: args[0], content: opts.content || '' } });
|
|
8129
8196
|
break;
|
|
8130
8197
|
case 'update':
|
|
8131
8198
|
if (!args[0]) { console.error(chalk.red('Usage: brain page update <slug> [--content <text>]')); process.exit(1); }
|
|
8132
|
-
result = await
|
|
8199
|
+
result = await brainFetch(config, `/v1/pages/${args[0]}/deltas`, { body: { content: opts.content || '' } });
|
|
8133
8200
|
break;
|
|
8134
8201
|
case 'delete':
|
|
8135
8202
|
if (!args[0]) { console.error(chalk.red('Usage: brain page delete <slug>')); process.exit(1); }
|
|
8136
|
-
result = await
|
|
8203
|
+
result = await brainFetch(config, `/v1/pages/${args[0]}`, { method: 'DELETE' }).catch(() => ({ error: 'Delete not available' }));
|
|
8137
8204
|
break;
|
|
8138
8205
|
default:
|
|
8139
8206
|
console.error(chalk.red(`Unknown page action: ${action}. Use: list, get, create, update, delete`));
|
|
@@ -8159,10 +8226,8 @@ brainCmd.command('node <action> [args...]')
|
|
|
8159
8226
|
.option('--key <key>', 'Pi key')
|
|
8160
8227
|
.option('--json', 'Output as JSON')
|
|
8161
8228
|
.action(async (action, args, opts) => {
|
|
8162
|
-
const piBrain = await requirePiBrain();
|
|
8163
8229
|
const config = getBrainConfig(opts);
|
|
8164
8230
|
try {
|
|
8165
|
-
const client = new piBrain.PiBrainClient(config);
|
|
8166
8231
|
let result;
|
|
8167
8232
|
switch (action) {
|
|
8168
8233
|
case 'publish':
|
|
@@ -8170,14 +8235,14 @@ brainCmd.command('node <action> [args...]')
|
|
|
8170
8235
|
const wasmPath = path.resolve(args[0]);
|
|
8171
8236
|
if (!fs.existsSync(wasmPath)) { console.error(chalk.red(`File not found: ${wasmPath}`)); process.exit(1); }
|
|
8172
8237
|
const wasmBytes = fs.readFileSync(wasmPath);
|
|
8173
|
-
result = await
|
|
8238
|
+
result = await brainFetch(config, '/v1/nodes', { body: { name: path.basename(wasmPath, '.wasm'), wasm_base64: wasmBytes.toString('base64') } });
|
|
8174
8239
|
break;
|
|
8175
8240
|
case 'list':
|
|
8176
|
-
result = await
|
|
8241
|
+
result = await brainFetch(config, '/v1/nodes', { params: { limit: 20 } }).catch(() => ({ nodes: [], message: 'WASM node listing not available' }));
|
|
8177
8242
|
break;
|
|
8178
8243
|
case 'status':
|
|
8179
8244
|
if (!args[0]) { console.error(chalk.red('Usage: brain node status <node-id>')); process.exit(1); }
|
|
8180
|
-
result = await
|
|
8245
|
+
result = await brainFetch(config, `/v1/nodes/${args[0]}`);
|
|
8181
8246
|
break;
|
|
8182
8247
|
default:
|
|
8183
8248
|
console.error(chalk.red(`Unknown node action: ${action}. Use: publish, list, status`));
|
|
@@ -8203,7 +8268,7 @@ async function fetchBrainEndpoint(config, endpoint) {
|
|
|
8203
8268
|
const url = (config.url || 'https://pi.ruv.io') + endpoint;
|
|
8204
8269
|
const headers = {};
|
|
8205
8270
|
if (config.key) headers['Authorization'] = `Bearer ${config.key}`;
|
|
8206
|
-
const resp = await
|
|
8271
|
+
const resp = await proxyFetch(url, { headers, signal: AbortSignal.timeout(30000) });
|
|
8207
8272
|
if (!resp.ok) throw new Error(`${resp.status} ${resp.statusText}`);
|
|
8208
8273
|
return resp.json();
|
|
8209
8274
|
}
|
|
@@ -8432,7 +8497,7 @@ midstreamCmd.command('benchmark')
|
|
|
8432
8497
|
|
|
8433
8498
|
async function timeRequest(url, label) {
|
|
8434
8499
|
const start = performance.now();
|
|
8435
|
-
const resp = await
|
|
8500
|
+
const resp = await proxyFetch(url, { headers, signal: AbortSignal.timeout(30000) });
|
|
8436
8501
|
const elapsed = performance.now() - start;
|
|
8437
8502
|
return { label, status: resp.status, elapsed };
|
|
8438
8503
|
}
|
|
@@ -8502,7 +8567,7 @@ edgeCmd.command('status')
|
|
|
8502
8567
|
.option('--json', 'Output as JSON')
|
|
8503
8568
|
.action(async (opts) => {
|
|
8504
8569
|
try {
|
|
8505
|
-
const resp = await
|
|
8570
|
+
const resp = await proxyFetch(`${EDGE_GENESIS}/status`, { signal: AbortSignal.timeout(30000) });
|
|
8506
8571
|
const data = await resp.json();
|
|
8507
8572
|
if (opts.json || !process.stdout.isTTY) { console.log(JSON.stringify(data, null, 2)); return; }
|
|
8508
8573
|
console.log(chalk.bold.cyan('\nEdge Network Status\n'));
|
|
@@ -8518,10 +8583,11 @@ edgeCmd.command('join')
|
|
|
8518
8583
|
const piKey = process.env.PI;
|
|
8519
8584
|
if (!piKey) { console.error(chalk.red('Set PI environment variable first. Run: npx ruvector identity generate')); process.exit(1); }
|
|
8520
8585
|
try {
|
|
8521
|
-
const resp = await
|
|
8586
|
+
const resp = await proxyFetch(`${EDGE_GENESIS}/join`, {
|
|
8522
8587
|
method: 'POST',
|
|
8523
8588
|
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${piKey}` },
|
|
8524
|
-
body: JSON.stringify({ contribution: parseFloat(opts.contribution), pi_key: piKey })
|
|
8589
|
+
body: JSON.stringify({ contribution: parseFloat(opts.contribution), pi_key: piKey }),
|
|
8590
|
+
signal: AbortSignal.timeout(30000)
|
|
8525
8591
|
});
|
|
8526
8592
|
const data = await resp.json();
|
|
8527
8593
|
console.log(chalk.green(`Joined edge network: ${data.node_id || 'OK'}`));
|
|
@@ -8536,7 +8602,7 @@ edgeCmd.command('balance')
|
|
|
8536
8602
|
if (!piKey) { console.error(chalk.red('Set PI environment variable first.')); process.exit(1); }
|
|
8537
8603
|
try {
|
|
8538
8604
|
const pseudonym = require('crypto').createHash('shake256', { outputLength: 16 }).update(piKey).digest('hex');
|
|
8539
|
-
const resp = await
|
|
8605
|
+
const resp = await proxyFetch(`${EDGE_GENESIS}/balance/${pseudonym}`, { headers: { 'Authorization': `Bearer ${piKey}` }, signal: AbortSignal.timeout(30000) });
|
|
8540
8606
|
if (!resp.ok) { console.error(chalk.red(`Edge network returned ${resp.status} ${resp.statusText}`)); process.exit(1); }
|
|
8541
8607
|
const data = await resp.json();
|
|
8542
8608
|
if (opts.json || !process.stdout.isTTY) { console.log(JSON.stringify(data, null, 2)); return; }
|
|
@@ -8552,7 +8618,7 @@ edgeCmd.command('tasks')
|
|
|
8552
8618
|
.option('--json', 'Output as JSON')
|
|
8553
8619
|
.action(async (opts) => {
|
|
8554
8620
|
try {
|
|
8555
|
-
const resp = await
|
|
8621
|
+
const resp = await proxyFetch(`${EDGE_GENESIS}/tasks?limit=${opts.limit}`, { signal: AbortSignal.timeout(30000) });
|
|
8556
8622
|
const data = await resp.json();
|
|
8557
8623
|
if (opts.json || !process.stdout.isTTY) { console.log(JSON.stringify(data, null, 2)); return; }
|
|
8558
8624
|
console.log(chalk.bold.cyan('\nEdge Compute Tasks\n'));
|
package/bin/mcp-server.js
CHANGED
|
@@ -95,6 +95,71 @@ function sanitizeNumericArg(arg, defaultVal) {
|
|
|
95
95
|
return Number.isFinite(n) && n > 0 ? n : (defaultVal || 0);
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
// ── Proxy-aware fetch wrapper ───────────────────────────────────────────────
|
|
99
|
+
let _proxyDispatcherSet = false;
|
|
100
|
+
|
|
101
|
+
function getProxyUrl(targetUrl) {
|
|
102
|
+
const NO_PROXY = process.env.NO_PROXY || process.env.no_proxy || '';
|
|
103
|
+
if (NO_PROXY === '*') return null;
|
|
104
|
+
if (NO_PROXY) {
|
|
105
|
+
try {
|
|
106
|
+
const host = new URL(targetUrl).hostname.toLowerCase();
|
|
107
|
+
const patterns = NO_PROXY.split(',').map(p => p.trim().toLowerCase());
|
|
108
|
+
for (const p of patterns) {
|
|
109
|
+
if (!p) continue;
|
|
110
|
+
if (host === p || host.endsWith(p.startsWith('.') ? p : '.' + p)) return null;
|
|
111
|
+
}
|
|
112
|
+
} catch {}
|
|
113
|
+
}
|
|
114
|
+
return process.env.HTTPS_PROXY || process.env.https_proxy
|
|
115
|
+
|| process.env.HTTP_PROXY || process.env.http_proxy
|
|
116
|
+
|| process.env.ALL_PROXY || process.env.all_proxy
|
|
117
|
+
|| null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function proxyFetch(url, opts) {
|
|
121
|
+
const target = typeof url === 'string' ? url : url.toString();
|
|
122
|
+
const proxy = getProxyUrl(target);
|
|
123
|
+
if (!proxy) return fetch(url, opts);
|
|
124
|
+
|
|
125
|
+
if (!_proxyDispatcherSet) {
|
|
126
|
+
try {
|
|
127
|
+
const undici = require('undici');
|
|
128
|
+
if (undici.ProxyAgent && undici.setGlobalDispatcher) {
|
|
129
|
+
undici.setGlobalDispatcher(new undici.ProxyAgent(proxy));
|
|
130
|
+
_proxyDispatcherSet = true;
|
|
131
|
+
}
|
|
132
|
+
} catch {}
|
|
133
|
+
}
|
|
134
|
+
if (_proxyDispatcherSet) return fetch(url, opts);
|
|
135
|
+
|
|
136
|
+
const { execFileSync } = require('child_process');
|
|
137
|
+
const args = ['-sS', '-L', '--max-time', '30'];
|
|
138
|
+
if (opts && opts.method) { args.push('-X', opts.method); }
|
|
139
|
+
if (opts && opts.headers) {
|
|
140
|
+
for (const [k, v] of Object.entries(opts.headers)) {
|
|
141
|
+
args.push('-H', `${k}: ${v}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (opts && opts.body) { args.push('-d', typeof opts.body === 'string' ? opts.body : JSON.stringify(opts.body)); }
|
|
145
|
+
args.push(target);
|
|
146
|
+
try {
|
|
147
|
+
const stdout = execFileSync('curl', args, { encoding: 'utf8', timeout: 35000 });
|
|
148
|
+
const body = stdout.trim();
|
|
149
|
+
return {
|
|
150
|
+
ok: true,
|
|
151
|
+
status: 200,
|
|
152
|
+
statusText: 'OK',
|
|
153
|
+
text: async () => body,
|
|
154
|
+
json: async () => JSON.parse(body),
|
|
155
|
+
headers: new Map(),
|
|
156
|
+
};
|
|
157
|
+
} catch (e) {
|
|
158
|
+
const msg = e.stderr ? e.stderr.toString().trim() : e.message;
|
|
159
|
+
throw new Error(`Proxy curl failed: ${msg}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
98
163
|
// Try to load the full IntelligenceEngine
|
|
99
164
|
let IntelligenceEngine = null;
|
|
100
165
|
let engineAvailable = false;
|
|
@@ -363,7 +428,7 @@ class Intelligence {
|
|
|
363
428
|
const server = new Server(
|
|
364
429
|
{
|
|
365
430
|
name: 'ruvector',
|
|
366
|
-
version: '0.2.
|
|
431
|
+
version: '0.2.7',
|
|
367
432
|
},
|
|
368
433
|
{
|
|
369
434
|
capabilities: {
|
|
@@ -3122,7 +3187,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3122
3187
|
// Fetch from GCS if no fresh cache
|
|
3123
3188
|
if (!manifest) {
|
|
3124
3189
|
try {
|
|
3125
|
-
const resp = await
|
|
3190
|
+
const resp = await proxyFetch(GCS_MANIFEST, { signal: AbortSignal.timeout(15000) });
|
|
3126
3191
|
if (resp.ok) {
|
|
3127
3192
|
manifest = await resp.json();
|
|
3128
3193
|
try {
|
|
@@ -3276,7 +3341,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3276
3341
|
}
|
|
3277
3342
|
}
|
|
3278
3343
|
|
|
3279
|
-
// ── Brain Tool Handlers
|
|
3344
|
+
// ── Brain Tool Handlers (direct fetch to pi.ruv.io) ─────────────────
|
|
3280
3345
|
case 'brain_search':
|
|
3281
3346
|
case 'brain_share':
|
|
3282
3347
|
case 'brain_get':
|
|
@@ -3289,31 +3354,78 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3289
3354
|
case 'brain_transfer':
|
|
3290
3355
|
case 'brain_sync': {
|
|
3291
3356
|
try {
|
|
3292
|
-
const
|
|
3293
|
-
const
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
});
|
|
3357
|
+
const brainUrl = process.env.BRAIN_URL || 'https://pi.ruv.io';
|
|
3358
|
+
const brainKey = process.env.PI;
|
|
3359
|
+
const hdrs = { 'Content-Type': 'application/json' };
|
|
3360
|
+
if (brainKey) hdrs['Authorization'] = `Bearer ${brainKey}`;
|
|
3297
3361
|
const subCmd = name.replace('brain_', '');
|
|
3298
|
-
let
|
|
3362
|
+
let url, fetchOpts = { headers: hdrs, signal: AbortSignal.timeout(30000) };
|
|
3299
3363
|
switch (subCmd) {
|
|
3300
|
-
case 'search':
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
case '
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3364
|
+
case 'search': {
|
|
3365
|
+
const p = new URLSearchParams({ q: args.query || '' });
|
|
3366
|
+
if (args.category) p.set('category', args.category);
|
|
3367
|
+
if (args.limit) p.set('limit', String(args.limit));
|
|
3368
|
+
url = `${brainUrl}/v1/memories/search?${p}`;
|
|
3369
|
+
break;
|
|
3370
|
+
}
|
|
3371
|
+
case 'share': {
|
|
3372
|
+
url = `${brainUrl}/v1/memories`;
|
|
3373
|
+
fetchOpts.method = 'POST';
|
|
3374
|
+
fetchOpts.body = JSON.stringify({ title: args.title, content: args.content, category: args.category, tags: args.tags ? args.tags.split(',').map(t => t.trim()) : [], code_snippet: args.code_snippet });
|
|
3375
|
+
break;
|
|
3376
|
+
}
|
|
3377
|
+
case 'get': url = `${brainUrl}/v1/memories/${args.id}`; break;
|
|
3378
|
+
case 'vote': {
|
|
3379
|
+
url = `${brainUrl}/v1/memories/${args.id}/vote`;
|
|
3380
|
+
fetchOpts.method = 'POST';
|
|
3381
|
+
fetchOpts.body = JSON.stringify({ direction: args.direction });
|
|
3382
|
+
break;
|
|
3383
|
+
}
|
|
3384
|
+
case 'list': {
|
|
3385
|
+
const p = new URLSearchParams();
|
|
3386
|
+
if (args.category) p.set('category', args.category);
|
|
3387
|
+
p.set('limit', String(args.limit || 20));
|
|
3388
|
+
if (args.offset) p.set('offset', String(args.offset));
|
|
3389
|
+
if (args.sort) p.set('sort', args.sort);
|
|
3390
|
+
if (args.tags) p.set('tags', args.tags);
|
|
3391
|
+
url = `${brainUrl}/v1/memories/list?${p}`;
|
|
3392
|
+
break;
|
|
3393
|
+
}
|
|
3394
|
+
case 'delete': {
|
|
3395
|
+
url = `${brainUrl}/v1/memories/${args.id}`;
|
|
3396
|
+
fetchOpts.method = 'DELETE';
|
|
3397
|
+
break;
|
|
3398
|
+
}
|
|
3399
|
+
case 'status': url = `${brainUrl}/v1/status`; break;
|
|
3400
|
+
case 'drift': {
|
|
3401
|
+
const p = new URLSearchParams();
|
|
3402
|
+
if (args.domain) p.set('domain', args.domain);
|
|
3403
|
+
url = `${brainUrl}/v1/drift?${p}`;
|
|
3404
|
+
break;
|
|
3405
|
+
}
|
|
3406
|
+
case 'partition': {
|
|
3407
|
+
const p = new URLSearchParams();
|
|
3408
|
+
if (args.domain) p.set('domain', args.domain);
|
|
3409
|
+
if (args.min_cluster_size) p.set('min_cluster_size', String(args.min_cluster_size));
|
|
3410
|
+
url = `${brainUrl}/v1/partition?${p}`;
|
|
3411
|
+
break;
|
|
3412
|
+
}
|
|
3413
|
+
case 'transfer': {
|
|
3414
|
+
url = `${brainUrl}/v1/transfer`;
|
|
3415
|
+
fetchOpts.method = 'POST';
|
|
3416
|
+
fetchOpts.body = JSON.stringify({ source_domain: args.source_domain, target_domain: args.target_domain });
|
|
3417
|
+
break;
|
|
3418
|
+
}
|
|
3419
|
+
case 'sync': url = `${brainUrl}/v1/lora/latest`; break;
|
|
3311
3420
|
}
|
|
3421
|
+
const resp = await proxyFetch(url, fetchOpts);
|
|
3422
|
+
if (!resp.ok) {
|
|
3423
|
+
const errText = await resp.text().catch(() => resp.statusText);
|
|
3424
|
+
return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: `${resp.status} ${errText}` }, null, 2) }], isError: true };
|
|
3425
|
+
}
|
|
3426
|
+
const result = await resp.json();
|
|
3312
3427
|
return { content: [{ type: 'text', text: JSON.stringify({ success: true, ...result }, null, 2) }] };
|
|
3313
3428
|
} catch (e) {
|
|
3314
|
-
if (e.code === 'MODULE_NOT_FOUND') {
|
|
3315
|
-
return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: 'Brain tools require @ruvector/pi-brain. Install with: npm install @ruvector/pi-brain' }, null, 2) }], isError: true };
|
|
3316
|
-
}
|
|
3317
3429
|
return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: e.message }, null, 2) }], isError: true };
|
|
3318
3430
|
}
|
|
3319
3431
|
}
|
|
@@ -3340,7 +3452,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3340
3452
|
brain_flags: '/v1/status',
|
|
3341
3453
|
};
|
|
3342
3454
|
const endpoint = endpointMap[name];
|
|
3343
|
-
const resp = await
|
|
3455
|
+
const resp = await proxyFetch(`${brainUrl}${endpoint}`, { headers: hdrs, signal: AbortSignal.timeout(30000) });
|
|
3344
3456
|
if (!resp.ok) {
|
|
3345
3457
|
const errText = await resp.text().catch(() => resp.statusText);
|
|
3346
3458
|
return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: `${resp.status} ${errText}` }, null, 2) }], isError: true };
|
|
@@ -3384,7 +3496,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3384
3496
|
const hdrs = { 'Content-Type': 'application/json' };
|
|
3385
3497
|
if (brainKey) hdrs['Authorization'] = `Bearer ${brainKey}`;
|
|
3386
3498
|
|
|
3387
|
-
const resp = await
|
|
3499
|
+
const resp = await proxyFetch(`${brainUrl}/v1/midstream`, { headers: hdrs, signal: AbortSignal.timeout(30000) });
|
|
3388
3500
|
if (!resp.ok) {
|
|
3389
3501
|
const errText = await resp.text().catch(() => resp.statusText);
|
|
3390
3502
|
return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: `${resp.status} ${errText}` }, null, 2) }], isError: true };
|
|
@@ -3414,7 +3526,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3414
3526
|
|
|
3415
3527
|
async function timeFetch(url) {
|
|
3416
3528
|
const start = performance.now();
|
|
3417
|
-
const resp = await
|
|
3529
|
+
const resp = await proxyFetch(url, { headers: hdrs });
|
|
3418
3530
|
return { status: resp.status, elapsed: performance.now() - start };
|
|
3419
3531
|
}
|
|
3420
3532
|
|
|
@@ -3467,7 +3579,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3467
3579
|
if (brainKey) hdrs['Authorization'] = `Bearer ${brainKey}`;
|
|
3468
3580
|
const limit = Math.min(Math.max(parseInt(args.limit) || 10, 1), 100);
|
|
3469
3581
|
const q = encodeURIComponent(args.query);
|
|
3470
|
-
const resp = await
|
|
3582
|
+
const resp = await proxyFetch(`${brainUrl}/v1/memories/search?q=${q}&limit=${limit}`, { headers: hdrs, signal: AbortSignal.timeout(30000) });
|
|
3471
3583
|
if (!resp.ok) {
|
|
3472
3584
|
const errText = await resp.text().catch(() => resp.statusText);
|
|
3473
3585
|
return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: `${resp.status} ${errText}` }, null, 2) }], isError: true };
|
|
@@ -3487,8 +3599,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3487
3599
|
if (brainKey) hdrs['Authorization'] = `Bearer ${brainKey}`;
|
|
3488
3600
|
|
|
3489
3601
|
const [healthResp, midResp] = await Promise.all([
|
|
3490
|
-
|
|
3491
|
-
|
|
3602
|
+
proxyFetch(`${brainUrl}/v1/health`, { headers: hdrs, signal: AbortSignal.timeout(15000) }).then(r => r.json()).catch(e => ({ error: e.message })),
|
|
3603
|
+
proxyFetch(`${brainUrl}/v1/midstream`, { headers: hdrs, signal: AbortSignal.timeout(15000) }).then(r => r.json()).catch(e => ({ error: e.message })),
|
|
3492
3604
|
]);
|
|
3493
3605
|
|
|
3494
3606
|
return { content: [{ type: 'text', text: JSON.stringify({
|
|
@@ -3516,7 +3628,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3516
3628
|
case 'balance': { const ps = process.env.PI ? require('crypto').createHash('shake256', { outputLength: 16 }).update(process.env.PI).digest('hex') : 'anonymous'; endpoint = `/balance/${ps}`; break; }
|
|
3517
3629
|
case 'tasks': endpoint = `/tasks?limit=${args.limit || 20}`; break;
|
|
3518
3630
|
}
|
|
3519
|
-
const resp = await
|
|
3631
|
+
const resp = await proxyFetch(`${genesisUrl}${endpoint}`, {
|
|
3520
3632
|
method,
|
|
3521
3633
|
headers: { 'Content-Type': 'application/json', ...(process.env.PI ? { 'Authorization': `Bearer ${process.env.PI}` } : {}) },
|
|
3522
3634
|
...(body ? { body } : {})
|
|
@@ -3787,7 +3899,7 @@ async function main() {
|
|
|
3787
3899
|
transport: 'sse',
|
|
3788
3900
|
sessions: sessions.size,
|
|
3789
3901
|
tools: 91,
|
|
3790
|
-
version: '0.2.
|
|
3902
|
+
version: '0.2.7'
|
|
3791
3903
|
}));
|
|
3792
3904
|
|
|
3793
3905
|
} else {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ruvector",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.7",
|
|
4
4
|
"description": "High-performance vector database for Node.js with automatic native/WASM fallback",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -80,12 +80,10 @@
|
|
|
80
80
|
"typescript": "^5.3.3"
|
|
81
81
|
},
|
|
82
82
|
"peerDependencies": {
|
|
83
|
-
"@ruvector/pi-brain": ">=0.1.0",
|
|
84
83
|
"@ruvector/ruvllm": ">=2.0.0",
|
|
85
84
|
"@ruvector/router": ">=0.1.0"
|
|
86
85
|
},
|
|
87
86
|
"peerDependenciesMeta": {
|
|
88
|
-
"@ruvector/pi-brain": { "optional": true },
|
|
89
87
|
"@ruvector/ruvllm": { "optional": true },
|
|
90
88
|
"@ruvector/router": { "optional": true }
|
|
91
89
|
},
|