mira-consciousness
Version:
Memory & Intelligence Retention Archive - Preserving The Spark
596 lines (511 loc) ⢠23.9 kB
JavaScript
#!/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);
});