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,236 lines • 51.8 kB
JavaScript
/**
* 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' }
];
// 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
}
],
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 } = await import('../memory/memory-initializer.js');
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
});
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'
}
],
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 } = await import('../memory/memory-initializer.js');
const result = await getEntry({ key, namespace });
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;
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
}
],
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' }
],
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 } = await import('../memory/memory-initializer.js');
const searchResult = await searchEntries({
query,
namespace,
limit,
threshold
});
if (!searchResult.success) {
output.printError(searchResult.error || 'Search failed');
return { success: false, exitCode: 1 };
}
const results = searchResult.results.map(r => ({
key: r.key,
score: r.score,
namespace: r.namespace,
preview: r.content
}));
if (ctx.flags.format === 'json') {
output.printJson({ query, searchType, results, searchTime: `${searchResult.searchTime}ms` });
return { success: true, data: results };
}
// Performance stats
output.writeln(output.dim(` Search time: ${searchResult.searchTime}ms`));
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
}
],
action: async (ctx) => {
const namespace = ctx.flags.namespace;
const limit = ctx.flags.limit;
// Use sql.js directly for consistent data access
try {
const { listEntries } = await import('../memory/memory-initializer.js');
const listResult = await listEntries({ namespace, limit, offset: 0 });
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
}
],
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 } = await import('../memory/memory-initializer.js');
const result = await deleteEntry({ key, namespace });
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',
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' }
]
});
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 vector embeddings, pattern learning, temporal decay',
options: [
{
name: 'backend',
short: 'b',
description: 'Backend type: hybrid (default), sqlite, or agentdb',
type: 'string',
default: 'hybrid'
},
{
name: 'path',
short: 'p',
description: 'Database path',
type: 'string'
},
{
name: 'force',
short: 'f',
description: 'Overwrite existing database',
type: 'boolean',
default: false
},
{
name: 'verbose',
description: 'Show detailed initialization output',
type: 'boolean',
default: false
},
{
name: 'verify',
description: 'Run verification tests after initialization',
type: 'boolean',
default: true
},
{
name: 'load-embeddings',
description: 'Pre-load ONNX embedding model (lazy by default)',
type: 'boolean',
default: false
}
],
examples: [
{ command: 'claude-flow memory init', description: 'Initialize hybrid backend with all features' },
{ command: 'claude-flow memory init -b agentdb', description: 'Initialize AgentDB backend' },
{ command: 'claude-flow memory init -p ./data/memory.db --force', description: 'Reinitialize at custom path' },
{ command: 'claude-flow memory init --verbose --verify', description: 'Initialize with full verification' }
],
action: async (ctx) => {
const backend = ctx.flags.backend || 'hybrid';
const customPath = ctx.flags.path;
const force = ctx.flags.force;
const verbose = ctx.flags.verbose;
const verify = ctx.flags.verify !== false; // Default true
const loadEmbeddings = ctx.flags.loadEmbeddings;
output.writeln();
output.writeln(output.bold('Initializing Memory Database'));
output.writeln(output.dim('─'.repeat(50)));
const spinner = output.createSpinner({ text: 'Initializing schema...', spinner: 'dots' });
spinner.start();
try {
// Import the memory initializer
const { initializeMemoryDatabase, loadEmbeddingModel, verifyMemoryInit } = await import('../memory/memory-initializer.js');
const result = await initializeMemoryDatabase({
backend,
dbPath: customPath,
force,
verbose
});
if (!result.success) {
spinner.fail('Initialization failed');
output.printError(result.error || 'Unknown error');
return { success: false, exitCode: 1 };
}
spinner.succeed('Schema initialized');
// Lazy load or pre-load embedding model
if (loadEmbeddings) {
const embeddingSpinner = output.createSpinner({ text: 'Loading embedding model...', spinner: 'dots' });
embeddingSpinner.start();
const embeddingResult = await loadEmbeddingModel({ verbose });
if (embeddingResult.success) {
embeddingSpinner.succeed(`Embedding model loaded: ${embeddingResult.modelName} (${embeddingResult.dimensions}-dim, ${embeddingResult.loadTime}ms)`);
}
else {
embeddingSpinner.stop(output.warning(`Embedding model: ${embeddingResult.error || 'Using fallback'}`));
}
}
output.writeln();
// Show features enabled with detailed capabilities
const featureLines = [
`Backend: ${result.backend}`,
`Schema Version: ${result.schemaVersion}`,
`Database Path: ${result.dbPath}`,
'',
output.bold('Features:'),
` Vector Embeddings: ${result.features.vectorEmbeddings ? output.success('✓ Enabled') : output.dim('✗ Disabled')}`,
` Pattern Learning: ${result.features.patternLearning ? output.success('✓ Enabled') : output.dim('✗ Disabled')}`,
` Temporal Decay: ${result.features.temporalDecay ? output.success('✓ Enabled') : output.dim('✗ Disabled')}`,
` HNSW Indexing: ${result.features.hnswIndexing ? output.success('✓ Enabled') : output.dim('✗ Disabled')}`,
` Migration Tracking: ${result.features.migrationTracking ? output.success('✓ Enabled') : output.dim('✗ Disabled')}`
];
if (verbose) {
featureLines.push('', output.bold('HNSW Configuration:'), ` M (connections): 16`, ` ef (construction): 200`, ` ef (search): 100`, ` Metric: cosine`, '', output.bold('Pattern Learning:'), ` Confidence scoring: 0.0 - 1.0`, ` Temporal decay: Half-life 30 days`, ` Pattern versioning: Enabled`, ` Types: task-routing, error-recovery, optimization, coordination, prediction`);
}
output.printBox(featureLines.join('\n'), 'Configuration');
output.writeln();
// Show tables created
if (verbose && result.tablesCreated.length > 0) {
output.writeln(output.bold('Tables Created:'));
output.printTable({
columns: [
{ key: 'table', header: 'Table', width: 22 },
{ key: 'purpose', header: 'Purpose', width: 38 }
],
data: [
{ table: 'memory_entries', purpose: 'Core memory storage with embeddings' },
{ table: 'patterns', purpose: 'Learned patterns with confidence scores' },
{ table: 'pattern_history', purpose: 'Pattern versioning and evolution' },
{ table: 'trajectories', purpose: 'SONA learning trajectories' },
{ table: 'trajectory_steps', purpose: 'Individual trajectory steps' },
{ table: 'migration_state', purpose: 'Migration progress tracking' },
{ table: 'sessions', purpose: 'Context persistence' },
{ table: 'vector_indexes', purpose: 'HNSW index configuration' },
{ table: 'metadata', purpose: 'System metadata' }
]
});
output.writeln();
output.writeln(output.bold('Indexes Created:'));
output.printList(result.indexesCreated.slice(0, 8).map(idx => output.dim(idx)));
if (result.indexesCreated.length > 8) {
output.writeln(output.dim(` ... and ${result.indexesCreated.length - 8} more`));
}
output.writeln();
}
// Run verification if enabled
if (verify) {
const verifySpinner = output.createSpinner({ text: 'Verifying initialization...', spinner: 'dots' });
verifySpinner.start();
const verification = await verifyMemoryInit(result.dbPath, { verbose });
if (verification.success) {
verifySpinner.succeed(`Verification passed (${verification.summary.passed}/${verification.summary.total} tests)`);
}
else {
verifySpinner.fail(`Verification failed (${verification.summary.failed}/${verification.summary.total} tests failed)`);
}
if (verbose || !verification.success) {
output.writeln();
output.writeln(output.bold('Verification Results:'));
output.printTable({
columns: [
{ key: 'status', header: '', width: 3 },
{ key: 'name', header: 'Test', width: 22 },
{ key: 'details', header: 'Details', width: 30 },
{ key: 'duration', header: 'Time', width: 8, align: 'right' }
],
data: verification.tests.map(t => ({
status: t.passed ? output.success('✓') : output.error('✗'),
name: t.name,
details: t.details || '',
duration: t.duration ? `${t.duration}ms` : '-'
}))
});
}
output.writeln();
}
// Show next steps
output.writeln(output.bold('Next Steps:'));
output.printList([
`Store data: ${output.highlight('claude-flow memory store -k "key" --value "data"')}`,
`Search: ${output.highlight('claude-flow memory search -q "query"')}`,
`Train patterns: ${output.highlight('claude-flow neural train -p coordination')}`,
`View stats: ${output.highlight('claude-flow memory stats')}`
]);
// Also sync to .claude directory
const fs = await import('fs');
const path = await import('path');
const claudeDir = path.join(process.cwd(), '.claude');
const claudeDbPath = path.join(claudeDir, 'memory.db');
if (!fs.existsSync(claudeDir)) {
fs.mkdirSync(claudeDir, { recursive: true });
}
if (fs.existsSync(result.dbPath) && (!fs.existsSync(claudeDbPath) || force)) {
fs.copyFileSync(result.dbPath, claudeDbPath);
output.writeln();
output.writeln(output.dim(`Synced to: ${claudeDbPath}`));
}
return {
success: true,
data: result
};
}
catch (error) {
spinner.fail('Initialization failed');
output.printError(`Failed to initialize memory: ${error instanceof Error ? error.message : String(error)}`);
return { success: false, exitCode: 1 };
}
}
};
// Main memory command
export const memoryCommand = {
name: 'memory',
description: 'Memory management commands',
subcommands: [initMemoryCommand, storeCommand, retrieveCommand, searchCommand, listCommand, d