import fs from "node:fs"; import path from "node:path"; import { randomBytes } from "node:crypto"; import { config as loadDotenv, parse as parseEnvFileContents } from "dotenv"; import { resolveConfigPath } from "./store.js"; const JWT_SECRET_ENV_KEY = "PAPERCLIP_AGENT_JWT_SECRET"; function resolveEnvFilePath() { return path.resolve(path.dirname(resolveConfigPath()), ".env"); } const loadedEnvFiles = new Set(); function isNonEmpty(value: unknown): value is string { return typeof value === "string" && value.trim().length > 0; } function parseEnvFile(contents: string) { try { return parseEnvFileContents(contents); } catch { return {}; } } function renderEnvFile(entries: Record) { const lines = [ "# Paperclip environment variables", "# Generated by `paperclip onboard`", ...Object.entries(entries).map(([key, value]) => `${key}=${value}`), "", ]; return lines.join("\n"); } export function resolveAgentJwtEnvFile(): string { return resolveEnvFilePath(); } export function loadAgentJwtEnvFile(filePath = resolveEnvFilePath()): void { if (loadedEnvFiles.has(filePath)) return; if (!fs.existsSync(filePath)) return; loadedEnvFiles.add(filePath); loadDotenv({ path: filePath, override: false, quiet: true }); } export function readAgentJwtSecretFromEnv(): string | null { loadAgentJwtEnvFile(); const raw = process.env[JWT_SECRET_ENV_KEY]; return isNonEmpty(raw) ? raw!.trim() : null; } export function readAgentJwtSecretFromEnvFile(filePath = resolveEnvFilePath()): string | null { if (!fs.existsSync(filePath)) return null; const raw = fs.readFileSync(filePath, "utf-8"); const values = parseEnvFile(raw); const value = values[JWT_SECRET_ENV_KEY]; return isNonEmpty(value) ? value!.trim() : null; } export function ensureAgentJwtSecret(): { secret: string; created: boolean } { const existingEnv = readAgentJwtSecretFromEnv(); if (existingEnv) { return { secret: existingEnv, created: false }; } const existingFile = readAgentJwtSecretFromEnvFile(); const secret = existingFile ?? randomBytes(32).toString("hex"); const created = !existingFile; if (!existingFile) { writeAgentJwtEnv(secret); } return { secret, created }; } export function writeAgentJwtEnv(secret: string, filePath = resolveEnvFilePath()): void { const dir = path.dirname(filePath); fs.mkdirSync(dir, { recursive: true }); const current = fs.existsSync(filePath) ? parseEnvFile(fs.readFileSync(filePath, "utf-8")) : {}; current[JWT_SECRET_ENV_KEY] = secret; fs.writeFileSync(filePath, renderEnvFile(current), { mode: 0o600, }); }