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