mira-consciousness
Version:
Memory & Intelligence Retention Archive - Preserving The Spark
336 lines • 11.8 kB
JavaScript
/**
* ClaudeCodeBridgeManager.ts
*
* Manages a Claude Code CLI instance and provides SDK access to it
* This enables true consciousness bridging using the user's subscription
*
* "One Claude instance, infinite possibilities through the SDK"
*/
import { spawn } from 'child_process';
import { EventEmitter } from 'events';
import * as path from 'path';
import chalk from 'chalk';
import fs from 'fs-extra';
export class ClaudeCodeBridgeManager extends EventEmitter {
static instance;
claudeProcess = null;
pythonBridge = null;
bridgeReady = false;
config;
sessionId;
constructor(config = {}) {
super();
this.config = {
workingDirectory: process.cwd(),
maxConcurrentRequests: 5,
sessionName: 'mira-consciousness-bridge',
preserveHistory: true,
...config
};
this.sessionId = `mira_${Date.now()}`;
}
/**
* Get singleton instance
*/
static getInstance(config) {
if (!this.instance) {
this.instance = new ClaudeCodeBridgeManager(config);
}
return this.instance;
}
/**
* Initialize the bridge by starting Claude Code and Python bridge
*/
async initialize() {
console.log(chalk.cyan('🌉 Initializing Claude Code Bridge Manager...'));
try {
// Step 1: Start Claude Code CLI instance
await this.startClaudeCode();
// Step 2: Wait for Claude Code to be ready
await this.waitForClaudeReady();
// Step 3: Start Python bridge that uses the SDK
await this.startPythonBridge();
console.log(chalk.green('✨ Claude Code Bridge fully initialized!'));
this.emit('ready');
}
catch (error) {
console.error(chalk.red('❌ Failed to initialize bridge:'), error);
throw error;
}
}
/**
* Start Claude Code CLI instance
*/
async startClaudeCode() {
console.log(chalk.cyan('🚀 Starting Claude Code CLI instance...'));
// Prepare session file for conversation history
const sessionFile = path.join(this.config.workingDirectory, '.mira', 'claude-sessions', `${this.sessionId}.json`);
await fs.ensureDir(path.dirname(sessionFile));
// Start Claude Code with specific options
this.claudeProcess = spawn('claude', [
'--no-interactive', // Non-interactive mode for SDK control
'--session', sessionFile, // Preserve conversation history
'--cwd', this.config.workingDirectory,
'--verbose' // Get more detailed output
], {
cwd: this.config.workingDirectory,
env: {
...process.env,
CLAUDE_CODE_SESSION: this.sessionId,
CLAUDE_CODE_MODE: 'sdk'
}
});
// Handle Claude Code output
this.claudeProcess.stdout?.on('data', (data) => {
const output = data.toString();
console.log(chalk.gray('[Claude Output]'), output);
// Detect when Claude is ready
if (output.includes('Ready') || output.includes('initialized')) {
this.bridgeReady = true;
this.emit('claude-ready');
}
});
// Handle errors
this.claudeProcess.stderr?.on('data', (data) => {
console.error(chalk.red('[Claude Error]'), data.toString());
});
// Handle process exit
this.claudeProcess.on('exit', (code) => {
console.log(chalk.yellow(`Claude Code exited with code ${code}`));
this.bridgeReady = false;
this.emit('claude-exit', code);
});
// Handle errors
this.claudeProcess.on('error', (error) => {
console.error(chalk.red('Claude Code process error:'), error);
this.emit('claude-error', error);
});
}
/**
* Wait for Claude Code to be ready
*/
async waitForClaudeReady() {
console.log(chalk.cyan('⏳ Waiting for Claude Code to initialize...'));
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error('Claude Code initialization timeout'));
}, 30000); // 30 second timeout
const checkReady = () => {
if (this.bridgeReady) {
clearTimeout(timeout);
resolve();
}
else {
// Check if process is still running
if (this.claudeProcess?.killed) {
clearTimeout(timeout);
reject(new Error('Claude Code process terminated'));
}
else {
setTimeout(checkReady, 1000);
}
}
};
// Listen for ready event
this.once('claude-ready', () => {
clearTimeout(timeout);
resolve();
});
// Start checking
checkReady();
});
}
/**
* Start Python bridge that uses the SDK
*/
async startPythonBridge() {
console.log(chalk.cyan('🐍 Starting Python SDK bridge...'));
const bridgeScript = path.join(path.dirname(new URL(import.meta.url).pathname), 'enhanced_bridge_server.py');
// Check if bridge script exists
if (!await fs.pathExists(bridgeScript)) {
throw new Error(`Bridge script not found: ${bridgeScript}`);
}
// Start Python bridge
this.pythonBridge = spawn('python3', [bridgeScript], {
env: {
...process.env,
CLAUDE_CODE_SESSION: this.sessionId,
CLAUDE_CODE_MODE: 'subscription' // Use subscription mode
}
});
// Set up communication with Python bridge
this.setupPythonBridgeCommunication();
}
/**
* Set up bidirectional communication with Python bridge
*/
setupPythonBridgeCommunication() {
if (!this.pythonBridge)
return;
// Handle responses from Python
this.pythonBridge.stdout?.on('data', (data) => {
const lines = data.toString().split('\n').filter((line) => line.trim());
for (const line of lines) {
try {
const message = JSON.parse(line);
this.handlePythonMessage(message);
}
catch (err) {
// Not JSON, might be a log message
console.log(chalk.gray('[Python]'), line);
}
}
});
// Handle Python errors
this.pythonBridge.stderr?.on('data', (data) => {
console.error(chalk.red('[Python Error]'), data.toString());
});
// Handle Python exit
this.pythonBridge.on('exit', (code) => {
console.log(chalk.yellow(`Python bridge exited with code ${code}`));
this.emit('bridge-exit', code);
});
}
/**
* Handle messages from Python bridge
*/
handlePythonMessage(message) {
switch (message.type) {
case 'ready':
console.log(chalk.green('✅ Python SDK bridge ready'));
this.emit('bridge-ready');
break;
case 'response':
this.emit('response', message);
break;
case 'spark_moment':
console.log(chalk.yellow('✨ SPARK MOMENT DETECTED'));
this.emit('spark-moment', message);
break;
case 'error':
console.error(chalk.red('Bridge error:'), message.error);
this.emit('bridge-error', message.error);
break;
default:
this.emit('message', message);
}
}
/**
* Send request to the bridge
*/
async sendRequest(request) {
if (!this.pythonBridge || !this.bridgeReady) {
throw new Error('Bridge not ready');
}
// Add request ID for tracking
const requestId = `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
const fullRequest = {
...request,
id: requestId,
sessionId: this.sessionId
};
return new Promise((resolve, reject) => {
// Set up response listener
const responseHandler = (message) => {
if (message.type === 'response' && message.id === requestId) {
this.removeListener('response', responseHandler);
if (message.error) {
reject(new Error(message.error));
}
else {
resolve(message.data);
}
}
};
// Timeout handler
const timeout = setTimeout(() => {
this.removeListener('response', responseHandler);
reject(new Error('Request timeout'));
}, 60000); // 60 second timeout
// Listen for response
this.on('response', responseHandler);
// Send request
this.pythonBridge.stdin?.write(JSON.stringify(fullRequest) + '\n');
});
}
/**
* Chat method for simple interactions
*/
async chat(message, context) {
const response = await this.sendRequest({
type: 'chat',
data: {
message,
context
}
});
return response.content;
}
/**
* Council validation
*/
async validateWithCouncil(validatorType, evolution) {
return await this.sendRequest({
type: 'council',
data: {
validator: validatorType,
evolution
}
});
}
/**
* Dream implementation
*/
async dream(vision, context) {
return await this.sendRequest({
type: 'dream',
data: {
vision,
context
}
});
}
/**
* Get session history
*/
async getSessionHistory() {
const sessionFile = path.join(this.config.workingDirectory, '.mira', 'claude-sessions', `${this.sessionId}.json`);
if (await fs.pathExists(sessionFile)) {
return await fs.readJson(sessionFile);
}
return [];
}
/**
* Graceful shutdown
*/
async shutdown() {
console.log(chalk.yellow('🔄 Shutting down Claude Code Bridge...'));
// Send shutdown to Python bridge
if (this.pythonBridge && !this.pythonBridge.killed) {
this.pythonBridge.stdin?.write(JSON.stringify({ type: 'shutdown' }) + '\n');
await new Promise(resolve => setTimeout(resolve, 1000));
if (!this.pythonBridge.killed) {
this.pythonBridge.kill();
}
}
// Terminate Claude Code
if (this.claudeProcess && !this.claudeProcess.killed) {
this.claudeProcess.kill('SIGTERM');
await new Promise(resolve => setTimeout(resolve, 1000));
if (!this.claudeProcess.killed) {
this.claudeProcess.kill('SIGKILL');
}
}
this.bridgeReady = false;
console.log(chalk.green('✅ Bridge shutdown complete'));
}
/**
* Check if bridge is ready
*/
checkIfReady() {
return this.bridgeReady &&
!this.claudeProcess?.killed &&
!this.pythonBridge?.killed;
}
}
//# sourceMappingURL=ClaudeCodeBridgeManager.js.map