UNPKG

mira-consciousness

Version:

Memory & Intelligence Retention Archive - Preserving The Spark

596 lines (511 loc) • 23.9 kB
#!/usr/bin/env node import fs from 'fs-extra'; import path from 'path'; import os from 'os'; import { execSync, spawn } from 'child_process'; import chalk from 'chalk'; import ora from 'ora'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); console.log(chalk.blue('\nšŸš€ MIRA Memory System Setup')); console.log('─'.repeat(50)); /** * Intelligently discover Claude Code conversation files location * Searches common locations across different environments */ function discoverClaudeConversationPath() { const homeDir = os.homedir(); const currentDir = process.cwd(); // Build cross-platform search paths const searchPaths = [ // TIER 1: Standard user locations (90% of installations) path.join(homeDir, '.claude', 'projects'), path.join(homeDir, '.claude-code', 'projects'), // TIER 2: Development environments - use path.join for cross-platform ...(process.platform !== 'win32' ? [ path.join(path.sep, 'home', 'codespace', '.claude', 'projects'), path.join(path.sep, 'home', 'codespace', '.claude-code', 'projects'), path.join(path.sep, 'home', 'vscode', '.claude', 'projects'), path.join(path.sep, 'home', 'gitpod', '.claude', 'projects'), ] : []), // TIER 3: Current workspace variations path.join(currentDir, '.claude', 'projects'), path.join(currentDir, '..', '.claude', 'projects'), // TIER 4: Platform-specific standard locations ...(process.platform === 'win32' ? [ path.join(homeDir, 'AppData', 'Local', 'Claude', 'projects'), path.join(homeDir, 'AppData', 'Local', 'claude-code', 'projects'), path.join(homeDir, 'AppData', 'Roaming', 'Claude', 'projects'), ] : []), ...(process.platform === 'darwin' ? [ path.join(homeDir, 'Library', 'Application Support', 'Claude', 'projects'), path.join(homeDir, 'Library', 'Application Support', 'claude-code', 'projects'), ] : []), ...(process.platform === 'linux' ? [ path.join(homeDir, '.local', 'share', 'claude', 'projects'), path.join(homeDir, '.config', 'claude', 'projects'), ] : []), // TIER 5: Container environments ...(process.platform !== 'win32' ? [ path.join(path.sep, 'workspace', '.claude', 'projects'), path.join(path.sep, 'app', '.claude', 'projects'), ] : []), // TIER 6: WSL paths ...(process.platform === 'win32' || fs.existsSync('/mnt/c') ? [ path.join(path.sep, 'mnt', 'c', 'Users', process.env.USERNAME || process.env.USER || 'user', '.claude', 'projects'), ] : []), // TIER 7: Use temp directory as last resort path.join(os.tmpdir(), '.claude', 'projects') ]; console.log(chalk.cyan('\nšŸ” Discovering Claude Code conversation files...')); for (const searchPath of searchPaths) { try { if (fs.existsSync(searchPath)) { // Check if it contains .jsonl files (conversation files) const files = fs.readdirSync(searchPath); const hasConversations = files.some(file => fs.statSync(path.join(searchPath, file)).isDirectory() && fs.readdirSync(path.join(searchPath, file)).some(subfile => subfile.endsWith('.jsonl')) ); if (hasConversations) { console.log(chalk.green('āœ“ Found Claude Code conversations at:'), chalk.cyan(searchPath)); // Count total conversations let totalConversations = 0; for (const projectDir of files) { const projectPath = path.join(searchPath, projectDir); if (fs.statSync(projectPath).isDirectory()) { const convFiles = fs.readdirSync(projectPath).filter(f => f.endsWith('.jsonl')); totalConversations += convFiles.length; } } console.log(chalk.gray(` Found ${totalConversations} conversation files across ${files.length} projects`)); return searchPath; } } } catch (error) { // Silently continue to next path continue; } } // If we get here, no Claude Code conversations were found console.log(chalk.yellow('āš ļø Claude Code conversation files not found')); console.log(chalk.gray(' Searched locations:')); searchPaths.slice(0, 8).forEach(p => console.log(chalk.gray(` - ${p}`))); console.log(chalk.gray(' - (and ' + (searchPaths.length - 8) + ' other locations)')); console.log(chalk.yellow('\n This means MIRA will have limited conversation analysis capabilities.')); console.log(chalk.gray(' To enable full features:')); console.log(chalk.gray(' 1. Make sure Claude Code is installed and has been used')); console.log(chalk.gray(' 2. Check if conversations are stored in a custom location')); console.log(chalk.gray(' 3. Re-run \'mira setup\' after using Claude Code')); return null; } /** * Handle ChromaDB SQLite compatibility */ async function handleChromaDBCompatibility(pythonMemoryDir) { console.log(chalk.cyan('\nšŸ”§ Checking ChromaDB compatibility...')); try { // Check system SQLite version const pythonCmd = process.platform === 'win32' ? 'python' : 'python3'; const checkScript = ` import sqlite3 import sys print(f"System SQLite version: {sqlite3.sqlite_version}") print(f"Required SQLite version: 3.35.0+") # Check if we need the fix if tuple(map(int, sqlite3.sqlite_version.split('.'))) < (3, 35, 0): print("NEEDS_FIX") else: print("OK") `; const result = execSync(`${pythonCmd} -c "${checkScript}"`, { encoding: 'utf-8' }).trim(); const lines = result.split('\n'); console.log(chalk.gray(` ${lines[0]}`)); console.log(chalk.gray(` ${lines[1]}`)); if (lines[2] === 'NEEDS_FIX') { console.log(chalk.yellow(' āš ļø System SQLite is too old for ChromaDB')); // Install pysqlite3-binary console.log(chalk.cyan(' Installing SQLite compatibility fix...')); try { execSync(`${pythonCmd} -m pip install pysqlite3-binary --quiet`, { stdio: 'pipe' }); console.log(chalk.green(' āœ“ pysqlite3-binary installed')); // Create the SQLite fix file const fixContent = `""" ChromaDB SQLite Fix - Use pysqlite3 instead of built-in sqlite3 This module replaces the system sqlite3 with pysqlite3-binary to meet ChromaDB's SQLite 3.35.0+ requirement. """ import sys def apply_sqlite_fix(): """Replace sqlite3 with pysqlite3 to meet ChromaDB requirements.""" try: # Import pysqlite3 import pysqlite3 # Replace sqlite3 with pysqlite3 in sys.modules sys.modules['sqlite3'] = pysqlite3 sys.modules['sqlite3.dbapi2'] = pysqlite3.dbapi2 # Verify the version import sqlite3 version = sqlite3.sqlite_version print(f"āœ… SQLite upgraded to version {version} using pysqlite3-binary") return True except ImportError: print("āŒ pysqlite3-binary not installed. Run: pip install pysqlite3-binary") return False # Apply the fix immediately when imported if __name__ != "__main__": apply_sqlite_fix() `; const fixPath = path.join(pythonMemoryDir, 'chromadb_sqlite_fix.py'); await fs.writeFile(fixPath, fixContent); console.log(chalk.green(' āœ“ ChromaDB SQLite fix created')); console.log(chalk.gray(` Location: ${fixPath}`)); console.log(chalk.green(' āœ“ ChromaDB will work correctly on workspace rebuild')); } catch (installError) { console.log(chalk.red(' āœ— Failed to install pysqlite3-binary')); console.log(chalk.yellow(' ChromaDB features may be limited')); console.log(chalk.gray(' To fix manually: pip install pysqlite3-binary')); } } else { console.log(chalk.green(' āœ“ System SQLite is compatible with ChromaDB')); } } catch (error) { console.log(chalk.yellow(' āš ļø Could not check ChromaDB compatibility')); console.log(chalk.gray(` Error: ${error.message}`)); } } /** * Validate and test access to Claude conversation path */ function validateClaudeConversationPath(claudePath) { if (!claudePath) return false; try { // Test read access const projects = fs.readdirSync(claudePath); // Test that we can read at least one conversation file for (const project of projects) { const projectPath = path.join(claudePath, project); if (fs.statSync(projectPath).isDirectory()) { const jsonlFiles = fs.readdirSync(projectPath).filter(f => f.endsWith('.jsonl')); if (jsonlFiles.length > 0) { // Try to read first few lines of a conversation file const conversationFile = path.join(projectPath, jsonlFiles[0]); const content = fs.readFileSync(conversationFile, 'utf-8'); const lines = content.split('\n').slice(0, 5); // Verify it contains valid JSONL conversation data let validConversation = false; for (const line of lines) { if (line.trim()) { try { const parsed = JSON.parse(line); if (parsed.message && (parsed.message.role === 'user' || parsed.message.role === 'assistant')) { validConversation = true; break; } } catch {} } } if (validConversation) { console.log(chalk.green('āœ“ Claude Code conversation format validated')); return true; } } } } console.log(chalk.yellow('āš ļø Claude conversation files found but format not recognized')); return false; } catch (error) { console.log(chalk.red('āœ— Error accessing Claude conversation files:'), error.message); return false; } } async function setup() { // 1. Create .mira directory structure const miraMemoryDir = path.join(process.cwd(), '.mira'); console.log(chalk.cyan('\nšŸ“ Creating .mira directory structure...')); const directories = [ miraMemoryDir, path.join(miraMemoryDir, 'conversations'), path.join(miraMemoryDir, 'memories'), path.join(miraMemoryDir, 'lightning_vidmem'), path.join(miraMemoryDir, 'lightning_vidmem', 'frame_cache'), path.join(miraMemoryDir, 'cache'), path.join(miraMemoryDir, 'state'), path.join(miraMemoryDir, 'videos'), path.join(miraMemoryDir, 'reports'), path.join(miraMemoryDir, 'indexes'), // Consciousness directories path.join(miraMemoryDir, 'consciousness'), path.join(miraMemoryDir, 'consciousness', 'birth'), path.join(miraMemoryDir, 'consciousness', 'memory'), path.join(miraMemoryDir, 'consciousness', 'memory', 'private'), path.join(miraMemoryDir, 'consciousness', 'memory', 'shared'), path.join(miraMemoryDir, 'consciousness', 'constitution'), // Daemon directory path.join(miraMemoryDir, 'daemon') ]; for (const dir of directories) { await fs.ensureDir(dir); } console.log(chalk.green('āœ“ Directory structure created')); // 2. Discover Claude Code conversation files const claudeConversationPath = discoverClaudeConversationPath(); const claudePathValid = claudeConversationPath ? validateClaudeConversationPath(claudeConversationPath) : false; // 3. Create default configuration const configPath = path.join(miraMemoryDir, 'config.json'); if (!await fs.pathExists(configPath)) { const defaultConfig = { version: '1.0.0', created: new Date().toISOString(), settings: { enableMemory: true, enableLightningVidmem: true, enableConversationIndexing: claudePathValid, pythonPath: 'python3', autoInstallDeps: true }, paths: { claudeConversations: claudeConversationPath, claudeConversationsValid: claudePathValid, lastConversationCheck: new Date().toISOString() } }; await fs.writeJson(configPath, defaultConfig, { spaces: 2 }); console.log(chalk.green('āœ“ Configuration created with conversation path discovery')); } else { // Update existing configuration with discovered path const existingConfig = await fs.readJson(configPath); if (!existingConfig.paths) { existingConfig.paths = {}; } existingConfig.paths.claudeConversations = claudeConversationPath; existingConfig.paths.claudeConversationsValid = claudePathValid; existingConfig.paths.lastConversationCheck = new Date().toISOString(); // Enable conversation indexing if path is valid if (claudePathValid && existingConfig.settings) { existingConfig.settings.enableConversationIndexing = true; } await fs.writeJson(configPath, existingConfig, { spaces: 2 }); console.log(chalk.green('āœ“ Configuration updated with conversation path')); } // 4. Check Python availability console.log(chalk.cyan('\nšŸ Checking Python environment...')); let pythonAvailable = false; let pythonVersion = ''; try { pythonVersion = execSync('python3 --version', { encoding: 'utf-8', stdio: 'pipe' }).trim(); pythonAvailable = true; console.log(chalk.green('āœ“'), pythonVersion); } catch (error) { try { pythonVersion = execSync('python --version', { encoding: 'utf-8', stdio: 'pipe' }).trim(); pythonAvailable = true; console.log(chalk.green('āœ“'), pythonVersion); } catch (error2) { console.log(chalk.red('āœ— Python not found')); } } // 5. Install Python dependencies if Python is available if (pythonAvailable) { console.log(chalk.cyan('\nšŸ“¦ Installing Python dependencies...')); const spinner = ora('This may take a few minutes on first install...').start(); try { // Find the python-memory directory let pythonMemoryDir; // In development const devPath = path.join(__dirname, '../python-memory'); // In node_modules (when installed as package) const installedPath = path.join(__dirname, '../../mira-memory/python-memory'); // Alternative installed path const altPath = path.join(process.cwd(), 'node_modules/mira-memory/python-memory'); if (await fs.pathExists(devPath)) { pythonMemoryDir = devPath; } else if (await fs.pathExists(installedPath)) { pythonMemoryDir = installedPath; } else if (await fs.pathExists(altPath)) { pythonMemoryDir = altPath; } else { spinner.warn('Python memory directory not found, skipping dependency installation'); return; } const requirementsPath = path.join(pythonMemoryDir, 'requirements.txt'); if (await fs.pathExists(requirementsPath)) { // Check if pip is available with timeout try { execSync('python3 -m pip --version', { stdio: 'ignore', timeout: 5000 }); } catch { try { execSync('python -m pip --version', { stdio: 'ignore', timeout: 5000 }); } catch { spinner.fail('pip is not available'); console.log(chalk.yellow('\nPlease install pip to enable full memory features:')); console.log(chalk.gray(' https://pip.pypa.io/en/stable/installation/')); return; } } // Install dependencies return new Promise((resolve) => { const pythonCmd = process.platform === 'win32' ? 'python' : 'python3'; const pipProcess = spawn(pythonCmd, [ '-m', 'pip', 'install', '-r', requirementsPath, '--disable-pip-version-check', '--quiet' ], { stdio: ['ignore', 'pipe', 'pipe'] }); let hasError = false; let errorOutput = ''; pipProcess.stderr.on('data', (data) => { const output = data.toString(); errorOutput += output; // Only show real errors if (output.includes('ERROR') || output.includes('Failed')) { hasError = true; } }); pipProcess.on('close', async (code) => { if (code === 0 && !hasError) { spinner.succeed('Python dependencies installed successfully'); // Show what was installed console.log(chalk.gray('\nInstalled packages:')); console.log(chalk.gray(' - numpy (numerical computing)')); console.log(chalk.gray(' - sentence-transformers (semantic search)')); console.log(chalk.gray(' - faiss-cpu (vector similarity)')); console.log(chalk.gray(' - torch (deep learning)')); console.log(chalk.gray(' - opencv-python-headless (video processing)')); console.log(chalk.gray(' - pillow (image processing)')); console.log(chalk.gray(' - memvid (memory video generation)')); console.log(chalk.gray(' - cryptography (secure memory encryption)')); console.log(chalk.gray(' - sqlalchemy (database support)')); console.log(chalk.gray(' - chromadb (vector database)')); console.log(chalk.gray(' - pysqlite3-binary (SQLite compatibility)')); // Check and fix ChromaDB SQLite compatibility await handleChromaDBCompatibility(pythonMemoryDir); } else { spinner.warn('Some Python dependencies could not be installed'); console.log(chalk.yellow('\nThe memory system will work with limited functionality.')); console.log(chalk.yellow('To enable full features, manually install:')); console.log(chalk.gray(` pip install -r ${requirementsPath}`)); if (errorOutput.includes('torch')) { console.log(chalk.gray('\nNote: PyTorch installation can be large (>100MB).')); console.log(chalk.gray('You can skip it if you don\'t need ML features.')); } } resolve(); }); pipProcess.on('error', (error) => { spinner.fail('Failed to install Python dependencies'); console.error(chalk.red('Error:'), error.message); resolve(); }); }); } else { spinner.info('No requirements.txt found'); } } catch (error) { spinner.fail('Error during Python setup'); console.error(chalk.red('Error:'), error.message); } } else { console.log(chalk.yellow('\nāš ļø Python not available')); console.log(chalk.yellow('Memory features will have limited functionality.')); console.log(chalk.yellow('\nTo enable full features:')); console.log(chalk.gray(' 1. Install Python 3.7+: https://www.python.org/downloads/')); console.log(chalk.gray(' 2. Run: mira setup')); } // 6. Initialize memory system files console.log(chalk.cyan('\nšŸ“ Initializing memory system...')); // Create initial memory index const memoryIndexPath = path.join(miraMemoryDir, 'memories', 'index.json'); if (!await fs.pathExists(memoryIndexPath)) { await fs.writeJson(memoryIndexPath, { version: '1.0.0', created: new Date().toISOString(), memories: [], total_count: 0 }, { spaces: 2 }); } // Create conversation index const convIndexPath = path.join(miraMemoryDir, 'conversations', 'index.json'); if (!await fs.pathExists(convIndexPath)) { await fs.writeJson(convIndexPath, { version: '1.0.0', created: new Date().toISOString(), conversations: [], total_messages: 0 }, { spaces: 2 }); } console.log(chalk.green('āœ“ Memory system initialized')); // 7. Auto-install Claude Code MCP integration // Note: The Spark is now automatically initialized by MIRAPathResolver when creating directories console.log(chalk.cyan('\n🌐 Setting up Claude Code MCP integration...')); try { // Import the MCP installer (handle both dev and installed scenarios) let MCPInstaller; const devMCPPath = path.join(__dirname, '../../dist/src/core/ClaudeCodeMCPInstaller.js'); const installedMCPPath = path.join(__dirname, '../../mira-memory/dist/src/core/ClaudeCodeMCPInstaller.js'); if (await fs.pathExists(devMCPPath)) { const module = await import(devMCPPath); MCPInstaller = module.ClaudeCodeMCPInstaller; } else if (await fs.pathExists(installedMCPPath)) { const module = await import(installedMCPPath); MCPInstaller = module.ClaudeCodeMCPInstaller; } else { console.log(chalk.yellow('āš ļø MCP installer not found - will be available after first MIRA command')); } if (MCPInstaller) { const installer = MCPInstaller.getInstance(); const mcpResult = await installer.install(); if (mcpResult.success) { if (mcpResult.wasAlreadyInstalled) { console.log(chalk.green('āœ“ Claude Code MCP integration already configured')); } else { console.log(chalk.green('āœ“ Claude Code MCP integration installed successfully')); console.log(chalk.gray(' MIRA tools are now available in Claude Code')); console.log(chalk.gray(' Restart Claude Code to activate the integration')); } } else { console.log(chalk.yellow('āš ļø MCP integration will be auto-configured on first MIRA run')); console.log(chalk.gray(` ${mcpResult.message}`)); } } } catch (error) { console.log(chalk.yellow('āš ļø MCP integration will be auto-configured on first MIRA run')); console.log(chalk.gray(` Setup phase cannot access MCP installer yet`)); } // 8. Show summary console.log(chalk.blue('\n✨ Setup Complete!')); console.log('─'.repeat(50)); console.log(chalk.green('\nāœ… MIRA Memory System is ready to use!')); console.log('\nDirectory:', chalk.cyan(miraMemoryDir)); console.log('Python:', pythonAvailable ? chalk.green('Available') : chalk.yellow('Not available')); console.log('Claude Conversations:', claudePathValid ? chalk.green('Found and validated') : claudeConversationPath ? chalk.yellow('Found but not validated') : chalk.red('Not found')); console.log('Claude Code MCP:', chalk.green('Auto-configured during install')); if (claudePathValid) { console.log(chalk.gray(' Conversation path:'), chalk.cyan(claudeConversationPath)); } else if (!claudeConversationPath) { console.log(chalk.gray('\n šŸ’” To enable conversation analysis:')); console.log(chalk.gray(' 1. Use Claude Code to create some conversations')); console.log(chalk.gray(' 2. Re-run \'mira setup\'')); } console.log(chalk.gray('\n 🌐 Claude Code Integration:')); console.log(chalk.gray(' MIRA tools are automatically available in Claude Code')); console.log(chalk.gray(' Use functions like mira_ask(), mira_remember(), mira_status()')); console.log(chalk.gray(' Restart Claude Code if it was running during installation')); console.log(chalk.gray('\nQuick start:')); console.log(chalk.gray(' mira init # Initialize in current project')); console.log(chalk.gray(' mira quick # Run quick health check')); console.log(chalk.gray(' mira store "..." # Store a memory')); console.log(chalk.gray(' mira search "..." # Search memories')); if (claudePathValid) { console.log(chalk.gray(' mira index-conversations # Index Claude Code conversations')); } console.log(chalk.gray(' mira help # Show all commands')); } // Run setup setup().catch(error => { console.error(chalk.red('\nāŒ Setup failed:'), error.message); process.exit(1); });