ruvector 0.1.20 ā 0.1.21
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 +252 -1378
- package/bin/ruvector.js +805 -0
- package/dist/index.d.mts +95 -0
- package/dist/index.d.ts +84 -20
- package/dist/index.js +330 -77
- package/dist/index.mjs +306 -0
- package/package.json +49 -30
- package/.claude-flow/metrics/agent-metrics.json +0 -1
- package/.claude-flow/metrics/performance.json +0 -87
- package/.claude-flow/metrics/task-metrics.json +0 -10
- package/PACKAGE_SUMMARY.md +0 -409
- package/bin/cli.js +0 -287
- package/dist/index.d.ts.map +0 -1
- package/dist/types.d.ts +0 -145
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -2
- package/examples/api-usage.js +0 -211
- package/examples/cli-demo.sh +0 -85
package/bin/ruvector.js
ADDED
|
@@ -0,0 +1,805 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* rUvector CLI
|
|
5
|
+
*
|
|
6
|
+
* Beautiful command-line interface for vector database operations
|
|
7
|
+
* Includes: Vector Search, Graph/Cypher, GNN, Compression, and more
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const { Command } = require('commander');
|
|
11
|
+
const chalk = require('chalk');
|
|
12
|
+
const ora = require('ora');
|
|
13
|
+
const Table = require('cli-table3');
|
|
14
|
+
const fs = require('fs').promises;
|
|
15
|
+
const path = require('path');
|
|
16
|
+
|
|
17
|
+
// Lazy load backends to improve startup time
|
|
18
|
+
let vectorBackend = null;
|
|
19
|
+
let graphBackend = null;
|
|
20
|
+
let gnnBackend = null;
|
|
21
|
+
|
|
22
|
+
function getVectorBackend() {
|
|
23
|
+
if (!vectorBackend) {
|
|
24
|
+
try {
|
|
25
|
+
const { VectorIndex, getBackendInfo, Utils } = require('../dist/index.js');
|
|
26
|
+
vectorBackend = { VectorIndex, getBackendInfo, Utils };
|
|
27
|
+
} catch (e) {
|
|
28
|
+
console.error(chalk.red('Vector backend not available:', e.message));
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return vectorBackend;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function getGraphBackend() {
|
|
36
|
+
if (!graphBackend) {
|
|
37
|
+
try {
|
|
38
|
+
graphBackend = require('@ruvector/graph-node');
|
|
39
|
+
} catch (e) {
|
|
40
|
+
try {
|
|
41
|
+
graphBackend = require('@ruvector/graph-wasm');
|
|
42
|
+
} catch (e2) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return graphBackend;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function getGnnBackend() {
|
|
51
|
+
if (!gnnBackend) {
|
|
52
|
+
try {
|
|
53
|
+
gnnBackend = require('@ruvector/gnn-node');
|
|
54
|
+
} catch (e) {
|
|
55
|
+
try {
|
|
56
|
+
gnnBackend = require('@ruvector/gnn-wasm');
|
|
57
|
+
} catch (e2) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return gnnBackend;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const program = new Command();
|
|
66
|
+
|
|
67
|
+
// Utility to format numbers
|
|
68
|
+
function formatNumber(num) {
|
|
69
|
+
if (num >= 1_000_000) {
|
|
70
|
+
return `${(num / 1_000_000).toFixed(2)}M`;
|
|
71
|
+
} else if (num >= 1_000) {
|
|
72
|
+
return `${(num / 1_000).toFixed(2)}K`;
|
|
73
|
+
}
|
|
74
|
+
return num.toString();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Utility to format bytes
|
|
78
|
+
function formatBytes(bytes) {
|
|
79
|
+
if (bytes >= 1_073_741_824) {
|
|
80
|
+
return `${(bytes / 1_073_741_824).toFixed(2)} GB`;
|
|
81
|
+
} else if (bytes >= 1_048_576) {
|
|
82
|
+
return `${(bytes / 1_048_576).toFixed(2)} MB`;
|
|
83
|
+
} else if (bytes >= 1_024) {
|
|
84
|
+
return `${(bytes / 1_024).toFixed(2)} KB`;
|
|
85
|
+
}
|
|
86
|
+
return `${bytes} B`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Utility to format duration
|
|
90
|
+
function formatDuration(ms) {
|
|
91
|
+
if (ms >= 1000) {
|
|
92
|
+
return `${(ms / 1000).toFixed(2)}s`;
|
|
93
|
+
}
|
|
94
|
+
return `${ms.toFixed(2)}ms`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Doctor command - diagnose installation
|
|
98
|
+
program
|
|
99
|
+
.command('doctor')
|
|
100
|
+
.description('Diagnose installation and check all dependencies')
|
|
101
|
+
.action(async () => {
|
|
102
|
+
console.log(chalk.bold.cyan('\n𩺠rUvector Doctor - Diagnosing Installation\n'));
|
|
103
|
+
|
|
104
|
+
const checks = [];
|
|
105
|
+
|
|
106
|
+
// Check Node.js version
|
|
107
|
+
const nodeVersion = process.version;
|
|
108
|
+
const nodeMajor = parseInt(nodeVersion.slice(1).split('.')[0]);
|
|
109
|
+
checks.push({
|
|
110
|
+
name: 'Node.js Version',
|
|
111
|
+
status: nodeMajor >= 16 ? 'pass' : 'fail',
|
|
112
|
+
message: nodeVersion,
|
|
113
|
+
hint: nodeMajor < 16 ? 'Requires Node.js >= 16.0.0' : null
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// Check core package
|
|
117
|
+
let coreVersion = null;
|
|
118
|
+
try {
|
|
119
|
+
const core = require('@ruvector/core');
|
|
120
|
+
coreVersion = typeof core.version === 'function' ? core.version() : (core.version || 'installed');
|
|
121
|
+
checks.push({
|
|
122
|
+
name: '@ruvector/core',
|
|
123
|
+
status: 'pass',
|
|
124
|
+
message: coreVersion
|
|
125
|
+
});
|
|
126
|
+
} catch (e) {
|
|
127
|
+
checks.push({
|
|
128
|
+
name: '@ruvector/core',
|
|
129
|
+
status: 'warn',
|
|
130
|
+
message: 'Not installed (using fallback)',
|
|
131
|
+
hint: 'npm install @ruvector/core'
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Check graph package (Node.js native)
|
|
136
|
+
try {
|
|
137
|
+
const graph = require('@ruvector/graph-node');
|
|
138
|
+
checks.push({
|
|
139
|
+
name: '@ruvector/graph-node',
|
|
140
|
+
status: 'pass',
|
|
141
|
+
message: graph.version || 'installed'
|
|
142
|
+
});
|
|
143
|
+
} catch (e) {
|
|
144
|
+
// Check WASM fallback
|
|
145
|
+
try {
|
|
146
|
+
const graphWasm = require('@ruvector/graph-wasm');
|
|
147
|
+
checks.push({
|
|
148
|
+
name: '@ruvector/graph-wasm',
|
|
149
|
+
status: 'pass',
|
|
150
|
+
message: graphWasm.version || 'installed (WASM)'
|
|
151
|
+
});
|
|
152
|
+
} catch (e2) {
|
|
153
|
+
checks.push({
|
|
154
|
+
name: 'Graph Module',
|
|
155
|
+
status: 'warn',
|
|
156
|
+
message: 'Not installed',
|
|
157
|
+
hint: 'npm install @ruvector/graph-node'
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Check GNN package (Node.js native)
|
|
163
|
+
try {
|
|
164
|
+
const gnn = require('@ruvector/gnn-node');
|
|
165
|
+
checks.push({
|
|
166
|
+
name: '@ruvector/gnn-node',
|
|
167
|
+
status: 'pass',
|
|
168
|
+
message: gnn.version || 'installed'
|
|
169
|
+
});
|
|
170
|
+
} catch (e) {
|
|
171
|
+
// Check WASM fallback
|
|
172
|
+
try {
|
|
173
|
+
const gnnWasm = require('@ruvector/gnn-wasm');
|
|
174
|
+
checks.push({
|
|
175
|
+
name: '@ruvector/gnn-wasm',
|
|
176
|
+
status: 'pass',
|
|
177
|
+
message: gnnWasm.version || 'installed (WASM)'
|
|
178
|
+
});
|
|
179
|
+
} catch (e2) {
|
|
180
|
+
checks.push({
|
|
181
|
+
name: 'GNN Module',
|
|
182
|
+
status: 'warn',
|
|
183
|
+
message: 'Not installed',
|
|
184
|
+
hint: 'npm install @ruvector/gnn-node'
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Check dist files
|
|
190
|
+
const distPath = require('path').join(__dirname, '..', 'dist', 'index.js');
|
|
191
|
+
try {
|
|
192
|
+
require('fs').accessSync(distPath);
|
|
193
|
+
checks.push({
|
|
194
|
+
name: 'Built dist files',
|
|
195
|
+
status: 'pass',
|
|
196
|
+
message: 'Found'
|
|
197
|
+
});
|
|
198
|
+
} catch (e) {
|
|
199
|
+
checks.push({
|
|
200
|
+
name: 'Built dist files',
|
|
201
|
+
status: 'fail',
|
|
202
|
+
message: 'Not found',
|
|
203
|
+
hint: 'Run npm run build in the ruvector package'
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Display results
|
|
208
|
+
const table = new Table({
|
|
209
|
+
head: ['Check', 'Status', 'Details'],
|
|
210
|
+
colWidths: [25, 10, 40]
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
let hasErrors = false;
|
|
214
|
+
let hasWarnings = false;
|
|
215
|
+
|
|
216
|
+
checks.forEach(check => {
|
|
217
|
+
let statusIcon;
|
|
218
|
+
if (check.status === 'pass') {
|
|
219
|
+
statusIcon = chalk.green('ā Pass');
|
|
220
|
+
} else if (check.status === 'warn') {
|
|
221
|
+
statusIcon = chalk.yellow('ā Warn');
|
|
222
|
+
hasWarnings = true;
|
|
223
|
+
} else {
|
|
224
|
+
statusIcon = chalk.red('ā Fail');
|
|
225
|
+
hasErrors = true;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
table.push([
|
|
229
|
+
check.name,
|
|
230
|
+
statusIcon,
|
|
231
|
+
check.message + (check.hint ? chalk.gray(` (${check.hint})`) : '')
|
|
232
|
+
]);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
console.log(table.toString());
|
|
236
|
+
console.log();
|
|
237
|
+
|
|
238
|
+
// Summary
|
|
239
|
+
if (hasErrors) {
|
|
240
|
+
console.log(chalk.red('ā Some required checks failed. Please fix the issues above.'));
|
|
241
|
+
} else if (hasWarnings) {
|
|
242
|
+
console.log(chalk.yellow('ā All required checks passed, but some optional modules are missing.'));
|
|
243
|
+
console.log(chalk.cyan(' Install optional modules for full functionality.'));
|
|
244
|
+
} else {
|
|
245
|
+
console.log(chalk.green('ā All checks passed! rUvector is ready to use.'));
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Show available features
|
|
249
|
+
console.log(chalk.bold.cyan('\nš¦ Available Commands:\n'));
|
|
250
|
+
console.log(chalk.white(' Core: ') + chalk.green('info, init, stats, insert, search, benchmark'));
|
|
251
|
+
if (getGraphBackend()) {
|
|
252
|
+
console.log(chalk.white(' Graph: ') + chalk.green('graph query, graph create-node'));
|
|
253
|
+
} else {
|
|
254
|
+
console.log(chalk.white(' Graph: ') + chalk.gray('(install @ruvector/graph-node)'));
|
|
255
|
+
}
|
|
256
|
+
if (getGnnBackend()) {
|
|
257
|
+
console.log(chalk.white(' GNN: ') + chalk.green('gnn layer, gnn compress'));
|
|
258
|
+
} else {
|
|
259
|
+
console.log(chalk.white(' GNN: ') + chalk.gray('(install @ruvector/gnn-node)'));
|
|
260
|
+
}
|
|
261
|
+
console.log();
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
// Info command
|
|
265
|
+
program
|
|
266
|
+
.command('info')
|
|
267
|
+
.description('Show backend information and available modules')
|
|
268
|
+
.action(() => {
|
|
269
|
+
const { getBackendInfo } = getVectorBackend();
|
|
270
|
+
const info = getBackendInfo();
|
|
271
|
+
|
|
272
|
+
console.log(chalk.bold.cyan('\nš rUvector - All-in-One Vector Database\n'));
|
|
273
|
+
|
|
274
|
+
const table = new Table({
|
|
275
|
+
chars: { 'mid': '', 'left-mid': '', 'mid-mid': '', 'right-mid': '' }
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
table.push(
|
|
279
|
+
['Backend Type', chalk.green(info.type === 'native' ? 'ā” Native' : 'š WASM')],
|
|
280
|
+
['Version', info.version],
|
|
281
|
+
['Features', info.features.join(', ')]
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
console.log(table.toString());
|
|
285
|
+
console.log();
|
|
286
|
+
|
|
287
|
+
// Show available modules
|
|
288
|
+
console.log(chalk.bold.cyan('š¦ Available Modules:\n'));
|
|
289
|
+
const modulesTable = new Table({
|
|
290
|
+
head: ['Module', 'Status', 'Description'],
|
|
291
|
+
colWidths: [20, 12, 45]
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
// Check vector
|
|
295
|
+
modulesTable.push(['Vector Search', chalk.green('ā Ready'), 'HNSW index, similarity search']);
|
|
296
|
+
|
|
297
|
+
// Check graph
|
|
298
|
+
const graphAvailable = getGraphBackend() !== null;
|
|
299
|
+
modulesTable.push([
|
|
300
|
+
'Graph/Cypher',
|
|
301
|
+
graphAvailable ? chalk.green('ā Ready') : chalk.yellow('ā Optional'),
|
|
302
|
+
'Neo4j-compatible queries, hyperedges'
|
|
303
|
+
]);
|
|
304
|
+
|
|
305
|
+
// Check GNN
|
|
306
|
+
const gnnAvailable = getGnnBackend() !== null;
|
|
307
|
+
modulesTable.push([
|
|
308
|
+
'GNN Layers',
|
|
309
|
+
gnnAvailable ? chalk.green('ā Ready') : chalk.yellow('ā Optional'),
|
|
310
|
+
'Neural network on graph topology'
|
|
311
|
+
]);
|
|
312
|
+
|
|
313
|
+
// Built-in features
|
|
314
|
+
modulesTable.push(['Compression', chalk.green('ā Built-in'), 'f32āf16āPQ8āPQ4āBinary (2-32x)']);
|
|
315
|
+
modulesTable.push(['WASM/Browser', chalk.green('ā Built-in'), 'Client-side vector search']);
|
|
316
|
+
|
|
317
|
+
console.log(modulesTable.toString());
|
|
318
|
+
console.log();
|
|
319
|
+
|
|
320
|
+
if (!graphAvailable || !gnnAvailable) {
|
|
321
|
+
console.log(chalk.cyan('š” Install optional modules:'));
|
|
322
|
+
if (!graphAvailable) {
|
|
323
|
+
console.log(chalk.white(' npm install @ruvector/graph-node'));
|
|
324
|
+
}
|
|
325
|
+
if (!gnnAvailable) {
|
|
326
|
+
console.log(chalk.white(' npm install @ruvector/gnn-node'));
|
|
327
|
+
}
|
|
328
|
+
console.log();
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
// Init command
|
|
333
|
+
program
|
|
334
|
+
.command('init <path>')
|
|
335
|
+
.description('Initialize a new vector index')
|
|
336
|
+
.option('-d, --dimension <number>', 'Vector dimension', '384')
|
|
337
|
+
.option('-m, --metric <type>', 'Distance metric (cosine|euclidean|dot)', 'cosine')
|
|
338
|
+
.option('-t, --type <type>', 'Index type (flat|hnsw)', 'hnsw')
|
|
339
|
+
.option('--hnsw-m <number>', 'HNSW M parameter', '16')
|
|
340
|
+
.option('--hnsw-ef <number>', 'HNSW ef_construction parameter', '200')
|
|
341
|
+
.action(async (indexPath, options) => {
|
|
342
|
+
const spinner = ora('Initializing vector index...').start();
|
|
343
|
+
|
|
344
|
+
try {
|
|
345
|
+
const { VectorIndex } = getVectorBackend();
|
|
346
|
+
const index = new VectorIndex({
|
|
347
|
+
dimension: parseInt(options.dimension),
|
|
348
|
+
metric: options.metric,
|
|
349
|
+
indexType: options.type,
|
|
350
|
+
hnswConfig: options.type === 'hnsw' ? {
|
|
351
|
+
m: parseInt(options.hnswM),
|
|
352
|
+
efConstruction: parseInt(options.hnswEf)
|
|
353
|
+
} : undefined
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
await index.save(indexPath);
|
|
357
|
+
|
|
358
|
+
spinner.succeed(chalk.green('Index initialized successfully!'));
|
|
359
|
+
|
|
360
|
+
console.log(chalk.cyan('\nConfiguration:'));
|
|
361
|
+
console.log(` Path: ${chalk.white(indexPath)}`);
|
|
362
|
+
console.log(` Dimension: ${chalk.white(options.dimension)}`);
|
|
363
|
+
console.log(` Metric: ${chalk.white(options.metric)}`);
|
|
364
|
+
console.log(` Type: ${chalk.white(options.type)}`);
|
|
365
|
+
|
|
366
|
+
if (options.type === 'hnsw') {
|
|
367
|
+
console.log(chalk.cyan('\nHNSW Parameters:'));
|
|
368
|
+
console.log(` M: ${chalk.white(options.hnswM)}`);
|
|
369
|
+
console.log(` ef_construction: ${chalk.white(options.hnswEf)}`);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
console.log();
|
|
373
|
+
} catch (error) {
|
|
374
|
+
spinner.fail(chalk.red('Failed to initialize index'));
|
|
375
|
+
console.error(chalk.red(error.message));
|
|
376
|
+
process.exit(1);
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
// Stats command
|
|
381
|
+
program
|
|
382
|
+
.command('stats <path>')
|
|
383
|
+
.description('Show index statistics')
|
|
384
|
+
.action(async (indexPath) => {
|
|
385
|
+
const spinner = ora('Loading index...').start();
|
|
386
|
+
|
|
387
|
+
try {
|
|
388
|
+
const { VectorIndex } = getVectorBackend();
|
|
389
|
+
const index = await VectorIndex.load(indexPath);
|
|
390
|
+
const stats = await index.stats();
|
|
391
|
+
|
|
392
|
+
spinner.succeed(chalk.green('Index loaded'));
|
|
393
|
+
|
|
394
|
+
console.log(chalk.bold.cyan('\nš Index Statistics\n'));
|
|
395
|
+
|
|
396
|
+
const table = new Table({
|
|
397
|
+
chars: { 'mid': '', 'left-mid': '', 'mid-mid': '', 'right-mid': '' }
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
table.push(
|
|
401
|
+
['Vectors', chalk.white(formatNumber(stats.vectorCount))],
|
|
402
|
+
['Dimension', chalk.white(stats.dimension)],
|
|
403
|
+
['Index Type', chalk.white(stats.indexType)],
|
|
404
|
+
['Memory Usage', chalk.white(stats.memoryUsage ? formatBytes(stats.memoryUsage) : 'N/A')]
|
|
405
|
+
);
|
|
406
|
+
|
|
407
|
+
console.log(table.toString());
|
|
408
|
+
console.log();
|
|
409
|
+
} catch (error) {
|
|
410
|
+
spinner.fail(chalk.red('Failed to load index'));
|
|
411
|
+
console.error(chalk.red(error.message));
|
|
412
|
+
process.exit(1);
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
// Insert command
|
|
417
|
+
program
|
|
418
|
+
.command('insert <path> <vectors-file>')
|
|
419
|
+
.description('Insert vectors from JSON file')
|
|
420
|
+
.option('-b, --batch-size <number>', 'Batch size', '1000')
|
|
421
|
+
.action(async (indexPath, vectorsFile, options) => {
|
|
422
|
+
let spinner = ora('Loading index...').start();
|
|
423
|
+
|
|
424
|
+
try {
|
|
425
|
+
const { VectorIndex } = getVectorBackend();
|
|
426
|
+
const index = await VectorIndex.load(indexPath);
|
|
427
|
+
spinner.succeed();
|
|
428
|
+
|
|
429
|
+
spinner = ora('Loading vectors...').start();
|
|
430
|
+
const data = await fs.readFile(vectorsFile, 'utf-8');
|
|
431
|
+
const vectors = JSON.parse(data);
|
|
432
|
+
spinner.succeed(chalk.green(`Loaded ${vectors.length} vectors`));
|
|
433
|
+
|
|
434
|
+
const startTime = Date.now();
|
|
435
|
+
spinner = ora('Inserting vectors...').start();
|
|
436
|
+
|
|
437
|
+
let lastProgress = 0;
|
|
438
|
+
await index.insertBatch(vectors, {
|
|
439
|
+
batchSize: parseInt(options.batchSize),
|
|
440
|
+
progressCallback: (progress) => {
|
|
441
|
+
const percent = Math.floor(progress * 100);
|
|
442
|
+
if (percent > lastProgress) {
|
|
443
|
+
spinner.text = `Inserting vectors... ${percent}%`;
|
|
444
|
+
lastProgress = percent;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
const duration = Date.now() - startTime;
|
|
450
|
+
const throughput = vectors.length / (duration / 1000);
|
|
451
|
+
|
|
452
|
+
spinner.succeed(chalk.green('Vectors inserted!'));
|
|
453
|
+
|
|
454
|
+
console.log(chalk.cyan('\nPerformance:'));
|
|
455
|
+
console.log(` Duration: ${chalk.white(formatDuration(duration))}`);
|
|
456
|
+
console.log(` Throughput: ${chalk.white(formatNumber(throughput))} vectors/sec`);
|
|
457
|
+
|
|
458
|
+
spinner = ora('Saving index...').start();
|
|
459
|
+
await index.save(indexPath);
|
|
460
|
+
spinner.succeed(chalk.green('Index saved'));
|
|
461
|
+
|
|
462
|
+
console.log();
|
|
463
|
+
} catch (error) {
|
|
464
|
+
spinner.fail(chalk.red('Operation failed'));
|
|
465
|
+
console.error(chalk.red(error.message));
|
|
466
|
+
process.exit(1);
|
|
467
|
+
}
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
// Search command
|
|
471
|
+
program
|
|
472
|
+
.command('search <path>')
|
|
473
|
+
.description('Search for similar vectors')
|
|
474
|
+
.requiredOption('-q, --query <vector>', 'Query vector as JSON array')
|
|
475
|
+
.option('-k, --top-k <number>', 'Number of results', '10')
|
|
476
|
+
.option('--ef <number>', 'HNSW ef parameter')
|
|
477
|
+
.action(async (indexPath, options) => {
|
|
478
|
+
const spinner = ora('Loading index...').start();
|
|
479
|
+
|
|
480
|
+
try {
|
|
481
|
+
const { VectorIndex } = getVectorBackend();
|
|
482
|
+
const index = await VectorIndex.load(indexPath);
|
|
483
|
+
spinner.succeed();
|
|
484
|
+
|
|
485
|
+
const query = JSON.parse(options.query);
|
|
486
|
+
|
|
487
|
+
spinner.text = 'Searching...';
|
|
488
|
+
spinner.start();
|
|
489
|
+
|
|
490
|
+
const startTime = Date.now();
|
|
491
|
+
const results = await index.search(query, {
|
|
492
|
+
k: parseInt(options.topK),
|
|
493
|
+
ef: options.ef ? parseInt(options.ef) : undefined
|
|
494
|
+
});
|
|
495
|
+
const duration = Date.now() - startTime;
|
|
496
|
+
|
|
497
|
+
spinner.succeed(chalk.green(`Found ${results.length} results in ${formatDuration(duration)}`));
|
|
498
|
+
|
|
499
|
+
console.log(chalk.bold.cyan('\nš Search Results\n'));
|
|
500
|
+
|
|
501
|
+
const table = new Table({
|
|
502
|
+
head: ['Rank', 'ID', 'Score', 'Metadata'],
|
|
503
|
+
colWidths: [6, 20, 12, 40]
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
results.forEach((result, i) => {
|
|
507
|
+
table.push([
|
|
508
|
+
chalk.yellow(`#${i + 1}`),
|
|
509
|
+
result.id,
|
|
510
|
+
chalk.green(result.score.toFixed(4)),
|
|
511
|
+
result.metadata ? JSON.stringify(result.metadata).substring(0, 37) + '...' : ''
|
|
512
|
+
]);
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
console.log(table.toString());
|
|
516
|
+
console.log();
|
|
517
|
+
} catch (error) {
|
|
518
|
+
spinner.fail(chalk.red('Search failed'));
|
|
519
|
+
console.error(chalk.red(error.message));
|
|
520
|
+
process.exit(1);
|
|
521
|
+
}
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
// Benchmark command
|
|
525
|
+
program
|
|
526
|
+
.command('benchmark')
|
|
527
|
+
.description('Run performance benchmarks')
|
|
528
|
+
.option('-d, --dimension <number>', 'Vector dimension', '384')
|
|
529
|
+
.option('-n, --num-vectors <number>', 'Number of vectors', '10000')
|
|
530
|
+
.option('-q, --num-queries <number>', 'Number of queries', '100')
|
|
531
|
+
.action(async (options) => {
|
|
532
|
+
const { VectorIndex, Utils } = getVectorBackend();
|
|
533
|
+
const dimension = parseInt(options.dimension);
|
|
534
|
+
const numVectors = parseInt(options.numVectors);
|
|
535
|
+
const numQueries = parseInt(options.numQueries);
|
|
536
|
+
|
|
537
|
+
console.log(chalk.bold.cyan('\nā” Performance Benchmark\n'));
|
|
538
|
+
console.log(chalk.cyan('Configuration:'));
|
|
539
|
+
console.log(` Dimension: ${chalk.white(dimension)}`);
|
|
540
|
+
console.log(` Vectors: ${chalk.white(formatNumber(numVectors))}`);
|
|
541
|
+
console.log(` Queries: ${chalk.white(formatNumber(numQueries))}`);
|
|
542
|
+
console.log();
|
|
543
|
+
|
|
544
|
+
const results = [];
|
|
545
|
+
|
|
546
|
+
try {
|
|
547
|
+
// Create index
|
|
548
|
+
let spinner = ora('Creating index...').start();
|
|
549
|
+
const index = new VectorIndex({
|
|
550
|
+
dimension,
|
|
551
|
+
metric: 'cosine',
|
|
552
|
+
indexType: 'hnsw'
|
|
553
|
+
});
|
|
554
|
+
spinner.succeed();
|
|
555
|
+
|
|
556
|
+
// Generate vectors
|
|
557
|
+
spinner = ora('Generating vectors...').start();
|
|
558
|
+
const vectors = [];
|
|
559
|
+
for (let i = 0; i < numVectors; i++) {
|
|
560
|
+
vectors.push({
|
|
561
|
+
id: `vec_${i}`,
|
|
562
|
+
values: Utils.randomVector(dimension)
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
spinner.succeed();
|
|
566
|
+
|
|
567
|
+
// Insert benchmark
|
|
568
|
+
spinner = ora('Benchmarking inserts...').start();
|
|
569
|
+
const insertStart = Date.now();
|
|
570
|
+
await index.insertBatch(vectors, { batchSize: 1000 });
|
|
571
|
+
const insertDuration = Date.now() - insertStart;
|
|
572
|
+
const insertThroughput = numVectors / (insertDuration / 1000);
|
|
573
|
+
spinner.succeed();
|
|
574
|
+
|
|
575
|
+
results.push({
|
|
576
|
+
operation: 'Insert',
|
|
577
|
+
duration: insertDuration,
|
|
578
|
+
throughput: insertThroughput
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
// Search benchmark
|
|
582
|
+
spinner = ora('Benchmarking searches...').start();
|
|
583
|
+
const queries = [];
|
|
584
|
+
for (let i = 0; i < numQueries; i++) {
|
|
585
|
+
queries.push(Utils.randomVector(dimension));
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
const searchStart = Date.now();
|
|
589
|
+
for (const query of queries) {
|
|
590
|
+
await index.search(query, { k: 10 });
|
|
591
|
+
}
|
|
592
|
+
const searchDuration = Date.now() - searchStart;
|
|
593
|
+
const searchThroughput = numQueries / (searchDuration / 1000);
|
|
594
|
+
spinner.succeed();
|
|
595
|
+
|
|
596
|
+
results.push({
|
|
597
|
+
operation: 'Search',
|
|
598
|
+
duration: searchDuration,
|
|
599
|
+
throughput: searchThroughput
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
// Display results
|
|
603
|
+
console.log(chalk.bold.cyan('\nš Results\n'));
|
|
604
|
+
|
|
605
|
+
const table = new Table({
|
|
606
|
+
head: ['Operation', 'Total Time', 'Throughput'],
|
|
607
|
+
colWidths: [15, 20, 25]
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
results.forEach(result => {
|
|
611
|
+
table.push([
|
|
612
|
+
chalk.white(result.operation),
|
|
613
|
+
chalk.yellow(formatDuration(result.duration)),
|
|
614
|
+
chalk.green(`${formatNumber(result.throughput)} ops/sec`)
|
|
615
|
+
]);
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
console.log(table.toString());
|
|
619
|
+
console.log();
|
|
620
|
+
|
|
621
|
+
// Backend info
|
|
622
|
+
const { getBackendInfo } = getVectorBackend();
|
|
623
|
+
const info = getBackendInfo();
|
|
624
|
+
console.log(chalk.cyan(`Backend: ${chalk.white(info.type)}`));
|
|
625
|
+
console.log();
|
|
626
|
+
|
|
627
|
+
} catch (error) {
|
|
628
|
+
console.error(chalk.red('Benchmark failed:'), error.message);
|
|
629
|
+
process.exit(1);
|
|
630
|
+
}
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
// ============================================================================
|
|
634
|
+
// GRAPH COMMANDS
|
|
635
|
+
// ============================================================================
|
|
636
|
+
|
|
637
|
+
const graphCmd = program
|
|
638
|
+
.command('graph')
|
|
639
|
+
.description('Graph database commands (Cypher queries, nodes, edges)');
|
|
640
|
+
|
|
641
|
+
graphCmd
|
|
642
|
+
.command('query <cypher>')
|
|
643
|
+
.description('Execute a Cypher query')
|
|
644
|
+
.option('-f, --format <type>', 'Output format (table|json)', 'table')
|
|
645
|
+
.action(async (cypher, options) => {
|
|
646
|
+
const graph = getGraphBackend();
|
|
647
|
+
if (!graph) {
|
|
648
|
+
console.error(chalk.red('Graph module not installed. Run: npm install @ruvector/graph-node'));
|
|
649
|
+
process.exit(1);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
const spinner = ora('Executing Cypher query...').start();
|
|
653
|
+
try {
|
|
654
|
+
const db = new graph.GraphDB();
|
|
655
|
+
const results = await db.query(cypher);
|
|
656
|
+
spinner.succeed(chalk.green(`Query returned ${results.length} results`));
|
|
657
|
+
|
|
658
|
+
if (options.format === 'json') {
|
|
659
|
+
console.log(JSON.stringify(results, null, 2));
|
|
660
|
+
} else {
|
|
661
|
+
if (results.length > 0) {
|
|
662
|
+
const table = new Table({
|
|
663
|
+
head: Object.keys(results[0]).map(k => chalk.cyan(k))
|
|
664
|
+
});
|
|
665
|
+
results.forEach(row => {
|
|
666
|
+
table.push(Object.values(row).map(v =>
|
|
667
|
+
typeof v === 'object' ? JSON.stringify(v) : String(v)
|
|
668
|
+
));
|
|
669
|
+
});
|
|
670
|
+
console.log(table.toString());
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
} catch (error) {
|
|
674
|
+
spinner.fail(chalk.red('Query failed'));
|
|
675
|
+
console.error(chalk.red(error.message));
|
|
676
|
+
process.exit(1);
|
|
677
|
+
}
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
graphCmd
|
|
681
|
+
.command('create-node')
|
|
682
|
+
.description('Create a new node')
|
|
683
|
+
.requiredOption('-l, --label <label>', 'Node label')
|
|
684
|
+
.requiredOption('-p, --properties <json>', 'Node properties as JSON')
|
|
685
|
+
.action(async (options) => {
|
|
686
|
+
const graph = getGraphBackend();
|
|
687
|
+
if (!graph) {
|
|
688
|
+
console.error(chalk.red('Graph module not installed. Run: npm install @ruvector/graph-node'));
|
|
689
|
+
process.exit(1);
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
try {
|
|
693
|
+
const db = new graph.GraphDB();
|
|
694
|
+
const props = JSON.parse(options.properties);
|
|
695
|
+
const nodeId = await db.createNode(options.label, props);
|
|
696
|
+
console.log(chalk.green(`ā Created node: ${nodeId}`));
|
|
697
|
+
} catch (error) {
|
|
698
|
+
console.error(chalk.red('Failed to create node:', error.message));
|
|
699
|
+
process.exit(1);
|
|
700
|
+
}
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
// ============================================================================
|
|
704
|
+
// GNN COMMANDS
|
|
705
|
+
// ============================================================================
|
|
706
|
+
|
|
707
|
+
const gnnCmd = program
|
|
708
|
+
.command('gnn')
|
|
709
|
+
.description('Graph Neural Network commands');
|
|
710
|
+
|
|
711
|
+
gnnCmd
|
|
712
|
+
.command('layer')
|
|
713
|
+
.description('Create and test a GNN layer')
|
|
714
|
+
.option('-i, --input-dim <number>', 'Input dimension', '128')
|
|
715
|
+
.option('-h, --hidden-dim <number>', 'Hidden dimension', '256')
|
|
716
|
+
.option('--heads <number>', 'Attention heads', '4')
|
|
717
|
+
.action(async (options) => {
|
|
718
|
+
const gnn = getGnnBackend();
|
|
719
|
+
if (!gnn) {
|
|
720
|
+
console.error(chalk.red('GNN module not installed. Run: npm install @ruvector/gnn-node'));
|
|
721
|
+
process.exit(1);
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
try {
|
|
725
|
+
const layer = new gnn.RuvectorLayer(
|
|
726
|
+
parseInt(options.inputDim),
|
|
727
|
+
parseInt(options.hiddenDim),
|
|
728
|
+
parseInt(options.heads),
|
|
729
|
+
0.1 // dropout
|
|
730
|
+
);
|
|
731
|
+
console.log(chalk.green('ā GNN Layer created'));
|
|
732
|
+
console.log(chalk.cyan('Configuration:'));
|
|
733
|
+
console.log(` Input dim: ${options.inputDim}`);
|
|
734
|
+
console.log(` Hidden dim: ${options.hiddenDim}`);
|
|
735
|
+
console.log(` Attention heads: ${options.heads}`);
|
|
736
|
+
} catch (error) {
|
|
737
|
+
console.error(chalk.red('Failed to create GNN layer:', error.message));
|
|
738
|
+
process.exit(1);
|
|
739
|
+
}
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
gnnCmd
|
|
743
|
+
.command('compress')
|
|
744
|
+
.description('Compress a vector using adaptive compression')
|
|
745
|
+
.requiredOption('-v, --vector <json>', 'Vector as JSON array')
|
|
746
|
+
.option('-f, --frequency <number>', 'Access frequency (0-1)', '0.5')
|
|
747
|
+
.action(async (options) => {
|
|
748
|
+
const gnn = getGnnBackend();
|
|
749
|
+
if (!gnn) {
|
|
750
|
+
console.error(chalk.red('GNN module not installed. Run: npm install @ruvector/gnn-node'));
|
|
751
|
+
process.exit(1);
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
try {
|
|
755
|
+
const vector = JSON.parse(options.vector);
|
|
756
|
+
const freq = parseFloat(options.frequency);
|
|
757
|
+
const compressor = new gnn.TensorCompress();
|
|
758
|
+
const compressed = compressor.compress(vector, freq);
|
|
759
|
+
|
|
760
|
+
console.log(chalk.green('ā Vector compressed'));
|
|
761
|
+
console.log(chalk.cyan('Compression info:'));
|
|
762
|
+
console.log(` Original size: ${vector.length * 4} bytes`);
|
|
763
|
+
console.log(` Compressed: ${JSON.stringify(compressed).length} bytes`);
|
|
764
|
+
console.log(` Access frequency: ${freq}`);
|
|
765
|
+
console.log(` Level: ${gnn.getCompressionLevel(freq)}`);
|
|
766
|
+
} catch (error) {
|
|
767
|
+
console.error(chalk.red('Compression failed:', error.message));
|
|
768
|
+
process.exit(1);
|
|
769
|
+
}
|
|
770
|
+
});
|
|
771
|
+
|
|
772
|
+
// Version
|
|
773
|
+
program.version(require('../package.json').version, '-v, --version', 'Show version');
|
|
774
|
+
|
|
775
|
+
// Help customization
|
|
776
|
+
program.on('--help', () => {
|
|
777
|
+
console.log('');
|
|
778
|
+
console.log(chalk.bold.cyan('Diagnostics:'));
|
|
779
|
+
console.log(' $ ruvector doctor Diagnose installation');
|
|
780
|
+
console.log(' $ ruvector info Show backend info');
|
|
781
|
+
console.log('');
|
|
782
|
+
console.log(chalk.bold.cyan('Vector Commands:'));
|
|
783
|
+
console.log(' $ ruvector init my-index.bin -d 384 Initialize index');
|
|
784
|
+
console.log(' $ ruvector stats my-index.bin Show index statistics');
|
|
785
|
+
console.log(' $ ruvector insert my-index.bin vectors.json Insert vectors');
|
|
786
|
+
console.log(' $ ruvector search my-index.bin -q "[0.1,...]" -k 10');
|
|
787
|
+
console.log(' $ ruvector benchmark -d 384 -n 10000 Run benchmarks');
|
|
788
|
+
console.log('');
|
|
789
|
+
console.log(chalk.bold.cyan('Graph Commands (requires @ruvector/graph-node):'));
|
|
790
|
+
console.log(' $ ruvector graph query "MATCH (n) RETURN n" Execute Cypher');
|
|
791
|
+
console.log(' $ ruvector graph create-node -l Person -p \'{"name":"Alice"}\'');
|
|
792
|
+
console.log('');
|
|
793
|
+
console.log(chalk.bold.cyan('GNN Commands (requires @ruvector/gnn-node):'));
|
|
794
|
+
console.log(' $ ruvector gnn layer -i 128 -h 256 --heads 4 Create GNN layer');
|
|
795
|
+
console.log(' $ ruvector gnn compress -v "[0.1,...]" -f 0.5 Compress vector');
|
|
796
|
+
console.log('');
|
|
797
|
+
console.log(chalk.cyan('For more info: https://github.com/ruvnet/ruvector'));
|
|
798
|
+
console.log('');
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
program.parse(process.argv);
|
|
802
|
+
|
|
803
|
+
if (!process.argv.slice(2).length) {
|
|
804
|
+
program.outputHelp();
|
|
805
|
+
}
|