Files
shockbot/utils/runContext.ts
T
Colin McDonnell ba1966f17c feat: encrypted account-level secrets (#501)
* feat: add encrypted account-level secrets with UI for adding API keys

Adds AccountSecret model with AES-256-GCM encryption, API routes for
CRUD, "Add secret" button in model costs section, and injects decrypted
secrets into action env (YAML secrets take precedence).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: repo secrets, sidebar icons, lazy learnings history

- add repo-level secrets with inheritance from org secrets
- add icons to console sidebar sections
- fix learnings history modal: lazy fetch with hover prefetch,
  strip content from list response, load content per-expansion

Made-with: Cursor

* add input validation bounds for secrets and fix client-side name filter

Made-with: Cursor

* refactor: migrate all client-side data fetching to TanStack Query

Replace manual useState/useEffect/fetch patterns and the custom
usePolling hook with useQuery, useInfiniteQuery, and useMutation
across the entire frontend for consistent caching, background
refetching, and reactive invalidation.

- ActiveWorkflowRunsSection: useQuery + refetchInterval
- WorkflowRunHistory: useInfiniteQuery + polling query
- LearningsSection: useQuery per revision (lazy)
- FlagsSettings: self-contained useQuery + useMutation
- SecretsCard: useMutation for delete
- AddWorkflowButton, VerifyWorkflowButton: useMutation
- EmailSignupForm, email-waitlist: useMutation
- providers.tsx: enable refetchOnWindowFocus
- Delete usePolling.ts (no remaining consumers)

Made-with: Cursor

* address PR review: squash migrations, rename accountSecrets → dbSecrets

Squash the two separate secrets migrations into a single migration.
Rename the wire format field from accountSecrets to dbSecrets since
it now carries merged account + repo secrets.

Made-with: Cursor

* fix: update proxyKeys.ts imports after cache.ts -> yes package migration

Made-with: Cursor

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-31 05:02:38 +00:00

112 lines
2.7 KiB
TypeScript

import type { PushPermission, ShellPermission } from "../external.ts";
import { apiFetch } from "./apiFetch.ts";
import type { RepoContext } from "./github.ts";
export interface Mode {
id: string;
name: string;
description: string;
prompt: string;
}
export interface RepoSettings {
model: string | null;
modes: Mode[];
setupScript: string | null;
postCheckoutScript: string | null;
prepushScript: string | null;
push: PushPermission;
shell: ShellPermission;
prApproveEnabled: boolean;
modeInstructions: Record<string, string>;
learnings: string | null;
}
export interface RunContext {
settings: RepoSettings;
apiToken: string;
oss: boolean;
proxyModel?: string | undefined;
dbSecrets?: Record<string, string> | undefined;
}
const defaultSettings: RepoSettings = {
model: null,
modes: [],
setupScript: null,
postCheckoutScript: null,
prepushScript: null,
push: "restricted",
shell: "restricted",
prApproveEnabled: false,
modeInstructions: {},
learnings: null,
};
const defaultRunContext: RunContext = {
settings: defaultSettings,
apiToken: "",
oss: false,
};
/**
* 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 timeoutMs = 30000;
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await apiFetch({
path: `/api/repo/${params.repoContext.owner}/${params.repoContext.name}/run-context`,
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;
oss?: boolean;
proxyModel?: string;
dbSecrets?: Record<string, string>;
} | null;
if (data === null) {
return defaultRunContext;
}
return {
settings: {
...defaultSettings,
...data.settings,
modes: data.settings?.modes ?? [],
setupScript: data.settings?.setupScript ?? null,
postCheckoutScript: data.settings?.postCheckoutScript ?? null,
prepushScript: data.settings?.prepushScript ?? null,
},
apiToken: data.apiToken,
oss: data.oss ?? false,
proxyModel: data.proxyModel,
dbSecrets: data.dbSecrets,
};
} catch {
clearTimeout(timeoutId);
return defaultRunContext;
}
}