UNPKG

claude-flow-tbowman01

Version:

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

397 lines (335 loc) 12.1 kB
/** * Simplified Process UI without keypress dependency * Uses basic stdin reading for compatibility */ import chalk from 'chalk'; import type { ProcessManager } from './process-manager.js'; import { ProcessInfo, ProcessStatus, SystemStats } from './types.js'; export class ProcessUI { private processManager: ProcessManager; private running = false; private selectedIndex = 0; constructor(processManager: ProcessManager) { this.processManager = processManager; this.setupEventListeners(); } private setupEventListeners(): void { this.processManager.on( 'statusChanged', ({ processId, status }: { processId: string; status: ProcessStatus }) => { if (this.running) { this.render(); } }, ); this.processManager.on( 'processError', ({ processId, error }: { processId: string; error: Error }) => { if (this.running) { console.log( chalk.red( `\nProcess ${processId} error: ${error instanceof Error ? error.message : String(error)}`, ), ); } }, ); } async start(): Promise<void> { this.running = true; // Clear screen console.clear(); // Initial render this.render(); // Simple input loop const decoder = new TextDecoder(); const encoder = new TextEncoder(); while (this.running) { // Show prompt await Deno.stdout.write(encoder.encode('\nCommand: ')); // Read single character const buf = new Uint8Array(1024); const n = await Deno.stdin.read(buf); if (n === null) break; const input = decoder.decode(buf.subarray(0, n)).trim(); if (input.length > 0) { await this.handleCommand(input); } } } async stop(): Promise<void> { this.running = false; console.clear(); } private async handleCommand(input: string): Promise<void> { const processes = this.processManager.getAllProcesses(); switch (input.toLowerCase()) { case 'q': case 'quit': case 'exit': await this.handleExit(); break; case 'a': case 'all': await this.startAll(); break; case 'z': case 'stop-all': await this.stopAll(); break; case 'r': case 'refresh': this.render(); break; case 'h': case 'help': case '?': this.showHelp(); break; default: // Check if it's a number (process selection) const num = parseInt(input); if (!isNaN(num) && num >= 1 && num <= processes.length) { this.selectedIndex = num - 1; await this.showProcessMenu(processes[this.selectedIndex]); } else { console.log(chalk.yellow('Invalid command. Type "h" for help.')); } break; } } private render(): void { console.clear(); const processes = this.processManager.getAllProcesses(); const stats = this.processManager.getSystemStats(); // Header console.log(chalk.cyan.bold('🧠 Claude-Flow Process Manager')); console.log(chalk.gray('─'.repeat(60))); // System stats console.log( chalk.white('System Status:'), chalk.green(`${stats.runningProcesses}/${stats.totalProcesses} running`), ); if (stats.errorProcesses > 0) { console.log(chalk.red(`⚠️ ${stats.errorProcesses} processes with errors`)); } console.log(); // Process list console.log(chalk.white.bold('Processes:')); console.log(chalk.gray('─'.repeat(60))); processes.forEach((process, index) => { const num = `[${index + 1}]`.padEnd(4); const status = this.getStatusDisplay(process.status); const name = process.name.padEnd(25); console.log(`${chalk.gray(num)} ${status} ${chalk.white(name)}`); if (process.metrics?.lastError) { console.log(chalk.red(` Error: ${process.metrics.lastError}`)); } }); // Footer console.log(chalk.gray('─'.repeat(60))); console.log(chalk.gray('Commands: [1-9] Select process [a] Start All [z] Stop All')); console.log(chalk.gray('[r] Refresh [h] Help [q] Quit')); } private async showProcessMenu(process: ProcessInfo): Promise<void> { console.log(); console.log(chalk.cyan.bold(`Selected: ${process.name}`)); console.log(chalk.gray('─'.repeat(40))); if (process.status === ProcessStatus.STOPPED) { console.log('[s] Start'); } else if (process.status === ProcessStatus.RUNNING) { console.log('[x] Stop'); console.log('[r] Restart'); } console.log('[d] Details'); console.log('[c] Cancel'); const decoder = new TextDecoder(); const encoder = new TextEncoder(); await Deno.stdout.write(encoder.encode('\nAction: ')); const buf = new Uint8Array(1024); const n = await Deno.stdin.read(buf); if (n === null) return; const action = decoder.decode(buf.subarray(0, n)).trim().toLowerCase(); switch (action) { case 's': if (process.status === ProcessStatus.STOPPED) { await this.startProcess(process.id); } break; case 'x': if (process.status === ProcessStatus.RUNNING) { await this.stopProcess(process.id); } break; case 'r': if (process.status === ProcessStatus.RUNNING) { await this.restartProcess(process.id); } break; case 'd': this.showProcessDetails(process); await this.waitForKey(); break; } this.render(); } private showProcessDetails(process: ProcessInfo): void { console.log(); console.log(chalk.cyan.bold(`📋 Process Details: ${process.name}`)); console.log(chalk.gray('─'.repeat(60))); console.log(chalk.white('ID:'), process.id); console.log(chalk.white('Type:'), process.type); console.log(chalk.white('Status:'), this.getStatusDisplay(process.status), process.status); if (process.pid) { console.log(chalk.white('PID:'), process.pid); } if (process.startTime) { const uptime = Date.now() - process.startTime; console.log(chalk.white('Uptime:'), this.formatUptime(uptime)); } if (process.metrics) { console.log(); console.log(chalk.white.bold('Metrics:')); if (process.metrics.cpu !== undefined) { console.log(chalk.white('CPU:'), `${process.metrics.cpu.toFixed(1)}%`); } if (process.metrics.memory !== undefined) { console.log(chalk.white('Memory:'), `${process.metrics.memory.toFixed(0)} MB`); } if (process.metrics.restarts !== undefined) { console.log(chalk.white('Restarts:'), process.metrics.restarts); } if (process.metrics.lastError) { console.log(chalk.red('Last Error:'), process.metrics.lastError); } } console.log(); console.log(chalk.gray('Press any key to continue...')); } private async waitForKey(): Promise<void> { const buf = new Uint8Array(1); await Deno.stdin.read(buf); } private getStatusDisplay(status: ProcessStatus): string { switch (status) { case ProcessStatus.RUNNING: return chalk.green('●'); case ProcessStatus.STOPPED: return chalk.gray('○'); case ProcessStatus.STARTING: return chalk.yellow('◐'); case ProcessStatus.STOPPING: return chalk.yellow('◑'); case ProcessStatus.ERROR: return chalk.red('✗'); case ProcessStatus.CRASHED: return chalk.red('☠'); default: return chalk.gray('?'); } } private formatUptime(ms: number): string { const seconds = Math.floor(ms / 1000); const minutes = Math.floor(seconds / 60); const hours = Math.floor(minutes / 60); const days = Math.floor(hours / 24); if (days > 0) { return `${days}d ${hours % 24}h`; } else if (hours > 0) { return `${hours}h ${minutes % 60}m`; } else if (minutes > 0) { return `${minutes}m ${seconds % 60}s`; } else { return `${seconds}s`; } } private showHelp(): void { console.log(); console.log(chalk.cyan.bold('🧠 Claude-Flow Process Manager - Help')); console.log(chalk.gray('─'.repeat(60))); console.log(); console.log(chalk.white.bold('Commands:')); console.log(' 1-9 - Select process by number'); console.log(' a - Start all processes'); console.log(' z - Stop all processes'); console.log(' r - Refresh display'); console.log(' h/? - Show this help'); console.log(' q - Quit'); console.log(); console.log(chalk.white.bold('Process Actions:')); console.log(' s - Start selected process'); console.log(' x - Stop selected process'); console.log(' r - Restart selected process'); console.log(' d - Show process details'); console.log(); console.log(chalk.gray('Press any key to continue...')); } private async startProcess(processId: string): Promise<void> { try { console.log(chalk.yellow(`Starting ${processId}...`)); await this.processManager.startProcess(processId); console.log(chalk.green(`✓ Started ${processId}`)); } catch (error) { console.log(chalk.red(`✗ Failed to start ${processId}: ${(error as Error).message}`)); } await this.waitForKey(); } private async stopProcess(processId: string): Promise<void> { try { console.log(chalk.yellow(`Stopping ${processId}...`)); await this.processManager.stopProcess(processId); console.log(chalk.green(`✓ Stopped ${processId}`)); } catch (error) { console.log(chalk.red(`✗ Failed to stop ${processId}: ${(error as Error).message}`)); } await this.waitForKey(); } private async restartProcess(processId: string): Promise<void> { try { console.log(chalk.yellow(`Restarting ${processId}...`)); await this.processManager.restartProcess(processId); console.log(chalk.green(`✓ Restarted ${processId}`)); } catch (error) { console.log(chalk.red(`✗ Failed to restart ${processId}: ${(error as Error).message}`)); } await this.waitForKey(); } private async startAll(): Promise<void> { try { console.log(chalk.yellow('Starting all processes...')); await this.processManager.startAll(); console.log(chalk.green('✓ All processes started')); } catch (error) { console.log(chalk.red(`✗ Failed to start all: ${(error as Error).message}`)); } await this.waitForKey(); this.render(); } private async stopAll(): Promise<void> { try { console.log(chalk.yellow('Stopping all processes...')); await this.processManager.stopAll(); console.log(chalk.green('✓ All processes stopped')); } catch (error) { console.log(chalk.red(`✗ Failed to stop all: ${(error as Error).message}`)); } await this.waitForKey(); this.render(); } private async handleExit(): Promise<void> { const processes = this.processManager.getAllProcesses(); const hasRunning = processes.some((p) => p.status === ProcessStatus.RUNNING); if (hasRunning) { console.log(); console.log(chalk.yellow('⚠️ Some processes are still running.')); console.log('Stop all processes before exiting? [y/N]: '); const decoder = new TextDecoder(); const buf = new Uint8Array(1024); const n = await Deno.stdin.read(buf); if (n && decoder.decode(buf.subarray(0, n)).trim().toLowerCase() === 'y') { await this.stopAll(); } } await this.stop(); } }