UNPKG

claude-code-subagents-orchestrator

Version:

Claude Code Sub-agents Orchestrator - A powerful MCP server for orchestrating multiple AI sub-agents for complex task execution in Claude Code

496 lines (411 loc) • 16.3 kB
#!/usr/bin/env node /** * Health Check and Diagnostic Tool * Comprehensive system diagnostics for Claude Code Subagents Orchestrator */ import fs from 'fs-extra'; import path from 'path'; import os from 'os'; import chalk from 'chalk'; import { spawn } from 'child_process'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // Configuration const REQUIRED_NODE_VERSION = '18.0.0'; const REQUIRED_NPM_VERSION = '8.0.0'; const PACKAGE_NAME = 'claude-code-subagents-orchestrator'; // Platform-specific paths const CLAUDE_CONFIG_PATHS = { win32: path.join(os.homedir(), 'AppData', 'Roaming', 'Claude', 'claude_desktop_config.json'), darwin: path.join(os.homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json'), linux: path.join(os.homedir(), '.config', 'claude', 'claude_desktop_config.json') }; class HealthChecker { constructor(options = {}) { this.verbose = options.verbose || false; this.json = options.json || false; this.fix = options.fix || false; this.platform = os.platform(); this.results = { timestamp: new Date().toISOString(), platform: this.platform, checks: [], summary: { total: 0, passed: 0, failed: 0, warnings: 0 }, recommendations: [] }; } log(message, level = 'info') { if (this.json) return; const colors = { info: chalk.blue, success: chalk.green, warning: chalk.yellow, error: chalk.red, verbose: chalk.gray }; const color = colors[level] || chalk.white; console.log(color(message)); } addCheck(name, status, message, details = {}) { const check = { name, status, // 'pass', 'fail', 'warning' message, details, timestamp: new Date().toISOString() }; this.results.checks.push(check); this.results.summary.total++; switch (status) { case 'pass': this.results.summary.passed++; this.log(`āœ“ ${name}: ${message}`, 'success'); break; case 'fail': this.results.summary.failed++; this.log(`āœ— ${name}: ${message}`, 'error'); break; case 'warning': this.results.summary.warnings++; this.log(`⚠ ${name}: ${message}`, 'warning'); break; } if (this.verbose && Object.keys(details).length > 0) { this.log(JSON.stringify(details, null, 2), 'verbose'); } } addRecommendation(recommendation) { this.results.recommendations.push(recommendation); if (!this.json) { this.log(`šŸ’” ${recommendation}`, 'warning'); } } async runCommand(command, args = []) { return new Promise((resolve, reject) => { const child = spawn(command, args, { stdio: 'pipe' }); let stdout = ''; let stderr = ''; child.stdout.on('data', (data) => { stdout += data.toString(); }); child.stderr.on('data', (data) => { stderr += data.toString(); }); child.on('close', (code) => { resolve({ code, stdout: stdout.trim(), stderr: stderr.trim() }); }); child.on('error', (error) => { reject(error); }); }); } compareVersions(version1, version2) { const v1 = version1.split('.').map(Number); const v2 = version2.split('.').map(Number); for (let i = 0; i < Math.max(v1.length, v2.length); i++) { const a = v1[i] || 0; const b = v2[i] || 0; if (a > b) return 1; if (a < b) return -1; } return 0; } async checkNodeJs() { try { const result = await this.runCommand('node', ['--version']); if (result.code !== 0) { this.addCheck('Node.js', 'fail', 'Node.js not found or not working'); return; } const version = result.stdout.replace('v', ''); const isCompatible = this.compareVersions(version, REQUIRED_NODE_VERSION) >= 0; if (isCompatible) { this.addCheck('Node.js', 'pass', `Version ${version} (compatible)`, { version }); } else { this.addCheck('Node.js', 'fail', `Version ${version} (requires ${REQUIRED_NODE_VERSION}+)`, { version, required: REQUIRED_NODE_VERSION }); this.addRecommendation(`Update Node.js to version ${REQUIRED_NODE_VERSION} or higher`); } } catch (error) { this.addCheck('Node.js', 'fail', 'Node.js not installed', { error: error.message }); this.addRecommendation('Install Node.js from https://nodejs.org/'); } } async checkNpm() { try { const result = await this.runCommand('npm', ['--version']); if (result.code !== 0) { this.addCheck('npm', 'fail', 'npm not found or not working'); return; } const version = result.stdout; const isCompatible = this.compareVersions(version, REQUIRED_NPM_VERSION) >= 0; if (isCompatible) { this.addCheck('npm', 'pass', `Version ${version} (compatible)`, { version }); } else { this.addCheck('npm', 'warning', `Version ${version} (recommended ${REQUIRED_NPM_VERSION}+)`, { version, recommended: REQUIRED_NPM_VERSION }); this.addRecommendation(`Update npm with: npm install -g npm@latest`); } } catch (error) { this.addCheck('npm', 'fail', 'npm not installed', { error: error.message }); this.addRecommendation('Install npm (usually comes with Node.js)'); } } async checkPackageInstallation() { try { // Check global installation const result = await this.runCommand('npm', ['list', '-g', PACKAGE_NAME, '--depth=0']); if (result.code === 0) { const match = result.stdout.match(new RegExp(`${PACKAGE_NAME}@([\\d\\.]+)`)); const version = match ? match[1] : 'unknown'; this.addCheck('Package Installation', 'pass', `Globally installed (v${version})`, { version, type: 'global' }); } else { this.addCheck('Package Installation', 'fail', 'Not globally installed'); this.addRecommendation(`Install globally with: npm install -g ${PACKAGE_NAME}`); } } catch (error) { this.addCheck('Package Installation', 'fail', 'Installation check failed', { error: error.message }); } } async checkExecutable() { try { const result = await this.runCommand('claude-orchestrator', ['--version']); if (result.code === 0) { const version = result.stdout.match(/(\d+\.\d+\.\d+)/)?.[1] || 'unknown'; this.addCheck('Executable', 'pass', `Working (v${version})`, { version }); } else { this.addCheck('Executable', 'fail', 'Command not working'); this.addRecommendation('Reinstall the package or check PATH configuration'); } } catch (error) { this.addCheck('Executable', 'fail', 'Command not found', { error: error.message }); this.addRecommendation('Make sure the package is installed globally and PATH is configured'); } } async checkClaudeCode() { const configPath = CLAUDE_CONFIG_PATHS[this.platform]; if (!configPath) { this.addCheck('Claude Code', 'warning', 'Unsupported platform for auto-detection'); return; } try { const configDir = path.dirname(configPath); // Check if config directory exists if (await fs.pathExists(configDir)) { this.addCheck('Claude Code', 'pass', 'Installation directory found', { configDir }); // Check configuration file if (await fs.pathExists(configPath)) { try { const config = await fs.readJson(configPath); this.addCheck('Claude Config', 'pass', 'Configuration file loaded', { configPath, hasGlobalConfig: !!config.mcpServers, serverCount: config.mcpServers ? Object.keys(config.mcpServers).length : 0 }); // Check our server registration if (config.mcpServers && config.mcpServers['claude-code-subagents-orchestrator']) { this.addCheck('MCP Registration', 'pass', 'Orchestrator is registered'); } else { this.addCheck('MCP Registration', 'fail', 'Orchestrator not registered'); this.addRecommendation('Run: claude-orchestrator init'); } } catch (error) { this.addCheck('Claude Config', 'warning', 'Configuration file exists but invalid', { error: error.message }); } } else { this.addCheck('Claude Config', 'warning', 'No configuration file found'); this.addRecommendation('Run: claude-orchestrator init'); } } else { this.addCheck('Claude Code', 'fail', 'Installation not found', { expectedDir: configDir }); this.addRecommendation('Install Claude Code from https://claude.ai/code'); } } catch (error) { this.addCheck('Claude Code', 'fail', 'Check failed', { error: error.message }); } } async checkServerFiles() { try { const packageJsonPath = path.resolve(__dirname, '..', 'package.json'); if (await fs.pathExists(packageJsonPath)) { const packageJson = await fs.readJson(packageJsonPath); this.addCheck('Package Files', 'pass', 'package.json found', { version: packageJson.version }); // Check server executable const serverPath = path.resolve(__dirname, '..', 'dist', 'server.js'); if (await fs.pathExists(serverPath)) { this.addCheck('Server Executable', 'pass', 'Server file found', { serverPath }); } else { this.addCheck('Server Executable', 'fail', 'Server file missing'); this.addRecommendation('Rebuild the package with: npm run build'); } } else { this.addCheck('Package Files', 'fail', 'package.json not found'); } } catch (error) { this.addCheck('Package Files', 'fail', 'File check failed', { error: error.message }); } } async checkDependencies() { try { const packageJsonPath = path.resolve(__dirname, '..', 'package.json'); if (await fs.pathExists(packageJsonPath)) { const packageJson = await fs.readJson(packageJsonPath); const dependencies = Object.keys(packageJson.dependencies || {}); let missingDeps = []; for (const dep of dependencies) { try { require.resolve(dep); } catch { missingDeps.push(dep); } } if (missingDeps.length === 0) { this.addCheck('Dependencies', 'pass', `All ${dependencies.length} dependencies available`); } else { this.addCheck('Dependencies', 'fail', `${missingDeps.length} dependencies missing`, { missing: missingDeps }); this.addRecommendation('Run: npm install'); } } else { this.addCheck('Dependencies', 'warning', 'Cannot check dependencies (package.json not found)'); } } catch (error) { this.addCheck('Dependencies', 'fail', 'Dependency check failed', { error: error.message }); } } async checkPermissions() { try { // Check write permissions for config directory const configPath = CLAUDE_CONFIG_PATHS[this.platform]; if (configPath) { const configDir = path.dirname(configPath); try { await fs.access(configDir, fs.constants.W_OK); this.addCheck('Permissions', 'pass', 'Can write to Claude config directory'); } catch { this.addCheck('Permissions', 'fail', 'Cannot write to Claude config directory'); this.addRecommendation('Check file permissions for Claude config directory'); } } // Check npm global permissions try { const result = await this.runCommand('npm', ['config', 'get', 'prefix']); if (result.code === 0) { const npmPrefix = result.stdout; this.addCheck('NPM Permissions', 'pass', 'NPM prefix accessible', { prefix: npmPrefix }); } } catch { this.addCheck('NPM Permissions', 'warning', 'NPM permission check failed'); } } catch (error) { this.addCheck('Permissions', 'fail', 'Permission check failed', { error: error.message }); } } async checkNetwork() { try { // Simple connectivity check const { default: fetch } = await import('node-fetch'); const response = await fetch('https://registry.npmjs.org/', { method: 'HEAD', timeout: 5000 }); if (response.ok) { this.addCheck('Network', 'pass', 'NPM registry accessible'); } else { this.addCheck('Network', 'warning', 'NPM registry returned non-200 status'); } } catch (error) { this.addCheck('Network', 'warning', 'Network connectivity check failed', { error: error.message }); this.addRecommendation('Check internet connection and firewall settings'); } } async checkSystemInfo() { const info = { platform: this.platform, arch: os.arch(), nodeVersion: process.version, npmVersion: process.env.npm_version || 'unknown', memory: { total: Math.round(os.totalmem() / 1024 / 1024), free: Math.round(os.freemem() / 1024 / 1024) }, uptime: os.uptime() }; this.addCheck('System Info', 'pass', 'System information collected', info); } async runAllChecks() { this.log('šŸ” Running health checks...', 'info'); this.log('', 'info'); await this.checkSystemInfo(); await this.checkNodeJs(); await this.checkNpm(); await this.checkPackageInstallation(); await this.checkExecutable(); await this.checkClaudeCode(); await this.checkServerFiles(); await this.checkDependencies(); await this.checkPermissions(); await this.checkNetwork(); } generateReport() { const { summary } = this.results; if (this.json) { console.log(JSON.stringify(this.results, null, 2)); return; } this.log('', 'info'); this.log('šŸ“Š Health Check Summary', 'info'); this.log('═══════════════════════', 'info'); this.log(`Total checks: ${summary.total}`, 'info'); this.log(`āœ“ Passed: ${summary.passed}`, 'success'); this.log(`āœ— Failed: ${summary.failed}`, 'error'); this.log(`⚠ Warnings: ${summary.warnings}`, 'warning'); const overallHealth = summary.failed === 0 ? 'HEALTHY' : 'ISSUES DETECTED'; const healthColor = summary.failed === 0 ? 'success' : 'error'; this.log(`\nOverall Health: ${overallHealth}`, healthColor); if (this.results.recommendations.length > 0) { this.log('\nšŸ’” Recommendations:', 'warning'); this.results.recommendations.forEach((rec, index) => { this.log(`${index + 1}. ${rec}`, 'warning'); }); } if (summary.failed > 0) { this.log('\nāŒ Critical issues detected. Please address the failed checks.', 'error'); process.exit(1); } else if (summary.warnings > 0) { this.log('\nāš ļø Some warnings detected. System should work but may have limitations.', 'warning'); } else { this.log('\nāœ… All checks passed! System is healthy.', 'success'); } } } // CLI interface async function main() { const args = process.argv.slice(2); const options = { verbose: args.includes('--verbose') || args.includes('-v'), json: args.includes('--json'), fix: args.includes('--fix') }; const checker = new HealthChecker(options); try { await checker.runAllChecks(); checker.generateReport(); } catch (error) { if (options.json) { console.log(JSON.stringify({ error: error.message }, null, 2)); } else { console.error(chalk.red('āŒ Health check failed:'), error.message); } process.exit(1); } } // Only run if called directly if (import.meta.url === `file://${process.argv[1]}`) { main(); } export { HealthChecker };