centralize env management via createAgentEnv

This commit is contained in:
David Blass
2025-11-26 16:33:02 -05:00
parent 7853f9ef56
commit 1a882a11b8
9 changed files with 97 additions and 190 deletions
+2 -2
View File
@@ -2,7 +2,7 @@ import { query, type SDKMessage } from "@anthropic-ai/claude-agent-sdk";
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, installFromNpmTarball } from "./shared.ts"; import { agent, installFromNpmTarball, setupProcessAgentEnv } from "./shared.ts";
export const claude = agent({ export const claude = agent({
name: "claude", name: "claude",
@@ -15,7 +15,7 @@ export const claude = agent({
}); });
}, },
run: async ({ payload, mcpServers, apiKey, cliPath }) => { run: async ({ payload, mcpServers, apiKey, cliPath }) => {
process.env.ANTHROPIC_API_KEY = apiKey; setupProcessAgentEnv({ ANTHROPIC_API_KEY: apiKey });
const prompt = addInstructions(payload); const prompt = addInstructions(payload);
console.log(prompt); console.log(prompt);
+11 -13
View File
@@ -4,7 +4,12 @@ import { join } from "node:path";
import { Codex, type CodexOptions, type ThreadEvent } from "@openai/codex-sdk"; import { Codex, type CodexOptions, type ThreadEvent } from "@openai/codex-sdk";
import { log } from "../utils/cli.ts"; import { log } from "../utils/cli.ts";
import { addInstructions } from "./instructions.ts"; import { addInstructions } from "./instructions.ts";
import { agent, type ConfigureMcpServersParams, installFromNpmTarball } from "./shared.ts"; import {
agent,
type ConfigureMcpServersParams,
installFromNpmTarball,
setupProcessAgentEnv,
} from "./shared.ts";
export const codex = agent({ export const codex = agent({
name: "codex", name: "codex",
@@ -15,16 +20,16 @@ export const codex = agent({
executablePath: "bin/codex.js", executablePath: "bin/codex.js",
}); });
}, },
run: async ({ payload, mcpServers, apiKey, cliPath, githubInstallationToken }) => { run: async ({ payload, mcpServers, apiKey, cliPath }) => {
process.env.OPENAI_API_KEY = apiKey;
process.env.GITHUB_INSTALLATION_TOKEN = githubInstallationToken;
// create config directory for codex before setting HOME // create config directory for codex before setting HOME
const tempHome = process.env.PULLFROG_TEMP_DIR!; const tempHome = process.env.PULLFROG_TEMP_DIR!;
const configDir = join(tempHome, ".config", "codex"); const configDir = join(tempHome, ".config", "codex");
mkdirSync(configDir, { recursive: true }); mkdirSync(configDir, { recursive: true });
process.env.HOME = tempHome; setupProcessAgentEnv({
OPENAI_API_KEY: apiKey,
HOME: tempHome,
});
configureCodexMcpServers({ mcpServers, cliPath }); configureCodexMcpServers({ mcpServers, cliPath });
@@ -35,10 +40,6 @@ export const codex = agent({
}; };
const codex = new Codex(codexOptions); const codex = new Codex(codexOptions);
// Configure thread options to match Claude's permissions (bypassPermissions)
// approvalPolicy: "never" = no approval needed (equivalent to bypassPermissions)
// sandboxMode: "workspace-write" = allow file writes
// networkAccessEnabled: true = allow network access (needed for GitHub API calls)
const thread = codex.startThread({ const thread = codex.startThread({
approvalPolicy: "never", approvalPolicy: "never",
sandboxMode: "workspace-write", sandboxMode: "workspace-write",
@@ -46,10 +47,8 @@ export const codex = agent({
}); });
try { try {
// Use runStreamed to get streaming events similar to claude.ts
const streamedTurn = await thread.runStreamed(addInstructions(payload)); const streamedTurn = await thread.runStreamed(addInstructions(payload));
// Stream events and handle them
let finalOutput = ""; let finalOutput = "";
for await (const event of streamedTurn.events) { for await (const event of streamedTurn.events) {
const handler = messageHandlers[event.type]; const handler = messageHandlers[event.type];
@@ -58,7 +57,6 @@ export const codex = agent({
handler(event as never); handler(event as never);
} }
// Capture final response from agent messages
if (event.type === "item.completed" && event.item.type === "agent_message") { if (event.type === "item.completed" && event.item.type === "agent_message") {
finalOutput = event.item.text; finalOutput = event.item.text;
} }
+9 -14
View File
@@ -4,7 +4,12 @@ import { homedir } from "node:os";
import { join } from "node:path"; import { join } from "node:path";
import { log } from "../utils/cli.ts"; import { log } from "../utils/cli.ts";
import { addInstructions } from "./instructions.ts"; import { addInstructions } from "./instructions.ts";
import { agent, type ConfigureMcpServersParams, installFromCurl } from "./shared.ts"; import {
agent,
type ConfigureMcpServersParams,
createAgentEnv,
installFromCurl,
} from "./shared.ts";
// cursor cli event types inferred from stream-json output // cursor cli event types inferred from stream-json output
interface CursorSystemEvent { interface CursorSystemEvent {
@@ -138,10 +143,7 @@ export const cursor = agent({
executableName: "cursor-agent", executableName: "cursor-agent",
}); });
}, },
run: async ({ payload, apiKey, cliPath, githubInstallationToken, mcpServers }) => { run: async ({ payload, apiKey, cliPath, mcpServers }) => {
process.env.CURSOR_API_KEY = apiKey;
process.env.GITHUB_INSTALLATION_TOKEN = githubInstallationToken;
configureCursorMcpServers({ mcpServers, cliPath }); configureCursorMcpServers({ mcpServers, cliPath });
try { try {
@@ -165,16 +167,9 @@ export const cursor = agent({
], ],
{ {
cwd: process.cwd(), cwd: process.cwd(),
env: { env: createAgentEnv({
CURSOR_API_KEY: apiKey, CURSOR_API_KEY: apiKey,
GITHUB_INSTALLATION_TOKEN: githubInstallationToken, }),
LOG_LEVEL: process.env.LOG_LEVEL,
NODE_ENV: process.env.NODE_ENV,
HOME: process.env.HOME,
PATH: process.env.PATH,
// Don't override HOME - Cursor CLI needs access to macOS keychain
// MCP config is written to tempDir/.cursor/mcp.json which Cursor will find
},
stdio: ["ignore", "pipe", "pipe"], // Ignore stdin, pipe stdout/stderr stdio: ["ignore", "pipe", "pipe"], // Ignore stdin, pipe stdout/stderr
} }
); );
+9 -14
View File
@@ -2,7 +2,12 @@ import { spawnSync } from "node:child_process";
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 { agent, type ConfigureMcpServersParams, installFromGithub } from "./shared.ts"; import {
agent,
type ConfigureMcpServersParams,
createAgentEnv,
installFromGithub,
} from "./shared.ts";
// gemini cli event types inferred from stream-json output (NDJSON format) // gemini cli event types inferred from stream-json output (NDJSON format)
interface GeminiInitEvent { interface GeminiInitEvent {
@@ -152,16 +157,12 @@ export const gemini = agent({
assetName: "gemini.js", assetName: "gemini.js",
}); });
}, },
run: async ({ payload, apiKey, mcpServers, githubInstallationToken, cliPath }) => { run: async ({ payload, apiKey, mcpServers, cliPath }) => {
configureGeminiMcpServers({ mcpServers, cliPath }); configureGeminiMcpServers({ mcpServers, cliPath });
if (!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");
} }
// Set environment variables for Gemini CLI and MCP servers
process.env.GEMINI_API_KEY = apiKey;
process.env.GITHUB_INSTALLATION_TOKEN = githubInstallationToken;
const sessionPrompt = addInstructions(payload); const sessionPrompt = addInstructions(payload);
log.info(`Starting Gemini CLI with prompt: ${payload.prompt.substring(0, 100)}...`); log.info(`Starting Gemini CLI with prompt: ${payload.prompt.substring(0, 100)}...`);
@@ -170,15 +171,9 @@ export const gemini = agent({
const result = await spawn({ const result = await spawn({
cmd: "node", cmd: "node",
args: [cliPath, "--yolo", "--output-format=stream-json", "-p", sessionPrompt], args: [cliPath, "--yolo", "--output-format=stream-json", "-p", sessionPrompt],
env: { env: createAgentEnv({
PATH: process.env.PATH || "",
HOME: process.env.HOME || "",
TMPDIR: process.env.TMPDIR || "/tmp",
GEMINI_API_KEY: apiKey, GEMINI_API_KEY: apiKey,
GITHUB_INSTALLATION_TOKEN: githubInstallationToken, }),
LOG_LEVEL: process.env.LOG_LEVEL!,
NODE_ENV: process.env.NODE_ENV!,
},
timeout: 600000, // 10 minutes timeout: 600000, // 10 minutes
onStdout: async (chunk) => { onStdout: async (chunk) => {
const text = chunk.toString(); const text = chunk.toString();
+35 -9
View File
@@ -8,6 +8,7 @@ import type { McpHttpServerConfig } from "@anthropic-ai/claude-agent-sdk";
import type { show } from "@ark/util"; import type { show } from "@ark/util";
import { type AgentManifest, type AgentName, agentsManifest, type Payload } from "../external.ts"; import { type AgentManifest, type AgentName, agentsManifest, type Payload } from "../external.ts";
import { log } from "../utils/cli.ts"; import { log } from "../utils/cli.ts";
import { getGitHubInstallationToken } from "../utils/github.ts";
/** /**
* Result returned by agent execution * Result returned by agent execution
@@ -24,7 +25,6 @@ export interface AgentResult {
*/ */
export interface AgentConfig { export interface AgentConfig {
apiKey: string; apiKey: string;
githubInstallationToken: string;
payload: Payload; payload: Payload;
mcpServers: Record<string, McpHttpServerConfig>; mcpServers: Record<string, McpHttpServerConfig>;
cliPath: string; cliPath: string;
@@ -38,6 +38,35 @@ export interface ConfigureMcpServersParams {
cliPath: string; cliPath: string;
} }
/**
* Add agent-specific vars to a whitelisted environment object for agent subprocesses.
*
* @param agentSpecificVars - Object containing agent-specific environment variables to include
* @returns Whitelisted environment object safe for subprocess spawning
*/
export function createAgentEnv(agentSpecificVars: Record<string, string>): Record<string, string> {
return {
PATH: process.env.PATH,
HOME: process.env.HOME,
LOG_LEVEL: process.env.LOG_LEVEL,
NODE_ENV: process.env.NODE_ENV,
GITHUB_TOKEN: getGitHubInstallationToken(),
...agentSpecificVars,
// values could be undefined but will be ignored
} as never;
}
/**
* Set up whitelisted environment variables in the current process.
* Used for SDKs that run in the same process (e.g., Claude SDK, Codex SDK).
* Includes agent-agnostic vars (PATH, HOME, LOG_LEVEL, NODE_ENV) plus agent-specific vars.
*
* @param agentSpecificVars - Object containing agent-specific environment variables to include
*/
export function setupProcessAgentEnv(agentSpecificVars: Record<string, string>): void {
Object.assign(process.env, createAgentEnv(agentSpecificVars));
}
/** /**
* Parameters for installing from npm tarball * Parameters for installing from npm tarball
*/ */
@@ -296,17 +325,14 @@ export async function installFromCurl({
log.info(`Installing to temp directory at ${tempDir}...`); log.info(`Installing to temp directory at ${tempDir}...`);
// Run the install script with HOME set to temp directory
// The Cursor install script installs to $HOME/.local/bin/{executableName}
// By setting HOME=tempDir, we ensure it installs to tempDir/.local/bin/{executableName}
const installResult = spawnSync("bash", [installScriptPath], { const installResult = spawnSync("bash", [installScriptPath], {
cwd: tempDir, cwd: tempDir,
env: { env: {
HOME: tempDir, // Cursor install script uses HOME for installation path // Run the install script with HOME set to temp directory
PATH: process.env.PATH || "", // ensuring a fresh install for each run
SHELL: process.env.SHELL || "/bin/bash", HOME: tempDir,
USER: process.env.USER || "", SHELL: process.env.SHELL,
TMPDIR: process.env.TMPDIR || "/tmp", USER: process.env.USER,
}, },
stdio: "pipe", stdio: "pipe",
encoding: "utf-8", encoding: "utf-8",
+3 -36
View File
@@ -1,5 +1,4 @@
import { existsSync, readFileSync } from "node:fs"; import { mkdtemp } from "node:fs/promises";
import { mkdtemp, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os"; import { tmpdir } from "node:os";
import { join } from "node:path"; import { join } from "node:path";
import { flatMorph } from "@ark/util"; import { flatMorph } from "@ark/util";
@@ -18,7 +17,7 @@ import { log } from "./utils/cli.ts";
import { import {
parseRepoContext, parseRepoContext,
type RepoContext, type RepoContext,
revokeInstallationToken, revokeGitHubInstallationToken,
setupGitHubInstallationToken, setupGitHubInstallationToken,
} from "./utils/github.ts"; } from "./utils/github.ts";
import { setupGitAuth, setupGitBranch, setupGitConfig } from "./utils/setup.ts"; import { setupGitAuth, setupGitBranch, setupGitConfig } from "./utils/setup.ts";
@@ -49,9 +48,7 @@ export interface MainResult {
} }
export async function main(inputs: Inputs): Promise<MainResult> { export async function main(inputs: Inputs): Promise<MainResult> {
let pollInterval: NodeJS.Timeout | null = null;
let mcpServerClose: (() => Promise<void>) | undefined; let mcpServerClose: (() => Promise<void>) | undefined;
let githubInstallationToken: string | undefined;
try { try {
// parse payload early to extract agent // parse payload early to extract agent
@@ -59,15 +56,12 @@ export async function main(inputs: Inputs): Promise<MainResult> {
const partialCtx = await initializeContext(inputs, payload); const partialCtx = await initializeContext(inputs, payload);
const ctx = partialCtx as MainContext; const ctx = partialCtx as MainContext;
githubInstallationToken = ctx.githubInstallationToken;
setupGitAuth({ setupGitAuth({
githubInstallationToken: ctx.githubInstallationToken, githubInstallationToken: ctx.githubInstallationToken,
repoContext: ctx.repoContext, repoContext: ctx.repoContext,
}); });
await setupTempDirectory(ctx); await setupTempDirectory(ctx);
setupMcpLogPolling(ctx);
pollInterval = ctx.pollInterval;
setupGitBranch(ctx.payload); setupGitBranch(ctx.payload);
await startMcpServer(ctx); await startMcpServer(ctx);
@@ -87,15 +81,10 @@ export async function main(inputs: Inputs): Promise<MainResult> {
error: errorMessage, error: errorMessage,
}; };
} finally { } finally {
if (pollInterval) {
clearInterval(pollInterval);
}
if (mcpServerClose) { if (mcpServerClose) {
await mcpServerClose(); await mcpServerClose();
} }
if (githubInstallationToken) { await revokeGitHubInstallationToken();
await revokeInstallationToken(githubInstallationToken);
}
} }
} }
@@ -170,8 +159,6 @@ interface MainContext {
agentName: AgentNameType; agentName: AgentNameType;
agent: (typeof agents)[AgentNameType]; agent: (typeof agents)[AgentNameType];
sharedTempDir: string; sharedTempDir: string;
mcpLogPath: string;
pollInterval: NodeJS.Timeout | null;
payload: Payload; payload: Payload;
mcpServerUrl: string; mcpServerUrl: string;
mcpServerClose: () => Promise<void>; mcpServerClose: () => Promise<void>;
@@ -210,8 +197,6 @@ async function initializeContext(
agent, agent,
payload: resolvedPayload, payload: resolvedPayload,
sharedTempDir: "", sharedTempDir: "",
mcpLogPath: "",
pollInterval: null,
}; };
} }
@@ -262,25 +247,9 @@ async function setupTempDirectory(
): Promise<void> { ): Promise<void> {
ctx.sharedTempDir = await mkdtemp(join(tmpdir(), "pullfrog-")); ctx.sharedTempDir = await mkdtemp(join(tmpdir(), "pullfrog-"));
process.env.PULLFROG_TEMP_DIR = ctx.sharedTempDir; process.env.PULLFROG_TEMP_DIR = ctx.sharedTempDir;
ctx.mcpLogPath = join(ctx.sharedTempDir, "mcpLog.txt");
await writeFile(ctx.mcpLogPath, "", "utf-8");
log.info(`📂 PULLFROG_TEMP_DIR has been created at ${ctx.sharedTempDir}`); log.info(`📂 PULLFROG_TEMP_DIR has been created at ${ctx.sharedTempDir}`);
} }
function setupMcpLogPolling(ctx: MainContext): void {
let lastSize = 0;
ctx.pollInterval = setInterval(() => {
if (existsSync(ctx.mcpLogPath)) {
const content = readFileSync(ctx.mcpLogPath, "utf-8");
if (content.length > lastSize) {
const newContent = content.slice(lastSize);
process.stdout.write(newContent);
lastSize = content.length;
}
}
}, 100);
}
function parsePayload(inputs: Inputs): Payload { function parsePayload(inputs: Inputs): Payload {
try { try {
const parsedPrompt = JSON.parse(inputs.prompt); const parsedPrompt = JSON.parse(inputs.prompt);
@@ -307,7 +276,6 @@ async function startMcpServer(ctx: MainContext): Promise<void> {
const githubRepository = `${repoContext.owner}/${repoContext.name}`; const githubRepository = `${repoContext.owner}/${repoContext.name}`;
const allModes = [...modes, ...(ctx.payload.modes || [])]; const allModes = [...modes, ...(ctx.payload.modes || [])];
process.env.GITHUB_INSTALLATION_TOKEN = ctx.githubInstallationToken;
process.env.GITHUB_REPOSITORY = githubRepository; process.env.GITHUB_REPOSITORY = githubRepository;
process.env.PULLFROG_MODES = JSON.stringify(allModes); process.env.PULLFROG_MODES = JSON.stringify(allModes);
process.env.PULLFROG_PAYLOAD = JSON.stringify(ctx.payload); process.env.PULLFROG_PAYLOAD = JSON.stringify(ctx.payload);
@@ -352,7 +320,6 @@ async function runAgent(ctx: MainContext): Promise<AgentResult> {
return ctx.agent.run({ return ctx.agent.run({
payload: ctx.payload, payload: ctx.payload,
mcpServers: ctx.mcpServers, mcpServers: ctx.mcpServers,
githubInstallationToken: ctx.githubInstallationToken,
apiKey: ctx.apiKey, apiKey: ctx.apiKey,
cliPath: ctx.cliPath, cliPath: ctx.cliPath,
}); });
+7 -10
View File
@@ -2,7 +2,7 @@ import { Octokit } from "@octokit/rest";
import type { StandardSchemaV1 } from "@standard-schema/spec"; import type { StandardSchemaV1 } from "@standard-schema/spec";
import type { FastMCP, Tool } from "fastmcp"; import type { FastMCP, Tool } from "fastmcp";
import type { Payload } from "../external.ts"; import type { Payload } from "../external.ts";
import { parseRepoContext, type RepoContext } from "../utils/github.ts"; import { getGitHubInstallationToken, parseRepoContext, type RepoContext } from "../utils/github.ts";
export interface ToolResult { export interface ToolResult {
content: { content: {
@@ -28,15 +28,10 @@ export function getPayload(): Payload {
} }
export function getMcpContext(): McpContext { export function getMcpContext(): McpContext {
const githubInstallationToken = process.env.GITHUB_INSTALLATION_TOKEN;
if (!githubInstallationToken) {
throw new Error("GITHUB_INSTALLATION_TOKEN environment variable is required");
}
return { return {
...parseRepoContext(), ...parseRepoContext(),
octokit: new Octokit({ octokit: new Octokit({
auth: githubInstallationToken, auth: getGitHubInstallationToken(),
}), }),
payload: getPayload(), payload: getPayload(),
}; };
@@ -56,9 +51,10 @@ export const addTools = (server: FastMCP, tools: Tool<any, any>[]) => {
return server; return server;
}; };
export const contextualize = export const contextualize = <T>(
<T>(executor: (params: T, ctx: McpContext) => Promise<Record<string, any>>) => executor: (params: T, ctx: McpContext) => Promise<Record<string, any>>
async (params: T): Promise<ToolResult> => { ) => {
return async (params: T): Promise<ToolResult> => {
try { try {
const ctx = getMcpContext(); const ctx = getMcpContext();
const result = await executor(params, ctx); const result = await executor(params, ctx);
@@ -67,6 +63,7 @@ export const contextualize =
return handleToolError(error); return handleToolError(error);
} }
}; };
};
const handleToolSuccess = (data: Record<string, any>): ToolResult => { const handleToolSuccess = (data: Record<string, any>): ToolResult => {
return { return {
+1 -88
View File
@@ -3,8 +3,7 @@
*/ */
import { spawnSync } from "node:child_process"; import { spawnSync } from "node:child_process";
import { appendFileSync, existsSync } from "node:fs"; import { existsSync } from "node:fs";
import { join } from "node:path";
import * as core from "@actions/core"; import * as core from "@actions/core";
import { table } from "table"; import { table } from "table";
@@ -325,41 +324,8 @@ export const log = {
log.info(output.trimEnd()); log.info(output.trimEnd());
}, },
/**
* Log MCP tool call information to mcpLog.txt in the temp directory
*/
toolCallToFile: ({
toolName,
request,
result,
error,
}: {
toolName: string;
request: unknown;
result?: string;
error?: string;
}): void => {
const logPath = getMcpLogPath();
const params: Parameters<typeof formatToolCall>[0] = { toolName, request };
if (error) {
params.error = error;
} else if (result) {
params.result = result;
}
const logEntry = formatToolCall(params);
appendFileSync(logPath, logEntry, "utf-8");
},
}; };
/**
* Get the path to the MCP log file in the temp directory
*/
function getMcpLogPath(): string {
const tempDir = process.env.PULLFROG_TEMP_DIR!;
return join(tempDir, "mcpLog.txt");
}
/** /**
* Format a value as JSON, using compact format for simple values and pretty-printed for complex ones * Format a value as JSON, using compact format for simple values and pretty-printed for complex ones
*/ */
@@ -385,59 +351,6 @@ export function formatIndentedField(label: string, content: string): string {
return formatted; return formatted;
} }
/**
* Format the input field for a tool call
*/
function formatToolInput(request: unknown): string {
const requestFormatted = formatJsonValue(request);
if (requestFormatted === "{}") {
return "";
}
return formatIndentedField("input", requestFormatted);
}
/**
* Format the result field for a tool call, parsing JSON if possible
*/
function formatToolResult(result: string): string {
try {
const parsed = JSON.parse(result);
const formatted = formatJsonValue(parsed);
return formatIndentedField("result", formatted);
} catch {
// Not JSON, display as-is
return formatIndentedField("result", result);
}
}
/**
* Format a complete tool call entry with tool name, input, result, and error
*/
function formatToolCall({
toolName,
request,
result,
error,
}: {
toolName: string;
request: unknown;
result?: string;
error?: string;
}): string {
let logEntry = `${toolName}\n`;
logEntry += formatToolInput(request);
if (error) {
logEntry += formatIndentedField("error", error);
} else if (result) {
logEntry += formatToolResult(result);
}
logEntry += "\n";
return logEntry;
}
/** /**
* Finds a CLI executable path by checking if it's installed globally * Finds a CLI executable path by checking if it's installed globally
* @param name The name of the CLI executable to find * @param name The name of the CLI executable to find
+20 -4
View File
@@ -241,21 +241,37 @@ async function acquireNewToken(): Promise<string> {
} }
} }
// Store token in memory instead of process.env
let githubInstallationToken: string | undefined;
/** /**
* Setup GitHub installation token for the action * Setup GitHub installation token for the action
*/ */
export async function setupGitHubInstallationToken(): Promise<string> { export async function setupGitHubInstallationToken(): Promise<string> {
const acquiredToken = await acquireNewToken(); const acquiredToken = await acquireNewToken();
core.setSecret(acquiredToken); core.setSecret(acquiredToken);
process.env.GITHUB_INSTALLATION_TOKEN = acquiredToken; githubInstallationToken = acquiredToken;
return acquiredToken; return acquiredToken;
} }
/** /**
* Revoke GitHub installation token * Get the GitHub installation token from memory
*/ */
export async function revokeInstallationToken(token: string): Promise<void> { export function getGitHubInstallationToken(): string {
if (!githubInstallationToken) {
throw new Error("GitHub installation token not set. Call setupGitHubInstallationToken first.");
}
return githubInstallationToken;
}
export async function revokeGitHubInstallationToken(): Promise<void> {
if (!githubInstallationToken) {
return;
}
const token = githubInstallationToken;
githubInstallationToken = undefined;
const apiUrl = process.env.GITHUB_API_URL || "https://api.github.com"; const apiUrl = process.env.GITHUB_API_URL || "https://api.github.com";
try { try {