mirror of
https://github.com/markwylde/claude-code-gitea-action.git
synced 2026-02-20 02:22:49 +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 }}
|
cd ${{ github.action_path }}
|
||||||
bun install
|
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
|
- name: Prepare action
|
||||||
id: prepare
|
id: prepare
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -114,9 +103,17 @@ runs:
|
|||||||
- name: Run Claude Code
|
- name: Run Claude Code
|
||||||
id: claude-code
|
id: claude-code
|
||||||
if: steps.prepare.outputs.contains_trigger == 'true'
|
if: steps.prepare.outputs.contains_trigger == 'true'
|
||||||
shell: bash
|
uses: anthropics/claude-code-base-action@c8e31bd52d9a149b3f8309d7978c6edaa282688d # v0.0.8
|
||||||
run: |
|
with:
|
||||||
bun run ${{ github.action_path }}/src/entrypoints/execute-claude.ts
|
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:
|
env:
|
||||||
# Core configuration
|
# Core configuration
|
||||||
PROMPT_FILE: /tmp/claude-prompts/claude-prompt.txt
|
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