Spaces:
Paused
Paused
File size: 3,959 Bytes
b152fd5 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 | 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(configPath?: string) {
return path.resolve(path.dirname(resolveConfigPath(configPath)), ".env");
}
const loadedEnvFiles = new Set<string>();
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 formatEnvValue(value: string): string {
if (/^[A-Za-z0-9_./:@-]+$/.test(value)) {
return value;
}
return JSON.stringify(value);
}
function renderEnvFile(entries: Record<string, string>) {
const lines = [
"# Paperclip environment variables",
"# Generated by Paperclip CLI commands",
...Object.entries(entries).map(([key, value]) => `${key}=${formatEnvValue(value)}`),
"",
];
return lines.join("\n");
}
export function resolvePaperclipEnvFile(configPath?: string): string {
return resolveEnvFilePath(configPath);
}
export function resolveAgentJwtEnvFile(configPath?: string): string {
return resolveEnvFilePath(configPath);
}
export function loadPaperclipEnvFile(configPath?: string): void {
loadAgentJwtEnvFile(resolveEnvFilePath(configPath));
}
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(configPath?: string): string | null {
loadAgentJwtEnvFile(resolveEnvFilePath(configPath));
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(configPath?: string): { secret: string; created: boolean } {
const existingEnv = readAgentJwtSecretFromEnv(configPath);
if (existingEnv) {
return { secret: existingEnv, created: false };
}
const envFilePath = resolveEnvFilePath(configPath);
const existingFile = readAgentJwtSecretFromEnvFile(envFilePath);
const secret = existingFile ?? randomBytes(32).toString("hex");
const created = !existingFile;
if (!existingFile) {
writeAgentJwtEnv(secret, envFilePath);
}
return { secret, created };
}
export function writeAgentJwtEnv(secret: string, filePath = resolveEnvFilePath()): void {
mergePaperclipEnvEntries({ [JWT_SECRET_ENV_KEY]: secret }, filePath);
}
export function readPaperclipEnvEntries(filePath = resolveEnvFilePath()): Record<string, string> {
if (!fs.existsSync(filePath)) return {};
return parseEnvFile(fs.readFileSync(filePath, "utf-8"));
}
export function writePaperclipEnvEntries(entries: Record<string, string>, filePath = resolveEnvFilePath()): void {
const dir = path.dirname(filePath);
fs.mkdirSync(dir, { recursive: true });
fs.writeFileSync(filePath, renderEnvFile(entries), {
mode: 0o600,
});
}
export function mergePaperclipEnvEntries(
entries: Record<string, string>,
filePath = resolveEnvFilePath(),
): Record<string, string> {
const current = readPaperclipEnvEntries(filePath);
const next = {
...current,
...Object.fromEntries(
Object.entries(entries).filter(([, value]) => typeof value === "string" && value.trim().length > 0),
),
};
writePaperclipEnvEntries(next, filePath);
return next;
}
|