import { z } from "zod"; const Env = z.object({ NODE_ENV: z.enum(["development", "production", "test"]).default("development"), APP_BASE_URL: z.string().url().default("http://localhost:8470"), DATABASE_URL: z.string().min(1), ARTIFACT_ROOT: z.string().min(1).default("./data/artifacts"), SESSION_SECRET: z.string().min(32, "SESSION_SECRET must be at least 32 characters"), ADMIN_BOOTSTRAP_USERNAME: z.string().min(1).default("admin"), ADMIN_BOOTSTRAP_PASSWORD: z.string().optional(), WORKER_ENROLLMENT_SECRET: z.string().min(16).optional(), INTERNAL_API_ALLOWED_CIDRS: z.string().default("127.0.0.1/32,::1/128,192.168.2.0/24"), TRUST_PROXY: z.coerce.number().int().min(0).max(1).default(0), LOG_LEVEL: z.string().default("info"), SCHEDULER_TICK_MS: z.coerce.number().int().min(500).default(5000), LEASE_TTL_SECONDS: z.coerce.number().int().min(30).default(120), DEFAULT_MAX_BUDGET_USD: z.coerce.number().positive().default(25), DEFAULT_MAX_RUNTIME_MINUTES: z.coerce.number().int().positive().default(240), PUBLISHING_ENABLED: z.coerce.number().int().min(0).max(1).default(1), DISPATCH_ENABLED: z.coerce.number().int().min(0).max(1).default(1), STAGING_MODE: z.coerce.number().int().min(0).max(1).default(0), ANTHROPIC_MODEL: z.string().default("claude-opus-5-5"), ANTHROPIC_REVIEWER_MODEL: z.string().default("claude-opus-5-5"), }); export type Env = z.infer; let cached: Env | undefined; export function getEnv(): Env { if (cached) return cached; const parsed = Env.safeParse(process.env); if (!parsed.success) { const msg = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; "); throw new Error(`invalid environment: ${msg}`); } cached = parsed.data; return cached; } export function resetEnvCache(): void { cached = undefined; }