src / auditLog.ts
import * as fs from "node:fs/promises";
import * as path from "node:path";
import { BACKUP_DIR_NAME } from "./pathGuard";
const LOG_HEADER = `# Activity log
Append-only record of mutations performed by LM Studio plugins on this root.
One line per event:
\`<ISO timestamp> [<kind>] <subject> — <message>\`
`;
export function logPathForRoot(root: string): string {
return path.join(root, BACKUP_DIR_NAME, "log.md");
}
export async function appendAudit(
root: string,
kind: string,
subject: string,
message: string,
): Promise<void> {
const logPath = logPathForRoot(root);
const ts = new Date().toISOString();
const safeSubject = subject.replace(/\n/g, " ").trim();
const safeMessage = message.replace(/\n/g, " ").trim();
const line = `${ts} [${kind}] ${safeSubject} — ${safeMessage}\n`;
await fs.mkdir(path.dirname(logPath), { recursive: true });
try {
await fs.access(logPath);
} catch {
await fs.writeFile(logPath, LOG_HEADER, "utf-8");
}
await fs.appendFile(logPath, line, "utf-8");
}
export async function readAuditTail(root: string, n: number): Promise<string[]> {
const logPath = logPathForRoot(root);
try {
const content = await fs.readFile(logPath, "utf-8");
const lines = content.split("\n").filter((l) => /^\d{4}-\d{2}-\d{2}T/.test(l));
return lines.slice(-n);
} catch {
return [];
}
}