Files
shockbot/utils/runContext.ts
T
pullfrog[bot] 1d59fd3d21 feat: Lifecycle hooks (#219)
* flatten lifecycle hooks into RepoSettings string fields

replace the separate LifecycleHook model with setupScript and
postCheckoutScript string fields directly on RepoSettings. move the UI
into the Agent settings section alongside environment variables and
custom instructions. delete the standalone lifecycle-hooks API route,
component, and schema since the existing settings PATCH endpoint
handles the new fields automatically.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: pass env to lifecycle hook spawn so scripts can use package managers

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Colin McDonnell <colinmcd94@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-06 07:16:14 +00:00

103 lines
2.4 KiB
TypeScript

import type { AgentName, BashPermission, PushPermission, ToolPermission } from "../external.ts";
import type { RepoContext } from "./github.ts";
export interface Mode {
id: string;
name: string;
description: string;
prompt: string;
}
export interface RepoSettings {
defaultAgent: AgentName | null;
modes: Mode[];
setupScript: string | null;
postCheckoutScript: string | null;
repoInstructions: string;
web: ToolPermission;
search: ToolPermission;
push: PushPermission;
bash: BashPermission;
}
export interface RunContext {
settings: RepoSettings;
apiToken: string;
}
const defaultSettings: RepoSettings = {
defaultAgent: null,
modes: [],
setupScript: null,
postCheckoutScript: null,
repoInstructions: "",
web: "enabled",
search: "enabled",
push: "restricted",
bash: "restricted",
};
const defaultRunContext: RunContext = {
settings: defaultSettings,
apiToken: "",
};
/**
* fetch run context from Pullfrog API
* returns settings + API token for subsequent calls
* returns defaults if fetch fails
*/
export async function fetchRunContext(params: {
token: string;
repoContext: RepoContext;
}): Promise<RunContext> {
const apiUrl = process.env.API_URL || "https://pullfrog.com";
const timeoutMs = 30000;
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await fetch(
`${apiUrl}/api/repo/${params.repoContext.owner}/${params.repoContext.name}/run-context`,
{
method: "GET",
headers: {
Authorization: `Bearer ${params.token}`,
"Content-Type": "application/json",
},
signal: controller.signal,
}
);
clearTimeout(timeoutId);
if (!response.ok) {
return defaultRunContext;
}
const data = (await response.json()) as {
settings: RepoSettings | null;
apiToken: string;
} | null;
if (data === null) {
return defaultRunContext;
}
return {
settings: {
...defaultSettings,
...data.settings,
// ensure arrays are never undefined (API may omit new fields for existing repos)
modes: data.settings?.modes ?? [],
setupScript: data.settings?.setupScript ?? null,
postCheckoutScript: data.settings?.postCheckoutScript ?? null,
},
apiToken: data.apiToken,
};
} catch {
clearTimeout(timeoutId);
return defaultRunContext;
}
}