This repository has no description
1

Configure Feed

Select the types of activity you want to include in your feed.

at main 3.5 kB View raw
1// Config = committed loup.config.json (non-secret: target repo, branch, repo root) 2// + .env (secret: handle, app password). Both read from CWD, falling back to the 3// package dir. repoRoot defaults to the CWD — so `loup serve` run inside a repo 4// operates on that repo. 5 6import { existsSync, readFileSync } from "node:fs"; 7import { dirname, resolve } from "node:path"; 8import { fileURLToPath } from "node:url"; 9 10const PKG_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), ".."); 11 12export interface Config { 13 live: boolean; 14 service: string; 15 handle: string | undefined; 16 appPassword: string | undefined; 17 targetRepoDid: string | undefined; 18 targetRepoName: string; 19 targetBranch: string; 20 repoRoot: string; 21 /** Source files/dirs (repo-relative) loup feeds the model so it can locate + fix issues. */ 22 sourcePaths: string[]; 23 port: number; 24 anthropicKey: string | undefined; 25 /** Vision model. Override with LOUP_MODEL. */ 26 model: string; 27 /** When true, skip the vision pass. */ 28 visionOff: boolean; 29 /** Offline canned demo (`--demo`): replay the known-good PRs, no LLM/network. */ 30 demo: boolean; 31} 32 33function expandHome(p: string): string { 34 return p.startsWith("~/") ? resolve(process.env.HOME ?? "", p.slice(2)) : resolve(p); 35} 36 37function loadEnvFile(): void { 38 for (const dir of [process.cwd(), PKG_ROOT]) { 39 const p = resolve(dir, ".env"); 40 if (!existsSync(p)) continue; 41 for (const raw of readFileSync(p, "utf8").split("\n")) { 42 const line = raw.trim(); 43 if (!line || line.startsWith("#")) continue; 44 const i = line.indexOf("="); 45 if (i === -1) continue; 46 const k = line.slice(0, i).trim(); 47 const v = line.slice(i + 1).trim(); 48 if (k && process.env[k] === undefined) process.env[k] = v; 49 } 50 break; 51 } 52} 53 54function loadJsonConfig(): Record<string, unknown> { 55 for (const dir of [process.cwd(), PKG_ROOT]) { 56 const p = resolve(dir, "loup.config.json"); 57 if (existsSync(p)) return JSON.parse(readFileSync(p, "utf8")); 58 } 59 return {}; 60} 61 62export function loadConfig(): Config { 63 loadEnvFile(); 64 const j = loadJsonConfig(); 65 const env = process.env; 66 const repoRoot = expandHome((env.LOUP_REPO_ROOT ?? (j.repoRoot as string)) || process.cwd()); 67 return { 68 live: env.LOUP_LIVE === "1" || env.LOUP_LIVE === "true", 69 service: (j.service as string) ?? "https://tngl.sh", 70 handle: env.TANGLED_HANDLE, 71 appPassword: env.TANGLED_APP_PASSWORD, 72 targetRepoDid: env.TANGLED_TARGET_REPO_DID ?? (j.targetRepoDid as string), 73 targetRepoName: (j.targetRepo as string) ?? "unknown/repo", 74 targetBranch: (j.targetBranch as string) ?? "main", 75 repoRoot, 76 sourcePaths: Array.isArray(j.sourcePaths) ? (j.sourcePaths as string[]) : [], 77 port: Number(env.PORT ?? j.port ?? 4319), 78 anthropicKey: env.ANTHROPIC_API_KEY, 79 model: env.LOUP_MODEL ?? (j.model as string) ?? "claude-opus-4-8", 80 visionOff: env.LOUP_NO_VISION === "1" || env.LOUP_NO_VISION === "true", 81 demo: env.LOUP_DEMO === "1" || env.LOUP_DEMO === "true", 82 }; 83} 84 85// pds.ts (copied) imports resolveRepoRoot; keep a compatible shim. 86export function resolveRepoRoot(cfg: Config, _fallback: string): string { 87 return cfg.repoRoot; 88} 89 90export function canGoLive(c: Config): { ok: boolean; missing: string[] } { 91 const missing: string[] = []; 92 if (!c.handle) missing.push("TANGLED_HANDLE"); 93 if (!c.appPassword) missing.push("TANGLED_APP_PASSWORD"); 94 if (!c.targetRepoDid) missing.push("targetRepoDid"); 95 return { ok: missing.length === 0, missing }; 96}