Refactor action with agent interface system and make anthropic_api_key optional

- Created extensible agent interface with install() and execute() methods
- Moved Claude Code logic to agents/claude.ts implementing Agent interface
- Added utilities directory for reusable functions (exec, files)
- Refactored index.ts to be minimal (35 lines) using agent abstraction
- Made anthropic_api_key optional in action.yml
- Updated Node.js imports to use node: prefix convention
- Bumped version to 0.0.5
- Architecture now supports multiple agents (OpenAI, Gemini, etc.)
This commit is contained in:
Colin McDonnell
2025-08-28 13:37:20 -07:00
parent 6a0d9cc244
commit 1abbd7ff41
10 changed files with 364 additions and 71 deletions
+78
View File
@@ -0,0 +1,78 @@
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
const command = `$HOME/.local/bin/claude --dangerously-skip-permissions "${promptFile}"`;
core.info(`Executing: ${command}`);
const { stdout, stderr } = await executeCommand(command, {
ANTHROPIC_API_KEY: this.apiKey,
});
if (stderr) {
core.warning(`Claude Code stderr: ${stderr}`);
}
if (stdout) {
core.info("Claude Code output:");
console.log(stdout);
}
core.info("Claude Code executed successfully");
return {
success: true,
output: stdout,
error: stderr || undefined,
metadata: {
promptFile,
command,
},
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : "Unknown error";
return {
success: false,
error: `Failed to execute Claude Code: ${errorMessage}`,
};
}
}
}
+16
View File
@@ -0,0 +1,16 @@
import { ClaudeAgent } from "./claude";
import type { Agent, AgentConfig } from "./types";
export type AgentType = "claude";
/**
* Factory for creating agent instances
*/
export function createAgent(type: AgentType, config: AgentConfig): Agent {
switch (type) {
case "claude":
return new ClaudeAgent(config);
default:
throw new Error(`Unsupported agent type: ${type}`);
}
}
+3
View File
@@ -0,0 +1,3 @@
export * from "./claude";
export * from "./factory";
export * from "./types";
+34
View File
@@ -0,0 +1,34 @@
/**
* Standard interface for all Pullfrog agents
*/
export interface Agent {
/**
* Install the agent and any required dependencies
*/
install(): Promise<void>;
/**
* Execute the agent with the given prompt
* @param prompt The prompt to send to the agent
* @param options Additional options specific to the agent
*/
execute(prompt: string, options?: Record<string, any>): Promise<AgentResult>;
}
/**
* Result returned by agent execution
*/
export interface AgentResult {
success: boolean;
output?: string;
error?: string;
metadata?: Record<string, any>;
}
/**
* Configuration for agent creation
*/
export interface AgentConfig {
apiKey?: string;
[key: string]: any;
}