UNPKG

claude-flow-tbowman01

Version:

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

493 lines 19.6 kB
/** * Monitor command for Claude-Flow - Live dashboard mode */ import { Command } from 'commander'; import { promises as fs } from 'node:fs'; import { existsSync } from 'fs'; import chalk from 'chalk'; import Table from 'cli-table3'; import { formatProgressBar, formatDuration, formatStatusIndicator } from '../formatter.js'; export const monitorCommand = new Command() .description('Start live monitoring dashboard') .option('-i, --interval <seconds>', 'Update interval in seconds', '2') .option('-c, --compact', 'Compact view mode') .option('--no-graphs', 'Disable ASCII graphs') .option('--focus <component:string>', 'Focus on specific component') .action(async (options) => { await startMonitorDashboard(options); }); class Dashboard { options; data = []; maxDataPoints = 60; // 2 minutes at 2-second intervals running = true; alerts = []; startTime = Date.now(); exportData = []; constructor(options) { this.options = options; this.options.threshold = this.options.threshold || 80; } async start() { // Hide cursor and clear screen process.stdout.write('\x1b[?25l'); console.clear(); // Setup signal handlers const cleanup = () => { this.running = false; process.stdout.write('\x1b[?25h'); // Show cursor console.log('\n' + chalk.gray('Monitor stopped')); process.exit(0); }; process.on('SIGINT', cleanup); process.on('SIGTERM', cleanup); // Start monitoring loop await this.monitoringLoop(); } async monitoringLoop() { while (this.running) { try { const data = await this.collectData(); this.data.push(data); // Keep only recent data points if (this.data.length > this.maxDataPoints) { this.data = this.data.slice(-this.maxDataPoints); } this.render(); await new Promise((resolve) => setTimeout(resolve, this.options.interval * 1000)); } catch (error) { this.renderError(error); await new Promise((resolve) => setTimeout(resolve, this.options.interval * 1000)); } } } async collectData() { // Mock data collection - in production, this would connect to the orchestrator const timestamp = new Date(); const cpuUsage = 10 + Math.random() * 20; // 10-30% const memoryUsage = 200 + Math.random() * 100; // 200-300MB return { timestamp, system: { cpu: cpuUsage, memory: memoryUsage, agents: 3 + Math.floor(Math.random() * 3), tasks: 5 + Math.floor(Math.random() * 10), }, components: { orchestrator: { status: 'healthy', load: Math.random() * 100 }, terminal: { status: 'healthy', load: Math.random() * 100 }, memory: { status: 'healthy', load: Math.random() * 100 }, coordination: { status: 'healthy', load: Math.random() * 100 }, mcp: { status: 'healthy', load: Math.random() * 100 }, }, agents: this.generateMockAgents(), tasks: this.generateMockTasks(), events: this.generateMockEvents(), }; } render() { console.clear(); const latest = this.data[this.data.length - 1]; if (!latest) return; // Header this.renderHeader(latest); if (this.options.focus) { this.renderFocusedComponent(latest, this.options.focus); } else { // System overview this.renderSystemOverview(latest); // Components status this.renderComponentsStatus(latest); if (!this.options.compact) { // Agents and tasks this.renderAgentsAndTasks(latest); // Recent events this.renderRecentEvents(latest); // Performance graphs if (!this.options.noGraphs) { this.renderPerformanceGraphs(); } } } // Footer this.renderFooter(); } renderHeader(data) { const time = data.timestamp.toLocaleTimeString(); console.log(chalk.cyan.bold('Claude-Flow Live Monitor') + chalk.gray(` - ${time}`)); console.log('═'.repeat(80)); } renderSystemOverview(data) { console.log(chalk.white.bold('System Overview')); console.log('─'.repeat(40)); const cpuBar = formatProgressBar(data.system.cpu, 100, 20, 'CPU'); const memoryBar = formatProgressBar(data.system.memory, 1024, 20, 'Memory'); console.log(`${cpuBar} ${data.system.cpu.toFixed(1)}%`); console.log(`${memoryBar} ${data.system.memory.toFixed(0)}MB`); console.log(`${chalk.white('Agents:')} ${data.system.agents} active`); console.log(`${chalk.white('Tasks:')} ${data.system.tasks} in queue`); console.log(); } renderComponentsStatus(data) { console.log(chalk.white.bold('Components')); console.log('─'.repeat(40)); const tableData = []; for (const [name, component] of Object.entries(data.components)) { const statusIcon = formatStatusIndicator(component.status); const loadBar = this.createMiniProgressBar(component.load, 100, 10); tableData.push({ Component: name, Status: `${statusIcon} ${component.status}`, Load: `${loadBar} ${component.load.toFixed(0)}%`, }); } console.table(tableData); console.log(); } renderAgentsAndTasks(data) { // Agents table console.log(chalk.white.bold('Active Agents')); console.log('─'.repeat(40)); if (data.agents.length > 0) { const agentTable = new Table({ head: ['Agent ID', 'Type', 'Status', 'Tasks'], style: { border: [], head: [] }, }); for (const agent of data.agents.slice(0, 5)) { const statusIcon = formatStatusIndicator(agent.status); agentTable.push([ chalk.gray(agent.id.substring(0, 8) + '...'), chalk.cyan(agent.type), `${statusIcon} ${agent.status}`, agent.activeTasks.toString(), ]); } console.log(agentTable.toString()); } else { console.log(chalk.gray('No active agents')); } console.log(); // Recent tasks console.log(chalk.white.bold('Recent Tasks')); console.log('─'.repeat(40)); if (data.tasks.length > 0) { const taskTable = new Table({ head: ['Task ID', 'Type', 'Status', 'Duration'], style: { border: [], head: [] }, }); for (const task of data.tasks.slice(0, 5)) { const statusIcon = formatStatusIndicator(task.status); taskTable.push([ chalk.gray(task.id.substring(0, 8) + '...'), chalk.white(task.type), `${statusIcon} ${task.status}`, task.duration ? formatDuration(task.duration) : '-', ]); } console.log(taskTable.toString()); } else { console.log(chalk.gray('No recent tasks')); } console.log(); } renderRecentEvents(data) { console.log(chalk.white.bold('Recent Events')); console.log('─'.repeat(40)); if (data.events.length > 0) { for (const event of data.events.slice(0, 3)) { const time = new Date(event.timestamp).toLocaleTimeString(); const icon = this.getEventIcon(event.type); console.log(`${chalk.gray(time)} ${icon} ${event.message}`); } } else { console.log(chalk.gray('No recent events')); } console.log(); } renderPerformanceGraphs() { console.log(chalk.white.bold('Performance (Last 60s)')); console.log('─'.repeat(40)); if (this.data.length >= 2) { // CPU graph console.log(chalk.cyan('CPU Usage:')); console.log(this.createSparkline(this.data.map((d) => d.system.cpu), 30)); // Memory graph console.log(chalk.cyan('Memory Usage:')); console.log(this.createSparkline(this.data.map((d) => d.system.memory), 30)); } else { console.log(chalk.gray('Collecting data...')); } console.log(); } renderFocusedComponent(data, componentName) { const component = data.components[componentName]; if (!component) { console.log(chalk.red(`Component '${componentName}' not found`)); return; } console.log(chalk.white.bold(`${componentName} Details`)); console.log('─'.repeat(40)); const statusIcon = formatStatusIndicator(component.status); console.log(`${statusIcon} Status: ${component.status}`); console.log(`Load: ${formatProgressBar(component.load, 100, 30)} ${component.load.toFixed(1)}%`); // Add component-specific metrics here console.log(); } renderFooter() { console.log('─'.repeat(80)); console.log(chalk.gray('Press Ctrl+C to exit • Update interval: ') + chalk.yellow(`${this.options.interval}s`)); } renderError(error) { console.clear(); console.log(chalk.red.bold('Monitor Error')); console.log('─'.repeat(40)); if (error.message.includes('ECONNREFUSED')) { console.log(chalk.red('✗ Cannot connect to Claude-Flow')); console.log(chalk.gray('Make sure Claude-Flow is running with: claude-flow start')); } else { console.log(chalk.red('Error:'), error.message); } console.log('\n' + chalk.gray('Retrying in ') + chalk.yellow(`${this.options.interval}s...`)); } createMiniProgressBar(current, max, width) { const filled = Math.floor((current / max) * width); const empty = width - filled; return chalk.green('█'.repeat(filled)) + chalk.gray('░'.repeat(empty)); } createSparkline(data, width) { if (data.length < 2) return chalk.gray('▁'.repeat(width)); const max = Math.max(...data); const min = Math.min(...data); const range = max - min || 1; const chars = ['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█']; const recent = data.slice(-width); return recent .map((value) => { const normalized = (value - min) / range; const charIndex = Math.floor(normalized * (chars.length - 1)); return chalk.cyan(chars[charIndex]); }) .join(''); } getEventIcon(type) { const icons = { agent_spawned: chalk.green('↗'), agent_terminated: chalk.red('↙'), task_completed: chalk.green('✓'), task_failed: chalk.red('✗'), task_assigned: chalk.blue('→'), system_warning: chalk.yellow('⚠'), system_error: chalk.red('✗'), }; return icons[type] || chalk.blue('•'); } generateMockAgents() { return [ { id: 'agent-001', type: 'coordinator', status: 'active', activeTasks: Math.floor(Math.random() * 5) + 1, }, { id: 'agent-002', type: 'researcher', status: 'active', activeTasks: Math.floor(Math.random() * 8) + 1, }, { id: 'agent-003', type: 'implementer', status: Math.random() > 0.7 ? 'idle' : 'active', activeTasks: Math.floor(Math.random() * 3), }, ]; } generateMockTasks() { const types = ['research', 'implementation', 'analysis', 'coordination']; const statuses = ['running', 'pending', 'completed', 'failed']; return Array.from({ length: 8 }, (_, i) => ({ id: `task-${String(i + 1).padStart(3, '0')}`, type: types[Math.floor(Math.random() * types.length)], status: statuses[Math.floor(Math.random() * statuses.length)], duration: Math.random() > 0.5 ? Math.floor(Math.random() * 120000) : null, })); } generateMockEvents() { const events = [ { type: 'task_completed', message: 'Research task completed successfully' }, { type: 'agent_spawned', message: 'New implementer agent spawned' }, { type: 'task_assigned', message: 'Task assigned to coordinator agent' }, { type: 'system_warning', message: 'High memory usage detected' }, ]; const eventTypes = [ { type: 'task_completed', message: 'Research task completed successfully', level: 'info', }, { type: 'agent_spawned', message: 'New implementer agent spawned', level: 'info' }, { type: 'task_assigned', message: 'Task assigned to coordinator agent', level: 'info', }, { type: 'system_warning', message: 'High memory usage detected', level: 'warn' }, { type: 'task_failed', message: 'Analysis task failed due to timeout', level: 'error', }, { type: 'system_info', message: 'System health check completed', level: 'info' }, { type: 'memory_gc', message: 'Garbage collection triggered', level: 'debug' }, { type: 'network_event', message: 'MCP connection established', level: 'info' }, ]; const components = ['orchestrator', 'terminal', 'memory', 'coordination', 'mcp']; return Array.from({ length: 6 + Math.floor(Math.random() * 4) }, (_, i) => { const event = eventTypes[Math.floor(Math.random() * eventTypes.length)]; return { ...event, timestamp: Date.now() - i * Math.random() * 300000, // Random intervals up to 5 minutes component: Math.random() > 0.3 ? components[Math.floor(Math.random() * components.length)] : undefined, }; }).sort((a, b) => b.timestamp - a.timestamp); } async checkSystemRunning() { try { return await existsSync('.claude-flow.pid'); } catch { return false; } } async getRealSystemData() { // This would connect to the actual orchestrator for real data // For now, return null to use mock data return null; } generateComponentStatus() { const components = ['orchestrator', 'terminal', 'memory', 'coordination', 'mcp']; const statuses = ['healthy', 'degraded', 'error']; const result = {}; for (const component of components) { const status = statuses[Math.floor(Math.random() * statuses.length)]; const hasErrors = Math.random() > 0.8; result[component] = { status: status, load: Math.random() * 100, uptime: Math.random() * 3600000, // Up to 1 hour errors: hasErrors ? Math.floor(Math.random() * 5) : 0, lastError: hasErrors ? 'Connection timeout' : undefined, }; } return result; } checkAlerts(data) { const newAlerts = []; // Check system thresholds if (data.system.cpu > this.options.threshold) { newAlerts.push({ id: 'cpu-high', type: 'warning', message: `CPU usage high: ${data.system.cpu.toFixed(1)}%`, component: 'system', timestamp: Date.now(), acknowledged: false, }); } if (data.system.memory > 800) { newAlerts.push({ id: 'memory-high', type: 'warning', message: `Memory usage high: ${data.system.memory.toFixed(0)}MB`, component: 'system', timestamp: Date.now(), acknowledged: false, }); } // Check component status for (const [name, component] of Object.entries(data.components)) { if (component.status === 'error') { newAlerts.push({ id: `component-error-${name}`, type: 'error', message: `Component ${name} is in error state`, component: name, timestamp: Date.now(), acknowledged: false, }); } if (component.load > this.options.threshold) { newAlerts.push({ id: `component-load-${name}`, type: 'warning', message: `Component ${name} load high: ${component.load.toFixed(1)}%`, component: name, timestamp: Date.now(), acknowledged: false, }); } } // Update alerts list (keep only recent ones) this.alerts = [...this.alerts, ...newAlerts] .filter((alert) => Date.now() - alert.timestamp < 300000) // 5 minutes .slice(-10); // Keep max 10 alerts } async exportMonitoringData() { try { const exportData = { metadata: { exportTime: new Date().toISOString(), duration: formatDuration(Date.now() - this.startTime), dataPoints: this.exportData.length, interval: this.options.interval, }, data: this.exportData, alerts: this.alerts, }; await fs.writeFile(this.options.export, JSON.stringify(exportData, null, 2)); console.log(chalk.green(`✓ Monitoring data exported to ${this.options.export}`)); } catch (error) { console.error(chalk.red('Failed to export data:'), error.message); } } } async function startMonitorDashboard(options) { // Validate options if (options.interval < 1) { console.error(chalk.red('Update interval must be at least 1 second')); return; } if (options.threshold < 1 || options.threshold > 100) { console.error(chalk.red('Threshold must be between 1 and 100')); return; } if (options.export) { // Check if export path is writable try { await fs.writeFile(options.export, ''); await Deno.remove(options.export); } catch { console.error(chalk.red(`Cannot write to export file: ${options.export}`)); return; } } const dashboard = new Dashboard(options); await dashboard.start(); } //# sourceMappingURL=monitor.js.map