mirror of
https://github.com/markwylde/claude-code-gitea-action.git
synced 2026-02-19 18:12:50 +08:00
Attempt to make this work
This commit is contained in:
25
action.yml
25
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
|
||||
|
||||
@@ -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<string, string>;
|
||||
}
|
||||
|
||||
interface MCPConfig {
|
||||
mcpServers: Record<string, MCPServer>;
|
||||
}
|
||||
|
||||
export class ClaudeExecutor {
|
||||
private config: ClaudeExecutorConfig;
|
||||
|
||||
constructor(config: ClaudeExecutorConfig) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
private async readPrompt(): Promise<string> {
|
||||
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<string | null> {
|
||||
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<ClaudeExecutorResult> {
|
||||
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<ClaudeExecutorResult> {
|
||||
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<ClaudeExecutorResult> {
|
||||
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<ClaudeExecutorResult> {
|
||||
const executor = new ClaudeExecutor(config);
|
||||
return await executor.execute();
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
Reference in New Issue
Block a user