@every-env/cli
Version:
Multi-agent orchestrator for AI-powered development workflows
460 lines ⢠17.4 kB
JavaScript
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