claude-flow-tbowman01
Version:
Enterprise-grade AI agent orchestration with ruv-swarm integration (Alpha Release)
548 lines (474 loc) • 18.8 kB
text/typescript
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}`));
});
}