import { createHash } from "node:crypto"; import { chmodSync, existsSync, mkdirSync, readFileSync, realpathSync, renameSync, writeFileSync } from "node:fs"; import os from "node:os"; import path from "node:path"; export type MachineDataKind = "train" | "traces" | "observed" | "rejected"; export type MachineSettings = { autoTrace: boolean; }; const DEFAULT_SETTINGS: MachineSettings = { autoTrace: false, }; const DATA_FILES: Record = { train: "train.jsonl", traces: "traces.jsonl", observed: "observed.jsonl", rejected: "rejected.jsonl", }; export function defaultMachineDataBaseDir( _platform = process.platform, _env: Record = process.env, home = os.homedir(), ): string { return path.join(home, ".machine"); } export function machineDataBaseDir(cwd: string): string { const configured = process.env.MACHINE_DATA_DIR?.trim(); return configured ? path.resolve(cwd, configured) : defaultMachineDataBaseDir(); } export function canonicalProjectPath(cwd: string): string { const absolute = path.resolve(cwd); try { return realpathSync.native(absolute); } catch { // The path may not exist yet (fresh project); the resolved path is the // best key available, and it still agrees once the directory exists. return absolute; } } export function machineProjectKey(cwd: string): string { // Canonicalize so the same checkout reached through a symlinked path (very // common on macOS, where /tmp is /private/tmp and os.tmpdir() returns the // /var/folders spelling) maps to one store instead of two. const canonical = canonicalProjectPath(cwd); const name = path.basename(canonical) .toLowerCase() .replace(/[^a-z0-9._-]+/g, "-") .replace(/^-+|-+$/g, "") .slice(0, 48) || "project"; const digest = createHash("sha256").update(canonical).digest("hex").slice(0, 12); return `${name}-${digest}`; } export function machineProjectDataDir(cwd: string): string { return path.join(machineDataBaseDir(cwd), machineProjectKey(cwd)); } export function machineDataFile(cwd: string, kind: MachineDataKind): string { return path.join(machineProjectDataDir(cwd), DATA_FILES[kind]); } export function machineSettingsFile(cwd: string): string { return path.join(machineProjectDataDir(cwd), "settings.json"); } export function ensureMachineDataDir(cwd: string): string { const dir = machineProjectDataDir(cwd); mkdirSync(dir, { recursive: true, mode: 0o700 }); hardenMachinePath(dir, 0o700); return dir; } export function hardenMachinePath(filePath: string, mode: number): void { if (process.platform !== "win32") chmodSync(filePath, mode); } export function readMachineSettings(cwd: string): MachineSettings { const file = machineSettingsFile(cwd); if (!existsSync(file)) return { ...DEFAULT_SETTINGS }; try { const parsed = JSON.parse(readFileSync(file, "utf8")) as Partial; return { autoTrace: parsed.autoTrace === true, }; } catch { return { ...DEFAULT_SETTINGS }; } } export function writeMachineSettings(cwd: string, settings: MachineSettings): void { const dir = ensureMachineDataDir(cwd); const target = path.join(dir, "settings.json"); const temporary = path.join(dir, `.settings-${process.pid}-${Date.now()}.tmp`); writeFileSync(temporary, `${JSON.stringify(settings, null, 2)}\n`, { encoding: "utf8", mode: 0o600 }); renameSync(temporary, target); hardenMachinePath(target, 0o600); }