57 lines
1.9 KiB
TypeScript
57 lines
1.9 KiB
TypeScript
/*
|
|
* Veskslop daemon — persistent action log.
|
|
*
|
|
* Writes one JSON line per event: triggers, commands, replies, and errors.
|
|
* The file rotates at a size limit. Secret-looking strings are redacted.
|
|
*/
|
|
|
|
import { appendFileSync, existsSync, mkdirSync, readFileSync, renameSync, statSync } from "node:fs";
|
|
import { dirname } from "node:path";
|
|
|
|
import { redactValue } from "./communication.js";
|
|
|
|
export const ACTION_LOG_MAX_BYTES = 5 * 1024 * 1024;
|
|
|
|
export class ActionLog {
|
|
constructor(
|
|
private readonly file = process.env.VESKSLOP_ACTION_LOG ?? ".veskslop-actions.jsonl",
|
|
private readonly maxBytes = Number(process.env.VESKSLOP_ACTION_LOG_MAX_BYTES ?? ACTION_LOG_MAX_BYTES)
|
|
) {
|
|
try {
|
|
mkdirSync(dirname(this.file), { recursive: true });
|
|
} catch {
|
|
// the current directory works
|
|
}
|
|
}
|
|
|
|
append(entry: Record<string, unknown> & { type: string }) {
|
|
const line = JSON.stringify(redactValue({ timestamp: new Date().toISOString(), ...entry }));
|
|
|
|
try {
|
|
if (existsSync(this.file) && statSync(this.file).size + line.length + 1 > this.maxBytes) {
|
|
renameSync(this.file, `${this.file}.1`);
|
|
}
|
|
appendFileSync(this.file, line + "\n");
|
|
} catch (error) {
|
|
console.error("[action-log] write failed:", error);
|
|
}
|
|
}
|
|
|
|
read(limit = 100): unknown[] {
|
|
try {
|
|
if (!existsSync(this.file)) return [];
|
|
const lines = readFileSync(this.file, "utf8").split("\n").filter(Boolean);
|
|
return lines.slice(-limit).map(line => {
|
|
try {
|
|
return JSON.parse(line);
|
|
} catch {
|
|
return { type: "unparsed", line };
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error("[action-log] read failed:", error);
|
|
return [];
|
|
}
|
|
}
|
|
}
|