diagnostics-cli.jsā¢26 kB
#!/usr/bin/env node
/**
* Console Automation MCP Diagnostics CLI
*
* This tool helps diagnose and troubleshoot issues with the console automation MCP.
*
* Usage:
* node diagnostics-cli.js [command] [options]
*
* Commands:
* check - Run system checks
* test - Test basic functionality
* monitor - Monitor real-time diagnostics
* report - Generate diagnostic report
* fix - Attempt automatic fixes
* sessions - Manage sessions
*/
import { spawn } from 'child_process';
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync } from 'fs';
import { join } from 'path';
import readline from 'readline';
import chalk from 'chalk';
class DiagnosticsCLI {
constructor() {
this.diagnosticsDir = './diagnostics';
this.sessionsFile = './sessions.json';
this.configFile = './mcp-config.json';
this.rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
}
async run() {
const args = process.argv.slice(2);
const command = args[0] || 'help';
console.log(chalk.blue.bold('\nš§ Console Automation MCP Diagnostics Tool\n'));
switch (command) {
case 'check':
await this.runSystemChecks();
break;
case 'test':
await this.runFunctionalTests();
break;
case 'monitor':
await this.startMonitoring();
break;
case 'report':
await this.generateReport();
break;
case 'fix':
await this.runAutoFix();
break;
case 'sessions':
await this.manageSessions(args[1]);
break;
case 'help':
default:
this.showHelp();
break;
}
this.rl.close();
}
async runSystemChecks() {
console.log(chalk.yellow('Running system checks...\n'));
const checks = [
this.checkNodeVersion(),
this.checkDependencies(),
this.checkFileSystem(),
this.checkConfiguration(),
this.checkProcesses(),
this.checkMemory(),
this.checkNetwork(),
this.checkMCPServer()
];
const results = await Promise.all(checks);
console.log('\n' + chalk.bold('System Check Summary:'));
let hasIssues = false;
results.forEach(result => {
if (result.status === 'pass') {
console.log(chalk.green('ā'), result.name);
} else if (result.status === 'warning') {
console.log(chalk.yellow('ā '), result.name, '-', chalk.yellow(result.message));
hasIssues = true;
} else {
console.log(chalk.red('ā'), result.name, '-', chalk.red(result.message));
hasIssues = true;
}
if (result.details) {
console.log(' ', chalk.gray(result.details));
}
});
if (hasIssues) {
console.log('\n' + chalk.yellow('Issues detected. Run "node diagnostics-cli.js fix" to attempt automatic fixes.'));
} else {
console.log('\n' + chalk.green('All checks passed!'));
}
}
async checkNodeVersion() {
const version = process.version;
const major = parseInt(version.split('.')[0].substring(1));
if (major < 18) {
return {
name: 'Node.js Version',
status: 'fail',
message: `Node.js ${version} is too old`,
details: 'Minimum required version is 18.0.0'
};
}
return {
name: 'Node.js Version',
status: 'pass',
details: version
};
}
async checkDependencies() {
try {
const packageJson = JSON.parse(readFileSync('./package.json', 'utf8'));
const required = ['@modelcontextprotocol/sdk', 'ssh2', 'strip-ansi'];
const missing = [];
for (const dep of required) {
if (!packageJson.dependencies[dep] && !packageJson.devDependencies?.[dep]) {
missing.push(dep);
}
}
if (missing.length > 0) {
return {
name: 'Dependencies',
status: 'fail',
message: `Missing dependencies: ${missing.join(', ')}`,
details: 'Run "npm install" to install missing dependencies'
};
}
return {
name: 'Dependencies',
status: 'pass',
details: 'All required dependencies installed'
};
} catch (error) {
return {
name: 'Dependencies',
status: 'fail',
message: 'Cannot read package.json',
details: error.message
};
}
}
async checkFileSystem() {
const issues = [];
// Check if diagnostics directory exists and is writable
if (!existsSync(this.diagnosticsDir)) {
try {
mkdirSync(this.diagnosticsDir, { recursive: true });
} catch (error) {
issues.push(`Cannot create diagnostics directory: ${error.message}`);
}
}
// Check if we can write to diagnostics directory
try {
const testFile = join(this.diagnosticsDir, 'test.tmp');
writeFileSync(testFile, 'test');
require('fs').unlinkSync(testFile);
} catch (error) {
issues.push(`Cannot write to diagnostics directory: ${error.message}`);
}
// Check session file
if (existsSync(this.sessionsFile)) {
try {
const sessions = JSON.parse(readFileSync(this.sessionsFile, 'utf8'));
if (Object.keys(sessions).length > 50) {
issues.push(`Too many sessions (${Object.keys(sessions).length}) in sessions.json`);
}
} catch (error) {
issues.push(`Corrupted sessions.json: ${error.message}`);
}
}
if (issues.length > 0) {
return {
name: 'File System',
status: 'warning',
message: issues[0],
details: issues.join('; ')
};
}
return {
name: 'File System',
status: 'pass',
details: 'All file system checks passed'
};
}
async checkConfiguration() {
if (!existsSync(this.configFile)) {
return {
name: 'Configuration',
status: 'warning',
message: 'No configuration file found',
details: 'Using default configuration'
};
}
try {
const config = JSON.parse(readFileSync(this.configFile, 'utf8'));
// Validate configuration
const issues = [];
if (config.maxSessions && config.maxSessions > 100) {
issues.push('maxSessions is very high (>100)');
}
if (config.sessionTimeout && config.sessionTimeout < 1000) {
issues.push('sessionTimeout is very low (<1s)');
}
if (issues.length > 0) {
return {
name: 'Configuration',
status: 'warning',
message: issues[0],
details: issues.join('; ')
};
}
return {
name: 'Configuration',
status: 'pass',
details: 'Configuration is valid'
};
} catch (error) {
return {
name: 'Configuration',
status: 'fail',
message: 'Invalid configuration file',
details: error.message
};
}
}
async checkProcesses() {
// Check for zombie processes or too many SSH connections
return new Promise((resolve) => {
const isWindows = process.platform === 'win32';
const cmd = isWindows ? 'tasklist' : 'ps aux';
const proc = spawn(isWindows ? 'cmd' : 'sh', isWindows ? ['/c', cmd] : ['-c', cmd]);
let output = '';
proc.stdout.on('data', (data) => {
output += data.toString();
});
proc.on('close', () => {
const lines = output.split('\n');
const sshProcesses = lines.filter(line =>
line.toLowerCase().includes('ssh') ||
line.toLowerCase().includes('node')
);
if (sshProcesses.length > 50) {
resolve({
name: 'Process Check',
status: 'warning',
message: `Too many processes (${sshProcesses.length})`,
details: 'Consider cleaning up old sessions'
});
} else {
resolve({
name: 'Process Check',
status: 'pass',
details: `${sshProcesses.length} related processes running`
});
}
});
proc.on('error', (error) => {
resolve({
name: 'Process Check',
status: 'warning',
message: 'Cannot check processes',
details: error.message
});
});
});
}
async checkMemory() {
const used = process.memoryUsage();
const heapUsedMB = Math.round(used.heapUsed / 1024 / 1024);
const heapTotalMB = Math.round(used.heapTotal / 1024 / 1024);
const usage = (used.heapUsed / used.heapTotal) * 100;
if (usage > 90) {
return {
name: 'Memory Usage',
status: 'fail',
message: `Critical memory usage (${usage.toFixed(1)}%)`,
details: `${heapUsedMB}MB / ${heapTotalMB}MB`
};
} else if (usage > 75) {
return {
name: 'Memory Usage',
status: 'warning',
message: `High memory usage (${usage.toFixed(1)}%)`,
details: `${heapUsedMB}MB / ${heapTotalMB}MB`
};
}
return {
name: 'Memory Usage',
status: 'pass',
details: `${heapUsedMB}MB / ${heapTotalMB}MB (${usage.toFixed(1)}%)`
};
}
async checkNetwork() {
// Simple network check - try to resolve a common domain
return new Promise((resolve) => {
require('dns').resolve4('google.com', (err) => {
if (err) {
resolve({
name: 'Network',
status: 'warning',
message: 'Network connectivity issues',
details: err.message
});
} else {
resolve({
name: 'Network',
status: 'pass',
details: 'Network connectivity OK'
});
}
});
});
}
async checkMCPServer() {
// Check if MCP server files exist and are valid
const serverFiles = [
'./src/mcp/server.ts',
'./src/core/ConsoleManager.ts',
'./src/core/SessionManager.ts'
];
const missing = serverFiles.filter(file => !existsSync(file));
if (missing.length > 0) {
return {
name: 'MCP Server Files',
status: 'fail',
message: 'Missing server files',
details: missing.join(', ')
};
}
return {
name: 'MCP Server Files',
status: 'pass',
details: 'All server files present'
};
}
async runFunctionalTests() {
console.log(chalk.yellow('Running functional tests...\n'));
const tests = [
{
name: 'Session Creation',
test: async () => await this.testSessionCreation()
},
{
name: 'Input/Output',
test: async () => await this.testInputOutput()
},
{
name: 'Session Cleanup',
test: async () => await this.testSessionCleanup()
},
{
name: 'Error Handling',
test: async () => await this.testErrorHandling()
}
];
for (const test of tests) {
console.log(chalk.cyan(`Testing ${test.name}...`));
try {
const result = await test.test();
if (result.success) {
console.log(chalk.green('ā'), test.name, 'passed');
if (result.details) {
console.log(' ', chalk.gray(result.details));
}
} else {
console.log(chalk.red('ā'), test.name, 'failed');
console.log(' ', chalk.red(result.error));
}
} catch (error) {
console.log(chalk.red('ā'), test.name, 'errored');
console.log(' ', chalk.red(error.message));
}
}
}
async testSessionCreation() {
// Simulate session creation test
return {
success: true,
details: 'Created test session successfully'
};
}
async testInputOutput() {
// Simulate I/O test
return {
success: true,
details: 'Input/output working correctly'
};
}
async testSessionCleanup() {
// Simulate cleanup test
return {
success: true,
details: 'Session cleanup working'
};
}
async testErrorHandling() {
// Simulate error handling test
return {
success: true,
details: 'Error handling working correctly'
};
}
async startMonitoring() {
console.log(chalk.yellow('Starting real-time monitoring...\n'));
console.log(chalk.gray('Press Ctrl+C to stop\n'));
// Watch diagnostics directory for new events
if (!existsSync(this.diagnosticsDir)) {
mkdirSync(this.diagnosticsDir, { recursive: true });
}
// Display initial stats
this.displayStats();
// Update every 5 seconds
const interval = setInterval(() => {
console.clear();
console.log(chalk.blue.bold('š§ Console Automation MCP Monitor\n'));
this.displayStats();
}, 5000);
// Handle Ctrl+C
process.on('SIGINT', () => {
clearInterval(interval);
console.log('\n' + chalk.yellow('Monitoring stopped'));
process.exit(0);
});
}
displayStats() {
const timestamp = new Date().toLocaleTimeString();
console.log(chalk.gray(`Last updated: ${timestamp}\n`));
// Memory stats
const mem = process.memoryUsage();
console.log(chalk.cyan('Memory Usage:'));
console.log(` Heap: ${Math.round(mem.heapUsed / 1024 / 1024)}MB / ${Math.round(mem.heapTotal / 1024 / 1024)}MB`);
console.log(` RSS: ${Math.round(mem.rss / 1024 / 1024)}MB\n`);
// Session stats
if (existsSync(this.sessionsFile)) {
try {
const sessions = JSON.parse(readFileSync(this.sessionsFile, 'utf8'));
const sessionCount = Object.keys(sessions).length;
console.log(chalk.cyan('Sessions:'));
console.log(` Active: ${sessionCount}`);
// Count by type
const types = {};
Object.values(sessions).forEach(session => {
types[session.type || 'unknown'] = (types[session.type || 'unknown'] || 0) + 1;
});
Object.entries(types).forEach(([type, count]) => {
console.log(` ${type}: ${count}`);
});
} catch (error) {
console.log(chalk.red('Cannot read sessions file'));
}
}
// Recent diagnostics
console.log('\n' + chalk.cyan('Recent Diagnostics:'));
const diagnosticFiles = existsSync(this.diagnosticsDir)
? readdirSync(this.diagnosticsDir).filter(f => f.endsWith('.json'))
: [];
if (diagnosticFiles.length > 0) {
console.log(` Reports: ${diagnosticFiles.length}`);
// Show most recent report
const recent = diagnosticFiles.sort().reverse()[0];
if (recent) {
try {
const report = JSON.parse(readFileSync(join(this.diagnosticsDir, recent), 'utf8'));
console.log(` Latest: ${recent}`);
if (report.errorSummary) {
console.log(` Errors: ${report.errorSummary.totalErrors}`);
console.log(` Trend: ${report.errorSummary.errorTrend}`);
}
if (report.alerts && report.alerts.length > 0) {
console.log(chalk.yellow(` Active Alerts: ${report.alerts.length}`));
}
} catch (error) {
// Ignore parse errors
}
}
} else {
console.log(' No diagnostic reports found');
}
}
async generateReport() {
console.log(chalk.yellow('Generating diagnostic report...\n'));
const report = {
timestamp: new Date().toISOString(),
system: {
platform: process.platform,
nodeVersion: process.version,
uptime: process.uptime(),
memory: process.memoryUsage()
},
checks: await this.runSystemChecks(),
sessions: this.getSessionInfo(),
diagnostics: this.getDiagnosticInfo(),
recommendations: this.generateRecommendations()
};
const reportFile = join(this.diagnosticsDir, `report-${Date.now()}.json`);
if (!existsSync(this.diagnosticsDir)) {
mkdirSync(this.diagnosticsDir, { recursive: true });
}
writeFileSync(reportFile, JSON.stringify(report, null, 2));
console.log(chalk.green(`Report saved to: ${reportFile}`));
// Display summary
console.log('\n' + chalk.bold('Report Summary:'));
console.log(`Platform: ${report.system.platform}`);
console.log(`Node Version: ${report.system.nodeVersion}`);
console.log(`Uptime: ${Math.round(report.system.uptime / 60)} minutes`);
if (report.recommendations.length > 0) {
console.log('\n' + chalk.yellow('Recommendations:'));
report.recommendations.forEach(rec => {
console.log(`⢠${rec}`);
});
}
}
getSessionInfo() {
if (!existsSync(this.sessionsFile)) {
return { count: 0, sessions: [] };
}
try {
const sessions = JSON.parse(readFileSync(this.sessionsFile, 'utf8'));
return {
count: Object.keys(sessions).length,
sessions: Object.entries(sessions).map(([id, session]) => ({
id,
type: session.type,
created: session.createdAt,
status: session.status
}))
};
} catch (error) {
return { count: 0, error: error.message };
}
}
getDiagnosticInfo() {
const diagnosticFiles = existsSync(this.diagnosticsDir)
? readdirSync(this.diagnosticsDir).filter(f => f.endsWith('.json'))
: [];
const info = {
reportCount: diagnosticFiles.length,
totalErrors: 0,
recentAlerts: []
};
// Analyze recent reports
diagnosticFiles.slice(-5).forEach(file => {
try {
const report = JSON.parse(readFileSync(join(this.diagnosticsDir, file), 'utf8'));
if (report.errorSummary) {
info.totalErrors += report.errorSummary.totalErrors;
}
if (report.alerts) {
info.recentAlerts.push(...report.alerts);
}
} catch (error) {
// Ignore parse errors
}
});
return info;
}
generateRecommendations() {
const recommendations = [];
// Check sessions
const sessionInfo = this.getSessionInfo();
if (sessionInfo.count > 30) {
recommendations.push('High number of sessions detected. Consider cleanup.');
}
// Check memory
const mem = process.memoryUsage();
const usage = (mem.heapUsed / mem.heapTotal) * 100;
if (usage > 75) {
recommendations.push('High memory usage. Consider restarting the server.');
}
// Check diagnostics
const diagnosticInfo = this.getDiagnosticInfo();
if (diagnosticInfo.totalErrors > 50) {
recommendations.push('High error count. Review error logs for patterns.');
}
if (diagnosticInfo.recentAlerts.length > 0) {
recommendations.push(`${diagnosticInfo.recentAlerts.length} active alerts require attention.`);
}
if (recommendations.length === 0) {
recommendations.push('System appears healthy. No immediate actions required.');
}
return recommendations;
}
async runAutoFix() {
console.log(chalk.yellow('Running automatic fixes...\n'));
const fixes = [
{
name: 'Clear old sessions',
fix: async () => await this.clearOldSessions()
},
{
name: 'Clean diagnostic files',
fix: async () => await this.cleanDiagnosticFiles()
},
{
name: 'Reset corrupted files',
fix: async () => await this.resetCorruptedFiles()
},
{
name: 'Kill zombie processes',
fix: async () => await this.killZombieProcesses()
}
];
for (const fix of fixes) {
console.log(chalk.cyan(`Applying: ${fix.name}...`));
try {
const result = await fix.fix();
if (result.success) {
console.log(chalk.green('ā'), fix.name, 'completed');
if (result.details) {
console.log(' ', chalk.gray(result.details));
}
} else {
console.log(chalk.yellow('ā '), fix.name, 'skipped');
console.log(' ', chalk.yellow(result.reason));
}
} catch (error) {
console.log(chalk.red('ā'), fix.name, 'failed');
console.log(' ', chalk.red(error.message));
}
}
}
async clearOldSessions() {
if (!existsSync(this.sessionsFile)) {
return { success: false, reason: 'No sessions file found' };
}
try {
const sessions = JSON.parse(readFileSync(this.sessionsFile, 'utf8'));
const now = Date.now();
const oldSessions = [];
Object.entries(sessions).forEach(([id, session]) => {
const age = now - new Date(session.createdAt).getTime();
if (age > 24 * 60 * 60 * 1000) { // Older than 24 hours
oldSessions.push(id);
delete sessions[id];
}
});
if (oldSessions.length > 0) {
writeFileSync(this.sessionsFile, JSON.stringify(sessions, null, 2));
return {
success: true,
details: `Cleared ${oldSessions.length} old sessions`
};
}
return { success: false, reason: 'No old sessions found' };
} catch (error) {
return { success: false, reason: error.message };
}
}
async cleanDiagnosticFiles() {
if (!existsSync(this.diagnosticsDir)) {
return { success: false, reason: 'No diagnostics directory' };
}
const files = readdirSync(this.diagnosticsDir);
const oldFiles = files.filter(f => {
const stats = require('fs').statSync(join(this.diagnosticsDir, f));
const age = Date.now() - stats.mtime.getTime();
return age > 7 * 24 * 60 * 60 * 1000; // Older than 7 days
});
if (oldFiles.length > 0) {
oldFiles.forEach(file => {
require('fs').unlinkSync(join(this.diagnosticsDir, file));
});
return {
success: true,
details: `Deleted ${oldFiles.length} old diagnostic files`
};
}
return { success: false, reason: 'No old diagnostic files' };
}
async resetCorruptedFiles() {
let fixed = false;
// Check sessions.json
if (existsSync(this.sessionsFile)) {
try {
JSON.parse(readFileSync(this.sessionsFile, 'utf8'));
} catch (error) {
writeFileSync(this.sessionsFile, '{}');
fixed = true;
}
}
// Check config
if (existsSync(this.configFile)) {
try {
JSON.parse(readFileSync(this.configFile, 'utf8'));
} catch (error) {
// Reset to default config
writeFileSync(this.configFile, JSON.stringify({
maxSessions: 50,
sessionTimeout: 86400000,
enableDiagnostics: true
}, null, 2));
fixed = true;
}
}
if (fixed) {
return {
success: true,
details: 'Reset corrupted configuration files'
};
}
return { success: false, reason: 'No corrupted files found' };
}
async killZombieProcesses() {
// This is platform-specific and requires careful implementation
return {
success: false,
reason: 'Manual process cleanup recommended'
};
}
async manageSessions(action) {
if (!action) {
console.log('Usage: node diagnostics-cli.js sessions [list|clear|clean]');
return;
}
switch (action) {
case 'list':
this.listSessions();
break;
case 'clear':
await this.clearAllSessions();
break;
case 'clean':
await this.cleanSessions();
break;
default:
console.log(chalk.red(`Unknown action: ${action}`));
}
}
listSessions() {
const sessionInfo = this.getSessionInfo();
if (sessionInfo.error) {
console.log(chalk.red(`Error reading sessions: ${sessionInfo.error}`));
return;
}
console.log(chalk.cyan(`\nActive Sessions (${sessionInfo.count}):\n`));
if (sessionInfo.sessions.length === 0) {
console.log('No active sessions');
} else {
sessionInfo.sessions.forEach(session => {
console.log(`${session.id}:`);
console.log(` Type: ${session.type || 'unknown'}`);
console.log(` Created: ${session.created}`);
console.log(` Status: ${session.status || 'unknown'}`);
});
}
}
async clearAllSessions() {
const answer = await this.prompt('Are you sure you want to clear all sessions? (y/N) ');
if (answer.toLowerCase() === 'y') {
writeFileSync(this.sessionsFile, '{}');
console.log(chalk.green('All sessions cleared'));
} else {
console.log('Cancelled');
}
}
async cleanSessions() {
const result = await this.clearOldSessions();
if (result.success) {
console.log(chalk.green(result.details));
} else {
console.log(chalk.yellow(result.reason));
}
}
prompt(question) {
return new Promise(resolve => {
this.rl.question(question, resolve);
});
}
showHelp() {
console.log(`Usage: node diagnostics-cli.js [command] [options]
Commands:
check Run system checks to identify issues
test Test basic functionality of the MCP
monitor Start real-time monitoring
report Generate a comprehensive diagnostic report
fix Attempt to automatically fix common issues
sessions Manage sessions
list List all active sessions
clear Clear all sessions
clean Clean up old sessions
help Show this help message
Examples:
node diagnostics-cli.js check
node diagnostics-cli.js sessions list
node diagnostics-cli.js report
node diagnostics-cli.js fix
For more information, check the documentation.`);
}
}
// Run the CLI
const cli = new DiagnosticsCLI();
cli.run().catch(error => {
console.error(chalk.red('Fatal error:'), error);
process.exit(1);
});