UNPKG

@every-env/cli

Version:

Multi-agent orchestrator for AI-powered development workflows

460 lines • 17.4 kB
#!/usr/bin/env node import { Command } from 'commander'; import { ConfigLoader } from './core/config-loader.js'; import { logger, setLogLevel } from './utils/logger.js'; import { execCommand } from './utils/exec.js'; import chalk from 'chalk'; import ora from 'ora'; import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs'; import { fileURLToPath } from 'url'; import { dirname, join } from 'path'; import { defaultConfig, defaultPrompts } from './templates/config.js'; import { exec } from 'child_process'; import { promisify } from 'util'; import { executeCopyCommands } from './commands/copy-commands.js'; import { createInitCommand } from './commands/init.js'; import { runPlanCommand } from './commands/plan.js'; import { runWorkCommand } from './commands/work.js'; import { runReviewCommand } from './commands/review.js'; const execAsync = promisify(exec); const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const packageJson = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8')); const program = new Command(); program .name('every') .description('Simple CLI for AI-powered development workflows') .version(packageJson.version); // Create docs subcommand const docsCommand = program.command('docs'); docsCommand.description('Documentation generation commands'); // Global options for docs command docsCommand .option('-c, --config <path>', 'Configuration file path') .option('-v, --verbose', 'Verbose output') .option('--debug', 'Debug output'); // Update command docsCommand .command('update') .description('Update existing documentation') .option('--pattern <names...>', 'Specific patterns to update') .option('--force', 'Force regeneration') .action(async (options) => { await runCommand('update', { ...docsCommand.opts(), ...options }); }); // Run command docsCommand .command('run <pattern>') .description('Run a specific documentation pattern') .option('--only <items...>', 'Only process specific items') .option('--force', 'Force regeneration') .action(async (pattern, options) => { await runCommand('run', { ...docsCommand.opts(), ...options, pattern }); }); // List command docsCommand .command('list') .description('List available documentation patterns') .action(async () => { await runCommand('list', docsCommand.opts()); }); // Status command docsCommand .command('status') .description('Show status of last documentation run') .action(async () => { await runCommand('status', docsCommand.opts()); }); // Main command runner async function runCommand(command, options) { try { // Set log level if (options.debug) { setLogLevel('debug'); } else if (options.verbose) { setLogLevel('info'); } // Show version console.log(chalk.cyan(`\nšŸš€ @every/cli v${packageJson.version}\n`)); if (command === 'init' && !options.config) { await initializeProject(options); return; } // Load configuration const spinner = ora('Loading configuration...').start(); const configLoader = new ConfigLoader(); const config = await configLoader.load(options.config); spinner.succeed('Configuration loaded'); // Execute command switch (command) { case 'init': await executeInit(config, options); break; case 'update': await executeUpdate(config, options); break; case 'run': await executeRun(config, options); break; case 'list': await executeList(config); break; case 'status': await executeStatus(config); break; } } catch (error) { logger.error('Command failed:', error); console.error(chalk.red(`\nāœ– Error: ${error instanceof Error ? error.message : 'Unknown error'}`)); process.exit(1); } } export async function initializeProject(options) { const everyEnvDir = '.every-env'; const configPath = join(everyEnvDir, 'config.json'); // Create .every-env directory if it doesn't exist if (!existsSync(everyEnvDir)) { mkdirSync(everyEnvDir, { recursive: true }); console.log(chalk.green(`āœ“ Created ${everyEnvDir} directory`)); } // Check if config already exists if (existsSync(configPath) && !options.force) { console.log(chalk.yellow('Configuration file already exists. Use --force to overwrite.')); return; } // Check which Claude command is available const claudeCommand = await detectClaudeCommand(); if (claudeCommand) { console.log(chalk.green(`āœ“ Detected Claude command: ${claudeCommand}`)); } else { console.log(chalk.yellow('⚠ Claude Code not detected. Please install it with: npm install -g @anthropic-ai/claude-code')); } console.log(chalk.green('Creating sample configuration...')); // Update the sample config with the detected command const configWithCommand = { ...defaultConfig, docs: { ...defaultConfig.docs, defaultCommand: claudeCommand || 'claude' } }; // Write configuration file writeFileSync(configPath, JSON.stringify(configWithCommand, null, 2)); console.log(chalk.green(`āœ“ Configuration created: ${configPath}`)); // Create prompts directory and files const promptsDir = join(everyEnvDir, 'prompts'); if (!existsSync(promptsDir)) { mkdirSync(promptsDir, { recursive: true }); console.log(chalk.green(`āœ“ Created prompts directory: ${promptsDir}`)); } // Write default prompts for (const [filename, content] of Object.entries(defaultPrompts)) { const promptPath = join(promptsDir, filename); if (!existsSync(promptPath) || options.force) { writeFileSync(promptPath, content); console.log(chalk.green(`āœ“ Created prompt: ${promptPath}`)); } } console.log(chalk.cyan('\nšŸŽ‰ Project initialized successfully!')); console.log(chalk.gray('Next steps:')); console.log(chalk.gray(' 1. Review the configuration in .every-env/config.json')); console.log(chalk.gray(' 2. Customize prompts in .every-env/prompts/')); console.log(chalk.gray(' 3. Run: every docs update')); } async function detectClaudeCommand() { const commands = ['claude', 'claude-code']; for (const cmd of commands) { try { await execAsync(`which ${cmd}`); return cmd; } catch { // Command not found, try next } } try { await execAsync('npm list -g @anthropic-ai/claude-code'); return 'claude-code'; } catch { return null; } } // Wrapper for the plan command async function runPlanCommandWrapper(_args, options, rawArgs) { try { console.log(chalk.cyan(`\nšŸš€ @every/cli v${packageJson.version}\n`)); // Extract pass-through args and plan description const passThroughArgs = []; const planDescription = []; let i = 0; while (i < rawArgs.length) { const arg = rawArgs[i]; if (arg === '-a' || arg === '--agentcli') { // Skip the flag and its value i += 2; } else if (arg.startsWith('-')) { // This is an option, add it to pass-through passThroughArgs.push(arg); // Check if next arg is the value for this option if (i + 1 < rawArgs.length && !rawArgs[i + 1].startsWith('-')) { passThroughArgs.push(rawArgs[i + 1]); i += 2; } else { i++; } } else { // This is a positional argument (plan description) planDescription.push(arg); i++; } } await runPlanCommand(planDescription, { agentcli: options?.agentcli, passThroughArgs }); } catch (error) { // Error already logged by runPlanCommand process.exit(1); } } // Wrapper for the work command async function runWorkCommandWrapper(_args, options, rawArgs) { try { console.log(chalk.cyan(`\nšŸš€ @every/cli v${packageJson.version}\n`)); // Extract pass-through args and work target const passThroughArgs = []; const workTargets = []; let i = 0; while (i < rawArgs.length) { const arg = rawArgs[i]; if (arg === '-a' || arg === '--agentcli') { // Skip the flag and its value i += 2; } else if (arg.startsWith('-')) { // This is an option, add it to pass-through passThroughArgs.push(arg); // Check if next arg is the value for this option if (i + 1 < rawArgs.length && !rawArgs[i + 1].startsWith('-')) { passThroughArgs.push(rawArgs[i + 1]); i += 2; } else { i++; } } else { // This is a positional argument (work target) workTargets.push(arg); i++; } } await runWorkCommand(workTargets, { agentcli: options?.agentcli, passThroughArgs }); } catch (error) { // Error already logged by runWorkCommand process.exit(1); } } // Wrapper for the review command async function runReviewCommandWrapper(_args, options, rawArgs) { try { console.log(chalk.cyan(`\nšŸš€ @every/cli v${packageJson.version}\n`)); // Extract pass-through args and review target const passThroughArgs = []; const reviewTargets = []; let i = 0; while (i < rawArgs.length) { const arg = rawArgs[i]; if (arg === '-a' || arg === '--agentcli') { // Skip the flag and its value i += 2; } else if (arg === '-w' || arg === '--setup-worktree') { // This flag is handled by our CLI, don't pass through i += 1; } else if (arg === '-m' || arg === '--multi-agent') { // This flag is handled by our CLI, don't pass through i += 1; } else if (arg === '-p' || arg === '--patterns') { // Skip the flag and collect pattern values i++; while (i < rawArgs.length && !rawArgs[i].startsWith('-')) { i++; } } else if (arg === '--parallel') { // Skip the flag and its value i += 2; } else if (arg === '-o' || arg === '--output-dir') { // Skip the flag and its value i += 2; } else if (arg.startsWith('-')) { // This is an option, add it to pass-through passThroughArgs.push(arg); // Check if next arg is the value for this option if (i + 1 < rawArgs.length && !rawArgs[i + 1].startsWith('-')) { passThroughArgs.push(rawArgs[i + 1]); i += 2; } else { i++; } } else { // This is a positional argument (review target) reviewTargets.push(arg); i++; } } // Check if multi-agent mode is requested if (options?.multiAgent) { // Multi-agent review not implemented yet logger.warn("Multi-agent review mode is not yet implemented. Using standard review."); } // Use the standard review command await runReviewCommand(reviewTargets, { agentcli: options?.agentcli, passThroughArgs, setupWorktree: Boolean(options?.setupWorktree) }); } catch (error) { // Error already logged by runReviewCommand process.exit(1); } } // Simplified research command async function runResearchCommand(args) { try { console.log(chalk.cyan(`\nšŸš€ @every/cli v${packageJson.version}\n`)); const researchTask = args.join(' '); if (!researchTask) { throw new Error('Please provide a research task. Usage: every research "your task description"'); } // Read runtime config to get claude args const configPath = join(process.cwd(), '.every-env', 'config.json'); let claudeArgs = []; try { const { readRuntimeConfigIfExists } = await import('./utils/runtime-config.js'); const runtimeConfig = await readRuntimeConfigIfExists(configPath); if (runtimeConfig?.agents?.claude?.args) { claudeArgs = runtimeConfig.agents.claude.args; } } catch { // Ignore if config doesn't exist } // Detect Claude command const claudeCommand = await detectClaudeCommand() || 'claude'; console.log(chalk.cyan(`\nšŸ” Researching: "${researchTask}"\n`)); // Build args with any configured args const commandArgs = [...claudeArgs, 'research', researchTask]; // Direct exec to Claude with research command await execCommand(claudeCommand, commandArgs); } catch (error) { logger.error('Research command failed:', error); console.error(chalk.red(`\nāœ– Error: ${error instanceof Error ? error.message : 'Unknown error'}`)); process.exit(1); } } // Simplified docs command implementations async function executeInit(_config, _options) { console.log(chalk.green('āœ“ Project already initialized')); } async function executeUpdate(_config, _options) { console.log(chalk.yellow('Update command simplified - use direct Claude commands instead')); } async function executeRun(_config, _options) { console.log(chalk.yellow('Run command simplified - use direct Claude commands instead')); } async function executeList(config) { console.log(chalk.cyan('\nAvailable patterns:\n')); config.docs.patterns.forEach((pattern) => { console.log(chalk.green(` ${pattern.name}`)); if (pattern.description) { console.log(chalk.gray(` ${pattern.description}`)); } }); console.log(); } async function executeStatus(_config) { console.log(chalk.cyan('Status: Ready')); } // Add plan command directly program .command('plan [description...]') .description('Create implementation plans and work breakdowns') .option('-a, --agentcli <agentcli>', 'Specify the agent CLI to use (e.g., codex, claude)') .allowUnknownOption() .action(async (description, options) => { // Get all raw args after 'plan' command const rawArgs = process.argv.slice(process.argv.indexOf('plan') + 1); await runPlanCommandWrapper(description, options, rawArgs); }); // Add work command program .command('work [target...]') .description('Execute work on a plan or target') .option('-a, --agentcli <agentcli>', 'Specify the agent CLI to use (e.g., codex, claude)') .allowUnknownOption() .action(async (target, options) => { // Get all raw args after 'work' command const rawArgs = process.argv.slice(process.argv.indexOf('work') + 1); await runWorkCommandWrapper(target, options, rawArgs); }); // Add review command program .command('review [target...]') .description('Review code changes or pull requests') .option('-a, --agentcli <agentcli>', 'Specify the agent CLI to use (e.g., codex, claude)') .option('-w, --setup-worktree', 'Create an isolated git worktree for PR reviews before launching the agent') .option('-m, --multi-agent', 'Use multi-agent pattern-based review system') .option('-p, --patterns <patterns...>', 'Specific review patterns to execute') .option('--parallel <number>', 'Number of agents to run in parallel', '5') .option('-o, --output-dir <path>', 'Directory for review artifacts') .allowUnknownOption() .action(async (target, options) => { // Get all raw args after 'review' command const rawArgs = process.argv.slice(process.argv.indexOf('review') + 1); await runReviewCommandWrapper(target, options, rawArgs); }); // Add research command program .command('research [task...]') .description('Conduct in-depth research on a specific task or topic') .action(async (task) => { await runResearchCommand(task); }); // Add copy-commands command program .command('copy-commands') .description('Copy built-in .claude commands and agents from the package into the current project') .action(async () => { await executeCopyCommands(); }); // Add init command program.addCommand(createInitCommand()); // Parse command line arguments program.parse(); //# sourceMappingURL=cli.js.map