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
+160 -51
View File
@@ -10746,7 +10746,7 @@ var require_mock_interceptor = __commonJS({
var require_mock_client = __commonJS({
"node_modules/.pnpm/undici@5.29.0/node_modules/undici/lib/mock/mock-client.js"(exports2, module2) {
"use strict";
var { promisify } = require("util");
var { promisify: promisify2 } = require("util");
var Client = require_client();
var { buildMockDispatch } = require_mock_utils();
var {
@@ -10786,7 +10786,7 @@ var require_mock_client = __commonJS({
return new MockInterceptor(opts, this[kDispatches]);
}
async [kClose]() {
await promisify(this[kOriginalClose])();
await promisify2(this[kOriginalClose])();
this[kConnected] = 0;
this[kMockAgent][Symbols.kClients].delete(this[kOrigin]);
}
@@ -10799,7 +10799,7 @@ var require_mock_client = __commonJS({
var require_mock_pool = __commonJS({
"node_modules/.pnpm/undici@5.29.0/node_modules/undici/lib/mock/mock-pool.js"(exports2, module2) {
"use strict";
var { promisify } = require("util");
var { promisify: promisify2 } = require("util");
var Pool = require_pool();
var { buildMockDispatch } = require_mock_utils();
var {
@@ -10839,7 +10839,7 @@ var require_mock_pool = __commonJS({
return new MockInterceptor(opts, this[kDispatches]);
}
async [kClose]() {
await promisify(this[kOriginalClose])();
await promisify2(this[kOriginalClose])();
this[kConnected] = 0;
this[kMockAgent][Symbols.kClients].delete(this[kOrigin]);
}
@@ -17581,12 +17581,12 @@ var require_lib = __commonJS({
throw new Error("Client has already been disposed.");
}
const parsedUrl = new URL(requestUrl);
let info2 = this._prepareRequest(verb, parsedUrl, headers);
let info3 = this._prepareRequest(verb, parsedUrl, headers);
const maxTries = this._allowRetries && RetryableHttpVerbs.includes(verb) ? this._maxRetries + 1 : 1;
let numTries = 0;
let response;
do {
response = yield this.requestRaw(info2, data);
response = yield this.requestRaw(info3, data);
if (response && response.message && response.message.statusCode === HttpCodes.Unauthorized) {
let authenticationHandler;
for (const handler of this.handlers) {
@@ -17596,7 +17596,7 @@ var require_lib = __commonJS({
}
}
if (authenticationHandler) {
return authenticationHandler.handleAuthentication(this, info2, data);
return authenticationHandler.handleAuthentication(this, info3, data);
} else {
return response;
}
@@ -17619,8 +17619,8 @@ var require_lib = __commonJS({
}
}
}
info2 = this._prepareRequest(verb, parsedRedirectUrl, headers);
response = yield this.requestRaw(info2, data);
info3 = this._prepareRequest(verb, parsedRedirectUrl, headers);
response = yield this.requestRaw(info3, data);
redirectsRemaining--;
}
if (!response.message.statusCode || !HttpResponseRetryCodes.includes(response.message.statusCode)) {
@@ -17649,7 +17649,7 @@ var require_lib = __commonJS({
* @param info
* @param data
*/
requestRaw(info2, data) {
requestRaw(info3, data) {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve, reject) => {
function callbackForResult(err, res) {
@@ -17661,7 +17661,7 @@ var require_lib = __commonJS({
resolve(res);
}
}
this.requestRawWithCallback(info2, data, callbackForResult);
this.requestRawWithCallback(info3, data, callbackForResult);
});
});
}
@@ -17671,12 +17671,12 @@ var require_lib = __commonJS({
* @param data
* @param onResult
*/
requestRawWithCallback(info2, data, onResult) {
requestRawWithCallback(info3, data, onResult) {
if (typeof data === "string") {
if (!info2.options.headers) {
info2.options.headers = {};
if (!info3.options.headers) {
info3.options.headers = {};
}
info2.options.headers["Content-Length"] = Buffer.byteLength(data, "utf8");
info3.options.headers["Content-Length"] = Buffer.byteLength(data, "utf8");
}
let callbackCalled = false;
function handleResult(err, res) {
@@ -17685,7 +17685,7 @@ var require_lib = __commonJS({
onResult(err, res);
}
}
const req = info2.httpModule.request(info2.options, (msg) => {
const req = info3.httpModule.request(info3.options, (msg) => {
const res = new HttpClientResponse(msg);
handleResult(void 0, res);
});
@@ -17697,7 +17697,7 @@ var require_lib = __commonJS({
if (socket) {
socket.end();
}
handleResult(new Error(`Request timeout: ${info2.options.path}`));
handleResult(new Error(`Request timeout: ${info3.options.path}`));
});
req.on("error", function(err) {
handleResult(err);
@@ -17733,27 +17733,27 @@ var require_lib = __commonJS({
return this._getProxyAgentDispatcher(parsedUrl, proxyUrl);
}
_prepareRequest(method, requestUrl, headers) {
const info2 = {};
info2.parsedUrl = requestUrl;
const usingSsl = info2.parsedUrl.protocol === "https:";
info2.httpModule = usingSsl ? https : http;
const info3 = {};
info3.parsedUrl = requestUrl;
const usingSsl = info3.parsedUrl.protocol === "https:";
info3.httpModule = usingSsl ? https : http;
const defaultPort = usingSsl ? 443 : 80;
info2.options = {};
info2.options.host = info2.parsedUrl.hostname;
info2.options.port = info2.parsedUrl.port ? parseInt(info2.parsedUrl.port) : defaultPort;
info2.options.path = (info2.parsedUrl.pathname || "") + (info2.parsedUrl.search || "");
info2.options.method = method;
info2.options.headers = this._mergeHeaders(headers);
info3.options = {};
info3.options.host = info3.parsedUrl.hostname;
info3.options.port = info3.parsedUrl.port ? parseInt(info3.parsedUrl.port) : defaultPort;
info3.options.path = (info3.parsedUrl.pathname || "") + (info3.parsedUrl.search || "");
info3.options.method = method;
info3.options.headers = this._mergeHeaders(headers);
if (this.userAgent != null) {
info2.options.headers["user-agent"] = this.userAgent;
info3.options.headers["user-agent"] = this.userAgent;
}
info2.options.agent = this._getAgent(info2.parsedUrl);
info3.options.agent = this._getAgent(info3.parsedUrl);
if (this.handlers) {
for (const handler of this.handlers) {
handler.prepareRequest(info2.options);
handler.prepareRequest(info3.options);
}
}
return info2;
return info3;
}
_mergeHeaders(headers) {
if (this.requestOptions && this.requestOptions.headers) {
@@ -19411,7 +19411,7 @@ var require_exec = __commonJS({
exports2.getExecOutput = exports2.exec = void 0;
var string_decoder_1 = require("string_decoder");
var tr = __importStar(require_toolrunner());
function exec(commandLine, args, options) {
function exec2(commandLine, args, options) {
return __awaiter(this, void 0, void 0, function* () {
const commandArgs = tr.argStringToArray(commandLine);
if (commandArgs.length === 0) {
@@ -19423,7 +19423,7 @@ var require_exec = __commonJS({
return runner.exec();
});
}
exports2.exec = exec;
exports2.exec = exec2;
function getExecOutput(commandLine, args, options) {
var _a, _b;
return __awaiter(this, void 0, void 0, function* () {
@@ -19446,7 +19446,7 @@ var require_exec = __commonJS({
}
};
const listeners = Object.assign(Object.assign({}, options === null || options === void 0 ? void 0 : options.listeners), { stdout: stdOutListener, stderr: stdErrListener });
const exitCode = yield exec(commandLine, args, Object.assign(Object.assign({}, options), { listeners }));
const exitCode = yield exec2(commandLine, args, Object.assign(Object.assign({}, options), { listeners }));
stdout += stdoutDecoder.end();
stderr += stderrDecoder.end();
return {
@@ -19524,12 +19524,12 @@ var require_platform = __commonJS({
Object.defineProperty(exports2, "__esModule", { value: true });
exports2.getDetails = exports2.isLinux = exports2.isMacOS = exports2.isWindows = exports2.arch = exports2.platform = void 0;
var os_1 = __importDefault(require("os"));
var exec = __importStar(require_exec());
var exec2 = __importStar(require_exec());
var getWindowsInfo = () => __awaiter(void 0, void 0, void 0, function* () {
const { stdout: version } = yield exec.getExecOutput('powershell -command "(Get-CimInstance -ClassName Win32_OperatingSystem).Version"', void 0, {
const { stdout: version } = yield exec2.getExecOutput('powershell -command "(Get-CimInstance -ClassName Win32_OperatingSystem).Version"', void 0, {
silent: true
});
const { stdout: name } = yield exec.getExecOutput('powershell -command "(Get-CimInstance -ClassName Win32_OperatingSystem).Caption"', void 0, {
const { stdout: name } = yield exec2.getExecOutput('powershell -command "(Get-CimInstance -ClassName Win32_OperatingSystem).Caption"', void 0, {
silent: true
});
return {
@@ -19539,7 +19539,7 @@ var require_platform = __commonJS({
});
var getMacOsInfo = () => __awaiter(void 0, void 0, void 0, function* () {
var _a, _b, _c, _d;
const { stdout } = yield exec.getExecOutput("sw_vers", void 0, {
const { stdout } = yield exec2.getExecOutput("sw_vers", void 0, {
silent: true
});
const version = (_b = (_a = stdout.match(/ProductVersion:\s*(.+)/)) === null || _a === void 0 ? void 0 : _a[1]) !== null && _b !== void 0 ? _b : "";
@@ -19550,7 +19550,7 @@ var require_platform = __commonJS({
};
});
var getLinuxInfo = () => __awaiter(void 0, void 0, void 0, function* () {
const { stdout } = yield exec.getExecOutput("lsb_release", ["-i", "-r", "-s"], {
const { stdout } = yield exec2.getExecOutput("lsb_release", ["-i", "-r", "-s"], {
silent: true
});
const [name, version] = stdout.trim().split("\n");
@@ -19735,18 +19735,18 @@ Support boolean input list: \`true | True | TRUE | false | False | FALSE\``);
(0, command_1.issueCommand)("error", (0, utils_1.toCommandProperties)(properties), message instanceof Error ? message.toString() : message);
}
exports2.error = error;
function warning(message, properties = {}) {
function warning2(message, properties = {}) {
(0, command_1.issueCommand)("warning", (0, utils_1.toCommandProperties)(properties), message instanceof Error ? message.toString() : message);
}
exports2.warning = warning;
exports2.warning = warning2;
function notice(message, properties = {}) {
(0, command_1.issueCommand)("notice", (0, utils_1.toCommandProperties)(properties), message instanceof Error ? message.toString() : message);
}
exports2.notice = notice;
function info2(message) {
function info3(message) {
process.stdout.write(message + os.EOL);
}
exports2.info = info2;
exports2.info = info3;
function startGroup(name) {
(0, command_1.issue)("group", name);
}
@@ -19809,16 +19809,125 @@ Support boolean input list: \`true | True | TRUE | false | False | FALSE\``);
});
// index.ts
var core2 = __toESM(require_core(), 1);
// agents/claude.ts
var core = __toESM(require_core(), 1);
try {
const message = core.getInput("message") || "Hello from Pullfrog Action!";
console.log(`\u{1F438} Pullfrog says: ${message}`);
core.info(`Action executed successfully with message: ${message}`);
core.setOutput("message", message);
} catch (error) {
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
core.setFailed(`Action failed: ${errorMessage}`);
// utils/exec.ts
var import_node_child_process = require("node:child_process");
var import_node_util = require("node:util");
var execAsync = (0, import_node_util.promisify)(import_node_child_process.exec);
async function executeCommand(command, env) {
const execEnv = env ? { ...process.env, ...env } : process.env;
return execAsync(command, { env: execEnv });
}
// utils/files.ts
var import_node_fs = require("node:fs");
var import_node_os = require("node:os");
var import_node_path = require("node:path");
function createTempFile(content, filename = "temp.txt") {
const tempDir = (0, import_node_fs.mkdtempSync)((0, import_node_path.join)((0, import_node_os.tmpdir)(), "pullfrog-"));
const filePath = (0, import_node_path.join)(tempDir, filename);
(0, import_node_fs.writeFileSync)(filePath, content);
return filePath;
}
// agents/claude.ts
var ClaudeAgent = class {
constructor(config) {
if (!config.apiKey) {
throw new Error("Claude agent requires an API key");
}
this.apiKey = config.apiKey;
}
/**
* Install Claude Code CLI
*/
async install() {
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) {
core.info("Executing Claude Code...");
try {
const promptFile = createTempFile(prompt, "prompt.txt");
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 || void 0,
metadata: {
promptFile,
command
}
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : "Unknown error";
return {
success: false,
error: `Failed to execute Claude Code: ${errorMessage}`
};
}
}
};
// agents/factory.ts
function createAgent(type, config) {
switch (type) {
case "claude":
return new ClaudeAgent(config);
default:
throw new Error(`Unsupported agent type: ${type}`);
}
}
// index.ts
async function main() {
try {
const prompt = core2.getInput("prompt", { required: true });
const anthropicApiKey = core2.getInput("anthropic_api_key", { required: true });
if (!anthropicApiKey) {
throw new Error("anthropic_api_key is required");
}
core2.info(`\u{1F438} Pullfrog Claude Code Action starting...`);
core2.info(`Prompt: ${prompt}`);
const agent = createAgent("claude", { apiKey: anthropicApiKey });
await agent.install();
const result = await agent.execute(prompt);
if (!result.success) {
throw new Error(result.error || "Agent execution failed");
}
core2.setOutput("status", "success");
core2.setOutput("prompt", prompt);
core2.setOutput("output", result.output || "");
} catch (error) {
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
core2.setFailed(`Action failed: ${errorMessage}`);
}
}
main();
/*! Bundled license information:
undici/lib/fetch/body.js: