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
JavaScript
/**
* 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;