UNPKG

claude-flow

Version:

Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration

349 lines 14 kB
/** * V3 CLI RuVector Import Command * Import data from sql.js/JSON memory to RuVector PostgreSQL * * Usage: * npx claude-flow ruvector import --input memory-export.json * npx claude-flow ruvector import --from-memory * npx claude-flow ruvector import --input data.json --batch-size 100 * * Created with care by ruv.io */ import { output } from '../../output.js'; import * as fs from 'fs'; import * as path from 'path'; /** * Format a ruvector embedding array for PostgreSQL */ function formatEmbedding(embedding, dimensions = 384) { // Ensure correct dimensions by padding or truncating const padded = [...embedding]; while (padded.length < dimensions) { padded.push(0); } if (padded.length > dimensions) { padded.length = dimensions; } return `'[${padded.join(',')}]'::ruvector(${dimensions})`; } /** * Escape string for PostgreSQL */ function escapeString(str) { return str.replace(/'/g, "''").replace(/\\/g, '\\\\'); } /** * Generate SQL INSERT statement for a memory entry */ function generateInsertSQL(entry) { const key = escapeString(entry.key); const value = typeof entry.value === 'string' ? escapeString(entry.value) : escapeString(JSON.stringify(entry.value)); const namespace = entry.namespace || 'default'; const metadata = entry.metadata ? escapeString(JSON.stringify(entry.metadata)) : '{}'; let embeddingClause = 'NULL'; if (entry.embedding && Array.isArray(entry.embedding) && entry.embedding.length > 0) { embeddingClause = formatEmbedding(entry.embedding); } return `INSERT INTO claude_flow.memory_entries (key, value, embedding, namespace, metadata, created_at, updated_at) VALUES ( '${key}', '${value}', ${embeddingClause}, '${namespace}', '${metadata}'::jsonb, ${entry.created_at ? `'${entry.created_at}'::timestamptz` : 'NOW()'}, ${entry.updated_at ? `'${entry.updated_at}'::timestamptz` : 'NOW()'} ) ON CONFLICT (key, namespace) DO UPDATE SET value = EXCLUDED.value, embedding = COALESCE(EXCLUDED.embedding, claude_flow.memory_entries.embedding), metadata = EXCLUDED.metadata, updated_at = NOW();`; } /** * RuVector Import command - import from sql.js/JSON to PostgreSQL */ export const importCommand = { name: 'import', description: 'Import data from sql.js/JSON memory to RuVector PostgreSQL', aliases: ['load', 'migrate-data'], options: [ { name: 'input', short: 'i', description: 'Input JSON file path', type: 'string', }, { name: 'from-memory', description: 'Export from current Claude-Flow memory and import', type: 'boolean', default: false, }, { name: 'output', short: 'o', description: 'Output SQL file instead of executing (dry-run)', type: 'string', }, { name: 'batch-size', short: 'b', description: 'Batch size for imports', type: 'number', default: 100, }, { name: 'host', short: 'h', description: 'PostgreSQL host', type: 'string', default: 'localhost', }, { name: 'port', short: 'p', description: 'PostgreSQL port', type: 'number', default: 5432, }, { name: 'database', short: 'd', description: 'Database name', type: 'string', default: 'claude_flow', }, { name: 'user', short: 'u', description: 'Database user', type: 'string', default: 'claude', }, { name: 'password', description: 'Database password (or use PGPASSWORD env var)', type: 'string', }, { name: 'container', short: 'c', description: 'Docker container name to exec into', type: 'string', default: 'ruvector-postgres', }, { name: 'verbose', short: 'v', description: 'Verbose output', type: 'boolean', default: false, }, ], examples: [ { command: 'claude-flow ruvector import --input memory-export.json', description: 'Import from JSON file' }, { command: 'claude-flow ruvector import --input data.json --output import.sql', description: 'Generate SQL file (dry-run)' }, { command: 'claude-flow ruvector import --from-memory', description: 'Export current memory and import' }, { command: 'claude-flow ruvector import --input data.json --container my-postgres', description: 'Import using custom container' }, ], action: async (ctx) => { const inputFile = ctx.flags.input; const fromMemory = ctx.flags['from-memory']; const outputFile = ctx.flags.output; const batchSize = ctx.flags['batch-size'] || 100; const containerName = ctx.flags.container || 'ruvector-postgres'; const verbose = ctx.flags.verbose; output.writeln(); output.writeln(output.bold('RuVector PostgreSQL Import')); output.writeln(output.dim('='.repeat(50))); output.writeln(); // Validate input if (!inputFile && !fromMemory) { output.printError('Either --input <file> or --from-memory is required'); output.writeln(); output.printInfo('Examples:'); output.writeln(' claude-flow ruvector import --input memory-export.json'); output.writeln(' claude-flow ruvector import --from-memory'); return { success: false, message: 'Missing input source' }; } let entries = []; // Load entries from JSON file if (inputFile) { if (!fs.existsSync(inputFile)) { output.printError(`Input file not found: ${inputFile}`); return { success: false, message: 'File not found' }; } try { output.printInfo(`Reading: ${inputFile}`); const content = fs.readFileSync(inputFile, 'utf-8'); const data = JSON.parse(content); // Handle different JSON formats if (Array.isArray(data)) { entries = data; } else if (data.entries && Array.isArray(data.entries)) { entries = data.entries; } else if (data.results && Array.isArray(data.results)) { entries = data.results; } else if (typeof data === 'object') { // Convert object format { key: value } to entries entries = Object.entries(data).map(([key, value]) => ({ key, value: typeof value === 'object' ? JSON.stringify(value) : String(value), })); } output.printSuccess(`Loaded ${entries.length} entries from file`); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); output.printError(`Failed to parse JSON: ${errorMessage}`); return { success: false, message: errorMessage }; } } // Export from current memory if (fromMemory) { output.printInfo('Exporting from current Claude-Flow memory...'); output.printWarning('Note: Run "npx claude-flow memory list --format json > memory-export.json" first'); output.printInfo('Then use: npx claude-flow ruvector import --input memory-export.json'); return { success: false, message: 'Use explicit JSON export first' }; } if (entries.length === 0) { output.printWarning('No entries to import'); return { success: true }; } // Calculate statistics const stats = { total: entries.length, imported: 0, skipped: 0, errors: 0, withEmbeddings: 0, byNamespace: {}, }; // Generate SQL statements const sqlStatements = []; sqlStatements.push('-- RuVector PostgreSQL Import'); sqlStatements.push(`-- Generated: ${new Date().toISOString()}`); sqlStatements.push(`-- Total entries: ${entries.length}`); sqlStatements.push(''); sqlStatements.push('BEGIN;'); sqlStatements.push(''); for (const entry of entries) { try { // Track statistics const ns = entry.namespace || 'default'; stats.byNamespace[ns] = (stats.byNamespace[ns] || 0) + 1; if (entry.embedding && entry.embedding.length > 0) { stats.withEmbeddings++; } const sql = generateInsertSQL(entry); sqlStatements.push(sql); sqlStatements.push(''); stats.imported++; if (verbose) { output.writeln(output.dim(` Processed: ${entry.key} (${ns})`)); } } catch (error) { stats.errors++; const errorMessage = error instanceof Error ? error.message : String(error); output.printWarning(`Skipped entry "${entry.key}": ${errorMessage}`); } } sqlStatements.push('COMMIT;'); sqlStatements.push(''); sqlStatements.push(`-- Import complete: ${stats.imported} entries`); const fullSQL = sqlStatements.join('\n'); // Output SQL file (dry-run) if (outputFile) { try { output.printInfo(`Writing SQL to: ${outputFile}`); fs.writeFileSync(outputFile, fullSQL); output.printSuccess(`SQL file created: ${outputFile}`); output.writeln(); output.printInfo('To execute the import:'); output.writeln(` docker exec -i ${containerName} psql -U claude -d claude_flow < ${outputFile}`); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); output.printError(`Failed to write SQL file: ${errorMessage}`); return { success: false, message: errorMessage }; } } else { // Execute directly via docker exec output.printInfo(`Importing to PostgreSQL via container: ${containerName}`); output.writeln(); // Write to temp file for execution const tempFile = path.join(process.cwd(), '.ruvector-import-temp.sql'); try { fs.writeFileSync(tempFile, fullSQL); output.printInfo('Executing import...'); output.writeln(); output.writeln(output.dim('Command:')); output.writeln(output.dim(` docker exec -i ${containerName} psql -U claude -d claude_flow < ${tempFile}`)); output.writeln(); // Execute via child_process const { execSync } = await import('child_process'); try { const result = execSync(`docker exec -i ${containerName} psql -U claude -d claude_flow < ${tempFile}`, { encoding: 'utf-8', timeout: 60000, }); if (verbose) { output.writeln(output.dim(result)); } output.printSuccess('Import completed successfully!'); } catch (execError) { const execErrorMessage = execError instanceof Error ? execError.message : String(execError); output.printError(`Import failed: ${execErrorMessage}`); output.writeln(); output.printInfo('You can manually run the import with:'); output.writeln(` docker exec -i ${containerName} psql -U claude -d claude_flow < ${tempFile}`); return { success: false, message: execErrorMessage }; } finally { // Clean up temp file try { fs.unlinkSync(tempFile); } catch { // Ignore cleanup errors } } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); output.printError(`Failed to create temp file: ${errorMessage}`); return { success: false, message: errorMessage }; } } // Print statistics output.writeln(); output.printBox([ 'Import Statistics', '', ` Total entries: ${stats.total}`, ` Imported: ${stats.imported}`, ` With embeddings: ${stats.withEmbeddings}`, ` Errors: ${stats.errors}`, '', 'By Namespace:', ...Object.entries(stats.byNamespace).map(([ns, count]) => ` ${ns}: ${count}`), ].join('\n'), 'Import Complete'); output.writeln(); // Show verification command output.printInfo('To verify the import:'); output.writeln(` docker exec ${containerName} psql -U claude -d claude_flow -c "SELECT COUNT(*) FROM claude_flow.memory_entries;"`); output.writeln(); return { success: true }; }, }; export default importCommand; //# sourceMappingURL=import.js.map