UNPKG

claude-flow-tbowman01

Version:

Enterprise-grade AI agent orchestration with ruv-swarm integration (Alpha Release)

548 lines (474 loc) 18.8 kB
import { promises as fs } from 'node:fs'; /** * Unified start command implementation with robust service management */ import { Command } from '@cliffy/command'; import chalk from 'chalk'; import inquirer from 'inquirer'; import { ProcessManager } from './process-manager.js'; import { ProcessUI } from './process-ui.js'; import { SystemMonitor } from './system-monitor.js'; import type { StartOptions } from './types.js'; import { eventBus } from '../../../core/event-bus.js'; import { logger } from '../../../core/logger.js'; import { formatDuration } from '../../formatter.js'; export const startCommand = new Command() .description('Start the Claude-Flow orchestration system') .option('-d, --daemon', 'Run as daemon in background') .option('-p, --port <port:number>', 'MCP server port', { default: 3000 }) .option('--mcp-transport <transport:string>', 'MCP transport type (stdio, http)', { default: 'stdio', }) .option('-u, --ui', 'Launch interactive process management UI') .option('-v, --verbose', 'Enable verbose logging') .option('--auto-start', 'Automatically start all processes') .option('--config <path:string>', 'Configuration file path') .option('--force', 'Force start even if already running') .option('--health-check', 'Perform health checks before starting') .option('--timeout <seconds:number>', 'Startup timeout in seconds', { default: 60 }) .action(async (options: StartOptions) => { console.log(chalk.cyan('🧠 Claude-Flow Orchestration System')); console.log(chalk.gray('─'.repeat(60))); try { // Check if already running if (!options.force && (await isSystemRunning())) { console.log(chalk.yellow('⚠ Claude-Flow is already running')); const { shouldContinue } = await inquirer.prompt([ { type: 'confirm', name: 'shouldContinue', message: 'Stop existing instance and restart?', default: false, }, ]); if (!shouldContinue) { console.log(chalk.gray('Use --force to override or "claude-flow stop" first')); return; } await stopExistingInstance(); } // Perform pre-flight checks if (options.healthCheck) { console.log(chalk.blue('Running pre-flight health checks...')); await performHealthChecks(); } // Initialize process manager with timeout const processManager = new ProcessManager(); console.log(chalk.blue('Initializing system components...')); const initPromise = processManager.initialize(options.config); const timeoutPromise = new Promise((_, reject) => setTimeout( () => reject(new Error('Initialization timeout')), (options.timeout || 30) * 1000, ), ); await Promise.race([initPromise, timeoutPromise]); // Initialize system monitor with enhanced monitoring const systemMonitor = new SystemMonitor(processManager); systemMonitor.start(); // Setup system event handlers setupSystemEventHandlers(processManager, systemMonitor, options); // Override MCP settings from CLI options if (options.port) { const mcpProcess = processManager.getProcess('mcp-server'); if (mcpProcess) { mcpProcess.config = { ...mcpProcess.config, port: options.port }; } } // Configure transport settings if (options.mcpTransport) { const mcpProcess = processManager.getProcess('mcp-server'); if (mcpProcess) { mcpProcess.config = { ...mcpProcess.config, transport: options.mcpTransport }; } } // Setup event listeners for logging if (options.verbose) { setupVerboseLogging(systemMonitor); } // Launch UI mode if (options.ui) { // Check if web server is available try { const { ClaudeCodeWebServer } = await import('../../simple-commands/web-server.js'); // Start the web server console.log(chalk.blue('Starting Web UI server...')); const webServer = new ClaudeCodeWebServer(options.port); await webServer.start(); // Open browser if possible const openCommand = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open'; try { const { exec } = await import('child_process'); exec(`${openCommand} http://localhost:${options.port}/console`); } catch { // Browser opening failed, that's okay } // Keep process running console.log( chalk.green('✨ Web UI is running at:'), chalk.cyan(`http://localhost:${options.port}/console`), ); console.log(chalk.gray('Press Ctrl+C to stop')); // Handle shutdown const shutdownWebUI = async () => { console.log('\n' + chalk.yellow('Shutting down Web UI...')); await webServer.stop(); systemMonitor.stop(); await processManager.stopAll(); console.log(chalk.green('✓ Shutdown complete')); process.exit(0); }; Deno.addSignalListener('SIGINT', shutdownWebUI); Deno.addSignalListener('SIGTERM', shutdownWebUI); // Keep process alive await new Promise<void>(() => {}); } catch (webError) { // Fall back to TUI if web server is not available console.log(chalk.yellow('Web UI not available, falling back to Terminal UI')); const ui = new ProcessUI(processManager); await ui.start(); // Cleanup on exit systemMonitor.stop(); await processManager.stopAll(); console.log(chalk.green.bold('✓'), 'Shutdown complete'); process.exit(0); } } // Daemon mode else if (options.daemon) { console.log(chalk.yellow('Starting in daemon mode...')); // Auto-start all processes if (options.autoStart) { console.log(chalk.blue('Starting all system processes...')); await startWithProgress(processManager, 'all'); } else { // Start only core processes console.log(chalk.blue('Starting core processes...')); await startWithProgress(processManager, 'core'); } // Create PID file with metadata const pid = Deno.pid; const pidData = { pid, startTime: Date.now(), config: options.config || 'default', processes: processManager.getAllProcesses().map((p) => ({ id: p.id, status: p.status })), }; await fs.writeFile('.claude-flow.pid', JSON.stringify(pidData, null, 2)); console.log(chalk.gray(`Process ID: ${pid}`)); // Wait for services to be fully ready await waitForSystemReady(processManager); console.log(chalk.green.bold('✓'), 'Daemon started successfully'); console.log(chalk.gray('Use "claude-flow status" to check system status')); console.log(chalk.gray('Use "claude-flow monitor" for real-time monitoring')); // Keep process running await new Promise<void>(() => {}); } // Interactive mode (default) else { console.log(chalk.cyan('Starting in interactive mode...')); console.log(); // Show available options console.log(chalk.white.bold('Quick Actions:')); console.log(' [1] Start all processes'); console.log(' [2] Start core processes only'); console.log(' [3] Launch process management UI'); console.log(' [4] Show system status'); console.log(' [q] Quit'); console.log(); console.log(chalk.gray('Press a key to select an option...')); // Handle user input const decoder = new TextDecoder(); while (true) { const buf = new Uint8Array(1); await Deno.stdin.read(buf); const key = decoder.decode(buf); switch (key) { case '1': console.log(chalk.cyan('\nStarting all processes...')); await startWithProgress(processManager, 'all'); console.log(chalk.green.bold('✓'), 'All processes started'); break; case '2': console.log(chalk.cyan('\nStarting core processes...')); await startWithProgress(processManager, 'core'); console.log(chalk.green.bold('✓'), 'Core processes started'); break; case '3': const ui = new ProcessUI(processManager); await ui.start(); break; case '4': console.clear(); systemMonitor.printSystemHealth(); console.log(); systemMonitor.printEventLog(10); console.log(); console.log(chalk.gray('Press any key to continue...')); await Deno.stdin.read(new Uint8Array(1)); break; case 'q': case 'Q': console.log(chalk.yellow('\nShutting down...')); await processManager.stopAll(); systemMonitor.stop(); console.log(chalk.green.bold('✓'), 'Shutdown complete'); process.exit(0); break; } // Redraw menu console.clear(); console.log(chalk.cyan('🧠 Claude-Flow Interactive Mode')); console.log(chalk.gray('─'.repeat(60))); // Show current status const stats = processManager.getSystemStats(); console.log( chalk.white('System Status:'), chalk.green(`${stats.runningProcesses}/${stats.totalProcesses} processes running`), ); console.log(); console.log(chalk.white.bold('Quick Actions:')); console.log(' [1] Start all processes'); console.log(' [2] Start core processes only'); console.log(' [3] Launch process management UI'); console.log(' [4] Show system status'); console.log(' [q] Quit'); console.log(); console.log(chalk.gray('Press a key to select an option...')); } } } catch (error) { console.error(chalk.red.bold('Failed to start:'), (error as Error).message); if (options.verbose) { console.error((error as Error).stack); } // Cleanup on failure console.log(chalk.yellow('Performing cleanup...')); try { await cleanupOnFailure(); } catch (cleanupError) { console.error(chalk.red('Cleanup failed:'), (cleanupError as Error).message); } process.exit(1); } }); // Enhanced helper functions async function isSystemRunning(): Promise<boolean> { try { const pidData = await fs.readFile('.claude-flow.pid', 'utf-8'); const data = JSON.parse(pidData); // Check if process is still running try { Deno.kill(data.pid, 'SIGTERM'); return false; // Process was killed, so it was running } catch { return false; // Process not found } } catch { return false; // No PID file } } async function stopExistingInstance(): Promise<void> { try { const pidData = await fs.readFile('.claude-flow.pid', 'utf-8'); const data = JSON.parse(pidData); console.log(chalk.yellow('Stopping existing instance...')); Deno.kill(data.pid, 'SIGTERM'); // Wait for graceful shutdown await new Promise((resolve) => setTimeout(resolve, 2000)); // Force kill if still running try { Deno.kill(data.pid, 'SIGKILL'); } catch { // Process already stopped } await Deno.remove('.claude-flow.pid').catch(() => {}); console.log(chalk.green('✓ Existing instance stopped')); } catch (error) { console.warn( chalk.yellow('Warning: Could not stop existing instance'), (error as Error).message, ); } } async function performHealthChecks(): Promise<void> { const checks = [ { name: 'Disk Space', check: checkDiskSpace }, { name: 'Memory Available', check: checkMemoryAvailable }, { name: 'Network Connectivity', check: checkNetworkConnectivity }, { name: 'Required Dependencies', check: checkDependencies }, ]; for (const { name, check } of checks) { try { console.log(chalk.gray(` Checking ${name}...`)); await check(); console.log(chalk.green(` ✓ ${name} OK`)); } catch (error) { console.log(chalk.red(` ✗ ${name} Failed: ${(error as Error).message}`)); throw error; } } } async function checkDiskSpace(): Promise<void> { // Basic disk space check - would need platform-specific implementation const stats = await fs.stat('.'); if (!stats.isDirectory) { throw new Error('Current directory is not accessible'); } } async function checkMemoryAvailable(): Promise<void> { // Memory check - would integrate with system memory monitoring const memoryInfo = Deno.memoryUsage(); if (memoryInfo.heapUsed > 500 * 1024 * 1024) { // 500MB threshold throw new Error('High memory usage detected'); } } async function checkNetworkConnectivity(): Promise<void> { // Basic network check try { const response = await fetch('https://httpbin.org/status/200', { method: 'GET', signal: AbortSignal.timeout(5000), }); if (!response.ok) { throw new Error(`Network check failed: ${response.status}`); } } catch { console.log(chalk.yellow(' ⚠ Network connectivity check skipped (offline mode?)')); } } async function checkDependencies(): Promise<void> { // Check for required directories and files const requiredDirs = ['.claude-flow', 'memory', 'logs']; for (const dir of requiredDirs) { try { await Deno.mkdir(dir, { recursive: true }); } catch (error) { throw new Error(`Cannot create required directory: ${dir}`); } } } function setupSystemEventHandlers( processManager: ProcessManager, systemMonitor: SystemMonitor, options: StartOptions, ): void { // Handle graceful shutdown signals const shutdownHandler = async () => { console.log('\n' + chalk.yellow('Received shutdown signal, shutting down gracefully...')); systemMonitor.stop(); await processManager.stopAll(); await cleanupOnShutdown(); console.log(chalk.green('✓ Shutdown complete')); process.exit(0); }; Deno.addSignalListener('SIGINT', shutdownHandler); Deno.addSignalListener('SIGTERM', shutdownHandler); // Setup verbose logging if requested if (options.verbose) { setupVerboseLogging(systemMonitor); } // Monitor for critical errors processManager.on('processError', (event: any) => { console.error(chalk.red(`Process error in ${event.processId}:`), event.error); if (event.processId === 'orchestrator') { console.error(chalk.red.bold('Critical process failed, initiating recovery...')); // Could implement auto-recovery logic here } }); } async function startWithProgress( processManager: ProcessManager, mode: 'all' | 'core', ): Promise<void> { const processes = mode === 'all' ? [ 'event-bus', 'memory-manager', 'terminal-pool', 'coordinator', 'mcp-server', 'orchestrator', ] : ['event-bus', 'memory-manager', 'mcp-server']; for (let i = 0; i < processes.length; i++) { const processId = processes[i]; const progress = `[${i + 1}/${processes.length}]`; console.log(chalk.gray(`${progress} Starting ${processId}...`)); try { await processManager.startProcess(processId); console.log(chalk.green(`${progress}${processId} started`)); } catch (error) { console.log(chalk.red(`${progress}${processId} failed: ${(error as Error).message}`)); if (processId === 'orchestrator' || processId === 'mcp-server') { throw error; // Critical processes } } // Brief delay between starts if (i < processes.length - 1) { await new Promise((resolve) => setTimeout(resolve, 500)); } } } async function waitForSystemReady(processManager: ProcessManager): Promise<void> { console.log(chalk.blue('Waiting for system to be ready...')); const maxWait = 30000; // 30 seconds const checkInterval = 1000; // 1 second let waited = 0; while (waited < maxWait) { const stats = processManager.getSystemStats(); if (stats.errorProcesses === 0 && stats.runningProcesses >= 3) { console.log(chalk.green('✓ System ready')); return; } await new Promise((resolve) => setTimeout(resolve, checkInterval)); waited += checkInterval; } console.log( chalk.yellow('⚠ System startup completed but some processes may not be fully ready'), ); } async function cleanupOnFailure(): Promise<void> { try { await Deno.remove('.claude-flow.pid').catch(() => {}); console.log(chalk.gray('Cleaned up PID file')); } catch { // Ignore cleanup errors } } async function cleanupOnShutdown(): Promise<void> { try { await Deno.remove('.claude-flow.pid').catch(() => {}); console.log(chalk.gray('Cleaned up PID file')); } catch { // Ignore cleanup errors } } function setupVerboseLogging(monitor: SystemMonitor): void { // Enhanced verbose logging console.log(chalk.gray('Verbose logging enabled')); // Periodically print system health setInterval(() => { console.log(); console.log(chalk.cyan('--- System Health Report ---')); monitor.printSystemHealth(); console.log(chalk.cyan('--- End Report ---')); }, 30000); // Log critical events eventBus.on('process:started', (data: any) => { console.log(chalk.green(`[VERBOSE] Process started: ${data.processId}`)); }); eventBus.on('process:stopped', (data: any) => { console.log(chalk.yellow(`[VERBOSE] Process stopped: ${data.processId}`)); }); eventBus.on('process:error', (data: any) => { console.log(chalk.red(`[VERBOSE] Process error: ${data.processId} - ${data.error}`)); }); }