ruvector/npm/packages/postgres-cli/src/cli.ts
rUv ca30a68a8f
feat(postgres): Export ruvector_* attention functions and fix CLI (#55)
* chore: Add proptest regression data from test run

Records edge cases found during property testing that cause
integer overflow failures. These will help reproduce and fix
the boundary condition bugs in distance calculations.

* fix: Resolve property test failures with overflow handling

- Fix ScalarQuantized::distance() i16 overflow: use i32 for diff*diff
  (255*255=65025 overflows i16 max of 32767)
- Fix ScalarQuantized::quantize() division by zero when all values equal
  (handle scale=0 case by defaulting to 1.0)
- Bound vector_strategy() to -1000..1000 range to prevent overflow in
  distance calculations with extreme float values

All 177 tests now pass in ruvector-core.

* fix(cli): Resolve short option conflicts in clap argument definitions

- Change --dimensions from -d to -D to avoid conflict with global --debug
- Change --db from -d to -b across all subcommands (Insert, Search, Info,
  Benchmark, Export, Import) to avoid conflict with global --debug

Fixes clap panic in debug builds: "Short option names must be unique"

Note: 4 CLI integration tests still fail due to pre-existing issue where
VectorDB doesn't persist its configuration to disk. When reopening a
database, dimensions are read from config defaults (384) instead of
from the stored database metadata. This is an architectural issue
requiring VectorDB changes to implement proper metadata persistence.

* feat(core): Add database configuration persistence and fix CLI test

- Add CONFIG_TABLE to storage.rs for persisting DbOptions
- Implement save_config() and load_config() methods in VectorStorage
- Modify VectorDB::new() to load stored config for existing databases
- Fix dimension mismatch by recreating storage with correct dimensions
- Fix test_error_handling CLI test to use /dev/null/db.db path

This ensures database settings (dimensions, distance metric, HNSW config,
quantization) are preserved across restarts. Previously opening an existing
database would use default settings instead of stored configuration.

* fix(ruvLLM): Guard against edge cases in HNSW and softmax

- memory.rs: Fix random_level() to handle r=0 (ln(0) = -inf)
- memory.rs: Fix ml calculation when hnsw_m=1 (ln(1) = 0 → div by zero)
- router.rs: Add division-by-zero guard in softmax for larger arrays

These edge cases could cause undefined behavior or NaN propagation.

* fix(postgres-cli): Fix SQL parameter binding and type casting issues

- Fix createVectorTable: Use direct interpolation for DEFAULT clause
  since PostgreSQL doesn't support parameter binding in DEFAULT expressions
- Fix sparse vector functions: Change ::sparsevec casts to ::text since
  the extension uses text input parsing, not a native sparsevec type
- Fix listAttentionTypes: Replace non-existent ruvector_attention_types()
  function call with hardcoded list of 39 supported attention mechanisms
- Add Docker test infrastructure for simulating npx installation in clean
  environment (Dockerfile.npx-test and test-npx-install.sh)

Tested against ruvector-postgres:0.2.3 Docker container with verified
working functionality for: vector operations, hyperbolic geometry,
quantization, sparse vectors, and attention mechanism queries.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* chore(postgres-cli): Bump version to 0.2.1

Published to npm with bug fixes for SQL parameter binding and type casting.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(postgres-cli): Add dynamic version and optimized benchmarks

- Fix version mismatch: CLI now reads version from package.json instead
  of hardcoded value using createRequire for ESM compatibility
- Add optimized benchmark SQL files with performance improvements:
  - HNSW index (m=16, ef_construction=100) for 2.2x faster vector search
  - GIN index for 7x faster full-text search
  - B-tree indexes for 5x faster graph edge lookups
  - PARALLEL SAFE functions for parallel query execution
  - Pre-computed tsvector columns for FTS optimization

Benchmark targets:
- HNSW Vector Search: ~24ms (was 53ms)
- Hamming Distance: ~7.6ms (was 112ms)
- Full-Text Search: ~3.5ms (was 26ms)
- GraphSAGE Aggregation: ~2.6ms (was 13ms)
- Sparse Dot Product: ~27ms (was 134ms)

Published as @ruvector/postgres-cli@0.2.2

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(postgres): Export ruvector_* attention functions and fix CLI

Rust Extension (0.2.4):
- Add `pub` visibility to all pg_extern functions in attention/operators.rs
- Functions now exported: ruvector_attention_score, ruvector_softmax,
  ruvector_multi_head_attention, ruvector_flash_attention,
  ruvector_attention_types, ruvector_attention_scores

CLI (0.2.3):
- Update computeAttention to use actual extension functions:
  attention_score, attention_softmax, attention_weighted_add
- Simplify listAttentionTypes to show actually supported patterns
- Full attention computation now works against live PostgreSQL

The extension provides both primitive functions (attention_*) and
advanced functions (ruvector_*) for different use cases.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-06 12:28:10 -05:00

1063 lines
38 KiB
JavaScript

#!/usr/bin/env node
/**
* RuVector PostgreSQL CLI
* Comprehensive command-line interface for the RuVector PostgreSQL extension
*
* Features:
* - Vector operations (dense and sparse)
* - Attention mechanisms (scaled-dot, multi-head, flash)
* - Graph Neural Networks (GCN, GraphSAGE)
* - Graph operations with Cypher queries
* - Self-learning with ReasoningBank
* - Hyperbolic geometry (Poincare, Lorentz)
* - Agent routing (Tiny Dancer)
* - Vector quantization
* - Benchmarking
*/
import { Command } from 'commander';
import chalk from 'chalk';
import { createRequire } from 'module';
import { RuVectorClient } from './client.js';
import { VectorCommands } from './commands/vector.js';
import { AttentionCommands } from './commands/attention.js';
import { GnnCommands } from './commands/gnn.js';
import { GraphCommands } from './commands/graph.js';
import { LearningCommands } from './commands/learning.js';
import { BenchmarkCommands } from './commands/benchmark.js';
import { SparseCommands } from './commands/sparse.js';
import { HyperbolicCommands } from './commands/hyperbolic.js';
import { RoutingCommands } from './commands/routing.js';
import { QuantizationCommands } from './commands/quantization.js';
import { InstallCommands } from './commands/install.js';
// Read version from package.json
const require = createRequire(import.meta.url);
const pkg = require('../package.json');
const program = new Command();
program
.name('ruvector-pg')
.description('RuVector PostgreSQL CLI - Advanced AI Vector Database Extension')
.version(pkg.version)
.option('-c, --connection <string>', 'PostgreSQL connection string', 'postgresql://localhost:5432/ruvector')
.option('-v, --verbose', 'Enable verbose output');
// ============================================================================
// Vector Operations
// ============================================================================
const vector = program.command('vector').description('Dense vector operations');
vector
.command('create <name>')
.description('Create a new vector table')
.option('-d, --dim <number>', 'Vector dimensions', '384')
.option('-i, --index <type>', 'Index type (hnsw, ivfflat)', 'hnsw')
.action(async (name, options) => {
const client = new RuVectorClient(program.opts().connection);
await VectorCommands.create(client, name, options);
});
vector
.command('insert <table>')
.description('Insert vectors into a table')
.option('-f, --file <path>', 'JSON file with vectors')
.option('-t, --text <content>', 'Text to embed')
.action(async (table, options) => {
const client = new RuVectorClient(program.opts().connection);
await VectorCommands.insert(client, table, options);
});
vector
.command('search <table>')
.description('Search for similar vectors')
.option('-q, --query <vector>', 'Query vector as JSON array')
.option('-t, --text <content>', 'Text query to embed and search')
.option('-k, --top-k <number>', 'Number of results', '10')
.option('-m, --metric <type>', 'Distance metric (cosine, l2, ip)', 'cosine')
.action(async (table, options) => {
const client = new RuVectorClient(program.opts().connection);
await VectorCommands.search(client, table, options);
});
vector
.command('distance')
.description('Compute distance between two vectors')
.requiredOption('-a, --a <vector>', 'First vector as JSON array')
.requiredOption('-b, --b <vector>', 'Second vector as JSON array')
.option('-m, --metric <type>', 'Distance metric (cosine, l2, ip)', 'cosine')
.action(async (options) => {
const client = new RuVectorClient(program.opts().connection);
await VectorCommands.distance(client, options);
});
vector
.command('normalize')
.description('Normalize a vector to unit length')
.requiredOption('--vector <array>', 'Vector as JSON array')
.action(async (options) => {
const client = new RuVectorClient(program.opts().connection);
await VectorCommands.normalize(client, options);
});
// ============================================================================
// Sparse Vector Operations
// ============================================================================
const sparse = program.command('sparse').description('Sparse vector operations');
sparse
.command('create')
.description('Create a sparse vector from indices and values')
.requiredOption('--indices <array>', 'Non-zero indices as JSON array')
.requiredOption('--values <array>', 'Values as JSON array')
.requiredOption('--dim <number>', 'Total dimensionality')
.action(async (options) => {
const client = new RuVectorClient(program.opts().connection);
await SparseCommands.create(client, options);
});
sparse
.command('distance')
.description('Compute distance between sparse vectors')
.requiredOption('-a, --a <sparse>', 'First sparse vector')
.requiredOption('-b, --b <sparse>', 'Second sparse vector')
.option('-m, --metric <type>', 'Distance metric (dot, cosine, euclidean, manhattan)', 'cosine')
.action(async (options) => {
const client = new RuVectorClient(program.opts().connection);
await SparseCommands.distance(client, options);
});
sparse
.command('bm25')
.description('Compute BM25 relevance score')
.requiredOption('--query <sparse>', 'Query sparse vector (IDF weights)')
.requiredOption('--doc <sparse>', 'Document sparse vector (term frequencies)')
.requiredOption('--doc-len <number>', 'Document length')
.requiredOption('--avg-doc-len <number>', 'Average document length')
.option('--k1 <number>', 'Term frequency saturation', '1.2')
.option('--b <number>', 'Length normalization', '0.75')
.action(async (options) => {
const client = new RuVectorClient(program.opts().connection);
await SparseCommands.bm25(client, options);
});
sparse
.command('top-k')
.description('Keep only top-k elements by value')
.requiredOption('-s, --sparse <vector>', 'Sparse vector')
.requiredOption('-k, --k <number>', 'Number of elements to keep')
.action(async (options) => {
const client = new RuVectorClient(program.opts().connection);
await SparseCommands.topK(client, options);
});
sparse
.command('prune')
.description('Remove elements below threshold')
.requiredOption('-s, --sparse <vector>', 'Sparse vector')
.requiredOption('--threshold <number>', 'Minimum absolute value threshold')
.action(async (options) => {
const client = new RuVectorClient(program.opts().connection);
await SparseCommands.prune(client, options);
});
sparse
.command('dense-to-sparse')
.description('Convert dense vector to sparse')
.requiredOption('-d, --dense <array>', 'Dense vector as JSON array')
.action(async (options) => {
const client = new RuVectorClient(program.opts().connection);
await SparseCommands.denseToSparse(client, options);
});
sparse
.command('sparse-to-dense <sparse>')
.description('Convert sparse vector to dense')
.action(async (sparseVec) => {
const client = new RuVectorClient(program.opts().connection);
await SparseCommands.sparseToDense(client, sparseVec);
});
sparse
.command('info <sparse>')
.description('Get sparse vector information')
.action(async (sparseVec) => {
const client = new RuVectorClient(program.opts().connection);
await SparseCommands.info(client, sparseVec);
});
sparse
.command('help')
.description('Show sparse vector help')
.action(() => SparseCommands.showHelp());
// ============================================================================
// Hyperbolic Operations
// ============================================================================
const hyperbolic = program.command('hyperbolic').description('Hyperbolic geometry operations');
hyperbolic
.command('poincare-distance')
.description('Compute Poincare ball distance')
.requiredOption('-a, --a <vector>', 'First vector as JSON array')
.requiredOption('-b, --b <vector>', 'Second vector as JSON array')
.option('--curvature <number>', 'Curvature (negative)', '-1.0')
.action(async (options) => {
const client = new RuVectorClient(program.opts().connection);
await HyperbolicCommands.poincareDistance(client, options);
});
hyperbolic
.command('lorentz-distance')
.description('Compute Lorentz/hyperboloid distance')
.requiredOption('-a, --a <vector>', 'First vector as JSON array')
.requiredOption('-b, --b <vector>', 'Second vector as JSON array')
.option('--curvature <number>', 'Curvature (negative)', '-1.0')
.action(async (options) => {
const client = new RuVectorClient(program.opts().connection);
await HyperbolicCommands.lorentzDistance(client, options);
});
hyperbolic
.command('mobius-add')
.description('Perform Mobius addition in Poincare ball')
.requiredOption('-a, --a <vector>', 'First vector as JSON array')
.requiredOption('-b, --b <vector>', 'Second vector as JSON array')
.option('--curvature <number>', 'Curvature (negative)', '-1.0')
.action(async (options) => {
const client = new RuVectorClient(program.opts().connection);
await HyperbolicCommands.mobiusAdd(client, options);
});
hyperbolic
.command('exp-map')
.description('Exponential map: tangent space to manifold')
.requiredOption('--base <vector>', 'Base point on manifold')
.requiredOption('--tangent <vector>', 'Tangent vector at base')
.option('--curvature <number>', 'Curvature (negative)', '-1.0')
.action(async (options) => {
const client = new RuVectorClient(program.opts().connection);
await HyperbolicCommands.expMap(client, options);
});
hyperbolic
.command('log-map')
.description('Logarithmic map: manifold to tangent space')
.requiredOption('--base <vector>', 'Base point on manifold')
.requiredOption('--target <vector>', 'Target point on manifold')
.option('--curvature <number>', 'Curvature (negative)', '-1.0')
.action(async (options) => {
const client = new RuVectorClient(program.opts().connection);
await HyperbolicCommands.logMap(client, options);
});
hyperbolic
.command('poincare-to-lorentz')
.description('Convert Poincare to Lorentz coordinates')
.requiredOption('--vector <array>', 'Poincare vector')
.option('--curvature <number>', 'Curvature (negative)', '-1.0')
.action(async (options) => {
const client = new RuVectorClient(program.opts().connection);
await HyperbolicCommands.poincareToLorentz(client, options);
});
hyperbolic
.command('lorentz-to-poincare')
.description('Convert Lorentz to Poincare coordinates')
.requiredOption('--vector <array>', 'Lorentz vector')
.option('--curvature <number>', 'Curvature (negative)', '-1.0')
.action(async (options) => {
const client = new RuVectorClient(program.opts().connection);
await HyperbolicCommands.lorentzToPoincare(client, options);
});
hyperbolic
.command('minkowski-dot')
.description('Compute Minkowski inner product')
.requiredOption('-a, --a <vector>', 'First vector')
.requiredOption('-b, --b <vector>', 'Second vector')
.action(async (options) => {
const client = new RuVectorClient(program.opts().connection);
await HyperbolicCommands.minkowskiDot(client, options.a, options.b);
});
hyperbolic
.command('help')
.description('Show hyperbolic geometry help')
.action(() => HyperbolicCommands.showHelp());
// ============================================================================
// Routing/Agent Operations
// ============================================================================
const routing = program.command('routing').description('Tiny Dancer agent routing');
routing
.command('register')
.description('Register a new agent')
.requiredOption('--name <name>', 'Agent name')
.requiredOption('--type <type>', 'Agent type (llm, embedding, specialized)')
.requiredOption('--capabilities <list>', 'Capabilities (comma-separated)')
.requiredOption('--cost <number>', 'Cost per request in dollars')
.requiredOption('--latency <number>', 'Average latency in ms')
.requiredOption('--quality <number>', 'Quality score (0-1)')
.action(async (options) => {
const client = new RuVectorClient(program.opts().connection);
await RoutingCommands.registerAgent(client, options);
});
routing
.command('register-full')
.description('Register agent with full JSON config')
.requiredOption('--config <json>', 'Full agent configuration as JSON')
.action(async (options) => {
const client = new RuVectorClient(program.opts().connection);
await RoutingCommands.registerAgentFull(client, options);
});
routing
.command('update')
.description('Update agent metrics after a request')
.requiredOption('--name <name>', 'Agent name')
.requiredOption('--latency <number>', 'Observed latency in ms')
.requiredOption('--success <boolean>', 'Whether request succeeded')
.option('--quality <number>', 'Quality score for this request')
.action(async (options) => {
const client = new RuVectorClient(program.opts().connection);
await RoutingCommands.updateMetrics(client, {
...options,
success: options.success === 'true',
});
});
routing
.command('remove <name>')
.description('Remove an agent')
.action(async (name) => {
const client = new RuVectorClient(program.opts().connection);
await RoutingCommands.removeAgent(client, name);
});
routing
.command('set-active <name> <active>')
.description('Enable or disable an agent')
.action(async (name, active) => {
const client = new RuVectorClient(program.opts().connection);
await RoutingCommands.setActive(client, name, active === 'true');
});
routing
.command('route')
.description('Route a request to the best agent')
.requiredOption('--embedding <array>', 'Request embedding as JSON array')
.option('--optimize-for <target>', 'Optimization target (cost, latency, quality, balanced)', 'balanced')
.option('--constraints <json>', 'Routing constraints as JSON')
.action(async (options) => {
const client = new RuVectorClient(program.opts().connection);
await RoutingCommands.route(client, options);
});
routing
.command('list')
.description('List all registered agents')
.action(async () => {
const client = new RuVectorClient(program.opts().connection);
await RoutingCommands.listAgents(client);
});
routing
.command('get <name>')
.description('Get detailed agent information')
.action(async (name) => {
const client = new RuVectorClient(program.opts().connection);
await RoutingCommands.getAgent(client, name);
});
routing
.command('find')
.description('Find agents by capability')
.requiredOption('--capability <name>', 'Capability to search for')
.option('--limit <number>', 'Maximum results', '10')
.action(async (options) => {
const client = new RuVectorClient(program.opts().connection);
await RoutingCommands.findByCapability(client, options);
});
routing
.command('stats')
.description('Get routing statistics')
.action(async () => {
const client = new RuVectorClient(program.opts().connection);
await RoutingCommands.stats(client);
});
routing
.command('clear')
.description('Clear all agents')
.action(async () => {
const client = new RuVectorClient(program.opts().connection);
await RoutingCommands.clearAgents(client);
});
routing
.command('help')
.description('Show routing help')
.action(() => RoutingCommands.showHelp());
// ============================================================================
// Quantization Operations
// ============================================================================
const quantization = program.command('quantization').description('Vector quantization operations');
quantization.alias('quant');
quantization
.command('binary')
.description('Binary quantize a vector (1-bit per dimension)')
.requiredOption('--vector <array>', 'Vector as JSON array')
.action(async (options) => {
const client = new RuVectorClient(program.opts().connection);
await QuantizationCommands.binaryQuantize(client, options);
});
quantization
.command('scalar')
.description('Scalar quantize a vector (8-bit per dimension)')
.requiredOption('--vector <array>', 'Vector as JSON array')
.action(async (options) => {
const client = new RuVectorClient(program.opts().connection);
await QuantizationCommands.scalarQuantize(client, options);
});
quantization
.command('compare <vector>')
.description('Compare all quantization methods on a vector')
.action(async (vector) => {
const client = new RuVectorClient(program.opts().connection);
await QuantizationCommands.compare(client, vector);
});
quantization
.command('stats')
.description('Show quantization statistics')
.action(async () => {
const client = new RuVectorClient(program.opts().connection);
await QuantizationCommands.stats(client);
});
quantization
.command('help')
.description('Show quantization help')
.action(() => QuantizationCommands.showHelp());
// ============================================================================
// Attention Operations
// ============================================================================
const attention = program.command('attention').description('Attention mechanism operations');
attention
.command('compute')
.description('Compute attention between vectors')
.option('-q, --query <vector>', 'Query vector')
.option('-k, --keys <vectors>', 'Key vectors (JSON array)')
.option('-v, --values <vectors>', 'Value vectors (JSON array)')
.option('-t, --type <type>', 'Attention type (scaled_dot, multi_head, flash)', 'scaled_dot')
.action(async (options) => {
const client = new RuVectorClient(program.opts().connection);
await AttentionCommands.compute(client, options);
});
attention
.command('list-types')
.description('List available attention types')
.action(async () => {
const client = new RuVectorClient(program.opts().connection);
await AttentionCommands.listTypes(client);
});
// ============================================================================
// GNN Operations
// ============================================================================
const gnn = program.command('gnn').description('Graph Neural Network operations');
gnn
.command('create <name>')
.description('Create a GNN layer')
.option('-t, --type <type>', 'GNN type (gcn, graphsage, gat, gin)', 'gcn')
.option('-i, --input-dim <number>', 'Input dimensions', '384')
.option('-o, --output-dim <number>', 'Output dimensions', '128')
.action(async (name, options) => {
const client = new RuVectorClient(program.opts().connection);
await GnnCommands.create(client, name, options);
});
gnn
.command('forward <layer>')
.description('Forward pass through GNN layer')
.option('-f, --features <path>', 'Node features file')
.option('-e, --edges <path>', 'Edge list file')
.action(async (layer, options) => {
const client = new RuVectorClient(program.opts().connection);
await GnnCommands.forward(client, layer, options);
});
// ============================================================================
// Graph Operations
// ============================================================================
const graph = program.command('graph').description('Graph and Cypher operations');
graph
.command('create <name>')
.description('Create a new graph')
.action(async (name) => {
const client = new RuVectorClient(program.opts().connection);
try {
await client.connect();
await client.createGraph(name);
console.log(chalk.green(`Graph '${name}' created successfully`));
} catch (err) {
console.error(chalk.red('Error:'), (err as Error).message);
} finally {
await client.disconnect();
}
});
graph
.command('query <graphName> <cypher>')
.description('Execute a Cypher query on a graph')
.action(async (graphName, cypher) => {
const client = new RuVectorClient(program.opts().connection);
await GraphCommands.query(client, `${graphName}:${cypher}`);
});
graph
.command('create-node <graphName>')
.description('Create a graph node')
.option('-l, --labels <labels>', 'Node labels (comma-separated)')
.option('-p, --properties <json>', 'Node properties as JSON')
.action(async (graphName, options) => {
const client = new RuVectorClient(program.opts().connection);
try {
await client.connect();
const labels = options.labels ? options.labels.split(',').map((l: string) => l.trim()) : [];
const properties = options.properties ? JSON.parse(options.properties) : {};
const nodeId = await client.addNode(graphName, labels, properties);
console.log(chalk.green(`Node created with ID: ${nodeId}`));
} catch (err) {
console.error(chalk.red('Error:'), (err as Error).message);
} finally {
await client.disconnect();
}
});
graph
.command('create-edge <graphName>')
.description('Create a graph edge')
.requiredOption('--from <id>', 'Source node ID')
.requiredOption('--to <id>', 'Target node ID')
.requiredOption('--type <type>', 'Edge type/label')
.option('-p, --properties <json>', 'Edge properties as JSON', '{}')
.action(async (graphName, options) => {
const client = new RuVectorClient(program.opts().connection);
try {
await client.connect();
const properties = JSON.parse(options.properties);
const edgeId = await client.addEdge(
graphName,
parseInt(options.from),
parseInt(options.to),
options.type,
properties
);
console.log(chalk.green(`Edge created with ID: ${edgeId}`));
} catch (err) {
console.error(chalk.red('Error:'), (err as Error).message);
} finally {
await client.disconnect();
}
});
graph
.command('shortest-path <graphName>')
.description('Find shortest path between nodes')
.requiredOption('--start <id>', 'Starting node ID')
.requiredOption('--end <id>', 'Ending node ID')
.option('--max-hops <number>', 'Maximum hops', '10')
.action(async (graphName, options) => {
const client = new RuVectorClient(program.opts().connection);
try {
await client.connect();
const path = await client.shortestPath(
graphName,
parseInt(options.start),
parseInt(options.end),
parseInt(options.maxHops)
);
console.log(chalk.bold.blue('\nShortest Path:'));
console.log(` ${chalk.green('Length:')} ${path.length}`);
console.log(` ${chalk.green('Cost:')} ${path.cost}`);
console.log(` ${chalk.green('Nodes:')} ${path.nodes.join(' -> ')}`);
} catch (err) {
console.error(chalk.red('Error:'), (err as Error).message);
} finally {
await client.disconnect();
}
});
graph
.command('stats <graphName>')
.description('Get graph statistics')
.action(async (graphName) => {
const client = new RuVectorClient(program.opts().connection);
try {
await client.connect();
const stats = await client.graphStats(graphName);
console.log(chalk.bold.blue(`\nGraph: ${stats.name}`));
console.log(chalk.gray('-'.repeat(40)));
console.log(` ${chalk.green('Nodes:')} ${stats.node_count}`);
console.log(` ${chalk.green('Edges:')} ${stats.edge_count}`);
console.log(` ${chalk.green('Labels:')} ${stats.labels.join(', ') || 'none'}`);
console.log(` ${chalk.green('Edge Types:')} ${stats.edge_types.join(', ') || 'none'}`);
} catch (err) {
console.error(chalk.red('Error:'), (err as Error).message);
} finally {
await client.disconnect();
}
});
graph
.command('list')
.description('List all graphs')
.action(async () => {
const client = new RuVectorClient(program.opts().connection);
try {
await client.connect();
const graphs = await client.listGraphs();
if (graphs.length === 0) {
console.log(chalk.yellow('No graphs found'));
} else {
console.log(chalk.bold.blue(`\nGraphs (${graphs.length}):`));
graphs.forEach(g => console.log(` ${chalk.green('-')} ${g}`));
}
} catch (err) {
console.error(chalk.red('Error:'), (err as Error).message);
} finally {
await client.disconnect();
}
});
graph
.command('delete <graphName>')
.description('Delete a graph')
.action(async (graphName) => {
const client = new RuVectorClient(program.opts().connection);
try {
await client.connect();
await client.deleteGraph(graphName);
console.log(chalk.green(`Graph '${graphName}' deleted`));
} catch (err) {
console.error(chalk.red('Error:'), (err as Error).message);
} finally {
await client.disconnect();
}
});
graph
.command('traverse')
.description('Traverse the graph (legacy)')
.option('-s, --start <id>', 'Starting node ID')
.option('-d, --depth <number>', 'Max traversal depth', '3')
.option('-t, --type <type>', 'Traversal type (bfs, dfs)', 'bfs')
.action(async (options) => {
const client = new RuVectorClient(program.opts().connection);
await GraphCommands.traverse(client, options);
});
// ============================================================================
// Learning Operations
// ============================================================================
const learning = program.command('learning').description('Self-learning and ReasoningBank operations');
learning
.command('enable <table>')
.description('Enable learning for a table')
.option('--max-trajectories <number>', 'Maximum trajectories to track', '1000')
.option('--num-clusters <number>', 'Number of clusters for patterns', '10')
.action(async (table, options) => {
const client = new RuVectorClient(program.opts().connection);
try {
await client.connect();
const config = {
max_trajectories: parseInt(options.maxTrajectories),
num_clusters: parseInt(options.numClusters),
};
const result = await client.enableLearning(table, config);
console.log(chalk.green(result));
} catch (err) {
console.error(chalk.red('Error:'), (err as Error).message);
} finally {
await client.disconnect();
}
});
learning
.command('stats <table>')
.description('Get learning statistics for a table')
.action(async (table) => {
const client = new RuVectorClient(program.opts().connection);
try {
await client.connect();
const stats = await client.learningStats(table);
console.log(chalk.bold.blue('\nLearning Statistics:'));
console.log(chalk.gray('-'.repeat(40)));
console.log(chalk.bold('Trajectories:'));
console.log(` ${chalk.green('Total:')} ${stats.trajectories.total}`);
console.log(` ${chalk.green('With Feedback:')} ${stats.trajectories.with_feedback}`);
console.log(` ${chalk.green('Avg Latency:')} ${stats.trajectories.avg_latency_us}us`);
console.log(` ${chalk.green('Avg Precision:')} ${(stats.trajectories.avg_precision * 100).toFixed(1)}%`);
console.log(` ${chalk.green('Avg Recall:')} ${(stats.trajectories.avg_recall * 100).toFixed(1)}%`);
console.log(chalk.bold('\nPatterns:'));
console.log(` ${chalk.green('Total:')} ${stats.patterns.total}`);
console.log(` ${chalk.green('Samples:')} ${stats.patterns.total_samples}`);
console.log(` ${chalk.green('Avg Confidence:')} ${(stats.patterns.avg_confidence * 100).toFixed(1)}%`);
console.log(` ${chalk.green('Total Usage:')} ${stats.patterns.total_usage}`);
} catch (err) {
console.error(chalk.red('Error:'), (err as Error).message);
} finally {
await client.disconnect();
}
});
learning
.command('auto-tune <table>')
.description('Auto-tune search parameters')
.option('--optimize-for <target>', 'Optimization target (speed, accuracy, balanced)', 'balanced')
.action(async (table, options) => {
const client = new RuVectorClient(program.opts().connection);
try {
await client.connect();
const result = await client.autoTune(table, options.optimizeFor);
console.log(chalk.bold.blue('\nAuto-Tune Results:'));
console.log(JSON.stringify(result, null, 2));
} catch (err) {
console.error(chalk.red('Error:'), (err as Error).message);
} finally {
await client.disconnect();
}
});
learning
.command('extract-patterns <table>')
.description('Extract patterns from trajectories')
.option('--clusters <number>', 'Number of clusters', '10')
.action(async (table, options) => {
const client = new RuVectorClient(program.opts().connection);
try {
await client.connect();
const result = await client.extractPatterns(table, parseInt(options.clusters));
console.log(chalk.green(result));
} catch (err) {
console.error(chalk.red('Error:'), (err as Error).message);
} finally {
await client.disconnect();
}
});
learning
.command('get-params <table>')
.description('Get optimized search parameters for a query')
.requiredOption('--query <vector>', 'Query vector as JSON array')
.action(async (table, options) => {
const client = new RuVectorClient(program.opts().connection);
try {
await client.connect();
const queryVec = JSON.parse(options.query);
const params = await client.getSearchParams(table, queryVec);
console.log(chalk.bold.blue('\nOptimized Parameters:'));
console.log(` ${chalk.green('ef_search:')} ${params.ef_search}`);
console.log(` ${chalk.green('probes:')} ${params.probes}`);
console.log(` ${chalk.green('confidence:')} ${(params.confidence * 100).toFixed(1)}%`);
} catch (err) {
console.error(chalk.red('Error:'), (err as Error).message);
} finally {
await client.disconnect();
}
});
learning
.command('clear <table>')
.description('Clear all learning data for a table')
.action(async (table) => {
const client = new RuVectorClient(program.opts().connection);
try {
await client.connect();
const result = await client.clearLearning(table);
console.log(chalk.green(result));
} catch (err) {
console.error(chalk.red('Error:'), (err as Error).message);
} finally {
await client.disconnect();
}
});
learning
.command('train')
.description('Train from trajectories (legacy)')
.option('-f, --file <path>', 'Trajectory data file')
.option('-e, --epochs <number>', 'Training epochs', '10')
.action(async (options) => {
const client = new RuVectorClient(program.opts().connection);
await LearningCommands.train(client, options);
});
learning
.command('predict')
.description('Make a prediction (legacy)')
.option('-i, --input <vector>', 'Input vector')
.action(async (options) => {
const client = new RuVectorClient(program.opts().connection);
await LearningCommands.predict(client, options);
});
// ============================================================================
// Benchmark Operations
// ============================================================================
const benchmark = program.command('bench').description('Benchmarking operations');
benchmark
.command('run')
.description('Run comprehensive benchmarks')
.option('-t, --type <type>', 'Benchmark type (vector, attention, gnn, all)', 'all')
.option('-s, --size <number>', 'Dataset size', '10000')
.option('-d, --dim <number>', 'Vector dimensions', '384')
.action(async (options) => {
const client = new RuVectorClient(program.opts().connection);
await BenchmarkCommands.run(client, options);
});
benchmark
.command('report')
.description('Generate benchmark report')
.option('-f, --format <type>', 'Output format (json, table, markdown)', 'table')
.action(async (options) => {
const client = new RuVectorClient(program.opts().connection);
await BenchmarkCommands.report(client, options);
});
// ============================================================================
// Info & Utility Commands
// ============================================================================
program
.command('info')
.description('Show extension information and capabilities')
.action(async () => {
const client = new RuVectorClient(program.opts().connection);
try {
await client.connect();
const info = await client.getExtensionInfo();
console.log(chalk.bold.blue('\nRuVector PostgreSQL Extension'));
console.log(chalk.gray('='.repeat(50)));
console.log(`${chalk.green('Version:')} ${info.version}`);
if (info.simd_info) {
console.log(`${chalk.green('SIMD:')} ${info.simd_info}`);
}
console.log(`\n${chalk.green('Features:')}`);
info.features.forEach(f => console.log(` ${chalk.yellow('*')} ${f}`));
// Get memory stats
try {
const memStats = await client.getMemoryStats();
console.log(`\n${chalk.green('Memory Usage:')}`);
console.log(` Index Memory: ${memStats.index_memory_mb.toFixed(2)} MB`);
console.log(` Vector Cache: ${memStats.vector_cache_mb.toFixed(2)} MB`);
console.log(` Quantization: ${memStats.quantization_tables_mb.toFixed(2)} MB`);
console.log(` ${chalk.bold('Total:')} ${memStats.total_extension_mb.toFixed(2)} MB`);
} catch {
// Memory stats may not be available
}
console.log();
} catch (err) {
console.error(chalk.red('Error:'), (err as Error).message);
} finally {
await client.disconnect();
}
});
program
.command('extension')
.description('Install/upgrade RuVector extension in existing PostgreSQL')
.option('--upgrade', 'Upgrade existing installation')
.action(async (options) => {
const client = new RuVectorClient(program.opts().connection);
try {
await client.connect();
await client.installExtension(options.upgrade);
console.log(chalk.green('RuVector extension installed successfully!'));
} catch (err) {
console.error(chalk.red('Error:'), (err as Error).message);
} finally {
await client.disconnect();
}
});
program
.command('memory')
.description('Show memory statistics')
.action(async () => {
const client = new RuVectorClient(program.opts().connection);
try {
await client.connect();
const stats = await client.getMemoryStats();
console.log(chalk.bold.blue('\nMemory Statistics:'));
console.log(chalk.gray('-'.repeat(40)));
console.log(` ${chalk.green('Index Memory:')} ${stats.index_memory_mb.toFixed(2)} MB`);
console.log(` ${chalk.green('Vector Cache:')} ${stats.vector_cache_mb.toFixed(2)} MB`);
console.log(` ${chalk.green('Quantization:')} ${stats.quantization_tables_mb.toFixed(2)} MB`);
console.log(` ${chalk.bold.green('Total:')} ${stats.total_extension_mb.toFixed(2)} MB`);
} catch (err) {
console.error(chalk.red('Error:'), (err as Error).message);
} finally {
await client.disconnect();
}
});
// ============================================================================
// Installation & Server Management
// ============================================================================
program
.command('install')
.description('Install RuVector PostgreSQL (Docker or native)')
.option('-m, --method <type>', 'Installation method: docker, native, auto', 'auto')
.option('-p, --port <number>', 'PostgreSQL port', '5432')
.option('-u, --user <name>', 'Database user', 'ruvector')
.option('--password <pass>', 'Database password', 'ruvector')
.option('-d, --database <name>', 'Database name', 'ruvector')
.option('--data-dir <path>', 'Persistent data directory')
.option('--name <name>', 'Container name', 'ruvector-postgres')
.option('--version <version>', 'RuVector version', '0.2.3')
.action(async (options) => {
try {
await InstallCommands.install({
method: options.method,
port: parseInt(options.port),
user: options.user,
password: options.password,
database: options.database,
dataDir: options.dataDir,
name: options.name,
version: options.version,
});
} catch (err) {
console.error(chalk.red('Error:'), (err as Error).message);
process.exit(1);
}
});
program
.command('uninstall')
.description('Uninstall RuVector PostgreSQL')
.option('--name <name>', 'Container name', 'ruvector-postgres')
.option('--remove-data', 'Also remove data volumes')
.action(async (options) => {
try {
await InstallCommands.uninstall({
name: options.name,
removeData: options.removeData,
});
} catch (err) {
console.error(chalk.red('Error:'), (err as Error).message);
process.exit(1);
}
});
program
.command('status')
.description('Show RuVector PostgreSQL installation status')
.option('--name <name>', 'Container name', 'ruvector-postgres')
.action(async (options) => {
try {
await InstallCommands.printStatus({ name: options.name });
} catch (err) {
console.error(chalk.red('Error:'), (err as Error).message);
process.exit(1);
}
});
program
.command('start')
.description('Start RuVector PostgreSQL')
.option('--name <name>', 'Container name', 'ruvector-postgres')
.action(async (options) => {
try {
await InstallCommands.start({ name: options.name });
} catch (err) {
console.error(chalk.red('Error:'), (err as Error).message);
process.exit(1);
}
});
program
.command('stop')
.description('Stop RuVector PostgreSQL')
.option('--name <name>', 'Container name', 'ruvector-postgres')
.action(async (options) => {
try {
await InstallCommands.stop({ name: options.name });
} catch (err) {
console.error(chalk.red('Error:'), (err as Error).message);
process.exit(1);
}
});
program
.command('logs')
.description('Show RuVector PostgreSQL logs')
.option('--name <name>', 'Container name', 'ruvector-postgres')
.option('-f, --follow', 'Follow log output')
.option('-n, --tail <lines>', 'Number of lines to show', '100')
.action(async (options) => {
try {
await InstallCommands.logs({
name: options.name,
follow: options.follow,
tail: parseInt(options.tail),
});
} catch (err) {
console.error(chalk.red('Error:'), (err as Error).message);
process.exit(1);
}
});
program
.command('psql [command]')
.description('Connect to RuVector PostgreSQL or execute SQL')
.option('--name <name>', 'Container name', 'ruvector-postgres')
.action(async (command, options) => {
try {
await InstallCommands.psql({
name: options.name,
command: command,
});
} catch (err) {
console.error(chalk.red('Error:'), (err as Error).message);
process.exit(1);
}
});
program.parse();