Files
shockbot/agents/claude.ts
T
Colin McDonnell 7745a0befb Update Claude agent to use proper headless mode
- Replace --dangerously-skip-permissions with official headless mode flags
- Use -p (--print) for non-interactive mode
- Add --output-format json for structured responses
- Use --permission-mode acceptEdits for automation
- Parse JSON response and extract metadata (cost, duration, session_id)
- Handle both successful and error responses properly
- Reference: https://docs.anthropic.com/en/docs/claude-code/sdk/sdk-headless
2025-08-28 13:42:04 -07:00

92 lines
2.7 KiB
TypeScript

import * as core from "@actions/core";
import { executeCommand } from "../utils/exec";
import { createTempFile } from "../utils/files";
import type { Agent, AgentConfig, AgentResult } from "./types";
/**
* Claude Code agent implementation
*/
export class ClaudeAgent implements Agent {
private apiKey: string;
constructor(config: AgentConfig) {
if (!config.apiKey) {
throw new Error("Claude agent requires an API key");
}
this.apiKey = config.apiKey;
}
/**
* Install Claude Code CLI
*/
async install(): Promise<void> {
core.info("Installing Claude Code...");
try {
await executeCommand("curl -fsSL https://claude.ai/install.sh | bash -s 1.0.93");
core.info("Claude Code installed successfully");
} catch (error) {
throw new Error(`Failed to install Claude Code: ${error}`);
}
}
/**
* Execute Claude Code with the given prompt
*/
async execute(prompt: string): Promise<AgentResult> {
core.info("Executing Claude Code...");
try {
// Create a temporary file for the prompt
const promptFile = createTempFile(prompt, "prompt.txt");
// Execute Claude Code with the prompt using proper headless mode
const command = `$HOME/.local/bin/claude -p "${promptFile}" --output-format json --permission-mode acceptEdits`;
core.info(`Executing: ${command}`);
const { stdout, stderr } = await executeCommand(command, {
ANTHROPIC_API_KEY: this.apiKey,
});
if (stderr) {
core.warning(`Claude Code stderr: ${stderr}`);
}
// Parse JSON response from Claude Code
let claudeResponse: any;
try {
claudeResponse = JSON.parse(stdout);
} catch {
core.warning("Failed to parse Claude Code JSON response, using raw output");
claudeResponse = { result: stdout };
}
if (claudeResponse.result) {
core.info("Claude Code output:");
console.log(claudeResponse.result);
}
core.info("Claude Code executed successfully");
return {
success: !claudeResponse.is_error,
output: claudeResponse.result || stdout,
error: claudeResponse.is_error ? claudeResponse.result : (stderr || undefined),
metadata: {
promptFile,
command,
session_id: claudeResponse.session_id,
cost_usd: claudeResponse.total_cost_usd,
duration_ms: claudeResponse.duration_ms,
num_turns: claudeResponse.num_turns,
},
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : "Unknown error";
return {
success: false,
error: `Failed to execute Claude Code: ${errorMessage}`,
};
}
}
}