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
This commit is contained in:
Claude 2025-11-26 15:01:15 +00:00
parent f40e96037c
commit 3069067ff1
2 changed files with 273 additions and 14 deletions

View file

@ -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>', 'Number of vectors', '10000')
.option('-q, --num-queries <number>', '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 <cypher>')
.description('Execute a Cypher query')
.option('-f, --format <type>', '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 <label>', 'Node label')
.requiredOption('-p, --properties <json>', 'Node properties as JSON')
.action(async (options) => {
const graph = getGraphBackend();
if (!graph) {
console.error(chalk.red('Graph module not installed. Run: npm install @ruvector/graph-node'));
process.exit(1);
}
try {
const db = new graph.GraphDB();
const props = JSON.parse(options.properties);
const nodeId = await db.createNode(options.label, props);
console.log(chalk.green(`✓ Created node: ${nodeId}`));
} catch (error) {
console.error(chalk.red('Failed to create node:', error.message));
process.exit(1);
}
});
// ============================================================================
// GNN COMMANDS
// ============================================================================
const gnnCmd = program
.command('gnn')
.description('Graph Neural Network commands');
gnnCmd
.command('layer')
.description('Create and test a GNN layer')
.option('-i, --input-dim <number>', 'Input dimension', '128')
.option('-h, --hidden-dim <number>', 'Hidden dimension', '256')
.option('--heads <number>', 'Attention heads', '4')
.action(async (options) => {
const gnn = getGnnBackend();
if (!gnn) {
console.error(chalk.red('GNN module not installed. Run: npm install @ruvector/gnn-node'));
process.exit(1);
}
try {
const layer = new gnn.RuvectorLayer(
parseInt(options.inputDim),
parseInt(options.hiddenDim),
parseInt(options.heads),
0.1 // dropout
);
console.log(chalk.green('✓ GNN Layer created'));
console.log(chalk.cyan('Configuration:'));
console.log(` Input dim: ${options.inputDim}`);
console.log(` Hidden dim: ${options.hiddenDim}`);
console.log(` Attention heads: ${options.heads}`);
} catch (error) {
console.error(chalk.red('Failed to create GNN layer:', error.message));
process.exit(1);
}
});
gnnCmd
.command('compress')
.description('Compress a vector using adaptive compression')
.requiredOption('-v, --vector <json>', 'Vector as JSON array')
.option('-f, --frequency <number>', 'Access frequency (0-1)', '0.5')
.action(async (options) => {
const gnn = getGnnBackend();
if (!gnn) {
console.error(chalk.red('GNN module not installed. Run: npm install @ruvector/gnn-node'));
process.exit(1);
}
try {
const vector = JSON.parse(options.vector);
const freq = parseFloat(options.frequency);
const compressor = new gnn.TensorCompress();
const compressed = compressor.compress(vector, freq);
console.log(chalk.green('✓ Vector compressed'));
console.log(chalk.cyan('Compression info:'));
console.log(` Original size: ${vector.length * 4} bytes`);
console.log(` Compressed: ${JSON.stringify(compressed).length} bytes`);
console.log(` Access frequency: ${freq}`);
console.log(` Level: ${gnn.getCompressionLevel(freq)}`);
} catch (error) {
console.error(chalk.red('Compression failed:', error.message));
process.exit(1);
}
});
// Version
program.version(require('../package.json').version, '-v, --version', 'Show version');
// Help customization
program.on('--help', () => {
console.log('');
console.log(chalk.cyan('Examples:'));
console.log(' $ ruvector info');
console.log(' $ ruvector init my-index.bin --dimension 384 --type hnsw');
console.log(' $ ruvector insert my-index.bin vectors.json');
console.log(' $ ruvector search my-index.bin --query "[0.1, 0.2, ...]" -k 10');
console.log(' $ ruvector stats my-index.bin');
console.log(' $ ruvector benchmark --dimension 384 --num-vectors 10000');
console.log(chalk.bold.cyan('Vector Commands:'));
console.log(' $ ruvector info Show backend info');
console.log(' $ ruvector init my-index.bin -d 384 Initialize index');
console.log(' $ ruvector insert my-index.bin vectors.json Insert vectors');
console.log(' $ ruvector search my-index.bin -q "[0.1,...]" -k 10');
console.log(' $ ruvector benchmark -d 384 -n 10000 Run benchmarks');
console.log('');
console.log(chalk.bold.cyan('Graph Commands (requires @ruvector/graph-node):'));
console.log(' $ ruvector graph query "MATCH (n) RETURN n" Execute Cypher');
console.log(' $ ruvector graph create-node -l Person -p \'{"name":"Alice"}\'');
console.log('');
console.log(chalk.bold.cyan('GNN Commands (requires @ruvector/gnn-node):'));
console.log(' $ ruvector gnn layer -i 128 -h 256 --heads 4 Create GNN layer');
console.log(' $ ruvector gnn compress -v "[0.1,...]" -f 0.5 Compress vector');
console.log('');
console.log(chalk.cyan('For more info: https://github.com/ruvnet/ruvector'));
console.log('');
});

View file

@ -1,7 +1,7 @@
{
"name": "ruvector",
"version": "0.1.1",
"description": "High-performance vector database with native bindings and WASM fallback",
"version": "0.1.2",
"description": "All-in-one vector database: HNSW search, Cypher queries, GNN layers, compression. Pinecone + Neo4j + PyTorch in one package.",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"bin": {
@ -34,7 +34,15 @@
"ai",
"rust",
"napi",
"wasm"
"wasm",
"graph",
"cypher",
"neo4j",
"gnn",
"neural-network",
"compression",
"hnsw",
"rag"
],
"author": "rUv",
"license": "MIT",
@ -51,7 +59,11 @@
"inquirer": "^8.2.6"
},
"optionalDependencies": {
"@ruvector/core": "^0.1.1"
"@ruvector/core": "^0.1.1",
"@ruvector/graph-node": "^0.1.0",
"@ruvector/graph-wasm": "^0.1.0",
"@ruvector/gnn-node": "^0.1.0",
"@ruvector/gnn-wasm": "^0.1.0"
},
"devDependencies": {
"@types/node": "^20.10.0",