claude-flow-tbowman01
Version:
Enterprise-grade AI agent orchestration with ruv-swarm integration (Alpha Release)
493 lines • 19.6 kB
JavaScript
/**
* 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