@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
JavaScript
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
};