ruvector 0.2.6 → 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 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 fetch(GCS_MANIFEST_URL);
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 });
@@ -7877,7 +7947,7 @@ async function brainFetch(config, endpoint, opts = {}) {
7877
7947
  const fetchOpts = { headers: brainHeaders(config), signal: AbortSignal.timeout(30000) };
7878
7948
  if (opts.method) fetchOpts.method = opts.method;
7879
7949
  if (opts.body) { fetchOpts.method = opts.method || 'POST'; fetchOpts.body = JSON.stringify(opts.body); }
7880
- const resp = await fetch(url.toString(), fetchOpts);
7950
+ const resp = await proxyFetch(url.toString(), fetchOpts);
7881
7951
  if (!resp.ok) {
7882
7952
  const errText = await resp.text().catch(() => resp.statusText);
7883
7953
  throw new Error(`${resp.status} ${errText}`);
@@ -7972,14 +8042,21 @@ brainCmd.command('list')
7972
8042
  .option('--url <url>', 'Brain server URL')
7973
8043
  .option('--key <key>', 'Pi key')
7974
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)')
7975
8048
  .action(async (opts) => {
7976
8049
  const config = getBrainConfig(opts);
7977
8050
  try {
7978
- const results = await brainFetch(config, '/v1/memories/list', { params: { category: opts.category, limit: opts.limit } });
7979
- 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; }
7980
8053
  console.log(chalk.bold.cyan('\nShared Brain Memories\n'));
7981
- if (!results.length) { console.log(chalk.dim(' No memories found.\n')); return; }
7982
- results.forEach((r, i) => {
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) => {
7983
8060
  console.log(` ${chalk.yellow(i + 1 + '.')} ${chalk.bold(r.title || r.id)} ${chalk.dim(`[${r.category || 'unknown'}]`)}`);
7984
8061
  });
7985
8062
  console.log();
@@ -8191,7 +8268,7 @@ async function fetchBrainEndpoint(config, endpoint) {
8191
8268
  const url = (config.url || 'https://pi.ruv.io') + endpoint;
8192
8269
  const headers = {};
8193
8270
  if (config.key) headers['Authorization'] = `Bearer ${config.key}`;
8194
- const resp = await fetch(url, { headers });
8271
+ const resp = await proxyFetch(url, { headers, signal: AbortSignal.timeout(30000) });
8195
8272
  if (!resp.ok) throw new Error(`${resp.status} ${resp.statusText}`);
8196
8273
  return resp.json();
8197
8274
  }
@@ -8420,7 +8497,7 @@ midstreamCmd.command('benchmark')
8420
8497
 
8421
8498
  async function timeRequest(url, label) {
8422
8499
  const start = performance.now();
8423
- const resp = await fetch(url, { headers });
8500
+ const resp = await proxyFetch(url, { headers, signal: AbortSignal.timeout(30000) });
8424
8501
  const elapsed = performance.now() - start;
8425
8502
  return { label, status: resp.status, elapsed };
8426
8503
  }
@@ -8490,7 +8567,7 @@ edgeCmd.command('status')
8490
8567
  .option('--json', 'Output as JSON')
8491
8568
  .action(async (opts) => {
8492
8569
  try {
8493
- const resp = await fetch(`${EDGE_GENESIS}/status`);
8570
+ const resp = await proxyFetch(`${EDGE_GENESIS}/status`, { signal: AbortSignal.timeout(30000) });
8494
8571
  const data = await resp.json();
8495
8572
  if (opts.json || !process.stdout.isTTY) { console.log(JSON.stringify(data, null, 2)); return; }
8496
8573
  console.log(chalk.bold.cyan('\nEdge Network Status\n'));
@@ -8506,10 +8583,11 @@ edgeCmd.command('join')
8506
8583
  const piKey = process.env.PI;
8507
8584
  if (!piKey) { console.error(chalk.red('Set PI environment variable first. Run: npx ruvector identity generate')); process.exit(1); }
8508
8585
  try {
8509
- const resp = await fetch(`${EDGE_GENESIS}/join`, {
8586
+ const resp = await proxyFetch(`${EDGE_GENESIS}/join`, {
8510
8587
  method: 'POST',
8511
8588
  headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${piKey}` },
8512
- 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)
8513
8591
  });
8514
8592
  const data = await resp.json();
8515
8593
  console.log(chalk.green(`Joined edge network: ${data.node_id || 'OK'}`));
@@ -8524,7 +8602,7 @@ edgeCmd.command('balance')
8524
8602
  if (!piKey) { console.error(chalk.red('Set PI environment variable first.')); process.exit(1); }
8525
8603
  try {
8526
8604
  const pseudonym = require('crypto').createHash('shake256', { outputLength: 16 }).update(piKey).digest('hex');
8527
- const resp = await fetch(`${EDGE_GENESIS}/balance/${pseudonym}`, { headers: { 'Authorization': `Bearer ${piKey}` } });
8605
+ const resp = await proxyFetch(`${EDGE_GENESIS}/balance/${pseudonym}`, { headers: { 'Authorization': `Bearer ${piKey}` }, signal: AbortSignal.timeout(30000) });
8528
8606
  if (!resp.ok) { console.error(chalk.red(`Edge network returned ${resp.status} ${resp.statusText}`)); process.exit(1); }
8529
8607
  const data = await resp.json();
8530
8608
  if (opts.json || !process.stdout.isTTY) { console.log(JSON.stringify(data, null, 2)); return; }
@@ -8540,7 +8618,7 @@ edgeCmd.command('tasks')
8540
8618
  .option('--json', 'Output as JSON')
8541
8619
  .action(async (opts) => {
8542
8620
  try {
8543
- const resp = await fetch(`${EDGE_GENESIS}/tasks?limit=${opts.limit}`);
8621
+ const resp = await proxyFetch(`${EDGE_GENESIS}/tasks?limit=${opts.limit}`, { signal: AbortSignal.timeout(30000) });
8544
8622
  const data = await resp.json();
8545
8623
  if (opts.json || !process.stdout.isTTY) { console.log(JSON.stringify(data, null, 2)); return; }
8546
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.6',
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 fetch(GCS_MANIFEST);
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 {
@@ -3320,6 +3385,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3320
3385
  const p = new URLSearchParams();
3321
3386
  if (args.category) p.set('category', args.category);
3322
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);
3323
3391
  url = `${brainUrl}/v1/memories/list?${p}`;
3324
3392
  break;
3325
3393
  }
@@ -3350,7 +3418,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3350
3418
  }
3351
3419
  case 'sync': url = `${brainUrl}/v1/lora/latest`; break;
3352
3420
  }
3353
- const resp = await fetch(url, fetchOpts);
3421
+ const resp = await proxyFetch(url, fetchOpts);
3354
3422
  if (!resp.ok) {
3355
3423
  const errText = await resp.text().catch(() => resp.statusText);
3356
3424
  return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: `${resp.status} ${errText}` }, null, 2) }], isError: true };
@@ -3384,7 +3452,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3384
3452
  brain_flags: '/v1/status',
3385
3453
  };
3386
3454
  const endpoint = endpointMap[name];
3387
- const resp = await fetch(`${brainUrl}${endpoint}`, { headers: hdrs });
3455
+ const resp = await proxyFetch(`${brainUrl}${endpoint}`, { headers: hdrs, signal: AbortSignal.timeout(30000) });
3388
3456
  if (!resp.ok) {
3389
3457
  const errText = await resp.text().catch(() => resp.statusText);
3390
3458
  return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: `${resp.status} ${errText}` }, null, 2) }], isError: true };
@@ -3428,7 +3496,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3428
3496
  const hdrs = { 'Content-Type': 'application/json' };
3429
3497
  if (brainKey) hdrs['Authorization'] = `Bearer ${brainKey}`;
3430
3498
 
3431
- const resp = await fetch(`${brainUrl}/v1/midstream`, { headers: hdrs });
3499
+ const resp = await proxyFetch(`${brainUrl}/v1/midstream`, { headers: hdrs, signal: AbortSignal.timeout(30000) });
3432
3500
  if (!resp.ok) {
3433
3501
  const errText = await resp.text().catch(() => resp.statusText);
3434
3502
  return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: `${resp.status} ${errText}` }, null, 2) }], isError: true };
@@ -3458,7 +3526,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3458
3526
 
3459
3527
  async function timeFetch(url) {
3460
3528
  const start = performance.now();
3461
- const resp = await fetch(url, { headers: hdrs });
3529
+ const resp = await proxyFetch(url, { headers: hdrs });
3462
3530
  return { status: resp.status, elapsed: performance.now() - start };
3463
3531
  }
3464
3532
 
@@ -3511,7 +3579,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3511
3579
  if (brainKey) hdrs['Authorization'] = `Bearer ${brainKey}`;
3512
3580
  const limit = Math.min(Math.max(parseInt(args.limit) || 10, 1), 100);
3513
3581
  const q = encodeURIComponent(args.query);
3514
- const resp = await fetch(`${brainUrl}/v1/memories/search?q=${q}&limit=${limit}`, { headers: hdrs });
3582
+ const resp = await proxyFetch(`${brainUrl}/v1/memories/search?q=${q}&limit=${limit}`, { headers: hdrs, signal: AbortSignal.timeout(30000) });
3515
3583
  if (!resp.ok) {
3516
3584
  const errText = await resp.text().catch(() => resp.statusText);
3517
3585
  return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: `${resp.status} ${errText}` }, null, 2) }], isError: true };
@@ -3531,8 +3599,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3531
3599
  if (brainKey) hdrs['Authorization'] = `Bearer ${brainKey}`;
3532
3600
 
3533
3601
  const [healthResp, midResp] = await Promise.all([
3534
- fetch(`${brainUrl}/v1/health`, { headers: hdrs }).then(r => r.json()).catch(e => ({ error: e.message })),
3535
- fetch(`${brainUrl}/v1/midstream`, { headers: hdrs }).then(r => r.json()).catch(e => ({ error: e.message })),
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 })),
3536
3604
  ]);
3537
3605
 
3538
3606
  return { content: [{ type: 'text', text: JSON.stringify({
@@ -3560,7 +3628,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3560
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; }
3561
3629
  case 'tasks': endpoint = `/tasks?limit=${args.limit || 20}`; break;
3562
3630
  }
3563
- const resp = await fetch(`${genesisUrl}${endpoint}`, {
3631
+ const resp = await proxyFetch(`${genesisUrl}${endpoint}`, {
3564
3632
  method,
3565
3633
  headers: { 'Content-Type': 'application/json', ...(process.env.PI ? { 'Authorization': `Bearer ${process.env.PI}` } : {}) },
3566
3634
  ...(body ? { body } : {})
@@ -3831,7 +3899,7 @@ async function main() {
3831
3899
  transport: 'sse',
3832
3900
  sessions: sessions.size,
3833
3901
  tools: 91,
3834
- version: '0.2.6'
3902
+ version: '0.2.7'
3835
3903
  }));
3836
3904
 
3837
3905
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ruvector",
3
- "version": "0.2.6",
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",