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

1,232 lines 64.1 kB
/** * V3 CLI Memory Command * Memory operations for AgentDB integration */ import { output } from '../output.js'; import { select, confirm, input } from '../prompt.js'; import { callMCPTool, MCPClientError } from '../mcp-client.js'; // Memory backends const BACKENDS = [ { value: 'agentdb', label: 'AgentDB', hint: 'Vector database with HNSW indexing (150x-12,500x faster)' }, { value: 'sqlite', label: 'SQLite', hint: 'Lightweight local storage' }, { value: 'hybrid', label: 'Hybrid', hint: 'SQLite + AgentDB (recommended)' }, { value: 'memory', label: 'In-Memory', hint: 'Fast but non-persistent' } ]; // #2105: shared --path option for memory subcommands. // Precedence: --path > CLAUDE_FLOW_DB_PATH env var > default root const DB_PATH_OPTION = { name: 'path', description: 'Override DB file path (also: CLAUDE_FLOW_DB_PATH env var). ' + 'Precedence: --path > CLAUDE_FLOW_DB_PATH > CLAUDE_FLOW_MEMORY_PATH/memory.db > cwd/.swarm/memory.db', type: 'string', }; // Store command const storeCommand = { name: 'store', description: 'Store data in memory', options: [ { name: 'key', short: 'k', description: 'Storage key/namespace', type: 'string', required: true }, { name: 'value', // Note: No short flag - global -v is reserved for verbose description: 'Value to store (use --value)', type: 'string' }, { name: 'namespace', short: 'n', description: 'Memory namespace', type: 'string', default: 'default' }, { name: 'ttl', description: 'Time to live in seconds', type: 'number' }, { name: 'tags', description: 'Comma-separated tags', type: 'string' }, { name: 'vector', description: 'Store as vector embedding', type: 'boolean', default: false }, { name: 'upsert', short: 'u', description: 'Update if key exists (insert or replace)', type: 'boolean', default: false }, DB_PATH_OPTION ], examples: [ { command: 'claude-flow memory store -k "api/auth" -v "JWT implementation"', description: 'Store text' }, { command: 'claude-flow memory store -k "pattern/singleton" --vector', description: 'Store vector' }, { command: 'claude-flow memory store -k "pattern" -v "updated" --upsert', description: 'Update existing' } ], action: async (ctx) => { const key = ctx.flags.key; let value = ctx.flags.value || ctx.args[0]; const namespace = ctx.flags.namespace; const ttl = ctx.flags.ttl; const tags = ctx.flags.tags ? ctx.flags.tags.split(',') : []; const asVector = ctx.flags.vector; const upsert = ctx.flags.upsert; if (!key) { output.printError('Key is required. Use --key or -k'); return { success: false, exitCode: 1 }; } if (!value && ctx.interactive) { value = await input({ message: 'Enter value to store:', validate: (v) => v.length > 0 || 'Value is required' }); } if (!value) { output.printError('Value is required. Use --value'); return { success: false, exitCode: 1 }; } const storeData = { key, namespace, value, ttl, tags, asVector, storedAt: new Date().toISOString(), size: Buffer.byteLength(value, 'utf8') }; output.printInfo(`Storing in ${namespace}/${key}...`); // Use direct sql.js storage with automatic embedding generation try { const { storeEntry, resolveDbPath: _rdbStore } = await import('../memory/memory-initializer.js'); const dbPath = _rdbStore(ctx.flags.path); if (asVector) { output.writeln(output.dim(' Generating embedding vector...')); } const result = await storeEntry({ key, value, namespace, generateEmbeddingFlag: true, // Always generate embeddings for semantic search tags, ttl, upsert, dbPath }); if (!result.success) { output.printError(result.error || 'Failed to store'); return { success: false, exitCode: 1 }; } output.writeln(); output.printTable({ columns: [ { key: 'property', header: 'Property', width: 15 }, { key: 'val', header: 'Value', width: 40 } ], data: [ { property: 'Key', val: key }, { property: 'Namespace', val: namespace }, { property: 'Size', val: `${storeData.size} bytes` }, { property: 'TTL', val: ttl ? `${ttl}s` : 'None' }, { property: 'Tags', val: tags.length > 0 ? tags.join(', ') : 'None' }, { property: 'Vector', val: result.embedding ? `Yes (${result.embedding.dimensions}-dim)` : 'No' }, { property: 'ID', val: result.id.substring(0, 20) } ] }); output.writeln(); output.printSuccess('Data stored successfully'); return { success: true, data: { ...storeData, id: result.id, embedding: result.embedding } }; } catch (error) { output.printError(`Failed to store: ${error instanceof Error ? error.message : 'Unknown error'}`); return { success: false, exitCode: 1 }; } } }; // Retrieve command const retrieveCommand = { name: 'retrieve', aliases: ['get'], description: 'Retrieve data from memory', options: [ { name: 'key', short: 'k', description: 'Storage key', type: 'string' }, { name: 'namespace', short: 'n', description: 'Memory namespace', type: 'string', default: 'default' }, // #2073: --format is the GLOBAL option (parser.ts:78) with choices // ['text', 'json', 'table'] and default 'text'. The retrieve handler // discriminates: 'json' emits parseable JSON, anything else (text/box/...) // emits the human-readable box. No per-command override needed; we just // document the behavior in the help text via examples. { // #2073: --value-only emits ONLY the value string (no wrapper). // Designed for piping into JSON.parse without any cleanup. name: 'value-only', description: 'Print only the stored value to stdout (no wrapper)', type: 'boolean', default: false }, DB_PATH_OPTION ], action: async (ctx) => { const key = ctx.flags.key || ctx.args[0]; const namespace = ctx.flags.namespace; if (!key) { output.printError('Key is required'); return { success: false, exitCode: 1 }; } // Use sql.js directly for consistent data access try { const { getEntry, resolveDbPath: _rdbRetrieve } = await import('../memory/memory-initializer.js'); const dbPathRetrieve = _rdbRetrieve(ctx.flags.path); const result = await getEntry({ key, namespace, dbPath: dbPathRetrieve }); if (!result.success) { output.printError(`Failed to retrieve: ${result.error}`); return { success: false, exitCode: 1 }; } if (!result.found || !result.entry) { output.printWarning(`Key not found: ${key}`); return { success: false, exitCode: 1, data: { key, found: false } }; } const entry = result.entry; // #2073: --value-only emits just the raw value (no decoration) for // piping into JSON.parse / jq / other downstream parsers without // any cleanup. if (ctx.flags['value-only'] || ctx.flags.valueOnly) { // Use process.stdout.write directly to bypass any printer-side // transformation of quotes/structural characters. process.stdout.write(entry.content); if (process.stdout.isTTY) process.stdout.write('\n'); return { success: true, data: entry }; } if (ctx.flags.format === 'json') { output.printJson(entry); return { success: true, data: entry }; } output.writeln(); output.printBox([ `Namespace: ${entry.namespace}`, `Key: ${entry.key}`, `Size: ${entry.content.length} bytes`, `Access Count: ${entry.accessCount}`, `Tags: ${entry.tags.length > 0 ? entry.tags.join(', ') : 'None'}`, `Vector: ${entry.hasEmbedding ? 'Yes' : 'No'}`, '', output.bold('Value:'), entry.content ].join('\n'), 'Memory Entry'); return { success: true, data: entry }; } catch (error) { output.printError(`Failed to retrieve: ${error instanceof Error ? error.message : 'Unknown error'}`); return { success: false, exitCode: 1 }; } } }; // Search command const searchCommand = { name: 'search', description: 'Search memory with semantic/vector search', options: [ { name: 'query', short: 'q', description: 'Search query', type: 'string', required: true }, { name: 'namespace', short: 'n', description: 'Memory namespace', type: 'string' }, { name: 'limit', short: 'l', description: 'Maximum results', type: 'number', default: 10 }, { name: 'threshold', description: 'Similarity threshold (0-1)', type: 'number', default: 0.7 }, { name: 'type', short: 't', description: 'Search type (semantic, keyword, hybrid)', type: 'string', default: 'semantic', choices: ['semantic', 'keyword', 'hybrid'] }, { name: 'build-hnsw', description: 'Build/rebuild HNSW index before searching (enables 150x-12,500x speedup)', type: 'boolean', default: false }, { name: 'smart', short: 's', description: 'Use SmartRetrieval pipeline (query expansion, RRF, MMR, recency)', type: 'boolean', default: false }, DB_PATH_OPTION ], examples: [ { command: 'claude-flow memory search -q "authentication patterns"', description: 'Semantic search' }, { command: 'claude-flow memory search -q "JWT" -t keyword', description: 'Keyword search' }, { command: 'claude-flow memory search -q "test" --build-hnsw', description: 'Build HNSW index and search' }, { command: 'claude-flow memory search -q "auth patterns" --smart', description: 'SmartRetrieval with RRF + MMR' } ], action: async (ctx) => { const query = ctx.flags.query || ctx.args[0]; const namespace = ctx.flags.namespace || 'all'; const limit = ctx.flags.limit || 10; const threshold = ctx.flags.threshold || 0.3; const searchType = ctx.flags.type || 'semantic'; const buildHnsw = (ctx.flags['build-hnsw'] || ctx.flags.buildHnsw); if (!query) { output.printError('Query is required. Use --query or -q'); return { success: false, exitCode: 1 }; } // Build/rebuild HNSW index if requested if (buildHnsw) { output.printInfo('Building HNSW index...'); try { const { getHNSWIndex, getHNSWStatus } = await import('../memory/memory-initializer.js'); const startTime = Date.now(); const index = await getHNSWIndex({ forceRebuild: true }); const buildTime = Date.now() - startTime; if (index) { const status = getHNSWStatus(); output.printSuccess(`HNSW index built (${status.entryCount} vectors, ${buildTime}ms)`); output.writeln(output.dim(` Dimensions: ${status.dimensions}, Metric: cosine`)); output.writeln(output.dim(` Search speedup: ${status.entryCount > 10000 ? '12,500x' : status.entryCount > 1000 ? '150x' : '10x'}`)); } else { output.printWarning('HNSW index not available (install @ruvector/core for acceleration)'); } output.writeln(); } catch (error) { output.printWarning(`HNSW build failed: ${error instanceof Error ? error.message : 'Unknown error'}`); output.writeln(output.dim(' Falling back to brute-force search')); output.writeln(); } } output.printInfo(`Searching: "${query}" (${searchType})`); output.writeln(); // Use direct sql.js search with vector similarity try { const { searchEntries, resolveDbPath: _rdbSearch } = await import('../memory/memory-initializer.js'); const dbPathSearch = _rdbSearch(ctx.flags.path); const useSmart = (ctx.flags.smart || ctx.flags.s); let results; let searchTimeMs; let smartStats; let backendLabel = 'HNSW + sql.js'; // #1846: feature-detect smartSearch — older published builds of // @claude-flow/memory don't expose it. Fall through to plain // semantic search with a one-line warning instead of throwing. // eslint-disable-next-line @typescript-eslint/no-explicit-any let smartSearchFn; if (useSmart) { try { // eslint-disable-next-line @typescript-eslint/no-explicit-any const memMod = await import('@claude-flow/memory'); if (typeof memMod.smartSearch === 'function') { smartSearchFn = memMod.smartSearch; } } catch { /* memory package not loadable */ } if (!smartSearchFn) { output.printWarning('Smart search requested but smartSearch is not available on the installed @claude-flow/memory build (#1846). Falling back to standard semantic search.'); } } if (useSmart && smartSearchFn) { // Adapt searchEntries to the SearchFn interface const rawSearch = async (req) => { const r = await searchEntries({ query: req.query, namespace: req.namespace || namespace, limit: req.limit || limit * 3, threshold: req.threshold ?? threshold, dbPath: dbPathSearch, }); return { results: r.results.map(e => ({ id: e.id, key: e.key, content: e.content, score: e.score, namespace: e.namespace, })), }; }; const smartResult = await smartSearchFn(rawSearch, { query, namespace, limit, threshold, }); results = smartResult.results.map((r) => ({ key: r.key, score: r.score, namespace: r.namespace, preview: r.content, })); searchTimeMs = smartResult.stats.durationMs; smartStats = smartResult.stats; backendLabel = 'SmartRetrieval (RRF + MMR + Recency)'; } else { const searchResult = await searchEntries({ query, namespace, limit, threshold, dbPath: dbPathSearch }); if (!searchResult.success) { output.printError(searchResult.error || 'Search failed'); return { success: false, exitCode: 1 }; } results = searchResult.results.map(r => ({ key: r.key, score: r.score, namespace: r.namespace, preview: r.content })); searchTimeMs = searchResult.searchTime; } if (ctx.flags.format === 'json') { output.printJson({ query, searchType, results, searchTime: `${searchTimeMs}ms`, ...(smartStats ? { stats: smartStats } : {}) }); return { success: true, data: results }; } // Performance stats output.writeln(output.dim(` Search time: ${searchTimeMs}ms`)); if (useSmart && smartStats) { output.writeln(output.dim(` Backend: ${backendLabel}`)); output.writeln(output.dim(` Variants: ${smartStats.variantCount}, Raw candidates: ${smartStats.rawCandidateCount}`)); } output.writeln(); if (results.length === 0) { output.printWarning('No results found'); output.writeln(output.dim('Try: claude-flow memory store -k "key" --value "data"')); return { success: true, data: [] }; } output.printTable({ columns: [ { key: 'key', header: 'Key', width: 20 }, { key: 'score', header: 'Score', width: 8, align: 'right', format: (v) => Number(v).toFixed(2) }, { key: 'namespace', header: 'Namespace', width: 12 }, { key: 'preview', header: 'Preview', width: 35 } ], data: results }); output.writeln(); output.printInfo(`Found ${results.length} results`); return { success: true, data: results }; } catch (error) { output.printError(`Search failed: ${error instanceof Error ? error.message : 'Unknown error'}`); return { success: false, exitCode: 1 }; } } }; // List command const listCommand = { name: 'list', aliases: ['ls'], description: 'List memory entries', options: [ { name: 'namespace', short: 'n', description: 'Filter by namespace', type: 'string' }, { name: 'tags', short: 't', description: 'Filter by tags (comma-separated)', type: 'string' }, { name: 'limit', short: 'l', description: 'Maximum entries', type: 'number', default: 20 }, DB_PATH_OPTION ], action: async (ctx) => { const namespace = ctx.flags.namespace; const limit = ctx.flags.limit; // Use sql.js directly for consistent data access try { const { listEntries, resolveDbPath: _rdbList } = await import('../memory/memory-initializer.js'); const dbPathList = _rdbList(ctx.flags.path); const listResult = await listEntries({ namespace, limit, offset: 0, dbPath: dbPathList }); if (!listResult.success) { output.printError(`Failed to list: ${listResult.error}`); return { success: false, exitCode: 1 }; } // Format entries for display const entries = listResult.entries.map(e => ({ key: e.key, namespace: e.namespace, size: e.size + ' B', vector: e.hasEmbedding ? '✓' : '-', accessCount: e.accessCount, updated: formatRelativeTime(e.updatedAt) })); if (ctx.flags.format === 'json') { output.printJson(listResult.entries); return { success: true, data: listResult.entries }; } output.writeln(); output.writeln(output.bold('Memory Entries')); output.writeln(); if (entries.length === 0) { output.printWarning('No entries found'); output.printInfo('Store data: claude-flow memory store -k "key" --value "data"'); return { success: true, data: [] }; } output.printTable({ columns: [ { key: 'key', header: 'Key', width: 25 }, { key: 'namespace', header: 'Namespace', width: 12 }, { key: 'size', header: 'Size', width: 10, align: 'right' }, { key: 'vector', header: 'Vector', width: 8, align: 'center' }, { key: 'accessCount', header: 'Accessed', width: 10, align: 'right' }, { key: 'updated', header: 'Updated', width: 12 } ], data: entries }); output.writeln(); output.printInfo(`Showing ${entries.length} of ${listResult.total} entries`); return { success: true, data: listResult.entries }; } catch (error) { output.printError(`Failed to list: ${error instanceof Error ? error.message : 'Unknown error'}`); return { success: false, exitCode: 1 }; } } }; // Helper function to format relative time function formatRelativeTime(isoDate) { const now = Date.now(); const date = new Date(isoDate).getTime(); const diff = now - date; const seconds = Math.floor(diff / 1000); const minutes = Math.floor(seconds / 60); const hours = Math.floor(minutes / 60); const days = Math.floor(hours / 24); if (days > 0) return `${days}d ago`; if (hours > 0) return `${hours}h ago`; if (minutes > 0) return `${minutes}m ago`; return 'just now'; } // Delete command const deleteCommand = { name: 'delete', aliases: ['rm'], description: 'Delete memory entry', options: [ { name: 'key', short: 'k', description: 'Storage key', type: 'string' }, { name: 'namespace', short: 'n', description: 'Memory namespace', type: 'string', default: 'default' }, { name: 'force', short: 'f', description: 'Skip confirmation', type: 'boolean', default: false }, DB_PATH_OPTION ], examples: [ { command: 'claude-flow memory delete -k "mykey"', description: 'Delete entry with default namespace' }, { command: 'claude-flow memory delete -k "lesson" -n "lessons"', description: 'Delete entry from specific namespace' }, { command: 'claude-flow memory delete mykey -f', description: 'Delete without confirmation' } ], action: async (ctx) => { // Support both --key flag and positional argument const key = ctx.flags.key || ctx.args[0]; const namespace = ctx.flags.namespace || 'default'; const force = ctx.flags.force; if (!key) { output.printError('Key is required. Use: memory delete -k "key" [-n "namespace"]'); return { success: false, exitCode: 1 }; } if (!force && ctx.interactive) { const confirmed = await confirm({ message: `Delete memory entry "${key}" from namespace "${namespace}"?`, default: false }); if (!confirmed) { output.printInfo('Operation cancelled'); return { success: true }; } } // Use sql.js directly for consistent data access (Issue #980) try { const { deleteEntry, resolveDbPath: _rdbDelete } = await import('../memory/memory-initializer.js'); const dbPathDelete = _rdbDelete(ctx.flags.path); const result = await deleteEntry({ key, namespace, dbPath: dbPathDelete }); if (!result.success) { output.printError(result.error || 'Failed to delete'); return { success: false, exitCode: 1 }; } if (result.deleted) { output.printSuccess(`Deleted "${key}" from namespace "${namespace}"`); output.printInfo(`Remaining entries: ${result.remainingEntries}`); } else { output.printWarning(`Key not found: "${key}" in namespace "${namespace}"`); } return { success: result.deleted, data: result }; } catch (error) { output.printError(`Failed to delete: ${error instanceof Error ? error.message : 'Unknown error'}`); return { success: false, exitCode: 1 }; } } }; // Stats command const statsCommand = { name: 'stats', description: 'Show memory statistics', options: [DB_PATH_OPTION], action: async (ctx) => { // Call MCP memory/stats tool for real statistics try { const statsResult = await callMCPTool('memory_stats', {}); const stats = { backend: statsResult.backend, entries: { total: statsResult.totalEntries, vectors: 0, // Would need vector backend support text: statsResult.totalEntries }, storage: { total: statsResult.totalSize, location: statsResult.location }, version: statsResult.version, oldestEntry: statsResult.oldestEntry, newestEntry: statsResult.newestEntry }; if (ctx.flags.format === 'json') { output.printJson(stats); return { success: true, data: stats }; } output.writeln(); output.writeln(output.bold('Memory Statistics')); output.writeln(); output.writeln(output.bold('Overview')); output.printTable({ columns: [ { key: 'metric', header: 'Metric', width: 20 }, { key: 'value', header: 'Value', width: 30, align: 'right' } ], data: [ { metric: 'Backend', value: stats.backend }, { metric: 'Version', value: stats.version }, { metric: 'Total Entries', value: stats.entries.total.toLocaleString() }, { metric: 'Total Storage', value: stats.storage.total }, { metric: 'Location', value: stats.storage.location } ] }); output.writeln(); output.writeln(output.bold('Timeline')); output.printTable({ columns: [ { key: 'metric', header: 'Metric', width: 20 }, { key: 'value', header: 'Value', width: 30, align: 'right' } ], data: [ { metric: 'Oldest Entry', value: stats.oldestEntry || 'N/A' }, { metric: 'Newest Entry', value: stats.newestEntry || 'N/A' } ] }); // #1622 — Surface the active embedding provider in `memory stats` so // users can tell which backend resolved at runtime (the 6-level // fallback chain in loadEmbeddingModel ranges from full ONNX to a // 128-dim hash that has no semantic understanding). Calling // loadEmbeddingModel() is cheap when the model is already cached; // a fresh call still resolves quickly because we only need the // metadata, not a real embedding. try { const { loadEmbeddingModel, getHNSWStatus } = await import('../memory/memory-initializer.js'); const embedding = await loadEmbeddingModel({ verbose: false }); const hnsw = getHNSWStatus(); // Map model name → semantic capability so users can spot the // hash-fallback case without reading docs. const semanticProviders = new Set([ 'Xenova/all-MiniLM-L6-v2', 'Xenova/all-mpnet-base-v2', 'Xenova/bge-small-en-v1.5', 'agentic-flow', 'agentic-flow/reasoningbank', 'ruvector/onnx', 'cached', ]); const isSemantic = embedding.success && semanticProviders.has(embedding.modelName); output.writeln(); output.writeln(output.bold('Embedding')); output.printTable({ columns: [ { key: 'metric', header: 'Metric', width: 20 }, { key: 'value', header: 'Value', width: 30, align: 'right' } ], data: [ { metric: 'Provider', value: embedding.success ? embedding.modelName : output.warning(`unavailable: ${embedding.error || 'unknown'}`), }, { metric: 'Dimensions', value: String(embedding.dimensions) }, { metric: 'Semantic Search', value: isSemantic ? output.success('yes') : output.warning('no — using hash fallback'), }, { metric: 'HNSW Index', // ruflo#1989 / #1987: `hnsw.entryCount` is in-process JS state // (the live HNSW index of the current Node process). A fresh // `memory stats` invocation has never indexed anything, so it // reports 0 even when the persistent DB has thousands of // entries with embeddings. Use the persistent count from the // MCP tool (`entriesWithEmbeddings`, which is the actual // count of rows that have a vector) as the source of truth. value: (() => { const persisted = typeof statsResult.entriesWithEmbeddings === 'number' ? statsResult.entriesWithEmbeddings : null; const live = hnsw.entryCount || 0; const total = persisted !== null ? Math.max(persisted, live) : live; if (!hnsw.available) return output.dim('not active'); if (total === 0) return output.warning('available but not initialized'); return output.success(`active (${total.toLocaleString()} entries)`); })(), }, ] }); } catch (e) { // Don't fail the whole stats command if introspection breaks — // the rest of the dashboard is still useful. output.writeln(); output.writeln(output.bold('Embedding')); output.printInfo(`Provider info unavailable: ${e instanceof Error ? e.message : String(e)}`); } output.writeln(); output.printInfo('V3 Performance: 150x-12,500x faster search with HNSW indexing'); return { success: true, data: stats }; } catch (error) { output.printError(`Failed to get stats: ${error instanceof Error ? error.message : 'Unknown error'}`); return { success: false, exitCode: 1 }; } } }; // Configure command const configureCommand = { name: 'configure', aliases: ['config'], description: 'Configure memory backend', options: [ { name: 'backend', short: 'b', description: 'Memory backend', type: 'string', choices: BACKENDS.map(b => b.value) }, { name: 'path', description: 'Storage path', type: 'string' }, { name: 'cache-size', description: 'Cache size in MB', type: 'number' }, { name: 'hnsw-m', description: 'HNSW M parameter', type: 'number', default: 16 }, { name: 'hnsw-ef', description: 'HNSW ef parameter', type: 'number', default: 200 } ], action: async (ctx) => { let backend = ctx.flags.backend; if (!backend && ctx.interactive) { backend = await select({ message: 'Select memory backend:', options: BACKENDS, default: 'hybrid' }); } const config = { backend: backend || 'hybrid', path: ctx.flags.path || './data/memory', cacheSize: ctx.flags.cacheSize || 256, hnsw: { m: ctx.flags.hnswM || 16, ef: ctx.flags.hnswEf || 200 } }; output.writeln(); output.printInfo('Memory Configuration'); output.writeln(); output.printTable({ columns: [ { key: 'setting', header: 'Setting', width: 20 }, { key: 'value', header: 'Value', width: 25 } ], data: [ { setting: 'Backend', value: config.backend }, { setting: 'Storage Path', value: config.path }, { setting: 'Cache Size', value: `${config.cacheSize} MB` }, { setting: 'HNSW M', value: config.hnsw.m }, { setting: 'HNSW ef', value: config.hnsw.ef } ] }); output.writeln(); output.printSuccess('Memory configuration updated'); return { success: true, data: config }; } }; // Cleanup command const cleanupCommand = { name: 'cleanup', description: 'Clean up stale and expired memory entries', options: [ { name: 'dry-run', short: 'd', description: 'Show what would be deleted', type: 'boolean', default: false }, { name: 'older-than', short: 'o', description: 'Delete entries older than (e.g., "7d", "30d")', type: 'string' }, { name: 'expired-only', short: 'e', description: 'Only delete expired TTL entries', type: 'boolean', default: false }, { name: 'low-quality', short: 'l', description: 'Delete low quality patterns (threshold)', type: 'number' }, { name: 'namespace', short: 'n', description: 'Clean specific namespace only', type: 'string' }, { name: 'force', short: 'f', description: 'Skip confirmation', type: 'boolean', default: false } ], examples: [ { command: 'claude-flow memory cleanup --dry-run', description: 'Preview cleanup' }, { command: 'claude-flow memory cleanup --older-than 30d', description: 'Delete entries older than 30 days' }, { command: 'claude-flow memory cleanup --expired-only', description: 'Clean expired entries' } ], action: async (ctx) => { const dryRun = ctx.flags.dryRun; const force = ctx.flags.force; if (dryRun) { output.writeln(output.warning('DRY RUN - No changes will be made')); } output.printInfo('Analyzing memory for cleanup...'); try { const result = await callMCPTool('memory_cleanup', { dryRun, olderThan: ctx.flags.olderThan, expiredOnly: ctx.flags.expiredOnly, lowQualityThreshold: ctx.flags.lowQuality, namespace: ctx.flags.namespace, }); if (ctx.flags.format === 'json') { output.printJson(result); return { success: true, data: result }; } output.writeln(); output.writeln(output.bold('Cleanup Analysis')); output.printTable({ columns: [ { key: 'category', header: 'Category', width: 20 }, { key: 'count', header: 'Count', width: 15, align: 'right' } ], data: [ { category: 'Expired (TTL)', count: result.candidates.expired }, { category: 'Stale (unused)', count: result.candidates.stale }, { category: 'Low Quality', count: result.candidates.lowQuality }, { category: output.bold('Total'), count: output.bold(String(result.candidates.total)) } ] }); if (!dryRun && result.candidates.total > 0 && !force) { const confirmed = await confirm({ message: `Delete ${result.candidates.total} entries (${result.freed.formatted})?`, default: false }); if (!confirmed) { output.printInfo('Cleanup cancelled'); return { success: true, data: result }; } } if (!dryRun) { output.writeln(); output.printSuccess(`Cleaned ${result.deleted.entries} entries`); output.printList([ `Vectors removed: ${result.deleted.vectors}`, `Patterns removed: ${result.deleted.patterns}`, `Space freed: ${result.freed.formatted}`, `Duration: ${result.duration}ms` ]); } return { success: true, data: result }; } catch (error) { if (error instanceof MCPClientError) { output.printError(`Cleanup error: ${error.message}`); } else { output.printError(`Unexpected error: ${String(error)}`); } return { success: false, exitCode: 1 }; } } }; // Compress command const compressCommand = { name: 'compress', description: 'Compress and optimize memory storage', options: [ { name: 'level', short: 'l', description: 'Compression level (fast, balanced, max)', type: 'string', choices: ['fast', 'balanced', 'max'], default: 'balanced' }, { name: 'target', short: 't', description: 'Target (vectors, text, patterns, all)', type: 'string', choices: ['vectors', 'text', 'patterns', 'all'], default: 'all' }, { name: 'quantize', short: 'z', description: 'Enable vector quantization (reduces memory 4-32x)', type: 'boolean', default: false }, { name: 'bits', description: 'Quantization bits (4, 8, 16)', type: 'number', default: 8 }, { name: 'rebuild-index', short: 'r', description: 'Rebuild HNSW index after compression', type: 'boolean', default: true } ], examples: [ { command: 'claude-flow memory compress', description: 'Balanced compression' }, { command: 'claude-flow memory compress --quantize --bits 4', description: '4-bit quantization (32x reduction)' }, { command: 'claude-flow memory compress -l max -t vectors', description: 'Max compression on vectors' } ], action: async (ctx) => { const level = ctx.flags.level || 'balanced'; const target = ctx.flags.target || 'all'; const quantize = ctx.flags.quantize; const bits = ctx.flags.bits || 8; const rebuildIndex = ctx.flags.rebuildIndex ?? true; output.writeln(); output.writeln(output.bold('Memory Compression')); output.writeln(output.dim(`Level: ${level}, Target: ${target}, Quantize: ${quantize ? `${bits}-bit` : 'no'}`)); output.writeln(); const spinner = output.createSpinner({ text: 'Analyzing current storage...', spinner: 'dots' }); spinner.start(); try { const result = await callMCPTool('memory_compress', { level, target, quantize, bits, rebuildIndex, }); spinner.succeed('Compression complete'); if (ctx.flags.format === 'json') { output.printJson(result); return { success: true, data: result }; } output.writeln(); output.writeln(output.bold('Storage Comparison')); output.printTable({ columns: [ { key: 'category', header: 'Category', width: 15 }, { key: 'before', header: 'Before', width: 12, align: 'right' }, { key: 'after', header: 'After', width: 12, align: 'right' }, { key: 'saved', header: 'Saved', width: 12, align: 'right' } ], data: [ { category: 'Vectors', before: result.before.vectorsSize, after: result.after.vectorsSize, saved: '-' }, { category: 'Text', before: result.before.textSize, after: result.after.textSize, saved: '-' }, { category: 'Patterns', before: result.before.patternsSize, after: result.after.patternsSize, saved: '-' }, { category: 'Index', before: result.before.indexSize, after: result.after.indexSize, saved: '-' }, { category: output.bold('Total'), before: result.before.totalSize, after: result.after.totalSize, saved: output.success(result.compression.formattedSaved) } ] }); output.writeln(); output.printBox([ `Compression Ratio: ${result.compression.ratio.toFixed(2)}x`, `Space Saved: ${result.compression.formattedSaved}`, `Quantization: ${result.compression.quantizationApplied ? `Yes (${bits}-bit)` : 'No'}`, `Index Rebuilt: ${result.compression.indexRebuilt ? 'Yes' : 'No'}`, `Duration: ${(result.duration / 1000).toFixed(1)}s` ].join('\n'), 'Results'); if (result.performance) { output.writeln(); output.writeln(output.bold('Performance Impact')); output.printList([ `Search latency: ${result.performance.searchLatencyBefore.toFixed(2)}ms → ${result.performance.searchLatencyAfter.toFixed(2)}ms`, `Speedup: ${output.success(result.performance.searchSpeedup)}` ]); } return { success: true, data: result }; } catch (error) { spinner.fail('Compression failed'); if (error instanceof MCPClientError) { output.printError(`Compression error: ${error.message}`); } else { output.printError(`Unexpected error: ${String(error)}`); } return { success: false, exitCode: 1 }; } } }; // Export command const exportCommand = { name: 'export', description: 'Export memory to file', options: [ { name: 'output', short: 'o', description: 'Output file path', type: 'string', required: true }, { name: 'format', short: 'f', description: 'Export format (json, csv, binary)', type: 'string', choices: ['json', 'csv', 'binary'], default: 'json' }, { name: 'namespace', short: 'n', description: 'Export specific namespace', type: 'string' }, { name: 'include-vectors', description: 'Include vector embeddings', type: 'boolean', default: true } ], examples: [ { command: 'claude-flow memory export -o ./backup.json', description: 'Export all to JSON' }, { command: 'claude-flow memory export -o ./data.csv -f csv', description: 'Export to CSV' } ], action: async (ctx) => { const outputPath = ctx.flags.output; const format = ctx.flags.format || 'json'; if (!outputPath) { output.printError('Output path is required. Use --output or -o'); return { success: false, exitCode: 1 }; } output.printInfo(`Exporting memory to ${outputPath}...`); try { const result = await callMCPTool('memory_export', { outputPath, format, namespace: ctx.flags.namespace, includeVectors: ctx.flags.includeVectors ?? true, }); output.printSuccess(`Exported to ${result.outputPath}`); output.printList([ `Entries: ${result.exported.entries}`, `Vectors: ${result.exported.vectors}`, `Patterns: ${result.exported.patterns}`, `File size: ${result.fileSize}` ]); return { success: true, data: result }; } catch (error) { if (error instanceof MCPClientError) { output.printError(`Export error: ${error.message}`); } else { output.printError(`Unexpected error: ${String(error)}`); } return { success: false, exitCode: 1 }; } } }; // Import command const importCommand = { name: 'import', description: 'Import memory from file', options: [ { name: 'input', short: 'i', description: 'Input file path', type: 'string', required: true }, { name: 'merge', short: 'm', description: 'Merge with existing (skip duplicates)', type: 'boolean', default: true }, { name: 'namespace', short: 'n', description: 'Import into specific namespace', type: 'string' } ], examples: [ { command: 'claude-flow memory import -i ./backup.json', description: 'Import from file' }, { command: 'claude-flow memory import -i ./data.json -n archive', description: 'Import to namespace' } ], action: async (ctx) => { const inputPath = ctx.flags.input || ctx.args[0]; if (!inputPath) { output.printError('Input path is required. Use --input or -i'); return { success: false, exitCode: 1 }; } output.printInfo(`Importing memory from ${inputPath}...`); try { const result = await callMCPTool('memory_import', { inputPath, merge: ctx.flags.merge ?? true, namespace: ctx.flags.namespace, }); output.printSuccess(`Imported from ${result.inputPath}`); output.printList([ `Entries: ${result.imported.entries}`, `Vectors: ${result.imported.vectors}`, `Patterns: ${result.imported.patterns}`, `Skipped (duplicates): ${result.skipped}`, `Duration: ${result.duration}ms` ]); return { success: true, data: result }; } catch (error) { if (error instanceof MCPClientError) { output.printError(`Import error: ${error.message}`); } else { output.printError(`Unexpected error: ${String(error)}`); } return { success: false, exitCode: 1 }; } } }; // Init subcommand - initialize memory database using sql.js const initMemoryCommand = { name: 'init', description: 'Initialize memory database with sql.js (WASM SQLite) - includes ve