UNPKG

@adiontaegerron/claude-sub-agent-manager

Version:

A CLI tool for managing Claude Code sub-agents in your projects

239 lines (202 loc) 7.56 kB
#!/usr/bin/env node const path = require('path'); const fs = require('fs'); const { spawn } = require('child_process'); const { program } = require('commander'); /** * Searches for config files by walking up the directory tree * @param {string} startDir - Directory to start searching from * @param {string[]} configNames - Array of possible config file names * @returns {string|null} - Path to found config file or null */ function findConfigFile(startDir, configNames) { let currentDir = startDir; while (currentDir !== path.parse(currentDir).root) { for (const configName of configNames) { const configPath = path.join(currentDir, configName); if (fs.existsSync(configPath)) { return configPath; } } // Check if we've reached a project root (presence of .git or package.json) const gitPath = path.join(currentDir, '.git'); const pkgPath = path.join(currentDir, 'package.json'); if (fs.existsSync(gitPath) || fs.existsSync(pkgPath)) { // This is likely the project root, stop searching if we haven't found config break; } // Move up one directory currentDir = path.dirname(currentDir); } // Check root directory as last resort for (const configName of configNames) { const configPath = path.join(currentDir, configName); if (fs.existsSync(configPath)) { return configPath; } } return null; } /** * Load and parse config file * @param {string} configPath - Path to config file * @returns {object} - Parsed config object */ function loadConfig(configPath) { try { const ext = path.extname(configPath).toLowerCase(); const content = fs.readFileSync(configPath, 'utf8'); if (ext === '.json') { return JSON.parse(content); } else if (ext === '.yaml' || ext === '.yml') { // For YAML support, we'll need to add js-yaml as a dependency try { const yaml = require('js-yaml'); return yaml.load(content); } catch (e) { console.error('YAML support requires js-yaml package. Please install it with: npm install js-yaml'); process.exit(1); } } else { console.error(`Unsupported config file format: ${ext}`); process.exit(1); } } catch (error) { console.error(`Error loading config file: ${error.message}`); process.exit(1); } } /** * Get the project root directory * @param {string} startDir - Directory to start searching from * @returns {string} - Project root directory */ function findProjectRoot(startDir) { let currentDir = startDir; while (currentDir !== path.parse(currentDir).root) { // Check for common project root indicators const indicators = ['.git', 'package.json', '.claude-agents.json', '.claude-agents.yaml', '.claude-agents.yml']; for (const indicator of indicators) { if (fs.existsSync(path.join(currentDir, indicator))) { return currentDir; } } currentDir = path.dirname(currentDir); } return startDir; // Fall back to start directory if no root found } // Parse command line arguments program .name('claude-agents') .description('Claude Sub-Agent Manager CLI') .version(require('./package.json').version) .option('-c, --config <path>', 'path to config file') .option('-r, --root <path>', 'project root directory') .option('-p, --port <number>', 'port to run the server on', '3001') .option('--no-browser', 'do not open browser automatically') .action((options) => { const startDir = process.cwd(); let configPath = options.config; let projectRoot = options.root; // Find config file if not specified if (!configPath) { const configNames = ['.claude-agents.json', '.claude-agents.yaml', '.claude-agents.yml']; configPath = findConfigFile(startDir, configNames); if (!configPath) { console.log('No config file found. Creating default .claude-agents.json in current directory...'); configPath = path.join(startDir, '.claude-agents.json'); const defaultConfig = { projectName: path.basename(startDir), agentsDirectory: ".claude/agents", techStackFile: "tech-stack-data.json", templatesDirectory: "agent-templates" }; fs.writeFileSync(configPath, JSON.stringify(defaultConfig, null, 2)); console.log(`Created ${configPath}`); } } // Load config const config = loadConfig(configPath); console.log(`Using config from: ${configPath}`); // Determine project root if (!projectRoot) { projectRoot = path.dirname(configPath); } // Set environment variables for the server process.env.CLAUDE_AGENTS_CONFIG = configPath; process.env.CLAUDE_AGENTS_ROOT = projectRoot; process.env.PORT = options.port; // Start the server const serverPath = path.join(__dirname, 'server.js'); // Check if server exists if (!fs.existsSync(serverPath)) { console.error('Error: Server not found. This may be due to incomplete installation.'); console.error('Please reinstall the package.'); process.exit(1); } const serverProcess = spawn('node', [serverPath], { stdio: ['pipe', 'pipe', 'pipe'], env: { ...process.env } }); let actualPort = options.port; let serverReady = false; // Capture server output to get actual port serverProcess.stdout.on('data', (data) => { const output = data.toString(); process.stdout.write(output); // Extract actual port from server output const portMatch = output.match(/Server running on http:\/\/localhost:(\d+)/); if (portMatch) { actualPort = portMatch[1]; serverReady = true; // Open browser after server is ready if (options.browser !== false && !process.env.BROWSER_OPENED) { process.env.BROWSER_OPENED = 'true'; setTimeout(() => { const open = require('open'); open(`http://localhost:${actualPort}`); }, 500); } } }); serverProcess.stderr.on('data', (data) => { process.stderr.write(data); }); // Handle graceful shutdown process.on('SIGINT', () => { console.log('\nShutting down Claude Agent Manager...'); serverProcess.kill(); process.exit(0); }); serverProcess.on('error', (error) => { console.error('Failed to start server:', error); process.exit(1); }); serverProcess.on('exit', (code) => { if (code !== 0 && code !== null) { console.error(`Server exited with code ${code}`); process.exit(code); } }); }); // Add init command to create config file program .command('init') .description('Initialize a new Claude agents config file in the current directory') .action(() => { const configPath = path.join(process.cwd(), '.claude-agents.json'); if (fs.existsSync(configPath)) { console.error('Config file already exists!'); process.exit(1); } const defaultConfig = { projectName: path.basename(process.cwd()), agentsDirectory: ".claude/agents", techStackFile: "tech-stack-data.json", templatesDirectory: "agent-templates" }; fs.writeFileSync(configPath, JSON.stringify(defaultConfig, null, 2)); console.log(`Created ${configPath}`); console.log('You can now run "claude-agents" to start the agent manager.'); }); program.parse();