From 3069067ff1edb5ea3bfd2cdc19ace1d80c83ff7d Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 26 Nov 2025 15:01:15 +0000 Subject: [PATCH] feat: Add graph, GNN, and compression commands to npx ruvector CLI - Add lazy-loading backends for vector, graph, and GNN modules - Add `info` command showing all available modules and their status - Add `graph query` and `graph create-node` for Cypher queries - Add `gnn layer` and `gnn compress` for neural network operations - Update package.json with optional dependencies for all sub-packages - Add comprehensive help text with examples for all commands - Bump version to 0.1.2 with updated description and keywords --- npm/ruvector/bin/ruvector.js | 267 +++++++++++++++++++++++++++++++++-- npm/ruvector/package.json | 20 ++- 2 files changed, 273 insertions(+), 14 deletions(-) diff --git a/npm/ruvector/bin/ruvector.js b/npm/ruvector/bin/ruvector.js index 812a431d..682e13eb 100755 --- a/npm/ruvector/bin/ruvector.js +++ b/npm/ruvector/bin/ruvector.js @@ -4,16 +4,64 @@ * rUvector CLI * * Beautiful command-line interface for vector database operations + * Includes: Vector Search, Graph/Cypher, GNN, Compression, and more */ const { Command } = require('commander'); const chalk = require('chalk'); const ora = require('ora'); const Table = require('cli-table3'); -const { VectorIndex, getBackendInfo, Utils } = require('../dist/index.js'); const fs = require('fs').promises; const path = require('path'); +// Lazy load backends to improve startup time +let vectorBackend = null; +let graphBackend = null; +let gnnBackend = null; + +function getVectorBackend() { + if (!vectorBackend) { + try { + const { VectorIndex, getBackendInfo, Utils } = require('../dist/index.js'); + vectorBackend = { VectorIndex, getBackendInfo, Utils }; + } catch (e) { + console.error(chalk.red('Vector backend not available:', e.message)); + process.exit(1); + } + } + return vectorBackend; +} + +function getGraphBackend() { + if (!graphBackend) { + try { + graphBackend = require('@ruvector/graph-node'); + } catch (e) { + try { + graphBackend = require('@ruvector/graph-wasm'); + } catch (e2) { + return null; + } + } + } + return graphBackend; +} + +function getGnnBackend() { + if (!gnnBackend) { + try { + gnnBackend = require('@ruvector/gnn-node'); + } catch (e) { + try { + gnnBackend = require('@ruvector/gnn-wasm'); + } catch (e2) { + return null; + } + } + } + return gnnBackend; +} + const program = new Command(); // Utility to format numbers @@ -49,11 +97,12 @@ function formatDuration(ms) { // Info command program .command('info') - .description('Show backend information') + .description('Show backend information and available modules') .action(() => { + const { getBackendInfo } = getVectorBackend(); const info = getBackendInfo(); - console.log(chalk.bold.cyan('\nπŸš€ rUvector Backend Information\n')); + console.log(chalk.bold.cyan('\nπŸš€ rUvector - All-in-One Vector Database\n')); const table = new Table({ chars: { 'mid': '', 'left-mid': '', 'mid-mid': '', 'right-mid': '' } @@ -67,6 +116,50 @@ program console.log(table.toString()); console.log(); + + // Show available modules + console.log(chalk.bold.cyan('πŸ“¦ Available Modules:\n')); + const modulesTable = new Table({ + head: ['Module', 'Status', 'Description'], + colWidths: [20, 12, 45] + }); + + // Check vector + modulesTable.push(['Vector Search', chalk.green('βœ“ Ready'), 'HNSW index, similarity search']); + + // Check graph + const graphAvailable = getGraphBackend() !== null; + modulesTable.push([ + 'Graph/Cypher', + graphAvailable ? chalk.green('βœ“ Ready') : chalk.yellow('β—‹ Optional'), + 'Neo4j-compatible queries, hyperedges' + ]); + + // Check GNN + const gnnAvailable = getGnnBackend() !== null; + modulesTable.push([ + 'GNN Layers', + gnnAvailable ? chalk.green('βœ“ Ready') : chalk.yellow('β—‹ Optional'), + 'Neural network on graph topology' + ]); + + // Built-in features + modulesTable.push(['Compression', chalk.green('βœ“ Built-in'), 'f32β†’f16β†’PQ8β†’PQ4β†’Binary (2-32x)']); + modulesTable.push(['WASM/Browser', chalk.green('βœ“ Built-in'), 'Client-side vector search']); + + console.log(modulesTable.toString()); + console.log(); + + if (!graphAvailable || !gnnAvailable) { + console.log(chalk.cyan('πŸ’‘ Install optional modules:')); + if (!graphAvailable) { + console.log(chalk.white(' npm install @ruvector/graph-node')); + } + if (!gnnAvailable) { + console.log(chalk.white(' npm install @ruvector/gnn-node')); + } + console.log(); + } }); // Init command @@ -82,6 +175,7 @@ program const spinner = ora('Initializing vector index...').start(); try { + const { VectorIndex } = getVectorBackend(); const index = new VectorIndex({ dimension: parseInt(options.dimension), metric: options.metric, @@ -124,6 +218,7 @@ program const spinner = ora('Loading index...').start(); try { + const { VectorIndex } = getVectorBackend(); const index = await VectorIndex.load(indexPath); const stats = await index.stats(); @@ -160,6 +255,7 @@ program let spinner = ora('Loading index...').start(); try { + const { VectorIndex } = getVectorBackend(); const index = await VectorIndex.load(indexPath); spinner.succeed(); @@ -215,6 +311,7 @@ program const spinner = ora('Loading index...').start(); try { + const { VectorIndex } = getVectorBackend(); const index = await VectorIndex.load(indexPath); spinner.succeed(); @@ -265,6 +362,7 @@ program .option('-n, --num-vectors ', 'Number of vectors', '10000') .option('-q, --num-queries ', 'Number of queries', '100') .action(async (options) => { + const { VectorIndex, Utils } = getVectorBackend(); const dimension = parseInt(options.dimension); const numVectors = parseInt(options.numVectors); const numQueries = parseInt(options.numQueries); @@ -354,6 +452,7 @@ program console.log(); // Backend info + const { getBackendInfo } = getVectorBackend(); const info = getBackendInfo(); console.log(chalk.cyan(`Backend: ${chalk.white(info.type)}`)); console.log(); @@ -364,19 +463,167 @@ program } }); +// ============================================================================ +// GRAPH COMMANDS +// ============================================================================ + +const graphCmd = program + .command('graph') + .description('Graph database commands (Cypher queries, nodes, edges)'); + +graphCmd + .command('query ') + .description('Execute a Cypher query') + .option('-f, --format ', 'Output format (table|json)', 'table') + .action(async (cypher, options) => { + const graph = getGraphBackend(); + if (!graph) { + console.error(chalk.red('Graph module not installed. Run: npm install @ruvector/graph-node')); + process.exit(1); + } + + const spinner = ora('Executing Cypher query...').start(); + try { + const db = new graph.GraphDB(); + const results = await db.query(cypher); + spinner.succeed(chalk.green(`Query returned ${results.length} results`)); + + if (options.format === 'json') { + console.log(JSON.stringify(results, null, 2)); + } else { + if (results.length > 0) { + const table = new Table({ + head: Object.keys(results[0]).map(k => chalk.cyan(k)) + }); + results.forEach(row => { + table.push(Object.values(row).map(v => + typeof v === 'object' ? JSON.stringify(v) : String(v) + )); + }); + console.log(table.toString()); + } + } + } catch (error) { + spinner.fail(chalk.red('Query failed')); + console.error(chalk.red(error.message)); + process.exit(1); + } + }); + +graphCmd + .command('create-node') + .description('Create a new node') + .requiredOption('-l, --label