/** * JSONL file logger for peaks-loop. * * * Default-on file logging: every peaks-loop invocation writes a * structured log entry to `/.peaks/logs/peaks-loop-YYYY-MM-DD.log` * (UTC date stamp). One JSON object per line. * * Hard contracts enforced here: * - stdout is NEVER touched by the logger (G7). Use the returned * `LogLine` string and pipe it to the file only. * - The log dir is created lazily on first write (G1). * - File mode is 0o600 on POSIX (R3). Windows uses ACLs and the * 0o600 bit is a no-op there; the call is best-effort. * - All write paths go through `redact` so a leaked secret can * never reach disk (G6). * - The log level can be overridden via `PEAKS_LOG_LEVEL` (G3) and * `PEAKS_LOG_DATE_OVERRIDE` (AC2) for deterministic tests. * * No third-party deps (R1): `fs.appendFileSync` + `JSON.stringify` * is enough; pino/winston are NOT pulled in. */ import { dirname } from 'node:path'; export type LogLevel = 'debug' | 'info' | 'warn' | 'error'; export type LogEntry = { ts: string; level: LogLevel; command: string; msg: string; sessionId?: string; version?: string; batchId?: string; data?: Record; }; /** * Returns the canonical log directory. On Windows, `homedir()` is * `%USERPROFILE%`; on macOS / Linux, `$HOME`. The path is the * user-global `~/.peaks/logs/` — never inside a project tree (NG6). */ export declare function resolveLogDir(): string; /** * Returns the UTC date-stamped file name. `date` is injectable so * tests can pin the date without monkey-patching `Date`. */ export declare function buildLogFileName(date: Date): string; export type WriteLogOptions = { /** Override the clock for deterministic tests. Defaults to `new Date()`. */ now?: () => Date; /** * Override the log dir for tests. Defaults to `resolveLogDir()`. * Allows the test harness to redirect to a temp dir without * monkey-patching `os.homedir()`. */ dirOverride?: string; /** * Override the date used for the file name + entry `ts`. The PRD * AC2 contract is `PEAKS_LOG_DATE_OVERRIDE=YYYY-MM-DD`; the * program-level flag plumbing in `program.ts` reads it and passes * it down. The `now` option still drives the `ts` field, so the * caller MUST pass a matching `Date` for the AC2 invariant. */ dateOverride?: string; }; /** * Write a single JSONL log entry. Returns the absolute file path * the entry was written to, or `null` when the entry was filtered * out by the current log level. * * Failures inside the logger (mkdir, appendFile, chmod) are caught * and swallowed: a logger that takes down the CLI is worse than a * logger that drops a line. The swallow is intentional per the PRD * non-goals (NG1: no remote ship; the user only ever sees a file * they read directly). */ export declare function writeLogEntry(entry: LogEntry, opts?: WriteLogOptions): string | null; export type ReadLogOptions = { now?: () => Date; dirOverride?: string; dateOverride?: string; }; /** * Read the current day's log file and return all parseable JSON * entries. Malformed lines are skipped (the on-disk file is JSONL, * but a crash mid-write can leave a partial line; we don't want * that to break `peaks log tail`). */ export declare function readLogEntries(opts?: ReadLogOptions): LogEntry[]; /** * Print a log line to stderr IF the current process is in verbose * mode (`--verbose` / `-v` / `PEAKS_LOG_LEVEL=debug`). This is the * G3 opt-in channel: by default, stderr stays clean so the CLI's * `--json` envelope is not contaminated. * * Returns `true` when the line was written; `false` otherwise. The * caller can use the boolean to detect silent-failure patterns (a * `peaks log tail` that prints nothing when the user expected a * verbose stream). */ export declare function maybeWriteStderr(entry: LogEntry, opts: { verbose: boolean; }): boolean; export { dirname };