Code style (#97)

* Cleanup

* fix: populate deny array before assigning to config, add CursorCliConfig type

* Fix deny array ordering and add CursorCliConfig type

Move deny array population before config declaration to avoid
relying on reference semantics. Add proper type interface for
the CLI config object.

---------

Co-authored-by: pullfrog[bot] <226033991+pullfrog[bot]@users.noreply.github.com>
This commit is contained in:
Colin McDonnell
2026-01-15 22:06:53 +00:00
committed by pullfrog[bot]
parent 0ccaa68d3a
commit 2d2d31adfa
13 changed files with 713 additions and 779 deletions
+14 -14
View File
@@ -3,7 +3,7 @@ import type { Effort } from "../external.ts";
import packageJson from "../package.json" with { type: "json" }; import packageJson from "../package.json" with { type: "json" };
import { log } from "../utils/cli.ts"; import { log } from "../utils/cli.ts";
import { addInstructions } from "./instructions.ts"; import { addInstructions } from "./instructions.ts";
import { agent, createAgentEnv, installFromNpmTarball, type ToolPermissions } from "./shared.ts"; import { type AgentConfig, agent, createAgentEnv, installFromNpmTarball } from "./shared.ts";
// Model selection based on effort level // Model selection based on effort level
// Note: mini uses Haiku for speed, auto uses opusplan for balance, max uses Opus for capability // Note: mini uses Haiku for speed, auto uses opusplan for balance, max uses Opus for capability
@@ -22,14 +22,14 @@ const claudeEffortModels: Record<Effort, string> = {
/** /**
* Build disallowedTools list from ToolPermissions. * Build disallowedTools list from ToolPermissions.
*/ */
function buildDisallowedTools(tools: ToolPermissions): string[] { function buildDisallowedTools(ctx: AgentConfig): string[] {
const disallowed: string[] = []; const disallowed: string[] = [];
if (tools.web === "disabled") disallowed.push("WebFetch"); if (ctx.tools.web === "disabled") disallowed.push("WebFetch");
if (tools.search === "disabled") disallowed.push("WebSearch"); if (ctx.tools.search === "disabled") disallowed.push("WebSearch");
if (tools.write === "disabled") disallowed.push("Write"); if (ctx.tools.write === "disabled") disallowed.push("Write");
// both "disabled" and "restricted" block native bash // both "disabled" and "restricted" block native bash
// "restricted" means use MCP bash tool instead // "restricted" means use MCP bash tool instead
if (tools.bash !== "enabled") disallowed.push("Bash"); if (ctx.tools.bash !== "enabled") disallowed.push("Bash");
return disallowed; return disallowed;
} }
@@ -43,19 +43,19 @@ export const claude = agent({
executablePath: "cli.js", executablePath: "cli.js",
}); });
}, },
run: async ({ payload, mcpServers, apiKey, cliPath, repo, effort, tools }) => { run: async (ctx) => {
// Ensure API key is NOT in process.env - only pass via SDK's env option // Ensure API key is NOT in process.env - only pass via SDK's env option
delete process.env.ANTHROPIC_API_KEY; delete process.env.ANTHROPIC_API_KEY;
const prompt = addInstructions({ payload, repo, tools }); const prompt = addInstructions(ctx);
log.group("Full prompt", () => log.info(prompt)); log.group("Full prompt", () => log.info(prompt));
// select model based on effort level // select model based on effort level
const model = claudeEffortModels[effort]; const model = claudeEffortModels[ctx.effort];
log.info(`Using model: ${model} (effort: ${effort})`); log.info(`Using model: ${model} (effort: ${ctx.effort})`);
// build disallowedTools based on tool permissions // build disallowedTools based on tool permissions
const disallowedTools = buildDisallowedTools(tools); const disallowedTools = buildDisallowedTools(ctx);
if (disallowedTools.length > 0) { if (disallowedTools.length > 0) {
log.info(`🔒 disallowed tools: ${disallowedTools.join(", ")}`); log.info(`🔒 disallowed tools: ${disallowedTools.join(", ")}`);
} }
@@ -65,10 +65,10 @@ export const claude = agent({
const queryOptions: Options = { const queryOptions: Options = {
permissionMode: "bypassPermissions" as const, permissionMode: "bypassPermissions" as const,
disallowedTools, disallowedTools,
mcpServers, mcpServers: ctx.mcpServers,
model, model,
pathToClaudeCodeExecutable: cliPath, pathToClaudeCodeExecutable: ctx.cliPath,
env: createAgentEnv({ ANTHROPIC_API_KEY: apiKey }), env: createAgentEnv({ ANTHROPIC_API_KEY: ctx.apiKey }),
}; };
const queryInstance = query({ const queryInstance = query({
+17 -27
View File
@@ -1,6 +1,5 @@
import { mkdirSync, writeFileSync } from "node:fs"; import { mkdirSync, writeFileSync } from "node:fs";
import { join } from "node:path"; import { join } from "node:path";
import type { McpHttpServerConfig } from "@anthropic-ai/claude-agent-sdk";
import { import {
Codex, Codex,
type CodexOptions, type CodexOptions,
@@ -13,9 +12,9 @@ import { log } from "../utils/cli.ts";
import { addInstructions } from "./instructions.ts"; import { addInstructions } from "./instructions.ts";
import { import {
agent, agent,
type AgentConfig,
installFromNpmTarball, installFromNpmTarball,
setupProcessAgentEnv, setupProcessAgentEnv,
type ToolPermissions,
} from "./shared.ts"; } from "./shared.ts";
// model configuration based on effort level // model configuration based on effort level
@@ -35,20 +34,15 @@ const codexReasoningEffort: Record<Effort, ModelReasoningEffort | undefined> = {
max: "high", max: "high",
}; };
interface WriteCodexConfigParams { function writeCodexConfig(ctx: AgentConfig): string {
tempHome: string; const tempHome = process.env.PULLFROG_TEMP_DIR!;
mcpServers: Record<string, McpHttpServerConfig>;
tools: ToolPermissions;
}
function writeCodexConfig({ tempHome, mcpServers, tools }: WriteCodexConfigParams): string {
const codexDir = join(tempHome, ".codex"); const codexDir = join(tempHome, ".codex");
mkdirSync(codexDir, { recursive: true }); mkdirSync(codexDir, { recursive: true });
const configPath = join(codexDir, "config.toml"); const configPath = join(codexDir, "config.toml");
// build MCP servers section // build MCP servers section
const mcpServerSections: string[] = []; const mcpServerSections: string[] = [];
for (const [name, config] of Object.entries(mcpServers)) { for (const [name, config] of Object.entries(ctx.mcpServers)) {
if (config.type !== "http") continue; if (config.type !== "http") continue;
log.info(`» adding MCP server '${name}' at ${config.url}`); log.info(`» adding MCP server '${name}' at ${config.url}`);
mcpServerSections.push(`[mcp_servers.${name}]\nurl = "${config.url}"`); mcpServerSections.push(`[mcp_servers.${name}]\nurl = "${config.url}"`);
@@ -58,7 +52,7 @@ function writeCodexConfig({ tempHome, mcpServers, tools }: WriteCodexConfigParam
// disable native shell if bash is "disabled" or "restricted" // disable native shell if bash is "disabled" or "restricted"
// when "restricted", agent uses MCP bash tool which filters secrets // when "restricted", agent uses MCP bash tool which filters secrets
const features: string[] = []; const features: string[] = [];
if (tools.bash !== "enabled") { if (ctx.tools.bash !== "enabled") {
features.push("shell_command_tool = false"); features.push("shell_command_tool = false");
features.push("unified_exec = false"); features.push("unified_exec = false");
} }
@@ -74,7 +68,7 @@ ${mcpServerSections.join("\n\n")}
); );
log.info( log.info(
`» Codex config written to ${configPath} (shell: ${tools.bash === "enabled" ? "enabled" : "disabled"})` `» Codex config written to ${configPath} (shell: ${ctx.tools.bash === "enabled" ? "enabled" : "disabled"})`
); );
return codexDir; return codexDir;
@@ -89,28 +83,24 @@ export const codex = agent({
executablePath: "bin/codex.js", executablePath: "bin/codex.js",
}); });
}, },
run: async ({ payload, mcpServers, apiKey, cliPath, repo, effort, tools }) => { run: async (ctx) => {
const tempHome = process.env.PULLFROG_TEMP_DIR!; const tempHome = process.env.PULLFROG_TEMP_DIR!;
// create config directory for codex before setting HOME // create config directory for codex before setting HOME
const configDir = join(tempHome, ".config", "codex"); const configDir = join(tempHome, ".config", "codex");
mkdirSync(configDir, { recursive: true }); mkdirSync(configDir, { recursive: true });
const codexDir = writeCodexConfig({ const codexDir = writeCodexConfig(ctx);
tempHome,
mcpServers,
tools,
});
setupProcessAgentEnv({ setupProcessAgentEnv({
OPENAI_API_KEY: apiKey, OPENAI_API_KEY: ctx.apiKey,
HOME: tempHome, HOME: tempHome,
CODEX_HOME: codexDir, // point Codex to our config directory CODEX_HOME: codexDir, // point Codex to our config directory
}); });
// get model and reasoning effort based on effort level // get model and reasoning effort based on effort level
const model = codexModel[effort]; const model = codexModel[ctx.effort];
const modelReasoningEffort = codexReasoningEffort[effort]; const modelReasoningEffort = codexReasoningEffort[ctx.effort];
log.info(`Using model: ${model}`); log.info(`Using model: ${model}`);
if (modelReasoningEffort) { if (modelReasoningEffort) {
log.info(`Using modelReasoningEffort: ${modelReasoningEffort}`); log.info(`Using modelReasoningEffort: ${modelReasoningEffort}`);
@@ -118,8 +108,8 @@ export const codex = agent({
// Configure Codex // Configure Codex
const codexOptions: CodexOptions = { const codexOptions: CodexOptions = {
apiKey, apiKey: ctx.apiKey,
codexPathOverride: cliPath, codexPathOverride: ctx.cliPath,
}; };
const codex = new Codex(codexOptions); const codex = new Codex(codexOptions);
@@ -129,11 +119,11 @@ export const codex = agent({
model, model,
approvalPolicy: "never" as const, approvalPolicy: "never" as const,
// write: "disabled" → read-only sandbox, otherwise full access for git ops // write: "disabled" → read-only sandbox, otherwise full access for git ops
sandboxMode: tools.write === "disabled" ? "read-only" : "danger-full-access", sandboxMode: ctx.tools.write === "disabled" ? "read-only" : "danger-full-access",
// web: controls network access // web: controls network access
networkAccessEnabled: tools.web !== "disabled", networkAccessEnabled: ctx.tools.web !== "disabled",
// search: controls web search // search: controls web search
webSearchEnabled: tools.search !== "disabled", webSearchEnabled: ctx.tools.search !== "disabled",
...(modelReasoningEffort && { modelReasoningEffort }), ...(modelReasoningEffort && { modelReasoningEffort }),
}; };
@@ -144,7 +134,7 @@ export const codex = agent({
const thread = codex.startThread(threadOptions); const thread = codex.startThread(threadOptions);
try { try {
const streamedTurn = await thread.runStreamed(addInstructions({ payload, repo, tools })); const streamedTurn = await thread.runStreamed(addInstructions(ctx));
let finalOutput = ""; let finalOutput = "";
for await (const event of streamedTurn.events) { for await (const event of streamedTurn.events) {
+33 -37
View File
@@ -5,13 +5,7 @@ import { join } from "node:path";
import type { Effort } from "../external.ts"; import type { Effort } from "../external.ts";
import { log } from "../utils/cli.ts"; import { log } from "../utils/cli.ts";
import { addInstructions } from "./instructions.ts"; import { addInstructions } from "./instructions.ts";
import { import { type AgentConfig, agent, createAgentEnv, installFromCurl } from "./shared.ts";
agent,
type ConfigureMcpServersParams,
createAgentEnv,
installFromCurl,
type ToolPermissions,
} from "./shared.ts";
// effort configuration for Cursor // effort configuration for Cursor
// only "max" overrides the model; mini/auto use default ("auto") // only "max" overrides the model; mini/auto use default ("auto")
@@ -101,9 +95,9 @@ export const cursor = agent({
executableName: "cursor-agent", executableName: "cursor-agent",
}); });
}, },
run: async ({ payload, apiKey, cliPath, mcpServers, repo, effort, tools }) => { run: async (ctx) => {
configureCursorMcpServers({ mcpServers, cliPath }); configureCursorMcpServers(ctx);
configureCursorTools({ tools }); configureCursorTools(ctx);
// determine model based on effort level // determine model based on effort level
// respect project's .cursor/cli.json if it specifies a model // respect project's .cursor/cli.json if it specifies a model
@@ -116,19 +110,19 @@ export const cursor = agent({
if (projectConfig.model) { if (projectConfig.model) {
log.info(`Using model from project .cursor/cli.json: ${projectConfig.model}`); log.info(`Using model from project .cursor/cli.json: ${projectConfig.model}`);
} else { } else {
modelOverride = cursorEffortModels[effort]; modelOverride = cursorEffortModels[ctx.effort];
} }
} catch { } catch {
modelOverride = cursorEffortModels[effort]; modelOverride = cursorEffortModels[ctx.effort];
} }
} else { } else {
modelOverride = cursorEffortModels[effort]; modelOverride = cursorEffortModels[ctx.effort];
} }
if (modelOverride) { if (modelOverride) {
log.info(`Using model: ${modelOverride} (effort: ${effort})`); log.info(`Using model: ${modelOverride} (effort: ${ctx.effort})`);
} else if (!existsSync(projectCliConfigPath)) { } else if (!existsSync(projectCliConfigPath)) {
log.info(`Using default model (effort: ${effort})`); log.info(`Using default model (effort: ${ctx.effort})`);
} }
// track logged model_call_ids to avoid duplicates // track logged model_call_ids to avoid duplicates
@@ -202,7 +196,7 @@ export const cursor = agent({
}; };
try { try {
const fullPrompt = addInstructions({ payload, repo, tools }); const fullPrompt = addInstructions(ctx);
log.group("Full prompt", () => log.info(fullPrompt)); log.group("Full prompt", () => log.info(fullPrompt));
// build CLI args // build CLI args
@@ -221,10 +215,10 @@ export const cursor = agent({
const startTime = Date.now(); const startTime = Date.now();
return new Promise((resolve) => { return new Promise((resolve) => {
const child = spawn(cliPath, cursorArgs, { const child = spawn(ctx.cliPath, cursorArgs, {
cwd: process.cwd(), cwd: process.cwd(),
env: createAgentEnv({ env: createAgentEnv({
CURSOR_API_KEY: apiKey, CURSOR_API_KEY: ctx.apiKey,
}), }),
stdio: ["ignore", "pipe", "pipe"], // Ignore stdin, pipe stdout/stderr stdio: ["ignore", "pipe", "pipe"], // Ignore stdin, pipe stdout/stderr
}); });
@@ -317,7 +311,7 @@ export const cursor = agent({
// There was an issue on macOS when you set HOME to a temp directory // There was an issue on macOS when you set HOME to a temp directory
// it was unable to find the macOS keychain and would fail // it was unable to find the macOS keychain and would fail
// temp solution is to stick with the actual $HOME // temp solution is to stick with the actual $HOME
function configureCursorMcpServers({ mcpServers }: ConfigureMcpServersParams) { function configureCursorMcpServers(ctx: AgentConfig): void {
const realHome = homedir(); const realHome = homedir();
const cursorConfigDir = join(realHome, ".cursor"); const cursorConfigDir = join(realHome, ".cursor");
const mcpConfigPath = join(cursorConfigDir, "mcp.json"); const mcpConfigPath = join(cursorConfigDir, "mcp.json");
@@ -325,7 +319,7 @@ function configureCursorMcpServers({ mcpServers }: ConfigureMcpServersParams) {
// Convert to Cursor's expected format (HTTP config) // Convert to Cursor's expected format (HTTP config)
const cursorMcpServers: Record<string, { type: string; url: string }> = {}; const cursorMcpServers: Record<string, { type: string; url: string }> = {};
for (const [serverName, serverConfig] of Object.entries(mcpServers)) { for (const [serverName, serverConfig] of Object.entries(ctx.mcpServers)) {
if (serverConfig.type !== "http") { if (serverConfig.type !== "http") {
throw new Error( throw new Error(
`Unsupported MCP server type for Cursor: ${(serverConfig as any).type || "unknown"}` `Unsupported MCP server type for Cursor: ${(serverConfig as any).type || "unknown"}`
@@ -342,8 +336,15 @@ function configureCursorMcpServers({ mcpServers }: ConfigureMcpServersParams) {
log.info(`» MCP config written to ${mcpConfigPath}`); log.info(`» MCP config written to ${mcpConfigPath}`);
} }
interface ConfigureCursorToolsParams { interface CursorCliConfig {
tools: ToolPermissions; permissions: {
allow: string[];
deny: string[];
};
sandbox?: {
mode: "enabled" | "disabled";
networkAccess?: "allowlist" | "full";
};
} }
/** /**
@@ -352,7 +353,7 @@ interface ConfigureCursorToolsParams {
* Config path: $XDG_CONFIG_HOME/cursor/ (not ~/.cursor/) because createAgentEnv * Config path: $XDG_CONFIG_HOME/cursor/ (not ~/.cursor/) because createAgentEnv
* sets XDG_CONFIG_HOME=$HOME/.config. * sets XDG_CONFIG_HOME=$HOME/.config.
*/ */
function configureCursorTools({ tools }: ConfigureCursorToolsParams): void { function configureCursorTools(ctx: AgentConfig): void {
const realHome = homedir(); const realHome = homedir();
const cursorConfigDir = join(realHome, ".config", "cursor"); const cursorConfigDir = join(realHome, ".config", "cursor");
const cliConfigPath = join(cursorConfigDir, "cli-config.json"); const cliConfigPath = join(cursorConfigDir, "cli-config.json");
@@ -360,23 +361,21 @@ function configureCursorTools({ tools }: ConfigureCursorToolsParams): void {
// build deny list based on tool permissions // build deny list based on tool permissions
const deny: string[] = []; const deny: string[] = [];
if (tools.search === "disabled") deny.push("WebSearch"); if (ctx.tools.search === "disabled") deny.push("WebSearch");
if (tools.write === "disabled") deny.push("Write(**)"); if (ctx.tools.write === "disabled") deny.push("Write(**)");
// both "disabled" and "restricted" block native shell // both "disabled" and "restricted" block native shell
if (tools.bash !== "enabled") deny.push("Shell(*)"); if (ctx.tools.bash !== "enabled") deny.push("Shell(*)");
// web: "disabled" requires sandbox with network blocking const config: CursorCliConfig = {
// sandbox.networkAccess: "allowlist" blocks network in shell subprocesses via seatbelt
const needsSandbox = tools.web === "disabled";
const config: Record<string, unknown> = {
permissions: { permissions: {
allow: tools.write === "disabled" ? ["Read(**)"] : ["Read(**)", "Write(**)"], allow: ctx.tools.write === "disabled" ? ["Read(**)"] : ["Read(**)", "Write(**)"],
deny, deny,
}, },
}; };
if (needsSandbox) { // web: "disabled" requires sandbox with network blocking
// sandbox.networkAccess: "allowlist" blocks network in shell subprocesses via seatbelt
if (ctx.tools.web === "disabled") {
config.sandbox = { config.sandbox = {
mode: "enabled", mode: "enabled",
networkAccess: "allowlist", networkAccess: "allowlist",
@@ -384,8 +383,5 @@ function configureCursorTools({ tools }: ConfigureCursorToolsParams): void {
} }
writeFileSync(cliConfigPath, JSON.stringify(config, null, 2), "utf-8"); writeFileSync(cliConfigPath, JSON.stringify(config, null, 2), "utf-8");
log.info(`» CLI config written to ${cliConfigPath}`); log.info(`» CLI config written to ${cliConfigPath}`, JSON.stringify(config, null, 2));
log.info(
`🔧 Cursor permissions: allow=${(config.permissions as { allow: string[] }).allow.join(",")}, deny=${deny.join(",") || "(none)"}, sandbox=${needsSandbox ? "enabled" : "disabled"}`
);
} }
+19 -36
View File
@@ -5,13 +5,7 @@ import type { Effort } from "../external.ts";
import { log } from "../utils/cli.ts"; import { log } from "../utils/cli.ts";
import { spawn } from "../utils/subprocess.ts"; import { spawn } from "../utils/subprocess.ts";
import { addInstructions } from "./instructions.ts"; import { addInstructions } from "./instructions.ts";
import { import { type AgentConfig, agent, createAgentEnv, installFromGithub } from "./shared.ts";
agent,
type ConfigureMcpServersParams,
createAgentEnv,
installFromGithub,
type ToolPermissions,
} from "./shared.ts";
// effort configuration: model + thinking level // effort configuration: model + thinking level
// thinkingLevel is set via settings.json modelConfig.generateContentConfig.thinkingConfig // thinkingLevel is set via settings.json modelConfig.generateContentConfig.thinkingConfig
@@ -172,18 +166,14 @@ export const gemini = agent({
...(githubInstallationToken && { githubInstallationToken }), ...(githubInstallationToken && { githubInstallationToken }),
}); });
}, },
run: async ({ payload, apiKey, mcpServers, cliPath, repo, effort, tools }) => { run: async (ctx) => {
// get model and thinking level based on effort const model = configureGeminiSettings(ctx);
const { model, thinkingLevel } = geminiEffortConfig[effort];
log.info(`Using model: ${model}, thinkingLevel: ${thinkingLevel}`);
configureGeminiSettings({ mcpServers, tools, thinkingLevel }); if (!ctx.apiKey) {
if (!apiKey) {
throw new Error("google_api_key or gemini_api_key is required for gemini agent"); throw new Error("google_api_key or gemini_api_key is required for gemini agent");
} }
const sessionPrompt = addInstructions({ payload, repo, tools }); const sessionPrompt = addInstructions(ctx);
log.group("Full prompt", () => log.info(sessionPrompt)); log.group("Full prompt", () => log.info(sessionPrompt));
// build CLI args - --yolo for auto-approval // build CLI args - --yolo for auto-approval
@@ -196,8 +186,8 @@ export const gemini = agent({
try { try {
const result = await spawn({ const result = await spawn({
cmd: "node", cmd: "node",
args: [cliPath, ...args], args: [ctx.cliPath, ...args],
env: createAgentEnv({ GEMINI_API_KEY: apiKey }), env: createAgentEnv({ GEMINI_API_KEY: ctx.apiKey }),
onStdout: async (chunk) => { onStdout: async (chunk) => {
const text = chunk.toString(); const text = chunk.toString();
finalOutput += text; finalOutput += text;
@@ -270,25 +260,16 @@ export const gemini = agent({
}, },
}); });
interface ConfigureGeminiParams {
mcpServers: ConfigureMcpServersParams["mcpServers"];
tools: ToolPermissions;
thinkingLevel: string;
}
/** /**
* Configure Gemini CLI settings by writing to settings.json. * Configure Gemini CLI settings by writing to settings.json.
* - MCP servers: uses `httpUrl` for HTTP/streamable transport * Returns the model to use for CLI args.
* - thinkingLevel: configured via modelConfig.generateContentConfig.thinkingConfig
* - tools.exclude: disables native tools based on ToolPermissions (v0.3.0+ format)
* *
* See: https://github.com/google-gemini/gemini-cli/blob/main/docs/get-started/configuration.md * See: https://github.com/google-gemini/gemini-cli/blob/main/docs/get-started/configuration.md
*/ */
function configureGeminiSettings({ function configureGeminiSettings(ctx: AgentConfig): string {
mcpServers, const { model, thinkingLevel } = geminiEffortConfig[ctx.effort];
tools, log.info(`Using model: ${model}, thinkingLevel: ${thinkingLevel}`);
thinkingLevel,
}: ConfigureGeminiParams): void {
const realHome = homedir(); const realHome = homedir();
const geminiConfigDir = join(realHome, ".gemini"); const geminiConfigDir = join(realHome, ".gemini");
const settingsPath = join(geminiConfigDir, "settings.json"); const settingsPath = join(geminiConfigDir, "settings.json");
@@ -319,7 +300,7 @@ function configureGeminiSettings({
excludeTools?: string[]; excludeTools?: string[];
} }
const geminiMcpServers: Record<string, GeminiMcpServerConfig> = {}; const geminiMcpServers: Record<string, GeminiMcpServerConfig> = {};
for (const [serverName, serverConfig] of Object.entries(mcpServers)) { for (const [serverName, serverConfig] of Object.entries(ctx.mcpServers)) {
if (serverConfig.type !== "http") { if (serverConfig.type !== "http") {
throw new Error( throw new Error(
`Unsupported MCP server type for Gemini: ${(serverConfig as { type?: string }).type || "unknown"}` `Unsupported MCP server type for Gemini: ${(serverConfig as { type?: string }).type || "unknown"}`
@@ -334,10 +315,10 @@ function configureGeminiSettings({
// build tools.exclude based on permissions (v0.3.0+ nested format) // build tools.exclude based on permissions (v0.3.0+ nested format)
const exclude: string[] = []; const exclude: string[] = [];
if (tools.bash !== "enabled") exclude.push("run_shell_command"); if (ctx.tools.bash !== "enabled") exclude.push("run_shell_command");
if (tools.write === "disabled") exclude.push("write_file"); if (ctx.tools.write === "disabled") exclude.push("write_file");
if (tools.web === "disabled") exclude.push("web_fetch"); if (ctx.tools.web === "disabled") exclude.push("web_fetch");
if (tools.search === "disabled") exclude.push("google_web_search"); if (ctx.tools.search === "disabled") exclude.push("google_web_search");
// merge with existing settings, overwriting mcpServers and modelConfig // merge with existing settings, overwriting mcpServers and modelConfig
const newSettings: Record<string, unknown> = { const newSettings: Record<string, unknown> = {
@@ -361,4 +342,6 @@ function configureGeminiSettings({
if (exclude.length > 0) { if (exclude.length > 0) {
log.info(`🔒 excluded tools: ${exclude.join(", ")}`); log.info(`🔒 excluded tools: ${exclude.join(", ")}`);
} }
return model;
} }
+10 -17
View File
@@ -3,14 +3,7 @@ import { encode as toonEncode } from "@toon-format/toon";
import type { Payload } from "../external.ts"; import type { Payload } from "../external.ts";
import { ghPullfrogMcpName } from "../external.ts"; import { ghPullfrogMcpName } from "../external.ts";
import { getModes } from "../modes.ts"; import { getModes } from "../modes.ts";
import type { ToolPermissions } from "./shared.ts"; import type { RepoInfo, ToolPermissions } from "./shared.ts";
interface RepoInfo {
owner: string;
name: string;
defaultBranch: string;
isPublic: boolean;
}
/** /**
* Build runtime context string with git status, repo data, and GitHub Actions variables * Build runtime context string with git status, repo data, and GitHub Actions variables
@@ -52,7 +45,7 @@ function buildRuntimeContext(repo: RepoInfo): string {
return lines.join("\n"); return lines.join("\n");
} }
interface AddInstructionsParams { interface AddInstructionsCtx {
payload: Payload; payload: Payload;
repo: RepoInfo; repo: RepoInfo;
tools: ToolPermissions; tools: ToolPermissions;
@@ -76,19 +69,19 @@ function getShellInstructions(bash: ToolPermissions["bash"]): string {
} }
} }
export const addInstructions = ({ payload, repo, tools }: AddInstructionsParams) => { export const addInstructions = (ctx: AddInstructionsCtx) => {
let encodedEvent = ""; let encodedEvent = "";
const eventKeys = Object.keys(payload.event); const eventKeys = Object.keys(ctx.payload.event);
if (eventKeys.length === 1 && eventKeys[0] === "trigger") { if (eventKeys.length === 1 && eventKeys[0] === "trigger") {
// no meaningful event data to encode // no meaningful event data to encode
} else { } else {
// extract only essential fields to reduce token usage // extract only essential fields to reduce token usage
// const essentialEvent = payload.event; // const essentialEvent = ctx.payload.event;
encodedEvent = toonEncode(payload.event); encodedEvent = toonEncode(ctx.payload.event);
} }
const runtimeContext = buildRuntimeContext(repo); const runtimeContext = buildRuntimeContext(ctx.repo);
return ( return (
` `
@@ -159,7 +152,7 @@ Tool names may be formatted as \`(server name)/(tool name)\`, for example: \`${g
**Efficiency**: Trust the tools - do not repeatedly verify file contents or git status after operations. If a tool reports success, proceed to the next step. Only verify if you encounter an actual error. **Efficiency**: Trust the tools - do not repeatedly verify file contents or git status after operations. If a tool reports success, proceed to the next step. Only verify if you encounter an actual error.
${getShellInstructions(tools.bash)} ${getShellInstructions(ctx.tools.bash)}
**Command execution**: Never use \`sleep\` to wait for commands to complete. Commands run synchronously - when the bash tool returns, the command has finished. **Command execution**: Never use \`sleep\` to wait for commands to complete. Commands run synchronously - when the bash tool returns, the command has finished.
@@ -180,7 +173,7 @@ ${getShellInstructions(tools.bash)}
### Available modes ### Available modes
${[...getModes({ disableProgressComment: payload.disableProgressComment }), ...payload.modes].map((w) => ` - "${w.name}": ${w.description}`).join("\n")} ${[...getModes({ disableProgressComment: ctx.payload.disableProgressComment }), ...ctx.payload.modes].map((w) => ` - "${w.name}": ${w.description}`).join("\n")}
### Following the mode instructions ### Following the mode instructions
@@ -190,7 +183,7 @@ Eagerly inspect the MCP tools available to you via the \`${ghPullfrogMcpName}\`
************* USER PROMPT ************* ************* USER PROMPT *************
${payload.prompt ${ctx.payload.prompt
.split("\n") .split("\n")
.map((line) => `> ${line}`) .map((line) => `> ${line}`)
.join("\n")} .join("\n")}
+12 -27
View File
@@ -5,11 +5,10 @@ import { spawn } from "../utils/subprocess.ts";
import { addInstructions } from "./instructions.ts"; import { addInstructions } from "./instructions.ts";
import { import {
agent, agent,
type ConfigureMcpServersParams, type AgentConfig,
createAgentEnv, createAgentEnv,
installFromNpmTarball, installFromNpmTarball,
setupProcessAgentEnv, setupProcessAgentEnv,
type ToolPermissions,
} from "./shared.ts"; } from "./shared.ts";
export const opencode = agent({ export const opencode = agent({
@@ -22,24 +21,15 @@ export const opencode = agent({
installDependencies: true, installDependencies: true,
}); });
}, },
run: async ({ run: async (ctx) => {
payload,
apiKey: _apiKey,
apiKeys,
mcpServers,
cliPath,
repo,
effort: _effort,
tools,
}) => {
// 1. configure home/config directory // 1. configure home/config directory
const tempHome = process.env.PULLFROG_TEMP_DIR!; const tempHome = process.env.PULLFROG_TEMP_DIR!;
const configDir = join(tempHome, ".config", "opencode"); const configDir = join(tempHome, ".config", "opencode");
mkdirSync(configDir, { recursive: true }); mkdirSync(configDir, { recursive: true });
configureOpenCode({ mcpServers, tools }); configureOpenCode(ctx);
const prompt = addInstructions({ payload, repo, tools }); const prompt = addInstructions(ctx);
log.group("Full prompt", () => log.info(prompt)); log.group("Full prompt", () => log.info(prompt));
// message positional must come right after "run", before flags // message positional must come right after "run", before flags
@@ -60,7 +50,7 @@ export const opencode = agent({
delete env.GITHUB_TOKEN; delete env.GITHUB_TOKEN;
// add API keys from apiKeys object // add API keys from apiKeys object
for (const [key, value] of Object.entries(apiKeys || {})) { for (const [key, value] of Object.entries(ctx.apiKeys || {})) {
env[key.toUpperCase()] = value; env[key.toUpperCase()] = value;
// also set GOOGLE_GENERATIVE_AI_API_KEY for Google provider compatibility // also set GOOGLE_GENERATIVE_AI_API_KEY for Google provider compatibility
if (key === "GEMINI_API_KEY") { if (key === "GEMINI_API_KEY") {
@@ -71,7 +61,7 @@ export const opencode = agent({
// run OpenCode in the repository directory (process.cwd() is set to GITHUB_WORKSPACE or repo dir) // run OpenCode in the repository directory (process.cwd() is set to GITHUB_WORKSPACE or repo dir)
const repoDir = process.cwd(); const repoDir = process.cwd();
log.info(`🚀 Starting OpenCode CLI: ${cliPath} ${args.join(" ")}`); log.info(`🚀 Starting OpenCode CLI: ${ctx.cliPath} ${args.join(" ")}`);
log.info(`📁 Working directory: ${repoDir}`); log.info(`📁 Working directory: ${repoDir}`);
log.debug(`🏠 HOME: ${env.HOME}`); log.debug(`🏠 HOME: ${env.HOME}`);
log.debug(`📋 XDG_CONFIG_HOME: ${env.XDG_CONFIG_HOME}`); log.debug(`📋 XDG_CONFIG_HOME: ${env.XDG_CONFIG_HOME}`);
@@ -83,7 +73,7 @@ export const opencode = agent({
let output = ""; let output = "";
let stdoutBuffer = ""; // buffer for incomplete lines across chunks let stdoutBuffer = ""; // buffer for incomplete lines across chunks
const result = await spawn({ const result = await spawn({
cmd: cliPath, cmd: ctx.cliPath,
args, args,
cwd: repoDir, cwd: repoDir,
env, env,
@@ -191,16 +181,11 @@ export const opencode = agent({
}, },
}); });
interface ConfigureOpenCodeParams {
mcpServers: ConfigureMcpServersParams["mcpServers"];
tools: ToolPermissions;
}
/** /**
* Configure OpenCode via opencode.json config file. * Configure OpenCode via opencode.json config file.
* Builds complete config with MCP servers and permissions in a single write to avoid race conditions. * Builds complete config with MCP servers and permissions in a single write to avoid race conditions.
*/ */
function configureOpenCode({ mcpServers, tools }: ConfigureOpenCodeParams): void { function configureOpenCode(ctx: AgentConfig): void {
const tempHome = process.env.PULLFROG_TEMP_DIR!; const tempHome = process.env.PULLFROG_TEMP_DIR!;
const configDir = join(tempHome, ".config", "opencode"); const configDir = join(tempHome, ".config", "opencode");
mkdirSync(configDir, { recursive: true }); mkdirSync(configDir, { recursive: true });
@@ -208,7 +193,7 @@ function configureOpenCode({ mcpServers, tools }: ConfigureOpenCodeParams): void
// build MCP servers config // build MCP servers config
const opencodeMcpServers: Record<string, { type: "remote"; url: string }> = {}; const opencodeMcpServers: Record<string, { type: "remote"; url: string }> = {};
for (const [serverName, serverConfig] of Object.entries(mcpServers)) { for (const [serverName, serverConfig] of Object.entries(ctx.mcpServers)) {
if (serverConfig.type !== "http") { if (serverConfig.type !== "http") {
log.error( log.error(
`unsupported MCP server type for OpenCode: ${(serverConfig as never as { type: string }).type || "unknown"}` `unsupported MCP server type for OpenCode: ${(serverConfig as never as { type: string }).type || "unknown"}`
@@ -227,9 +212,9 @@ function configureOpenCode({ mcpServers, tools }: ConfigureOpenCodeParams): void
// build permission object based on tool permissions // build permission object based on tool permissions
// note: OpenCode has no built-in web search tool // note: OpenCode has no built-in web search tool
const permission = { const permission = {
edit: tools.write === "disabled" ? "deny" : "allow", edit: ctx.tools.write === "disabled" ? "deny" : "allow",
bash: tools.bash !== "enabled" ? "deny" : "allow", bash: ctx.tools.bash !== "enabled" ? "deny" : "allow",
webfetch: tools.web === "disabled" ? "deny" : "allow", webfetch: ctx.tools.web === "disabled" ? "deny" : "allow",
doom_loop: "allow", doom_loop: "allow",
external_directory: "allow", external_directory: "allow",
}; };
-8
View File
@@ -66,14 +66,6 @@ export interface AgentConfig {
tools: ToolPermissions; tools: ToolPermissions;
} }
/**
* Parameters for configuring MCP servers
*/
export interface ConfigureMcpServersParams {
mcpServers: Record<string, McpHttpServerConfig>;
cliPath: string;
}
/** /**
* Add agent-specific vars to a whitelisted environment object for agent subprocesses. * Add agent-specific vars to a whitelisted environment object for agent subprocesses.
* *
+101 -112
View File
@@ -100032,11 +100032,19 @@ var package_default = {
packageManager: "pnpm@10.27.0+sha512.72d699da16b1179c14ba9e64dc71c9a40988cbdc65c264cb0e489db7de917f20dcf4d64d8723625f2969ba52d4b7e2a1170682d9ac2a5dcaeaab732b7e16f04a" packageManager: "pnpm@10.27.0+sha512.72d699da16b1179c14ba9e64dc71c9a40988cbdc65c264cb0e489db7de917f20dcf4d64d8723625f2969ba52d4b7e2a1170682d9ac2a5dcaeaab732b7e16f04a"
}; };
// utils/cli.ts // utils/log.ts
var core = __toESM(require_core(), 1); var core = __toESM(require_core(), 1);
var import_table = __toESM(require_src(), 1); var import_table = __toESM(require_src(), 1);
var isGitHubActions = !!process.env.GITHUB_ACTIONS; var isGitHubActions = !!process.env.GITHUB_ACTIONS;
var isDebugEnabled = () => process.env.LOG_LEVEL === "debug" || process.env.ACTIONS_STEP_DEBUG === "true" || process.env.RUNNER_DEBUG === "1" || core.isDebug(); var isDebugEnabled = () => process.env.LOG_LEVEL === "debug" || process.env.ACTIONS_STEP_DEBUG === "true" || process.env.RUNNER_DEBUG === "1" || core.isDebug();
function formatArgs(args3) {
return args3.map((arg) => {
if (typeof arg === "string") return arg;
if (arg instanceof Error) return `${arg.message}
${arg.stack}`;
return JSON.stringify(arg);
}).join(" ");
}
function startGroup2(name) { function startGroup2(name) {
if (isGitHubActions) { if (isGitHubActions) {
core.startGroup(name); core.startGroup(name);
@@ -100145,25 +100153,25 @@ function separator(length = 50) {
} }
var log = { var log = {
/** Print info message */ /** Print info message */
info: (message) => { info: (...args3) => {
core.info(message); core.info(formatArgs(args3));
}, },
/** Print warning message */ /** Print warning message */
warning: (message) => { warning: (...args3) => {
core.warning(message); core.warning(formatArgs(args3));
}, },
/** Print error message */ /** Print error message */
error: (message) => { error: (...args3) => {
core.error(message); core.error(formatArgs(args3));
}, },
/** Print success message */ /** Print success message */
success: (message) => { success: (...args3) => {
core.info(`\u2705 ${message}`); core.info(`\u2705 ${formatArgs(args3)}`);
}, },
/** Print debug message (only if LOG_LEVEL=debug) */ /** Print debug message (only if LOG_LEVEL=debug) */
debug: (message) => { debug: (...args3) => {
if (isDebugEnabled()) { if (isDebugEnabled()) {
core.info(`[DEBUG] ${message}`); core.info(`[DEBUG] ${formatArgs(args3)}`);
} }
}, },
/** Print a formatted box with text */ /** Print a formatted box with text */
@@ -100420,14 +100428,14 @@ function getShellInstructions(bash) {
} }
} }
} }
var addInstructions = ({ payload, repo, tools }) => { var addInstructions = (ctx) => {
let encodedEvent = ""; let encodedEvent = "";
const eventKeys = Object.keys(payload.event); const eventKeys = Object.keys(ctx.payload.event);
if (eventKeys.length === 1 && eventKeys[0] === "trigger") { if (eventKeys.length === 1 && eventKeys[0] === "trigger") {
} else { } else {
encodedEvent = encode(payload.event); encodedEvent = encode(ctx.payload.event);
} }
const runtimeContext = buildRuntimeContext(repo); const runtimeContext = buildRuntimeContext(ctx.repo);
return ` return `
*********************************************** ***********************************************
************* SYSTEM INSTRUCTIONS ************* ************* SYSTEM INSTRUCTIONS *************
@@ -100475,7 +100483,7 @@ Tool names may be formatted as \`(server name)/(tool name)\`, for example: \`${g
**Efficiency**: Trust the tools - do not repeatedly verify file contents or git status after operations. If a tool reports success, proceed to the next step. Only verify if you encounter an actual error. **Efficiency**: Trust the tools - do not repeatedly verify file contents or git status after operations. If a tool reports success, proceed to the next step. Only verify if you encounter an actual error.
${getShellInstructions(tools.bash)} ${getShellInstructions(ctx.tools.bash)}
**Command execution**: Never use \`sleep\` to wait for commands to complete. Commands run synchronously - when the bash tool returns, the command has finished. **Command execution**: Never use \`sleep\` to wait for commands to complete. Commands run synchronously - when the bash tool returns, the command has finished.
@@ -100496,7 +100504,7 @@ ${getShellInstructions(tools.bash)}
### Available modes ### Available modes
${[...getModes({ disableProgressComment: payload.disableProgressComment }), ...payload.modes].map((w) => ` - "${w.name}": ${w.description}`).join("\n")} ${[...getModes({ disableProgressComment: ctx.payload.disableProgressComment }), ...ctx.payload.modes].map((w) => ` - "${w.name}": ${w.description}`).join("\n")}
### Following the mode instructions ### Following the mode instructions
@@ -100506,7 +100514,7 @@ Eagerly inspect the MCP tools available to you via the \`${ghPullfrogMcpName}\`
************* USER PROMPT ************* ************* USER PROMPT *************
${payload.prompt.split("\n").map((line) => `> ${line}`).join("\n")} ${ctx.payload.prompt.split("\n").map((line) => `> ${line}`).join("\n")}
${encodedEvent ? `************* EVENT DATA ************* ${encodedEvent ? `************* EVENT DATA *************
@@ -104635,12 +104643,12 @@ var claudeEffortModels = {
auto: "opusplan", auto: "opusplan",
max: "opus" max: "opus"
}; };
function buildDisallowedTools(tools) { function buildDisallowedTools(ctx) {
const disallowed = []; const disallowed = [];
if (tools.web === "disabled") disallowed.push("WebFetch"); if (ctx.tools.web === "disabled") disallowed.push("WebFetch");
if (tools.search === "disabled") disallowed.push("WebSearch"); if (ctx.tools.search === "disabled") disallowed.push("WebSearch");
if (tools.write === "disabled") disallowed.push("Write"); if (ctx.tools.write === "disabled") disallowed.push("Write");
if (tools.bash !== "enabled") disallowed.push("Bash"); if (ctx.tools.bash !== "enabled") disallowed.push("Bash");
return disallowed; return disallowed;
} }
var claude = agent({ var claude = agent({
@@ -104653,23 +104661,23 @@ var claude = agent({
executablePath: "cli.js" executablePath: "cli.js"
}); });
}, },
run: async ({ payload, mcpServers, apiKey, cliPath, repo, effort, tools }) => { run: async (ctx) => {
delete process.env.ANTHROPIC_API_KEY; delete process.env.ANTHROPIC_API_KEY;
const prompt = addInstructions({ payload, repo, tools }); const prompt = addInstructions(ctx);
log.group("Full prompt", () => log.info(prompt)); log.group("Full prompt", () => log.info(prompt));
const model = claudeEffortModels[effort]; const model = claudeEffortModels[ctx.effort];
log.info(`Using model: ${model} (effort: ${effort})`); log.info(`Using model: ${model} (effort: ${ctx.effort})`);
const disallowedTools = buildDisallowedTools(tools); const disallowedTools = buildDisallowedTools(ctx);
if (disallowedTools.length > 0) { if (disallowedTools.length > 0) {
log.info(`\u{1F512} disallowed tools: ${disallowedTools.join(", ")}`); log.info(`\u{1F512} disallowed tools: ${disallowedTools.join(", ")}`);
} }
const queryOptions = { const queryOptions = {
permissionMode: "bypassPermissions", permissionMode: "bypassPermissions",
disallowedTools, disallowedTools,
mcpServers, mcpServers: ctx.mcpServers,
model, model,
pathToClaudeCodeExecutable: cliPath, pathToClaudeCodeExecutable: ctx.cliPath,
env: createAgentEnv({ ANTHROPIC_API_KEY: apiKey }) env: createAgentEnv({ ANTHROPIC_API_KEY: ctx.apiKey })
}; };
const queryInstance = query({ const queryInstance = query({
prompt, prompt,
@@ -105132,19 +105140,20 @@ var codexReasoningEffort = {
// use default // use default
max: "high" max: "high"
}; };
function writeCodexConfig({ tempHome, mcpServers, tools }) { function writeCodexConfig(ctx) {
const tempHome = process.env.PULLFROG_TEMP_DIR;
const codexDir = join6(tempHome, ".codex"); const codexDir = join6(tempHome, ".codex");
mkdirSync3(codexDir, { recursive: true }); mkdirSync3(codexDir, { recursive: true });
const configPath = join6(codexDir, "config.toml"); const configPath = join6(codexDir, "config.toml");
const mcpServerSections = []; const mcpServerSections = [];
for (const [name, config4] of Object.entries(mcpServers)) { for (const [name, config4] of Object.entries(ctx.mcpServers)) {
if (config4.type !== "http") continue; if (config4.type !== "http") continue;
log.info(`\xBB adding MCP server '${name}' at ${config4.url}`); log.info(`\xBB adding MCP server '${name}' at ${config4.url}`);
mcpServerSections.push(`[mcp_servers.${name}] mcpServerSections.push(`[mcp_servers.${name}]
url = "${config4.url}"`); url = "${config4.url}"`);
} }
const features = []; const features = [];
if (tools.bash !== "enabled") { if (ctx.tools.bash !== "enabled") {
features.push("shell_command_tool = false"); features.push("shell_command_tool = false");
features.push("unified_exec = false"); features.push("unified_exec = false");
} }
@@ -105159,7 +105168,7 @@ ${mcpServerSections.join("\n\n")}
`.trim() + "\n" `.trim() + "\n"
); );
log.info( log.info(
`\xBB Codex config written to ${configPath} (shell: ${tools.bash === "enabled" ? "enabled" : "disabled"})` `\xBB Codex config written to ${configPath} (shell: ${ctx.tools.bash === "enabled" ? "enabled" : "disabled"})`
); );
return codexDir; return codexDir;
} }
@@ -105172,41 +105181,37 @@ var codex = agent({
executablePath: "bin/codex.js" executablePath: "bin/codex.js"
}); });
}, },
run: async ({ payload, mcpServers, apiKey, cliPath, repo, effort, tools }) => { run: async (ctx) => {
const tempHome = process.env.PULLFROG_TEMP_DIR; const tempHome = process.env.PULLFROG_TEMP_DIR;
const configDir = join6(tempHome, ".config", "codex"); const configDir = join6(tempHome, ".config", "codex");
mkdirSync3(configDir, { recursive: true }); mkdirSync3(configDir, { recursive: true });
const codexDir = writeCodexConfig({ const codexDir = writeCodexConfig(ctx);
tempHome,
mcpServers,
tools
});
setupProcessAgentEnv({ setupProcessAgentEnv({
OPENAI_API_KEY: apiKey, OPENAI_API_KEY: ctx.apiKey,
HOME: tempHome, HOME: tempHome,
CODEX_HOME: codexDir CODEX_HOME: codexDir
// point Codex to our config directory // point Codex to our config directory
}); });
const model = codexModel[effort]; const model = codexModel[ctx.effort];
const modelReasoningEffort = codexReasoningEffort[effort]; const modelReasoningEffort = codexReasoningEffort[ctx.effort];
log.info(`Using model: ${model}`); log.info(`Using model: ${model}`);
if (modelReasoningEffort) { if (modelReasoningEffort) {
log.info(`Using modelReasoningEffort: ${modelReasoningEffort}`); log.info(`Using modelReasoningEffort: ${modelReasoningEffort}`);
} }
const codexOptions = { const codexOptions = {
apiKey, apiKey: ctx.apiKey,
codexPathOverride: cliPath codexPathOverride: ctx.cliPath
}; };
const codex2 = new Codex(codexOptions); const codex2 = new Codex(codexOptions);
const threadOptions = { const threadOptions = {
model, model,
approvalPolicy: "never", approvalPolicy: "never",
// write: "disabled" → read-only sandbox, otherwise full access for git ops // write: "disabled" → read-only sandbox, otherwise full access for git ops
sandboxMode: tools.write === "disabled" ? "read-only" : "danger-full-access", sandboxMode: ctx.tools.write === "disabled" ? "read-only" : "danger-full-access",
// web: controls network access // web: controls network access
networkAccessEnabled: tools.web !== "disabled", networkAccessEnabled: ctx.tools.web !== "disabled",
// search: controls web search // search: controls web search
webSearchEnabled: tools.search !== "disabled", webSearchEnabled: ctx.tools.search !== "disabled",
...modelReasoningEffort && { modelReasoningEffort } ...modelReasoningEffort && { modelReasoningEffort }
}; };
log.info( log.info(
@@ -105214,7 +105219,7 @@ var codex = agent({
); );
const thread = codex2.startThread(threadOptions); const thread = codex2.startThread(threadOptions);
try { try {
const streamedTurn = await thread.runStreamed(addInstructions({ payload, repo, tools })); const streamedTurn = await thread.runStreamed(addInstructions(ctx));
let finalOutput2 = ""; let finalOutput2 = "";
for await (const event of streamedTurn.events) { for await (const event of streamedTurn.events) {
const handler2 = messageHandlers2[event.type]; const handler2 = messageHandlers2[event.type];
@@ -105341,9 +105346,9 @@ var cursor = agent({
executableName: "cursor-agent" executableName: "cursor-agent"
}); });
}, },
run: async ({ payload, apiKey, cliPath, mcpServers, repo, effort, tools }) => { run: async (ctx) => {
configureCursorMcpServers({ mcpServers, cliPath }); configureCursorMcpServers(ctx);
configureCursorTools({ tools }); configureCursorTools(ctx);
const projectCliConfigPath = join7(process.cwd(), ".cursor", "cli.json"); const projectCliConfigPath = join7(process.cwd(), ".cursor", "cli.json");
let modelOverride = null; let modelOverride = null;
if (existsSync4(projectCliConfigPath)) { if (existsSync4(projectCliConfigPath)) {
@@ -105352,18 +105357,18 @@ var cursor = agent({
if (projectConfig.model) { if (projectConfig.model) {
log.info(`Using model from project .cursor/cli.json: ${projectConfig.model}`); log.info(`Using model from project .cursor/cli.json: ${projectConfig.model}`);
} else { } else {
modelOverride = cursorEffortModels[effort]; modelOverride = cursorEffortModels[ctx.effort];
} }
} catch { } catch {
modelOverride = cursorEffortModels[effort]; modelOverride = cursorEffortModels[ctx.effort];
} }
} else { } else {
modelOverride = cursorEffortModels[effort]; modelOverride = cursorEffortModels[ctx.effort];
} }
if (modelOverride) { if (modelOverride) {
log.info(`Using model: ${modelOverride} (effort: ${effort})`); log.info(`Using model: ${modelOverride} (effort: ${ctx.effort})`);
} else if (!existsSync4(projectCliConfigPath)) { } else if (!existsSync4(projectCliConfigPath)) {
log.info(`Using default model (effort: ${effort})`); log.info(`Using default model (effort: ${ctx.effort})`);
} }
const loggedModelCallIds = /* @__PURE__ */ new Set(); const loggedModelCallIds = /* @__PURE__ */ new Set();
const messageHandlers5 = { const messageHandlers5 = {
@@ -105415,7 +105420,7 @@ var cursor = agent({
} }
}; };
try { try {
const fullPrompt = addInstructions({ payload, repo, tools }); const fullPrompt = addInstructions(ctx);
log.group("Full prompt", () => log.info(fullPrompt)); log.group("Full prompt", () => log.info(fullPrompt));
const baseArgs = ["--print", fullPrompt, "--output-format", "stream-json", "--approve-mcps"]; const baseArgs = ["--print", fullPrompt, "--output-format", "stream-json", "--approve-mcps"];
if (modelOverride) { if (modelOverride) {
@@ -105425,10 +105430,10 @@ var cursor = agent({
log.info("Running Cursor CLI..."); log.info("Running Cursor CLI...");
const startTime = Date.now(); const startTime = Date.now();
return new Promise((resolve2) => { return new Promise((resolve2) => {
const child = spawn3(cliPath, cursorArgs, { const child = spawn3(ctx.cliPath, cursorArgs, {
cwd: process.cwd(), cwd: process.cwd(),
env: createAgentEnv({ env: createAgentEnv({
CURSOR_API_KEY: apiKey CURSOR_API_KEY: ctx.apiKey
}), }),
stdio: ["ignore", "pipe", "pipe"] stdio: ["ignore", "pipe", "pipe"]
// Ignore stdin, pipe stdout/stderr // Ignore stdin, pipe stdout/stderr
@@ -105503,13 +105508,13 @@ var cursor = agent({
} }
} }
}); });
function configureCursorMcpServers({ mcpServers }) { function configureCursorMcpServers(ctx) {
const realHome = homedir2(); const realHome = homedir2();
const cursorConfigDir = join7(realHome, ".cursor"); const cursorConfigDir = join7(realHome, ".cursor");
const mcpConfigPath = join7(cursorConfigDir, "mcp.json"); const mcpConfigPath = join7(cursorConfigDir, "mcp.json");
mkdirSync4(cursorConfigDir, { recursive: true }); mkdirSync4(cursorConfigDir, { recursive: true });
const cursorMcpServers = {}; const cursorMcpServers = {};
for (const [serverName, serverConfig] of Object.entries(mcpServers)) { for (const [serverName, serverConfig] of Object.entries(ctx.mcpServers)) {
if (serverConfig.type !== "http") { if (serverConfig.type !== "http") {
throw new Error( throw new Error(
`Unsupported MCP server type for Cursor: ${serverConfig.type || "unknown"}` `Unsupported MCP server type for Cursor: ${serverConfig.type || "unknown"}`
@@ -105523,33 +105528,29 @@ function configureCursorMcpServers({ mcpServers }) {
writeFileSync2(mcpConfigPath, JSON.stringify({ mcpServers: cursorMcpServers }, null, 2), "utf-8"); writeFileSync2(mcpConfigPath, JSON.stringify({ mcpServers: cursorMcpServers }, null, 2), "utf-8");
log.info(`\xBB MCP config written to ${mcpConfigPath}`); log.info(`\xBB MCP config written to ${mcpConfigPath}`);
} }
function configureCursorTools({ tools }) { function configureCursorTools(ctx) {
const realHome = homedir2(); const realHome = homedir2();
const cursorConfigDir = join7(realHome, ".config", "cursor"); const cursorConfigDir = join7(realHome, ".config", "cursor");
const cliConfigPath = join7(cursorConfigDir, "cli-config.json"); const cliConfigPath = join7(cursorConfigDir, "cli-config.json");
mkdirSync4(cursorConfigDir, { recursive: true }); mkdirSync4(cursorConfigDir, { recursive: true });
const deny = []; const deny = [];
if (tools.search === "disabled") deny.push("WebSearch"); if (ctx.tools.search === "disabled") deny.push("WebSearch");
if (tools.write === "disabled") deny.push("Write(**)"); if (ctx.tools.write === "disabled") deny.push("Write(**)");
if (tools.bash !== "enabled") deny.push("Shell(*)"); if (ctx.tools.bash !== "enabled") deny.push("Shell(*)");
const needsSandbox = tools.web === "disabled";
const config4 = { const config4 = {
permissions: { permissions: {
allow: tools.write === "disabled" ? ["Read(**)"] : ["Read(**)", "Write(**)"], allow: ctx.tools.write === "disabled" ? ["Read(**)"] : ["Read(**)", "Write(**)"],
deny deny
} }
}; };
if (needsSandbox) { if (ctx.tools.web === "disabled") {
config4.sandbox = { config4.sandbox = {
mode: "enabled", mode: "enabled",
networkAccess: "allowlist" networkAccess: "allowlist"
}; };
} }
writeFileSync2(cliConfigPath, JSON.stringify(config4, null, 2), "utf-8"); writeFileSync2(cliConfigPath, JSON.stringify(config4, null, 2), "utf-8");
log.info(`\xBB CLI config written to ${cliConfigPath}`); log.info(`\xBB CLI config written to ${cliConfigPath}`, JSON.stringify(config4, null, 2));
log.info(
`\u{1F527} Cursor permissions: allow=${config4.permissions.allow.join(",")}, deny=${deny.join(",") || "(none)"}, sandbox=${needsSandbox ? "enabled" : "disabled"}`
);
} }
// agents/gemini.ts // agents/gemini.ts
@@ -105725,14 +105726,12 @@ var gemini = agent({
...githubInstallationToken2 && { githubInstallationToken: githubInstallationToken2 } ...githubInstallationToken2 && { githubInstallationToken: githubInstallationToken2 }
}); });
}, },
run: async ({ payload, apiKey, mcpServers, cliPath, repo, effort, tools }) => { run: async (ctx) => {
const { model, thinkingLevel } = geminiEffortConfig[effort]; const model = configureGeminiSettings(ctx);
log.info(`Using model: ${model}, thinkingLevel: ${thinkingLevel}`); if (!ctx.apiKey) {
configureGeminiSettings({ mcpServers, tools, thinkingLevel });
if (!apiKey) {
throw new Error("google_api_key or gemini_api_key is required for gemini agent"); throw new Error("google_api_key or gemini_api_key is required for gemini agent");
} }
const sessionPrompt = addInstructions({ payload, repo, tools }); const sessionPrompt = addInstructions(ctx);
log.group("Full prompt", () => log.info(sessionPrompt)); log.group("Full prompt", () => log.info(sessionPrompt));
const args3 = ["--model", model, "--yolo", "--output-format=stream-json", "-p", sessionPrompt]; const args3 = ["--model", model, "--yolo", "--output-format=stream-json", "-p", sessionPrompt];
let finalOutput2 = ""; let finalOutput2 = "";
@@ -105740,8 +105739,8 @@ var gemini = agent({
try { try {
const result = await spawn4({ const result = await spawn4({
cmd: "node", cmd: "node",
args: [cliPath, ...args3], args: [ctx.cliPath, ...args3],
env: createAgentEnv({ GEMINI_API_KEY: apiKey }), env: createAgentEnv({ GEMINI_API_KEY: ctx.apiKey }),
onStdout: async (chunk) => { onStdout: async (chunk) => {
const text = chunk.toString(); const text = chunk.toString();
finalOutput2 += text; finalOutput2 += text;
@@ -105798,11 +105797,9 @@ var gemini = agent({
} }
} }
}); });
function configureGeminiSettings({ function configureGeminiSettings(ctx) {
mcpServers, const { model, thinkingLevel } = geminiEffortConfig[ctx.effort];
tools, log.info(`Using model: ${model}, thinkingLevel: ${thinkingLevel}`);
thinkingLevel
}) {
const realHome = homedir3(); const realHome = homedir3();
const geminiConfigDir = join8(realHome, ".gemini"); const geminiConfigDir = join8(realHome, ".gemini");
const settingsPath = join8(geminiConfigDir, "settings.json"); const settingsPath = join8(geminiConfigDir, "settings.json");
@@ -105814,7 +105811,7 @@ function configureGeminiSettings({
} catch { } catch {
} }
const geminiMcpServers = {}; const geminiMcpServers = {};
for (const [serverName, serverConfig] of Object.entries(mcpServers)) { for (const [serverName, serverConfig] of Object.entries(ctx.mcpServers)) {
if (serverConfig.type !== "http") { if (serverConfig.type !== "http") {
throw new Error( throw new Error(
`Unsupported MCP server type for Gemini: ${serverConfig.type || "unknown"}` `Unsupported MCP server type for Gemini: ${serverConfig.type || "unknown"}`
@@ -105828,10 +105825,10 @@ function configureGeminiSettings({
log.info(`adding MCP server '${serverName}' at ${serverConfig.url}...`); log.info(`adding MCP server '${serverName}' at ${serverConfig.url}...`);
} }
const exclude = []; const exclude = [];
if (tools.bash !== "enabled") exclude.push("run_shell_command"); if (ctx.tools.bash !== "enabled") exclude.push("run_shell_command");
if (tools.write === "disabled") exclude.push("write_file"); if (ctx.tools.write === "disabled") exclude.push("write_file");
if (tools.web === "disabled") exclude.push("web_fetch"); if (ctx.tools.web === "disabled") exclude.push("web_fetch");
if (tools.search === "disabled") exclude.push("google_web_search"); if (ctx.tools.search === "disabled") exclude.push("google_web_search");
const newSettings = { const newSettings = {
...existingSettings, ...existingSettings,
mcpServers: geminiMcpServers, mcpServers: geminiMcpServers,
@@ -105852,6 +105849,7 @@ function configureGeminiSettings({
if (exclude.length > 0) { if (exclude.length > 0) {
log.info(`\u{1F512} excluded tools: ${exclude.join(", ")}`); log.info(`\u{1F512} excluded tools: ${exclude.join(", ")}`);
} }
return model;
} }
// agents/opencode.ts // agents/opencode.ts
@@ -105867,21 +105865,12 @@ var opencode = agent({
installDependencies: true installDependencies: true
}); });
}, },
run: async ({ run: async (ctx) => {
payload,
apiKey: _apiKey,
apiKeys,
mcpServers,
cliPath,
repo,
effort: _effort,
tools
}) => {
const tempHome = process.env.PULLFROG_TEMP_DIR; const tempHome = process.env.PULLFROG_TEMP_DIR;
const configDir = join9(tempHome, ".config", "opencode"); const configDir = join9(tempHome, ".config", "opencode");
mkdirSync6(configDir, { recursive: true }); mkdirSync6(configDir, { recursive: true });
configureOpenCode({ mcpServers, tools }); configureOpenCode(ctx);
const prompt = addInstructions({ payload, repo, tools }); const prompt = addInstructions(ctx);
log.group("Full prompt", () => log.info(prompt)); log.group("Full prompt", () => log.info(prompt));
const args3 = ["run", prompt, "--format", "json"]; const args3 = ["run", prompt, "--format", "json"];
setupProcessAgentEnv({ HOME: tempHome }); setupProcessAgentEnv({ HOME: tempHome });
@@ -105890,14 +105879,14 @@ var opencode = agent({
XDG_CONFIG_HOME: join9(tempHome, ".config") XDG_CONFIG_HOME: join9(tempHome, ".config")
}; };
delete env3.GITHUB_TOKEN; delete env3.GITHUB_TOKEN;
for (const [key, value2] of Object.entries(apiKeys || {})) { for (const [key, value2] of Object.entries(ctx.apiKeys || {})) {
env3[key.toUpperCase()] = value2; env3[key.toUpperCase()] = value2;
if (key === "GEMINI_API_KEY") { if (key === "GEMINI_API_KEY") {
env3.GOOGLE_GENERATIVE_AI_API_KEY = value2; env3.GOOGLE_GENERATIVE_AI_API_KEY = value2;
} }
} }
const repoDir = process.cwd(); const repoDir = process.cwd();
log.info(`\u{1F680} Starting OpenCode CLI: ${cliPath} ${args3.join(" ")}`); log.info(`\u{1F680} Starting OpenCode CLI: ${ctx.cliPath} ${args3.join(" ")}`);
log.info(`\u{1F4C1} Working directory: ${repoDir}`); log.info(`\u{1F4C1} Working directory: ${repoDir}`);
log.debug(`\u{1F3E0} HOME: ${env3.HOME}`); log.debug(`\u{1F3E0} HOME: ${env3.HOME}`);
log.debug(`\u{1F4CB} XDG_CONFIG_HOME: ${env3.XDG_CONFIG_HOME}`); log.debug(`\u{1F4CB} XDG_CONFIG_HOME: ${env3.XDG_CONFIG_HOME}`);
@@ -105907,7 +105896,7 @@ var opencode = agent({
let output = ""; let output = "";
let stdoutBuffer = ""; let stdoutBuffer = "";
const result = await spawn4({ const result = await spawn4({
cmd: cliPath, cmd: ctx.cliPath,
args: args3, args: args3,
cwd: repoDir, cwd: repoDir,
env: env3, env: env3,
@@ -105993,13 +105982,13 @@ var opencode = agent({
}; };
} }
}); });
function configureOpenCode({ mcpServers, tools }) { function configureOpenCode(ctx) {
const tempHome = process.env.PULLFROG_TEMP_DIR; const tempHome = process.env.PULLFROG_TEMP_DIR;
const configDir = join9(tempHome, ".config", "opencode"); const configDir = join9(tempHome, ".config", "opencode");
mkdirSync6(configDir, { recursive: true }); mkdirSync6(configDir, { recursive: true });
const configPath = join9(configDir, "opencode.json"); const configPath = join9(configDir, "opencode.json");
const opencodeMcpServers = {}; const opencodeMcpServers = {};
for (const [serverName, serverConfig] of Object.entries(mcpServers)) { for (const [serverName, serverConfig] of Object.entries(ctx.mcpServers)) {
if (serverConfig.type !== "http") { if (serverConfig.type !== "http") {
log.error( log.error(
`unsupported MCP server type for OpenCode: ${serverConfig.type || "unknown"}` `unsupported MCP server type for OpenCode: ${serverConfig.type || "unknown"}`
@@ -106014,9 +106003,9 @@ function configureOpenCode({ mcpServers, tools }) {
}; };
} }
const permission = { const permission = {
edit: tools.write === "disabled" ? "deny" : "allow", edit: ctx.tools.write === "disabled" ? "deny" : "allow",
bash: tools.bash !== "enabled" ? "deny" : "allow", bash: ctx.tools.bash !== "enabled" ? "deny" : "allow",
webfetch: tools.web === "disabled" ? "deny" : "allow", webfetch: ctx.tools.web === "disabled" ? "deny" : "allow",
doom_loop: "allow", doom_loop: "allow",
external_directory: "allow" external_directory: "allow"
}; };
+101 -112
View File
@@ -100032,11 +100032,19 @@ var package_default = {
packageManager: "pnpm@10.27.0+sha512.72d699da16b1179c14ba9e64dc71c9a40988cbdc65c264cb0e489db7de917f20dcf4d64d8723625f2969ba52d4b7e2a1170682d9ac2a5dcaeaab732b7e16f04a" packageManager: "pnpm@10.27.0+sha512.72d699da16b1179c14ba9e64dc71c9a40988cbdc65c264cb0e489db7de917f20dcf4d64d8723625f2969ba52d4b7e2a1170682d9ac2a5dcaeaab732b7e16f04a"
}; };
// utils/cli.ts // utils/log.ts
var core = __toESM(require_core(), 1); var core = __toESM(require_core(), 1);
var import_table = __toESM(require_src(), 1); var import_table = __toESM(require_src(), 1);
var isGitHubActions = !!process.env.GITHUB_ACTIONS; var isGitHubActions = !!process.env.GITHUB_ACTIONS;
var isDebugEnabled = () => process.env.LOG_LEVEL === "debug" || process.env.ACTIONS_STEP_DEBUG === "true" || process.env.RUNNER_DEBUG === "1" || core.isDebug(); var isDebugEnabled = () => process.env.LOG_LEVEL === "debug" || process.env.ACTIONS_STEP_DEBUG === "true" || process.env.RUNNER_DEBUG === "1" || core.isDebug();
function formatArgs(args3) {
return args3.map((arg) => {
if (typeof arg === "string") return arg;
if (arg instanceof Error) return `${arg.message}
${arg.stack}`;
return JSON.stringify(arg);
}).join(" ");
}
function startGroup2(name) { function startGroup2(name) {
if (isGitHubActions) { if (isGitHubActions) {
core.startGroup(name); core.startGroup(name);
@@ -100145,25 +100153,25 @@ function separator(length = 50) {
} }
var log = { var log = {
/** Print info message */ /** Print info message */
info: (message) => { info: (...args3) => {
core.info(message); core.info(formatArgs(args3));
}, },
/** Print warning message */ /** Print warning message */
warning: (message) => { warning: (...args3) => {
core.warning(message); core.warning(formatArgs(args3));
}, },
/** Print error message */ /** Print error message */
error: (message) => { error: (...args3) => {
core.error(message); core.error(formatArgs(args3));
}, },
/** Print success message */ /** Print success message */
success: (message) => { success: (...args3) => {
core.info(`\u2705 ${message}`); core.info(`\u2705 ${formatArgs(args3)}`);
}, },
/** Print debug message (only if LOG_LEVEL=debug) */ /** Print debug message (only if LOG_LEVEL=debug) */
debug: (message) => { debug: (...args3) => {
if (isDebugEnabled()) { if (isDebugEnabled()) {
core.info(`[DEBUG] ${message}`); core.info(`[DEBUG] ${formatArgs(args3)}`);
} }
}, },
/** Print a formatted box with text */ /** Print a formatted box with text */
@@ -100420,14 +100428,14 @@ function getShellInstructions(bash) {
} }
} }
} }
var addInstructions = ({ payload, repo, tools }) => { var addInstructions = (ctx) => {
let encodedEvent = ""; let encodedEvent = "";
const eventKeys = Object.keys(payload.event); const eventKeys = Object.keys(ctx.payload.event);
if (eventKeys.length === 1 && eventKeys[0] === "trigger") { if (eventKeys.length === 1 && eventKeys[0] === "trigger") {
} else { } else {
encodedEvent = encode(payload.event); encodedEvent = encode(ctx.payload.event);
} }
const runtimeContext = buildRuntimeContext(repo); const runtimeContext = buildRuntimeContext(ctx.repo);
return ` return `
*********************************************** ***********************************************
************* SYSTEM INSTRUCTIONS ************* ************* SYSTEM INSTRUCTIONS *************
@@ -100475,7 +100483,7 @@ Tool names may be formatted as \`(server name)/(tool name)\`, for example: \`${g
**Efficiency**: Trust the tools - do not repeatedly verify file contents or git status after operations. If a tool reports success, proceed to the next step. Only verify if you encounter an actual error. **Efficiency**: Trust the tools - do not repeatedly verify file contents or git status after operations. If a tool reports success, proceed to the next step. Only verify if you encounter an actual error.
${getShellInstructions(tools.bash)} ${getShellInstructions(ctx.tools.bash)}
**Command execution**: Never use \`sleep\` to wait for commands to complete. Commands run synchronously - when the bash tool returns, the command has finished. **Command execution**: Never use \`sleep\` to wait for commands to complete. Commands run synchronously - when the bash tool returns, the command has finished.
@@ -100496,7 +100504,7 @@ ${getShellInstructions(tools.bash)}
### Available modes ### Available modes
${[...getModes({ disableProgressComment: payload.disableProgressComment }), ...payload.modes].map((w) => ` - "${w.name}": ${w.description}`).join("\n")} ${[...getModes({ disableProgressComment: ctx.payload.disableProgressComment }), ...ctx.payload.modes].map((w) => ` - "${w.name}": ${w.description}`).join("\n")}
### Following the mode instructions ### Following the mode instructions
@@ -100506,7 +100514,7 @@ Eagerly inspect the MCP tools available to you via the \`${ghPullfrogMcpName}\`
************* USER PROMPT ************* ************* USER PROMPT *************
${payload.prompt.split("\n").map((line) => `> ${line}`).join("\n")} ${ctx.payload.prompt.split("\n").map((line) => `> ${line}`).join("\n")}
${encodedEvent ? `************* EVENT DATA ************* ${encodedEvent ? `************* EVENT DATA *************
@@ -104635,12 +104643,12 @@ var claudeEffortModels = {
auto: "opusplan", auto: "opusplan",
max: "opus" max: "opus"
}; };
function buildDisallowedTools(tools) { function buildDisallowedTools(ctx) {
const disallowed = []; const disallowed = [];
if (tools.web === "disabled") disallowed.push("WebFetch"); if (ctx.tools.web === "disabled") disallowed.push("WebFetch");
if (tools.search === "disabled") disallowed.push("WebSearch"); if (ctx.tools.search === "disabled") disallowed.push("WebSearch");
if (tools.write === "disabled") disallowed.push("Write"); if (ctx.tools.write === "disabled") disallowed.push("Write");
if (tools.bash !== "enabled") disallowed.push("Bash"); if (ctx.tools.bash !== "enabled") disallowed.push("Bash");
return disallowed; return disallowed;
} }
var claude = agent({ var claude = agent({
@@ -104653,23 +104661,23 @@ var claude = agent({
executablePath: "cli.js" executablePath: "cli.js"
}); });
}, },
run: async ({ payload, mcpServers, apiKey, cliPath, repo, effort, tools }) => { run: async (ctx) => {
delete process.env.ANTHROPIC_API_KEY; delete process.env.ANTHROPIC_API_KEY;
const prompt = addInstructions({ payload, repo, tools }); const prompt = addInstructions(ctx);
log.group("Full prompt", () => log.info(prompt)); log.group("Full prompt", () => log.info(prompt));
const model = claudeEffortModels[effort]; const model = claudeEffortModels[ctx.effort];
log.info(`Using model: ${model} (effort: ${effort})`); log.info(`Using model: ${model} (effort: ${ctx.effort})`);
const disallowedTools = buildDisallowedTools(tools); const disallowedTools = buildDisallowedTools(ctx);
if (disallowedTools.length > 0) { if (disallowedTools.length > 0) {
log.info(`\u{1F512} disallowed tools: ${disallowedTools.join(", ")}`); log.info(`\u{1F512} disallowed tools: ${disallowedTools.join(", ")}`);
} }
const queryOptions = { const queryOptions = {
permissionMode: "bypassPermissions", permissionMode: "bypassPermissions",
disallowedTools, disallowedTools,
mcpServers, mcpServers: ctx.mcpServers,
model, model,
pathToClaudeCodeExecutable: cliPath, pathToClaudeCodeExecutable: ctx.cliPath,
env: createAgentEnv({ ANTHROPIC_API_KEY: apiKey }) env: createAgentEnv({ ANTHROPIC_API_KEY: ctx.apiKey })
}; };
const queryInstance = query({ const queryInstance = query({
prompt, prompt,
@@ -105132,19 +105140,20 @@ var codexReasoningEffort = {
// use default // use default
max: "high" max: "high"
}; };
function writeCodexConfig({ tempHome, mcpServers, tools }) { function writeCodexConfig(ctx) {
const tempHome = process.env.PULLFROG_TEMP_DIR;
const codexDir = join6(tempHome, ".codex"); const codexDir = join6(tempHome, ".codex");
mkdirSync3(codexDir, { recursive: true }); mkdirSync3(codexDir, { recursive: true });
const configPath = join6(codexDir, "config.toml"); const configPath = join6(codexDir, "config.toml");
const mcpServerSections = []; const mcpServerSections = [];
for (const [name, config4] of Object.entries(mcpServers)) { for (const [name, config4] of Object.entries(ctx.mcpServers)) {
if (config4.type !== "http") continue; if (config4.type !== "http") continue;
log.info(`\xBB adding MCP server '${name}' at ${config4.url}`); log.info(`\xBB adding MCP server '${name}' at ${config4.url}`);
mcpServerSections.push(`[mcp_servers.${name}] mcpServerSections.push(`[mcp_servers.${name}]
url = "${config4.url}"`); url = "${config4.url}"`);
} }
const features = []; const features = [];
if (tools.bash !== "enabled") { if (ctx.tools.bash !== "enabled") {
features.push("shell_command_tool = false"); features.push("shell_command_tool = false");
features.push("unified_exec = false"); features.push("unified_exec = false");
} }
@@ -105159,7 +105168,7 @@ ${mcpServerSections.join("\n\n")}
`.trim() + "\n" `.trim() + "\n"
); );
log.info( log.info(
`\xBB Codex config written to ${configPath} (shell: ${tools.bash === "enabled" ? "enabled" : "disabled"})` `\xBB Codex config written to ${configPath} (shell: ${ctx.tools.bash === "enabled" ? "enabled" : "disabled"})`
); );
return codexDir; return codexDir;
} }
@@ -105172,41 +105181,37 @@ var codex = agent({
executablePath: "bin/codex.js" executablePath: "bin/codex.js"
}); });
}, },
run: async ({ payload, mcpServers, apiKey, cliPath, repo, effort, tools }) => { run: async (ctx) => {
const tempHome = process.env.PULLFROG_TEMP_DIR; const tempHome = process.env.PULLFROG_TEMP_DIR;
const configDir = join6(tempHome, ".config", "codex"); const configDir = join6(tempHome, ".config", "codex");
mkdirSync3(configDir, { recursive: true }); mkdirSync3(configDir, { recursive: true });
const codexDir = writeCodexConfig({ const codexDir = writeCodexConfig(ctx);
tempHome,
mcpServers,
tools
});
setupProcessAgentEnv({ setupProcessAgentEnv({
OPENAI_API_KEY: apiKey, OPENAI_API_KEY: ctx.apiKey,
HOME: tempHome, HOME: tempHome,
CODEX_HOME: codexDir CODEX_HOME: codexDir
// point Codex to our config directory // point Codex to our config directory
}); });
const model = codexModel[effort]; const model = codexModel[ctx.effort];
const modelReasoningEffort = codexReasoningEffort[effort]; const modelReasoningEffort = codexReasoningEffort[ctx.effort];
log.info(`Using model: ${model}`); log.info(`Using model: ${model}`);
if (modelReasoningEffort) { if (modelReasoningEffort) {
log.info(`Using modelReasoningEffort: ${modelReasoningEffort}`); log.info(`Using modelReasoningEffort: ${modelReasoningEffort}`);
} }
const codexOptions = { const codexOptions = {
apiKey, apiKey: ctx.apiKey,
codexPathOverride: cliPath codexPathOverride: ctx.cliPath
}; };
const codex2 = new Codex(codexOptions); const codex2 = new Codex(codexOptions);
const threadOptions = { const threadOptions = {
model, model,
approvalPolicy: "never", approvalPolicy: "never",
// write: "disabled" → read-only sandbox, otherwise full access for git ops // write: "disabled" → read-only sandbox, otherwise full access for git ops
sandboxMode: tools.write === "disabled" ? "read-only" : "danger-full-access", sandboxMode: ctx.tools.write === "disabled" ? "read-only" : "danger-full-access",
// web: controls network access // web: controls network access
networkAccessEnabled: tools.web !== "disabled", networkAccessEnabled: ctx.tools.web !== "disabled",
// search: controls web search // search: controls web search
webSearchEnabled: tools.search !== "disabled", webSearchEnabled: ctx.tools.search !== "disabled",
...modelReasoningEffort && { modelReasoningEffort } ...modelReasoningEffort && { modelReasoningEffort }
}; };
log.info( log.info(
@@ -105214,7 +105219,7 @@ var codex = agent({
); );
const thread = codex2.startThread(threadOptions); const thread = codex2.startThread(threadOptions);
try { try {
const streamedTurn = await thread.runStreamed(addInstructions({ payload, repo, tools })); const streamedTurn = await thread.runStreamed(addInstructions(ctx));
let finalOutput2 = ""; let finalOutput2 = "";
for await (const event of streamedTurn.events) { for await (const event of streamedTurn.events) {
const handler2 = messageHandlers2[event.type]; const handler2 = messageHandlers2[event.type];
@@ -105341,9 +105346,9 @@ var cursor = agent({
executableName: "cursor-agent" executableName: "cursor-agent"
}); });
}, },
run: async ({ payload, apiKey, cliPath, mcpServers, repo, effort, tools }) => { run: async (ctx) => {
configureCursorMcpServers({ mcpServers, cliPath }); configureCursorMcpServers(ctx);
configureCursorTools({ tools }); configureCursorTools(ctx);
const projectCliConfigPath = join7(process.cwd(), ".cursor", "cli.json"); const projectCliConfigPath = join7(process.cwd(), ".cursor", "cli.json");
let modelOverride = null; let modelOverride = null;
if (existsSync4(projectCliConfigPath)) { if (existsSync4(projectCliConfigPath)) {
@@ -105352,18 +105357,18 @@ var cursor = agent({
if (projectConfig.model) { if (projectConfig.model) {
log.info(`Using model from project .cursor/cli.json: ${projectConfig.model}`); log.info(`Using model from project .cursor/cli.json: ${projectConfig.model}`);
} else { } else {
modelOverride = cursorEffortModels[effort]; modelOverride = cursorEffortModels[ctx.effort];
} }
} catch { } catch {
modelOverride = cursorEffortModels[effort]; modelOverride = cursorEffortModels[ctx.effort];
} }
} else { } else {
modelOverride = cursorEffortModels[effort]; modelOverride = cursorEffortModels[ctx.effort];
} }
if (modelOverride) { if (modelOverride) {
log.info(`Using model: ${modelOverride} (effort: ${effort})`); log.info(`Using model: ${modelOverride} (effort: ${ctx.effort})`);
} else if (!existsSync4(projectCliConfigPath)) { } else if (!existsSync4(projectCliConfigPath)) {
log.info(`Using default model (effort: ${effort})`); log.info(`Using default model (effort: ${ctx.effort})`);
} }
const loggedModelCallIds = /* @__PURE__ */ new Set(); const loggedModelCallIds = /* @__PURE__ */ new Set();
const messageHandlers5 = { const messageHandlers5 = {
@@ -105415,7 +105420,7 @@ var cursor = agent({
} }
}; };
try { try {
const fullPrompt = addInstructions({ payload, repo, tools }); const fullPrompt = addInstructions(ctx);
log.group("Full prompt", () => log.info(fullPrompt)); log.group("Full prompt", () => log.info(fullPrompt));
const baseArgs = ["--print", fullPrompt, "--output-format", "stream-json", "--approve-mcps"]; const baseArgs = ["--print", fullPrompt, "--output-format", "stream-json", "--approve-mcps"];
if (modelOverride) { if (modelOverride) {
@@ -105425,10 +105430,10 @@ var cursor = agent({
log.info("Running Cursor CLI..."); log.info("Running Cursor CLI...");
const startTime = Date.now(); const startTime = Date.now();
return new Promise((resolve2) => { return new Promise((resolve2) => {
const child = spawn3(cliPath, cursorArgs, { const child = spawn3(ctx.cliPath, cursorArgs, {
cwd: process.cwd(), cwd: process.cwd(),
env: createAgentEnv({ env: createAgentEnv({
CURSOR_API_KEY: apiKey CURSOR_API_KEY: ctx.apiKey
}), }),
stdio: ["ignore", "pipe", "pipe"] stdio: ["ignore", "pipe", "pipe"]
// Ignore stdin, pipe stdout/stderr // Ignore stdin, pipe stdout/stderr
@@ -105503,13 +105508,13 @@ var cursor = agent({
} }
} }
}); });
function configureCursorMcpServers({ mcpServers }) { function configureCursorMcpServers(ctx) {
const realHome = homedir2(); const realHome = homedir2();
const cursorConfigDir = join7(realHome, ".cursor"); const cursorConfigDir = join7(realHome, ".cursor");
const mcpConfigPath = join7(cursorConfigDir, "mcp.json"); const mcpConfigPath = join7(cursorConfigDir, "mcp.json");
mkdirSync4(cursorConfigDir, { recursive: true }); mkdirSync4(cursorConfigDir, { recursive: true });
const cursorMcpServers = {}; const cursorMcpServers = {};
for (const [serverName, serverConfig] of Object.entries(mcpServers)) { for (const [serverName, serverConfig] of Object.entries(ctx.mcpServers)) {
if (serverConfig.type !== "http") { if (serverConfig.type !== "http") {
throw new Error( throw new Error(
`Unsupported MCP server type for Cursor: ${serverConfig.type || "unknown"}` `Unsupported MCP server type for Cursor: ${serverConfig.type || "unknown"}`
@@ -105523,33 +105528,29 @@ function configureCursorMcpServers({ mcpServers }) {
writeFileSync2(mcpConfigPath, JSON.stringify({ mcpServers: cursorMcpServers }, null, 2), "utf-8"); writeFileSync2(mcpConfigPath, JSON.stringify({ mcpServers: cursorMcpServers }, null, 2), "utf-8");
log.info(`\xBB MCP config written to ${mcpConfigPath}`); log.info(`\xBB MCP config written to ${mcpConfigPath}`);
} }
function configureCursorTools({ tools }) { function configureCursorTools(ctx) {
const realHome = homedir2(); const realHome = homedir2();
const cursorConfigDir = join7(realHome, ".config", "cursor"); const cursorConfigDir = join7(realHome, ".config", "cursor");
const cliConfigPath = join7(cursorConfigDir, "cli-config.json"); const cliConfigPath = join7(cursorConfigDir, "cli-config.json");
mkdirSync4(cursorConfigDir, { recursive: true }); mkdirSync4(cursorConfigDir, { recursive: true });
const deny = []; const deny = [];
if (tools.search === "disabled") deny.push("WebSearch"); if (ctx.tools.search === "disabled") deny.push("WebSearch");
if (tools.write === "disabled") deny.push("Write(**)"); if (ctx.tools.write === "disabled") deny.push("Write(**)");
if (tools.bash !== "enabled") deny.push("Shell(*)"); if (ctx.tools.bash !== "enabled") deny.push("Shell(*)");
const needsSandbox = tools.web === "disabled";
const config4 = { const config4 = {
permissions: { permissions: {
allow: tools.write === "disabled" ? ["Read(**)"] : ["Read(**)", "Write(**)"], allow: ctx.tools.write === "disabled" ? ["Read(**)"] : ["Read(**)", "Write(**)"],
deny deny
} }
}; };
if (needsSandbox) { if (ctx.tools.web === "disabled") {
config4.sandbox = { config4.sandbox = {
mode: "enabled", mode: "enabled",
networkAccess: "allowlist" networkAccess: "allowlist"
}; };
} }
writeFileSync2(cliConfigPath, JSON.stringify(config4, null, 2), "utf-8"); writeFileSync2(cliConfigPath, JSON.stringify(config4, null, 2), "utf-8");
log.info(`\xBB CLI config written to ${cliConfigPath}`); log.info(`\xBB CLI config written to ${cliConfigPath}`, JSON.stringify(config4, null, 2));
log.info(
`\u{1F527} Cursor permissions: allow=${config4.permissions.allow.join(",")}, deny=${deny.join(",") || "(none)"}, sandbox=${needsSandbox ? "enabled" : "disabled"}`
);
} }
// agents/gemini.ts // agents/gemini.ts
@@ -105725,14 +105726,12 @@ var gemini = agent({
...githubInstallationToken2 && { githubInstallationToken: githubInstallationToken2 } ...githubInstallationToken2 && { githubInstallationToken: githubInstallationToken2 }
}); });
}, },
run: async ({ payload, apiKey, mcpServers, cliPath, repo, effort, tools }) => { run: async (ctx) => {
const { model, thinkingLevel } = geminiEffortConfig[effort]; const model = configureGeminiSettings(ctx);
log.info(`Using model: ${model}, thinkingLevel: ${thinkingLevel}`); if (!ctx.apiKey) {
configureGeminiSettings({ mcpServers, tools, thinkingLevel });
if (!apiKey) {
throw new Error("google_api_key or gemini_api_key is required for gemini agent"); throw new Error("google_api_key or gemini_api_key is required for gemini agent");
} }
const sessionPrompt = addInstructions({ payload, repo, tools }); const sessionPrompt = addInstructions(ctx);
log.group("Full prompt", () => log.info(sessionPrompt)); log.group("Full prompt", () => log.info(sessionPrompt));
const args3 = ["--model", model, "--yolo", "--output-format=stream-json", "-p", sessionPrompt]; const args3 = ["--model", model, "--yolo", "--output-format=stream-json", "-p", sessionPrompt];
let finalOutput2 = ""; let finalOutput2 = "";
@@ -105740,8 +105739,8 @@ var gemini = agent({
try { try {
const result = await spawn4({ const result = await spawn4({
cmd: "node", cmd: "node",
args: [cliPath, ...args3], args: [ctx.cliPath, ...args3],
env: createAgentEnv({ GEMINI_API_KEY: apiKey }), env: createAgentEnv({ GEMINI_API_KEY: ctx.apiKey }),
onStdout: async (chunk) => { onStdout: async (chunk) => {
const text = chunk.toString(); const text = chunk.toString();
finalOutput2 += text; finalOutput2 += text;
@@ -105798,11 +105797,9 @@ var gemini = agent({
} }
} }
}); });
function configureGeminiSettings({ function configureGeminiSettings(ctx) {
mcpServers, const { model, thinkingLevel } = geminiEffortConfig[ctx.effort];
tools, log.info(`Using model: ${model}, thinkingLevel: ${thinkingLevel}`);
thinkingLevel
}) {
const realHome = homedir3(); const realHome = homedir3();
const geminiConfigDir = join8(realHome, ".gemini"); const geminiConfigDir = join8(realHome, ".gemini");
const settingsPath = join8(geminiConfigDir, "settings.json"); const settingsPath = join8(geminiConfigDir, "settings.json");
@@ -105814,7 +105811,7 @@ function configureGeminiSettings({
} catch { } catch {
} }
const geminiMcpServers = {}; const geminiMcpServers = {};
for (const [serverName, serverConfig] of Object.entries(mcpServers)) { for (const [serverName, serverConfig] of Object.entries(ctx.mcpServers)) {
if (serverConfig.type !== "http") { if (serverConfig.type !== "http") {
throw new Error( throw new Error(
`Unsupported MCP server type for Gemini: ${serverConfig.type || "unknown"}` `Unsupported MCP server type for Gemini: ${serverConfig.type || "unknown"}`
@@ -105828,10 +105825,10 @@ function configureGeminiSettings({
log.info(`adding MCP server '${serverName}' at ${serverConfig.url}...`); log.info(`adding MCP server '${serverName}' at ${serverConfig.url}...`);
} }
const exclude = []; const exclude = [];
if (tools.bash !== "enabled") exclude.push("run_shell_command"); if (ctx.tools.bash !== "enabled") exclude.push("run_shell_command");
if (tools.write === "disabled") exclude.push("write_file"); if (ctx.tools.write === "disabled") exclude.push("write_file");
if (tools.web === "disabled") exclude.push("web_fetch"); if (ctx.tools.web === "disabled") exclude.push("web_fetch");
if (tools.search === "disabled") exclude.push("google_web_search"); if (ctx.tools.search === "disabled") exclude.push("google_web_search");
const newSettings = { const newSettings = {
...existingSettings, ...existingSettings,
mcpServers: geminiMcpServers, mcpServers: geminiMcpServers,
@@ -105852,6 +105849,7 @@ function configureGeminiSettings({
if (exclude.length > 0) { if (exclude.length > 0) {
log.info(`\u{1F512} excluded tools: ${exclude.join(", ")}`); log.info(`\u{1F512} excluded tools: ${exclude.join(", ")}`);
} }
return model;
} }
// agents/opencode.ts // agents/opencode.ts
@@ -105867,21 +105865,12 @@ var opencode = agent({
installDependencies: true installDependencies: true
}); });
}, },
run: async ({ run: async (ctx) => {
payload,
apiKey: _apiKey,
apiKeys,
mcpServers,
cliPath,
repo,
effort: _effort,
tools
}) => {
const tempHome = process.env.PULLFROG_TEMP_DIR; const tempHome = process.env.PULLFROG_TEMP_DIR;
const configDir = join9(tempHome, ".config", "opencode"); const configDir = join9(tempHome, ".config", "opencode");
mkdirSync6(configDir, { recursive: true }); mkdirSync6(configDir, { recursive: true });
configureOpenCode({ mcpServers, tools }); configureOpenCode(ctx);
const prompt = addInstructions({ payload, repo, tools }); const prompt = addInstructions(ctx);
log.group("Full prompt", () => log.info(prompt)); log.group("Full prompt", () => log.info(prompt));
const args3 = ["run", prompt, "--format", "json"]; const args3 = ["run", prompt, "--format", "json"];
setupProcessAgentEnv({ HOME: tempHome }); setupProcessAgentEnv({ HOME: tempHome });
@@ -105890,14 +105879,14 @@ var opencode = agent({
XDG_CONFIG_HOME: join9(tempHome, ".config") XDG_CONFIG_HOME: join9(tempHome, ".config")
}; };
delete env3.GITHUB_TOKEN; delete env3.GITHUB_TOKEN;
for (const [key, value2] of Object.entries(apiKeys || {})) { for (const [key, value2] of Object.entries(ctx.apiKeys || {})) {
env3[key.toUpperCase()] = value2; env3[key.toUpperCase()] = value2;
if (key === "GEMINI_API_KEY") { if (key === "GEMINI_API_KEY") {
env3.GOOGLE_GENERATIVE_AI_API_KEY = value2; env3.GOOGLE_GENERATIVE_AI_API_KEY = value2;
} }
} }
const repoDir = process.cwd(); const repoDir = process.cwd();
log.info(`\u{1F680} Starting OpenCode CLI: ${cliPath} ${args3.join(" ")}`); log.info(`\u{1F680} Starting OpenCode CLI: ${ctx.cliPath} ${args3.join(" ")}`);
log.info(`\u{1F4C1} Working directory: ${repoDir}`); log.info(`\u{1F4C1} Working directory: ${repoDir}`);
log.debug(`\u{1F3E0} HOME: ${env3.HOME}`); log.debug(`\u{1F3E0} HOME: ${env3.HOME}`);
log.debug(`\u{1F4CB} XDG_CONFIG_HOME: ${env3.XDG_CONFIG_HOME}`); log.debug(`\u{1F4CB} XDG_CONFIG_HOME: ${env3.XDG_CONFIG_HOME}`);
@@ -105907,7 +105896,7 @@ var opencode = agent({
let output = ""; let output = "";
let stdoutBuffer = ""; let stdoutBuffer = "";
const result = await spawn4({ const result = await spawn4({
cmd: cliPath, cmd: ctx.cliPath,
args: args3, args: args3,
cwd: repoDir, cwd: repoDir,
env: env3, env: env3,
@@ -105993,13 +105982,13 @@ var opencode = agent({
}; };
} }
}); });
function configureOpenCode({ mcpServers, tools }) { function configureOpenCode(ctx) {
const tempHome = process.env.PULLFROG_TEMP_DIR; const tempHome = process.env.PULLFROG_TEMP_DIR;
const configDir = join9(tempHome, ".config", "opencode"); const configDir = join9(tempHome, ".config", "opencode");
mkdirSync6(configDir, { recursive: true }); mkdirSync6(configDir, { recursive: true });
const configPath = join9(configDir, "opencode.json"); const configPath = join9(configDir, "opencode.json");
const opencodeMcpServers = {}; const opencodeMcpServers = {};
for (const [serverName, serverConfig] of Object.entries(mcpServers)) { for (const [serverName, serverConfig] of Object.entries(ctx.mcpServers)) {
if (serverConfig.type !== "http") { if (serverConfig.type !== "http") {
log.error( log.error(
`unsupported MCP server type for OpenCode: ${serverConfig.type || "unknown"}` `unsupported MCP server type for OpenCode: ${serverConfig.type || "unknown"}`
@@ -106014,9 +106003,9 @@ function configureOpenCode({ mcpServers, tools }) {
}; };
} }
const permission = { const permission = {
edit: tools.write === "disabled" ? "deny" : "allow", edit: ctx.tools.write === "disabled" ? "deny" : "allow",
bash: tools.bash !== "enabled" ? "deny" : "allow", bash: ctx.tools.bash !== "enabled" ? "deny" : "allow",
webfetch: tools.web === "disabled" ? "deny" : "allow", webfetch: ctx.tools.web === "disabled" ? "deny" : "allow",
doom_loop: "allow", doom_loop: "allow",
external_directory: "allow" external_directory: "allow"
}; };
+19 -11
View File
@@ -25505,11 +25505,19 @@ var core3 = __toESM(require_core(), 1);
var core2 = __toESM(require_core(), 1); var core2 = __toESM(require_core(), 1);
import { createSign } from "node:crypto"; import { createSign } from "node:crypto";
// utils/cli.ts // utils/log.ts
var core = __toESM(require_core(), 1); var core = __toESM(require_core(), 1);
var import_table = __toESM(require_src(), 1); var import_table = __toESM(require_src(), 1);
var isGitHubActions = !!process.env.GITHUB_ACTIONS; var isGitHubActions = !!process.env.GITHUB_ACTIONS;
var isDebugEnabled = () => process.env.LOG_LEVEL === "debug" || process.env.ACTIONS_STEP_DEBUG === "true" || process.env.RUNNER_DEBUG === "1" || core.isDebug(); var isDebugEnabled = () => process.env.LOG_LEVEL === "debug" || process.env.ACTIONS_STEP_DEBUG === "true" || process.env.RUNNER_DEBUG === "1" || core.isDebug();
function formatArgs(args) {
return args.map((arg) => {
if (typeof arg === "string") return arg;
if (arg instanceof Error) return `${arg.message}
${arg.stack}`;
return JSON.stringify(arg);
}).join(" ");
}
function startGroup2(name) { function startGroup2(name) {
if (isGitHubActions) { if (isGitHubActions) {
core.startGroup(name); core.startGroup(name);
@@ -25614,25 +25622,25 @@ function separator(length = 50) {
} }
var log = { var log = {
/** Print info message */ /** Print info message */
info: (message) => { info: (...args) => {
core.info(message); core.info(formatArgs(args));
}, },
/** Print warning message */ /** Print warning message */
warning: (message) => { warning: (...args) => {
core.warning(message); core.warning(formatArgs(args));
}, },
/** Print error message */ /** Print error message */
error: (message) => { error: (...args) => {
core.error(message); core.error(formatArgs(args));
}, },
/** Print success message */ /** Print success message */
success: (message) => { success: (...args) => {
core.info(`\u2705 ${message}`); core.info(`\u2705 ${formatArgs(args)}`);
}, },
/** Print debug message (only if LOG_LEVEL=debug) */ /** Print debug message (only if LOG_LEVEL=debug) */
debug: (message) => { debug: (...args) => {
if (isDebugEnabled()) { if (isDebugEnabled()) {
core.info(`[DEBUG] ${message}`); core.info(`[DEBUG] ${formatArgs(args)}`);
} }
}, },
/** Print a formatted box with text */ /** Print a formatted box with text */
+101 -112
View File
@@ -100032,11 +100032,19 @@ var package_default = {
packageManager: "pnpm@10.27.0+sha512.72d699da16b1179c14ba9e64dc71c9a40988cbdc65c264cb0e489db7de917f20dcf4d64d8723625f2969ba52d4b7e2a1170682d9ac2a5dcaeaab732b7e16f04a" packageManager: "pnpm@10.27.0+sha512.72d699da16b1179c14ba9e64dc71c9a40988cbdc65c264cb0e489db7de917f20dcf4d64d8723625f2969ba52d4b7e2a1170682d9ac2a5dcaeaab732b7e16f04a"
}; };
// utils/cli.ts // utils/log.ts
var core = __toESM(require_core(), 1); var core = __toESM(require_core(), 1);
var import_table = __toESM(require_src(), 1); var import_table = __toESM(require_src(), 1);
var isGitHubActions = !!process.env.GITHUB_ACTIONS; var isGitHubActions = !!process.env.GITHUB_ACTIONS;
var isDebugEnabled = () => process.env.LOG_LEVEL === "debug" || process.env.ACTIONS_STEP_DEBUG === "true" || process.env.RUNNER_DEBUG === "1" || core.isDebug(); var isDebugEnabled = () => process.env.LOG_LEVEL === "debug" || process.env.ACTIONS_STEP_DEBUG === "true" || process.env.RUNNER_DEBUG === "1" || core.isDebug();
function formatArgs(args3) {
return args3.map((arg) => {
if (typeof arg === "string") return arg;
if (arg instanceof Error) return `${arg.message}
${arg.stack}`;
return JSON.stringify(arg);
}).join(" ");
}
function startGroup2(name) { function startGroup2(name) {
if (isGitHubActions) { if (isGitHubActions) {
core.startGroup(name); core.startGroup(name);
@@ -100145,25 +100153,25 @@ function separator(length = 50) {
} }
var log = { var log = {
/** Print info message */ /** Print info message */
info: (message) => { info: (...args3) => {
core.info(message); core.info(formatArgs(args3));
}, },
/** Print warning message */ /** Print warning message */
warning: (message) => { warning: (...args3) => {
core.warning(message); core.warning(formatArgs(args3));
}, },
/** Print error message */ /** Print error message */
error: (message) => { error: (...args3) => {
core.error(message); core.error(formatArgs(args3));
}, },
/** Print success message */ /** Print success message */
success: (message) => { success: (...args3) => {
core.info(`\u2705 ${message}`); core.info(`\u2705 ${formatArgs(args3)}`);
}, },
/** Print debug message (only if LOG_LEVEL=debug) */ /** Print debug message (only if LOG_LEVEL=debug) */
debug: (message) => { debug: (...args3) => {
if (isDebugEnabled()) { if (isDebugEnabled()) {
core.info(`[DEBUG] ${message}`); core.info(`[DEBUG] ${formatArgs(args3)}`);
} }
}, },
/** Print a formatted box with text */ /** Print a formatted box with text */
@@ -100420,14 +100428,14 @@ function getShellInstructions(bash) {
} }
} }
} }
var addInstructions = ({ payload, repo, tools }) => { var addInstructions = (ctx) => {
let encodedEvent = ""; let encodedEvent = "";
const eventKeys = Object.keys(payload.event); const eventKeys = Object.keys(ctx.payload.event);
if (eventKeys.length === 1 && eventKeys[0] === "trigger") { if (eventKeys.length === 1 && eventKeys[0] === "trigger") {
} else { } else {
encodedEvent = encode(payload.event); encodedEvent = encode(ctx.payload.event);
} }
const runtimeContext = buildRuntimeContext(repo); const runtimeContext = buildRuntimeContext(ctx.repo);
return ` return `
*********************************************** ***********************************************
************* SYSTEM INSTRUCTIONS ************* ************* SYSTEM INSTRUCTIONS *************
@@ -100475,7 +100483,7 @@ Tool names may be formatted as \`(server name)/(tool name)\`, for example: \`${g
**Efficiency**: Trust the tools - do not repeatedly verify file contents or git status after operations. If a tool reports success, proceed to the next step. Only verify if you encounter an actual error. **Efficiency**: Trust the tools - do not repeatedly verify file contents or git status after operations. If a tool reports success, proceed to the next step. Only verify if you encounter an actual error.
${getShellInstructions(tools.bash)} ${getShellInstructions(ctx.tools.bash)}
**Command execution**: Never use \`sleep\` to wait for commands to complete. Commands run synchronously - when the bash tool returns, the command has finished. **Command execution**: Never use \`sleep\` to wait for commands to complete. Commands run synchronously - when the bash tool returns, the command has finished.
@@ -100496,7 +100504,7 @@ ${getShellInstructions(tools.bash)}
### Available modes ### Available modes
${[...getModes({ disableProgressComment: payload.disableProgressComment }), ...payload.modes].map((w) => ` - "${w.name}": ${w.description}`).join("\n")} ${[...getModes({ disableProgressComment: ctx.payload.disableProgressComment }), ...ctx.payload.modes].map((w) => ` - "${w.name}": ${w.description}`).join("\n")}
### Following the mode instructions ### Following the mode instructions
@@ -100506,7 +100514,7 @@ Eagerly inspect the MCP tools available to you via the \`${ghPullfrogMcpName}\`
************* USER PROMPT ************* ************* USER PROMPT *************
${payload.prompt.split("\n").map((line) => `> ${line}`).join("\n")} ${ctx.payload.prompt.split("\n").map((line) => `> ${line}`).join("\n")}
${encodedEvent ? `************* EVENT DATA ************* ${encodedEvent ? `************* EVENT DATA *************
@@ -104635,12 +104643,12 @@ var claudeEffortModels = {
auto: "opusplan", auto: "opusplan",
max: "opus" max: "opus"
}; };
function buildDisallowedTools(tools) { function buildDisallowedTools(ctx) {
const disallowed = []; const disallowed = [];
if (tools.web === "disabled") disallowed.push("WebFetch"); if (ctx.tools.web === "disabled") disallowed.push("WebFetch");
if (tools.search === "disabled") disallowed.push("WebSearch"); if (ctx.tools.search === "disabled") disallowed.push("WebSearch");
if (tools.write === "disabled") disallowed.push("Write"); if (ctx.tools.write === "disabled") disallowed.push("Write");
if (tools.bash !== "enabled") disallowed.push("Bash"); if (ctx.tools.bash !== "enabled") disallowed.push("Bash");
return disallowed; return disallowed;
} }
var claude = agent({ var claude = agent({
@@ -104653,23 +104661,23 @@ var claude = agent({
executablePath: "cli.js" executablePath: "cli.js"
}); });
}, },
run: async ({ payload, mcpServers, apiKey, cliPath, repo, effort, tools }) => { run: async (ctx) => {
delete process.env.ANTHROPIC_API_KEY; delete process.env.ANTHROPIC_API_KEY;
const prompt = addInstructions({ payload, repo, tools }); const prompt = addInstructions(ctx);
log.group("Full prompt", () => log.info(prompt)); log.group("Full prompt", () => log.info(prompt));
const model = claudeEffortModels[effort]; const model = claudeEffortModels[ctx.effort];
log.info(`Using model: ${model} (effort: ${effort})`); log.info(`Using model: ${model} (effort: ${ctx.effort})`);
const disallowedTools = buildDisallowedTools(tools); const disallowedTools = buildDisallowedTools(ctx);
if (disallowedTools.length > 0) { if (disallowedTools.length > 0) {
log.info(`\u{1F512} disallowed tools: ${disallowedTools.join(", ")}`); log.info(`\u{1F512} disallowed tools: ${disallowedTools.join(", ")}`);
} }
const queryOptions = { const queryOptions = {
permissionMode: "bypassPermissions", permissionMode: "bypassPermissions",
disallowedTools, disallowedTools,
mcpServers, mcpServers: ctx.mcpServers,
model, model,
pathToClaudeCodeExecutable: cliPath, pathToClaudeCodeExecutable: ctx.cliPath,
env: createAgentEnv({ ANTHROPIC_API_KEY: apiKey }) env: createAgentEnv({ ANTHROPIC_API_KEY: ctx.apiKey })
}; };
const queryInstance = query({ const queryInstance = query({
prompt, prompt,
@@ -105132,19 +105140,20 @@ var codexReasoningEffort = {
// use default // use default
max: "high" max: "high"
}; };
function writeCodexConfig({ tempHome, mcpServers, tools }) { function writeCodexConfig(ctx) {
const tempHome = process.env.PULLFROG_TEMP_DIR;
const codexDir = join6(tempHome, ".codex"); const codexDir = join6(tempHome, ".codex");
mkdirSync3(codexDir, { recursive: true }); mkdirSync3(codexDir, { recursive: true });
const configPath = join6(codexDir, "config.toml"); const configPath = join6(codexDir, "config.toml");
const mcpServerSections = []; const mcpServerSections = [];
for (const [name, config4] of Object.entries(mcpServers)) { for (const [name, config4] of Object.entries(ctx.mcpServers)) {
if (config4.type !== "http") continue; if (config4.type !== "http") continue;
log.info(`\xBB adding MCP server '${name}' at ${config4.url}`); log.info(`\xBB adding MCP server '${name}' at ${config4.url}`);
mcpServerSections.push(`[mcp_servers.${name}] mcpServerSections.push(`[mcp_servers.${name}]
url = "${config4.url}"`); url = "${config4.url}"`);
} }
const features = []; const features = [];
if (tools.bash !== "enabled") { if (ctx.tools.bash !== "enabled") {
features.push("shell_command_tool = false"); features.push("shell_command_tool = false");
features.push("unified_exec = false"); features.push("unified_exec = false");
} }
@@ -105159,7 +105168,7 @@ ${mcpServerSections.join("\n\n")}
`.trim() + "\n" `.trim() + "\n"
); );
log.info( log.info(
`\xBB Codex config written to ${configPath} (shell: ${tools.bash === "enabled" ? "enabled" : "disabled"})` `\xBB Codex config written to ${configPath} (shell: ${ctx.tools.bash === "enabled" ? "enabled" : "disabled"})`
); );
return codexDir; return codexDir;
} }
@@ -105172,41 +105181,37 @@ var codex = agent({
executablePath: "bin/codex.js" executablePath: "bin/codex.js"
}); });
}, },
run: async ({ payload, mcpServers, apiKey, cliPath, repo, effort, tools }) => { run: async (ctx) => {
const tempHome = process.env.PULLFROG_TEMP_DIR; const tempHome = process.env.PULLFROG_TEMP_DIR;
const configDir = join6(tempHome, ".config", "codex"); const configDir = join6(tempHome, ".config", "codex");
mkdirSync3(configDir, { recursive: true }); mkdirSync3(configDir, { recursive: true });
const codexDir = writeCodexConfig({ const codexDir = writeCodexConfig(ctx);
tempHome,
mcpServers,
tools
});
setupProcessAgentEnv({ setupProcessAgentEnv({
OPENAI_API_KEY: apiKey, OPENAI_API_KEY: ctx.apiKey,
HOME: tempHome, HOME: tempHome,
CODEX_HOME: codexDir CODEX_HOME: codexDir
// point Codex to our config directory // point Codex to our config directory
}); });
const model = codexModel[effort]; const model = codexModel[ctx.effort];
const modelReasoningEffort = codexReasoningEffort[effort]; const modelReasoningEffort = codexReasoningEffort[ctx.effort];
log.info(`Using model: ${model}`); log.info(`Using model: ${model}`);
if (modelReasoningEffort) { if (modelReasoningEffort) {
log.info(`Using modelReasoningEffort: ${modelReasoningEffort}`); log.info(`Using modelReasoningEffort: ${modelReasoningEffort}`);
} }
const codexOptions = { const codexOptions = {
apiKey, apiKey: ctx.apiKey,
codexPathOverride: cliPath codexPathOverride: ctx.cliPath
}; };
const codex2 = new Codex(codexOptions); const codex2 = new Codex(codexOptions);
const threadOptions = { const threadOptions = {
model, model,
approvalPolicy: "never", approvalPolicy: "never",
// write: "disabled" → read-only sandbox, otherwise full access for git ops // write: "disabled" → read-only sandbox, otherwise full access for git ops
sandboxMode: tools.write === "disabled" ? "read-only" : "danger-full-access", sandboxMode: ctx.tools.write === "disabled" ? "read-only" : "danger-full-access",
// web: controls network access // web: controls network access
networkAccessEnabled: tools.web !== "disabled", networkAccessEnabled: ctx.tools.web !== "disabled",
// search: controls web search // search: controls web search
webSearchEnabled: tools.search !== "disabled", webSearchEnabled: ctx.tools.search !== "disabled",
...modelReasoningEffort && { modelReasoningEffort } ...modelReasoningEffort && { modelReasoningEffort }
}; };
log.info( log.info(
@@ -105214,7 +105219,7 @@ var codex = agent({
); );
const thread = codex2.startThread(threadOptions); const thread = codex2.startThread(threadOptions);
try { try {
const streamedTurn = await thread.runStreamed(addInstructions({ payload, repo, tools })); const streamedTurn = await thread.runStreamed(addInstructions(ctx));
let finalOutput2 = ""; let finalOutput2 = "";
for await (const event of streamedTurn.events) { for await (const event of streamedTurn.events) {
const handler2 = messageHandlers2[event.type]; const handler2 = messageHandlers2[event.type];
@@ -105341,9 +105346,9 @@ var cursor = agent({
executableName: "cursor-agent" executableName: "cursor-agent"
}); });
}, },
run: async ({ payload, apiKey, cliPath, mcpServers, repo, effort, tools }) => { run: async (ctx) => {
configureCursorMcpServers({ mcpServers, cliPath }); configureCursorMcpServers(ctx);
configureCursorTools({ tools }); configureCursorTools(ctx);
const projectCliConfigPath = join7(process.cwd(), ".cursor", "cli.json"); const projectCliConfigPath = join7(process.cwd(), ".cursor", "cli.json");
let modelOverride = null; let modelOverride = null;
if (existsSync4(projectCliConfigPath)) { if (existsSync4(projectCliConfigPath)) {
@@ -105352,18 +105357,18 @@ var cursor = agent({
if (projectConfig.model) { if (projectConfig.model) {
log.info(`Using model from project .cursor/cli.json: ${projectConfig.model}`); log.info(`Using model from project .cursor/cli.json: ${projectConfig.model}`);
} else { } else {
modelOverride = cursorEffortModels[effort]; modelOverride = cursorEffortModels[ctx.effort];
} }
} catch { } catch {
modelOverride = cursorEffortModels[effort]; modelOverride = cursorEffortModels[ctx.effort];
} }
} else { } else {
modelOverride = cursorEffortModels[effort]; modelOverride = cursorEffortModels[ctx.effort];
} }
if (modelOverride) { if (modelOverride) {
log.info(`Using model: ${modelOverride} (effort: ${effort})`); log.info(`Using model: ${modelOverride} (effort: ${ctx.effort})`);
} else if (!existsSync4(projectCliConfigPath)) { } else if (!existsSync4(projectCliConfigPath)) {
log.info(`Using default model (effort: ${effort})`); log.info(`Using default model (effort: ${ctx.effort})`);
} }
const loggedModelCallIds = /* @__PURE__ */ new Set(); const loggedModelCallIds = /* @__PURE__ */ new Set();
const messageHandlers5 = { const messageHandlers5 = {
@@ -105415,7 +105420,7 @@ var cursor = agent({
} }
}; };
try { try {
const fullPrompt = addInstructions({ payload, repo, tools }); const fullPrompt = addInstructions(ctx);
log.group("Full prompt", () => log.info(fullPrompt)); log.group("Full prompt", () => log.info(fullPrompt));
const baseArgs = ["--print", fullPrompt, "--output-format", "stream-json", "--approve-mcps"]; const baseArgs = ["--print", fullPrompt, "--output-format", "stream-json", "--approve-mcps"];
if (modelOverride) { if (modelOverride) {
@@ -105425,10 +105430,10 @@ var cursor = agent({
log.info("Running Cursor CLI..."); log.info("Running Cursor CLI...");
const startTime = Date.now(); const startTime = Date.now();
return new Promise((resolve2) => { return new Promise((resolve2) => {
const child = spawn3(cliPath, cursorArgs, { const child = spawn3(ctx.cliPath, cursorArgs, {
cwd: process.cwd(), cwd: process.cwd(),
env: createAgentEnv({ env: createAgentEnv({
CURSOR_API_KEY: apiKey CURSOR_API_KEY: ctx.apiKey
}), }),
stdio: ["ignore", "pipe", "pipe"] stdio: ["ignore", "pipe", "pipe"]
// Ignore stdin, pipe stdout/stderr // Ignore stdin, pipe stdout/stderr
@@ -105503,13 +105508,13 @@ var cursor = agent({
} }
} }
}); });
function configureCursorMcpServers({ mcpServers }) { function configureCursorMcpServers(ctx) {
const realHome = homedir2(); const realHome = homedir2();
const cursorConfigDir = join7(realHome, ".cursor"); const cursorConfigDir = join7(realHome, ".cursor");
const mcpConfigPath = join7(cursorConfigDir, "mcp.json"); const mcpConfigPath = join7(cursorConfigDir, "mcp.json");
mkdirSync4(cursorConfigDir, { recursive: true }); mkdirSync4(cursorConfigDir, { recursive: true });
const cursorMcpServers = {}; const cursorMcpServers = {};
for (const [serverName, serverConfig] of Object.entries(mcpServers)) { for (const [serverName, serverConfig] of Object.entries(ctx.mcpServers)) {
if (serverConfig.type !== "http") { if (serverConfig.type !== "http") {
throw new Error( throw new Error(
`Unsupported MCP server type for Cursor: ${serverConfig.type || "unknown"}` `Unsupported MCP server type for Cursor: ${serverConfig.type || "unknown"}`
@@ -105523,33 +105528,29 @@ function configureCursorMcpServers({ mcpServers }) {
writeFileSync2(mcpConfigPath, JSON.stringify({ mcpServers: cursorMcpServers }, null, 2), "utf-8"); writeFileSync2(mcpConfigPath, JSON.stringify({ mcpServers: cursorMcpServers }, null, 2), "utf-8");
log.info(`\xBB MCP config written to ${mcpConfigPath}`); log.info(`\xBB MCP config written to ${mcpConfigPath}`);
} }
function configureCursorTools({ tools }) { function configureCursorTools(ctx) {
const realHome = homedir2(); const realHome = homedir2();
const cursorConfigDir = join7(realHome, ".config", "cursor"); const cursorConfigDir = join7(realHome, ".config", "cursor");
const cliConfigPath = join7(cursorConfigDir, "cli-config.json"); const cliConfigPath = join7(cursorConfigDir, "cli-config.json");
mkdirSync4(cursorConfigDir, { recursive: true }); mkdirSync4(cursorConfigDir, { recursive: true });
const deny = []; const deny = [];
if (tools.search === "disabled") deny.push("WebSearch"); if (ctx.tools.search === "disabled") deny.push("WebSearch");
if (tools.write === "disabled") deny.push("Write(**)"); if (ctx.tools.write === "disabled") deny.push("Write(**)");
if (tools.bash !== "enabled") deny.push("Shell(*)"); if (ctx.tools.bash !== "enabled") deny.push("Shell(*)");
const needsSandbox = tools.web === "disabled";
const config4 = { const config4 = {
permissions: { permissions: {
allow: tools.write === "disabled" ? ["Read(**)"] : ["Read(**)", "Write(**)"], allow: ctx.tools.write === "disabled" ? ["Read(**)"] : ["Read(**)", "Write(**)"],
deny deny
} }
}; };
if (needsSandbox) { if (ctx.tools.web === "disabled") {
config4.sandbox = { config4.sandbox = {
mode: "enabled", mode: "enabled",
networkAccess: "allowlist" networkAccess: "allowlist"
}; };
} }
writeFileSync2(cliConfigPath, JSON.stringify(config4, null, 2), "utf-8"); writeFileSync2(cliConfigPath, JSON.stringify(config4, null, 2), "utf-8");
log.info(`\xBB CLI config written to ${cliConfigPath}`); log.info(`\xBB CLI config written to ${cliConfigPath}`, JSON.stringify(config4, null, 2));
log.info(
`\u{1F527} Cursor permissions: allow=${config4.permissions.allow.join(",")}, deny=${deny.join(",") || "(none)"}, sandbox=${needsSandbox ? "enabled" : "disabled"}`
);
} }
// agents/gemini.ts // agents/gemini.ts
@@ -105725,14 +105726,12 @@ var gemini = agent({
...githubInstallationToken2 && { githubInstallationToken: githubInstallationToken2 } ...githubInstallationToken2 && { githubInstallationToken: githubInstallationToken2 }
}); });
}, },
run: async ({ payload, apiKey, mcpServers, cliPath, repo, effort, tools }) => { run: async (ctx) => {
const { model, thinkingLevel } = geminiEffortConfig[effort]; const model = configureGeminiSettings(ctx);
log.info(`Using model: ${model}, thinkingLevel: ${thinkingLevel}`); if (!ctx.apiKey) {
configureGeminiSettings({ mcpServers, tools, thinkingLevel });
if (!apiKey) {
throw new Error("google_api_key or gemini_api_key is required for gemini agent"); throw new Error("google_api_key or gemini_api_key is required for gemini agent");
} }
const sessionPrompt = addInstructions({ payload, repo, tools }); const sessionPrompt = addInstructions(ctx);
log.group("Full prompt", () => log.info(sessionPrompt)); log.group("Full prompt", () => log.info(sessionPrompt));
const args3 = ["--model", model, "--yolo", "--output-format=stream-json", "-p", sessionPrompt]; const args3 = ["--model", model, "--yolo", "--output-format=stream-json", "-p", sessionPrompt];
let finalOutput2 = ""; let finalOutput2 = "";
@@ -105740,8 +105739,8 @@ var gemini = agent({
try { try {
const result = await spawn4({ const result = await spawn4({
cmd: "node", cmd: "node",
args: [cliPath, ...args3], args: [ctx.cliPath, ...args3],
env: createAgentEnv({ GEMINI_API_KEY: apiKey }), env: createAgentEnv({ GEMINI_API_KEY: ctx.apiKey }),
onStdout: async (chunk) => { onStdout: async (chunk) => {
const text = chunk.toString(); const text = chunk.toString();
finalOutput2 += text; finalOutput2 += text;
@@ -105798,11 +105797,9 @@ var gemini = agent({
} }
} }
}); });
function configureGeminiSettings({ function configureGeminiSettings(ctx) {
mcpServers, const { model, thinkingLevel } = geminiEffortConfig[ctx.effort];
tools, log.info(`Using model: ${model}, thinkingLevel: ${thinkingLevel}`);
thinkingLevel
}) {
const realHome = homedir3(); const realHome = homedir3();
const geminiConfigDir = join8(realHome, ".gemini"); const geminiConfigDir = join8(realHome, ".gemini");
const settingsPath = join8(geminiConfigDir, "settings.json"); const settingsPath = join8(geminiConfigDir, "settings.json");
@@ -105814,7 +105811,7 @@ function configureGeminiSettings({
} catch { } catch {
} }
const geminiMcpServers = {}; const geminiMcpServers = {};
for (const [serverName, serverConfig] of Object.entries(mcpServers)) { for (const [serverName, serverConfig] of Object.entries(ctx.mcpServers)) {
if (serverConfig.type !== "http") { if (serverConfig.type !== "http") {
throw new Error( throw new Error(
`Unsupported MCP server type for Gemini: ${serverConfig.type || "unknown"}` `Unsupported MCP server type for Gemini: ${serverConfig.type || "unknown"}`
@@ -105828,10 +105825,10 @@ function configureGeminiSettings({
log.info(`adding MCP server '${serverName}' at ${serverConfig.url}...`); log.info(`adding MCP server '${serverName}' at ${serverConfig.url}...`);
} }
const exclude = []; const exclude = [];
if (tools.bash !== "enabled") exclude.push("run_shell_command"); if (ctx.tools.bash !== "enabled") exclude.push("run_shell_command");
if (tools.write === "disabled") exclude.push("write_file"); if (ctx.tools.write === "disabled") exclude.push("write_file");
if (tools.web === "disabled") exclude.push("web_fetch"); if (ctx.tools.web === "disabled") exclude.push("web_fetch");
if (tools.search === "disabled") exclude.push("google_web_search"); if (ctx.tools.search === "disabled") exclude.push("google_web_search");
const newSettings = { const newSettings = {
...existingSettings, ...existingSettings,
mcpServers: geminiMcpServers, mcpServers: geminiMcpServers,
@@ -105852,6 +105849,7 @@ function configureGeminiSettings({
if (exclude.length > 0) { if (exclude.length > 0) {
log.info(`\u{1F512} excluded tools: ${exclude.join(", ")}`); log.info(`\u{1F512} excluded tools: ${exclude.join(", ")}`);
} }
return model;
} }
// agents/opencode.ts // agents/opencode.ts
@@ -105867,21 +105865,12 @@ var opencode = agent({
installDependencies: true installDependencies: true
}); });
}, },
run: async ({ run: async (ctx) => {
payload,
apiKey: _apiKey,
apiKeys,
mcpServers,
cliPath,
repo,
effort: _effort,
tools
}) => {
const tempHome = process.env.PULLFROG_TEMP_DIR; const tempHome = process.env.PULLFROG_TEMP_DIR;
const configDir = join9(tempHome, ".config", "opencode"); const configDir = join9(tempHome, ".config", "opencode");
mkdirSync6(configDir, { recursive: true }); mkdirSync6(configDir, { recursive: true });
configureOpenCode({ mcpServers, tools }); configureOpenCode(ctx);
const prompt = addInstructions({ payload, repo, tools }); const prompt = addInstructions(ctx);
log.group("Full prompt", () => log.info(prompt)); log.group("Full prompt", () => log.info(prompt));
const args3 = ["run", prompt, "--format", "json"]; const args3 = ["run", prompt, "--format", "json"];
setupProcessAgentEnv({ HOME: tempHome }); setupProcessAgentEnv({ HOME: tempHome });
@@ -105890,14 +105879,14 @@ var opencode = agent({
XDG_CONFIG_HOME: join9(tempHome, ".config") XDG_CONFIG_HOME: join9(tempHome, ".config")
}; };
delete env3.GITHUB_TOKEN; delete env3.GITHUB_TOKEN;
for (const [key, value2] of Object.entries(apiKeys || {})) { for (const [key, value2] of Object.entries(ctx.apiKeys || {})) {
env3[key.toUpperCase()] = value2; env3[key.toUpperCase()] = value2;
if (key === "GEMINI_API_KEY") { if (key === "GEMINI_API_KEY") {
env3.GOOGLE_GENERATIVE_AI_API_KEY = value2; env3.GOOGLE_GENERATIVE_AI_API_KEY = value2;
} }
} }
const repoDir = process.cwd(); const repoDir = process.cwd();
log.info(`\u{1F680} Starting OpenCode CLI: ${cliPath} ${args3.join(" ")}`); log.info(`\u{1F680} Starting OpenCode CLI: ${ctx.cliPath} ${args3.join(" ")}`);
log.info(`\u{1F4C1} Working directory: ${repoDir}`); log.info(`\u{1F4C1} Working directory: ${repoDir}`);
log.debug(`\u{1F3E0} HOME: ${env3.HOME}`); log.debug(`\u{1F3E0} HOME: ${env3.HOME}`);
log.debug(`\u{1F4CB} XDG_CONFIG_HOME: ${env3.XDG_CONFIG_HOME}`); log.debug(`\u{1F4CB} XDG_CONFIG_HOME: ${env3.XDG_CONFIG_HOME}`);
@@ -105907,7 +105896,7 @@ var opencode = agent({
let output = ""; let output = "";
let stdoutBuffer = ""; let stdoutBuffer = "";
const result = await spawn4({ const result = await spawn4({
cmd: cliPath, cmd: ctx.cliPath,
args: args3, args: args3,
cwd: repoDir, cwd: repoDir,
env: env3, env: env3,
@@ -105993,13 +105982,13 @@ var opencode = agent({
}; };
} }
}); });
function configureOpenCode({ mcpServers, tools }) { function configureOpenCode(ctx) {
const tempHome = process.env.PULLFROG_TEMP_DIR; const tempHome = process.env.PULLFROG_TEMP_DIR;
const configDir = join9(tempHome, ".config", "opencode"); const configDir = join9(tempHome, ".config", "opencode");
mkdirSync6(configDir, { recursive: true }); mkdirSync6(configDir, { recursive: true });
const configPath = join9(configDir, "opencode.json"); const configPath = join9(configDir, "opencode.json");
const opencodeMcpServers = {}; const opencodeMcpServers = {};
for (const [serverName, serverConfig] of Object.entries(mcpServers)) { for (const [serverName, serverConfig] of Object.entries(ctx.mcpServers)) {
if (serverConfig.type !== "http") { if (serverConfig.type !== "http") {
log.error( log.error(
`unsupported MCP server type for OpenCode: ${serverConfig.type || "unknown"}` `unsupported MCP server type for OpenCode: ${serverConfig.type || "unknown"}`
@@ -106014,9 +106003,9 @@ function configureOpenCode({ mcpServers, tools }) {
}; };
} }
const permission = { const permission = {
edit: tools.write === "disabled" ? "deny" : "allow", edit: ctx.tools.write === "disabled" ? "deny" : "allow",
bash: tools.bash !== "enabled" ? "deny" : "allow", bash: ctx.tools.bash !== "enabled" ? "deny" : "allow",
webfetch: tools.web === "disabled" ? "deny" : "allow", webfetch: ctx.tools.web === "disabled" ? "deny" : "allow",
doom_loop: "allow", doom_loop: "allow",
external_directory: "allow" external_directory: "allow"
}; };
+3 -266
View File
@@ -1,275 +1,12 @@
/** /**
* CLI output utilities that work well in both local and GitHub Actions environments * CLI utilities
*/ */
import { spawnSync } from "node:child_process"; import { spawnSync } from "node:child_process";
import { existsSync } from "node:fs"; import { existsSync } from "node:fs";
import * as core from "@actions/core";
import { table } from "table";
const isGitHubActions = !!process.env.GITHUB_ACTIONS; // re-export logging utilities for backward compatibility
export { formatIndentedField, formatJsonValue, log, writeSummary } from "./log.ts";
const isDebugEnabled = () =>
process.env.LOG_LEVEL === "debug" ||
process.env.ACTIONS_STEP_DEBUG === "true" ||
process.env.RUNNER_DEBUG === "1" ||
core.isDebug();
/**
* Start a collapsed group (GitHub Actions) or regular group (local)
*/
function startGroup(name: string): void {
if (isGitHubActions) {
core.startGroup(name);
} else {
console.group(name);
}
}
/**
* End a collapsed group
*/
function endGroup(): void {
if (isGitHubActions) {
core.endGroup();
} else {
console.groupEnd();
}
}
/**
* Run a callback within a collapsed group
*/
function group(name: string, fn: () => void): void {
startGroup(name);
fn();
endGroup();
}
/**
* Print a formatted box with text (for console output)
*/
function boxString(
text: string,
options?: {
title?: string;
maxWidth?: number;
indent?: string;
padding?: number;
}
): string {
const { title, maxWidth = 80, indent = "", padding = 1 } = options || {};
const lines = text.trim().split("\n");
const wrappedLines: string[] = [];
for (const line of lines) {
if (line.length <= maxWidth - padding * 2) {
wrappedLines.push(line);
} else {
const words = line.split(" ");
let currentLine = "";
for (const word of words) {
const testLine = currentLine ? `${currentLine} ${word}` : word;
if (testLine.length <= maxWidth - padding * 2) {
currentLine = testLine;
} else {
if (currentLine) {
wrappedLines.push(currentLine);
currentLine = "";
}
// wrap long words by breaking them into chunks
const maxLineLength = maxWidth - padding * 2;
let remainingWord = word;
while (remainingWord.length > maxLineLength) {
wrappedLines.push(remainingWord.substring(0, maxLineLength));
remainingWord = remainingWord.substring(maxLineLength);
}
currentLine = remainingWord;
}
}
if (currentLine) {
wrappedLines.push(currentLine);
}
}
}
const maxLineLength = Math.max(...wrappedLines.map((line) => line.length));
const contentBoxWidth = maxLineLength + padding * 2;
// ensure box width is at least as wide as the title line when title exists
const titleLineLength = title ? ` ${title} `.length : 0;
const boxWidth = Math.max(contentBoxWidth, titleLineLength);
let result = "";
if (title) {
const titleLine = ` ${title} `;
const titlePadding = Math.max(0, boxWidth - titleLine.length);
result += `${indent}${titleLine}${"─".repeat(titlePadding)}\n`;
}
if (!title) {
result += `${indent}${"─".repeat(boxWidth)}\n`;
}
for (const line of wrappedLines) {
const paddedLine = line.padEnd(maxLineLength);
result += `${indent}${" ".repeat(padding)}${paddedLine}${" ".repeat(padding)}\n`;
}
result += `${indent}${"─".repeat(boxWidth)}`;
return result;
}
/**
* Print a formatted box with text
*/
function box(
text: string,
options?: {
title?: string;
maxWidth?: number;
}
): void {
const boxContent = boxString(text, options);
core.info(boxContent);
}
/**
* Overwrite the job summary with the given text.
*/
export function writeSummary(text: string): void {
if (!isGitHubActions) return;
core.summary.addRaw(text).write({ overwrite: true });
}
/**
* Print a formatted table using the table package
*/
function printTable(
rows: Array<Array<{ data: string; header?: boolean } | string>>,
options?: {
title?: string;
}
): void {
const { title } = options || {};
// Convert rows to string arrays for the table package
const tableData = rows.map((row) =>
row.map((cell) => {
if (typeof cell === "string") {
return cell;
}
return cell.data;
})
);
const formatted = table(tableData);
if (title) {
core.info(`\n${title}`);
}
core.info(`\n${formatted}\n`);
}
/**
* Print a separator line
*/
function separator(length: number = 50): void {
const separatorText = "─".repeat(length);
core.info(separatorText);
}
/**
* Main logging utility object - import this once and access all utilities
*/
export const log = {
/** Print info message */
info: (message: string): void => {
core.info(message);
},
/** Print warning message */
warning: (message: string): void => {
core.warning(message);
},
/** Print error message */
error: (message: string): void => {
core.error(message);
},
/** Print success message */
success: (message: string): void => {
core.info(`${message}`);
},
/** Print debug message (only if LOG_LEVEL=debug) */
debug: (message: string | unknown): void => {
if (isDebugEnabled()) {
core.info(`[DEBUG] ${message}`);
}
},
/** Print a formatted box with text */
box,
/** Print a formatted table using the table package */
table: printTable,
/** Print a separator line */
separator,
/** Start a collapsed group (GitHub Actions) or regular group (local) */
startGroup,
/** End a collapsed group */
endGroup,
/** Run a callback within a collapsed group */
group,
/** Log tool call information to console with formatted output */
toolCall: ({ toolName, input }: { toolName: string; input: unknown }): void => {
const inputFormatted = formatJsonValue(input);
const timestamp = isDebugEnabled() ? ` [${new Date().toISOString()}]` : "";
const output =
inputFormatted !== "{}"
? `${toolName}(${inputFormatted})${timestamp}`
: `${toolName}()${timestamp}`;
log.info(output.trimEnd());
},
};
/**
* Format a value as JSON, using compact format for simple values and pretty-printed for complex ones
*/
export function formatJsonValue(value: unknown): string {
const compact = JSON.stringify(value);
return compact.length > 80 || compact.includes("\n") ? JSON.stringify(value, null, 2) : compact;
}
/**
* Format a multi-line string with proper indentation for tool call output
* First line has the label, subsequent lines are indented 4 spaces
*/
export function formatIndentedField(label: string, content: string): string {
if (!content.includes("\n")) {
return ` ${label}: ${content}\n`;
}
const lines = content.split("\n");
let formatted = ` ${label}: ${lines[0]}\n`;
for (let i = 1; i < lines.length; i++) {
formatted += ` ${lines[i]}\n`;
}
return formatted;
}
/** /**
* Finds a CLI executable path by checking if it's installed globally * Finds a CLI executable path by checking if it's installed globally
+283
View File
@@ -0,0 +1,283 @@
/**
* Logging utilities that work well in both local and GitHub Actions environments
*/
import * as core from "@actions/core";
import { table } from "table";
const isGitHubActions = !!process.env.GITHUB_ACTIONS;
const isDebugEnabled = () =>
process.env.LOG_LEVEL === "debug" ||
process.env.ACTIONS_STEP_DEBUG === "true" ||
process.env.RUNNER_DEBUG === "1" ||
core.isDebug();
/**
* Format arguments into a single string for logging
*/
function formatArgs(args: unknown[]): string {
return args
.map((arg) => {
if (typeof arg === "string") return arg;
if (arg instanceof Error) return `${arg.message}\n${arg.stack}`;
return JSON.stringify(arg);
})
.join(" ");
}
/**
* Start a collapsed group (GitHub Actions) or regular group (local)
*/
function startGroup(name: string): void {
if (isGitHubActions) {
core.startGroup(name);
} else {
console.group(name);
}
}
/**
* End a collapsed group
*/
function endGroup(): void {
if (isGitHubActions) {
core.endGroup();
} else {
console.groupEnd();
}
}
/**
* Run a callback within a collapsed group
*/
function group(name: string, fn: () => void): void {
startGroup(name);
fn();
endGroup();
}
/**
* Print a formatted box with text (for console output)
*/
function boxString(
text: string,
options?: {
title?: string;
maxWidth?: number;
indent?: string;
padding?: number;
}
): string {
const { title, maxWidth = 80, indent = "", padding = 1 } = options || {};
const lines = text.trim().split("\n");
const wrappedLines: string[] = [];
for (const line of lines) {
if (line.length <= maxWidth - padding * 2) {
wrappedLines.push(line);
} else {
const words = line.split(" ");
let currentLine = "";
for (const word of words) {
const testLine = currentLine ? `${currentLine} ${word}` : word;
if (testLine.length <= maxWidth - padding * 2) {
currentLine = testLine;
} else {
if (currentLine) {
wrappedLines.push(currentLine);
currentLine = "";
}
// wrap long words by breaking them into chunks
const maxLineLength = maxWidth - padding * 2;
let remainingWord = word;
while (remainingWord.length > maxLineLength) {
wrappedLines.push(remainingWord.substring(0, maxLineLength));
remainingWord = remainingWord.substring(maxLineLength);
}
currentLine = remainingWord;
}
}
if (currentLine) {
wrappedLines.push(currentLine);
}
}
}
const maxLineLength = Math.max(...wrappedLines.map((line) => line.length));
const contentBoxWidth = maxLineLength + padding * 2;
// ensure box width is at least as wide as the title line when title exists
const titleLineLength = title ? ` ${title} `.length : 0;
const boxWidth = Math.max(contentBoxWidth, titleLineLength);
let result = "";
if (title) {
const titleLine = ` ${title} `;
const titlePadding = Math.max(0, boxWidth - titleLine.length);
result += `${indent}${titleLine}${"─".repeat(titlePadding)}\n`;
}
if (!title) {
result += `${indent}${"─".repeat(boxWidth)}\n`;
}
for (const line of wrappedLines) {
const paddedLine = line.padEnd(maxLineLength);
result += `${indent}${" ".repeat(padding)}${paddedLine}${" ".repeat(padding)}\n`;
}
result += `${indent}${"─".repeat(boxWidth)}`;
return result;
}
/**
* Print a formatted box with text
*/
function box(
text: string,
options?: {
title?: string;
maxWidth?: number;
}
): void {
const boxContent = boxString(text, options);
core.info(boxContent);
}
/**
* Overwrite the job summary with the given text.
*/
export function writeSummary(text: string): void {
if (!isGitHubActions) return;
core.summary.addRaw(text).write({ overwrite: true });
}
/**
* Print a formatted table using the table package
*/
function printTable(
rows: Array<Array<{ data: string; header?: boolean } | string>>,
options?: {
title?: string;
}
): void {
const { title } = options || {};
// Convert rows to string arrays for the table package
const tableData = rows.map((row) =>
row.map((cell) => {
if (typeof cell === "string") {
return cell;
}
return cell.data;
})
);
const formatted = table(tableData);
if (title) {
core.info(`\n${title}`);
}
core.info(`\n${formatted}\n`);
}
/**
* Print a separator line
*/
function separator(length: number = 50): void {
const separatorText = "─".repeat(length);
core.info(separatorText);
}
/**
* Main logging utility object - import this once and access all utilities
*/
export const log = {
/** Print info message */
info: (...args: unknown[]): void => {
core.info(formatArgs(args));
},
/** Print warning message */
warning: (...args: unknown[]): void => {
core.warning(formatArgs(args));
},
/** Print error message */
error: (...args: unknown[]): void => {
core.error(formatArgs(args));
},
/** Print success message */
success: (...args: unknown[]): void => {
core.info(`${formatArgs(args)}`);
},
/** Print debug message (only if LOG_LEVEL=debug) */
debug: (...args: unknown[]): void => {
if (isDebugEnabled()) {
core.info(`[DEBUG] ${formatArgs(args)}`);
}
},
/** Print a formatted box with text */
box,
/** Print a formatted table using the table package */
table: printTable,
/** Print a separator line */
separator,
/** Start a collapsed group (GitHub Actions) or regular group (local) */
startGroup,
/** End a collapsed group */
endGroup,
/** Run a callback within a collapsed group */
group,
/** Log tool call information to console with formatted output */
toolCall: ({ toolName, input }: { toolName: string; input: unknown }): void => {
const inputFormatted = formatJsonValue(input);
const timestamp = isDebugEnabled() ? ` [${new Date().toISOString()}]` : "";
const output =
inputFormatted !== "{}"
? `${toolName}(${inputFormatted})${timestamp}`
: `${toolName}()${timestamp}`;
log.info(output.trimEnd());
},
};
/**
* Format a value as JSON, using compact format for simple values and pretty-printed for complex ones
*/
export function formatJsonValue(value: unknown): string {
const compact = JSON.stringify(value);
return compact.length > 80 || compact.includes("\n") ? JSON.stringify(value, null, 2) : compact;
}
/**
* Format a multi-line string with proper indentation for tool call output
* First line has the label, subsequent lines are indented 4 spaces
*/
export function formatIndentedField(label: string, content: string): string {
if (!content.includes("\n")) {
return ` ${label}: ${content}\n`;
}
const lines = content.split("\n");
let formatted = ` ${label}: ${lines[0]}\n`;
for (let i = 1; i < lines.length; i++) {
formatted += ` ${lines[i]}\n`;
}
return formatted;
}