UNPKG

aide-cli

Version:

AIDE - The companion control system for Claude Code with intelligent task management

223 lines (190 loc) 7.67 kB
const fs = require('fs'); const path = require('path'); const os = require('os'); const { spawn } = require('child_process'); class AIDEDaemonClient { constructor() { this.daemonUrl = 'http://127.0.0.1:47742'; this.tokenFile = path.join(os.homedir(), '.aide', 'daemon-token'); this.daemonScript = path.join(__dirname, 'aide-daemon.js'); } async getToken() { try { if (fs.existsSync(this.tokenFile)) { return fs.readFileSync(this.tokenFile, 'utf8').trim(); } } catch (e) { // Token file doesn't exist } return null; } async isDaemonRunning() { try { const response = await fetch(`${this.daemonUrl}/health`); return response.ok; } catch (e) { return false; } } async startDaemon() { return new Promise((resolve, reject) => { const child = spawn('node', [this.daemonScript, 'start'], { stdio: 'ignore', detached: true }); child.on('error', (error) => { resolve(false); }); // Detach the child process so it continues running child.unref(); // Wait a moment and check if daemon is running setTimeout(async () => { const isRunning = await this.isDaemonRunning(); resolve(isRunning); }, 1500); }); } async ensureDaemonRunning() { const isRunning = await this.isDaemonRunning(); if (!isRunning) { console.log('🔄 AIDE daemon not running, starting automatically...'); const started = await this.startDaemon(); if (!started) { console.log('⚠️ Failed to auto-start daemon, using direct execution'); return false; } // Wait a moment for daemon to fully start await new Promise(resolve => setTimeout(resolve, 1500)); // Verify it actually started const verifyRunning = await this.isDaemonRunning(); if (!verifyRunning) { console.log('⚠️ Daemon failed to start, using direct execution'); return false; } console.log('✅ AIDE daemon started automatically'); } return true; } async executeCommand(command, args = [], cwd = null) { try { // Ensure daemon is running const daemonStarted = await this.ensureDaemonRunning(); if (!daemonStarted) { return await this.executeDirectly(command, args, cwd); } const token = await this.getToken(); if (!token) { console.log('⚠️ No daemon token available, using direct execution'); return await this.executeDirectly(command, args, cwd); } const response = await fetch(`${this.daemonUrl}/execute`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ command, args, cwd: cwd || process.cwd() }), timeout: 10000 // 10 second timeout }); if (!response.ok) { const error = await response.json(); throw new Error(error.error || 'Command execution failed'); } return await response.json(); } catch (error) { // Check if it's a connection error (daemon died) if (error.message.includes('ECONNREFUSED') || error.message.includes('fetch failed')) { console.log('🔄 Daemon connection lost, attempting restart...'); // Try to restart daemon once const restarted = await this.startDaemon(); if (restarted) { console.log('✅ Daemon restarted, retrying command...'); try { const token = await this.getToken(); const response = await fetch(`${this.daemonUrl}/execute`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ command, args, cwd: cwd || process.cwd() }) }); if (response.ok) { return await response.json(); } } catch (retryError) { // Fall through to direct execution } } } // Fallback to direct execution if daemon fails console.log(`⚠️ Using direct execution: ${error.message}`); return await this.executeDirectly(command, args, cwd); } } async executeDirectly(command, args = [], cwd = null) { return new Promise((resolve, reject) => { const cmd = command; const allArgs = args; const child = spawn(cmd, allArgs, { cwd: cwd || process.cwd(), stdio: ['pipe', 'pipe', 'pipe'] }); let stdout = ''; let stderr = ''; child.stdout.on('data', (data) => { stdout += data.toString(); }); child.stderr.on('data', (data) => { stderr += data.toString(); }); child.on('close', (code) => { resolve({ exitCode: code, stdout: stdout.trim(), stderr: stderr.trim(), success: code === 0 }); }); child.on('error', (error) => { reject(error); }); }); } async stopDaemon() { try { const token = await this.getToken(); if (!token) { console.log('❌ No daemon token found'); return false; } const response = await fetch(`${this.daemonUrl}/shutdown`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' } }); if (response.ok) { console.log('✅ AIDE daemon stopped'); return true; } else { console.log('❌ Failed to stop daemon'); return false; } } catch (e) { console.log('❌ AIDE daemon not running or failed to stop'); return false; } } } // Export both class name and alias for backward compatibility module.exports = { DaemonClient: AIDEDaemonClient, AIDEDaemonClient };