UNPKG

@every-env/cli

Version:

Multi-agent orchestrator for AI-powered development workflows

152 lines 7.77 kB
import { BaseAgent } from './base-agent.js'; import { logger } from '../utils/logger.js'; import { existsSync, writeFileSync, mkdirSync } from 'fs'; import { dirname } from 'path'; import { validatePath } from '../utils/security.js'; export class ClaudeAgent extends BaseAgent { async run(prompt, outputPath) { this.startTime = Date.now(); logger.debug(`[${this.config.id}] Starting run with output path:`, { outputPath }); try { // Build command arguments const args = this.buildArgs(prompt, outputPath); const command = this.config.command || 'claude'; logger.debug(`Running Claude agent ${this.config.id}`, { command: command, args: args.slice(0, 3), // Log command and flags, not full prompt }); // Log full command in debug mode for troubleshooting logger.debug(`Full command details for ${this.config.id}:`, { fullCommand: `${command} ${args.slice(0, -1).join(' ')} [prompt]`, workingDir: this.options.workingDir || this.config.workingDir || process.cwd(), promptLength: prompt.length, outputPath }); // Spawn the process with enhanced prompt for stdin mode const enhancedPrompt = this.config.promptMode === 'stdin' ? this.enhancePromptWithOutput(prompt, outputPath) : prompt; logger.debug(`[${this.config.id}] About to spawn process`, { promptMode: this.config.promptMode, enhancedPromptLength: enhancedPrompt.length, hasOutputInstruction: enhancedPrompt.includes(outputPath) }); const exitCode = await this.spawnProcess(command, args, enhancedPrompt); logger.debug(`[${this.config.id}] Process completed with exit code:`, { exitCode }); // In interactive mode, Claude creates files directly if (exitCode === 0) { logger.debug(`[${this.config.id}] Process succeeded, checking for output file`); // Check if Claude created the file if (existsSync(outputPath)) { logger.debug(`[${this.config.id}] Output file exists at:`, { outputPath }); return this.buildResult('success', outputPath); } else if (this.stdout.length > 0 && this.config.flags?.includes('--print')) { logger.debug(`[${this.config.id}] No file created but stdout available with --print flag`); // Only capture stdout if --print flag was explicitly set try { // Validate output path is within working directory const baseDir = this.options.workingDir || this.config.workingDir || process.cwd(); const safeOutputPath = validatePath(outputPath, baseDir); // Ensure output directory exists const outputDir = dirname(safeOutputPath); if (!existsSync(outputDir)) { mkdirSync(outputDir, { recursive: true }); } // Write stdout to file const content = this.stdout.join(''); writeFileSync(safeOutputPath, content); logger.debug(`[${this.config.id}] Wrote stdout to file:`, { outputPath: safeOutputPath, contentLength: content.length }); return this.buildResult('success', safeOutputPath); } catch (writeError) { logger.error(`[${this.config.id}] Failed to write output file:`, writeError); return this.buildResult('failed', undefined, `Failed to write output: ${writeError instanceof Error ? writeError.message : 'Unknown error'}`); } } else { // In interactive mode without file creation logger.debug(`[${this.config.id}] Success without file creation (interactive mode)`); return this.buildResult('success', outputPath); } } else { logger.error(`[${this.config.id}] Process failed with non-zero exit code:`, { exitCode, stdoutLength: this.stdout.length, stderrLength: this.stderr.length }); return this.buildResult('failed', undefined, `Exit code: ${exitCode}, Stdout length: ${this.stdout.length}`); } } catch (error) { logger.error(`Agent ${this.config.id} failed:`, error); // Handle specific error cases if (error instanceof Error) { if (error.message.includes('ENOENT')) { return this.buildResult('failed', undefined, 'Claude Code is not installed or not in PATH. Please install it with: npm install -g @anthropic-ai/claude-code'); } return this.buildResult('failed', undefined, error.message); } return this.buildResult('failed', undefined, 'Unknown error'); } } buildArgs(prompt, outputPath) { const args = []; logger.debug(`[${this.config.id}] Starting buildArgs, promptMode: ${this.config.promptMode}`); // Add custom flags if (this.config.flags) { logger.debug(`[${this.config.id}] Adding custom flags:`, { flags: this.config.flags }); args.push(...this.config.flags); } // Removed automatic --print flag to enable interactive mode logger.debug(`[${this.config.id}] Args after adding --print:`, { args: [...args] }); // Add prompt based on mode switch (this.config.promptMode) { case 'flag': { // Ensure output path is included in prompt const enhancedPrompt = this.enhancePromptWithOutput(prompt, outputPath); args.push(enhancedPrompt); break; } case 'stdin': // Prompt will be sent via stdin, no need to add to args break; case 'arg': args.push(prompt); break; } // Add any additional Claude-specific flags if (this.options.env?.CLAUDE_SKIP_PERMISSIONS === 'true') { args.push('--dangerously-skip-permissions'); } // Add allowedTools flags if (this.config.allowedTools && this.config.allowedTools.length > 0) { logger.debug(`[${this.config.id}] Adding allowedTools:`, { allowedTools: this.config.allowedTools }); this.config.allowedTools.forEach(tool => { args.push('--allowedTools', tool); }); } else { logger.debug(`[${this.config.id}] No allowedTools configured`); } return args; } enhancePromptWithOutput(prompt, outputPath) { // Check if prompt already mentions output if (prompt.includes(outputPath)) { logger.debug(`[${this.config.id}] Prompt already contains output path`); return prompt; } // Add output instruction to end of prompt const enhanced = `${prompt}\n\nSave the documentation to: ${outputPath}`; logger.debug(`[${this.config.id}] Enhanced prompt with output instruction`); return enhanced; } } //# sourceMappingURL=claude-agent.js.map