UNPKG

claude-coordination-system

Version:

🤖 Multi-Claude Parallel Processing Coordination System - Organize multiple Claude AI instances to work together seamlessly on complex development tasks

1,045 lines (894 loc) 37.3 kB
/** * Interactive Terminal Interface for Multi-Claude Coordination System * Simple, fast, and reliable terminal interface */ const readline = require('readline'); const chalk = require('chalk'); const { logUser } = require('./development-logger'); class TerminalInterface { constructor(coordinatorCore, webDashboard = null) { this.coordinatorCore = coordinatorCore; this.webDashboard = webDashboard; this.rl = null; this.isRunning = false; this.currentScreen = 'main'; // PERFORMANCE FIX: Ultra-fast terminal cache this.statusCache = null; this.statusCacheTime = 0; this.cacheMaxAge = 200; // 200ms cache for terminal responsiveness } /** * Get cached system status for ultra-fast terminal performance */ async getCachedSystemStatus() { // Use cache for instant terminal responsiveness if (this.statusCache && (Date.now() - this.statusCacheTime) < this.cacheMaxAge) { return this.statusCache; } try { const status = await this.getCachedSystemStatus(); // Update cache this.statusCache = status; this.statusCacheTime = Date.now(); return status; } catch (error) { // Return mock data if coordinator fails return { healthy: false, activeWorkers: 0, completedTasks: 0, totalTasks: 0, fileLocks: 0, workers: [], coordination: { sessions: {}, groups: {} }, error: error.message }; } } /** * Start the interactive terminal interface */ async start() { this.isRunning = true; // Create readline interface this.rl = readline.createInterface({ input: process.stdin, output: process.stdout }); // Hide cursor and prepare terminal process.stdout.write('\x1B[?25l'); await logUser('Start Terminal Interface', 'Interactive monitoring started', 'STARTED'); // Show initial screen await this.showMainScreen(); // Setup key handling this.setupKeyHandling(); } /** * Stop the terminal interface */ async stop() { this.isRunning = false; if (this.rl) { this.rl.close(); } // Show cursor and clean terminal process.stdout.write('\x1B[?25h\x1B[2J\x1B[H'); await logUser('Stop Terminal Interface', 'Interactive monitoring stopped', 'STOPPED'); } /** * Setup keyboard event handling */ setupKeyHandling() { try { // Check if stdin supports raw mode if (process.stdin.isTTY && process.stdin.setRawMode) { process.stdin.setRawMode(true); process.stdin.resume(); } else { console.log(chalk.yellow('⚠️ Running in non-interactive mode - keyboard shortcuts disabled')); return; } } catch (error) { console.log(chalk.yellow('⚠️ Interactive terminal not available in this environment')); return; } process.stdin.on('data', (key) => { const char = key.toString(); // Handle key presses immediately without await - use setImmediate for async operations switch (char) { case '\x03': // Ctrl+C setImmediate(async () => { await this.stop(); process.exit(0); }); break; case '\x1b': // ESC if (this.currentScreen !== 'main') { this.currentScreen = 'main'; setImmediate(() => this.showMainScreen()); } break; case 'm': case 'M': this.currentScreen = 'monitor'; setImmediate(() => this.showMonitorScreen()); break; case 'w': case 'W': this.currentScreen = 'workers'; setImmediate(() => this.showWorkersScreen()); break; case 's': case 'S': this.currentScreen = 'sync'; setImmediate(() => this.showSyncScreen()); break; case 'h': case 'H': this.currentScreen = 'help'; setImmediate(() => this.showHelpScreen()); break; case 'r': case 'R': setImmediate(() => this.refreshCurrentScreen()); break; case 'd': case 'D': if (this.currentScreen === 'workers') { setImmediate(() => this.showWorkerDeleteMenu()); } else if (this.currentScreen === 'sync') { setImmediate(() => this.showSyncDeleteMenu()); } break; case 'g': case 'G': if (this.currentScreen === 'workers') { setImmediate(() => this.showWorkerReassignMenu()); } else if (this.currentScreen === 'sync') { setImmediate(() => this.showSyncReassignMenu()); } break; case 'q': case 'Q': setImmediate(async () => { await this.stop(); process.exit(0); }); break; } }); } /** * Clear screen and position cursor */ clearScreen() { process.stdout.write('\x1B[2J\x1B[H'); } /** * Show main dashboard screen */ async showMainScreen() { try { // Instant screen clear and header - no waiting this.clearScreen(); // Show header immediately while loading data in background console.log(chalk.blue.bold('🤖 Multi-Claude Coordination System Dashboard')); console.log(chalk.gray('═'.repeat(60))); console.log(); console.log(chalk.yellow('Loading system status...')); const status = await this.getCachedSystemStatus(); // Clear and redraw with actual data this.clearScreen(); // Header console.log(chalk.blue.bold('🤖 Multi-Claude Coordination System Dashboard')); console.log(chalk.gray('═'.repeat(60))); console.log(); // Status overview console.log(chalk.green('📊 System Status')); console.log(chalk.gray('─'.repeat(30))); console.log(`Active Workers: ${chalk.cyan(status.activeWorkers)}`); console.log(`Sync Sessions: ${chalk.blue(status.coordination?.sessions ? Object.keys(status.coordination.sessions).length : 0)}`); console.log(`Tasks Progress: ${chalk.cyan(status.completedTasks)}/${chalk.cyan(status.totalTasks)}`); console.log(`File Locks: ${chalk.cyan(status.fileLocks)}`); console.log(`System Health: ${status.healthy ? chalk.green('●') : chalk.red('●')} ${status.healthy ? 'Healthy' : 'Issues'}`); console.log(); // Workers summary if (status.workers.length > 0) { console.log(chalk.green('👥 Active Workers Summary')); console.log(chalk.gray('─'.repeat(40))); status.workers.slice(0, 5).forEach(worker => { const statusIcon = this.getWorkerStatusIcon(worker.status); const statusColor = this.getWorkerStatusColor(worker.status); console.log(`${statusIcon} ${chalk.bold(worker.id || 'Unknown')} [${statusColor(worker.group)}] - ${statusColor(worker.status?.toUpperCase() || 'UNKNOWN')}`); }); if (status.workers.length > 5) { console.log(chalk.gray(`... and ${status.workers.length - 5} more workers`)); } } else { console.log(chalk.yellow('No active workers')); } console.log(); // Commands panel this.showCommandsPanel(); } catch (error) { this.clearScreen(); console.log(chalk.red('❌ Error fetching system status:'), error.message); } } /** * Show detailed monitoring screen */ async showMonitorScreen() { try { // Instant screen clear and header this.clearScreen(); console.log(chalk.blue.bold('📊 Real-time Monitoring Dashboard')); console.log(chalk.gray('═'.repeat(60))); console.log(); console.log(chalk.yellow('Loading monitoring data...')); const status = await this.getCachedSystemStatus(); // Clear and redraw with data this.clearScreen(); console.log(chalk.blue.bold('📊 Real-time Monitoring Dashboard')); console.log(chalk.gray('═'.repeat(60))); console.log(); // System metrics console.log(chalk.green('🖥️ System Metrics')); console.log(chalk.gray('─'.repeat(30))); console.log(`Coordinator PID: ${chalk.cyan(process.pid)}`); console.log(`Memory Usage: ${chalk.cyan(Math.round(process.memoryUsage().rss / 1024 / 1024))}MB`); console.log(`Uptime: ${chalk.cyan(Math.round(process.uptime()))}s`); console.log(`Active Workers: ${chalk.cyan(status.activeWorkers)}`); console.log(`Sync Sessions: ${chalk.blue(status.coordination?.sessions ? Object.keys(status.coordination.sessions).length : 0)}`); console.log(`File Locks: ${chalk.cyan(status.fileLocks)}`); console.log(); // Worker details if (status.workers.length > 0) { console.log(chalk.green('🔧 Worker Details')); console.log(chalk.gray('─'.repeat(50))); status.workers.forEach(worker => { const statusIcon = this.getWorkerStatusIcon(worker.status); const statusColor = this.getWorkerStatusColor(worker.status); console.log(`${statusIcon} ${chalk.bold(worker.id || 'Unknown')}`); console.log(` Group: ${chalk.cyan(worker.group)}`); console.log(` Status: ${statusColor(worker.status?.toUpperCase() || 'UNKNOWN')}`); console.log(` Last Update: ${chalk.gray(new Date().toLocaleTimeString())}`); console.log(); }); } console.log(chalk.gray(`Last Updated: ${new Date().toLocaleTimeString()}`)); console.log(); // Commands console.log(chalk.yellow('📋 Commands: [ESC] Main | [R] Refresh | [Q] Quit')); } catch (error) { this.clearScreen(); console.log(chalk.red('❌ Error in monitoring:'), error.message); } } /** * Show workers management screen */ async showWorkersScreen() { try { // Instant screen clear and header this.clearScreen(); console.log(chalk.blue.bold('👥 Workers Management')); console.log(chalk.gray('═'.repeat(50))); console.log(); console.log(chalk.yellow('Loading workers data...')); const status = await this.getCachedSystemStatus(); // Clear and redraw with data this.clearScreen(); console.log(chalk.blue.bold('👥 Workers Management')); console.log(chalk.gray('═'.repeat(50))); console.log(); if (status.workers.length > 0) { status.workers.forEach((worker, index) => { const statusIcon = this.getWorkerStatusIcon(worker.status); const statusColor = this.getWorkerStatusColor(worker.status); console.log(`${chalk.cyan(`[${index + 1}]`)} ${statusIcon} ${chalk.bold(worker.id || 'Unknown')}`); console.log(` Group: ${chalk.cyan(worker.group)}`); console.log(` Status: ${statusColor(worker.status?.toUpperCase() || 'UNKNOWN')}`); console.log(` Management Commands:`); console.log(` Remove: claude-coord remove-worker --worker=${worker.id}`); console.log(` Reassign: claude-coord reassign-worker --worker=${worker.id} --group=NEWGROUP`); console.log(); }); } else { console.log(chalk.yellow('No active workers found.')); console.log(); console.log(chalk.cyan('To start a worker:')); console.log(' claude-worker --id=worker_1 --standby'); console.log(' claude-worker --id=worker_2 --group=TYPESCRIPT'); } console.log(chalk.blue('🎯 Worker Commands:')); console.log(chalk.gray('─'.repeat(25))); console.log(`${chalk.cyan('[D]')} - Delete worker (numbered list)`); console.log(`${chalk.cyan('[G]')} - Reassign worker to group (custom name input)`); console.log(`${chalk.cyan('[R]')} - Refresh | ${chalk.cyan('[ESC]')} - Main | ${chalk.cyan('[Q]')} - Quit`); } catch (error) { this.clearScreen(); console.log(chalk.red('❌ Error loading workers:'), error.message); } } /** * Show sync sessions screen */ async showSyncScreen() { try { // Instant screen clear and header this.clearScreen(); console.log(chalk.blue.bold('🔄 Claude Sync Sessions')); console.log(chalk.gray('═'.repeat(60))); console.log(); console.log(chalk.yellow('Loading sync sessions...')); const status = await this.getCachedSystemStatus(); // Clear and redraw with data this.clearScreen(); const coordination = status.coordination || {}; const sessions = coordination.sessions || {}; console.log(chalk.blue.bold('🔄 Claude Sync Sessions')); console.log(chalk.gray('═'.repeat(60))); console.log(); // Filter out invalid sessions (Total, Active issue fix) const validSessions = {}; Object.entries(sessions).forEach(([sessionId, session]) => { // Only include sessions with proper structure if (session && typeof session === 'object' && session.group && session.status && sessionId.length > 5) { validSessions[sessionId] = session; } }); if (Object.keys(validSessions).length === 0) { console.log(chalk.yellow('No active sync sessions')); console.log(chalk.gray('Start a sync session with: claude-sync --group=<group>')); } else { console.log(chalk.green(`📊 Active Sessions: ${Object.keys(validSessions).length}`)); console.log(chalk.gray('─'.repeat(40))); Object.entries(validSessions).forEach(([sessionId, session], index) => { const statusIcon = session.status === 'active' ? chalk.green('●') : chalk.red('○'); const shortId = sessionId.replace(/^claude_/, '').substring(0, 25); console.log(`${index + 1}. ${statusIcon} ${chalk.bold(shortId)}`); console.log(` Group: ${chalk.cyan(session.group || 'Unknown')}`); console.log(` Status: ${session.status || 'Unknown'}`); if (session.lastHeartbeat) { const lastSeen = this.formatTimeAgo(new Date(session.lastHeartbeat)); console.log(` Last seen: ${chalk.gray(lastSeen)}`); } if (session.metadata?.primaryTerminal) { console.log(` Role: ${chalk.yellow('Primary Terminal')}`); } console.log(); }); } console.log(chalk.blue('🎯 Sync Commands:')); console.log(chalk.gray('─'.repeat(25))); console.log(`${chalk.cyan('[D]')} - Delete sync session (numbered list)`); console.log(`${chalk.cyan('[G]')} - Reassign sync session to group (custom name input)`); console.log(`${chalk.cyan('[R]')} - Refresh | ${chalk.cyan('[ESC]')} - Main | ${chalk.cyan('[Q]')} - Quit`); } catch (error) { this.clearScreen(); console.error(chalk.red('Error loading sync sessions:'), error.message); } } /** * Show help screen */ async showHelpScreen() { this.clearScreen(); console.log(chalk.blue.bold('❓ Multi-Claude Coordination - Help')); console.log(chalk.gray('═'.repeat(50))); console.log(); console.log(chalk.green('🎮 Navigation Commands')); console.log(chalk.gray('─'.repeat(30))); console.log(`${chalk.cyan('[M]')} - Monitoring Dashboard`); console.log(`${chalk.cyan('[W]')} - Workers Management`); console.log(`${chalk.cyan('[S]')} - Sync Sessions Management`); console.log(`${chalk.cyan('[H]')} - Help (this screen)`); console.log(`${chalk.cyan('[R]')} - Refresh current screen`); console.log(`${chalk.cyan('[ESC]')} - Return to main screen`); console.log(`${chalk.cyan('[Q]')} - Quit application`); console.log(`${chalk.cyan('[Ctrl+C]')} - Force quit`); console.log(); console.log(chalk.green('🛠️ Terminal Commands')); console.log(chalk.gray('─'.repeat(30))); console.log(`${chalk.cyan('claude-coord status')} - Show system status`); console.log(`${chalk.cyan('claude-coord stop')} - Stop coordinator`); console.log(`${chalk.cyan('claude-worker --id=worker_1 --standby')} - Start worker`); console.log(`${chalk.cyan('claude-coord remove-worker --worker=ID')} - Remove worker`); console.log(`${chalk.cyan('claude-coord reassign-worker --worker=ID --group=GROUP')} - Reassign worker`); console.log(); console.log(chalk.green('🔄 Terminal Sync Commands')); console.log(chalk.gray('─'.repeat(35))); console.log(`${chalk.cyan('claude-sync --project=./my-app')} - Start sync in project directory`); console.log(`${chalk.cyan('claude-sync --group=frontend --files="src/**/*.tsx"')} - Sync specific files`); console.log(`${chalk.cyan('claude-sync --watch --auto-commit')} - Watch mode with auto-commit`); console.log(`${chalk.cyan('claude-worker --sync --id=sync_worker_1')} - Start sync worker`); console.log(`${chalk.cyan('claude-worker --sync --group=backend --standby')} - Standby sync worker`); console.log(`${chalk.cyan('claude-coord sync-status')} - Show sync session status`); console.log(`${chalk.cyan('claude-coord sync-stop --session=ID')} - Stop specific sync session`); console.log(`${chalk.cyan('claude-coord sync-clean')} - Clean stale sync sessions`); console.log(); console.log(chalk.green('🎛️ Claude Interface Commands')); console.log(chalk.gray('─'.repeat(40))); console.log(chalk.blue('Session Management:')); console.log(` ${chalk.cyan('[S]')} → Access Sync Sessions screen`); console.log(` ${chalk.cyan('[D] + number')} → Delete specific sync session`); console.log(` ${chalk.cyan('[G] + number')} → Reassign session to new group`); console.log(` ${chalk.cyan('[R]')} → Refresh sync sessions list`); console.log(); console.log(chalk.blue('Worker Sync Operations:')); console.log(` ${chalk.cyan('[W]')} → Access Workers Management screen`); console.log(` ${chalk.cyan('[T] + number')} → Assign worker to sync task`); console.log(` ${chalk.cyan('[P] + number')} → Pause/Resume worker sync`); console.log(` ${chalk.cyan('[K] + number')} → Kill worker (stops sync)`); console.log(); console.log(chalk.blue('Real-time Sync Control:')); console.log(` ${chalk.cyan('Ctrl+S')} → Force sync all sessions`); console.log(` ${chalk.cyan('Ctrl+P')} → Pause all sync operations`); console.log(` ${chalk.cyan('Ctrl+R')} → Resume all sync operations`); console.log(` ${chalk.cyan('Ctrl+L')} → View sync logs in real-time`); console.log(); console.log(chalk.green('🌐 Web Dashboard')); console.log(chalk.gray('─'.repeat(30))); const dashboardPort = this.webDashboard?.port || this.coordinatorCore?.port || 7777; console.log(`Open ${chalk.cyan(`http://localhost:${dashboardPort}`)} in your browser`); console.log(`for web-based monitoring and management`); console.log(); console.log(chalk.yellow('📋 Commands: [ESC] Main | [M] Monitor | [W] Workers | [Q] Quit')); } /** * Show commands panel */ showCommandsPanel() { console.log(chalk.blue('🎮 Navigation')); console.log(chalk.gray('─'.repeat(20))); console.log(`${chalk.cyan('[M]')} Monitor | ${chalk.cyan('[W]')} Workers | ${chalk.cyan('[S]')} Sync | ${chalk.cyan('[H]')} Help | ${chalk.cyan('[R]')} Refresh | ${chalk.cyan('[Q]')} Quit`); console.log(); // Get dynamic port from coordinator const dashboardPort = this.webDashboard?.port || this.coordinatorCore?.port || 7777; console.log(chalk.blue('🌐 Web Dashboard: ') + chalk.cyan(`http://localhost:${dashboardPort}`)); } /** * Get status icon for worker */ getWorkerStatusIcon(status) { switch (status) { case 'working': return chalk.green('🔧'); case 'inactive': return chalk.yellow('⏸️'); case 'standby': return chalk.gray('⏳'); case 'stale': return chalk.red('⚠️'); case 'completed': return chalk.green('✅'); case 'error': return chalk.red('❌'); default: return chalk.gray('❓'); } } /** * Get status color for worker */ getWorkerStatusColor(status) { switch (status) { case 'working': return chalk.green; case 'inactive': return chalk.yellow; case 'standby': return chalk.gray; case 'stale': return chalk.red; case 'completed': return chalk.green; case 'error': return chalk.red; default: return chalk.gray; } } /** * Refresh current screen */ async refreshCurrentScreen() { switch (this.currentScreen) { case 'main': await this.showMainScreen(); break; case 'monitor': await this.showMonitorScreen(); break; case 'workers': await this.showWorkersScreen(); break; case 'sync': await this.showSyncScreen(); break; case 'help': await this.showHelpScreen(); break; default: await this.showMainScreen(); } } /** * Format time ago helper */ formatTimeAgo(date) { const now = new Date(); const diffMs = now - date; const diffSecs = Math.floor(diffMs / 1000); const diffMins = Math.floor(diffSecs / 60); const diffHours = Math.floor(diffMins / 60); if (diffSecs < 60) return `${diffSecs}s ago`; if (diffMins < 60) return `${diffMins}m ago`; if (diffHours < 24) return `${diffHours}h ago`; return date.toLocaleDateString(); } /** * Show worker delete menu */ async showWorkerDeleteMenu() { try { const status = await this.getCachedSystemStatus(); if (status.workers.length === 0) { this.clearScreen(); console.log(chalk.yellow('⚠️ No workers to remove')); console.log(chalk.gray('Press any key to return...')); setTimeout(() => this.showWorkersScreen(), 2000); return; } this.clearScreen(); console.log(chalk.red.bold('🗑️ Remove Worker')); console.log(chalk.gray('═'.repeat(40))); console.log(); status.workers.forEach((worker, index) => { const statusIcon = this.getWorkerStatusIcon(worker.status); const statusColor = this.getWorkerStatusColor(worker.status); console.log(`${index + 1}. ${statusIcon} ${chalk.bold(worker.id || 'Unknown')} [${statusColor(worker.group)}]`); }); console.log(); console.log(chalk.yellow('Enter worker number to remove (1-' + status.workers.length + '), or press ESC to cancel:')); // Wait for user input await this.waitForWorkerSelection(status.workers, 'remove'); } catch (error) { this.clearScreen(); console.error(chalk.red('Error loading workers:'), error.message); setTimeout(() => this.showWorkersScreen(), 2000); } } /** * Show worker reassign menu */ async showWorkerReassignMenu() { try { const status = await this.getCachedSystemStatus(); if (status.workers.length === 0) { this.clearScreen(); console.log(chalk.yellow('⚠️ No workers to reassign')); console.log(chalk.gray('Press any key to return...')); setTimeout(() => this.showWorkersScreen(), 2000); return; } this.clearScreen(); console.log(chalk.blue.bold('🔄 Reassign Worker')); console.log(chalk.gray('═'.repeat(40))); console.log(); status.workers.forEach((worker, index) => { const statusIcon = this.getWorkerStatusIcon(worker.status); const statusColor = this.getWorkerStatusColor(worker.status); console.log(`${index + 1}. ${statusIcon} ${chalk.bold(worker.id || 'Unknown')} [${statusColor(worker.group)}]`); }); console.log(); console.log(chalk.yellow('Enter worker number to reassign (1-' + status.workers.length + '), or press ESC to cancel:')); // Wait for user input await this.waitForWorkerSelection(status.workers, 'reassign'); } catch (error) { this.clearScreen(); console.error(chalk.red('Error loading workers:'), error.message); setTimeout(() => this.showWorkersScreen(), 2000); } } /** * Show sync delete menu */ async showSyncDeleteMenu() { try { const status = await this.getCachedSystemStatus(); const sessions = status.coordination?.sessions || {}; const sessionList = Object.entries(sessions); if (sessionList.length === 0) { this.clearScreen(); console.log(chalk.yellow('⚠️ No sync sessions to remove')); console.log(chalk.gray('Press any key to return...')); setTimeout(() => this.showSyncScreen(), 2000); return; } this.clearScreen(); console.log(chalk.red.bold('🗑️ Remove Sync Session')); console.log(chalk.gray('═'.repeat(40))); console.log(); sessionList.forEach(([sessionId, session], index) => { const statusIcon = session.status === 'active' ? chalk.green('●') : chalk.red('○'); const shortId = sessionId.replace(/^claude_/, '').substring(0, 25); console.log(`${index + 1}. ${statusIcon} ${chalk.bold(shortId)} [${chalk.cyan(session.group || 'Unknown')}]`); }); console.log(); console.log(chalk.yellow('Enter session number to remove (1-' + sessionList.length + '), or press ESC to cancel:')); // Wait for user input await this.waitForSyncSelection(sessionList, 'remove'); } catch (error) { this.clearScreen(); console.error(chalk.red('Error loading sync sessions:'), error.message); setTimeout(() => this.showSyncScreen(), 2000); } } /** * Show sync reassign menu */ async showSyncReassignMenu() { try { const status = await this.getCachedSystemStatus(); const sessions = status.coordination?.sessions || {}; const sessionList = Object.entries(sessions); if (sessionList.length === 0) { this.clearScreen(); console.log(chalk.yellow('⚠️ No sync sessions to reassign')); console.log(chalk.gray('Press any key to return...')); setTimeout(() => this.showSyncScreen(), 2000); return; } this.clearScreen(); console.log(chalk.blue.bold('🔄 Reassign Sync Session')); console.log(chalk.gray('═'.repeat(40))); console.log(); sessionList.forEach(([sessionId, session], index) => { const statusIcon = session.status === 'active' ? chalk.green('●') : chalk.red('○'); const shortId = sessionId.replace(/^claude_/, '').substring(0, 25); console.log(`${index + 1}. ${statusIcon} ${chalk.bold(shortId)} [${chalk.cyan(session.group || 'Unknown')}]`); }); console.log(); console.log(chalk.yellow('Enter session number to reassign (1-' + sessionList.length + '), or press ESC to cancel:')); // Wait for user input await this.waitForSyncSelection(sessionList, 'reassign'); } catch (error) { this.clearScreen(); console.error(chalk.red('Error loading sync sessions:'), error.message); setTimeout(() => this.showSyncScreen(), 2000); } } /** * Wait for worker selection */ async waitForWorkerSelection(workers, action) { return new Promise((resolve) => { const tempHandler = (key) => { const char = key.toString(); if (char === '\x1b') { // ESC process.stdin.removeListener('data', tempHandler); this.showWorkersScreen(); resolve(); return; } const num = parseInt(char); if (num >= 1 && num <= workers.length) { const selectedWorker = workers[num - 1]; process.stdin.removeListener('data', tempHandler); if (action === 'remove') { this.executeWorkerRemoval(selectedWorker); } else if (action === 'reassign') { this.executeWorkerReassignment(selectedWorker); } resolve(); } }; process.stdin.on('data', tempHandler); }); } /** * Wait for sync selection */ async waitForSyncSelection(sessionList, action) { return new Promise((resolve) => { const tempHandler = (key) => { const char = key.toString(); if (char === '\x1b') { // ESC process.stdin.removeListener('data', tempHandler); this.showSyncScreen(); resolve(); return; } const num = parseInt(char); if (num >= 1 && num <= sessionList.length) { const [sessionId, session] = sessionList[num - 1]; process.stdin.removeListener('data', tempHandler); if (action === 'remove') { this.executeSyncRemoval(sessionId, session); } else if (action === 'reassign') { this.executeSyncReassignment(sessionId, session); } resolve(); } }; process.stdin.on('data', tempHandler); }); } /** * Execute worker removal */ async executeWorkerRemoval(worker) { try { this.clearScreen(); console.log(chalk.red.bold('🗑️ Removing Worker...')); console.log(chalk.gray('═'.repeat(40))); console.log(); console.log(`Worker: ${chalk.bold(worker.id)}`); console.log(`Group: ${chalk.cyan(worker.group)}`); console.log(); const result = await this.coordinatorCore.removeWorker(worker.id); if (result.success) { console.log(chalk.green('✅ Worker removed successfully')); } else { console.log(chalk.yellow('⚠️ Worker not found or already removed')); } setTimeout(() => this.showWorkersScreen(), 2000); } catch (error) { console.error(chalk.red('❌ Failed to remove worker:'), error.message); setTimeout(() => this.showWorkersScreen(), 3000); } } /** * Execute worker reassignment */ async executeWorkerReassignment(worker) { this.clearScreen(); console.log(chalk.blue.bold('🔄 Reassign Worker')); console.log(chalk.gray('═'.repeat(40))); console.log(); console.log(`Worker: ${chalk.bold(worker.id)}`); console.log(`Current Group: ${chalk.cyan(worker.group)}`); console.log(); console.log(chalk.yellow('Enter new group name (or press ESC to cancel):')); console.log(chalk.gray('Examples: FRONTEND, BACKEND, API, DATABASE, UI, etc.')); console.log(); return new Promise((resolve) => { let inputBuffer = ''; const tempHandler = async (key) => { const char = key.toString(); if (char === '\x1b') { // ESC process.stdin.removeListener('data', tempHandler); this.showWorkersScreen(); resolve(); return; } if (char === '\r' || char === '\n') { // Enter const newGroup = inputBuffer.trim().toUpperCase(); if (newGroup.length > 0) { process.stdin.removeListener('data', tempHandler); try { this.clearScreen(); console.log(chalk.blue.bold('🔄 Reassigning Worker...')); console.log(); console.log(`Worker: ${chalk.bold(worker.id)}`); console.log(`From: ${chalk.cyan(worker.group)} → To: ${chalk.cyan(newGroup)}`); console.log(); const result = await this.coordinatorCore.reassignWorker(worker.id, newGroup); if (result.success) { console.log(chalk.green('✅ Worker reassigned successfully')); } else { console.log(chalk.yellow('⚠️ Worker not found or reassignment failed')); } } catch (error) { console.error(chalk.red('❌ Failed to reassign worker:'), error.message); } setTimeout(() => this.showWorkersScreen(), 2000); resolve(); } return; } if (char === '\x7f' || char === '\b') { // Backspace if (inputBuffer.length > 0) { inputBuffer = inputBuffer.slice(0, -1); process.stdout.write('\b \b'); } return; } if (char.match(/[a-zA-Z0-9_-]/)) { inputBuffer += char; process.stdout.write(char); } }; process.stdin.on('data', tempHandler); }); } /** * Execute sync removal */ async executeSyncRemoval(sessionId, session) { try { this.clearScreen(); console.log(chalk.red.bold('🗑️ Removing Sync Session...')); console.log(chalk.gray('═'.repeat(40))); console.log(); const shortId = sessionId.replace(/^claude_/, '').substring(0, 25); console.log(`Session: ${chalk.bold(shortId)}`); console.log(`Group: ${chalk.cyan(session.group || 'Unknown')}`); console.log(); // Use the correct method name if (this.coordinatorCore.removeSyncSession) { const result = await this.coordinatorCore.removeSyncSession(sessionId); if (result.success) { console.log(chalk.green('✅ Sync session removed successfully')); } else { console.log(chalk.yellow('⚠️ Sync session not found or already removed')); } } else { console.log(chalk.yellow('⚠️ Sync session removal not supported in this version')); } setTimeout(() => this.showSyncScreen(), 2000); } catch (error) { console.error(chalk.red('❌ Failed to remove sync session:'), error.message); setTimeout(() => this.showSyncScreen(), 3000); } } /** * Execute sync reassignment */ async executeSyncReassignment(sessionId, session) { this.clearScreen(); console.log(chalk.blue.bold('🔄 Reassign Sync Session')); console.log(chalk.gray('═'.repeat(40))); console.log(); const shortId = sessionId.replace(/^claude_/, '').substring(0, 25); console.log(`Session: ${chalk.bold(shortId)}`); console.log(`Current Group: ${chalk.cyan(session.group || 'Unknown')}`); console.log(); console.log(chalk.yellow('Enter new group name (or press ESC to cancel):')); console.log(chalk.gray('Examples: FRONTEND, BACKEND, API, DATABASE, UI, etc.')); console.log(); return new Promise((resolve) => { let inputBuffer = ''; const tempHandler = async (key) => { const char = key.toString(); if (char === '\x1b') { // ESC process.stdin.removeListener('data', tempHandler); this.showSyncScreen(); resolve(); return; } if (char === '\r' || char === '\n') { // Enter const newGroup = inputBuffer.trim().toUpperCase(); if (newGroup.length > 0) { process.stdin.removeListener('data', tempHandler); try { this.clearScreen(); console.log(chalk.blue.bold('🔄 Reassigning Sync Session...')); console.log(); console.log(`Session: ${chalk.bold(shortId)}`); console.log(`From: ${chalk.cyan(session.group || 'Unknown')} → To: ${chalk.cyan(newGroup)}`); console.log(); // Use the correct method name if (this.coordinatorCore.reassignSyncSession) { const result = await this.coordinatorCore.reassignSyncSession(sessionId, newGroup); if (result.success) { console.log(chalk.green('✅ Sync session reassigned successfully')); } else { console.log(chalk.yellow('⚠️ Sync session not found or reassignment failed')); } } else { console.log(chalk.yellow('⚠️ Sync session reassignment not supported in this version')); } } catch (error) { console.error(chalk.red('❌ Failed to reassign sync session:'), error.message); } setTimeout(() => this.showSyncScreen(), 2000); resolve(); } return; } if (char === '\x7f' || char === '\b') { // Backspace if (inputBuffer.length > 0) { inputBuffer = inputBuffer.slice(0, -1); process.stdout.write('\b \b'); } return; } if (char.match(/[a-zA-Z0-9_-]/)) { inputBuffer += char; process.stdout.write(char); } }; process.stdin.on('data', tempHandler); }); } } module.exports = TerminalInterface;