From 44d513b712924b0ffaed82b69746f7261c9e45e0 Mon Sep 17 00:00:00 2001 From: Mark Wylde Date: Sat, 31 May 2025 00:16:57 +0100 Subject: [PATCH] Attempt to make this work --- action.yml | 25 ++- src/claude/executor.ts | 302 ------------------------------ src/entrypoints/execute-claude.ts | 61 ------ 3 files changed, 11 insertions(+), 377 deletions(-) delete mode 100644 src/claude/executor.ts delete mode 100644 src/entrypoints/execute-claude.ts diff --git a/action.yml b/action.yml index d09c71a..4d3cc46 100644 --- a/action.yml +++ b/action.yml @@ -83,17 +83,6 @@ runs: cd ${{ github.action_path }} bun install - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - - - name: Install Claude Code CLI - shell: bash - run: | - # Install Claude Code CLI via npm - npm install -g @anthropic-ai/claude-code - - name: Prepare action id: prepare shell: bash @@ -114,9 +103,17 @@ runs: - name: Run Claude Code id: claude-code if: steps.prepare.outputs.contains_trigger == 'true' - shell: bash - run: | - bun run ${{ github.action_path }}/src/entrypoints/execute-claude.ts + uses: anthropics/claude-code-base-action@c8e31bd52d9a149b3f8309d7978c6edaa282688d # v0.0.8 + with: + prompt_file: /tmp/claude-prompts/claude-prompt.txt + allowed_tools: ${{ env.ALLOWED_TOOLS }} + disallowed_tools: ${{ env.DISALLOWED_TOOLS }} + timeout_minutes: ${{ inputs.timeout_minutes }} + model: ${{ inputs.model || inputs.anthropic_model }} + mcp_config: ${{ steps.prepare.outputs.mcp_config }} + use_bedrock: ${{ inputs.use_bedrock }} + use_vertex: ${{ inputs.use_vertex }} + anthropic_api_key: ${{ inputs.anthropic_api_key }} env: # Core configuration PROMPT_FILE: /tmp/claude-prompts/claude-prompt.txt diff --git a/src/claude/executor.ts b/src/claude/executor.ts deleted file mode 100644 index 02b7970..0000000 --- a/src/claude/executor.ts +++ /dev/null @@ -1,302 +0,0 @@ -#!/usr/bin/env bun - -import Anthropic from "@anthropic-ai/sdk"; -import * as fs from "fs"; -import { spawn } from "child_process"; -import { promisify } from "util"; - -export interface ClaudeExecutorConfig { - apiKey?: string; - model?: string; - promptFile?: string; - prompt?: string; - maxTurns?: number; - timeoutMinutes?: number; - mcpConfig?: string; - allowedTools?: string; - disallowedTools?: string; - useBedrock?: boolean; - useVertex?: boolean; -} - -export interface ClaudeExecutorResult { - conclusion: "success" | "failure"; - executionFile?: string; - error?: string; -} - -interface MCPServer { - command: string; - args: string[]; - env?: Record; -} - -interface MCPConfig { - mcpServers: Record; -} - -export class ClaudeExecutor { - private config: ClaudeExecutorConfig; - - constructor(config: ClaudeExecutorConfig) { - this.config = config; - } - - private async readPrompt(): Promise { - console.log("[CLAUDE-EXECUTOR] Reading prompt..."); - - if (this.config.prompt) { - console.log("[CLAUDE-EXECUTOR] Using direct prompt"); - return this.config.prompt; - } - - if (this.config.promptFile) { - console.log(`[CLAUDE-EXECUTOR] Using prompt file: ${this.config.promptFile}`); - if (!fs.existsSync(this.config.promptFile)) { - console.error(`[CLAUDE-EXECUTOR] Prompt file not found: ${this.config.promptFile}`); - throw new Error(`Prompt file not found: ${this.config.promptFile}`); - } - const content = fs.readFileSync(this.config.promptFile, "utf-8"); - console.log(`[CLAUDE-EXECUTOR] Prompt file size: ${content.length} bytes`); - return content; - } - - console.error("[CLAUDE-EXECUTOR] Neither prompt nor promptFile provided"); - throw new Error("Either prompt or promptFile must be provided"); - } - - private parseTools(): { allowed: string[]; disallowed: string[] } { - const allowed = this.config.allowedTools - ? this.config.allowedTools - .split(",") - .map((t) => t.trim()) - .filter(Boolean) - : []; - - const disallowed = this.config.disallowedTools - ? this.config.disallowedTools - .split(",") - .map((t) => t.trim()) - .filter(Boolean) - : []; - - return { allowed, disallowed }; - } - - private createExecutionLog(result: any, error?: string): string { - const logData = { - conclusion: error ? "failure" : "success", - model: this.config.model || "claude-3-7-sonnet-20250219", - timestamp: new Date().toISOString(), - result, - error, - }; - - const logFile = "/tmp/claude-execution.json"; - fs.writeFileSync(logFile, JSON.stringify(logData, null, 2)); - return logFile; - } - - private async setupMCPConfig(): Promise { - if (!this.config.mcpConfig) { - console.log("[CLAUDE-EXECUTOR] No MCP config provided"); - return null; - } - - try { - const mcpConfig: MCPConfig = JSON.parse(this.config.mcpConfig); - const configFile = "/tmp/mcp-config.json"; - - console.log("[CLAUDE-EXECUTOR] Setting up MCP configuration..."); - console.log("[CLAUDE-EXECUTOR] MCP servers:", Object.keys(mcpConfig.mcpServers)); - - fs.writeFileSync(configFile, JSON.stringify(mcpConfig, null, 2)); - - console.log("[CLAUDE-EXECUTOR] MCP config written to:", configFile); - return configFile; - } catch (error) { - console.error("[CLAUDE-EXECUTOR] Failed to parse MCP config:", error); - return null; - } - } - - async execute(): Promise { - try { - const prompt = await this.readPrompt(); - const tools = this.parseTools(); - - console.log(`[CLAUDE-EXECUTOR] Prompt length: ${prompt.length} characters`); - console.log( - `[CLAUDE-EXECUTOR] Executing Claude with model: ${this.config.model || "claude-3-7-sonnet-20250219"}`, - ); - console.log(`[CLAUDE-EXECUTOR] Allowed tools: ${tools.allowed.join(", ") || "none"}`); - console.log(`[CLAUDE-EXECUTOR] Disallowed tools: ${tools.disallowed.join(", ") || "none"}`); - - // Setup MCP configuration if provided - const mcpConfigFile = await this.setupMCPConfig(); - - if (mcpConfigFile) { - console.log("[CLAUDE-EXECUTOR] Using Claude Code with MCP configuration"); - return await this.executeWithMCP(prompt, mcpConfigFile, tools); - } else { - console.log("[CLAUDE-EXECUTOR] No MCP config provided, falling back to basic API"); - return await this.executeBasicAPI(prompt); - } - } catch (error) { - console.error("[CLAUDE-EXECUTOR] Claude execution failed:", error); - - const executionFile = this.createExecutionLog(null, String(error)); - - return { - conclusion: "failure", - executionFile, - error: String(error), - }; - } - } - - private async executeWithMCP( - prompt: string, - mcpConfigFile: string, - tools: { allowed: string[]; disallowed: string[] } - ): Promise { - console.log("[CLAUDE-EXECUTOR] Starting Claude Code with MCP support..."); - - if (!this.config.apiKey) { - throw new Error("Anthropic API key is required"); - } - - // Write prompt to a temporary file - const promptFile = "/tmp/claude-prompt.txt"; - fs.writeFileSync(promptFile, prompt); - - // Build Claude Code command arguments - const args = [ - "-p", `@${promptFile}` - ]; - - // Add MCP config if available - if (mcpConfigFile) { - args.push("--mcp-config", mcpConfigFile); - } - - // Add model if specified - if (this.config.model) { - args.push("--model", this.config.model); - } - - // Add allowed tools if specified - if (tools.allowed.length > 0) { - args.push("--allowedTools", tools.allowed.join(",")); - } - - // Add disallowed tools if specified - if (tools.disallowed.length > 0) { - args.push("--disallowedTools", tools.disallowed.join(",")); - } - - console.log("[CLAUDE-EXECUTOR] Claude Code command:", "claude", args.join(" ")); - - return new Promise((resolve) => { - const claude = spawn("claude", args, { - env: { - ...process.env, - ANTHROPIC_API_KEY: this.config.apiKey, - }, - }); - - let stdout = ""; - let stderr = ""; - - claude.stdout?.on("data", (data) => { - const chunk = data.toString(); - stdout += chunk; - console.log("[CLAUDE-STDOUT]", chunk.trim()); - }); - - claude.stderr?.on("data", (data) => { - const chunk = data.toString(); - stderr += chunk; - console.log("[CLAUDE-STDERR]", chunk.trim()); - }); - - claude.on("close", (code) => { - console.log(`[CLAUDE-EXECUTOR] Claude Code process exited with code ${code}`); - - const result = { - stdout, - stderr, - exitCode: code, - success: code === 0, - }; - - const executionFile = this.createExecutionLog(result, code !== 0 ? `Process exited with code ${code}` : undefined); - - resolve({ - conclusion: code === 0 ? "success" : "failure", - executionFile, - error: code !== 0 ? `Claude Code exited with code ${code}: ${stderr}` : undefined, - }); - }); - - claude.on("error", (error) => { - console.error("[CLAUDE-EXECUTOR] Failed to spawn Claude Code process:", error); - const executionFile = this.createExecutionLog(null, String(error)); - resolve({ - conclusion: "failure", - executionFile, - error: String(error), - }); - }); - }); - } - - private async executeBasicAPI(prompt: string): Promise { - console.log("[CLAUDE-EXECUTOR] WARNING: Using simple Anthropic API - MCP tools not supported in this implementation"); - console.log("[CLAUDE-EXECUTOR] This explains why Claude cannot use mcp__local_git_ops tools"); - - if (!this.config.apiKey) { - throw new Error("Anthropic API key is required"); - } - - const anthropic = new Anthropic({ - apiKey: this.config.apiKey, - }); - - console.log("[CLAUDE-EXECUTOR] Starting simple Anthropic API call..."); - console.log("[CLAUDE-EXECUTOR] Prompt preview:", prompt.substring(0, 500) + "..."); - - // Create a simple message request - const response = await anthropic.messages.create({ - model: this.config.model || "claude-3-7-sonnet-20250219", - max_tokens: 8192, - messages: [ - { - role: "user", - content: prompt, - }, - ], - }); - - console.log("[CLAUDE-EXECUTOR] Claude response received successfully"); - console.log("[CLAUDE-EXECUTOR] Response type:", response.content[0]?.type); - - if (response.content[0]?.type === "text") { - console.log("[CLAUDE-EXECUTOR] Response preview:", response.content[0].text.substring(0, 500) + "..."); - } - - const executionFile = this.createExecutionLog(response); - - return { - conclusion: "success", - executionFile, - }; - } -} - -export async function runClaude( - config: ClaudeExecutorConfig, -): Promise { - const executor = new ClaudeExecutor(config); - return await executor.execute(); -} diff --git a/src/entrypoints/execute-claude.ts b/src/entrypoints/execute-claude.ts deleted file mode 100644 index 09a0239..0000000 --- a/src/entrypoints/execute-claude.ts +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/env bun - -import * as core from "@actions/core"; -import { runClaude, type ClaudeExecutorConfig } from "../claude/executor"; - -async function main() { - try { - console.log("[EXECUTE-CLAUDE] Starting execute-claude.ts entry point..."); - console.log(`[EXECUTE-CLAUDE] ANTHROPIC_API_KEY: ${process.env.ANTHROPIC_API_KEY ? '***' : 'undefined'}`); - console.log(`[EXECUTE-CLAUDE] MODEL: ${process.env.MODEL || 'undefined'}`); - console.log(`[EXECUTE-CLAUDE] ANTHROPIC_MODEL: ${process.env.ANTHROPIC_MODEL || 'undefined'}`); - console.log(`[EXECUTE-CLAUDE] PROMPT_FILE: ${process.env.PROMPT_FILE || 'undefined'}`); - console.log(`[EXECUTE-CLAUDE] ALLOWED_TOOLS: ${process.env.ALLOWED_TOOLS || 'undefined'}`); - console.log(`[EXECUTE-CLAUDE] DISALLOWED_TOOLS: ${process.env.DISALLOWED_TOOLS || 'undefined'}`); - console.log(`[EXECUTE-CLAUDE] MCP_CONFIG length: ${process.env.MCP_CONFIG?.length || 0}`); - console.log(`[EXECUTE-CLAUDE] USE_BEDROCK: ${process.env.USE_BEDROCK}`); - console.log(`[EXECUTE-CLAUDE] USE_VERTEX: ${process.env.USE_VERTEX}`); - - const config: ClaudeExecutorConfig = { - apiKey: process.env.ANTHROPIC_API_KEY, - model: process.env.ANTHROPIC_MODEL || process.env.MODEL, - promptFile: process.env.PROMPT_FILE, - prompt: process.env.PROMPT, - maxTurns: process.env.MAX_TURNS - ? parseInt(process.env.MAX_TURNS) - : undefined, - timeoutMinutes: process.env.TIMEOUT_MINUTES - ? parseInt(process.env.TIMEOUT_MINUTES) - : 30, - mcpConfig: process.env.MCP_CONFIG, - allowedTools: process.env.ALLOWED_TOOLS, - disallowedTools: process.env.DISALLOWED_TOOLS, - useBedrock: process.env.USE_BEDROCK === "true", - useVertex: process.env.USE_VERTEX === "true", - }; - - console.log("[EXECUTE-CLAUDE] Configuration prepared, starting Claude execution..."); - const result = await runClaude(config); - - // Set outputs for GitHub Actions - core.setOutput("conclusion", result.conclusion); - if (result.executionFile) { - core.setOutput("execution_file", result.executionFile); - } - - if (result.conclusion === "failure") { - core.setFailed(result.error || "Claude execution failed"); - } else { - console.log("Claude execution completed successfully"); - } - } catch (error) { - console.error("Failed to execute Claude:", error); - core.setFailed(`Failed to execute Claude: ${error}`); - } -} - -main().catch((error) => { - console.error("Unhandled error:", error); - core.setFailed(`Unhandled error: ${error}`); - process.exit(1); -});