export interface Config { /** Port for the HTTP server */ port: number; /** Coder API base URL (e.g. https://coder.samson.media) */ coderUrl: string; /** Coder API session token */ coderToken: string; /** Coder template ID for workspaces */ coderTemplateId: string; /** Gitea API token for the dev bot account */ giteaDevToken: string; /** Gitea API token for the review bot account */ giteaReviewToken: string; /** Anthropic API key passed to Claude Code in workspaces (fallback) */ anthropicApiKey: string; /** Claude Code OAuth token for Max subscription (preferred) */ claudeOauthToken: string; /** Username of the dev bot (to filter out self-replies) */ botDevUsername: string; /** Username of the review bot (to filter out self-replies) */ botReviewUsername: string; /** Gitea base URL (e.g. https://gitea.samson.media) */ giteaUrl: string; /** Base URL for task-complete callbacks (e.g. https://sdlc.samson.media) */ callbackUrl: string; /** Optional webhook secret for verifying Gitea signatures */ webhookSecret?: string; /** Redis connection URL (e.g. redis://redis:6379) */ redisUrl: string; /** Max concurrent workspace creations (default: 2) */ queueConcurrency: number; /** Max concurrent cleanup jobs (default: 5) */ cleanupConcurrency: number; /** Minutes before a workspace is considered stale (default: 120) */ staleWorkspaceMinutes: number; } function required(name: string): string { const value = process.env[name]; if (!value) { throw new Error(`Missing required environment variable: ${name}`); } return value; } export function loadConfig(): Config { return { port: parseInt(process.env.PORT || "3000", 10), coderUrl: required("CODER_URL"), coderToken: required("CODER_TOKEN"), coderTemplateId: required("CODER_TEMPLATE_ID"), giteaDevToken: required("GITEA_DEV_TOKEN"), giteaReviewToken: required("GITEA_REVIEW_TOKEN"), anthropicApiKey: process.env.ANTHROPIC_API_KEY || "", claudeOauthToken: process.env.CLAUDE_OAUTH_TOKEN || "", botDevUsername: process.env.BOT_DEV_USERNAME || "claude-dev", botReviewUsername: process.env.BOT_REVIEW_USERNAME || "claude-review", giteaUrl: process.env.GITEA_URL || "https://gitea.samson.media", callbackUrl: required("CALLBACK_URL"), webhookSecret: process.env.WEBHOOK_SECRET, redisUrl: required("REDIS_URL"), queueConcurrency: parseInt(process.env.QUEUE_CONCURRENCY || "2", 10), cleanupConcurrency: parseInt(process.env.CLEANUP_CONCURRENCY || "5", 10), staleWorkspaceMinutes: parseInt(process.env.STALE_WORKSPACE_MINUTES || "120", 10), }; }