UNPKG

agentcrumbs

Version:
145 lines 4.11 kB
import fs from "node:fs"; import path from "node:path"; import os from "node:os"; const DEFAULT_BASE = path.join(os.homedir(), ".agentcrumbs"); const DEFAULT_FILE = "crumbs.jsonl"; const MAX_FILE_SIZE = 50 * 1024 * 1024; // 50MB before rotation export class CrumbStore { filePath; fd; constructor(dir) { const storeDir = dir ?? DEFAULT_BASE; if (!fs.existsSync(storeDir)) { fs.mkdirSync(storeDir, { recursive: true }); } this.filePath = path.join(storeDir, DEFAULT_FILE); } static forApp(app, baseDir) { const base = baseDir ?? DEFAULT_BASE; return new CrumbStore(path.join(base, app)); } static listApps(baseDir) { const base = baseDir ?? DEFAULT_BASE; try { const entries = fs.readdirSync(base, { withFileTypes: true }); return entries .filter((e) => e.isDirectory()) .map((e) => e.name) .sort(); } catch { return []; } } static readAllApps(baseDir) { const apps = CrumbStore.listApps(baseDir); const allCrumbs = []; for (const app of apps) { const store = CrumbStore.forApp(app, baseDir); allCrumbs.push(...store.readAll()); } // Also read legacy flat file if it exists const base = baseDir ?? DEFAULT_BASE; const legacyPath = path.join(base, DEFAULT_FILE); try { if (fs.existsSync(legacyPath)) { const legacy = new CrumbStore(base); const legacyCrumbs = legacy.readAll(); // Tag legacy crumbs missing app field for (const crumb of legacyCrumbs) { if (!crumb.app) crumb.app = "unknown"; allCrumbs.push(crumb); } } } catch { // ignore } allCrumbs.sort((a, b) => a.ts.localeCompare(b.ts)); return allCrumbs; } ensureFd() { if (this.fd === undefined) { this.fd = fs.openSync(this.filePath, "a"); } return this.fd; } append(crumb) { const line = JSON.stringify(crumb) + "\n"; fs.writeSync(this.ensureFd(), line); this.maybeRotate(); } appendRaw(line) { fs.writeSync(this.ensureFd(), line.endsWith("\n") ? line : line + "\n"); this.maybeRotate(); } maybeRotate() { try { const stat = fs.fstatSync(this.ensureFd()); if (stat.size > MAX_FILE_SIZE) { this.rotate(); } } catch { // ignore } } rotate() { if (this.fd !== undefined) { fs.closeSync(this.fd); this.fd = undefined; } const rotated = this.filePath + ".1"; try { if (fs.existsSync(rotated)) fs.unlinkSync(rotated); fs.renameSync(this.filePath, rotated); } catch { // ignore rotation errors } } getFilePath() { return this.filePath; } readAll() { try { const content = fs.readFileSync(this.filePath, "utf-8"); return content .split("\n") .filter(Boolean) .map((line) => { try { return JSON.parse(line); } catch { return null; } }) .filter((c) => c !== null); } catch { return []; } } clear() { if (this.fd !== undefined) { fs.closeSync(this.fd); this.fd = undefined; } try { fs.writeFileSync(this.filePath, ""); } catch { // ignore } } close() { if (this.fd !== undefined) { fs.closeSync(this.fd); this.fd = undefined; } } } //# sourceMappingURL=store.js.map