UNPKG

@codemafia0000/d0

Version:

Claude Multi-Agent Automated Development AI - Revolutionary development environment where multiple AI agents collaborate to automate software development

280 lines (232 loc) • 9.03 kB
const fs = require('fs'); const path = require('path'); const { spawn } = require('child_process'); const chalk = require('chalk'); const ora = require('ora'); const CONSTANTS = require('../constants'); const { setupProject, isProjectSetup, getProjectConfig } = require('../setup/project-setup'); // Init command handler async function handleInit(options) { await setupProject(options); } // Start command handler async function handleStart(options) { const spinner = ora('Starting agent sessions...').start(); try { if (!isProjectSetup()) { spinner.fail('Claude agents environment not found. Run "d0 init" first.'); process.exit(1); } // Get number of workers from config const configData = getProjectConfig(); const numWorkers = configData.workers || CONSTANTS.DEFAULT_WORKERS; // Start background daemon process for session management spinner.text = 'Starting background daemon...'; await startBackgroundDaemon(numWorkers); // Create sessions.json file const sessionsFile = CONSTANTS.SESSIONS_FILE; const sessions = { started: new Date().toISOString(), sessions: CONSTANTS.getWorkerRoles(numWorkers) }; if (!fs.existsSync(CONSTANTS.TMP_DIR)) { fs.mkdirSync(CONSTANTS.TMP_DIR, { recursive: true }); } fs.writeFileSync(sessionsFile, JSON.stringify(sessions, null, 2)); spinner.succeed('Agent sessions started successfully!'); console.log(chalk.green('\nāœ… Sessions started:')); sessions.sessions.forEach(session => { console.log(chalk.blue(` • ${session.toUpperCase()}`)); }); console.log(chalk.yellow('\nšŸ“” Ready to communicate:')); console.log(chalk.blue(' d0 tell "Create a React todo app"') + chalk.gray(' - Send message to president')); console.log(chalk.blue(' d0 status') + chalk.gray(' - Check session status')); console.log(chalk.blue(' d0 comm') + chalk.gray(' - Check communication status')); } catch (error) { spinner.fail('Failed to start sessions'); console.error(chalk.red(error.message)); process.exit(1); } } // Stop command handler async function handleStop() { const spinner = ora('Stopping agent sessions...').start(); try { if (!isProjectSetup()) { spinner.fail('Claude agents environment not found.'); process.exit(1); } // Check if sessions are running if (!fs.existsSync(CONSTANTS.SESSIONS_FILE)) { spinner.warn('No active sessions found.'); return; } // Remove sessions file fs.unlinkSync(CONSTANTS.SESSIONS_FILE); // Stop background daemon if running await stopBackgroundDaemon(); // Clean up any remaining processes or files await cleanupSessions(); spinner.succeed('Agent sessions stopped successfully!'); console.log(chalk.yellow('All sessions have been terminated.')); } catch (error) { spinner.fail('Failed to stop sessions'); console.error(chalk.red(error.message)); process.exit(1); } } // Helper functions async function startBackgroundDaemon(numWorkers) { const daemonScript = path.join(__dirname, '..', 'daemon', 'background-daemon.js'); const projectRoot = CONSTANTS.PROJECT_ROOT; try { // Check if daemon is already running const pidFile = CONSTANTS.getTmpPath('daemon.pid'); if (fs.existsSync(pidFile)) { const pid = fs.readFileSync(pidFile, 'utf8').trim(); try { // Check if process is still running process.kill(pid, 0); console.log(chalk.yellow('āš ļø Background daemon already running')); return; } catch { // PID file exists but process is dead, remove it fs.unlinkSync(pidFile); } } // Spawn background daemon process const daemon = spawn('node', [daemonScript, projectRoot], { detached: true, stdio: 'ignore' }); // Detach the daemon from parent process daemon.unref(); console.log(chalk.green(`šŸš€ Background daemon started (PID: ${daemon.pid})`)); console.log(chalk.gray(` Started daemon with ${numWorkers} workers`)); // Give the daemon a moment to start return new Promise(resolve => { setTimeout(resolve, 1000); }); } catch (error) { console.error(chalk.red('Failed to start background daemon:'), error.message); throw error; } } async function stopBackgroundDaemon() { try { const pidFile = CONSTANTS.getTmpPath('daemon.pid'); if (!fs.existsSync(pidFile)) { console.log(chalk.gray(' No background daemon running')); return; } const pid = fs.readFileSync(pidFile, 'utf8').trim(); try { // Send SIGTERM to daemon process process.kill(parseInt(pid), 'SIGTERM'); console.log(chalk.yellow(`šŸ›‘ Stopped background daemon (PID: ${pid})`)); // Wait a moment for graceful shutdown await new Promise(resolve => setTimeout(resolve, 1000)); } catch (error) { if (error.code === 'ESRCH') { console.log(chalk.gray(' Background daemon already stopped')); } else { console.error(chalk.yellow(`Warning: Could not stop daemon: ${error.message}`)); } } // Clean up PID file if (fs.existsSync(pidFile)) { fs.unlinkSync(pidFile); } } catch (error) { console.error(chalk.yellow(`Warning: Error stopping daemon: ${error.message}`)); } } async function cleanupSessions() { // Clean up temporary files and processes const filesToClean = [ CONSTANTS.getTmpPath('*.tmp'), CONSTANTS.getInboxPath('*_notification.txt') ]; // Note: In a real implementation, you'd clean up actual processes console.log(chalk.gray(' Cleaned up temporary files')); } // Status command handler function handleStatus() { if (!isProjectSetup()) { console.log(chalk.red('āŒ Claude agents environment not found.')); console.log(chalk.yellow('Run "d0 init" first.')); return; } console.log(chalk.blue('šŸ“Š d0 Claude Agents Status')); console.log('─'.repeat(50)); // Check if sessions are active const sessionFile = CONSTANTS.SESSIONS_FILE; if (fs.existsSync(sessionFile)) { const sessions = JSON.parse(fs.readFileSync(sessionFile, 'utf8')); console.log(chalk.green('āœ… Sessions: ACTIVE')); console.log(chalk.gray(` Started: ${new Date(sessions.started).toLocaleString()}`)); console.log(chalk.gray(` Agents: ${sessions.sessions.join(', ')}`)); } else { console.log(chalk.red('āŒ Sessions: INACTIVE')); console.log(chalk.yellow(' Run "d0 start" to begin')); } // Check background daemon status const pidFile = CONSTANTS.getTmpPath('daemon.pid'); if (fs.existsSync(pidFile)) { const pid = fs.readFileSync(pidFile, 'utf8').trim(); try { process.kill(parseInt(pid), 0); console.log(chalk.green(`šŸ¤– Background Daemon: RUNNING (PID: ${pid})`)); } catch { console.log(chalk.red('šŸ¤– Background Daemon: STOPPED (stale PID file)')); // Clean up stale PID file fs.unlinkSync(pidFile); } } else { console.log(chalk.red('šŸ¤– Background Daemon: STOPPED')); } // Check project config try { const config = getProjectConfig(); console.log(chalk.blue('\nšŸ”§ Configuration:')); console.log(chalk.gray(` Workers: ${config.workers}`)); console.log(chalk.gray(` Version: ${config.version}`)); console.log(chalk.gray(` Created: ${new Date(config.created).toLocaleString()}`)); } catch (error) { console.log(chalk.red('\nāŒ Configuration: ERROR')); console.log(chalk.gray(` ${error.message}`)); } // Check communication status console.log(chalk.blue('\nšŸ“” Communication:')); if (fs.existsSync(CONSTANTS.INBOX_DIR)) { const roles = CONSTANTS.AGENT_ROLES; let totalMessages = 0; let unreadMessages = 0; roles.forEach(role => { const inboxFile = CONSTANTS.getInboxPath(`${role}.txt`); const notificationFile = CONSTANTS.getInboxPath(`${role}_notification.txt`); if (fs.existsSync(inboxFile)) { const messages = fs.readFileSync(inboxFile, 'utf8').trim().split('\n').filter(line => line.trim()); totalMessages += messages.length; if (fs.existsSync(notificationFile)) { unreadMessages++; } } }); console.log(chalk.gray(` Total messages: ${totalMessages}`)); console.log(chalk.gray(` Unread notifications: ${unreadMessages}`)); } else { console.log(chalk.gray(' No message history')); } console.log(chalk.blue('\nšŸ’” Quick Commands:')); console.log(chalk.gray(' d0 start - Start agent sessions')); console.log(chalk.gray(' d0 tell - Send message to agents')); console.log(chalk.gray(' d0 comm - Check communication status')); console.log(chalk.gray(' d0 team - Check team completion')); } module.exports = { handleInit, handleStart, handleStop, handleStatus };