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