#!/usr/bin/env npx tsx /** * state/lib/env.ts -- Credential loader for snappy-os. * * Reads from .env.cache at the repo root (~/projects/snappy-os/.env.cache). * This is the canonical location; the kernel path * ~/.claude/skills/snappy-settings/.env.cache is a backwards-compat symlink * pointing back here so legacy kernel scripts keep working. * * Usage: * import { env } from "../lib/env.ts"; * const token = env("SLACK_BOT_TOKEN"); */ import { readFileSync, existsSync } from "fs"; import { join, dirname } from "path"; import { fileURLToPath } from "url"; const HERE = dirname(fileURLToPath(import.meta.url)); // state/lib → repo root const CACHE_PATH = join(HERE, "..", "..", ".env.cache"); let _cache: Record | null = null; function parseCache(): Record { if (_cache) return _cache; if (!existsSync(CACHE_PATH)) { console.error(`[snappy-os/env] .env.cache not found at ${CACHE_PATH}`); _cache = {}; return _cache; } const lines = readFileSync(CACHE_PATH, "utf-8").split("\n"); const result: Record = {}; for (const line of lines) { if (line.startsWith("#") || !line.includes("=")) continue; const eq = line.indexOf("="); const key = line.slice(0, eq).trim(); const val = line.slice(eq + 1).trim(); if (val && val !== "Not found.") result[key] = val; } _cache = result; return result; } export function env(key: string, required = true): string { const creds = parseCache(); const val = process.env[key] || creds[key]; if (!val && required) { throw new Error( `[snappy-os/env] Missing credential: ${key}. Create .env.cache at project root or symlink to your credential source: ln -s ${CACHE_PATH}`, ); } return val || ""; } export function loadAll(): Record { return { ...parseCache() }; } export function xano(): string { return env("XANO", false) || "https://xnwv-v1z6-dvnr.n7c.xano.io"; }