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.
@@ -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
+ }