@remnic/capture-screen 9.65.7 → 9.66.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -871,7 +871,7 @@ function helperPackageName(platform = process.platform, arch = process.arch) {
|
|
|
871
871
|
return `@remnic/capture-native-${platform}-${arch}`;
|
|
872
872
|
}
|
|
873
873
|
function installHint(pkg) {
|
|
874
|
-
return `native capture helper (${pkg}) is not available on this install
|
|
874
|
+
return `native capture helper (${pkg}) is not available on this install. Run \`npm install ${pkg}\` or build the Swift helper from source (packages/capture-native-darwin-helper) and set REMNIC_CAPTURE_HELPER_BIN to the binary`;
|
|
875
875
|
}
|
|
876
876
|
function isModuleNotFound(err) {
|
|
877
877
|
const code = err?.code;
|
|
@@ -2157,4 +2157,4 @@ export {
|
|
|
2157
2157
|
superviseReplay,
|
|
2158
2158
|
runCapture
|
|
2159
2159
|
};
|
|
2160
|
-
//# sourceMappingURL=chunk-
|
|
2160
|
+
//# sourceMappingURL=chunk-ONMPW4PH.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/axtree.ts","../src/simhash.ts","../src/dedup.ts","../src/denylist.ts","../src/errors.ts","../src/redact.ts","../src/capture.ts","../src/constants.ts","../src/config.ts","../src/util.ts","../src/coerce.ts","../src/control.ts","../src/token.ts","../src/validate.ts","../src/daemon.ts","../src/paths.ts","../src/helper.ts","../src/live.ts","../src/replay.ts","../src/daywindow.ts","../src/spool.ts","../src/cli.ts","../src/scheduler.ts"],"sourcesContent":["/**\n * Accessibility-tree text extraction. Walks a macOS AX-tree JSON snapshot and\n * concatenates the visible text, with three safety filters baked in:\n *\n * - AXSecureTextField nodes are skipped entirely (never read a password box),\n * including their subtree.\n * - Off-screen nodes (`offScreen: true`) are skipped with their subtree — text\n * the user cannot see is not \"on screen\".\n * - Traversal is bounded to `maxNodes` visited nodes, so a pathological tree\n * cannot exhaust memory/CPU; the result is flagged `truncated` when the cap\n * is hit.\n *\n * The shape is intentionally permissive: real AX dumps carry many roles and the\n * text can live on any of value/title/description/label. Unknown fields are\n * ignored.\n */\n\nexport const SECURE_ROLE = \"AXSecureTextField\";\n\nexport interface AxNode {\n role?: string;\n value?: string;\n title?: string;\n description?: string;\n label?: string;\n offScreen?: boolean;\n children?: AxNode[];\n}\n\nexport interface AxExtractResult {\n text: string;\n /** Nodes actually visited (bounded by maxNodes). */\n nodes: number;\n /** True when the maxNodes cap stopped traversal before the tree was exhausted. */\n truncated: boolean;\n}\n\nfunction nodeText(node: AxNode): string {\n const pieces: string[] = [];\n for (const field of [node.value, node.title, node.description, node.label]) {\n if (typeof field === \"string\" && field.trim().length > 0) pieces.push(field.trim());\n }\n return pieces.join(\" \");\n}\n\n/**\n * Extract visible, non-secure text from an AX tree. Iterative DFS with an\n * explicit stack so a deep tree cannot overflow the call stack, and a visited\n * counter that enforces the node cap.\n */\nexport function extractAxText(root: AxNode, maxNodes: number): AxExtractResult {\n const lines: string[] = [];\n const stack: AxNode[] = [root];\n let visited = 0;\n let truncated = false;\n while (stack.length > 0) {\n if (visited >= maxNodes) {\n truncated = true;\n break;\n }\n const node = stack.pop() as AxNode;\n visited += 1;\n if (node.offScreen === true) continue;\n if (node.role === SECURE_ROLE) continue;\n const text = nodeText(node);\n if (text.length > 0) lines.push(text);\n if (Array.isArray(node.children)) {\n // Push in reverse so children are visited in document order.\n for (let i = node.children.length - 1; i >= 0; i--) stack.push(node.children[i]);\n }\n }\n return { text: lines.join(\"\\n\"), nodes: visited, truncated };\n}\n","/**\n * 64-bit word-shingle SimHash for near-duplicate screen-text detection.\n * Text is lower-cased and tokenized to Unicode letter/number runs (so CJK,\n * Cyrillic, and other non-ASCII scripts tokenize instead of collapsing to an\n * empty set), then shingled into overlapping 2-word grams. Each gram is hashed\n * with 64-bit FNV-1a; the signed\n * per-bit vote across all grams yields a 64-bit fingerprint whose Hamming\n * distance tracks textual similarity: identical text → distance 0, a small edit\n * → a small distance, unrelated text → a large distance. Everything is BigInt\n * so the full 64 bits are exact.\n */\n\nconst MASK64 = (1n << 64n) - 1n;\nconst FNV_OFFSET = 14695981039346656037n;\nconst FNV_PRIME = 1099511628211n;\nconst SHINGLE_SIZE = 2;\n\nfunction tokenize(text: string): string[] {\n return text.toLowerCase().match(/[\\p{L}\\p{N}]+/gu) ?? [];\n}\n\nfunction shingles(tokens: string[]): string[] {\n if (tokens.length < SHINGLE_SIZE) {\n return tokens.length > 0 ? [tokens.join(\" \")] : [];\n }\n const out: string[] = [];\n for (let i = 0; i + SHINGLE_SIZE <= tokens.length; i++) {\n out.push(tokens.slice(i, i + SHINGLE_SIZE).join(\" \"));\n }\n return out;\n}\n\n/** 64-bit FNV-1a over the UTF-16 code units of `s`. */\nfunction hash64(s: string): bigint {\n let h = FNV_OFFSET;\n for (let i = 0; i < s.length; i++) {\n h ^= BigInt(s.charCodeAt(i));\n h = (h * FNV_PRIME) & MASK64;\n }\n return h;\n}\n\n/** 64-bit SimHash fingerprint of `text` (0n for empty/whitespace-only text). */\nexport function simhash(text: string): bigint {\n const grams = shingles(tokenize(text));\n if (grams.length === 0) return 0n;\n const votes = new Array<number>(64).fill(0);\n for (const gram of grams) {\n const h = hash64(gram);\n for (let b = 0; b < 64; b++) {\n votes[b] += (h >> BigInt(b)) & 1n ? 1 : -1;\n }\n }\n let out = 0n;\n for (let b = 0; b < 64; b++) {\n if (votes[b] > 0) out |= 1n << BigInt(b);\n }\n return out;\n}\n\n/** Hamming distance between two 64-bit fingerprints (0..64). */\nexport function hammingDistance(a: bigint, b: bigint): number {\n let x = (a ^ b) & MASK64;\n let count = 0;\n while (x !== 0n) {\n count += Number(x & 1n);\n x >>= 1n;\n }\n return count;\n}\n\n/** Fixed-width 16-char hex rendering (wire/simhash column form). */\nexport function simhashToHex(h: bigint): string {\n return (h & MASK64).toString(16).padStart(16, \"0\");\n}\n\nexport function simhashFromHex(hex: string): bigint {\n return BigInt(`0x${hex}`) & MASK64;\n}\n","/**\n * Per-window near-duplicate suppression. Keyed by (app, windowTitle): for each\n * window we remember the last STORED snapshot's SimHash and capture instant. A\n * new snapshot of the same window is stored only when it is meaningfully\n * different (Hamming distance > threshold) OR enough time has elapsed since the\n * last store (ttlSeconds), so a long unchanging window is refreshed periodically\n * while a stream of near-identical scroll states collapses to a few rows.\n * Distinct windows never dedup against each other (independent cache entries).\n *\n * The clock is the snapshot's own capturedAt (passed in as ms), never a\n * wall-clock read — so replay/fixtures are deterministic.\n */\n\nimport { hammingDistance } from \"./simhash.js\";\n\ninterface Entry {\n hash: bigint;\n atMs: number;\n}\n\nexport class DedupCache {\n #last = new Map<string, Entry>();\n readonly #threshold: number;\n readonly #ttlSeconds: number;\n\n constructor(threshold: number, ttlSeconds: number) {\n this.#threshold = threshold;\n this.#ttlSeconds = ttlSeconds;\n }\n\n static #key(app: string, windowTitle: string): string {\n // NUL separator: app/title are arbitrary text, so a printable delimiter\n // could be forged by a title to alias a different (app,title) pair.\n return `${app}\\u0000${windowTitle}`;\n }\n\n /** Seed the last-stored fingerprint for a window (used to prime from the spool). */\n seed(app: string, windowTitle: string, hash: bigint, atMs: number): void {\n this.#last.set(DedupCache.#key(app, windowTitle), { hash, atMs });\n }\n\n /**\n * Decide whether a snapshot should be stored, updating the cache when it is.\n * First snapshot of a window always stores. A negative elapsed (out-of-order\n * capture) stores defensively rather than dropping data.\n */\n shouldStore(app: string, windowTitle: string, hash: bigint, atMs: number): boolean {\n const key = DedupCache.#key(app, windowTitle);\n const prev = this.#last.get(key);\n let store: boolean;\n if (prev === undefined) {\n store = true;\n } else {\n const elapsedSeconds = (atMs - prev.atMs) / 1000;\n store =\n elapsedSeconds < 0 ||\n elapsedSeconds >= this.#ttlSeconds ||\n hammingDistance(hash, prev.hash) > this.#threshold;\n }\n if (store) this.#last.set(key, { hash, atMs });\n return store;\n }\n}\n","/**\n * Capture-time deny-lists, checked FIRST — before any text extraction, hashing,\n * or spool write. A match records NOTHING (not even metadata): the snapshot is\n * dropped whole. Three independent lists, each glob/substring matched\n * case-insensitively: application name, window title, and browser URL. Built-in\n * defaults cover common secret managers and private-browsing windows; the\n * user's config entries are additive.\n */\n\n/** Secret managers whose windows must never be captured. */\nexport const DEFAULT_DENY_APPS: readonly string[] = [\"1Password*\", \"Bitwarden*\", \"KeePass*\"];\n\n/** Private/incognito window-title heuristics (browsers signal these in the title). */\nexport const DEFAULT_DENY_TITLES: readonly string[] = [\n \"*incognito*\",\n \"*private browsing*\",\n \"*inprivate*\",\n \"*private window*\",\n];\n\n/** No default URL denials — URL patterns are user-supplied (site-specific). */\nexport const DEFAULT_DENY_URLS: readonly string[] = [];\n\nexport interface DenyLists {\n apps: readonly string[];\n titles: readonly string[];\n urls: readonly string[];\n}\n\nexport interface DenyCandidate {\n app: string;\n windowTitle: string;\n browserUrl?: string | null;\n}\n\n/** Compile a `*`/`?` glob to an anchored, case-insensitive RegExp. */\nexport function globToRegExp(glob: string): RegExp {\n const escaped = glob.replace(/[.+^${}()|[\\]\\\\]/g, \"\\\\$&\").replace(/\\*/g, \".*\").replace(/\\?/g, \".\");\n return new RegExp(`^${escaped}$`, \"i\");\n}\n\n/** True when `value` matches any glob in `patterns` (case-insensitive). */\nexport function matchesAnyGlob(patterns: readonly string[], value: string): boolean {\n return patterns.some((pattern) => globToRegExp(pattern).test(value));\n}\n\nfunction firstMatch(patterns: readonly string[], value: string, kind: string): string | null {\n for (const pattern of patterns) {\n if (globToRegExp(pattern).test(value)) return `${kind}:${pattern}`;\n }\n return null;\n}\n\n/**\n * The first deny rule that fires for this candidate, or null. Built-in defaults\n * are always checked in addition to the user lists. The returned string names\n * the rule (`app:1Password*`, `title:*incognito*`, `url:...`) for the\n * `test-snapshot` diagnostic.\n */\nexport function matchDenyRule(candidate: DenyCandidate, lists: DenyLists): string | null {\n const appRule = firstMatch([...DEFAULT_DENY_APPS, ...lists.apps], candidate.app, \"app\");\n if (appRule !== null) return appRule;\n const titleRule = firstMatch([...DEFAULT_DENY_TITLES, ...lists.titles], candidate.windowTitle, \"title\");\n if (titleRule !== null) return titleRule;\n if (typeof candidate.browserUrl === \"string\" && candidate.browserUrl.length > 0) {\n const urlRule = firstMatch([...DEFAULT_DENY_URLS, ...lists.urls], candidate.browserUrl, \"url\");\n if (urlRule !== null) return urlRule;\n }\n return null;\n}\n","/**\n * Error taxonomy for @remnic/capture-screen.\n *\n * Two authored-message classes, mirroring @remnic/capture-audio: configuration\n * problems and caller-correctable input. Both carry operator-safe messages\n * (never foreign error text, never captured screen text, never credentials).\n * The HTTP layer maps CaptureInputError to 400; anything else is a backend\n * fault (500).\n */\n\n/** Config load/validation failure — surfaced loudly, never silently defaulted. */\nexport class CaptureConfigError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"CaptureConfigError\";\n }\n}\n\n/** Caller-correctable request/CLI input — maps to HTTP 400. */\nexport class CaptureInputError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"CaptureInputError\";\n }\n}\n","/**\n * Daemon-side redaction, applied to snapshot text BEFORE it is hashed or\n * written to the spool. Built-in patterns catch US SSNs and payment-card\n * numbers (13–19 digits, optionally space/dash grouped, Luhn-valid); the user's\n * `redactionPatterns` (regex source strings) are applied in addition. Every\n * match is replaced with a fixed placeholder so the redacted text is stable\n * (identical inputs dedup identically).\n */\n\nimport { CaptureConfigError } from \"./errors.js\";\n\nexport const REDACTION_PLACEHOLDER = \"[REDACTED]\";\n\nconst SSN_RE = /\\b\\d{3}-\\d{2}-\\d{4}\\b/g;\n/** Candidate card runs: 13–19 digits with optional single space/dash separators. */\nconst CARD_RE = /\\b(?:\\d[ -]?){13,19}\\b/g;\n\nfunction luhnValid(digits: string): boolean {\n let sum = 0;\n let double = false;\n for (let i = digits.length - 1; i >= 0; i--) {\n let d = digits.charCodeAt(i) - 48;\n if (double) {\n d *= 2;\n if (d > 9) d -= 9;\n }\n sum += d;\n double = !double;\n }\n return sum % 10 === 0;\n}\n\nfunction redactCards(text: string): string {\n return text.replace(CARD_RE, (match) => {\n const digits = match.replace(/[ -]/g, \"\");\n if (digits.length < 13 || digits.length > 19 || !luhnValid(digits)) return match;\n return REDACTION_PLACEHOLDER;\n });\n}\n\n/** Compile user regex source strings once; an invalid pattern fails loudly. */\nexport function compileRedactionPatterns(sources: readonly string[]): RegExp[] {\n return sources.map((source) => {\n try {\n return new RegExp(source, \"g\");\n } catch {\n throw new CaptureConfigError(`redactionPatterns: '${source}' is not a valid regular expression`);\n }\n });\n}\n\n/** Apply built-in (SSN, card) then user redactions to `text`. */\nexport function redactText(text: string, userPatterns: readonly RegExp[] = []): string {\n let out = text.replace(SSN_RE, REDACTION_PLACEHOLDER);\n out = redactCards(out);\n for (const pattern of userPatterns) {\n // Reset lastIndex: a shared global RegExp carries state between calls.\n pattern.lastIndex = 0;\n out = out.replace(pattern, REDACTION_PLACEHOLDER);\n }\n return out;\n}\n","/**\n * Capture-time processing pipeline (pure, hardware-free). A raw candidate —\n * frontmost app/window plus either an AX tree or pre-extracted text — is turned\n * into a decision: dropped by a deny rule, skipped (OCR unavailable / deduped),\n * or a fully-formed spool snapshot. The steps, in order:\n *\n * 1. Deny-lists FIRST — a match records NOTHING (not even metadata).\n * 2. Text: pre-extracted text is used as-is; otherwise the AX tree is walked\n * (secure fields + off-screen nodes excluded, bounded to maxNodes). A\n * terminal-class window, or an AX tree with no visible text, routes to the\n * OCR seam; when OCR is unavailable the snapshot is skipped (reflected in\n * health) rather than crashing.\n * 3. Redaction — SSN/card + user patterns, before hashing or storage.\n * 4. Dedup — per-window SimHash gate (threshold / TTL).\n * 5. Content hash — length-prefixed SHA-256 so control chars can't collide.\n *\n * The OCR step is a seam (an injected callback), so the daemon wires the native\n * helper while tests inject a fake without any macOS binary.\n */\n\nimport { createHash } from \"node:crypto\";\n\nimport { extractAxText, type AxNode } from \"./axtree.js\";\nimport { DedupCache } from \"./dedup.js\";\nimport { matchDenyRule, matchesAnyGlob } from \"./denylist.js\";\nimport { compileRedactionPatterns, redactText } from \"./redact.js\";\nimport { simhash, simhashToHex } from \"./simhash.js\";\nimport type { DaemonConfig } from \"./config.js\";\nimport type { DaemonSnapshot, SnapshotInput, TextSource } from \"./spool.js\";\n\n/** Terminal-class apps whose windows expose no useful AX text (route to OCR). */\nexport const DEFAULT_TERMINAL_APPS: readonly string[] = [\n \"Terminal\",\n \"iTerm2\",\n \"iTerm\",\n \"Alacritty\",\n \"kitty\",\n \"WezTerm\",\n \"Warp\",\n \"Hyper\",\n \"Konsole\",\n \"gnome-terminal*\",\n];\n\n/**\n * True when `app` is a terminal-class window (routes to OCR — terminals expose\n * no useful AX text). `includeDefaults` prepends DEFAULT_TERMINAL_APPS; pass\n * false when `terminalApps` is already the merged list.\n */\nexport function isTerminalApp(app: string, terminalApps: readonly string[], includeDefaults = true): boolean {\n const patterns = includeDefaults ? [...DEFAULT_TERMINAL_APPS, ...terminalApps] : terminalApps;\n return matchesAnyGlob(patterns, app);\n}\n\n/** A raw capture candidate before processing. Provide `text` OR `ax`. */\nexport interface CaptureCandidate {\n capturedAtUtc: string;\n app: string;\n windowTitle: string;\n browserUrl?: string | null;\n /** Pre-extracted text (skips AX walking); source defaults to \"ax\". */\n text?: string;\n textSource?: TextSource;\n /** Accessibility tree to extract from when `text` is absent. */\n ax?: AxNode;\n}\n\nexport type CaptureDecision =\n | { action: \"denied\"; rule: string }\n | { action: \"skipped\"; reason: \"ocr-unavailable\" | \"dedup\" }\n | { action: \"store\"; snapshot: SnapshotInput };\n\n/** OCR seam: returns extracted text for a candidate, or null when unavailable. */\nexport type OcrFn = (candidate: CaptureCandidate) => string | null;\n\ninterface ContentHashFields {\n capturedAtUtc: string;\n app: string;\n windowTitle: string;\n browserUrl: string | null;\n text: string;\n textSource: string;\n}\n\n/**\n * SHA-256 over length-prefixed fields so control characters (incl. NUL) in the\n * captured text cannot make two distinct snapshots collide — a collision would\n * silently drop a valid capture via the UNIQUE content_hash + INSERT OR IGNORE.\n */\nexport function contentHash(fields: ContentHashFields): string {\n const hash = createHash(\"sha256\");\n const parts = [fields.capturedAtUtc, fields.app, fields.windowTitle, fields.browserUrl ?? \"\", fields.text, fields.textSource];\n for (const field of parts) {\n hash.update(`${Buffer.byteLength(field)}:`).update(field);\n }\n return hash.digest(\"hex\");\n}\n\nexport class CaptureProcessor {\n readonly #denyApps: string[];\n readonly #denyTitles: string[];\n readonly #denyUrls: string[];\n readonly #terminalApps: string[];\n readonly #maxNodes: number;\n readonly #redaction: RegExp[];\n readonly #cache: DedupCache;\n readonly #ocr: OcrFn | undefined;\n\n constructor(config: DaemonConfig, ocr?: OcrFn) {\n this.#denyApps = config.denyApps;\n this.#denyTitles = config.denyTitles;\n this.#denyUrls = config.denyUrls;\n this.#terminalApps = [...DEFAULT_TERMINAL_APPS, ...config.terminalApps];\n this.#maxNodes = config.maxNodes;\n this.#redaction = compileRedactionPatterns(config.redactionPatterns);\n this.#cache = new DedupCache(config.simhashThreshold, config.dedupTtlSeconds);\n this.#ocr = ocr;\n }\n\n /** Seed the dedup cache from prior spool state so restarts don't re-store. */\n seed(app: string, windowTitle: string, simhashHex: string, capturedAtUtc: string): void {\n this.#cache.seed(app, windowTitle, BigInt(`0x${simhashHex}`), Date.parse(capturedAtUtc));\n }\n\n process(candidate: CaptureCandidate): CaptureDecision {\n const denyRule = matchDenyRule(\n { app: candidate.app, windowTitle: candidate.windowTitle, browserUrl: candidate.browserUrl },\n { apps: this.#denyApps, titles: this.#denyTitles, urls: this.#denyUrls },\n );\n if (denyRule !== null) return { action: \"denied\", rule: denyRule };\n\n const extracted = this.#extractText(candidate);\n if (extracted === null) return { action: \"skipped\", reason: \"ocr-unavailable\" };\n const { source } = extracted;\n const text = redactText(extracted.text, this.#redaction);\n\n const fingerprint = simhash(text);\n const atMs = Date.parse(candidate.capturedAtUtc);\n if (!this.#cache.shouldStore(candidate.app, candidate.windowTitle, fingerprint, atMs)) {\n return { action: \"skipped\", reason: \"dedup\" };\n }\n\n const browserUrl = candidate.browserUrl ?? null;\n return {\n action: \"store\",\n snapshot: {\n capturedAtUtc: candidate.capturedAtUtc,\n app: candidate.app,\n windowTitle: candidate.windowTitle,\n browserUrl,\n text,\n textSource: source,\n contentHash: contentHash({\n capturedAtUtc: candidate.capturedAtUtc,\n app: candidate.app,\n windowTitle: candidate.windowTitle,\n browserUrl,\n text,\n textSource: source,\n }),\n simhash: simhashToHex(fingerprint),\n },\n };\n }\n\n /** Resolve visible text + its source, or null when OCR was needed but unavailable. */\n #extractText(candidate: CaptureCandidate): { text: string; source: TextSource } | null {\n if (typeof candidate.text === \"string\") {\n return { text: candidate.text, source: candidate.textSource ?? \"ax\" };\n }\n const axText = candidate.ax === undefined ? \"\" : extractAxText(candidate.ax, this.#maxNodes).text;\n const needsOcr = isTerminalApp(candidate.app, this.#terminalApps, false) || axText.trim() === \"\";\n if (!needsOcr) return { text: axText, source: \"ax\" };\n const ocrText = this.#ocr === undefined ? null : this.#ocr(candidate);\n if (ocrText !== null && ocrText.trim() !== \"\") return { text: ocrText, source: \"ocr\" };\n return null;\n }\n}\n\nexport interface AppStat {\n app: string;\n seconds: number;\n snapshotCount: number;\n}\n\nexport interface DayStats {\n date: string;\n timezone: string;\n snapshotCount: number;\n totalSeconds: number;\n apps: AppStat[];\n}\n\n/**\n * Per-app time attribution for a day. Each snapshot is credited the gap to the\n * next snapshot (capped at maxDwellSeconds); the final snapshot contributes no\n * dwell (no following instant to bound it). Apps sort by seconds desc, then name.\n */\nexport function computeStats(\n snapshots: DaemonSnapshot[],\n date: string,\n timezone: string,\n maxDwellSeconds: number,\n): DayStats {\n const ordered = [...snapshots].sort((a, b) => {\n const at = Date.parse(a.capturedAtUtc);\n const bt = Date.parse(b.capturedAtUtc);\n if (at !== bt) return at - bt;\n return a.id - b.id;\n });\n const seconds = new Map<string, number>();\n const counts = new Map<string, number>();\n let totalSeconds = 0;\n for (let i = 0; i < ordered.length; i++) {\n const snap = ordered[i];\n counts.set(snap.app, (counts.get(snap.app) ?? 0) + 1);\n if (i + 1 < ordered.length) {\n const gap = (Date.parse(ordered[i + 1].capturedAtUtc) - Date.parse(snap.capturedAtUtc)) / 1000;\n const dwell = Math.max(0, Math.min(gap, maxDwellSeconds));\n seconds.set(snap.app, (seconds.get(snap.app) ?? 0) + dwell);\n totalSeconds += dwell;\n }\n }\n const apps: AppStat[] = [...counts.keys()]\n .map((app) => ({ app, seconds: seconds.get(app) ?? 0, snapshotCount: counts.get(app) ?? 0 }))\n .sort((a, b) => (b.seconds !== a.seconds ? b.seconds - a.seconds : a.app < b.app ? -1 : a.app > b.app ? 1 : 0));\n return { date, timezone, snapshotCount: ordered.length, totalSeconds, apps };\n}\n","/** Package-wide constants for @remnic/capture-screen. */\n\n/**\n * Reported by GET /v1/health. Kept in sync with package.json by the release\n * tooling; the health endpoint tolerates drift because the connector never\n * gates on an exact match (it reads `ok`).\n */\nexport const CAPTURE_SCREEN_VERSION = \"9.14.0\";\n\n/** Loopback default; capture is local-first (charter). */\nexport const DEFAULT_HOST = \"127.0.0.1\";\nexport const DEFAULT_PORT = 4341;\n\n/** Spool schema version, persisted in the `meta` table. */\nexport const SPOOL_SCHEMA_VERSION = 1;\n\n/** Upper bound for the snapshots `limit` query parameter. */\nexport const MAX_SNAPSHOTS_LIMIT = 500;\n/** Default page size when `limit` is omitted. */\nexport const DEFAULT_SNAPSHOTS_LIMIT = 100;\n\n/** Default capture-time processing knobs (all overridable in config). */\nexport const DEFAULT_SPOOL_RETENTION_DAYS = 14;\nexport const DEFAULT_SIMHASH_THRESHOLD = 10;\nexport const DEFAULT_DEDUP_TTL_SECONDS = 60;\n/** Two snapshots of the same window within this gap belong to one session. */\nexport const DEFAULT_SESSION_GAP_SECONDS = 300;\n/** AX-tree traversal cap (nodes) — bounds pathological accessibility trees. */\nexport const DEFAULT_MAX_NODES = 4000;\n/** Per-snapshot dwell cap for /v1/stats time attribution. */\nexport const DEFAULT_MAX_DWELL_SECONDS = 300;\n/** Live capture loop cadence (#1899 Part 1; all overridable in config). */\n/** How often the loop polls the frontmost AX snapshot for a change. */\nexport const DEFAULT_POLL_INTERVAL_MS = 1000;\n/** Foreground must be stable this long after a change before a snapshot is stored. */\nexport const DEFAULT_SETTLE_MS = 500;\n/** Re-sample an unchanging foreground at least this often (dedup drops repeats). */\nexport const DEFAULT_IDLE_FALLBACK_SECONDS = 30;\n","/**\n * Daemon config (`~/.remnic/capture-screen/screen.json`), created by\n * `remnic-capture-screen init`. Strict and loud: an absent field takes the\n * documented default, but a present-but-invalid value throws CaptureConfigError\n * (no silent defaulting). Ports are integers in [1, 65535]; string arrays\n * (deny-lists, terminal-app globs, redaction patterns) reject non-string\n * members.\n *\n * Deny-lists, terminal-app globs, and redaction patterns here are ADDITIVE to\n * the built-in defaults (see denylist.ts / redact.ts / capture.ts).\n */\n\nimport { readFileSync } from \"node:fs\";\n\nimport { coerceNumber, coerceStringArray } from \"./coerce.js\";\nimport {\n DEFAULT_IDLE_FALLBACK_SECONDS,\n DEFAULT_POLL_INTERVAL_MS,\n DEFAULT_SETTLE_MS,\n DEFAULT_DEDUP_TTL_SECONDS,\n DEFAULT_HOST,\n DEFAULT_MAX_DWELL_SECONDS,\n DEFAULT_MAX_NODES,\n DEFAULT_PORT,\n DEFAULT_SESSION_GAP_SECONDS,\n DEFAULT_SIMHASH_THRESHOLD,\n DEFAULT_SPOOL_RETENTION_DAYS,\n} from \"./constants.js\";\nimport { CaptureConfigError } from \"./errors.js\";\nimport { describeValue } from \"./util.js\";\n\nexport interface DaemonConfig {\n host: string;\n port: number;\n spoolRetentionDays: number;\n simhashThreshold: number;\n dedupTtlSeconds: number;\n sessionGapSeconds: number;\n maxNodes: number;\n maxDwellSeconds: number;\n /** Live capture loop: poll interval (ms) for foreground-change detection. */\n pollIntervalMs: number;\n /** Live capture loop: settle window (ms) after a foreground change. */\n settleMs: number;\n /** Live capture loop: idle re-sample cadence (seconds). */\n idleFallbackSeconds: number;\n /** Additive deny-list globs (checked in addition to the built-in defaults). */\n denyApps: string[];\n denyTitles: string[];\n denyUrls: string[];\n /** Additive terminal-class app globs (route to OCR). */\n terminalApps: string[];\n /** Additive user redaction regex source strings. */\n redactionPatterns: string[];\n}\n\nexport function defaultDaemonConfig(): DaemonConfig {\n return {\n host: DEFAULT_HOST,\n port: DEFAULT_PORT,\n spoolRetentionDays: DEFAULT_SPOOL_RETENTION_DAYS,\n simhashThreshold: DEFAULT_SIMHASH_THRESHOLD,\n dedupTtlSeconds: DEFAULT_DEDUP_TTL_SECONDS,\n sessionGapSeconds: DEFAULT_SESSION_GAP_SECONDS,\n maxNodes: DEFAULT_MAX_NODES,\n maxDwellSeconds: DEFAULT_MAX_DWELL_SECONDS,\n pollIntervalMs: DEFAULT_POLL_INTERVAL_MS,\n settleMs: DEFAULT_SETTLE_MS,\n idleFallbackSeconds: DEFAULT_IDLE_FALLBACK_SECONDS,\n denyApps: [],\n denyTitles: [],\n denyUrls: [],\n terminalApps: [],\n redactionPatterns: [],\n };\n}\n\nconst KNOWN_TOP_KEYS: Record<string, true> = {\n host: true,\n port: true,\n spoolRetentionDays: true,\n simhashThreshold: true,\n dedupTtlSeconds: true,\n sessionGapSeconds: true,\n maxNodes: true,\n maxDwellSeconds: true,\n pollIntervalMs: true,\n settleMs: true,\n idleFallbackSeconds: true,\n denyApps: true,\n denyTitles: true,\n denyUrls: true,\n terminalApps: true,\n redactionPatterns: true,\n};\n\nfunction asObject(value: unknown, label: string): Record<string, unknown> {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n throw new CaptureConfigError(`${label}: expected an object, got ${describeValue(value)}`);\n }\n return value as Record<string, unknown>;\n}\n\nfunction requireString(value: unknown, label: string): string {\n if (typeof value !== \"string\" || value.trim() === \"\") {\n throw new CaptureConfigError(`${label}: expected a non-empty string, got ${describeValue(value)}`);\n }\n return value.trim();\n}\n\nexport function parseDaemonConfig(raw: unknown): DaemonConfig {\n const cfg = defaultDaemonConfig();\n const obj = asObject(raw, \"config\");\n for (const key of Object.keys(obj)) {\n if (!Object.hasOwn(KNOWN_TOP_KEYS, key)) {\n console.warn(`remnic-capture-screen: config: ignoring unknown key '${key}'`);\n }\n }\n\n if (obj.host !== undefined) cfg.host = requireString(obj.host, \"host\");\n if (obj.port !== undefined) cfg.port = coerceNumber(obj.port, \"port\", { integer: true, min: 1, max: 65535 });\n if (obj.spoolRetentionDays !== undefined) {\n cfg.spoolRetentionDays = coerceNumber(obj.spoolRetentionDays, \"spoolRetentionDays\", { integer: true, min: 1 });\n }\n if (obj.simhashThreshold !== undefined) {\n cfg.simhashThreshold = coerceNumber(obj.simhashThreshold, \"simhashThreshold\", { integer: true, min: 0, max: 64 });\n }\n if (obj.dedupTtlSeconds !== undefined) {\n cfg.dedupTtlSeconds = coerceNumber(obj.dedupTtlSeconds, \"dedupTtlSeconds\", { min: 0 });\n }\n if (obj.sessionGapSeconds !== undefined) {\n cfg.sessionGapSeconds = coerceNumber(obj.sessionGapSeconds, \"sessionGapSeconds\", { min: 0 });\n }\n if (obj.maxNodes !== undefined) {\n cfg.maxNodes = coerceNumber(obj.maxNodes, \"maxNodes\", { integer: true, min: 1 });\n }\n if (obj.maxDwellSeconds !== undefined) {\n cfg.maxDwellSeconds = coerceNumber(obj.maxDwellSeconds, \"maxDwellSeconds\", { min: 1 });\n }\n if (obj.pollIntervalMs !== undefined) {\n cfg.pollIntervalMs = coerceNumber(obj.pollIntervalMs, \"pollIntervalMs\", { integer: true, min: 100 });\n }\n if (obj.settleMs !== undefined) {\n cfg.settleMs = coerceNumber(obj.settleMs, \"settleMs\", { integer: true, min: 0 });\n }\n if (obj.idleFallbackSeconds !== undefined) {\n cfg.idleFallbackSeconds = coerceNumber(obj.idleFallbackSeconds, \"idleFallbackSeconds\", { min: 1 });\n }\n if (obj.denyApps !== undefined) cfg.denyApps = coerceStringArray(obj.denyApps, \"denyApps\");\n if (obj.denyTitles !== undefined) cfg.denyTitles = coerceStringArray(obj.denyTitles, \"denyTitles\");\n if (obj.denyUrls !== undefined) cfg.denyUrls = coerceStringArray(obj.denyUrls, \"denyUrls\");\n if (obj.terminalApps !== undefined) cfg.terminalApps = coerceStringArray(obj.terminalApps, \"terminalApps\");\n if (obj.redactionPatterns !== undefined) {\n cfg.redactionPatterns = coerceStringArray(obj.redactionPatterns, \"redactionPatterns\");\n }\n\n return cfg;\n}\n\nexport function loadDaemonConfig(configPath: string): DaemonConfig {\n let text: string;\n try {\n text = readFileSync(configPath, \"utf8\");\n } catch {\n throw new CaptureConfigError(`config not found at ${configPath} — run \\`remnic-capture-screen init\\` first`);\n }\n let raw: unknown;\n try {\n raw = JSON.parse(text);\n } catch (err) {\n throw new CaptureConfigError(`config at ${configPath} is not valid JSON: ${(err as Error).message}`);\n }\n return parseDaemonConfig(raw);\n}\n\nexport function serializeDaemonConfig(cfg: DaemonConfig): string {\n return `${JSON.stringify(cfg, null, 2)}\\n`;\n}\n","/** Small dependency-free helpers shared across the package. */\n\n/**\n * Format a Date as YYYY-MM-DD in the given IANA timezone. Local copy of the\n * pipeline helper — capture-screen is à-la-carte and does not depend on\n * @remnic/core.\n */\nexport function dateInTimezone(date: Date, timezone: string): string {\n const parts = new Intl.DateTimeFormat(\"en-CA\", {\n timeZone: timezone,\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n }).formatToParts(date);\n const get = (type: string) => parts.find((part) => part.type === type)?.value ?? \"\";\n return `${get(\"year\")}-${get(\"month\")}-${get(\"day\")}`;\n}\n\nconst LOOPBACK_HOSTS: Record<string, true> = {\n \"127.0.0.1\": true,\n \"::1\": true,\n localhost: true,\n \"::ffff:127.0.0.1\": true,\n};\n\n/** Strip a single pair of surrounding brackets from a URL-authority IPv6 host\n * (`[::1]` -> `::1`); non-bracketed hosts pass through unchanged. */\nexport function stripIpv6Brackets(host: string): string {\n const h = host.trim();\n return h.startsWith(\"[\") && h.endsWith(\"]\") ? h.slice(1, -1) : h;\n}\n\n/**\n * A host is loopback when it can only be reached from this machine. Binding\n * anything else (a LAN address, 0.0.0.0, ::) exposes the daemon to the network\n * and is refused — capture-screen serves plain HTTP with no TLS contract.\n */\nexport function isLoopbackHost(host: string): boolean {\n return Object.hasOwn(LOOPBACK_HOSTS, stripIpv6Brackets(host).toLowerCase());\n}\n\n/** Wrap an IPv6 host in brackets for use in a URL authority; IPv4/hostnames pass\n * through. Existing brackets are stripped first so `[::1]` -> `[::1]`, never `[[::1]]`. */\nexport function formatHostForUrl(host: string): string {\n const bare = stripIpv6Brackets(host);\n return bare.includes(\":\") ? `[${bare}]` : bare;\n}\n\n/** Compact, credential-free description of an unexpected value for messages. */\nexport function describeValue(value: unknown): string {\n if (value === null) return \"null\";\n if (Array.isArray(value)) return \"an array\";\n const t = typeof value;\n if (t === \"string\") return `a string`;\n if (t === \"object\") return \"an object\";\n return `${t} (${String(value)})`;\n}\n\n/**\n * Operator-safe error description — name + errno code only, never foreign\n * message text or filesystem paths. This is the CLI/stderr sanitizer that\n * replaces @remnic/core's displayErrorDetail: a stack or absolute path in a\n * captured-screen daemon's stderr could leak sensitive local layout.\n */\nexport function sanitizeError(err: unknown): string {\n if (!(err instanceof Error)) return \"unknown error\";\n const code = (err as NodeJS.ErrnoException).code;\n return typeof code === \"string\" && code.length > 0 ? `${err.name} (${code})` : err.name;\n}\n","/**\n * Config-layer coercion. Every helper THROWS on an unrecognized value (never\n * silently defaults); callers apply defaults only when a field is absent.\n * Boolean-ish strings coerce per the shared connector convention:\n * true/1/yes/on and false/0/no/off.\n */\n\nimport { CaptureConfigError } from \"./errors.js\";\nimport { describeValue } from \"./util.js\";\n\nconst BOOL_TOKENS: Record<string, boolean> = {\n true: true,\n \"1\": true,\n yes: true,\n on: true,\n false: false,\n \"0\": false,\n no: false,\n off: false,\n};\n\nexport function coerceBool(value: unknown, label: string): boolean {\n if (typeof value === \"boolean\") return value;\n if (typeof value === \"number\" && (value === 0 || value === 1)) return value === 1;\n if (typeof value === \"string\") {\n const token = value.trim().toLowerCase();\n if (Object.hasOwn(BOOL_TOKENS, token)) return BOOL_TOKENS[token];\n }\n throw new CaptureConfigError(\n `${label}: expected a boolean (true/false/1/0/yes/no/on/off), got ${describeValue(value)}`,\n );\n}\n\nexport interface NumberBounds {\n min?: number;\n max?: number;\n integer?: boolean;\n}\n\nexport function coerceNumber(value: unknown, label: string, bounds: NumberBounds = {}): number {\n let n: number;\n if (typeof value === \"number\") {\n n = value;\n } else if (typeof value === \"string\" && value.trim() !== \"\") {\n n = Number(value);\n } else {\n throw new CaptureConfigError(`${label}: expected a number, got ${describeValue(value)}`);\n }\n if (!Number.isFinite(n)) {\n throw new CaptureConfigError(`${label}: '${String(value)}' is not a finite number`);\n }\n if (bounds.integer && !Number.isInteger(n)) {\n throw new CaptureConfigError(`${label}: expected an integer, got ${n}`);\n }\n if (bounds.min !== undefined && n < bounds.min) {\n throw new CaptureConfigError(`${label}: must be >= ${bounds.min}, got ${n}`);\n }\n if (bounds.max !== undefined && n > bounds.max) {\n throw new CaptureConfigError(`${label}: must be <= ${bounds.max}, got ${n}`);\n }\n return n;\n}\n\n/** Coerce an unknown to a `string[]`, rejecting non-arrays and non-string members. */\nexport function coerceStringArray(value: unknown, label: string): string[] {\n if (!Array.isArray(value) || !value.every((item) => typeof item === \"string\")) {\n throw new CaptureConfigError(`${label}: expected an array of strings, got ${describeValue(value)}`);\n }\n return [...(value as string[])];\n}\n","/**\n * Daemon process control: an atomic, identity-bearing pid file plus liveness\n * probing.\n *\n * The pid file is JSON `{ pid, instanceId, startedAtIso, host, port }` written\n * via a temp-file + rename so a reader never sees a partial write, and reads\n * are tolerant of a concurrent delete. `instanceId` (the spool instance id)\n * lets `stop`/`status` confirm — over the authenticated health endpoint — that\n * the recorded pid really is our daemon before signalling it, which guards\n * against PID reuse. Removal is owner-checked so a late shutdown can't delete a\n * newer daemon's control file.\n */\n\nimport { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from \"node:fs\";\nimport { randomBytes } from \"node:crypto\";\nimport path from \"node:path\";\n\nexport interface PidRecord {\n pid: number;\n /** Daemon instance id (spool instance_id) for cross-process identity; null when unknown. */\n instanceId: string | null;\n /** ISO timestamp the record was written. */\n startedAtIso: string;\n /** Effective bound host, when known (so status/stop reach the daemon the CLI actually started). */\n host: string | null;\n /** Effective bound port, when known. */\n port: number | null;\n}\n\nexport interface PidWriteOptions {\n instanceId?: string | null;\n startedAtIso?: string;\n host?: string | null;\n port?: number | null;\n}\n\n/** Atomically write the pid record (temp file + rename) — no partial reads. */\nexport function writePidFile(pidPath: string, pid: number, options: PidWriteOptions = {}): void {\n mkdirSync(path.dirname(pidPath), { recursive: true });\n const record: PidRecord = {\n pid,\n instanceId: options.instanceId ?? null,\n startedAtIso: options.startedAtIso ?? new Date().toISOString(),\n host: options.host ?? null,\n port: options.port ?? null,\n };\n const tmp = `${pidPath}.${process.pid}.${randomBytes(4).toString(\"hex\")}.tmp`;\n writeFileSync(tmp, `${JSON.stringify(record)}\\n`, \"utf8\");\n renameSync(tmp, pidPath);\n}\n\n/** Read the pid record; a missing file or a partial/concurrent write returns null. */\nexport function readPidRecord(pidPath: string): PidRecord | null {\n let text: string;\n try {\n text = readFileSync(pidPath, \"utf8\");\n } catch {\n return null;\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n return null;\n }\n if (typeof parsed !== \"object\" || parsed === null) return null;\n const record = parsed as Record<string, unknown>;\n const pid = typeof record.pid === \"number\" ? record.pid : Number.NaN;\n if (!Number.isInteger(pid) || pid <= 0) return null;\n const port =\n typeof record.port === \"number\" && Number.isInteger(record.port) && record.port > 0 ? record.port : null;\n return {\n pid,\n instanceId: typeof record.instanceId === \"string\" ? record.instanceId : null,\n startedAtIso: typeof record.startedAtIso === \"string\" ? record.startedAtIso : \"\",\n host: typeof record.host === \"string\" && record.host !== \"\" ? record.host : null,\n port,\n };\n}\n\n/** Convenience accessor: the recorded pid, or null. */\nexport function readPidFile(pidPath: string): number | null {\n return readPidRecord(pidPath)?.pid ?? null;\n}\n\n/** Liveness via signal 0. ESRCH → gone; EPERM → alive but owned by another user. */\nexport function isProcessAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch (err) {\n return (err as NodeJS.ErrnoException).code === \"EPERM\";\n }\n}\n\n/** Remove the pid file unconditionally (stale reclaim). */\nexport function removePidFile(pidPath: string): void {\n rmSync(pidPath, { force: true });\n}\n\n/**\n * Remove the pid file only when it still records `pid`. Prevents a late\n * shutdown or `stop` from deleting a NEWER daemon's control file after a\n * restart or PID reuse.\n */\nexport function removePidFileIfOwner(pidPath: string, pid: number): void {\n const record = readPidRecord(pidPath);\n if (record && record.pid === pid) rmSync(pidPath, { force: true });\n}\n","/**\n * Bearer-token lifecycle. The daemon auto-generates a 256-bit token on first\n * use and stores it 0600; a pre-existing file is re-chmod'd 0600 defensively\n * because a world-readable token is a credential leak. The token is REQUIRED on\n * every request (even on loopback) so another local user cannot read captured\n * screen text off 127.0.0.1.\n */\n\nimport { Buffer } from \"node:buffer\";\nimport { randomBytes, timingSafeEqual } from \"node:crypto\";\nimport { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport path from \"node:path\";\n\nexport function generateToken(): string {\n return randomBytes(32).toString(\"base64url\");\n}\n\nexport function loadOrCreateToken(tokenPath: string): string {\n mkdirSync(path.dirname(tokenPath), { recursive: true });\n if (existsSync(tokenPath)) {\n chmodSync(tokenPath, 0o600);\n const existing = readFileSync(tokenPath, \"utf8\").trim();\n if (existing) return existing;\n }\n const token = generateToken();\n try {\n // Exclusive create: if two daemons start together, the loser gets EEXIST and\n // reads the winner's token rather than both persisting divergent values.\n writeFileSync(tokenPath, `${token}\\n`, { mode: 0o600, flag: \"wx\" });\n chmodSync(tokenPath, 0o600);\n return token;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"EEXIST\") throw err;\n chmodSync(tokenPath, 0o600);\n const raced = readFileSync(tokenPath, \"utf8\").trim();\n if (raced) return raced;\n // Pre-existing empty file (interrupted prior write): overwrite it.\n writeFileSync(tokenPath, `${token}\\n`, { mode: 0o600 });\n chmodSync(tokenPath, 0o600);\n return token;\n }\n}\n\n/** Constant-time compare; unequal lengths short-circuit to false. */\nexport function tokensMatch(expected: string, presented: string): boolean {\n const a = Buffer.from(expected, \"utf8\");\n const b = Buffer.from(presented, \"utf8\");\n if (a.length !== b.length) return false;\n return timingSafeEqual(a, b);\n}\n\n/** Parse `Authorization: Bearer <token>`; returns null when absent/malformed. */\nexport function bearerFromHeader(header: string | string[] | undefined): string | null {\n const value = Array.isArray(header) ? header[0] : header;\n if (!value) return null;\n const trimmed = value.trim();\n if (trimmed.slice(0, 6).toLowerCase() !== \"bearer\") return null;\n const separator = trimmed.charCodeAt(6);\n if (separator !== 32 && separator !== 9) return null;\n const token = trimmed.slice(6).trim();\n return token || null;\n}\n","/**\n * Request-input validation for the HTTP surface. Every failure raises\n * CaptureInputError, which the daemon maps to HTTP 400 — invalid date,\n * timezone, limit, or cursor is rejected loudly, never silently defaulted. The\n * keyset cursor is an opaque base64url token over the (capturedAtUtc, id) tuple\n * the snapshots query orders by, so pagination stays stable across snapshots\n * that share a capture instant.\n */\n\nimport { Buffer } from \"node:buffer\";\n\nimport { DEFAULT_SNAPSHOTS_LIMIT, MAX_SNAPSHOTS_LIMIT } from \"./constants.js\";\nimport { CaptureInputError } from \"./errors.js\";\n\nconst DATE_RE = /^\\d{4}-\\d{2}-\\d{2}$/;\n\n/** Validate a YYYY-MM-DD calendar date (rejects e.g. 2026-02-30). */\nexport function parseSnapshotDate(value: string | null | undefined): string {\n if (typeof value !== \"string\" || !DATE_RE.test(value)) {\n throw new CaptureInputError(`invalid date '${value ?? \"\"}' — expected YYYY-MM-DD`);\n }\n const [year, month, day] = value.split(\"-\").map(Number);\n const dt = new Date(Date.UTC(year, month - 1, day));\n dt.setUTCFullYear(year);\n if (dt.getUTCFullYear() !== year || dt.getUTCMonth() !== month - 1 || dt.getUTCDate() !== day) {\n throw new CaptureInputError(`invalid date '${value}' — not a real calendar date`);\n }\n return value;\n}\n\n/** Validate an IANA timezone by attempting to build a formatter for it. */\nexport function assertValidTimezone(value: string | null | undefined): string {\n if (typeof value !== \"string\" || value.trim() === \"\") {\n throw new CaptureInputError(\"invalid timezone '' — expected an IANA timezone\");\n }\n try {\n new Intl.DateTimeFormat(\"en-CA\", { timeZone: value });\n } catch {\n throw new CaptureInputError(`invalid timezone '${value}' — not a known IANA timezone`);\n }\n return value;\n}\n\n/** Absent limit → default; present-but-invalid → 400. */\nexport function parseLimit(value: string | null | undefined): number {\n if (value === null || value === undefined) return DEFAULT_SNAPSHOTS_LIMIT;\n const n = Number(value);\n if (value === \"\" || !Number.isInteger(n) || n < 1 || n > MAX_SNAPSHOTS_LIMIT) {\n throw new CaptureInputError(\n `invalid limit '${value}' — expected an integer between 1 and ${MAX_SNAPSHOTS_LIMIT}`,\n );\n }\n return n;\n}\n\nexport interface Cursor {\n capturedAtUtc: string;\n id: number;\n}\n\nexport function encodeCursor(capturedAtUtc: string, id: number): string {\n return Buffer.from(JSON.stringify([capturedAtUtc, id]), \"utf8\").toString(\"base64url\");\n}\n\n/** Absent cursor → null (first page); malformed cursor → 400. */\nexport function decodeCursor(value: string | null | undefined): Cursor | null {\n if (value === null || value === undefined || value === \"\") return null;\n let parsed: unknown;\n try {\n parsed = JSON.parse(Buffer.from(value, \"base64url\").toString(\"utf8\"));\n } catch {\n throw new CaptureInputError(\"invalid cursor — not a recognized pagination token\");\n }\n if (\n Array.isArray(parsed) &&\n parsed.length === 2 &&\n typeof parsed[0] === \"string\" &&\n typeof parsed[1] === \"number\" &&\n Number.isInteger(parsed[1]) &&\n parsed[1] >= 0 &&\n /^\\d{4}-\\d{2}-\\d{2}T/.test(parsed[0]) &&\n Number.isFinite(Date.parse(parsed[0])) &&\n new Date(parsed[0]).toISOString() === parsed[0]\n ) {\n return { capturedAtUtc: parsed[0], id: parsed[1] };\n }\n throw new CaptureInputError(\"invalid cursor — not a recognized pagination token\");\n}\n","/**\n * Loopback-only HTTP daemon. Serves the spool over three read-only routes:\n *\n * GET /v1/health → liveness + capture status + AX/OCR availability\n * GET /v1/snapshots → snapshots for a local day (keyset paged; wire shape\n * consumed by @remnic/core's ActivityHttpSourceClient)\n * GET /v1/stats → per-app time attribution for a local day\n *\n * Security: capture-screen serves PLAIN HTTP and has no TLS contract, so it\n * refuses to bind a non-loopback host — captured screen text must never cross\n * the network in cleartext. Every request MUST carry `Authorization: Bearer\n * <token>` matching the daemon token, even on loopback, so another local user\n * cannot read snapshots off 127.0.0.1. Input errors are 400; anything\n * unexpected is 500 with no foreign text.\n */\n\nimport http from \"node:http\";\nimport { Buffer } from \"node:buffer\";\n\nimport { computeStats } from \"./capture.js\";\nimport { CAPTURE_SCREEN_VERSION } from \"./constants.js\";\nimport { CaptureConfigError, CaptureInputError } from \"./errors.js\";\nimport { bearerFromHeader, tokensMatch } from \"./token.js\";\nimport { formatHostForUrl, isLoopbackHost } from \"./util.js\";\nimport { assertValidTimezone, parseLimit, parseSnapshotDate } from \"./validate.js\";\nimport type { DaemonConfig } from \"./config.js\";\nimport type { DaemonSnapshot, Spool } from \"./spool.js\";\n\nexport interface DaemonDeps {\n spool: Spool;\n config: DaemonConfig;\n token: string;\n /** Live capture status for /v1/health; false until the capture layer runs. */\n capturing?: boolean;\n /** Native-helper capabilities (false when the helper is unavailable). */\n axAvailable?: boolean;\n ocrAvailable?: boolean;\n /** Operator-facing hint surfaced on /v1/health when the helper is missing. */\n helperHint?: string | null;\n}\n\nexport interface DaemonHandle {\n server: http.Server;\n host: string;\n port: number;\n url: string;\n close(): Promise<void>;\n}\n\n/** Wire shape consumed by ActivityHttpSourceClient. browserUrl omitted when null. */\nfunction snapshotToWire(snap: DaemonSnapshot): Record<string, unknown> {\n const wire: Record<string, unknown> = {\n capturedAtUtc: snap.capturedAtUtc,\n app: snap.app,\n windowTitle: snap.windowTitle,\n text: snap.text,\n textSource: snap.textSource,\n contentHash: snap.contentHash,\n simhash: snap.simhash,\n };\n if (snap.browserUrl !== null) wire.browserUrl = snap.browserUrl;\n return wire;\n}\n\nfunction sendJson(res: http.ServerResponse, status: number, body: unknown): void {\n const payload = JSON.stringify(body);\n res.writeHead(status, {\n \"content-type\": \"application/json; charset=utf-8\",\n \"content-length\": Buffer.byteLength(payload),\n \"cache-control\": \"no-store\",\n });\n res.end(payload);\n}\n\nfunction handleHealth(deps: DaemonDeps, res: http.ServerResponse): void {\n const body: Record<string, unknown> = {\n ok: true,\n version: CAPTURE_SCREEN_VERSION,\n platform: process.platform,\n capturing: deps.capturing ?? false,\n axAvailable: deps.axAvailable ?? false,\n ocrAvailable: deps.ocrAvailable ?? false,\n pendingCount: deps.spool.countSnapshots(),\n instanceId: deps.spool.meta(\"instance_id\"),\n replayStatus: deps.spool.meta(\"replay_status\"),\n pid: process.pid,\n };\n if (deps.helperHint) body.helperHint = deps.helperHint;\n sendJson(res, 200, body);\n}\n\nfunction handleSnapshots(deps: DaemonDeps, url: URL, res: http.ServerResponse): void {\n const date = parseSnapshotDate(url.searchParams.get(\"date\"));\n const timezone = assertValidTimezone(url.searchParams.get(\"timezone\"));\n const limit = parseLimit(url.searchParams.get(\"limit\"));\n const cursor = url.searchParams.get(\"cursor\");\n const page = deps.spool.querySnapshots({ date, timezone, cursor, limit });\n sendJson(res, 200, { snapshots: page.snapshots.map(snapshotToWire), nextCursor: page.nextCursor });\n}\n\nfunction handleStats(deps: DaemonDeps, url: URL, res: http.ServerResponse): void {\n const date = parseSnapshotDate(url.searchParams.get(\"date\"));\n const timezone = assertValidTimezone(url.searchParams.get(\"timezone\"));\n const stats = computeStats(deps.spool.daySnapshots(date, timezone), date, timezone, deps.config.maxDwellSeconds);\n sendJson(res, 200, stats);\n}\n\nexport function createRequestHandler(deps: DaemonDeps): http.RequestListener {\n if (!isLoopbackHost(deps.config.host)) {\n throw new CaptureConfigError(\n `refusing to bind non-loopback host '${deps.config.host}': capture-screen serves plain HTTP with no TLS contract; ` +\n \"bind a loopback address (127.0.0.1 or ::1) only\",\n );\n }\n if (!deps.token) {\n throw new CaptureConfigError(\"daemon requires a bearer token\");\n }\n return (req, res) => {\n try {\n const presented = bearerFromHeader(req.headers[\"authorization\"]);\n if (!presented || !tokensMatch(deps.token, presented)) {\n res.setHeader(\"www-authenticate\", \"Bearer\");\n sendJson(res, 401, { error: \"unauthorized\" });\n return;\n }\n if (req.method !== \"GET\") {\n sendJson(res, 405, { error: \"method not allowed\" });\n return;\n }\n const url = new URL(req.url ?? \"/\", \"http://localhost\");\n switch (url.pathname) {\n case \"/v1/health\":\n handleHealth(deps, res);\n return;\n case \"/v1/snapshots\":\n handleSnapshots(deps, url, res);\n return;\n case \"/v1/stats\":\n handleStats(deps, url, res);\n return;\n default:\n sendJson(res, 404, { error: \"not found\" });\n }\n } catch (err) {\n if (err instanceof CaptureInputError) {\n sendJson(res, 400, { error: err.message });\n return;\n }\n sendJson(res, 500, { error: \"internal error\" });\n }\n };\n}\n\nexport function startDaemon(deps: DaemonDeps): Promise<DaemonHandle> {\n return new Promise((resolve, reject) => {\n let handler: http.RequestListener;\n try {\n handler = createRequestHandler(deps);\n } catch (err) {\n reject(err as Error);\n return;\n }\n const server = http.createServer(handler);\n const onError = (err: Error) => reject(err);\n server.once(\"error\", onError);\n server.listen(deps.config.port, deps.config.host, () => {\n server.removeListener(\"error\", onError);\n server.on(\"error\", (err: NodeJS.ErrnoException) => {\n process.stderr.write(`capture-screen daemon server error: ${err.code ?? err.name}\\n`);\n });\n const address = server.address();\n const port = typeof address === \"object\" && address ? address.port : deps.config.port;\n const host = deps.config.host;\n resolve({\n server,\n host,\n port,\n url: `http://${formatHostForUrl(host)}:${port}`,\n close: () =>\n new Promise<void>((res2, rej2) => {\n server.close((closeErr) => (closeErr ? rej2(closeErr) : res2()));\n }),\n });\n });\n });\n}\n","/** Filesystem layout for the capture working directory. */\n\nimport os from \"node:os\";\nimport path from \"node:path\";\n\nexport interface CapturePaths {\n baseDir: string;\n configPath: string;\n spoolPath: string;\n tokenPath: string;\n pidPath: string;\n logPath: string;\n}\n\n/** Expand a leading `~` / `~/` to the home directory; other paths pass through. */\nexport function expandTilde(p: string): string {\n if (p === \"~\") return os.homedir();\n if (p.startsWith(\"~/\")) return path.join(os.homedir(), p.slice(2));\n return p;\n}\n\n/**\n * Root of the capture working directory. `REMNIC_CAPTURE_SCREEN_DIR` overrides\n * the default `~/.remnic/capture-screen` (tests and multi-instance setups point\n * it at a scratch dir). A leading `~` expands to the home directory.\n */\nexport function captureBaseDir(env: NodeJS.ProcessEnv = process.env): string {\n const override = env.REMNIC_CAPTURE_SCREEN_DIR?.trim();\n if (override) return expandTilde(override);\n return path.join(os.homedir(), \".remnic\", \"capture-screen\");\n}\n\nexport function capturePaths(baseDir: string = captureBaseDir()): CapturePaths {\n return {\n baseDir,\n configPath: path.join(baseDir, \"screen.json\"),\n spoolPath: path.join(baseDir, \"screen.sqlite\"),\n tokenPath: path.join(baseDir, \"token\"),\n pidPath: path.join(baseDir, \"daemon.pid\"),\n logPath: path.join(baseDir, \"daemon.log\"),\n };\n}\n","/**\n * Native-helper seam. The actual screen reader is a platform Swift binary\n * shipped separately as `@remnic/capture-native-<platform>-<arch>`, exporting a\n * `helperBinaryPath`. This module resolves that binary, spawns it, and parses\n * its JSON — with two hard rules:\n *\n * - A MISSING helper package NEVER surfaces as a raw MODULE_NOT_FOUND: it\n * resolves to `{ binaryPath: null, hint }` with an actionable install hint,\n * and the daemon reports axAvailable/ocrAvailable = false (degraded but\n * honest).\n * - Every helper invocation is bounded and its output validated: a nonzero\n * exit, empty output, or invalid/partial JSON throws a sanitized\n * CaptureInputError, never a crash and never foreign text.\n *\n * `REMNIC_CAPTURE_HELPER_BIN` overrides resolution with an explicit binary path\n * (manual installs and the hardware-free test seam, which points it at a fake\n * script emitting canned JSON).\n */\n\nimport { spawn } from \"node:child_process\";\n\nimport type { AxNode } from \"./axtree.js\";\nimport { CaptureInputError } from \"./errors.js\";\nimport { expandTilde } from \"./paths.js\";\n\n/** Max helper stdout we will buffer (guards a runaway child). */\nconst MAX_OUTPUT_BYTES = 8 * 1024 * 1024;\nconst DEFAULT_TIMEOUT_MS = 15_000;\n\nexport interface HelperResolution {\n /** Absolute path to the helper binary, or null when unavailable. */\n binaryPath: string | null;\n /** Operator-facing install hint when unavailable, else null. */\n hint: string | null;\n}\n\n/** The npm package that would provide the helper for this platform/arch. */\nexport function helperPackageName(platform: string = process.platform, arch: string = process.arch): string {\n return `@remnic/capture-native-${platform}-${arch}`;\n}\n\nfunction installHint(pkg: string): string {\n return (\n `native capture helper (${pkg}) is not available on this install. ` +\n `Run \\`npm install ${pkg}\\` or build the Swift helper from source ` +\n `(packages/capture-native-darwin-helper) and set REMNIC_CAPTURE_HELPER_BIN to the binary`\n );\n}\n\nfunction isModuleNotFound(err: unknown): boolean {\n const code = (err as NodeJS.ErrnoException | undefined)?.code;\n return code === \"ERR_MODULE_NOT_FOUND\" || code === \"MODULE_NOT_FOUND\";\n}\n\n/**\n * Resolve the helper binary path. Order: explicit env override, then the\n * computed platform package (dynamic import), then unavailable-with-hint. A\n * missing or broken package degrades gracefully — it never throws.\n */\nexport async function resolveHelperBinaryPath(env: NodeJS.ProcessEnv = process.env): Promise<HelperResolution> {\n const override = env.REMNIC_CAPTURE_HELPER_BIN?.trim();\n if (override) return { binaryPath: expandTilde(override), hint: null };\n\n const pkg = helperPackageName();\n try {\n // Runtime-selected specifier: the helper package is platform/arch-specific\n // and absent on most hosts, so a static import is impossible here.\n const mod: unknown = await import(pkg);\n if (mod && typeof mod === \"object\" && \"helperBinaryPath\" in mod) {\n const value: unknown = mod.helperBinaryPath;\n if (typeof value === \"string\" && value.length > 0) return { binaryPath: value, hint: null };\n }\n // Package present but did not export a usable path — still degrade honestly.\n return { binaryPath: null, hint: `${pkg} is installed but exports no helperBinaryPath` };\n } catch (err) {\n if (isModuleNotFound(err)) return { binaryPath: null, hint: installHint(pkg) };\n // Any other load failure (broken binding, bad build) — degrade, never crash.\n return { binaryPath: null, hint: `${pkg} failed to load; reinstall it to enable live capture` };\n }\n}\n\ninterface SpawnOutcome {\n code: number | null;\n stdout: string;\n}\n\nfunction spawnHelper(binaryPath: string, args: string[], timeoutMs: number): Promise<SpawnOutcome> {\n return new Promise<SpawnOutcome>((resolve, reject) => {\n const child = spawn(binaryPath, args, { stdio: [\"ignore\", \"pipe\", \"pipe\"] });\n const chunks: Buffer[] = [];\n let size = 0;\n let settled = false;\n const timer = setTimeout(() => {\n if (settled) return;\n settled = true;\n child.kill(\"SIGKILL\");\n reject(new CaptureInputError(\"native helper timed out\"));\n }, timeoutMs);\n child.stdout.on(\"data\", (chunk: Buffer) => {\n size += chunk.length;\n if (size > MAX_OUTPUT_BYTES) {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n child.kill(\"SIGKILL\");\n reject(new CaptureInputError(\"native helper produced too much output\"));\n return;\n }\n chunks.push(chunk);\n });\n child.on(\"error\", (err) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n // Sanitize: name + errno only, never the spawn path.\n const code = (err as NodeJS.ErrnoException).code;\n reject(new CaptureInputError(`native helper failed to spawn (${code ?? err.name})`));\n });\n child.on(\"close\", (code) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n resolve({ code, stdout: Buffer.concat(chunks).toString(\"utf8\") });\n });\n });\n}\n\n/** Run a helper subcommand and return its parsed JSON, or throw a sanitized error. */\nexport async function runHelperCommand(\n binaryPath: string,\n args: string[],\n timeoutMs: number = DEFAULT_TIMEOUT_MS,\n): Promise<unknown> {\n const outcome = await spawnHelper(binaryPath, args, timeoutMs);\n if (outcome.code !== 0) {\n throw new CaptureInputError(`native helper exited with status ${outcome.code ?? \"unknown\"}`);\n }\n if (outcome.stdout.trim() === \"\") {\n throw new CaptureInputError(\"native helper produced no output\");\n }\n try {\n return JSON.parse(outcome.stdout);\n } catch {\n throw new CaptureInputError(\"native helper produced invalid JSON\");\n }\n}\n\nexport interface AxSnapshotOptions {\n frontmost?: boolean;\n pid?: number;\n maxNodes?: number;\n}\n\n/**\n * The helper's `ax-snapshot` payload: the frontmost window's context (app,\n * title, optional browser URL) plus its accessibility tree. The tree is\n * permissive (see AxNode); window context lets the daemon build a capture\n * candidate without a separate frontmost-window query. `windowId` is the\n * stable native window id (macOS CGWindowID) so follow-up OCR can target\n * the exact window this tree came from, not whatever is frontmost later.\n */\nexport interface AxSnapshot {\n app: string;\n windowTitle: string;\n windowId?: string;\n browserUrl?: string | null;\n tree: AxNode;\n}\n\nexport interface OcrWindowOptions {\n frontmost?: boolean;\n windowId?: string;\n}\n\n/** Thin wrapper over a resolved helper binary. */\nexport class NativeHelper {\n readonly binaryPath: string;\n\n constructor(binaryPath: string) {\n this.binaryPath = binaryPath;\n }\n\n /** `<helper> ax-snapshot [--frontmost|--pid N] [--max-nodes N]` -> window + AX tree JSON. */\n async axSnapshot(opts: AxSnapshotOptions = {}): Promise<AxSnapshot> {\n const args = [\"ax-snapshot\"];\n if (opts.pid !== undefined) args.push(\"--pid\", String(opts.pid));\n else args.push(\"--frontmost\");\n if (opts.maxNodes !== undefined) args.push(\"--max-nodes\", String(opts.maxNodes));\n const json = await runHelperCommand(this.binaryPath, args);\n if (json === null || typeof json !== \"object\" || Array.isArray(json)) {\n throw new CaptureInputError(\"native helper ax-snapshot did not return an object\");\n }\n if (!(\"app\" in json) || !(\"windowTitle\" in json) || !(\"tree\" in json)) {\n throw new CaptureInputError(\"native helper ax-snapshot missing app/windowTitle/tree\");\n }\n const app: unknown = json.app;\n const windowTitle: unknown = json.windowTitle;\n const browserUrl: unknown = \"browserUrl\" in json ? json.browserUrl : undefined;\n const windowIdRaw: unknown = \"windowId\" in json ? json.windowId : undefined;\n const tree: unknown = json.tree;\n if (typeof app !== \"string\" || typeof windowTitle !== \"string\") {\n throw new CaptureInputError(\"native helper ax-snapshot app/windowTitle must be strings\");\n }\n if (tree === null || typeof tree !== \"object\" || Array.isArray(tree)) {\n throw new CaptureInputError(\"native helper ax-snapshot tree must be an object\");\n }\n // CGWindowID may arrive as a JSON number or string; null/absent means the\n // helper could not resolve an id (OCR falls back to frontmost).\n let windowId: string | undefined;\n if (typeof windowIdRaw === \"string\") windowId = windowIdRaw;\n else if (typeof windowIdRaw === \"number\" && Number.isFinite(windowIdRaw)) windowId = String(windowIdRaw);\n else if (windowIdRaw !== undefined && windowIdRaw !== null) {\n throw new CaptureInputError(\"native helper ax-snapshot windowId must be a string or number\");\n }\n // Named cast (sanctioned): the tree is structurally an AxNode (all fields\n // optional) and extractAxText tolerates unknown shapes; a schema parse of an\n // arbitrary AX dump would be meaningless.\n const axTree = tree as AxNode;\n return {\n app,\n windowTitle,\n ...(windowId !== undefined ? { windowId } : {}),\n ...(typeof browserUrl === \"string\" ? { browserUrl } : {}),\n tree: axTree,\n };\n }\n\n /** `<helper> ocr-window [--frontmost|--window ID]` -> `{ text }` JSON. */\n async ocrWindow(opts: OcrWindowOptions = {}): Promise<string> {\n const args = [\"ocr-window\"];\n if (opts.windowId !== undefined) args.push(\"--window\", opts.windowId);\n else args.push(\"--frontmost\");\n const json = await runHelperCommand(this.binaryPath, args);\n if (json !== null && typeof json === \"object\" && !Array.isArray(json) && \"text\" in json) {\n const text: unknown = json.text;\n if (typeof text === \"string\") return text;\n }\n throw new CaptureInputError(\"native helper ocr-window did not return a text field\");\n }\n}\n","/**\n * Live capture cycle: one snapshot fetched through the native helper and run\n * through the processing pipeline. Shared by `test-snapshot` (which prints the\n * decision without storing) and available to a future capture scheduler.\n *\n * The routing (AX vs OCR) happens here because the native OCR call is async\n * while the processor's OCR seam is sync: a terminal-class or AX-empty window\n * has its OCR text fetched eagerly, then handed to the processor as\n * pre-extracted text. When OCR fails, the candidate is left text-less so the\n * processor skips it (ocr-unavailable) rather than storing empty AX text.\n */\n\nimport { extractAxText } from \"./axtree.js\";\nimport { isTerminalApp } from \"./capture.js\";\nimport { matchDenyRule } from \"./denylist.js\";\nimport type { CaptureCandidate, CaptureDecision, CaptureProcessor } from \"./capture.js\";\nimport type { DaemonConfig } from \"./config.js\";\nimport type { AxSnapshot, NativeHelper } from \"./helper.js\";\n\nexport async function captureViaHelper(\n helper: NativeHelper,\n processor: CaptureProcessor,\n config: DaemonConfig,\n capturedAtUtc: string,\n): Promise<CaptureDecision> {\n const snap = await helper.axSnapshot({ frontmost: true, maxNodes: config.maxNodes });\n return captureFromSnapshot(snap, helper, processor, config, capturedAtUtc);\n}\n\n/**\n * Run an already-fetched AX snapshot through the pipeline. The live scheduler\n * uses this so a single ax-snapshot poll drives both change detection and the\n * capture, avoiding a redundant fetch.\n */\nexport async function captureFromSnapshot(\n snap: AxSnapshot,\n helper: NativeHelper,\n processor: CaptureProcessor,\n config: DaemonConfig,\n capturedAtUtc: string,\n): Promise<CaptureDecision> {\n const axText = extractAxText(snap.tree, config.maxNodes).text;\n const candidate: CaptureCandidate = {\n capturedAtUtc,\n app: snap.app,\n windowTitle: snap.windowTitle,\n ...(snap.browserUrl != null ? { browserUrl: snap.browserUrl } : {}),\n };\n // Deny preflight: never OCR/screen-capture a deny-listed window. process()\n // applies deny too, but only AFTER text extraction — so without this an OCR\n // call would fire against a denied window before the rule is checked.\n const denied =\n matchDenyRule(\n { app: snap.app, windowTitle: snap.windowTitle, browserUrl: snap.browserUrl ?? null },\n { apps: config.denyApps, titles: config.denyTitles, urls: config.denyUrls },\n ) !== null;\n if (denied) {\n // Leave text-less; processor.process denies it below (no OCR ran).\n } else if (isTerminalApp(snap.app, config.terminalApps) || axText.trim() === \"\") {\n try {\n // Pin OCR to the snapshot's window when the helper reported its id: a\n // focus change between the ax-snapshot and this call must not redirect\n // OCR at a different window than the one this candidate is stored\n // under. The deny preflight above ran against this same window\n // identity, so the resolved OCR target is the window that was checked.\n // Without an id (legacy helper) frontmost is the only target available\n // and the preflight against the snap identity remains the guard.\n const ocrText = await helper.ocrWindow(\n snap.windowId !== undefined ? { windowId: snap.windowId } : { frontmost: true },\n );\n if (ocrText.trim() !== \"\") {\n candidate.text = ocrText;\n candidate.textSource = \"ocr\";\n }\n // Blank OCR: leave text-less so the processor reports ocr-unavailable\n // instead of persisting an empty snapshot.\n } catch {\n // OCR unavailable/failed: leave text-less so the processor reports\n // ocr-unavailable instead of persisting an empty snapshot.\n }\n } else {\n candidate.text = axText;\n candidate.textSource = \"ax\";\n }\n return processor.process(candidate);\n}\n","/**\n * `--replay <dir>` ingestion. Feeds synthetic candidate snapshots through the\n * FULL capture pipeline (deny-lists, AX/secure-field extraction, OCR routing,\n * redaction, dedup, supersession) into the spool — the CI-friendly, hardware-\n * free path that exercises every capture-time rule without a native helper.\n *\n * Each `*.json` fixture is either a single candidate or an array of them:\n *\n * {\n * \"capturedAtUtc\": \"2026-07-20T15:00:00.000Z\",\n * \"app\": \"Safari\",\n * \"windowTitle\": \"Example\",\n * \"browserUrl\": \"https://example.com\", // optional\n * \"text\": \"already extracted text\", // optional; OR provide \"ax\"\n * \"textSource\": \"ax\", // optional; \"ax\" | \"ocr\"\n * \"ax\": { \"role\": \"AXWindow\", \"children\": [ ... ] } // optional AX tree\n * }\n *\n * Candidates are processed in ascending capturedAt order (ties broken by file\n * order) so dedup/TTL behave deterministically regardless of how fixtures are\n * split across files. Ingestion is idempotent by content hash.\n */\n\nimport { lstatSync, readdirSync, readFileSync } from \"node:fs\";\nimport path from \"node:path\";\n\nimport type { CaptureCandidate, CaptureProcessor, OcrFn } from \"./capture.js\";\nimport { CaptureProcessor as Processor } from \"./capture.js\";\nimport type { DaemonConfig } from \"./config.js\";\nimport { CaptureConfigError } from \"./errors.js\";\nimport type { AxNode } from \"./axtree.js\";\nimport type { Spool } from \"./spool.js\";\n\nexport interface ReplayResult {\n files: number;\n candidates: number;\n stored: number;\n denied: number;\n deduped: number;\n ocrSkipped: number;\n superseded: number;\n /** True when a cooperative cancel (AbortSignal) stopped ingestion early. */\n aborted: boolean;\n}\n\nconst REPLAY_INSTANT = /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}(:\\d{2}(\\.\\d{1,9})?)?(Z|[+-]\\d{2}:\\d{2})$/;\n\nfunction asObject(value: unknown, where: string): Record<string, unknown> {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n throw new CaptureConfigError(`${where}: expected a snapshot object`);\n }\n return value as Record<string, unknown>;\n}\n\nfunction parseTimestamp(value: unknown, where: string): string {\n if (typeof value !== \"string\" || !REPLAY_INSTANT.test(value) || !Number.isFinite(Date.parse(value))) {\n throw new CaptureConfigError(`${where}: expected an ISO instant with a Z or numeric offset`);\n }\n const [cy, cm, cd] = value.slice(0, 10).split(\"-\").map(Number);\n const probe = new Date(Date.UTC(cy, cm - 1, cd));\n probe.setUTCFullYear(cy);\n if (probe.getUTCFullYear() !== cy || probe.getUTCMonth() !== cm - 1 || probe.getUTCDate() !== cd) {\n throw new CaptureConfigError(`${where}: '${value}' is not a real calendar date`);\n }\n // Canonicalize to UTC (Z) so an offset instant sorts correctly under the keyset.\n return new Date(value).toISOString();\n}\n\nfunction requireString(value: unknown, where: string): string {\n if (typeof value !== \"string\") throw new CaptureConfigError(`${where}: expected a string`);\n return value;\n}\n\nfunction parseCandidate(raw: unknown, where: string): CaptureCandidate {\n const obj = asObject(raw, where);\n const capturedAtUtc = parseTimestamp(obj.capturedAtUtc, `${where}.capturedAtUtc`);\n const candidate: CaptureCandidate = {\n capturedAtUtc,\n app: requireString(obj.app, `${where}.app`),\n windowTitle: requireString(obj.windowTitle, `${where}.windowTitle`),\n };\n if (obj.browserUrl !== undefined && obj.browserUrl !== null) {\n candidate.browserUrl = requireString(obj.browserUrl, `${where}.browserUrl`);\n }\n if (obj.text !== undefined) candidate.text = requireString(obj.text, `${where}.text`);\n if (obj.textSource !== undefined) {\n if (obj.textSource !== \"ax\" && obj.textSource !== \"ocr\") {\n throw new CaptureConfigError(`${where}.textSource: expected 'ax' or 'ocr'`);\n }\n candidate.textSource = obj.textSource;\n }\n if (obj.ax !== undefined) candidate.ax = asObject(obj.ax, `${where}.ax`) as AxNode;\n if (candidate.text === undefined && candidate.ax === undefined) {\n throw new CaptureConfigError(`${where}: a fixture must carry 'text' or 'ax' (one is required)`);\n }\n return candidate;\n}\n\nfunction listFixtureFiles(dir: string): string[] {\n let entries: string[];\n try {\n if (lstatSync(dir).isSymbolicLink()) {\n throw new CaptureConfigError(`replay dir ${dir} is a symlink; refusing to follow it`);\n }\n entries = readdirSync(dir)\n .filter((name) => name.endsWith(\".json\"))\n .sort();\n } catch (err) {\n if (err instanceof CaptureConfigError) throw err;\n throw new CaptureConfigError(`replay dir not found or unreadable: ${dir}`);\n }\n if (entries.length === 0) {\n throw new CaptureConfigError(`replay dir ${dir} contains no *.json fixtures`);\n }\n return entries;\n}\n\n/** Parse + validate every fixture without touching the spool (atomic failure). */\nfunction parseReplayDir(dir: string): { candidates: CaptureCandidate[]; files: number } {\n const entries = listFixtureFiles(dir);\n const candidates: CaptureCandidate[] = [];\n for (const name of entries) {\n const filePath = path.join(dir, name);\n if (lstatSync(filePath).isSymbolicLink()) {\n throw new CaptureConfigError(`replay fixture ${name} is a symlink; refusing to follow it`);\n }\n let raw: unknown;\n try {\n raw = JSON.parse(readFileSync(filePath, \"utf8\"));\n } catch (err) {\n throw new CaptureConfigError(`replay fixture ${name} is not valid JSON: ${(err as Error).message}`);\n }\n const docs = Array.isArray(raw) ? raw : [raw];\n docs.forEach((doc, i) => candidates.push(parseCandidate(doc, `${name}[${i}]`)));\n }\n // Stable ascending capture order (tie: original index) — deterministic dedup.\n const indexed = candidates.map((candidate, index) => ({ candidate, index }));\n indexed.sort((a, b) => {\n const at = Date.parse(a.candidate.capturedAtUtc);\n const bt = Date.parse(b.candidate.capturedAtUtc);\n return at !== bt ? at - bt : a.index - b.index;\n });\n return { candidates: indexed.map((entry) => entry.candidate), files: entries.length };\n}\n\nfunction seedProcessor(processor: CaptureProcessor, spool: Spool): void {\n for (const fp of spool.latestFingerprints()) {\n processor.seed(fp.app, fp.windowTitle, fp.simhash, fp.capturedAtUtc);\n }\n}\n\nfunction commit(processor: CaptureProcessor, spool: Spool, config: DaemonConfig, candidate: CaptureCandidate, result: ReplayResult): void {\n const decision = processor.process(candidate);\n if (decision.action === \"denied\") {\n result.denied += 1;\n } else if (decision.action === \"skipped\") {\n if (decision.reason === \"dedup\") result.deduped += 1;\n else result.ocrSkipped += 1;\n } else {\n const inserted = spool.insertSnapshot(decision.snapshot, config.sessionGapSeconds);\n if (inserted.inserted) {\n result.stored += 1;\n if (inserted.supersededId !== null) result.superseded += 1;\n }\n }\n}\n\n/** Commit size between event-loop yields in the responsive ingester. */\nexport const REPLAY_COMMIT_BATCH = 25;\n\n/** Synchronous ingest: validate the whole directory, then process it all. */\nexport function ingestReplayDir(spool: Spool, dir: string, config: DaemonConfig, ocr?: OcrFn): ReplayResult {\n const { candidates, files } = parseReplayDir(dir);\n const processor = new Processor(config, ocr);\n seedProcessor(processor, spool);\n const result: ReplayResult = {\n files,\n candidates: candidates.length,\n stored: 0,\n denied: 0,\n deduped: 0,\n ocrSkipped: 0,\n superseded: 0,\n aborted: false,\n };\n for (const candidate of candidates) commit(processor, spool, config, candidate, result);\n return result;\n}\n\n/**\n * Responsive ingest: validate up front (atomic), then process in bounded\n * batches with an event-loop yield between them so a co-hosted HTTP server\n * stays responsive during a large replay.\n */\nexport async function ingestReplayDirResponsive(\n spool: Spool,\n dir: string,\n config: DaemonConfig,\n options: { signal?: AbortSignal; ocr?: OcrFn } = {},\n): Promise<ReplayResult> {\n const { candidates, files } = parseReplayDir(dir);\n const processor = new Processor(config, options.ocr);\n seedProcessor(processor, spool);\n const result: ReplayResult = {\n files,\n candidates: candidates.length,\n stored: 0,\n denied: 0,\n deduped: 0,\n ocrSkipped: 0,\n superseded: 0,\n aborted: false,\n };\n for (let i = 0; i < candidates.length; i += REPLAY_COMMIT_BATCH) {\n if (options.signal?.aborted) {\n result.aborted = true;\n break;\n }\n for (const candidate of candidates.slice(i, i + REPLAY_COMMIT_BATCH)) {\n commit(processor, spool, config, candidate, result);\n }\n await new Promise<void>((resolve) => setImmediate(resolve));\n }\n return result;\n}\n","/**\n * DST-aware local-day window. Inlined from @remnic/core's activity digest\n * (capture-screen is à-la-carte and depends on nothing at runtime). Returns the\n * half-open [startUtc, endUtc) UTC instants bounding a local calendar day in an\n * IANA timezone, correct across spring-forward (skipped midnight) and fall-back\n * (repeated midnight) transitions.\n */\n\nimport { CaptureInputError } from \"./errors.js\";\n\nconst DATE_PATTERN = /^\\d{4}-\\d{2}-\\d{2}$/;\n\nfunction isValidDate(date: string): boolean {\n if (typeof date !== \"string\" || !DATE_PATTERN.test(date)) return false;\n // Reject impossible calendar days (2026-02-30, 2026-13-01): the UTC round-trip\n // must reproduce the same Y-M-D, else Date normalized an overflow.\n const parsed = new Date(`${date}T00:00:00Z`);\n return Number.isFinite(parsed.getTime()) && parsed.toISOString().slice(0, 10) === date;\n}\n\nfunction timezoneOffsetIso(instant: Date, timezone: string): string {\n const parts = new Intl.DateTimeFormat(\"en-US\", {\n timeZone: timezone,\n timeZoneName: \"longOffset\",\n }).formatToParts(instant);\n const name = parts.find((part) => part.type === \"timeZoneName\")?.value ?? \"GMT\";\n const match = name.match(/GMT([+-]\\d{2}:\\d{2})?/);\n return match?.[1] ?? \"+00:00\";\n}\n\nfunction shiftIsoDate(date: string, days: number): string {\n const parsed = new Date(`${date}T00:00:00Z`);\n parsed.setUTCDate(parsed.getUTCDate() + days);\n return parsed.toISOString().slice(0, 10);\n}\n\n/**\n * First UTC instant whose local wall-clock is `date` at 00:00. Probe several\n * instants across the day (and the prior UTC day, for zones east of UTC) to\n * collect every offset in play; keep an offset only if constructing local\n * midnight with it lands back on that same offset, then take the EARLIEST such\n * instant — the FIRST 00:00 across a fall-back that repeats local midnight.\n */\nfunction zonedDayStartIso(date: string, timezone: string): string {\n const prevDate = shiftIsoDate(date, -1);\n const probeOffsets = new Set(\n [\n `${prevDate}T12:00:00Z`,\n `${prevDate}T23:00:00Z`,\n `${date}T00:00:00Z`,\n `${date}T12:00:00Z`,\n `${date}T23:00:00Z`,\n ].map((iso) => timezoneOffsetIso(new Date(iso), timezone)),\n );\n let best: number | null = null;\n for (const offset of probeOffsets) {\n const candidate = Date.parse(`${date}T00:00:00${offset}`);\n if (!Number.isFinite(candidate)) continue;\n // Reject an offset whose local midnight does not actually occur (spring\n // forward skipped the wall clock): the offset in effect at the candidate\n // instant must equal the offset we used to build it.\n if (timezoneOffsetIso(new Date(candidate), timezone) !== offset) continue;\n if (best === null || candidate < best) best = candidate;\n }\n if (best === null) {\n // Local midnight was skipped by a spring-forward at 00:00. Advance to the\n // first local wall-clock minute on this date that actually exists, scanning\n // forward up to 3h.\n for (let minute = 1; minute <= 180 && best === null; minute++) {\n const hh = String(Math.floor(minute / 60)).padStart(2, \"0\");\n const mm = String(minute % 60).padStart(2, \"0\");\n for (const offset of probeOffsets) {\n const candidate = Date.parse(`${date}T${hh}:${mm}:00${offset}`);\n if (!Number.isFinite(candidate)) continue;\n if (timezoneOffsetIso(new Date(candidate), timezone) !== offset) continue;\n if (best === null || candidate < best) best = candidate;\n }\n }\n }\n if (best === null) {\n const noon = timezoneOffsetIso(new Date(`${date}T12:00:00Z`), timezone);\n best = Date.parse(`${date}T00:00:00${noon}`);\n }\n if (best === null || !Number.isFinite(best)) {\n throw new CaptureInputError(`could not resolve a local day start for '${date}' in '${timezone}'`);\n }\n return new Date(best).toISOString();\n}\n\n/** Half-open [startUtc, endUtc) UTC ISO bounds of a local day. */\nexport function activityDayWindow(date: string, timezone: string): { startUtc: string; endUtc: string } {\n if (!isValidDate(date)) {\n throw new CaptureInputError(`invalid date '${date}' — expected a real YYYY-MM-DD day`);\n }\n try {\n new Intl.DateTimeFormat(\"en-US\", { timeZone: timezone });\n } catch {\n throw new CaptureInputError(`invalid timezone '${timezone}' — not a known IANA timezone`);\n }\n return {\n startUtc: new Date(zonedDayStartIso(date, timezone)).toISOString(),\n endUtc: new Date(zonedDayStartIso(shiftIsoDate(date, 1), timezone)).toISOString(),\n };\n}\n","/**\n * SQLite spool — the daemon's local buffer of captured screen snapshots.\n *\n * Uses the built-in `node:sqlite` driver (no native dependency), keeping\n * @remnic/capture-screen à-la-carte: installing it pulls zero extra runtime\n * packages. WAL mode + foreign keys are enabled per connection.\n *\n * Schema (names/semantics fixed by issue #1899):\n * snapshots(id, captured_at_utc, app_name, window_title, browser_url NULL,\n * text, text_source (ax or ocr), content_hash UNIQUE, simhash,\n * superseded_by NULL -> snapshots(id))\n * meta(key, value)\n *\n * `content_hash` is UNIQUE and inserts are INSERT OR IGNORE, so re-ingesting an\n * identical snapshot is a content no-op (kill-9 / replay idempotency).\n * Supersession links the previous non-superseded snapshot of the same\n * (app, window) session to its replacement, so a consumer can skip stale states.\n * The read API pages by a stable (captured_at_utc, id) keyset over a half-open\n * local-day window.\n */\n\nimport { chmodSync } from \"node:fs\";\nimport { DatabaseSync } from \"node:sqlite\";\n\nimport { SPOOL_SCHEMA_VERSION } from \"./constants.js\";\nimport { activityDayWindow } from \"./daywindow.js\";\nimport { CaptureConfigError } from \"./errors.js\";\nimport { decodeCursor, encodeCursor } from \"./validate.js\";\n\nexport type TextSource = \"ax\" | \"ocr\";\n\nexport interface SnapshotInput {\n capturedAtUtc: string;\n app: string;\n windowTitle: string;\n browserUrl?: string | null;\n text: string;\n textSource: TextSource;\n contentHash: string;\n simhash: string;\n}\n\nexport interface InsertResult {\n id: number;\n inserted: boolean;\n /** Id of the prior snapshot this insert superseded, or null. */\n supersededId: number | null;\n}\n\nexport interface DaemonSnapshot {\n id: number;\n capturedAtUtc: string;\n app: string;\n windowTitle: string;\n browserUrl: string | null;\n text: string;\n textSource: TextSource;\n contentHash: string;\n simhash: string;\n supersededBy: number | null;\n}\n\nexport interface SnapshotPage {\n snapshots: DaemonSnapshot[];\n nextCursor: string | null;\n}\n\nexport interface QuerySnapshotsOptions {\n date: string;\n timezone: string;\n cursor?: string | null;\n limit: number;\n}\n\nexport interface WindowFingerprint {\n app: string;\n windowTitle: string;\n simhash: string;\n capturedAtUtc: string;\n}\n\ninterface SnapshotRow {\n id: number;\n capturedAtUtc: string;\n app: string;\n windowTitle: string;\n browserUrl: string | null;\n text: string;\n textSource: TextSource;\n contentHash: string;\n simhash: string;\n supersededBy: number | null;\n}\n\nconst SCHEMA_SQL = `\nCREATE TABLE IF NOT EXISTS meta (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS snapshots (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n captured_at_utc TEXT NOT NULL,\n app_name TEXT NOT NULL,\n window_title TEXT NOT NULL,\n browser_url TEXT,\n text TEXT NOT NULL,\n text_source TEXT NOT NULL,\n content_hash TEXT NOT NULL UNIQUE,\n simhash TEXT NOT NULL,\n superseded_by INTEGER REFERENCES snapshots(id) ON DELETE SET NULL\n);\nCREATE INDEX IF NOT EXISTS idx_snap_keyset ON snapshots(captured_at_utc, id);\nCREATE INDEX IF NOT EXISTS idx_snap_window ON snapshots(app_name, window_title, captured_at_utc);\n`;\n\nconst SELECT_COLUMNS =\n \"id, captured_at_utc AS capturedAtUtc, app_name AS app, window_title AS windowTitle, \" +\n \"browser_url AS browserUrl, text, text_source AS textSource, content_hash AS contentHash, \" +\n \"simhash, superseded_by AS supersededBy\";\n\nconst ISO_INSTANT = /^(\\d{4})-(\\d{2})-(\\d{2})T\\d{2}:\\d{2}(:\\d{2}(\\.\\d{1,9})?)?(Z|[+-]\\d{2}:\\d{2})$/;\n\n/**\n * Validate + canonicalize a capture instant to UTC `Z`. Date-only strings and\n * offsetless timestamps are rejected, and impossible calendar dates (Date.parse\n * silently rolls 2026-02-30 → Mar 2) are caught by re-checking the written\n * Y-M-D, so every persisted `captured_at_utc` and every keyset cursor is an\n * unambiguous, order-stable instant.\n */\nfunction canonicalInstant(value: string): string {\n const match = typeof value === \"string\" ? ISO_INSTANT.exec(value) : null;\n if (!match || !Number.isFinite(Date.parse(value))) {\n throw new CaptureConfigError(`capturedAtUtc: '${value}' is not a canonical ISO instant (need date, time, and Z or offset)`);\n }\n const year = Number(match[1]);\n const month = Number(match[2]);\n const day = Number(match[3]);\n const probe = new Date(Date.UTC(year, month - 1, day));\n probe.setUTCFullYear(year);\n if (probe.getUTCFullYear() !== year || probe.getUTCMonth() !== month - 1 || probe.getUTCDate() !== day) {\n throw new CaptureConfigError(`capturedAtUtc: '${value}' is not a real calendar date`);\n }\n return new Date(value).toISOString();\n}\n\nexport class Spool {\n #db: DatabaseSync;\n #closed = false;\n\n constructor(location: string) {\n this.#db = new DatabaseSync(location);\n this.#db.exec(\"PRAGMA journal_mode = WAL;\");\n this.#db.exec(\"PRAGMA foreign_keys = ON;\");\n this.#db.exec(\"PRAGMA busy_timeout = 5000;\");\n this.#db.exec(SCHEMA_SQL);\n if (location !== \":memory:\") {\n // Screen-capture history is sensitive; keep the spool owner-only (best\n // effort; ignored where chmod is a no-op).\n try {\n chmodSync(location, 0o600);\n // WAL mode writes <location>-wal / <location>-shm sidecars that hold the\n // same sensitive capture text; keep them owner-only too (best effort).\n for (const suffix of [\"-wal\", \"-shm\"]) {\n try {\n chmodSync(`${location}${suffix}`, 0o600);\n } catch {\n // sidecar absent yet / no POSIX perms\n }\n }\n } catch {\n // filesystem without POSIX perms\n }\n }\n this.#db\n .prepare(\"INSERT OR IGNORE INTO meta(key, value) VALUES (?, ?)\")\n .run(\"schema_version\", String(SPOOL_SCHEMA_VERSION));\n this.#db\n .prepare(\"INSERT OR IGNORE INTO meta(key, value) VALUES (?, ?)\")\n .run(\"instance_id\", `scr_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 10)}`);\n }\n\n close(): void {\n if (this.#closed) return;\n this.#closed = true;\n this.#db.close();\n }\n\n meta(key: string): string | null {\n const row = this.#db.prepare(\"SELECT value FROM meta WHERE key = ?\").get(key) as { value: string } | undefined;\n return row?.value ?? null;\n }\n\n setMeta(key: string, value: string): void {\n this.#db\n .prepare(\"INSERT INTO meta(key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value\")\n .run(key, value);\n }\n\n /**\n * Insert a snapshot. Idempotent by content_hash (INSERT OR IGNORE): a repeat\n * returns the existing row's id with `inserted:false` and performs no\n * supersession. On a genuinely new row, the previous non-superseded snapshot\n * of the same (app, window) captured within `sessionGapSeconds` is marked\n * superseded_by this row.\n */\n insertSnapshot(input: SnapshotInput, sessionGapSeconds: number): InsertResult {\n if (typeof input.text !== \"string\") throw new CaptureConfigError(\"snapshot.text: expected a string\");\n if (input.textSource !== \"ax\" && input.textSource !== \"ocr\") {\n throw new CaptureConfigError(\"snapshot.textSource: expected 'ax' or 'ocr'\");\n }\n if (typeof input.contentHash !== \"string\" || input.contentHash === \"\") {\n throw new CaptureConfigError(\"snapshot.contentHash: expected a non-empty string\");\n }\n if (typeof input.simhash !== \"string\" || input.simhash === \"\") {\n throw new CaptureConfigError(\"snapshot.simhash: expected a non-empty string\");\n }\n const capturedAtUtc = canonicalInstant(input.capturedAtUtc);\n const browserUrl = input.browserUrl ?? null;\n\n const db = this.#db;\n db.exec(\"BEGIN\");\n try {\n const result = db\n .prepare(\n \"INSERT OR IGNORE INTO snapshots(captured_at_utc, app_name, window_title, browser_url, text, text_source, content_hash, simhash) \" +\n \"VALUES (?,?,?,?,?,?,?,?)\",\n )\n .run(capturedAtUtc, input.app, input.windowTitle, browserUrl, input.text, input.textSource, input.contentHash, input.simhash);\n if (Number(result.changes) === 0) {\n const existing = db.prepare(\"SELECT id FROM snapshots WHERE content_hash = ?\").get(input.contentHash) as\n | { id: number }\n | undefined;\n db.exec(\"COMMIT\");\n return { id: existing?.id ?? 0, inserted: false, supersededId: null };\n }\n const id = Number(result.lastInsertRowid);\n const supersededId = this.#supersede(id, input.app, input.windowTitle, capturedAtUtc, sessionGapSeconds);\n db.exec(\"COMMIT\");\n return { id, inserted: true, supersededId };\n } catch (err) {\n db.exec(\"ROLLBACK\");\n throw err;\n }\n }\n\n /** Link the prior in-session snapshot of the same window to `newId`. */\n #supersede(newId: number, app: string, windowTitle: string, capturedAtUtc: string, sessionGapSeconds: number): number | null {\n const prior = this.#db\n .prepare(\n \"SELECT id, captured_at_utc AS capturedAtUtc FROM snapshots \" +\n \"WHERE app_name = ? AND window_title = ? AND superseded_by IS NULL AND id <> ? \" +\n \"AND captured_at_utc <= ? ORDER BY captured_at_utc DESC, id DESC LIMIT 1\",\n )\n .get(app, windowTitle, newId, capturedAtUtc) as { id: number; capturedAtUtc: string } | undefined;\n if (prior === undefined) return null;\n const gapSeconds = (Date.parse(capturedAtUtc) - Date.parse(prior.capturedAtUtc)) / 1000;\n if (gapSeconds < 0 || gapSeconds > sessionGapSeconds) return null;\n this.#db.prepare(\"UPDATE snapshots SET superseded_by = ? WHERE id = ?\").run(newId, prior.id);\n return prior.id;\n }\n\n getSnapshot(id: number): DaemonSnapshot | null {\n const row = this.#db.prepare(`SELECT ${SELECT_COLUMNS} FROM snapshots WHERE id = ?`).get(id) as\n | SnapshotRow\n | undefined;\n return row ? { ...row } : null;\n }\n\n countSnapshots(): number {\n return (this.#db.prepare(\"SELECT COUNT(*) AS n FROM snapshots\").get() as { n: number }).n;\n }\n\n /**\n * Snapshots whose capture instant falls in the half-open [start, end) UTC\n * window of the requested local day, paged by the stable (captured_at_utc, id)\n * keyset. The id tiebreak keeps pagination correct across snapshots that\n * share a capture instant.\n */\n querySnapshots(opts: QuerySnapshotsOptions): SnapshotPage {\n const { startUtc, endUtc } = activityDayWindow(opts.date, opts.timezone);\n const cursor = decodeCursor(opts.cursor ?? null);\n const afterAt = cursor ? cursor.capturedAtUtc : \"\";\n const afterId = cursor ? cursor.id : 0;\n const rows = this.#db\n .prepare(\n `SELECT ${SELECT_COLUMNS} FROM snapshots ` +\n \"WHERE superseded_by IS NULL AND captured_at_utc >= ? AND captured_at_utc < ? \" +\n \"AND (captured_at_utc > ? OR (captured_at_utc = ? AND id > ?)) \" +\n \"ORDER BY captured_at_utc ASC, id ASC LIMIT ?\",\n )\n .all(startUtc, endUtc, afterAt, afterAt, afterId, opts.limit + 1) as unknown as SnapshotRow[];\n const hasMore = rows.length > opts.limit;\n const page = hasMore ? rows.slice(0, opts.limit) : rows;\n const last = page[page.length - 1];\n return {\n snapshots: page.map((row) => ({ ...row })),\n nextCursor: hasMore && last ? encodeCursor(last.capturedAtUtc, last.id) : null,\n };\n }\n\n /** All snapshots in a local day's window, ordered — the basis for /v1/stats. */\n daySnapshots(date: string, timezone: string): DaemonSnapshot[] {\n const { startUtc, endUtc } = activityDayWindow(date, timezone);\n const rows = this.#db\n .prepare(\n `SELECT ${SELECT_COLUMNS} FROM snapshots WHERE superseded_by IS NULL AND captured_at_utc >= ? AND captured_at_utc < ? ` +\n \"ORDER BY captured_at_utc ASC, id ASC\",\n )\n .all(startUtc, endUtc) as unknown as SnapshotRow[];\n return rows.map((row) => ({ ...row }));\n }\n\n /** Latest non-superseded fingerprint per (app, window) — primes the dedup cache. */\n latestFingerprints(): WindowFingerprint[] {\n const rows = this.#db\n .prepare(\n \"SELECT app_name AS app, window_title AS windowTitle, simhash, captured_at_utc AS capturedAtUtc FROM snapshots s \" +\n \"WHERE superseded_by IS NULL AND id = (SELECT MAX(id) FROM snapshots t WHERE t.app_name = s.app_name AND t.window_title = s.window_title)\",\n )\n .all() as unknown as WindowFingerprint[];\n return rows;\n }\n\n /** Retention janitor: drop snapshots older than `days` (cutoff from `nowMs`). Returns rows removed. */\n pruneOlderThan(days: number, nowMs: number = Date.now()): number {\n const cutoff = new Date(nowMs - days * 86_400_000).toISOString();\n const result = this.#db.prepare(\"DELETE FROM snapshots WHERE captured_at_utc < ?\").run(cutoff);\n return Number(result.changes);\n }\n}\n","/**\n * `remnic-capture-screen` CLI. Subcommands:\n * init | start | stop | status | install-service | logs | test-snapshot\n *\n * `start --replay <dir>` feeds synthetic fixtures through the full capture\n * pipeline + HTTP API (the CI-friendly, hardware-free path). Live capture needs\n * the native helper (@remnic/capture-native-*); where it is absent the daemon\n * still serves the spool and reports axAvailable/ocrAvailable = false.\n *\n * The bearer token comes from the environment (REMNIC_CAPTURE_TOKEN), never\n * argv: a long-lived daemon's argv is world-readable via `ps`/`/proc`, so a\n * token on the command line would let any local account read captured screen\n * text. `--auth-token` is rejected. When the env var is unset, the token file\n * created by `init` is used instead.\n */\n\nimport { spawn } from \"node:child_process\";\nimport { chmodSync, existsSync, lstatSync, mkdirSync, openSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { setTimeout as delay } from \"node:timers/promises\";\nimport { dirname } from \"node:path\";\n\nimport { CaptureProcessor } from \"./capture.js\";\nimport { coerceNumber } from \"./coerce.js\";\nimport { CAPTURE_SCREEN_VERSION } from \"./constants.js\";\nimport {\n defaultDaemonConfig,\n loadDaemonConfig,\n serializeDaemonConfig,\n type DaemonConfig,\n} from \"./config.js\";\nimport {\n isProcessAlive,\n readPidRecord,\n removePidFile,\n removePidFileIfOwner,\n writePidFile,\n type PidRecord,\n} from \"./control.js\";\nimport { startDaemon, type DaemonHandle } from \"./daemon.js\";\nimport { CaptureConfigError, CaptureInputError } from \"./errors.js\";\nimport { NativeHelper, resolveHelperBinaryPath } from \"./helper.js\";\nimport { captureViaHelper } from \"./live.js\";\nimport { capturePaths, captureBaseDir, expandTilde, type CapturePaths } from \"./paths.js\";\nimport { CaptureScheduler } from \"./scheduler.js\";\nimport { ingestReplayDirResponsive } from \"./replay.js\";\nimport { Spool } from \"./spool.js\";\nimport { loadOrCreateToken } from \"./token.js\";\nimport { formatHostForUrl, isLoopbackHost, sanitizeError, stripIpv6Brackets } from \"./util.js\";\n\nexport interface CliIo {\n argv: string[];\n env?: NodeJS.ProcessEnv;\n stdout?: (line: string) => void;\n stderr?: (line: string) => void;\n}\n\ninterface ParsedArgs {\n command: string;\n positionals: string[];\n flags: Record<string, string | boolean>;\n}\n\nconst CAPTURE_TOKEN_ENV = \"REMNIC_CAPTURE_TOKEN\";\n/** Legacy alias honored across Remnic (formerly Engram); see README auth note. */\nconst LEGACY_CAPTURE_TOKEN_ENV = \"ENGRAM_CAPTURE_TOKEN\";\n\n/** Flags that consume the next argv token as their value. */\nconst VALUE_FLAGS: Record<string, true> = {\n replay: true,\n host: true,\n port: true,\n listen: true,\n \"base-dir\": true,\n spool: true,\n lines: true,\n};\n\n/** Standalone boolean flags. */\nconst BOOLEAN_FLAGS: Record<string, true> = {\n foreground: true,\n force: true,\n help: true,\n};\n\n/** Non-global flags each subcommand accepts; anything else is rejected. */\nconst COMMAND_FLAGS: Record<string, Record<string, true>> = {\n init: { force: true },\n start: { foreground: true, replay: true, host: true, port: true, listen: true, spool: true },\n stop: { force: true },\n status: {},\n \"install-service\": {},\n logs: { lines: true },\n \"test-snapshot\": {},\n help: {},\n};\n\n/** Flags accepted regardless of subcommand. */\nconst GLOBAL_FLAGS: Record<string, true> = { \"base-dir\": true, spool: true, help: true };\n\nconst READINESS_TIMEOUT_MS = 10_000;\nconst STOP_TIMEOUT_MS = 10_000;\n\nfunction parseArgs(argv: string[]): ParsedArgs {\n const tokens: string[] = [];\n const flags: Record<string, string | boolean> = {};\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i];\n if (arg.startsWith(\"--\")) {\n const key = arg.slice(2);\n if (key === \"auth-token\") {\n throw new CaptureInputError(\n `--auth-token is not accepted; set the ${CAPTURE_TOKEN_ENV} environment variable instead`,\n );\n }\n if (Object.hasOwn(VALUE_FLAGS, key)) {\n const next = argv[i + 1];\n if (next === undefined || next.startsWith(\"--\")) throw new CaptureInputError(`flag --${key} requires a value`);\n flags[key] = next;\n i += 1;\n } else if (Object.hasOwn(BOOLEAN_FLAGS, key)) {\n flags[key] = true;\n } else {\n throw new CaptureInputError(`unknown flag --${key}`);\n }\n } else {\n tokens.push(arg);\n }\n }\n const command = tokens.length > 0 ? tokens[0] : \"help\";\n return { command, positionals: tokens.slice(1), flags };\n}\n\nfunction resolvePaths(flags: Record<string, string | boolean>, env: NodeJS.ProcessEnv): CapturePaths {\n const baseDir =\n typeof flags[\"base-dir\"] === \"string\"\n ? captureBaseDir({ ...env, REMNIC_CAPTURE_SCREEN_DIR: flags[\"base-dir\"] })\n : captureBaseDir(env);\n const paths = capturePaths(baseDir);\n if (typeof flags.spool === \"string\") return { ...paths, spoolPath: expandTilde(flags.spool) };\n return paths;\n}\n\nfunction loadConfigOrDefault(paths: CapturePaths, stderr: (line: string) => void): DaemonConfig {\n if (existsSync(paths.configPath)) return loadDaemonConfig(paths.configPath);\n stderr(`no config at ${paths.configPath}; using defaults (run \\`init\\` to customize)`);\n return defaultDaemonConfig();\n}\n\nfunction applyBindingOverrides(config: DaemonConfig, flags: Record<string, string | boolean>): DaemonConfig {\n const next = { ...config };\n if (typeof flags.listen === \"string\") {\n const idx = flags.listen.lastIndexOf(\":\");\n if (idx <= 0) throw new CaptureInputError(`--listen expects host:port, got '${flags.listen}'`);\n next.host = flags.listen.slice(0, idx);\n next.port = coerceNumber(flags.listen.slice(idx + 1), \"--listen port\", { integer: true, min: 1, max: 65535 });\n }\n if (typeof flags.host === \"string\") next.host = flags.host;\n if (typeof flags.port === \"string\") next.port = coerceNumber(flags.port, \"--port\", { integer: true, min: 1, max: 65535 });\n next.host = stripIpv6Brackets(next.host);\n return next;\n}\n\nfunction healthUrlFor(host: string, port: number): string {\n return `http://${formatHostForUrl(host)}:${port}/v1/health`;\n}\n\nfunction recordHealthUrl(record: PidRecord, paths: CapturePaths, stderr: (l: string) => void): string {\n if (record.host !== null && record.port !== null) return healthUrlFor(record.host, record.port);\n const config = loadConfigOrDefault(paths, stderr);\n return healthUrlFor(config.host, config.port);\n}\n\n/** Token for probes/serving: env override first, then the on-disk token file. */\nexport function resolveToken(paths: CapturePaths, env: NodeJS.ProcessEnv, create: boolean): string {\n const fromEnv = (env[CAPTURE_TOKEN_ENV] ?? env[LEGACY_CAPTURE_TOKEN_ENV])?.trim();\n if (fromEnv) return fromEnv;\n if (create) return loadOrCreateToken(paths.tokenPath);\n if (existsSync(paths.tokenPath)) return readFileSync(paths.tokenPath, \"utf8\").trim();\n return \"\";\n}\n\nfunction tokenHeader(paths: CapturePaths, env: NodeJS.ProcessEnv): Record<string, string> {\n const token = resolveToken(paths, env, false);\n return token ? { authorization: `Bearer ${token}` } : {};\n}\n\nexport function ensurePrivateDir(dir: string): void {\n let isLink = false;\n try {\n isLink = lstatSync(dir).isSymbolicLink();\n } catch {\n // not present yet — mkdir below creates it\n }\n if (isLink) {\n throw new CaptureInputError(`refusing to use symlinked private directory '${dir}'`);\n }\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n try {\n chmodSync(dir, 0o700);\n } catch {\n // filesystem without POSIX perms\n }\n}\n\n/**\n * Prepare a custom --spool parent WITHOUT clobbering an existing directory's\n * mode. Absent → create a dedicated 0700 dir. Present → refuse a symlink or a\n * non-owner-only dir, but never chmod it, so `--spool ./x.sqlite` can't tighten\n * the caller's cwd. The daemon's own base-dir is handled by ensurePrivateDir.\n */\nexport function ensureSpoolParentDir(spoolPath: string): void {\n const dir = dirname(spoolPath);\n let stat: ReturnType<typeof lstatSync>;\n try {\n stat = lstatSync(dir);\n } catch {\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n return;\n }\n if (stat.isSymbolicLink()) {\n throw new CaptureInputError(`refusing to open the capture spool under symlinked directory '${dir}'`);\n }\n if (!stat.isDirectory()) {\n throw new CaptureInputError(`capture spool parent '${dir}' exists but is not a directory`);\n }\n if (process.platform !== \"win32\" && (stat.mode & 0o077) !== 0) {\n throw new CaptureInputError(\n `capture spool directory '${dir}' is not owner-only (mode ${(stat.mode & 0o777).toString(8)}); ` +\n \"point --spool at a private 0700 directory (the daemon creates one when absent)\",\n );\n }\n}\n\ninterface DaemonIdentity {\n instanceId: string;\n pid: number;\n}\n\nasync function probeIdentity(paths: CapturePaths, env: NodeJS.ProcessEnv, url: string): Promise<DaemonIdentity | null> {\n try {\n const res = await fetch(url, { headers: tokenHeader(paths, env), signal: AbortSignal.timeout(2000) });\n if (!res.ok) return null;\n const body: unknown = await res.json();\n if (body !== null && typeof body === \"object\" && \"instanceId\" in body && \"pid\" in body) {\n const instanceId: unknown = body.instanceId;\n const pid: unknown = body.pid;\n if (typeof instanceId === \"string\" && typeof pid === \"number\") return { instanceId, pid };\n }\n return null;\n } catch {\n return null;\n }\n}\n\nexport function recordChildPidOrTerminate(\n pid: number,\n paths: CapturePaths,\n binding: { host: string; port: number },\n stderr: (l: string) => void,\n): boolean {\n const existing = readPidRecord(paths.pidPath);\n if (existing !== null && existing.pid === pid && existing.instanceId !== null) return true;\n try {\n writePidFile(paths.pidPath, pid, binding);\n return true;\n } catch (err) {\n try {\n process.kill(pid, \"SIGTERM\");\n } catch {\n // already gone\n }\n stderr(`failed to record daemon pid: ${sanitizeError(err)}; terminated child pid ${pid}`);\n return false;\n }\n}\n\nasync function isOwnRunningDaemon(record: PidRecord, paths: CapturePaths, env: NodeJS.ProcessEnv, stderr: (l: string) => void): Promise<boolean> {\n if (record.instanceId === null) return true;\n const live = await probeIdentity(paths, env, recordHealthUrl(record, paths, stderr));\n if (live === null) return true;\n return live.instanceId === record.instanceId && live.pid === record.pid;\n}\n\nexport async function recordedDaemonIsRunning(record: PidRecord, paths: CapturePaths, env: NodeJS.ProcessEnv, stderr: (l: string) => void): Promise<boolean> {\n if (record.pid === process.pid) return false;\n if (!isProcessAlive(record.pid)) return false;\n return isOwnRunningDaemon(record, paths, env, stderr);\n}\n\n/**\n * Run replay ingestion as a supervised task AFTER the daemon is ready. Never\n * throws: success/failure is surfaced via the spool's `replay_status` meta\n * (also on /v1/health) and the daemon log, so a slow/failed replay never kills\n * the daemon or retracts its readiness.\n */\nexport async function superviseReplay(\n spool: Spool,\n replayDir: string,\n config: DaemonConfig,\n io: { stdout: (l: string) => void; stderr: (l: string) => void },\n signal?: AbortSignal,\n): Promise<void> {\n await Promise.resolve();\n spool.setMeta(\"replay_status\", \"running\");\n try {\n const summary = await ingestReplayDirResponsive(spool, replayDir, config, { signal });\n if (summary.aborted) {\n spool.setMeta(\"replay_status\", \"cancelled\");\n io.stdout(`replay: cancelled after ${summary.stored} snapshot(s)`);\n } else {\n spool.setMeta(\"replay_status\", \"ok\");\n io.stdout(\n `replay: stored ${summary.stored} of ${summary.candidates} candidate(s) ` +\n `(denied ${summary.denied}, deduped ${summary.deduped}, ocr-skipped ${summary.ocrSkipped}) ` +\n `from ${summary.files} fixture file(s)`,\n );\n }\n } catch (err) {\n const message = err instanceof CaptureConfigError || err instanceof CaptureInputError ? err.message : sanitizeError(err);\n const sanitized = message.replace(/\\/\\S+/g, \"<path>\");\n spool.setMeta(\"replay_status\", `failed: ${sanitized}`);\n io.stderr(`replay ingestion failed: ${message}`);\n }\n}\n\nfunction cmdInit(paths: CapturePaths, flags: Record<string, string | boolean>, stdout: (l: string) => void): number {\n ensurePrivateDir(paths.baseDir);\n if (existsSync(paths.configPath) && flags.force !== true) {\n stdout(`config already exists at ${paths.configPath} (use --force to overwrite)`);\n } else {\n writeFileSync(paths.configPath, serializeDaemonConfig(defaultDaemonConfig()), \"utf8\");\n stdout(`wrote default config to ${paths.configPath}`);\n }\n const token = loadOrCreateToken(paths.tokenPath);\n stdout(`token ready at ${paths.tokenPath} (${token.length} chars, mode 0600)`);\n stdout(`set ${CAPTURE_TOKEN_ENV} to override the token file when starting the daemon`);\n stdout(`spool will be created at ${paths.spoolPath} on first start`);\n return 0;\n}\n\nasync function cmdStart(\n paths: CapturePaths,\n flags: Record<string, string | boolean>,\n env: NodeJS.ProcessEnv,\n stdout: (l: string) => void,\n stderr: (l: string) => void,\n): Promise<number> {\n const config = applyBindingOverrides(loadConfigOrDefault(paths, stderr), flags);\n if (!isLoopbackHost(config.host)) {\n stderr(\n `refusing to bind non-loopback host '${config.host}': capture-screen serves plain HTTP with no TLS contract; ` +\n \"use a loopback address (127.0.0.1 or ::1)\",\n );\n return 1;\n }\n const replayDir = typeof flags.replay === \"string\" ? expandTilde(flags.replay) : null;\n const previousRecord = readPidRecord(paths.pidPath);\n if (previousRecord !== null) {\n if (await recordedDaemonIsRunning(previousRecord, paths, env, stderr)) {\n stdout(`daemon already running (pid ${previousRecord.pid})`);\n return 0;\n }\n if (previousRecord.pid !== process.pid) removePidFile(paths.pidPath);\n }\n\n if (flags.foreground !== true) {\n const entry = process.argv[1];\n const forwarded = [\"start\", \"--foreground\"];\n if (replayDir) forwarded.push(\"--replay\", replayDir);\n if (typeof flags[\"base-dir\"] === \"string\") forwarded.push(\"--base-dir\", flags[\"base-dir\"]);\n if (typeof flags.spool === \"string\") forwarded.push(\"--spool\", flags.spool);\n if (typeof flags.host === \"string\") forwarded.push(\"--host\", flags.host);\n if (typeof flags.port === \"string\") forwarded.push(\"--port\", flags.port);\n if (typeof flags.listen === \"string\") forwarded.push(\"--listen\", flags.listen);\n ensurePrivateDir(paths.baseDir);\n const logFd = openSync(paths.logPath, \"a\");\n const child = spawn(process.execPath, [entry, ...forwarded], {\n detached: true,\n stdio: [\"ignore\", logFd, logFd],\n env: { ...process.env, ...env },\n });\n child.on(\"error\", (err) => stderr(`daemon failed to launch: ${sanitizeError(err)}`));\n child.unref();\n if (typeof child.pid !== \"number\") {\n stderr(\"failed to spawn daemon process\");\n return 1;\n }\n if (!recordChildPidOrTerminate(child.pid, paths, { host: config.host, port: config.port }, stderr)) return 1;\n const deadline = Date.now() + READINESS_TIMEOUT_MS;\n while (Date.now() < deadline) {\n if (!isProcessAlive(child.pid)) {\n removePidFileIfOwner(paths.pidPath, child.pid);\n stderr(`daemon exited during startup; see ${paths.logPath}`);\n return 1;\n }\n if (readPidRecord(paths.pidPath)?.instanceId) {\n stdout(`started daemon (pid ${child.pid}); listening; logs at ${paths.logPath}`);\n return 0;\n }\n await delay(100);\n }\n try {\n process.kill(child.pid, \"SIGTERM\");\n } catch {\n // already gone\n }\n removePidFileIfOwner(paths.pidPath, child.pid);\n stderr(`daemon did not become ready within ${READINESS_TIMEOUT_MS / 1000}s; terminated pid ${child.pid}. See ${paths.logPath}.`);\n return 1;\n }\n\n ensurePrivateDir(paths.baseDir);\n const token = resolveToken(paths, env, true);\n const helperRes = await resolveHelperBinaryPath(env);\n const axAvailable = helperRes.binaryPath !== null;\n // A custom --spool may live outside base-dir; prepare its parent (create 0700\n // if absent; refuse a symlinked or non-owner-only existing dir) without ever\n // chmod-ing an existing directory.\n ensureSpoolParentDir(paths.spoolPath);\n const spool = new Spool(paths.spoolPath);\n // Retention janitor: prune expired rows once on start so a long-idle spool is\n // trimmed even if the (native) capture loop never runs on this platform.\n spool.pruneOlderThan(config.spoolRetentionDays);\n let handle: DaemonHandle;\n try {\n handle = await startDaemon({\n spool,\n config,\n token,\n capturing: axAvailable,\n axAvailable,\n ocrAvailable: axAvailable,\n helperHint: helperRes.hint,\n });\n } catch (err) {\n spool.close();\n throw err;\n }\n try {\n writePidFile(paths.pidPath, process.pid, {\n instanceId: spool.meta(\"instance_id\"),\n host: handle.host,\n port: handle.port,\n });\n } catch (err) {\n await handle.close();\n spool.close();\n throw err;\n }\n stdout(`listening on ${handle.url}`);\n if (helperRes.hint) stdout(`note: ${helperRes.hint}`);\n const replayAbort = new AbortController();\n const replayTask: Promise<void> = replayDir\n ? superviseReplay(spool, replayDir, config, { stdout, stderr }, replayAbort.signal)\n : Promise.resolve();\n\n // Live capture loop (#1899 Part 1): when the native helper is available, poll\n // the frontmost window and store on change/settle/idle through the same\n // pipeline as replay. On Linux (no helper) the daemon serves + replays only.\n let scheduler: CaptureScheduler | null = null;\n if (helperRes.binaryPath !== null) {\n const processor = new CaptureProcessor(config);\n for (const fp of spool.latestFingerprints()) {\n processor.seed(fp.app, fp.windowTitle, fp.simhash, fp.capturedAtUtc);\n }\n scheduler = new CaptureScheduler(new NativeHelper(helperRes.binaryPath), processor, spool, config, {\n onError: (err) => stderr(`capture loop error: ${sanitizeError(err)}`),\n });\n scheduler.start();\n }\n\n return await new Promise<number>((resolve) => {\n let closing = false;\n const shutdown = () => {\n if (closing) return;\n closing = true;\n // Stop the capture loop and cancel replay, then drain BOTH before closing\n // the spool so no capture/ingestion write can ever hit a closed database.\n replayAbort.abort();\n void Promise.resolve(scheduler?.stop())\n .catch(() => undefined)\n .then(() => replayTask.catch(() => undefined))\n .then(() => handle.close().catch(() => undefined))\n .finally(() => {\n spool.close();\n removePidFileIfOwner(paths.pidPath, process.pid);\n resolve(0);\n });\n };\n process.once(\"SIGINT\", shutdown);\n process.once(\"SIGTERM\", shutdown);\n });\n}\n\nasync function cmdStop(\n paths: CapturePaths,\n flags: Record<string, string | boolean>,\n env: NodeJS.ProcessEnv,\n stdout: (l: string) => void,\n stderr: (l: string) => void,\n): Promise<number> {\n const record = readPidRecord(paths.pidPath);\n if (record === null || !isProcessAlive(record.pid)) {\n removePidFile(paths.pidPath);\n stdout(\"daemon not running\");\n return 0;\n }\n if (record.instanceId !== null) {\n const live = await probeIdentity(paths, env, recordHealthUrl(record, paths, stderr));\n if (live !== null && (live.instanceId !== record.instanceId || live.pid !== record.pid)) {\n stderr(\n `recorded pid ${record.pid} does not match the daemon serving this endpoint (identity/pid mismatch); ` +\n `not signalling and preserving ${paths.pidPath}.`,\n );\n return 1;\n }\n if (live === null && flags.force !== true) {\n stderr(\n `cannot confirm daemon identity for pid ${record.pid} (health unreachable); not signalling. ` +\n `Re-run \\`stop --force\\` to stop it anyway, or remove ${paths.pidPath}.`,\n );\n return 1;\n }\n } else if (flags.force !== true) {\n stderr(\n `cannot verify daemon identity for pid ${record.pid} (no recorded instance id); not signalling. ` +\n `Re-run \\`stop --force\\` to stop it anyway, or remove ${paths.pidPath}.`,\n );\n return 1;\n }\n try {\n process.kill(record.pid, \"SIGTERM\");\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === \"ESRCH\") {\n removePidFile(paths.pidPath);\n stdout(\"daemon not running\");\n return 0;\n }\n if (code === \"EPERM\") {\n stderr(`daemon (pid ${record.pid}) is running but not controllable from this user`);\n return 1;\n }\n throw err;\n }\n const deadline = Date.now() + STOP_TIMEOUT_MS;\n while (Date.now() < deadline) {\n if (!isProcessAlive(record.pid) || readPidRecord(paths.pidPath) === null) {\n stdout(`daemon (pid ${record.pid}) stopped`);\n return 0;\n }\n await delay(100);\n }\n stdout(`sent SIGTERM to daemon (pid ${record.pid}); still shutting down after ${STOP_TIMEOUT_MS / 1000}s`);\n return 0;\n}\n\nasync function cmdStatus(paths: CapturePaths, env: NodeJS.ProcessEnv, stdout: (l: string) => void, stderr: (l: string) => void): Promise<number> {\n const record = readPidRecord(paths.pidPath);\n if (record === null || !isProcessAlive(record.pid)) {\n stdout(\"status: not running\");\n return 0;\n }\n try {\n const res = await fetch(recordHealthUrl(record, paths, stderr), {\n headers: tokenHeader(paths, env),\n signal: AbortSignal.timeout(2000),\n });\n const body = await res.text();\n stdout(`status: running (pid ${record.pid}) — HTTP ${res.status} ${body}`);\n } catch (err) {\n stdout(`status: process alive (pid ${record.pid}) but health check failed (${sanitizeError(err)})`);\n }\n return 0;\n}\n\nfunction cmdInstallService(stdout: (l: string) => void): number {\n stdout(\n `install-service is not yet implemented for platform '${process.platform}'. ` +\n \"No service was installed. Run `remnic-capture-screen start` under your process manager \" +\n \"(launchd on macOS, systemd --user on Linux) once the native capture helper is installed.\",\n );\n return 0;\n}\n\nfunction cmdLogs(paths: CapturePaths, flags: Record<string, string | boolean>, stdout: (l: string) => void): number {\n if (!existsSync(paths.logPath)) {\n stdout(`no log file at ${paths.logPath}`);\n return 0;\n }\n const lines = typeof flags.lines === \"string\" ? coerceNumber(flags.lines, \"--lines\", { integer: true, min: 1 }) : 200;\n const all = readFileSync(paths.logPath, \"utf8\").split(\"\\n\");\n stdout(all.slice(Math.max(0, all.length - lines)).join(\"\\n\"));\n return 0;\n}\n\n/**\n * `test-snapshot`: report what WOULD be captured now and which deny rule (if\n * any) fired — WITHOUT storing. Honest about degradation: with no native helper\n * it reports the unavailable capabilities + install hint and captures nothing.\n */\nasync function cmdTestSnapshot(\n paths: CapturePaths,\n env: NodeJS.ProcessEnv,\n stdout: (l: string) => void,\n stderr: (l: string) => void,\n): Promise<number> {\n const config = loadConfigOrDefault(paths, stderr);\n const helperRes = await resolveHelperBinaryPath(env);\n if (helperRes.binaryPath === null) {\n stdout(\n JSON.stringify(\n {\n capturing: false,\n axAvailable: false,\n ocrAvailable: false,\n helperHint: helperRes.hint,\n note: \"no live snapshot: native capture helper unavailable\",\n },\n null,\n 2,\n ),\n );\n return 0;\n }\n const helper = new NativeHelper(helperRes.binaryPath);\n const processor = new CaptureProcessor(config);\n const decision = await captureViaHelper(helper, processor, config, new Date().toISOString());\n if (decision.action === \"denied\") {\n stdout(JSON.stringify({ action: \"denied\", rule: decision.rule }, null, 2));\n } else if (decision.action === \"skipped\") {\n stdout(JSON.stringify({ action: \"skipped\", reason: decision.reason }, null, 2));\n } else {\n const snap = decision.snapshot;\n stdout(\n JSON.stringify(\n {\n action: \"would-store\",\n app: snap.app,\n windowTitle: snap.windowTitle,\n textSource: snap.textSource,\n textPreview: snap.text.slice(0, 200),\n contentHash: snap.contentHash,\n simhash: snap.simhash,\n denyRule: null,\n },\n null,\n 2,\n ),\n );\n }\n return 0;\n}\n\nfunction usage(stdout: (l: string) => void): number {\n stdout(\n [\n `remnic-capture-screen v${CAPTURE_SCREEN_VERSION}`,\n \"usage: remnic-capture-screen <command> [flags]\",\n \"commands: init | start | stop | status | install-service | logs | test-snapshot\",\n \"start flags: --foreground --replay <dir> --host <h> --port <n> --listen <host:port> --spool <path> --base-dir <dir>\",\n `token: set ${CAPTURE_TOKEN_ENV} (never --auth-token)`,\n ].join(\"\\n\"),\n );\n return 0;\n}\n\nexport async function runCapture(io: CliIo): Promise<number> {\n const env = io.env ?? process.env;\n const stdout = io.stdout ?? ((line: string) => console.log(line));\n const stderr = io.stderr ?? ((line: string) => console.error(line));\n try {\n const parsed = parseArgs(io.argv);\n const paths = resolvePaths(parsed.flags, env);\n if (parsed.flags.help === true || parsed.positionals.includes(\"-h\") || parsed.positionals.includes(\"--help\")) {\n return usage(stdout);\n }\n if (parsed.positionals.length > 0) {\n stderr(`unexpected argument(s): ${parsed.positionals.join(\" \")}`);\n usage(stderr);\n return 2;\n }\n const allowedFlags = COMMAND_FLAGS[parsed.command];\n if (allowedFlags !== undefined) {\n for (const key of Object.keys(parsed.flags)) {\n if (!Object.hasOwn(GLOBAL_FLAGS, key) && !Object.hasOwn(allowedFlags, key)) {\n stderr(`flag --${key} is not valid for command '${parsed.command}'`);\n usage(stderr);\n return 2;\n }\n }\n }\n switch (parsed.command) {\n case \"init\":\n return cmdInit(paths, parsed.flags, stdout);\n case \"start\":\n return await cmdStart(paths, parsed.flags, env, stdout, stderr);\n case \"stop\":\n return await cmdStop(paths, parsed.flags, env, stdout, stderr);\n case \"status\":\n return await cmdStatus(paths, env, stdout, stderr);\n case \"install-service\":\n return cmdInstallService(stdout);\n case \"logs\":\n return cmdLogs(paths, parsed.flags, stdout);\n case \"test-snapshot\":\n return await cmdTestSnapshot(paths, env, stdout, stderr);\n case \"help\":\n case \"--help\":\n case \"-h\":\n return usage(stdout);\n default:\n stderr(`unknown command '${parsed.command}'`);\n usage(stderr);\n return 2;\n }\n } catch (err) {\n if (err instanceof CaptureConfigError || err instanceof CaptureInputError) {\n stderr(`error: ${err.message}`);\n return err instanceof CaptureInputError ? 2 : 1;\n }\n stderr(`error: ${sanitizeError(err)}`);\n return 1;\n }\n}\n","/**\n * Event-driven live capture loop (#1899 Part 1).\n *\n * Each poll fetches the frontmost AX snapshot. A change in the foreground\n * identity (app, windowTitle, browserUrl) opens a settle window; once the\n * foreground has been stable for `settleMs` the snapshot is run through the\n * pipeline and stored. A foreground that never changes is re-sampled at least\n * every `idleFallbackSeconds` (dedup drops it when unchanged). This is pure\n * orchestration — the native macOS helper supplies the snapshots, and the same\n * pipeline (deny-list, redaction, dedup, supersession) that `--replay` uses is\n * applied here, so behaviour is fully testable off-macOS with a fake helper.\n */\n\nimport type { CaptureProcessor } from \"./capture.js\";\nimport type { DaemonConfig } from \"./config.js\";\nimport type { NativeHelper } from \"./helper.js\";\nimport { captureFromSnapshot } from \"./live.js\";\nimport type { Spool } from \"./spool.js\";\n\n/** Injectable clock/timer so the loop is deterministically testable. */\nexport interface SchedulerClock {\n now(): number;\n setInterval(fn: () => void, ms: number): ReturnType<typeof setInterval>;\n clearInterval(handle: ReturnType<typeof setInterval>): void;\n}\n\nconst systemClock: SchedulerClock = {\n now: () => Date.now(),\n setInterval: (fn, ms) => setInterval(fn, ms),\n clearInterval: (handle) => clearInterval(handle),\n};\n\nexport interface SchedulerHooks {\n /** Called when a poll cycle throws (helper failure); the loop keeps running. */\n onError?: (err: unknown) => void;\n /** Called after a snapshot is stored. */\n onStore?: (app: string, windowTitle: string) => void;\n}\n\nexport class CaptureScheduler {\n readonly #helper: NativeHelper;\n readonly #processor: CaptureProcessor;\n readonly #spool: Spool;\n readonly #config: DaemonConfig;\n readonly #hooks: SchedulerHooks;\n readonly #clock: SchedulerClock;\n #timer: ReturnType<typeof setInterval> | null = null;\n #inflight = false;\n #current: Promise<void> | null = null;\n #lastKey: string | null = null;\n #changeAt = 0;\n #pending = false;\n #lastCaptureAt = Number.NEGATIVE_INFINITY;\n\n constructor(\n helper: NativeHelper,\n processor: CaptureProcessor,\n spool: Spool,\n config: DaemonConfig,\n hooks: SchedulerHooks = {},\n clock: SchedulerClock = systemClock,\n ) {\n this.#helper = helper;\n this.#processor = processor;\n this.#spool = spool;\n this.#config = config;\n this.#hooks = hooks;\n this.#clock = clock;\n }\n\n /** Begin polling. Idempotent; stops automatically when `signal` aborts. */\n start(signal?: AbortSignal): void {\n if (this.#timer !== null) return;\n this.#timer = this.#clock.setInterval(() => {\n this.#current = this.tick();\n }, this.#config.pollIntervalMs);\n signal?.addEventListener(\"abort\", () => void this.stop(), { once: true });\n }\n\n /** Stop polling and await any in-flight tick, so a caller can safely close\n * shared resources (the spool) once this resolves. */\n async stop(): Promise<void> {\n if (this.#timer !== null) {\n this.#clock.clearInterval(this.#timer);\n this.#timer = null;\n }\n if (this.#current !== null) {\n await this.#current.catch(() => undefined);\n }\n }\n\n /**\n * One poll cycle. Exposed (not private) so tests can drive the loop\n * deterministically with a fake clock instead of real timers. Overlapping\n * ticks are skipped so a slow helper never runs two captures at once.\n */\n async tick(): Promise<void> {\n if (this.#inflight) return;\n this.#inflight = true;\n try {\n const snap = await this.#helper.axSnapshot({ frontmost: true, maxNodes: this.#config.maxNodes });\n const key = `${snap.app}\\u0000${snap.windowTitle}\\u0000${snap.browserUrl ?? \"\"}`;\n const now = this.#clock.now();\n // Anchor the idle heartbeat to the first poll so the fallback measures from\n // startup, not since epoch — otherwise it would preempt the settle window.\n if (this.#lastCaptureAt === Number.NEGATIVE_INFINITY) {\n this.#lastCaptureAt = now;\n }\n\n if (key !== this.#lastKey) {\n // Foreground changed — open a settle window; capture only once stable.\n this.#lastKey = key;\n this.#changeAt = now;\n this.#pending = true;\n return;\n }\n\n const settled = this.#pending && now - this.#changeAt >= this.#config.settleMs;\n // Idle is a heartbeat for an UNCHANGED foreground; it must not preempt a\n // change that is still inside its settle window.\n const idle = !this.#pending && now - this.#lastCaptureAt >= this.#config.idleFallbackSeconds * 1000;\n if (!settled && !idle) return;\n\n const decision = await captureFromSnapshot(\n snap,\n this.#helper,\n this.#processor,\n this.#config,\n new Date(now).toISOString(),\n );\n this.#pending = false;\n this.#lastCaptureAt = now;\n if (decision.action === \"store\") {\n this.#spool.insertSnapshot(decision.snapshot, this.#config.sessionGapSeconds);\n this.#hooks.onStore?.(snap.app, snap.windowTitle);\n }\n } catch (err) {\n this.#hooks.onError?.(err);\n } finally {\n this.#inflight = false;\n }\n }\n}\n"],"mappings":"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","names":["readFileSync","Buffer","randomBytes","mkdirSync","readFileSync","writeFileSync","path","Buffer","Buffer","Buffer","Buffer","path","readFileSync","path","asObject","requireString","path","readFileSync","chmodSync","chmodSync","spawn","chmodSync","existsSync","lstatSync","mkdirSync","readFileSync","writeFileSync","existsSync","readFileSync","lstatSync","mkdirSync","chmodSync","writeFileSync","spawn"]}
|
package/dist/cli-bin.js
CHANGED
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remnic/capture-screen",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.66.0",
|
|
4
4
|
"description": "Desktop screen-activity capture daemon for Remnic — local spool, loopback HTTP API, and replay ingestion for the activity source `screen` (à-la-carte)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
"node": ">=22.13.0"
|
|
26
26
|
},
|
|
27
27
|
"peerDependencies": {
|
|
28
|
-
"@remnic/capture-native-darwin-arm64": "^9.
|
|
29
|
-
"@remnic/capture-native-darwin-x64": "^9.
|
|
28
|
+
"@remnic/capture-native-darwin-arm64": "^9.66.0",
|
|
29
|
+
"@remnic/capture-native-darwin-x64": "^9.66.0"
|
|
30
30
|
},
|
|
31
31
|
"peerDependenciesMeta": {
|
|
32
32
|
"@remnic/capture-native-darwin-arm64": {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/axtree.ts","../src/simhash.ts","../src/dedup.ts","../src/denylist.ts","../src/errors.ts","../src/redact.ts","../src/capture.ts","../src/constants.ts","../src/config.ts","../src/util.ts","../src/coerce.ts","../src/control.ts","../src/token.ts","../src/validate.ts","../src/daemon.ts","../src/paths.ts","../src/helper.ts","../src/live.ts","../src/replay.ts","../src/daywindow.ts","../src/spool.ts","../src/cli.ts","../src/scheduler.ts"],"sourcesContent":["/**\n * Accessibility-tree text extraction. Walks a macOS AX-tree JSON snapshot and\n * concatenates the visible text, with three safety filters baked in:\n *\n * - AXSecureTextField nodes are skipped entirely (never read a password box),\n * including their subtree.\n * - Off-screen nodes (`offScreen: true`) are skipped with their subtree — text\n * the user cannot see is not \"on screen\".\n * - Traversal is bounded to `maxNodes` visited nodes, so a pathological tree\n * cannot exhaust memory/CPU; the result is flagged `truncated` when the cap\n * is hit.\n *\n * The shape is intentionally permissive: real AX dumps carry many roles and the\n * text can live on any of value/title/description/label. Unknown fields are\n * ignored.\n */\n\nexport const SECURE_ROLE = \"AXSecureTextField\";\n\nexport interface AxNode {\n role?: string;\n value?: string;\n title?: string;\n description?: string;\n label?: string;\n offScreen?: boolean;\n children?: AxNode[];\n}\n\nexport interface AxExtractResult {\n text: string;\n /** Nodes actually visited (bounded by maxNodes). */\n nodes: number;\n /** True when the maxNodes cap stopped traversal before the tree was exhausted. */\n truncated: boolean;\n}\n\nfunction nodeText(node: AxNode): string {\n const pieces: string[] = [];\n for (const field of [node.value, node.title, node.description, node.label]) {\n if (typeof field === \"string\" && field.trim().length > 0) pieces.push(field.trim());\n }\n return pieces.join(\" \");\n}\n\n/**\n * Extract visible, non-secure text from an AX tree. Iterative DFS with an\n * explicit stack so a deep tree cannot overflow the call stack, and a visited\n * counter that enforces the node cap.\n */\nexport function extractAxText(root: AxNode, maxNodes: number): AxExtractResult {\n const lines: string[] = [];\n const stack: AxNode[] = [root];\n let visited = 0;\n let truncated = false;\n while (stack.length > 0) {\n if (visited >= maxNodes) {\n truncated = true;\n break;\n }\n const node = stack.pop() as AxNode;\n visited += 1;\n if (node.offScreen === true) continue;\n if (node.role === SECURE_ROLE) continue;\n const text = nodeText(node);\n if (text.length > 0) lines.push(text);\n if (Array.isArray(node.children)) {\n // Push in reverse so children are visited in document order.\n for (let i = node.children.length - 1; i >= 0; i--) stack.push(node.children[i]);\n }\n }\n return { text: lines.join(\"\\n\"), nodes: visited, truncated };\n}\n","/**\n * 64-bit word-shingle SimHash for near-duplicate screen-text detection.\n * Text is lower-cased and tokenized to Unicode letter/number runs (so CJK,\n * Cyrillic, and other non-ASCII scripts tokenize instead of collapsing to an\n * empty set), then shingled into overlapping 2-word grams. Each gram is hashed\n * with 64-bit FNV-1a; the signed\n * per-bit vote across all grams yields a 64-bit fingerprint whose Hamming\n * distance tracks textual similarity: identical text → distance 0, a small edit\n * → a small distance, unrelated text → a large distance. Everything is BigInt\n * so the full 64 bits are exact.\n */\n\nconst MASK64 = (1n << 64n) - 1n;\nconst FNV_OFFSET = 14695981039346656037n;\nconst FNV_PRIME = 1099511628211n;\nconst SHINGLE_SIZE = 2;\n\nfunction tokenize(text: string): string[] {\n return text.toLowerCase().match(/[\\p{L}\\p{N}]+/gu) ?? [];\n}\n\nfunction shingles(tokens: string[]): string[] {\n if (tokens.length < SHINGLE_SIZE) {\n return tokens.length > 0 ? [tokens.join(\" \")] : [];\n }\n const out: string[] = [];\n for (let i = 0; i + SHINGLE_SIZE <= tokens.length; i++) {\n out.push(tokens.slice(i, i + SHINGLE_SIZE).join(\" \"));\n }\n return out;\n}\n\n/** 64-bit FNV-1a over the UTF-16 code units of `s`. */\nfunction hash64(s: string): bigint {\n let h = FNV_OFFSET;\n for (let i = 0; i < s.length; i++) {\n h ^= BigInt(s.charCodeAt(i));\n h = (h * FNV_PRIME) & MASK64;\n }\n return h;\n}\n\n/** 64-bit SimHash fingerprint of `text` (0n for empty/whitespace-only text). */\nexport function simhash(text: string): bigint {\n const grams = shingles(tokenize(text));\n if (grams.length === 0) return 0n;\n const votes = new Array<number>(64).fill(0);\n for (const gram of grams) {\n const h = hash64(gram);\n for (let b = 0; b < 64; b++) {\n votes[b] += (h >> BigInt(b)) & 1n ? 1 : -1;\n }\n }\n let out = 0n;\n for (let b = 0; b < 64; b++) {\n if (votes[b] > 0) out |= 1n << BigInt(b);\n }\n return out;\n}\n\n/** Hamming distance between two 64-bit fingerprints (0..64). */\nexport function hammingDistance(a: bigint, b: bigint): number {\n let x = (a ^ b) & MASK64;\n let count = 0;\n while (x !== 0n) {\n count += Number(x & 1n);\n x >>= 1n;\n }\n return count;\n}\n\n/** Fixed-width 16-char hex rendering (wire/simhash column form). */\nexport function simhashToHex(h: bigint): string {\n return (h & MASK64).toString(16).padStart(16, \"0\");\n}\n\nexport function simhashFromHex(hex: string): bigint {\n return BigInt(`0x${hex}`) & MASK64;\n}\n","/**\n * Per-window near-duplicate suppression. Keyed by (app, windowTitle): for each\n * window we remember the last STORED snapshot's SimHash and capture instant. A\n * new snapshot of the same window is stored only when it is meaningfully\n * different (Hamming distance > threshold) OR enough time has elapsed since the\n * last store (ttlSeconds), so a long unchanging window is refreshed periodically\n * while a stream of near-identical scroll states collapses to a few rows.\n * Distinct windows never dedup against each other (independent cache entries).\n *\n * The clock is the snapshot's own capturedAt (passed in as ms), never a\n * wall-clock read — so replay/fixtures are deterministic.\n */\n\nimport { hammingDistance } from \"./simhash.js\";\n\ninterface Entry {\n hash: bigint;\n atMs: number;\n}\n\nexport class DedupCache {\n #last = new Map<string, Entry>();\n readonly #threshold: number;\n readonly #ttlSeconds: number;\n\n constructor(threshold: number, ttlSeconds: number) {\n this.#threshold = threshold;\n this.#ttlSeconds = ttlSeconds;\n }\n\n static #key(app: string, windowTitle: string): string {\n // NUL separator: app/title are arbitrary text, so a printable delimiter\n // could be forged by a title to alias a different (app,title) pair.\n return `${app}\\u0000${windowTitle}`;\n }\n\n /** Seed the last-stored fingerprint for a window (used to prime from the spool). */\n seed(app: string, windowTitle: string, hash: bigint, atMs: number): void {\n this.#last.set(DedupCache.#key(app, windowTitle), { hash, atMs });\n }\n\n /**\n * Decide whether a snapshot should be stored, updating the cache when it is.\n * First snapshot of a window always stores. A negative elapsed (out-of-order\n * capture) stores defensively rather than dropping data.\n */\n shouldStore(app: string, windowTitle: string, hash: bigint, atMs: number): boolean {\n const key = DedupCache.#key(app, windowTitle);\n const prev = this.#last.get(key);\n let store: boolean;\n if (prev === undefined) {\n store = true;\n } else {\n const elapsedSeconds = (atMs - prev.atMs) / 1000;\n store =\n elapsedSeconds < 0 ||\n elapsedSeconds >= this.#ttlSeconds ||\n hammingDistance(hash, prev.hash) > this.#threshold;\n }\n if (store) this.#last.set(key, { hash, atMs });\n return store;\n }\n}\n","/**\n * Capture-time deny-lists, checked FIRST — before any text extraction, hashing,\n * or spool write. A match records NOTHING (not even metadata): the snapshot is\n * dropped whole. Three independent lists, each glob/substring matched\n * case-insensitively: application name, window title, and browser URL. Built-in\n * defaults cover common secret managers and private-browsing windows; the\n * user's config entries are additive.\n */\n\n/** Secret managers whose windows must never be captured. */\nexport const DEFAULT_DENY_APPS: readonly string[] = [\"1Password*\", \"Bitwarden*\", \"KeePass*\"];\n\n/** Private/incognito window-title heuristics (browsers signal these in the title). */\nexport const DEFAULT_DENY_TITLES: readonly string[] = [\n \"*incognito*\",\n \"*private browsing*\",\n \"*inprivate*\",\n \"*private window*\",\n];\n\n/** No default URL denials — URL patterns are user-supplied (site-specific). */\nexport const DEFAULT_DENY_URLS: readonly string[] = [];\n\nexport interface DenyLists {\n apps: readonly string[];\n titles: readonly string[];\n urls: readonly string[];\n}\n\nexport interface DenyCandidate {\n app: string;\n windowTitle: string;\n browserUrl?: string | null;\n}\n\n/** Compile a `*`/`?` glob to an anchored, case-insensitive RegExp. */\nexport function globToRegExp(glob: string): RegExp {\n const escaped = glob.replace(/[.+^${}()|[\\]\\\\]/g, \"\\\\$&\").replace(/\\*/g, \".*\").replace(/\\?/g, \".\");\n return new RegExp(`^${escaped}$`, \"i\");\n}\n\n/** True when `value` matches any glob in `patterns` (case-insensitive). */\nexport function matchesAnyGlob(patterns: readonly string[], value: string): boolean {\n return patterns.some((pattern) => globToRegExp(pattern).test(value));\n}\n\nfunction firstMatch(patterns: readonly string[], value: string, kind: string): string | null {\n for (const pattern of patterns) {\n if (globToRegExp(pattern).test(value)) return `${kind}:${pattern}`;\n }\n return null;\n}\n\n/**\n * The first deny rule that fires for this candidate, or null. Built-in defaults\n * are always checked in addition to the user lists. The returned string names\n * the rule (`app:1Password*`, `title:*incognito*`, `url:...`) for the\n * `test-snapshot` diagnostic.\n */\nexport function matchDenyRule(candidate: DenyCandidate, lists: DenyLists): string | null {\n const appRule = firstMatch([...DEFAULT_DENY_APPS, ...lists.apps], candidate.app, \"app\");\n if (appRule !== null) return appRule;\n const titleRule = firstMatch([...DEFAULT_DENY_TITLES, ...lists.titles], candidate.windowTitle, \"title\");\n if (titleRule !== null) return titleRule;\n if (typeof candidate.browserUrl === \"string\" && candidate.browserUrl.length > 0) {\n const urlRule = firstMatch([...DEFAULT_DENY_URLS, ...lists.urls], candidate.browserUrl, \"url\");\n if (urlRule !== null) return urlRule;\n }\n return null;\n}\n","/**\n * Error taxonomy for @remnic/capture-screen.\n *\n * Two authored-message classes, mirroring @remnic/capture-audio: configuration\n * problems and caller-correctable input. Both carry operator-safe messages\n * (never foreign error text, never captured screen text, never credentials).\n * The HTTP layer maps CaptureInputError to 400; anything else is a backend\n * fault (500).\n */\n\n/** Config load/validation failure — surfaced loudly, never silently defaulted. */\nexport class CaptureConfigError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"CaptureConfigError\";\n }\n}\n\n/** Caller-correctable request/CLI input — maps to HTTP 400. */\nexport class CaptureInputError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"CaptureInputError\";\n }\n}\n","/**\n * Daemon-side redaction, applied to snapshot text BEFORE it is hashed or\n * written to the spool. Built-in patterns catch US SSNs and payment-card\n * numbers (13–19 digits, optionally space/dash grouped, Luhn-valid); the user's\n * `redactionPatterns` (regex source strings) are applied in addition. Every\n * match is replaced with a fixed placeholder so the redacted text is stable\n * (identical inputs dedup identically).\n */\n\nimport { CaptureConfigError } from \"./errors.js\";\n\nexport const REDACTION_PLACEHOLDER = \"[REDACTED]\";\n\nconst SSN_RE = /\\b\\d{3}-\\d{2}-\\d{4}\\b/g;\n/** Candidate card runs: 13–19 digits with optional single space/dash separators. */\nconst CARD_RE = /\\b(?:\\d[ -]?){13,19}\\b/g;\n\nfunction luhnValid(digits: string): boolean {\n let sum = 0;\n let double = false;\n for (let i = digits.length - 1; i >= 0; i--) {\n let d = digits.charCodeAt(i) - 48;\n if (double) {\n d *= 2;\n if (d > 9) d -= 9;\n }\n sum += d;\n double = !double;\n }\n return sum % 10 === 0;\n}\n\nfunction redactCards(text: string): string {\n return text.replace(CARD_RE, (match) => {\n const digits = match.replace(/[ -]/g, \"\");\n if (digits.length < 13 || digits.length > 19 || !luhnValid(digits)) return match;\n return REDACTION_PLACEHOLDER;\n });\n}\n\n/** Compile user regex source strings once; an invalid pattern fails loudly. */\nexport function compileRedactionPatterns(sources: readonly string[]): RegExp[] {\n return sources.map((source) => {\n try {\n return new RegExp(source, \"g\");\n } catch {\n throw new CaptureConfigError(`redactionPatterns: '${source}' is not a valid regular expression`);\n }\n });\n}\n\n/** Apply built-in (SSN, card) then user redactions to `text`. */\nexport function redactText(text: string, userPatterns: readonly RegExp[] = []): string {\n let out = text.replace(SSN_RE, REDACTION_PLACEHOLDER);\n out = redactCards(out);\n for (const pattern of userPatterns) {\n // Reset lastIndex: a shared global RegExp carries state between calls.\n pattern.lastIndex = 0;\n out = out.replace(pattern, REDACTION_PLACEHOLDER);\n }\n return out;\n}\n","/**\n * Capture-time processing pipeline (pure, hardware-free). A raw candidate —\n * frontmost app/window plus either an AX tree or pre-extracted text — is turned\n * into a decision: dropped by a deny rule, skipped (OCR unavailable / deduped),\n * or a fully-formed spool snapshot. The steps, in order:\n *\n * 1. Deny-lists FIRST — a match records NOTHING (not even metadata).\n * 2. Text: pre-extracted text is used as-is; otherwise the AX tree is walked\n * (secure fields + off-screen nodes excluded, bounded to maxNodes). A\n * terminal-class window, or an AX tree with no visible text, routes to the\n * OCR seam; when OCR is unavailable the snapshot is skipped (reflected in\n * health) rather than crashing.\n * 3. Redaction — SSN/card + user patterns, before hashing or storage.\n * 4. Dedup — per-window SimHash gate (threshold / TTL).\n * 5. Content hash — length-prefixed SHA-256 so control chars can't collide.\n *\n * The OCR step is a seam (an injected callback), so the daemon wires the native\n * helper while tests inject a fake without any macOS binary.\n */\n\nimport { createHash } from \"node:crypto\";\n\nimport { extractAxText, type AxNode } from \"./axtree.js\";\nimport { DedupCache } from \"./dedup.js\";\nimport { matchDenyRule, matchesAnyGlob } from \"./denylist.js\";\nimport { compileRedactionPatterns, redactText } from \"./redact.js\";\nimport { simhash, simhashToHex } from \"./simhash.js\";\nimport type { DaemonConfig } from \"./config.js\";\nimport type { DaemonSnapshot, SnapshotInput, TextSource } from \"./spool.js\";\n\n/** Terminal-class apps whose windows expose no useful AX text (route to OCR). */\nexport const DEFAULT_TERMINAL_APPS: readonly string[] = [\n \"Terminal\",\n \"iTerm2\",\n \"iTerm\",\n \"Alacritty\",\n \"kitty\",\n \"WezTerm\",\n \"Warp\",\n \"Hyper\",\n \"Konsole\",\n \"gnome-terminal*\",\n];\n\n/**\n * True when `app` is a terminal-class window (routes to OCR — terminals expose\n * no useful AX text). `includeDefaults` prepends DEFAULT_TERMINAL_APPS; pass\n * false when `terminalApps` is already the merged list.\n */\nexport function isTerminalApp(app: string, terminalApps: readonly string[], includeDefaults = true): boolean {\n const patterns = includeDefaults ? [...DEFAULT_TERMINAL_APPS, ...terminalApps] : terminalApps;\n return matchesAnyGlob(patterns, app);\n}\n\n/** A raw capture candidate before processing. Provide `text` OR `ax`. */\nexport interface CaptureCandidate {\n capturedAtUtc: string;\n app: string;\n windowTitle: string;\n browserUrl?: string | null;\n /** Pre-extracted text (skips AX walking); source defaults to \"ax\". */\n text?: string;\n textSource?: TextSource;\n /** Accessibility tree to extract from when `text` is absent. */\n ax?: AxNode;\n}\n\nexport type CaptureDecision =\n | { action: \"denied\"; rule: string }\n | { action: \"skipped\"; reason: \"ocr-unavailable\" | \"dedup\" }\n | { action: \"store\"; snapshot: SnapshotInput };\n\n/** OCR seam: returns extracted text for a candidate, or null when unavailable. */\nexport type OcrFn = (candidate: CaptureCandidate) => string | null;\n\ninterface ContentHashFields {\n capturedAtUtc: string;\n app: string;\n windowTitle: string;\n browserUrl: string | null;\n text: string;\n textSource: string;\n}\n\n/**\n * SHA-256 over length-prefixed fields so control characters (incl. NUL) in the\n * captured text cannot make two distinct snapshots collide — a collision would\n * silently drop a valid capture via the UNIQUE content_hash + INSERT OR IGNORE.\n */\nexport function contentHash(fields: ContentHashFields): string {\n const hash = createHash(\"sha256\");\n const parts = [fields.capturedAtUtc, fields.app, fields.windowTitle, fields.browserUrl ?? \"\", fields.text, fields.textSource];\n for (const field of parts) {\n hash.update(`${Buffer.byteLength(field)}:`).update(field);\n }\n return hash.digest(\"hex\");\n}\n\nexport class CaptureProcessor {\n readonly #denyApps: string[];\n readonly #denyTitles: string[];\n readonly #denyUrls: string[];\n readonly #terminalApps: string[];\n readonly #maxNodes: number;\n readonly #redaction: RegExp[];\n readonly #cache: DedupCache;\n readonly #ocr: OcrFn | undefined;\n\n constructor(config: DaemonConfig, ocr?: OcrFn) {\n this.#denyApps = config.denyApps;\n this.#denyTitles = config.denyTitles;\n this.#denyUrls = config.denyUrls;\n this.#terminalApps = [...DEFAULT_TERMINAL_APPS, ...config.terminalApps];\n this.#maxNodes = config.maxNodes;\n this.#redaction = compileRedactionPatterns(config.redactionPatterns);\n this.#cache = new DedupCache(config.simhashThreshold, config.dedupTtlSeconds);\n this.#ocr = ocr;\n }\n\n /** Seed the dedup cache from prior spool state so restarts don't re-store. */\n seed(app: string, windowTitle: string, simhashHex: string, capturedAtUtc: string): void {\n this.#cache.seed(app, windowTitle, BigInt(`0x${simhashHex}`), Date.parse(capturedAtUtc));\n }\n\n process(candidate: CaptureCandidate): CaptureDecision {\n const denyRule = matchDenyRule(\n { app: candidate.app, windowTitle: candidate.windowTitle, browserUrl: candidate.browserUrl },\n { apps: this.#denyApps, titles: this.#denyTitles, urls: this.#denyUrls },\n );\n if (denyRule !== null) return { action: \"denied\", rule: denyRule };\n\n const extracted = this.#extractText(candidate);\n if (extracted === null) return { action: \"skipped\", reason: \"ocr-unavailable\" };\n const { source } = extracted;\n const text = redactText(extracted.text, this.#redaction);\n\n const fingerprint = simhash(text);\n const atMs = Date.parse(candidate.capturedAtUtc);\n if (!this.#cache.shouldStore(candidate.app, candidate.windowTitle, fingerprint, atMs)) {\n return { action: \"skipped\", reason: \"dedup\" };\n }\n\n const browserUrl = candidate.browserUrl ?? null;\n return {\n action: \"store\",\n snapshot: {\n capturedAtUtc: candidate.capturedAtUtc,\n app: candidate.app,\n windowTitle: candidate.windowTitle,\n browserUrl,\n text,\n textSource: source,\n contentHash: contentHash({\n capturedAtUtc: candidate.capturedAtUtc,\n app: candidate.app,\n windowTitle: candidate.windowTitle,\n browserUrl,\n text,\n textSource: source,\n }),\n simhash: simhashToHex(fingerprint),\n },\n };\n }\n\n /** Resolve visible text + its source, or null when OCR was needed but unavailable. */\n #extractText(candidate: CaptureCandidate): { text: string; source: TextSource } | null {\n if (typeof candidate.text === \"string\") {\n return { text: candidate.text, source: candidate.textSource ?? \"ax\" };\n }\n const axText = candidate.ax === undefined ? \"\" : extractAxText(candidate.ax, this.#maxNodes).text;\n const needsOcr = isTerminalApp(candidate.app, this.#terminalApps, false) || axText.trim() === \"\";\n if (!needsOcr) return { text: axText, source: \"ax\" };\n const ocrText = this.#ocr === undefined ? null : this.#ocr(candidate);\n if (ocrText !== null && ocrText.trim() !== \"\") return { text: ocrText, source: \"ocr\" };\n return null;\n }\n}\n\nexport interface AppStat {\n app: string;\n seconds: number;\n snapshotCount: number;\n}\n\nexport interface DayStats {\n date: string;\n timezone: string;\n snapshotCount: number;\n totalSeconds: number;\n apps: AppStat[];\n}\n\n/**\n * Per-app time attribution for a day. Each snapshot is credited the gap to the\n * next snapshot (capped at maxDwellSeconds); the final snapshot contributes no\n * dwell (no following instant to bound it). Apps sort by seconds desc, then name.\n */\nexport function computeStats(\n snapshots: DaemonSnapshot[],\n date: string,\n timezone: string,\n maxDwellSeconds: number,\n): DayStats {\n const ordered = [...snapshots].sort((a, b) => {\n const at = Date.parse(a.capturedAtUtc);\n const bt = Date.parse(b.capturedAtUtc);\n if (at !== bt) return at - bt;\n return a.id - b.id;\n });\n const seconds = new Map<string, number>();\n const counts = new Map<string, number>();\n let totalSeconds = 0;\n for (let i = 0; i < ordered.length; i++) {\n const snap = ordered[i];\n counts.set(snap.app, (counts.get(snap.app) ?? 0) + 1);\n if (i + 1 < ordered.length) {\n const gap = (Date.parse(ordered[i + 1].capturedAtUtc) - Date.parse(snap.capturedAtUtc)) / 1000;\n const dwell = Math.max(0, Math.min(gap, maxDwellSeconds));\n seconds.set(snap.app, (seconds.get(snap.app) ?? 0) + dwell);\n totalSeconds += dwell;\n }\n }\n const apps: AppStat[] = [...counts.keys()]\n .map((app) => ({ app, seconds: seconds.get(app) ?? 0, snapshotCount: counts.get(app) ?? 0 }))\n .sort((a, b) => (b.seconds !== a.seconds ? b.seconds - a.seconds : a.app < b.app ? -1 : a.app > b.app ? 1 : 0));\n return { date, timezone, snapshotCount: ordered.length, totalSeconds, apps };\n}\n","/** Package-wide constants for @remnic/capture-screen. */\n\n/**\n * Reported by GET /v1/health. Kept in sync with package.json by the release\n * tooling; the health endpoint tolerates drift because the connector never\n * gates on an exact match (it reads `ok`).\n */\nexport const CAPTURE_SCREEN_VERSION = \"9.14.0\";\n\n/** Loopback default; capture is local-first (charter). */\nexport const DEFAULT_HOST = \"127.0.0.1\";\nexport const DEFAULT_PORT = 4341;\n\n/** Spool schema version, persisted in the `meta` table. */\nexport const SPOOL_SCHEMA_VERSION = 1;\n\n/** Upper bound for the snapshots `limit` query parameter. */\nexport const MAX_SNAPSHOTS_LIMIT = 500;\n/** Default page size when `limit` is omitted. */\nexport const DEFAULT_SNAPSHOTS_LIMIT = 100;\n\n/** Default capture-time processing knobs (all overridable in config). */\nexport const DEFAULT_SPOOL_RETENTION_DAYS = 14;\nexport const DEFAULT_SIMHASH_THRESHOLD = 10;\nexport const DEFAULT_DEDUP_TTL_SECONDS = 60;\n/** Two snapshots of the same window within this gap belong to one session. */\nexport const DEFAULT_SESSION_GAP_SECONDS = 300;\n/** AX-tree traversal cap (nodes) — bounds pathological accessibility trees. */\nexport const DEFAULT_MAX_NODES = 4000;\n/** Per-snapshot dwell cap for /v1/stats time attribution. */\nexport const DEFAULT_MAX_DWELL_SECONDS = 300;\n/** Live capture loop cadence (#1899 Part 1; all overridable in config). */\n/** How often the loop polls the frontmost AX snapshot for a change. */\nexport const DEFAULT_POLL_INTERVAL_MS = 1000;\n/** Foreground must be stable this long after a change before a snapshot is stored. */\nexport const DEFAULT_SETTLE_MS = 500;\n/** Re-sample an unchanging foreground at least this often (dedup drops repeats). */\nexport const DEFAULT_IDLE_FALLBACK_SECONDS = 30;\n","/**\n * Daemon config (`~/.remnic/capture-screen/screen.json`), created by\n * `remnic-capture-screen init`. Strict and loud: an absent field takes the\n * documented default, but a present-but-invalid value throws CaptureConfigError\n * (no silent defaulting). Ports are integers in [1, 65535]; string arrays\n * (deny-lists, terminal-app globs, redaction patterns) reject non-string\n * members.\n *\n * Deny-lists, terminal-app globs, and redaction patterns here are ADDITIVE to\n * the built-in defaults (see denylist.ts / redact.ts / capture.ts).\n */\n\nimport { readFileSync } from \"node:fs\";\n\nimport { coerceNumber, coerceStringArray } from \"./coerce.js\";\nimport {\n DEFAULT_IDLE_FALLBACK_SECONDS,\n DEFAULT_POLL_INTERVAL_MS,\n DEFAULT_SETTLE_MS,\n DEFAULT_DEDUP_TTL_SECONDS,\n DEFAULT_HOST,\n DEFAULT_MAX_DWELL_SECONDS,\n DEFAULT_MAX_NODES,\n DEFAULT_PORT,\n DEFAULT_SESSION_GAP_SECONDS,\n DEFAULT_SIMHASH_THRESHOLD,\n DEFAULT_SPOOL_RETENTION_DAYS,\n} from \"./constants.js\";\nimport { CaptureConfigError } from \"./errors.js\";\nimport { describeValue } from \"./util.js\";\n\nexport interface DaemonConfig {\n host: string;\n port: number;\n spoolRetentionDays: number;\n simhashThreshold: number;\n dedupTtlSeconds: number;\n sessionGapSeconds: number;\n maxNodes: number;\n maxDwellSeconds: number;\n /** Live capture loop: poll interval (ms) for foreground-change detection. */\n pollIntervalMs: number;\n /** Live capture loop: settle window (ms) after a foreground change. */\n settleMs: number;\n /** Live capture loop: idle re-sample cadence (seconds). */\n idleFallbackSeconds: number;\n /** Additive deny-list globs (checked in addition to the built-in defaults). */\n denyApps: string[];\n denyTitles: string[];\n denyUrls: string[];\n /** Additive terminal-class app globs (route to OCR). */\n terminalApps: string[];\n /** Additive user redaction regex source strings. */\n redactionPatterns: string[];\n}\n\nexport function defaultDaemonConfig(): DaemonConfig {\n return {\n host: DEFAULT_HOST,\n port: DEFAULT_PORT,\n spoolRetentionDays: DEFAULT_SPOOL_RETENTION_DAYS,\n simhashThreshold: DEFAULT_SIMHASH_THRESHOLD,\n dedupTtlSeconds: DEFAULT_DEDUP_TTL_SECONDS,\n sessionGapSeconds: DEFAULT_SESSION_GAP_SECONDS,\n maxNodes: DEFAULT_MAX_NODES,\n maxDwellSeconds: DEFAULT_MAX_DWELL_SECONDS,\n pollIntervalMs: DEFAULT_POLL_INTERVAL_MS,\n settleMs: DEFAULT_SETTLE_MS,\n idleFallbackSeconds: DEFAULT_IDLE_FALLBACK_SECONDS,\n denyApps: [],\n denyTitles: [],\n denyUrls: [],\n terminalApps: [],\n redactionPatterns: [],\n };\n}\n\nconst KNOWN_TOP_KEYS: Record<string, true> = {\n host: true,\n port: true,\n spoolRetentionDays: true,\n simhashThreshold: true,\n dedupTtlSeconds: true,\n sessionGapSeconds: true,\n maxNodes: true,\n maxDwellSeconds: true,\n pollIntervalMs: true,\n settleMs: true,\n idleFallbackSeconds: true,\n denyApps: true,\n denyTitles: true,\n denyUrls: true,\n terminalApps: true,\n redactionPatterns: true,\n};\n\nfunction asObject(value: unknown, label: string): Record<string, unknown> {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n throw new CaptureConfigError(`${label}: expected an object, got ${describeValue(value)}`);\n }\n return value as Record<string, unknown>;\n}\n\nfunction requireString(value: unknown, label: string): string {\n if (typeof value !== \"string\" || value.trim() === \"\") {\n throw new CaptureConfigError(`${label}: expected a non-empty string, got ${describeValue(value)}`);\n }\n return value.trim();\n}\n\nexport function parseDaemonConfig(raw: unknown): DaemonConfig {\n const cfg = defaultDaemonConfig();\n const obj = asObject(raw, \"config\");\n for (const key of Object.keys(obj)) {\n if (!Object.hasOwn(KNOWN_TOP_KEYS, key)) {\n console.warn(`remnic-capture-screen: config: ignoring unknown key '${key}'`);\n }\n }\n\n if (obj.host !== undefined) cfg.host = requireString(obj.host, \"host\");\n if (obj.port !== undefined) cfg.port = coerceNumber(obj.port, \"port\", { integer: true, min: 1, max: 65535 });\n if (obj.spoolRetentionDays !== undefined) {\n cfg.spoolRetentionDays = coerceNumber(obj.spoolRetentionDays, \"spoolRetentionDays\", { integer: true, min: 1 });\n }\n if (obj.simhashThreshold !== undefined) {\n cfg.simhashThreshold = coerceNumber(obj.simhashThreshold, \"simhashThreshold\", { integer: true, min: 0, max: 64 });\n }\n if (obj.dedupTtlSeconds !== undefined) {\n cfg.dedupTtlSeconds = coerceNumber(obj.dedupTtlSeconds, \"dedupTtlSeconds\", { min: 0 });\n }\n if (obj.sessionGapSeconds !== undefined) {\n cfg.sessionGapSeconds = coerceNumber(obj.sessionGapSeconds, \"sessionGapSeconds\", { min: 0 });\n }\n if (obj.maxNodes !== undefined) {\n cfg.maxNodes = coerceNumber(obj.maxNodes, \"maxNodes\", { integer: true, min: 1 });\n }\n if (obj.maxDwellSeconds !== undefined) {\n cfg.maxDwellSeconds = coerceNumber(obj.maxDwellSeconds, \"maxDwellSeconds\", { min: 1 });\n }\n if (obj.pollIntervalMs !== undefined) {\n cfg.pollIntervalMs = coerceNumber(obj.pollIntervalMs, \"pollIntervalMs\", { integer: true, min: 100 });\n }\n if (obj.settleMs !== undefined) {\n cfg.settleMs = coerceNumber(obj.settleMs, \"settleMs\", { integer: true, min: 0 });\n }\n if (obj.idleFallbackSeconds !== undefined) {\n cfg.idleFallbackSeconds = coerceNumber(obj.idleFallbackSeconds, \"idleFallbackSeconds\", { min: 1 });\n }\n if (obj.denyApps !== undefined) cfg.denyApps = coerceStringArray(obj.denyApps, \"denyApps\");\n if (obj.denyTitles !== undefined) cfg.denyTitles = coerceStringArray(obj.denyTitles, \"denyTitles\");\n if (obj.denyUrls !== undefined) cfg.denyUrls = coerceStringArray(obj.denyUrls, \"denyUrls\");\n if (obj.terminalApps !== undefined) cfg.terminalApps = coerceStringArray(obj.terminalApps, \"terminalApps\");\n if (obj.redactionPatterns !== undefined) {\n cfg.redactionPatterns = coerceStringArray(obj.redactionPatterns, \"redactionPatterns\");\n }\n\n return cfg;\n}\n\nexport function loadDaemonConfig(configPath: string): DaemonConfig {\n let text: string;\n try {\n text = readFileSync(configPath, \"utf8\");\n } catch {\n throw new CaptureConfigError(`config not found at ${configPath} — run \\`remnic-capture-screen init\\` first`);\n }\n let raw: unknown;\n try {\n raw = JSON.parse(text);\n } catch (err) {\n throw new CaptureConfigError(`config at ${configPath} is not valid JSON: ${(err as Error).message}`);\n }\n return parseDaemonConfig(raw);\n}\n\nexport function serializeDaemonConfig(cfg: DaemonConfig): string {\n return `${JSON.stringify(cfg, null, 2)}\\n`;\n}\n","/** Small dependency-free helpers shared across the package. */\n\n/**\n * Format a Date as YYYY-MM-DD in the given IANA timezone. Local copy of the\n * pipeline helper — capture-screen is à-la-carte and does not depend on\n * @remnic/core.\n */\nexport function dateInTimezone(date: Date, timezone: string): string {\n const parts = new Intl.DateTimeFormat(\"en-CA\", {\n timeZone: timezone,\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n }).formatToParts(date);\n const get = (type: string) => parts.find((part) => part.type === type)?.value ?? \"\";\n return `${get(\"year\")}-${get(\"month\")}-${get(\"day\")}`;\n}\n\nconst LOOPBACK_HOSTS: Record<string, true> = {\n \"127.0.0.1\": true,\n \"::1\": true,\n localhost: true,\n \"::ffff:127.0.0.1\": true,\n};\n\n/** Strip a single pair of surrounding brackets from a URL-authority IPv6 host\n * (`[::1]` -> `::1`); non-bracketed hosts pass through unchanged. */\nexport function stripIpv6Brackets(host: string): string {\n const h = host.trim();\n return h.startsWith(\"[\") && h.endsWith(\"]\") ? h.slice(1, -1) : h;\n}\n\n/**\n * A host is loopback when it can only be reached from this machine. Binding\n * anything else (a LAN address, 0.0.0.0, ::) exposes the daemon to the network\n * and is refused — capture-screen serves plain HTTP with no TLS contract.\n */\nexport function isLoopbackHost(host: string): boolean {\n return Object.hasOwn(LOOPBACK_HOSTS, stripIpv6Brackets(host).toLowerCase());\n}\n\n/** Wrap an IPv6 host in brackets for use in a URL authority; IPv4/hostnames pass\n * through. Existing brackets are stripped first so `[::1]` -> `[::1]`, never `[[::1]]`. */\nexport function formatHostForUrl(host: string): string {\n const bare = stripIpv6Brackets(host);\n return bare.includes(\":\") ? `[${bare}]` : bare;\n}\n\n/** Compact, credential-free description of an unexpected value for messages. */\nexport function describeValue(value: unknown): string {\n if (value === null) return \"null\";\n if (Array.isArray(value)) return \"an array\";\n const t = typeof value;\n if (t === \"string\") return `a string`;\n if (t === \"object\") return \"an object\";\n return `${t} (${String(value)})`;\n}\n\n/**\n * Operator-safe error description — name + errno code only, never foreign\n * message text or filesystem paths. This is the CLI/stderr sanitizer that\n * replaces @remnic/core's displayErrorDetail: a stack or absolute path in a\n * captured-screen daemon's stderr could leak sensitive local layout.\n */\nexport function sanitizeError(err: unknown): string {\n if (!(err instanceof Error)) return \"unknown error\";\n const code = (err as NodeJS.ErrnoException).code;\n return typeof code === \"string\" && code.length > 0 ? `${err.name} (${code})` : err.name;\n}\n","/**\n * Config-layer coercion. Every helper THROWS on an unrecognized value (never\n * silently defaults); callers apply defaults only when a field is absent.\n * Boolean-ish strings coerce per the shared connector convention:\n * true/1/yes/on and false/0/no/off.\n */\n\nimport { CaptureConfigError } from \"./errors.js\";\nimport { describeValue } from \"./util.js\";\n\nconst BOOL_TOKENS: Record<string, boolean> = {\n true: true,\n \"1\": true,\n yes: true,\n on: true,\n false: false,\n \"0\": false,\n no: false,\n off: false,\n};\n\nexport function coerceBool(value: unknown, label: string): boolean {\n if (typeof value === \"boolean\") return value;\n if (typeof value === \"number\" && (value === 0 || value === 1)) return value === 1;\n if (typeof value === \"string\") {\n const token = value.trim().toLowerCase();\n if (Object.hasOwn(BOOL_TOKENS, token)) return BOOL_TOKENS[token];\n }\n throw new CaptureConfigError(\n `${label}: expected a boolean (true/false/1/0/yes/no/on/off), got ${describeValue(value)}`,\n );\n}\n\nexport interface NumberBounds {\n min?: number;\n max?: number;\n integer?: boolean;\n}\n\nexport function coerceNumber(value: unknown, label: string, bounds: NumberBounds = {}): number {\n let n: number;\n if (typeof value === \"number\") {\n n = value;\n } else if (typeof value === \"string\" && value.trim() !== \"\") {\n n = Number(value);\n } else {\n throw new CaptureConfigError(`${label}: expected a number, got ${describeValue(value)}`);\n }\n if (!Number.isFinite(n)) {\n throw new CaptureConfigError(`${label}: '${String(value)}' is not a finite number`);\n }\n if (bounds.integer && !Number.isInteger(n)) {\n throw new CaptureConfigError(`${label}: expected an integer, got ${n}`);\n }\n if (bounds.min !== undefined && n < bounds.min) {\n throw new CaptureConfigError(`${label}: must be >= ${bounds.min}, got ${n}`);\n }\n if (bounds.max !== undefined && n > bounds.max) {\n throw new CaptureConfigError(`${label}: must be <= ${bounds.max}, got ${n}`);\n }\n return n;\n}\n\n/** Coerce an unknown to a `string[]`, rejecting non-arrays and non-string members. */\nexport function coerceStringArray(value: unknown, label: string): string[] {\n if (!Array.isArray(value) || !value.every((item) => typeof item === \"string\")) {\n throw new CaptureConfigError(`${label}: expected an array of strings, got ${describeValue(value)}`);\n }\n return [...(value as string[])];\n}\n","/**\n * Daemon process control: an atomic, identity-bearing pid file plus liveness\n * probing.\n *\n * The pid file is JSON `{ pid, instanceId, startedAtIso, host, port }` written\n * via a temp-file + rename so a reader never sees a partial write, and reads\n * are tolerant of a concurrent delete. `instanceId` (the spool instance id)\n * lets `stop`/`status` confirm — over the authenticated health endpoint — that\n * the recorded pid really is our daemon before signalling it, which guards\n * against PID reuse. Removal is owner-checked so a late shutdown can't delete a\n * newer daemon's control file.\n */\n\nimport { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from \"node:fs\";\nimport { randomBytes } from \"node:crypto\";\nimport path from \"node:path\";\n\nexport interface PidRecord {\n pid: number;\n /** Daemon instance id (spool instance_id) for cross-process identity; null when unknown. */\n instanceId: string | null;\n /** ISO timestamp the record was written. */\n startedAtIso: string;\n /** Effective bound host, when known (so status/stop reach the daemon the CLI actually started). */\n host: string | null;\n /** Effective bound port, when known. */\n port: number | null;\n}\n\nexport interface PidWriteOptions {\n instanceId?: string | null;\n startedAtIso?: string;\n host?: string | null;\n port?: number | null;\n}\n\n/** Atomically write the pid record (temp file + rename) — no partial reads. */\nexport function writePidFile(pidPath: string, pid: number, options: PidWriteOptions = {}): void {\n mkdirSync(path.dirname(pidPath), { recursive: true });\n const record: PidRecord = {\n pid,\n instanceId: options.instanceId ?? null,\n startedAtIso: options.startedAtIso ?? new Date().toISOString(),\n host: options.host ?? null,\n port: options.port ?? null,\n };\n const tmp = `${pidPath}.${process.pid}.${randomBytes(4).toString(\"hex\")}.tmp`;\n writeFileSync(tmp, `${JSON.stringify(record)}\\n`, \"utf8\");\n renameSync(tmp, pidPath);\n}\n\n/** Read the pid record; a missing file or a partial/concurrent write returns null. */\nexport function readPidRecord(pidPath: string): PidRecord | null {\n let text: string;\n try {\n text = readFileSync(pidPath, \"utf8\");\n } catch {\n return null;\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n return null;\n }\n if (typeof parsed !== \"object\" || parsed === null) return null;\n const record = parsed as Record<string, unknown>;\n const pid = typeof record.pid === \"number\" ? record.pid : Number.NaN;\n if (!Number.isInteger(pid) || pid <= 0) return null;\n const port =\n typeof record.port === \"number\" && Number.isInteger(record.port) && record.port > 0 ? record.port : null;\n return {\n pid,\n instanceId: typeof record.instanceId === \"string\" ? record.instanceId : null,\n startedAtIso: typeof record.startedAtIso === \"string\" ? record.startedAtIso : \"\",\n host: typeof record.host === \"string\" && record.host !== \"\" ? record.host : null,\n port,\n };\n}\n\n/** Convenience accessor: the recorded pid, or null. */\nexport function readPidFile(pidPath: string): number | null {\n return readPidRecord(pidPath)?.pid ?? null;\n}\n\n/** Liveness via signal 0. ESRCH → gone; EPERM → alive but owned by another user. */\nexport function isProcessAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch (err) {\n return (err as NodeJS.ErrnoException).code === \"EPERM\";\n }\n}\n\n/** Remove the pid file unconditionally (stale reclaim). */\nexport function removePidFile(pidPath: string): void {\n rmSync(pidPath, { force: true });\n}\n\n/**\n * Remove the pid file only when it still records `pid`. Prevents a late\n * shutdown or `stop` from deleting a NEWER daemon's control file after a\n * restart or PID reuse.\n */\nexport function removePidFileIfOwner(pidPath: string, pid: number): void {\n const record = readPidRecord(pidPath);\n if (record && record.pid === pid) rmSync(pidPath, { force: true });\n}\n","/**\n * Bearer-token lifecycle. The daemon auto-generates a 256-bit token on first\n * use and stores it 0600; a pre-existing file is re-chmod'd 0600 defensively\n * because a world-readable token is a credential leak. The token is REQUIRED on\n * every request (even on loopback) so another local user cannot read captured\n * screen text off 127.0.0.1.\n */\n\nimport { Buffer } from \"node:buffer\";\nimport { randomBytes, timingSafeEqual } from \"node:crypto\";\nimport { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport path from \"node:path\";\n\nexport function generateToken(): string {\n return randomBytes(32).toString(\"base64url\");\n}\n\nexport function loadOrCreateToken(tokenPath: string): string {\n mkdirSync(path.dirname(tokenPath), { recursive: true });\n if (existsSync(tokenPath)) {\n chmodSync(tokenPath, 0o600);\n const existing = readFileSync(tokenPath, \"utf8\").trim();\n if (existing) return existing;\n }\n const token = generateToken();\n try {\n // Exclusive create: if two daemons start together, the loser gets EEXIST and\n // reads the winner's token rather than both persisting divergent values.\n writeFileSync(tokenPath, `${token}\\n`, { mode: 0o600, flag: \"wx\" });\n chmodSync(tokenPath, 0o600);\n return token;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"EEXIST\") throw err;\n chmodSync(tokenPath, 0o600);\n const raced = readFileSync(tokenPath, \"utf8\").trim();\n if (raced) return raced;\n // Pre-existing empty file (interrupted prior write): overwrite it.\n writeFileSync(tokenPath, `${token}\\n`, { mode: 0o600 });\n chmodSync(tokenPath, 0o600);\n return token;\n }\n}\n\n/** Constant-time compare; unequal lengths short-circuit to false. */\nexport function tokensMatch(expected: string, presented: string): boolean {\n const a = Buffer.from(expected, \"utf8\");\n const b = Buffer.from(presented, \"utf8\");\n if (a.length !== b.length) return false;\n return timingSafeEqual(a, b);\n}\n\n/** Parse `Authorization: Bearer <token>`; returns null when absent/malformed. */\nexport function bearerFromHeader(header: string | string[] | undefined): string | null {\n const value = Array.isArray(header) ? header[0] : header;\n if (!value) return null;\n const trimmed = value.trim();\n if (trimmed.slice(0, 6).toLowerCase() !== \"bearer\") return null;\n const separator = trimmed.charCodeAt(6);\n if (separator !== 32 && separator !== 9) return null;\n const token = trimmed.slice(6).trim();\n return token || null;\n}\n","/**\n * Request-input validation for the HTTP surface. Every failure raises\n * CaptureInputError, which the daemon maps to HTTP 400 — invalid date,\n * timezone, limit, or cursor is rejected loudly, never silently defaulted. The\n * keyset cursor is an opaque base64url token over the (capturedAtUtc, id) tuple\n * the snapshots query orders by, so pagination stays stable across snapshots\n * that share a capture instant.\n */\n\nimport { Buffer } from \"node:buffer\";\n\nimport { DEFAULT_SNAPSHOTS_LIMIT, MAX_SNAPSHOTS_LIMIT } from \"./constants.js\";\nimport { CaptureInputError } from \"./errors.js\";\n\nconst DATE_RE = /^\\d{4}-\\d{2}-\\d{2}$/;\n\n/** Validate a YYYY-MM-DD calendar date (rejects e.g. 2026-02-30). */\nexport function parseSnapshotDate(value: string | null | undefined): string {\n if (typeof value !== \"string\" || !DATE_RE.test(value)) {\n throw new CaptureInputError(`invalid date '${value ?? \"\"}' — expected YYYY-MM-DD`);\n }\n const [year, month, day] = value.split(\"-\").map(Number);\n const dt = new Date(Date.UTC(year, month - 1, day));\n dt.setUTCFullYear(year);\n if (dt.getUTCFullYear() !== year || dt.getUTCMonth() !== month - 1 || dt.getUTCDate() !== day) {\n throw new CaptureInputError(`invalid date '${value}' — not a real calendar date`);\n }\n return value;\n}\n\n/** Validate an IANA timezone by attempting to build a formatter for it. */\nexport function assertValidTimezone(value: string | null | undefined): string {\n if (typeof value !== \"string\" || value.trim() === \"\") {\n throw new CaptureInputError(\"invalid timezone '' — expected an IANA timezone\");\n }\n try {\n new Intl.DateTimeFormat(\"en-CA\", { timeZone: value });\n } catch {\n throw new CaptureInputError(`invalid timezone '${value}' — not a known IANA timezone`);\n }\n return value;\n}\n\n/** Absent limit → default; present-but-invalid → 400. */\nexport function parseLimit(value: string | null | undefined): number {\n if (value === null || value === undefined) return DEFAULT_SNAPSHOTS_LIMIT;\n const n = Number(value);\n if (value === \"\" || !Number.isInteger(n) || n < 1 || n > MAX_SNAPSHOTS_LIMIT) {\n throw new CaptureInputError(\n `invalid limit '${value}' — expected an integer between 1 and ${MAX_SNAPSHOTS_LIMIT}`,\n );\n }\n return n;\n}\n\nexport interface Cursor {\n capturedAtUtc: string;\n id: number;\n}\n\nexport function encodeCursor(capturedAtUtc: string, id: number): string {\n return Buffer.from(JSON.stringify([capturedAtUtc, id]), \"utf8\").toString(\"base64url\");\n}\n\n/** Absent cursor → null (first page); malformed cursor → 400. */\nexport function decodeCursor(value: string | null | undefined): Cursor | null {\n if (value === null || value === undefined || value === \"\") return null;\n let parsed: unknown;\n try {\n parsed = JSON.parse(Buffer.from(value, \"base64url\").toString(\"utf8\"));\n } catch {\n throw new CaptureInputError(\"invalid cursor — not a recognized pagination token\");\n }\n if (\n Array.isArray(parsed) &&\n parsed.length === 2 &&\n typeof parsed[0] === \"string\" &&\n typeof parsed[1] === \"number\" &&\n Number.isInteger(parsed[1]) &&\n parsed[1] >= 0 &&\n /^\\d{4}-\\d{2}-\\d{2}T/.test(parsed[0]) &&\n Number.isFinite(Date.parse(parsed[0])) &&\n new Date(parsed[0]).toISOString() === parsed[0]\n ) {\n return { capturedAtUtc: parsed[0], id: parsed[1] };\n }\n throw new CaptureInputError(\"invalid cursor — not a recognized pagination token\");\n}\n","/**\n * Loopback-only HTTP daemon. Serves the spool over three read-only routes:\n *\n * GET /v1/health → liveness + capture status + AX/OCR availability\n * GET /v1/snapshots → snapshots for a local day (keyset paged; wire shape\n * consumed by @remnic/core's ActivityHttpSourceClient)\n * GET /v1/stats → per-app time attribution for a local day\n *\n * Security: capture-screen serves PLAIN HTTP and has no TLS contract, so it\n * refuses to bind a non-loopback host — captured screen text must never cross\n * the network in cleartext. Every request MUST carry `Authorization: Bearer\n * <token>` matching the daemon token, even on loopback, so another local user\n * cannot read snapshots off 127.0.0.1. Input errors are 400; anything\n * unexpected is 500 with no foreign text.\n */\n\nimport http from \"node:http\";\nimport { Buffer } from \"node:buffer\";\n\nimport { computeStats } from \"./capture.js\";\nimport { CAPTURE_SCREEN_VERSION } from \"./constants.js\";\nimport { CaptureConfigError, CaptureInputError } from \"./errors.js\";\nimport { bearerFromHeader, tokensMatch } from \"./token.js\";\nimport { formatHostForUrl, isLoopbackHost } from \"./util.js\";\nimport { assertValidTimezone, parseLimit, parseSnapshotDate } from \"./validate.js\";\nimport type { DaemonConfig } from \"./config.js\";\nimport type { DaemonSnapshot, Spool } from \"./spool.js\";\n\nexport interface DaemonDeps {\n spool: Spool;\n config: DaemonConfig;\n token: string;\n /** Live capture status for /v1/health; false until the capture layer runs. */\n capturing?: boolean;\n /** Native-helper capabilities (false when the helper is unavailable). */\n axAvailable?: boolean;\n ocrAvailable?: boolean;\n /** Operator-facing hint surfaced on /v1/health when the helper is missing. */\n helperHint?: string | null;\n}\n\nexport interface DaemonHandle {\n server: http.Server;\n host: string;\n port: number;\n url: string;\n close(): Promise<void>;\n}\n\n/** Wire shape consumed by ActivityHttpSourceClient. browserUrl omitted when null. */\nfunction snapshotToWire(snap: DaemonSnapshot): Record<string, unknown> {\n const wire: Record<string, unknown> = {\n capturedAtUtc: snap.capturedAtUtc,\n app: snap.app,\n windowTitle: snap.windowTitle,\n text: snap.text,\n textSource: snap.textSource,\n contentHash: snap.contentHash,\n simhash: snap.simhash,\n };\n if (snap.browserUrl !== null) wire.browserUrl = snap.browserUrl;\n return wire;\n}\n\nfunction sendJson(res: http.ServerResponse, status: number, body: unknown): void {\n const payload = JSON.stringify(body);\n res.writeHead(status, {\n \"content-type\": \"application/json; charset=utf-8\",\n \"content-length\": Buffer.byteLength(payload),\n \"cache-control\": \"no-store\",\n });\n res.end(payload);\n}\n\nfunction handleHealth(deps: DaemonDeps, res: http.ServerResponse): void {\n const body: Record<string, unknown> = {\n ok: true,\n version: CAPTURE_SCREEN_VERSION,\n platform: process.platform,\n capturing: deps.capturing ?? false,\n axAvailable: deps.axAvailable ?? false,\n ocrAvailable: deps.ocrAvailable ?? false,\n pendingCount: deps.spool.countSnapshots(),\n instanceId: deps.spool.meta(\"instance_id\"),\n replayStatus: deps.spool.meta(\"replay_status\"),\n pid: process.pid,\n };\n if (deps.helperHint) body.helperHint = deps.helperHint;\n sendJson(res, 200, body);\n}\n\nfunction handleSnapshots(deps: DaemonDeps, url: URL, res: http.ServerResponse): void {\n const date = parseSnapshotDate(url.searchParams.get(\"date\"));\n const timezone = assertValidTimezone(url.searchParams.get(\"timezone\"));\n const limit = parseLimit(url.searchParams.get(\"limit\"));\n const cursor = url.searchParams.get(\"cursor\");\n const page = deps.spool.querySnapshots({ date, timezone, cursor, limit });\n sendJson(res, 200, { snapshots: page.snapshots.map(snapshotToWire), nextCursor: page.nextCursor });\n}\n\nfunction handleStats(deps: DaemonDeps, url: URL, res: http.ServerResponse): void {\n const date = parseSnapshotDate(url.searchParams.get(\"date\"));\n const timezone = assertValidTimezone(url.searchParams.get(\"timezone\"));\n const stats = computeStats(deps.spool.daySnapshots(date, timezone), date, timezone, deps.config.maxDwellSeconds);\n sendJson(res, 200, stats);\n}\n\nexport function createRequestHandler(deps: DaemonDeps): http.RequestListener {\n if (!isLoopbackHost(deps.config.host)) {\n throw new CaptureConfigError(\n `refusing to bind non-loopback host '${deps.config.host}': capture-screen serves plain HTTP with no TLS contract; ` +\n \"bind a loopback address (127.0.0.1 or ::1) only\",\n );\n }\n if (!deps.token) {\n throw new CaptureConfigError(\"daemon requires a bearer token\");\n }\n return (req, res) => {\n try {\n const presented = bearerFromHeader(req.headers[\"authorization\"]);\n if (!presented || !tokensMatch(deps.token, presented)) {\n res.setHeader(\"www-authenticate\", \"Bearer\");\n sendJson(res, 401, { error: \"unauthorized\" });\n return;\n }\n if (req.method !== \"GET\") {\n sendJson(res, 405, { error: \"method not allowed\" });\n return;\n }\n const url = new URL(req.url ?? \"/\", \"http://localhost\");\n switch (url.pathname) {\n case \"/v1/health\":\n handleHealth(deps, res);\n return;\n case \"/v1/snapshots\":\n handleSnapshots(deps, url, res);\n return;\n case \"/v1/stats\":\n handleStats(deps, url, res);\n return;\n default:\n sendJson(res, 404, { error: \"not found\" });\n }\n } catch (err) {\n if (err instanceof CaptureInputError) {\n sendJson(res, 400, { error: err.message });\n return;\n }\n sendJson(res, 500, { error: \"internal error\" });\n }\n };\n}\n\nexport function startDaemon(deps: DaemonDeps): Promise<DaemonHandle> {\n return new Promise((resolve, reject) => {\n let handler: http.RequestListener;\n try {\n handler = createRequestHandler(deps);\n } catch (err) {\n reject(err as Error);\n return;\n }\n const server = http.createServer(handler);\n const onError = (err: Error) => reject(err);\n server.once(\"error\", onError);\n server.listen(deps.config.port, deps.config.host, () => {\n server.removeListener(\"error\", onError);\n server.on(\"error\", (err: NodeJS.ErrnoException) => {\n process.stderr.write(`capture-screen daemon server error: ${err.code ?? err.name}\\n`);\n });\n const address = server.address();\n const port = typeof address === \"object\" && address ? address.port : deps.config.port;\n const host = deps.config.host;\n resolve({\n server,\n host,\n port,\n url: `http://${formatHostForUrl(host)}:${port}`,\n close: () =>\n new Promise<void>((res2, rej2) => {\n server.close((closeErr) => (closeErr ? rej2(closeErr) : res2()));\n }),\n });\n });\n });\n}\n","/** Filesystem layout for the capture working directory. */\n\nimport os from \"node:os\";\nimport path from \"node:path\";\n\nexport interface CapturePaths {\n baseDir: string;\n configPath: string;\n spoolPath: string;\n tokenPath: string;\n pidPath: string;\n logPath: string;\n}\n\n/** Expand a leading `~` / `~/` to the home directory; other paths pass through. */\nexport function expandTilde(p: string): string {\n if (p === \"~\") return os.homedir();\n if (p.startsWith(\"~/\")) return path.join(os.homedir(), p.slice(2));\n return p;\n}\n\n/**\n * Root of the capture working directory. `REMNIC_CAPTURE_SCREEN_DIR` overrides\n * the default `~/.remnic/capture-screen` (tests and multi-instance setups point\n * it at a scratch dir). A leading `~` expands to the home directory.\n */\nexport function captureBaseDir(env: NodeJS.ProcessEnv = process.env): string {\n const override = env.REMNIC_CAPTURE_SCREEN_DIR?.trim();\n if (override) return expandTilde(override);\n return path.join(os.homedir(), \".remnic\", \"capture-screen\");\n}\n\nexport function capturePaths(baseDir: string = captureBaseDir()): CapturePaths {\n return {\n baseDir,\n configPath: path.join(baseDir, \"screen.json\"),\n spoolPath: path.join(baseDir, \"screen.sqlite\"),\n tokenPath: path.join(baseDir, \"token\"),\n pidPath: path.join(baseDir, \"daemon.pid\"),\n logPath: path.join(baseDir, \"daemon.log\"),\n };\n}\n","/**\n * Native-helper seam. The actual screen reader is a platform Swift binary\n * shipped separately as `@remnic/capture-native-<platform>-<arch>`, exporting a\n * `helperBinaryPath`. This module resolves that binary, spawns it, and parses\n * its JSON — with two hard rules:\n *\n * - A MISSING helper package NEVER surfaces as a raw MODULE_NOT_FOUND: it\n * resolves to `{ binaryPath: null, hint }` with an actionable install hint,\n * and the daemon reports axAvailable/ocrAvailable = false (degraded but\n * honest).\n * - Every helper invocation is bounded and its output validated: a nonzero\n * exit, empty output, or invalid/partial JSON throws a sanitized\n * CaptureInputError, never a crash and never foreign text.\n *\n * `REMNIC_CAPTURE_HELPER_BIN` overrides resolution with an explicit binary path\n * (manual installs and the hardware-free test seam, which points it at a fake\n * script emitting canned JSON).\n */\n\nimport { spawn } from \"node:child_process\";\n\nimport type { AxNode } from \"./axtree.js\";\nimport { CaptureInputError } from \"./errors.js\";\nimport { expandTilde } from \"./paths.js\";\n\n/** Max helper stdout we will buffer (guards a runaway child). */\nconst MAX_OUTPUT_BYTES = 8 * 1024 * 1024;\nconst DEFAULT_TIMEOUT_MS = 15_000;\n\nexport interface HelperResolution {\n /** Absolute path to the helper binary, or null when unavailable. */\n binaryPath: string | null;\n /** Operator-facing install hint when unavailable, else null. */\n hint: string | null;\n}\n\n/** The npm package that would provide the helper for this platform/arch. */\nexport function helperPackageName(platform: string = process.platform, arch: string = process.arch): string {\n return `@remnic/capture-native-${platform}-${arch}`;\n}\n\nfunction installHint(pkg: string): string {\n return (\n `native capture helper (${pkg}) is not available on this install — it ships via a tracked follow-up ` +\n `(https://github.com/joshuaswarren/remnic/issues/2139). To enable live screen capture now, build the Swift ` +\n `helper from source (packages/capture-native-darwin-helper) and set REMNIC_CAPTURE_HELPER_BIN to the binary`\n );\n}\n\nfunction isModuleNotFound(err: unknown): boolean {\n const code = (err as NodeJS.ErrnoException | undefined)?.code;\n return code === \"ERR_MODULE_NOT_FOUND\" || code === \"MODULE_NOT_FOUND\";\n}\n\n/**\n * Resolve the helper binary path. Order: explicit env override, then the\n * computed platform package (dynamic import), then unavailable-with-hint. A\n * missing or broken package degrades gracefully — it never throws.\n */\nexport async function resolveHelperBinaryPath(env: NodeJS.ProcessEnv = process.env): Promise<HelperResolution> {\n const override = env.REMNIC_CAPTURE_HELPER_BIN?.trim();\n if (override) return { binaryPath: expandTilde(override), hint: null };\n\n const pkg = helperPackageName();\n try {\n // Runtime-selected specifier: the helper package is platform/arch-specific\n // and absent on most hosts, so a static import is impossible here.\n const mod: unknown = await import(pkg);\n if (mod && typeof mod === \"object\" && \"helperBinaryPath\" in mod) {\n const value: unknown = mod.helperBinaryPath;\n if (typeof value === \"string\" && value.length > 0) return { binaryPath: value, hint: null };\n }\n // Package present but did not export a usable path — still degrade honestly.\n return { binaryPath: null, hint: `${pkg} is installed but exports no helperBinaryPath` };\n } catch (err) {\n if (isModuleNotFound(err)) return { binaryPath: null, hint: installHint(pkg) };\n // Any other load failure (broken binding, bad build) — degrade, never crash.\n return { binaryPath: null, hint: `${pkg} failed to load; reinstall it to enable live capture` };\n }\n}\n\ninterface SpawnOutcome {\n code: number | null;\n stdout: string;\n}\n\nfunction spawnHelper(binaryPath: string, args: string[], timeoutMs: number): Promise<SpawnOutcome> {\n return new Promise<SpawnOutcome>((resolve, reject) => {\n const child = spawn(binaryPath, args, { stdio: [\"ignore\", \"pipe\", \"pipe\"] });\n const chunks: Buffer[] = [];\n let size = 0;\n let settled = false;\n const timer = setTimeout(() => {\n if (settled) return;\n settled = true;\n child.kill(\"SIGKILL\");\n reject(new CaptureInputError(\"native helper timed out\"));\n }, timeoutMs);\n child.stdout.on(\"data\", (chunk: Buffer) => {\n size += chunk.length;\n if (size > MAX_OUTPUT_BYTES) {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n child.kill(\"SIGKILL\");\n reject(new CaptureInputError(\"native helper produced too much output\"));\n return;\n }\n chunks.push(chunk);\n });\n child.on(\"error\", (err) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n // Sanitize: name + errno only, never the spawn path.\n const code = (err as NodeJS.ErrnoException).code;\n reject(new CaptureInputError(`native helper failed to spawn (${code ?? err.name})`));\n });\n child.on(\"close\", (code) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n resolve({ code, stdout: Buffer.concat(chunks).toString(\"utf8\") });\n });\n });\n}\n\n/** Run a helper subcommand and return its parsed JSON, or throw a sanitized error. */\nexport async function runHelperCommand(\n binaryPath: string,\n args: string[],\n timeoutMs: number = DEFAULT_TIMEOUT_MS,\n): Promise<unknown> {\n const outcome = await spawnHelper(binaryPath, args, timeoutMs);\n if (outcome.code !== 0) {\n throw new CaptureInputError(`native helper exited with status ${outcome.code ?? \"unknown\"}`);\n }\n if (outcome.stdout.trim() === \"\") {\n throw new CaptureInputError(\"native helper produced no output\");\n }\n try {\n return JSON.parse(outcome.stdout);\n } catch {\n throw new CaptureInputError(\"native helper produced invalid JSON\");\n }\n}\n\nexport interface AxSnapshotOptions {\n frontmost?: boolean;\n pid?: number;\n maxNodes?: number;\n}\n\n/**\n * The helper's `ax-snapshot` payload: the frontmost window's context (app,\n * title, optional browser URL) plus its accessibility tree. The tree is\n * permissive (see AxNode); window context lets the daemon build a capture\n * candidate without a separate frontmost-window query. `windowId` is the\n * stable native window id (macOS CGWindowID) so follow-up OCR can target\n * the exact window this tree came from, not whatever is frontmost later.\n */\nexport interface AxSnapshot {\n app: string;\n windowTitle: string;\n windowId?: string;\n browserUrl?: string | null;\n tree: AxNode;\n}\n\nexport interface OcrWindowOptions {\n frontmost?: boolean;\n windowId?: string;\n}\n\n/** Thin wrapper over a resolved helper binary. */\nexport class NativeHelper {\n readonly binaryPath: string;\n\n constructor(binaryPath: string) {\n this.binaryPath = binaryPath;\n }\n\n /** `<helper> ax-snapshot [--frontmost|--pid N] [--max-nodes N]` -> window + AX tree JSON. */\n async axSnapshot(opts: AxSnapshotOptions = {}): Promise<AxSnapshot> {\n const args = [\"ax-snapshot\"];\n if (opts.pid !== undefined) args.push(\"--pid\", String(opts.pid));\n else args.push(\"--frontmost\");\n if (opts.maxNodes !== undefined) args.push(\"--max-nodes\", String(opts.maxNodes));\n const json = await runHelperCommand(this.binaryPath, args);\n if (json === null || typeof json !== \"object\" || Array.isArray(json)) {\n throw new CaptureInputError(\"native helper ax-snapshot did not return an object\");\n }\n if (!(\"app\" in json) || !(\"windowTitle\" in json) || !(\"tree\" in json)) {\n throw new CaptureInputError(\"native helper ax-snapshot missing app/windowTitle/tree\");\n }\n const app: unknown = json.app;\n const windowTitle: unknown = json.windowTitle;\n const browserUrl: unknown = \"browserUrl\" in json ? json.browserUrl : undefined;\n const windowIdRaw: unknown = \"windowId\" in json ? json.windowId : undefined;\n const tree: unknown = json.tree;\n if (typeof app !== \"string\" || typeof windowTitle !== \"string\") {\n throw new CaptureInputError(\"native helper ax-snapshot app/windowTitle must be strings\");\n }\n if (tree === null || typeof tree !== \"object\" || Array.isArray(tree)) {\n throw new CaptureInputError(\"native helper ax-snapshot tree must be an object\");\n }\n // CGWindowID may arrive as a JSON number or string; null/absent means the\n // helper could not resolve an id (OCR falls back to frontmost).\n let windowId: string | undefined;\n if (typeof windowIdRaw === \"string\") windowId = windowIdRaw;\n else if (typeof windowIdRaw === \"number\" && Number.isFinite(windowIdRaw)) windowId = String(windowIdRaw);\n else if (windowIdRaw !== undefined && windowIdRaw !== null) {\n throw new CaptureInputError(\"native helper ax-snapshot windowId must be a string or number\");\n }\n // Named cast (sanctioned): the tree is structurally an AxNode (all fields\n // optional) and extractAxText tolerates unknown shapes; a schema parse of an\n // arbitrary AX dump would be meaningless.\n const axTree = tree as AxNode;\n return {\n app,\n windowTitle,\n ...(windowId !== undefined ? { windowId } : {}),\n ...(typeof browserUrl === \"string\" ? { browserUrl } : {}),\n tree: axTree,\n };\n }\n\n /** `<helper> ocr-window [--frontmost|--window ID]` -> `{ text }` JSON. */\n async ocrWindow(opts: OcrWindowOptions = {}): Promise<string> {\n const args = [\"ocr-window\"];\n if (opts.windowId !== undefined) args.push(\"--window\", opts.windowId);\n else args.push(\"--frontmost\");\n const json = await runHelperCommand(this.binaryPath, args);\n if (json !== null && typeof json === \"object\" && !Array.isArray(json) && \"text\" in json) {\n const text: unknown = json.text;\n if (typeof text === \"string\") return text;\n }\n throw new CaptureInputError(\"native helper ocr-window did not return a text field\");\n }\n}\n","/**\n * Live capture cycle: one snapshot fetched through the native helper and run\n * through the processing pipeline. Shared by `test-snapshot` (which prints the\n * decision without storing) and available to a future capture scheduler.\n *\n * The routing (AX vs OCR) happens here because the native OCR call is async\n * while the processor's OCR seam is sync: a terminal-class or AX-empty window\n * has its OCR text fetched eagerly, then handed to the processor as\n * pre-extracted text. When OCR fails, the candidate is left text-less so the\n * processor skips it (ocr-unavailable) rather than storing empty AX text.\n */\n\nimport { extractAxText } from \"./axtree.js\";\nimport { isTerminalApp } from \"./capture.js\";\nimport { matchDenyRule } from \"./denylist.js\";\nimport type { CaptureCandidate, CaptureDecision, CaptureProcessor } from \"./capture.js\";\nimport type { DaemonConfig } from \"./config.js\";\nimport type { AxSnapshot, NativeHelper } from \"./helper.js\";\n\nexport async function captureViaHelper(\n helper: NativeHelper,\n processor: CaptureProcessor,\n config: DaemonConfig,\n capturedAtUtc: string,\n): Promise<CaptureDecision> {\n const snap = await helper.axSnapshot({ frontmost: true, maxNodes: config.maxNodes });\n return captureFromSnapshot(snap, helper, processor, config, capturedAtUtc);\n}\n\n/**\n * Run an already-fetched AX snapshot through the pipeline. The live scheduler\n * uses this so a single ax-snapshot poll drives both change detection and the\n * capture, avoiding a redundant fetch.\n */\nexport async function captureFromSnapshot(\n snap: AxSnapshot,\n helper: NativeHelper,\n processor: CaptureProcessor,\n config: DaemonConfig,\n capturedAtUtc: string,\n): Promise<CaptureDecision> {\n const axText = extractAxText(snap.tree, config.maxNodes).text;\n const candidate: CaptureCandidate = {\n capturedAtUtc,\n app: snap.app,\n windowTitle: snap.windowTitle,\n ...(snap.browserUrl != null ? { browserUrl: snap.browserUrl } : {}),\n };\n // Deny preflight: never OCR/screen-capture a deny-listed window. process()\n // applies deny too, but only AFTER text extraction — so without this an OCR\n // call would fire against a denied window before the rule is checked.\n const denied =\n matchDenyRule(\n { app: snap.app, windowTitle: snap.windowTitle, browserUrl: snap.browserUrl ?? null },\n { apps: config.denyApps, titles: config.denyTitles, urls: config.denyUrls },\n ) !== null;\n if (denied) {\n // Leave text-less; processor.process denies it below (no OCR ran).\n } else if (isTerminalApp(snap.app, config.terminalApps) || axText.trim() === \"\") {\n try {\n // Pin OCR to the snapshot's window when the helper reported its id: a\n // focus change between the ax-snapshot and this call must not redirect\n // OCR at a different window than the one this candidate is stored\n // under. The deny preflight above ran against this same window\n // identity, so the resolved OCR target is the window that was checked.\n // Without an id (legacy helper) frontmost is the only target available\n // and the preflight against the snap identity remains the guard.\n const ocrText = await helper.ocrWindow(\n snap.windowId !== undefined ? { windowId: snap.windowId } : { frontmost: true },\n );\n if (ocrText.trim() !== \"\") {\n candidate.text = ocrText;\n candidate.textSource = \"ocr\";\n }\n // Blank OCR: leave text-less so the processor reports ocr-unavailable\n // instead of persisting an empty snapshot.\n } catch {\n // OCR unavailable/failed: leave text-less so the processor reports\n // ocr-unavailable instead of persisting an empty snapshot.\n }\n } else {\n candidate.text = axText;\n candidate.textSource = \"ax\";\n }\n return processor.process(candidate);\n}\n","/**\n * `--replay <dir>` ingestion. Feeds synthetic candidate snapshots through the\n * FULL capture pipeline (deny-lists, AX/secure-field extraction, OCR routing,\n * redaction, dedup, supersession) into the spool — the CI-friendly, hardware-\n * free path that exercises every capture-time rule without a native helper.\n *\n * Each `*.json` fixture is either a single candidate or an array of them:\n *\n * {\n * \"capturedAtUtc\": \"2026-07-20T15:00:00.000Z\",\n * \"app\": \"Safari\",\n * \"windowTitle\": \"Example\",\n * \"browserUrl\": \"https://example.com\", // optional\n * \"text\": \"already extracted text\", // optional; OR provide \"ax\"\n * \"textSource\": \"ax\", // optional; \"ax\" | \"ocr\"\n * \"ax\": { \"role\": \"AXWindow\", \"children\": [ ... ] } // optional AX tree\n * }\n *\n * Candidates are processed in ascending capturedAt order (ties broken by file\n * order) so dedup/TTL behave deterministically regardless of how fixtures are\n * split across files. Ingestion is idempotent by content hash.\n */\n\nimport { lstatSync, readdirSync, readFileSync } from \"node:fs\";\nimport path from \"node:path\";\n\nimport type { CaptureCandidate, CaptureProcessor, OcrFn } from \"./capture.js\";\nimport { CaptureProcessor as Processor } from \"./capture.js\";\nimport type { DaemonConfig } from \"./config.js\";\nimport { CaptureConfigError } from \"./errors.js\";\nimport type { AxNode } from \"./axtree.js\";\nimport type { Spool } from \"./spool.js\";\n\nexport interface ReplayResult {\n files: number;\n candidates: number;\n stored: number;\n denied: number;\n deduped: number;\n ocrSkipped: number;\n superseded: number;\n /** True when a cooperative cancel (AbortSignal) stopped ingestion early. */\n aborted: boolean;\n}\n\nconst REPLAY_INSTANT = /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}(:\\d{2}(\\.\\d{1,9})?)?(Z|[+-]\\d{2}:\\d{2})$/;\n\nfunction asObject(value: unknown, where: string): Record<string, unknown> {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n throw new CaptureConfigError(`${where}: expected a snapshot object`);\n }\n return value as Record<string, unknown>;\n}\n\nfunction parseTimestamp(value: unknown, where: string): string {\n if (typeof value !== \"string\" || !REPLAY_INSTANT.test(value) || !Number.isFinite(Date.parse(value))) {\n throw new CaptureConfigError(`${where}: expected an ISO instant with a Z or numeric offset`);\n }\n const [cy, cm, cd] = value.slice(0, 10).split(\"-\").map(Number);\n const probe = new Date(Date.UTC(cy, cm - 1, cd));\n probe.setUTCFullYear(cy);\n if (probe.getUTCFullYear() !== cy || probe.getUTCMonth() !== cm - 1 || probe.getUTCDate() !== cd) {\n throw new CaptureConfigError(`${where}: '${value}' is not a real calendar date`);\n }\n // Canonicalize to UTC (Z) so an offset instant sorts correctly under the keyset.\n return new Date(value).toISOString();\n}\n\nfunction requireString(value: unknown, where: string): string {\n if (typeof value !== \"string\") throw new CaptureConfigError(`${where}: expected a string`);\n return value;\n}\n\nfunction parseCandidate(raw: unknown, where: string): CaptureCandidate {\n const obj = asObject(raw, where);\n const capturedAtUtc = parseTimestamp(obj.capturedAtUtc, `${where}.capturedAtUtc`);\n const candidate: CaptureCandidate = {\n capturedAtUtc,\n app: requireString(obj.app, `${where}.app`),\n windowTitle: requireString(obj.windowTitle, `${where}.windowTitle`),\n };\n if (obj.browserUrl !== undefined && obj.browserUrl !== null) {\n candidate.browserUrl = requireString(obj.browserUrl, `${where}.browserUrl`);\n }\n if (obj.text !== undefined) candidate.text = requireString(obj.text, `${where}.text`);\n if (obj.textSource !== undefined) {\n if (obj.textSource !== \"ax\" && obj.textSource !== \"ocr\") {\n throw new CaptureConfigError(`${where}.textSource: expected 'ax' or 'ocr'`);\n }\n candidate.textSource = obj.textSource;\n }\n if (obj.ax !== undefined) candidate.ax = asObject(obj.ax, `${where}.ax`) as AxNode;\n if (candidate.text === undefined && candidate.ax === undefined) {\n throw new CaptureConfigError(`${where}: a fixture must carry 'text' or 'ax' (one is required)`);\n }\n return candidate;\n}\n\nfunction listFixtureFiles(dir: string): string[] {\n let entries: string[];\n try {\n if (lstatSync(dir).isSymbolicLink()) {\n throw new CaptureConfigError(`replay dir ${dir} is a symlink; refusing to follow it`);\n }\n entries = readdirSync(dir)\n .filter((name) => name.endsWith(\".json\"))\n .sort();\n } catch (err) {\n if (err instanceof CaptureConfigError) throw err;\n throw new CaptureConfigError(`replay dir not found or unreadable: ${dir}`);\n }\n if (entries.length === 0) {\n throw new CaptureConfigError(`replay dir ${dir} contains no *.json fixtures`);\n }\n return entries;\n}\n\n/** Parse + validate every fixture without touching the spool (atomic failure). */\nfunction parseReplayDir(dir: string): { candidates: CaptureCandidate[]; files: number } {\n const entries = listFixtureFiles(dir);\n const candidates: CaptureCandidate[] = [];\n for (const name of entries) {\n const filePath = path.join(dir, name);\n if (lstatSync(filePath).isSymbolicLink()) {\n throw new CaptureConfigError(`replay fixture ${name} is a symlink; refusing to follow it`);\n }\n let raw: unknown;\n try {\n raw = JSON.parse(readFileSync(filePath, \"utf8\"));\n } catch (err) {\n throw new CaptureConfigError(`replay fixture ${name} is not valid JSON: ${(err as Error).message}`);\n }\n const docs = Array.isArray(raw) ? raw : [raw];\n docs.forEach((doc, i) => candidates.push(parseCandidate(doc, `${name}[${i}]`)));\n }\n // Stable ascending capture order (tie: original index) — deterministic dedup.\n const indexed = candidates.map((candidate, index) => ({ candidate, index }));\n indexed.sort((a, b) => {\n const at = Date.parse(a.candidate.capturedAtUtc);\n const bt = Date.parse(b.candidate.capturedAtUtc);\n return at !== bt ? at - bt : a.index - b.index;\n });\n return { candidates: indexed.map((entry) => entry.candidate), files: entries.length };\n}\n\nfunction seedProcessor(processor: CaptureProcessor, spool: Spool): void {\n for (const fp of spool.latestFingerprints()) {\n processor.seed(fp.app, fp.windowTitle, fp.simhash, fp.capturedAtUtc);\n }\n}\n\nfunction commit(processor: CaptureProcessor, spool: Spool, config: DaemonConfig, candidate: CaptureCandidate, result: ReplayResult): void {\n const decision = processor.process(candidate);\n if (decision.action === \"denied\") {\n result.denied += 1;\n } else if (decision.action === \"skipped\") {\n if (decision.reason === \"dedup\") result.deduped += 1;\n else result.ocrSkipped += 1;\n } else {\n const inserted = spool.insertSnapshot(decision.snapshot, config.sessionGapSeconds);\n if (inserted.inserted) {\n result.stored += 1;\n if (inserted.supersededId !== null) result.superseded += 1;\n }\n }\n}\n\n/** Commit size between event-loop yields in the responsive ingester. */\nexport const REPLAY_COMMIT_BATCH = 25;\n\n/** Synchronous ingest: validate the whole directory, then process it all. */\nexport function ingestReplayDir(spool: Spool, dir: string, config: DaemonConfig, ocr?: OcrFn): ReplayResult {\n const { candidates, files } = parseReplayDir(dir);\n const processor = new Processor(config, ocr);\n seedProcessor(processor, spool);\n const result: ReplayResult = {\n files,\n candidates: candidates.length,\n stored: 0,\n denied: 0,\n deduped: 0,\n ocrSkipped: 0,\n superseded: 0,\n aborted: false,\n };\n for (const candidate of candidates) commit(processor, spool, config, candidate, result);\n return result;\n}\n\n/**\n * Responsive ingest: validate up front (atomic), then process in bounded\n * batches with an event-loop yield between them so a co-hosted HTTP server\n * stays responsive during a large replay.\n */\nexport async function ingestReplayDirResponsive(\n spool: Spool,\n dir: string,\n config: DaemonConfig,\n options: { signal?: AbortSignal; ocr?: OcrFn } = {},\n): Promise<ReplayResult> {\n const { candidates, files } = parseReplayDir(dir);\n const processor = new Processor(config, options.ocr);\n seedProcessor(processor, spool);\n const result: ReplayResult = {\n files,\n candidates: candidates.length,\n stored: 0,\n denied: 0,\n deduped: 0,\n ocrSkipped: 0,\n superseded: 0,\n aborted: false,\n };\n for (let i = 0; i < candidates.length; i += REPLAY_COMMIT_BATCH) {\n if (options.signal?.aborted) {\n result.aborted = true;\n break;\n }\n for (const candidate of candidates.slice(i, i + REPLAY_COMMIT_BATCH)) {\n commit(processor, spool, config, candidate, result);\n }\n await new Promise<void>((resolve) => setImmediate(resolve));\n }\n return result;\n}\n","/**\n * DST-aware local-day window. Inlined from @remnic/core's activity digest\n * (capture-screen is à-la-carte and depends on nothing at runtime). Returns the\n * half-open [startUtc, endUtc) UTC instants bounding a local calendar day in an\n * IANA timezone, correct across spring-forward (skipped midnight) and fall-back\n * (repeated midnight) transitions.\n */\n\nimport { CaptureInputError } from \"./errors.js\";\n\nconst DATE_PATTERN = /^\\d{4}-\\d{2}-\\d{2}$/;\n\nfunction isValidDate(date: string): boolean {\n if (typeof date !== \"string\" || !DATE_PATTERN.test(date)) return false;\n // Reject impossible calendar days (2026-02-30, 2026-13-01): the UTC round-trip\n // must reproduce the same Y-M-D, else Date normalized an overflow.\n const parsed = new Date(`${date}T00:00:00Z`);\n return Number.isFinite(parsed.getTime()) && parsed.toISOString().slice(0, 10) === date;\n}\n\nfunction timezoneOffsetIso(instant: Date, timezone: string): string {\n const parts = new Intl.DateTimeFormat(\"en-US\", {\n timeZone: timezone,\n timeZoneName: \"longOffset\",\n }).formatToParts(instant);\n const name = parts.find((part) => part.type === \"timeZoneName\")?.value ?? \"GMT\";\n const match = name.match(/GMT([+-]\\d{2}:\\d{2})?/);\n return match?.[1] ?? \"+00:00\";\n}\n\nfunction shiftIsoDate(date: string, days: number): string {\n const parsed = new Date(`${date}T00:00:00Z`);\n parsed.setUTCDate(parsed.getUTCDate() + days);\n return parsed.toISOString().slice(0, 10);\n}\n\n/**\n * First UTC instant whose local wall-clock is `date` at 00:00. Probe several\n * instants across the day (and the prior UTC day, for zones east of UTC) to\n * collect every offset in play; keep an offset only if constructing local\n * midnight with it lands back on that same offset, then take the EARLIEST such\n * instant — the FIRST 00:00 across a fall-back that repeats local midnight.\n */\nfunction zonedDayStartIso(date: string, timezone: string): string {\n const prevDate = shiftIsoDate(date, -1);\n const probeOffsets = new Set(\n [\n `${prevDate}T12:00:00Z`,\n `${prevDate}T23:00:00Z`,\n `${date}T00:00:00Z`,\n `${date}T12:00:00Z`,\n `${date}T23:00:00Z`,\n ].map((iso) => timezoneOffsetIso(new Date(iso), timezone)),\n );\n let best: number | null = null;\n for (const offset of probeOffsets) {\n const candidate = Date.parse(`${date}T00:00:00${offset}`);\n if (!Number.isFinite(candidate)) continue;\n // Reject an offset whose local midnight does not actually occur (spring\n // forward skipped the wall clock): the offset in effect at the candidate\n // instant must equal the offset we used to build it.\n if (timezoneOffsetIso(new Date(candidate), timezone) !== offset) continue;\n if (best === null || candidate < best) best = candidate;\n }\n if (best === null) {\n // Local midnight was skipped by a spring-forward at 00:00. Advance to the\n // first local wall-clock minute on this date that actually exists, scanning\n // forward up to 3h.\n for (let minute = 1; minute <= 180 && best === null; minute++) {\n const hh = String(Math.floor(minute / 60)).padStart(2, \"0\");\n const mm = String(minute % 60).padStart(2, \"0\");\n for (const offset of probeOffsets) {\n const candidate = Date.parse(`${date}T${hh}:${mm}:00${offset}`);\n if (!Number.isFinite(candidate)) continue;\n if (timezoneOffsetIso(new Date(candidate), timezone) !== offset) continue;\n if (best === null || candidate < best) best = candidate;\n }\n }\n }\n if (best === null) {\n const noon = timezoneOffsetIso(new Date(`${date}T12:00:00Z`), timezone);\n best = Date.parse(`${date}T00:00:00${noon}`);\n }\n if (best === null || !Number.isFinite(best)) {\n throw new CaptureInputError(`could not resolve a local day start for '${date}' in '${timezone}'`);\n }\n return new Date(best).toISOString();\n}\n\n/** Half-open [startUtc, endUtc) UTC ISO bounds of a local day. */\nexport function activityDayWindow(date: string, timezone: string): { startUtc: string; endUtc: string } {\n if (!isValidDate(date)) {\n throw new CaptureInputError(`invalid date '${date}' — expected a real YYYY-MM-DD day`);\n }\n try {\n new Intl.DateTimeFormat(\"en-US\", { timeZone: timezone });\n } catch {\n throw new CaptureInputError(`invalid timezone '${timezone}' — not a known IANA timezone`);\n }\n return {\n startUtc: new Date(zonedDayStartIso(date, timezone)).toISOString(),\n endUtc: new Date(zonedDayStartIso(shiftIsoDate(date, 1), timezone)).toISOString(),\n };\n}\n","/**\n * SQLite spool — the daemon's local buffer of captured screen snapshots.\n *\n * Uses the built-in `node:sqlite` driver (no native dependency), keeping\n * @remnic/capture-screen à-la-carte: installing it pulls zero extra runtime\n * packages. WAL mode + foreign keys are enabled per connection.\n *\n * Schema (names/semantics fixed by issue #1899):\n * snapshots(id, captured_at_utc, app_name, window_title, browser_url NULL,\n * text, text_source (ax or ocr), content_hash UNIQUE, simhash,\n * superseded_by NULL -> snapshots(id))\n * meta(key, value)\n *\n * `content_hash` is UNIQUE and inserts are INSERT OR IGNORE, so re-ingesting an\n * identical snapshot is a content no-op (kill-9 / replay idempotency).\n * Supersession links the previous non-superseded snapshot of the same\n * (app, window) session to its replacement, so a consumer can skip stale states.\n * The read API pages by a stable (captured_at_utc, id) keyset over a half-open\n * local-day window.\n */\n\nimport { chmodSync } from \"node:fs\";\nimport { DatabaseSync } from \"node:sqlite\";\n\nimport { SPOOL_SCHEMA_VERSION } from \"./constants.js\";\nimport { activityDayWindow } from \"./daywindow.js\";\nimport { CaptureConfigError } from \"./errors.js\";\nimport { decodeCursor, encodeCursor } from \"./validate.js\";\n\nexport type TextSource = \"ax\" | \"ocr\";\n\nexport interface SnapshotInput {\n capturedAtUtc: string;\n app: string;\n windowTitle: string;\n browserUrl?: string | null;\n text: string;\n textSource: TextSource;\n contentHash: string;\n simhash: string;\n}\n\nexport interface InsertResult {\n id: number;\n inserted: boolean;\n /** Id of the prior snapshot this insert superseded, or null. */\n supersededId: number | null;\n}\n\nexport interface DaemonSnapshot {\n id: number;\n capturedAtUtc: string;\n app: string;\n windowTitle: string;\n browserUrl: string | null;\n text: string;\n textSource: TextSource;\n contentHash: string;\n simhash: string;\n supersededBy: number | null;\n}\n\nexport interface SnapshotPage {\n snapshots: DaemonSnapshot[];\n nextCursor: string | null;\n}\n\nexport interface QuerySnapshotsOptions {\n date: string;\n timezone: string;\n cursor?: string | null;\n limit: number;\n}\n\nexport interface WindowFingerprint {\n app: string;\n windowTitle: string;\n simhash: string;\n capturedAtUtc: string;\n}\n\ninterface SnapshotRow {\n id: number;\n capturedAtUtc: string;\n app: string;\n windowTitle: string;\n browserUrl: string | null;\n text: string;\n textSource: TextSource;\n contentHash: string;\n simhash: string;\n supersededBy: number | null;\n}\n\nconst SCHEMA_SQL = `\nCREATE TABLE IF NOT EXISTS meta (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS snapshots (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n captured_at_utc TEXT NOT NULL,\n app_name TEXT NOT NULL,\n window_title TEXT NOT NULL,\n browser_url TEXT,\n text TEXT NOT NULL,\n text_source TEXT NOT NULL,\n content_hash TEXT NOT NULL UNIQUE,\n simhash TEXT NOT NULL,\n superseded_by INTEGER REFERENCES snapshots(id) ON DELETE SET NULL\n);\nCREATE INDEX IF NOT EXISTS idx_snap_keyset ON snapshots(captured_at_utc, id);\nCREATE INDEX IF NOT EXISTS idx_snap_window ON snapshots(app_name, window_title, captured_at_utc);\n`;\n\nconst SELECT_COLUMNS =\n \"id, captured_at_utc AS capturedAtUtc, app_name AS app, window_title AS windowTitle, \" +\n \"browser_url AS browserUrl, text, text_source AS textSource, content_hash AS contentHash, \" +\n \"simhash, superseded_by AS supersededBy\";\n\nconst ISO_INSTANT = /^(\\d{4})-(\\d{2})-(\\d{2})T\\d{2}:\\d{2}(:\\d{2}(\\.\\d{1,9})?)?(Z|[+-]\\d{2}:\\d{2})$/;\n\n/**\n * Validate + canonicalize a capture instant to UTC `Z`. Date-only strings and\n * offsetless timestamps are rejected, and impossible calendar dates (Date.parse\n * silently rolls 2026-02-30 → Mar 2) are caught by re-checking the written\n * Y-M-D, so every persisted `captured_at_utc` and every keyset cursor is an\n * unambiguous, order-stable instant.\n */\nfunction canonicalInstant(value: string): string {\n const match = typeof value === \"string\" ? ISO_INSTANT.exec(value) : null;\n if (!match || !Number.isFinite(Date.parse(value))) {\n throw new CaptureConfigError(`capturedAtUtc: '${value}' is not a canonical ISO instant (need date, time, and Z or offset)`);\n }\n const year = Number(match[1]);\n const month = Number(match[2]);\n const day = Number(match[3]);\n const probe = new Date(Date.UTC(year, month - 1, day));\n probe.setUTCFullYear(year);\n if (probe.getUTCFullYear() !== year || probe.getUTCMonth() !== month - 1 || probe.getUTCDate() !== day) {\n throw new CaptureConfigError(`capturedAtUtc: '${value}' is not a real calendar date`);\n }\n return new Date(value).toISOString();\n}\n\nexport class Spool {\n #db: DatabaseSync;\n #closed = false;\n\n constructor(location: string) {\n this.#db = new DatabaseSync(location);\n this.#db.exec(\"PRAGMA journal_mode = WAL;\");\n this.#db.exec(\"PRAGMA foreign_keys = ON;\");\n this.#db.exec(\"PRAGMA busy_timeout = 5000;\");\n this.#db.exec(SCHEMA_SQL);\n if (location !== \":memory:\") {\n // Screen-capture history is sensitive; keep the spool owner-only (best\n // effort; ignored where chmod is a no-op).\n try {\n chmodSync(location, 0o600);\n // WAL mode writes <location>-wal / <location>-shm sidecars that hold the\n // same sensitive capture text; keep them owner-only too (best effort).\n for (const suffix of [\"-wal\", \"-shm\"]) {\n try {\n chmodSync(`${location}${suffix}`, 0o600);\n } catch {\n // sidecar absent yet / no POSIX perms\n }\n }\n } catch {\n // filesystem without POSIX perms\n }\n }\n this.#db\n .prepare(\"INSERT OR IGNORE INTO meta(key, value) VALUES (?, ?)\")\n .run(\"schema_version\", String(SPOOL_SCHEMA_VERSION));\n this.#db\n .prepare(\"INSERT OR IGNORE INTO meta(key, value) VALUES (?, ?)\")\n .run(\"instance_id\", `scr_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 10)}`);\n }\n\n close(): void {\n if (this.#closed) return;\n this.#closed = true;\n this.#db.close();\n }\n\n meta(key: string): string | null {\n const row = this.#db.prepare(\"SELECT value FROM meta WHERE key = ?\").get(key) as { value: string } | undefined;\n return row?.value ?? null;\n }\n\n setMeta(key: string, value: string): void {\n this.#db\n .prepare(\"INSERT INTO meta(key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value\")\n .run(key, value);\n }\n\n /**\n * Insert a snapshot. Idempotent by content_hash (INSERT OR IGNORE): a repeat\n * returns the existing row's id with `inserted:false` and performs no\n * supersession. On a genuinely new row, the previous non-superseded snapshot\n * of the same (app, window) captured within `sessionGapSeconds` is marked\n * superseded_by this row.\n */\n insertSnapshot(input: SnapshotInput, sessionGapSeconds: number): InsertResult {\n if (typeof input.text !== \"string\") throw new CaptureConfigError(\"snapshot.text: expected a string\");\n if (input.textSource !== \"ax\" && input.textSource !== \"ocr\") {\n throw new CaptureConfigError(\"snapshot.textSource: expected 'ax' or 'ocr'\");\n }\n if (typeof input.contentHash !== \"string\" || input.contentHash === \"\") {\n throw new CaptureConfigError(\"snapshot.contentHash: expected a non-empty string\");\n }\n if (typeof input.simhash !== \"string\" || input.simhash === \"\") {\n throw new CaptureConfigError(\"snapshot.simhash: expected a non-empty string\");\n }\n const capturedAtUtc = canonicalInstant(input.capturedAtUtc);\n const browserUrl = input.browserUrl ?? null;\n\n const db = this.#db;\n db.exec(\"BEGIN\");\n try {\n const result = db\n .prepare(\n \"INSERT OR IGNORE INTO snapshots(captured_at_utc, app_name, window_title, browser_url, text, text_source, content_hash, simhash) \" +\n \"VALUES (?,?,?,?,?,?,?,?)\",\n )\n .run(capturedAtUtc, input.app, input.windowTitle, browserUrl, input.text, input.textSource, input.contentHash, input.simhash);\n if (Number(result.changes) === 0) {\n const existing = db.prepare(\"SELECT id FROM snapshots WHERE content_hash = ?\").get(input.contentHash) as\n | { id: number }\n | undefined;\n db.exec(\"COMMIT\");\n return { id: existing?.id ?? 0, inserted: false, supersededId: null };\n }\n const id = Number(result.lastInsertRowid);\n const supersededId = this.#supersede(id, input.app, input.windowTitle, capturedAtUtc, sessionGapSeconds);\n db.exec(\"COMMIT\");\n return { id, inserted: true, supersededId };\n } catch (err) {\n db.exec(\"ROLLBACK\");\n throw err;\n }\n }\n\n /** Link the prior in-session snapshot of the same window to `newId`. */\n #supersede(newId: number, app: string, windowTitle: string, capturedAtUtc: string, sessionGapSeconds: number): number | null {\n const prior = this.#db\n .prepare(\n \"SELECT id, captured_at_utc AS capturedAtUtc FROM snapshots \" +\n \"WHERE app_name = ? AND window_title = ? AND superseded_by IS NULL AND id <> ? \" +\n \"AND captured_at_utc <= ? ORDER BY captured_at_utc DESC, id DESC LIMIT 1\",\n )\n .get(app, windowTitle, newId, capturedAtUtc) as { id: number; capturedAtUtc: string } | undefined;\n if (prior === undefined) return null;\n const gapSeconds = (Date.parse(capturedAtUtc) - Date.parse(prior.capturedAtUtc)) / 1000;\n if (gapSeconds < 0 || gapSeconds > sessionGapSeconds) return null;\n this.#db.prepare(\"UPDATE snapshots SET superseded_by = ? WHERE id = ?\").run(newId, prior.id);\n return prior.id;\n }\n\n getSnapshot(id: number): DaemonSnapshot | null {\n const row = this.#db.prepare(`SELECT ${SELECT_COLUMNS} FROM snapshots WHERE id = ?`).get(id) as\n | SnapshotRow\n | undefined;\n return row ? { ...row } : null;\n }\n\n countSnapshots(): number {\n return (this.#db.prepare(\"SELECT COUNT(*) AS n FROM snapshots\").get() as { n: number }).n;\n }\n\n /**\n * Snapshots whose capture instant falls in the half-open [start, end) UTC\n * window of the requested local day, paged by the stable (captured_at_utc, id)\n * keyset. The id tiebreak keeps pagination correct across snapshots that\n * share a capture instant.\n */\n querySnapshots(opts: QuerySnapshotsOptions): SnapshotPage {\n const { startUtc, endUtc } = activityDayWindow(opts.date, opts.timezone);\n const cursor = decodeCursor(opts.cursor ?? null);\n const afterAt = cursor ? cursor.capturedAtUtc : \"\";\n const afterId = cursor ? cursor.id : 0;\n const rows = this.#db\n .prepare(\n `SELECT ${SELECT_COLUMNS} FROM snapshots ` +\n \"WHERE superseded_by IS NULL AND captured_at_utc >= ? AND captured_at_utc < ? \" +\n \"AND (captured_at_utc > ? OR (captured_at_utc = ? AND id > ?)) \" +\n \"ORDER BY captured_at_utc ASC, id ASC LIMIT ?\",\n )\n .all(startUtc, endUtc, afterAt, afterAt, afterId, opts.limit + 1) as unknown as SnapshotRow[];\n const hasMore = rows.length > opts.limit;\n const page = hasMore ? rows.slice(0, opts.limit) : rows;\n const last = page[page.length - 1];\n return {\n snapshots: page.map((row) => ({ ...row })),\n nextCursor: hasMore && last ? encodeCursor(last.capturedAtUtc, last.id) : null,\n };\n }\n\n /** All snapshots in a local day's window, ordered — the basis for /v1/stats. */\n daySnapshots(date: string, timezone: string): DaemonSnapshot[] {\n const { startUtc, endUtc } = activityDayWindow(date, timezone);\n const rows = this.#db\n .prepare(\n `SELECT ${SELECT_COLUMNS} FROM snapshots WHERE superseded_by IS NULL AND captured_at_utc >= ? AND captured_at_utc < ? ` +\n \"ORDER BY captured_at_utc ASC, id ASC\",\n )\n .all(startUtc, endUtc) as unknown as SnapshotRow[];\n return rows.map((row) => ({ ...row }));\n }\n\n /** Latest non-superseded fingerprint per (app, window) — primes the dedup cache. */\n latestFingerprints(): WindowFingerprint[] {\n const rows = this.#db\n .prepare(\n \"SELECT app_name AS app, window_title AS windowTitle, simhash, captured_at_utc AS capturedAtUtc FROM snapshots s \" +\n \"WHERE superseded_by IS NULL AND id = (SELECT MAX(id) FROM snapshots t WHERE t.app_name = s.app_name AND t.window_title = s.window_title)\",\n )\n .all() as unknown as WindowFingerprint[];\n return rows;\n }\n\n /** Retention janitor: drop snapshots older than `days` (cutoff from `nowMs`). Returns rows removed. */\n pruneOlderThan(days: number, nowMs: number = Date.now()): number {\n const cutoff = new Date(nowMs - days * 86_400_000).toISOString();\n const result = this.#db.prepare(\"DELETE FROM snapshots WHERE captured_at_utc < ?\").run(cutoff);\n return Number(result.changes);\n }\n}\n","/**\n * `remnic-capture-screen` CLI. Subcommands:\n * init | start | stop | status | install-service | logs | test-snapshot\n *\n * `start --replay <dir>` feeds synthetic fixtures through the full capture\n * pipeline + HTTP API (the CI-friendly, hardware-free path). Live capture needs\n * the native helper (@remnic/capture-native-*); where it is absent the daemon\n * still serves the spool and reports axAvailable/ocrAvailable = false.\n *\n * The bearer token comes from the environment (REMNIC_CAPTURE_TOKEN), never\n * argv: a long-lived daemon's argv is world-readable via `ps`/`/proc`, so a\n * token on the command line would let any local account read captured screen\n * text. `--auth-token` is rejected. When the env var is unset, the token file\n * created by `init` is used instead.\n */\n\nimport { spawn } from \"node:child_process\";\nimport { chmodSync, existsSync, lstatSync, mkdirSync, openSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { setTimeout as delay } from \"node:timers/promises\";\nimport { dirname } from \"node:path\";\n\nimport { CaptureProcessor } from \"./capture.js\";\nimport { coerceNumber } from \"./coerce.js\";\nimport { CAPTURE_SCREEN_VERSION } from \"./constants.js\";\nimport {\n defaultDaemonConfig,\n loadDaemonConfig,\n serializeDaemonConfig,\n type DaemonConfig,\n} from \"./config.js\";\nimport {\n isProcessAlive,\n readPidRecord,\n removePidFile,\n removePidFileIfOwner,\n writePidFile,\n type PidRecord,\n} from \"./control.js\";\nimport { startDaemon, type DaemonHandle } from \"./daemon.js\";\nimport { CaptureConfigError, CaptureInputError } from \"./errors.js\";\nimport { NativeHelper, resolveHelperBinaryPath } from \"./helper.js\";\nimport { captureViaHelper } from \"./live.js\";\nimport { capturePaths, captureBaseDir, expandTilde, type CapturePaths } from \"./paths.js\";\nimport { CaptureScheduler } from \"./scheduler.js\";\nimport { ingestReplayDirResponsive } from \"./replay.js\";\nimport { Spool } from \"./spool.js\";\nimport { loadOrCreateToken } from \"./token.js\";\nimport { formatHostForUrl, isLoopbackHost, sanitizeError, stripIpv6Brackets } from \"./util.js\";\n\nexport interface CliIo {\n argv: string[];\n env?: NodeJS.ProcessEnv;\n stdout?: (line: string) => void;\n stderr?: (line: string) => void;\n}\n\ninterface ParsedArgs {\n command: string;\n positionals: string[];\n flags: Record<string, string | boolean>;\n}\n\nconst CAPTURE_TOKEN_ENV = \"REMNIC_CAPTURE_TOKEN\";\n/** Legacy alias honored across Remnic (formerly Engram); see README auth note. */\nconst LEGACY_CAPTURE_TOKEN_ENV = \"ENGRAM_CAPTURE_TOKEN\";\n\n/** Flags that consume the next argv token as their value. */\nconst VALUE_FLAGS: Record<string, true> = {\n replay: true,\n host: true,\n port: true,\n listen: true,\n \"base-dir\": true,\n spool: true,\n lines: true,\n};\n\n/** Standalone boolean flags. */\nconst BOOLEAN_FLAGS: Record<string, true> = {\n foreground: true,\n force: true,\n help: true,\n};\n\n/** Non-global flags each subcommand accepts; anything else is rejected. */\nconst COMMAND_FLAGS: Record<string, Record<string, true>> = {\n init: { force: true },\n start: { foreground: true, replay: true, host: true, port: true, listen: true, spool: true },\n stop: { force: true },\n status: {},\n \"install-service\": {},\n logs: { lines: true },\n \"test-snapshot\": {},\n help: {},\n};\n\n/** Flags accepted regardless of subcommand. */\nconst GLOBAL_FLAGS: Record<string, true> = { \"base-dir\": true, spool: true, help: true };\n\nconst READINESS_TIMEOUT_MS = 10_000;\nconst STOP_TIMEOUT_MS = 10_000;\n\nfunction parseArgs(argv: string[]): ParsedArgs {\n const tokens: string[] = [];\n const flags: Record<string, string | boolean> = {};\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i];\n if (arg.startsWith(\"--\")) {\n const key = arg.slice(2);\n if (key === \"auth-token\") {\n throw new CaptureInputError(\n `--auth-token is not accepted; set the ${CAPTURE_TOKEN_ENV} environment variable instead`,\n );\n }\n if (Object.hasOwn(VALUE_FLAGS, key)) {\n const next = argv[i + 1];\n if (next === undefined || next.startsWith(\"--\")) throw new CaptureInputError(`flag --${key} requires a value`);\n flags[key] = next;\n i += 1;\n } else if (Object.hasOwn(BOOLEAN_FLAGS, key)) {\n flags[key] = true;\n } else {\n throw new CaptureInputError(`unknown flag --${key}`);\n }\n } else {\n tokens.push(arg);\n }\n }\n const command = tokens.length > 0 ? tokens[0] : \"help\";\n return { command, positionals: tokens.slice(1), flags };\n}\n\nfunction resolvePaths(flags: Record<string, string | boolean>, env: NodeJS.ProcessEnv): CapturePaths {\n const baseDir =\n typeof flags[\"base-dir\"] === \"string\"\n ? captureBaseDir({ ...env, REMNIC_CAPTURE_SCREEN_DIR: flags[\"base-dir\"] })\n : captureBaseDir(env);\n const paths = capturePaths(baseDir);\n if (typeof flags.spool === \"string\") return { ...paths, spoolPath: expandTilde(flags.spool) };\n return paths;\n}\n\nfunction loadConfigOrDefault(paths: CapturePaths, stderr: (line: string) => void): DaemonConfig {\n if (existsSync(paths.configPath)) return loadDaemonConfig(paths.configPath);\n stderr(`no config at ${paths.configPath}; using defaults (run \\`init\\` to customize)`);\n return defaultDaemonConfig();\n}\n\nfunction applyBindingOverrides(config: DaemonConfig, flags: Record<string, string | boolean>): DaemonConfig {\n const next = { ...config };\n if (typeof flags.listen === \"string\") {\n const idx = flags.listen.lastIndexOf(\":\");\n if (idx <= 0) throw new CaptureInputError(`--listen expects host:port, got '${flags.listen}'`);\n next.host = flags.listen.slice(0, idx);\n next.port = coerceNumber(flags.listen.slice(idx + 1), \"--listen port\", { integer: true, min: 1, max: 65535 });\n }\n if (typeof flags.host === \"string\") next.host = flags.host;\n if (typeof flags.port === \"string\") next.port = coerceNumber(flags.port, \"--port\", { integer: true, min: 1, max: 65535 });\n next.host = stripIpv6Brackets(next.host);\n return next;\n}\n\nfunction healthUrlFor(host: string, port: number): string {\n return `http://${formatHostForUrl(host)}:${port}/v1/health`;\n}\n\nfunction recordHealthUrl(record: PidRecord, paths: CapturePaths, stderr: (l: string) => void): string {\n if (record.host !== null && record.port !== null) return healthUrlFor(record.host, record.port);\n const config = loadConfigOrDefault(paths, stderr);\n return healthUrlFor(config.host, config.port);\n}\n\n/** Token for probes/serving: env override first, then the on-disk token file. */\nexport function resolveToken(paths: CapturePaths, env: NodeJS.ProcessEnv, create: boolean): string {\n const fromEnv = (env[CAPTURE_TOKEN_ENV] ?? env[LEGACY_CAPTURE_TOKEN_ENV])?.trim();\n if (fromEnv) return fromEnv;\n if (create) return loadOrCreateToken(paths.tokenPath);\n if (existsSync(paths.tokenPath)) return readFileSync(paths.tokenPath, \"utf8\").trim();\n return \"\";\n}\n\nfunction tokenHeader(paths: CapturePaths, env: NodeJS.ProcessEnv): Record<string, string> {\n const token = resolveToken(paths, env, false);\n return token ? { authorization: `Bearer ${token}` } : {};\n}\n\nexport function ensurePrivateDir(dir: string): void {\n let isLink = false;\n try {\n isLink = lstatSync(dir).isSymbolicLink();\n } catch {\n // not present yet — mkdir below creates it\n }\n if (isLink) {\n throw new CaptureInputError(`refusing to use symlinked private directory '${dir}'`);\n }\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n try {\n chmodSync(dir, 0o700);\n } catch {\n // filesystem without POSIX perms\n }\n}\n\n/**\n * Prepare a custom --spool parent WITHOUT clobbering an existing directory's\n * mode. Absent → create a dedicated 0700 dir. Present → refuse a symlink or a\n * non-owner-only dir, but never chmod it, so `--spool ./x.sqlite` can't tighten\n * the caller's cwd. The daemon's own base-dir is handled by ensurePrivateDir.\n */\nexport function ensureSpoolParentDir(spoolPath: string): void {\n const dir = dirname(spoolPath);\n let stat: ReturnType<typeof lstatSync>;\n try {\n stat = lstatSync(dir);\n } catch {\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n return;\n }\n if (stat.isSymbolicLink()) {\n throw new CaptureInputError(`refusing to open the capture spool under symlinked directory '${dir}'`);\n }\n if (!stat.isDirectory()) {\n throw new CaptureInputError(`capture spool parent '${dir}' exists but is not a directory`);\n }\n if (process.platform !== \"win32\" && (stat.mode & 0o077) !== 0) {\n throw new CaptureInputError(\n `capture spool directory '${dir}' is not owner-only (mode ${(stat.mode & 0o777).toString(8)}); ` +\n \"point --spool at a private 0700 directory (the daemon creates one when absent)\",\n );\n }\n}\n\ninterface DaemonIdentity {\n instanceId: string;\n pid: number;\n}\n\nasync function probeIdentity(paths: CapturePaths, env: NodeJS.ProcessEnv, url: string): Promise<DaemonIdentity | null> {\n try {\n const res = await fetch(url, { headers: tokenHeader(paths, env), signal: AbortSignal.timeout(2000) });\n if (!res.ok) return null;\n const body: unknown = await res.json();\n if (body !== null && typeof body === \"object\" && \"instanceId\" in body && \"pid\" in body) {\n const instanceId: unknown = body.instanceId;\n const pid: unknown = body.pid;\n if (typeof instanceId === \"string\" && typeof pid === \"number\") return { instanceId, pid };\n }\n return null;\n } catch {\n return null;\n }\n}\n\nexport function recordChildPidOrTerminate(\n pid: number,\n paths: CapturePaths,\n binding: { host: string; port: number },\n stderr: (l: string) => void,\n): boolean {\n const existing = readPidRecord(paths.pidPath);\n if (existing !== null && existing.pid === pid && existing.instanceId !== null) return true;\n try {\n writePidFile(paths.pidPath, pid, binding);\n return true;\n } catch (err) {\n try {\n process.kill(pid, \"SIGTERM\");\n } catch {\n // already gone\n }\n stderr(`failed to record daemon pid: ${sanitizeError(err)}; terminated child pid ${pid}`);\n return false;\n }\n}\n\nasync function isOwnRunningDaemon(record: PidRecord, paths: CapturePaths, env: NodeJS.ProcessEnv, stderr: (l: string) => void): Promise<boolean> {\n if (record.instanceId === null) return true;\n const live = await probeIdentity(paths, env, recordHealthUrl(record, paths, stderr));\n if (live === null) return true;\n return live.instanceId === record.instanceId && live.pid === record.pid;\n}\n\nexport async function recordedDaemonIsRunning(record: PidRecord, paths: CapturePaths, env: NodeJS.ProcessEnv, stderr: (l: string) => void): Promise<boolean> {\n if (record.pid === process.pid) return false;\n if (!isProcessAlive(record.pid)) return false;\n return isOwnRunningDaemon(record, paths, env, stderr);\n}\n\n/**\n * Run replay ingestion as a supervised task AFTER the daemon is ready. Never\n * throws: success/failure is surfaced via the spool's `replay_status` meta\n * (also on /v1/health) and the daemon log, so a slow/failed replay never kills\n * the daemon or retracts its readiness.\n */\nexport async function superviseReplay(\n spool: Spool,\n replayDir: string,\n config: DaemonConfig,\n io: { stdout: (l: string) => void; stderr: (l: string) => void },\n signal?: AbortSignal,\n): Promise<void> {\n await Promise.resolve();\n spool.setMeta(\"replay_status\", \"running\");\n try {\n const summary = await ingestReplayDirResponsive(spool, replayDir, config, { signal });\n if (summary.aborted) {\n spool.setMeta(\"replay_status\", \"cancelled\");\n io.stdout(`replay: cancelled after ${summary.stored} snapshot(s)`);\n } else {\n spool.setMeta(\"replay_status\", \"ok\");\n io.stdout(\n `replay: stored ${summary.stored} of ${summary.candidates} candidate(s) ` +\n `(denied ${summary.denied}, deduped ${summary.deduped}, ocr-skipped ${summary.ocrSkipped}) ` +\n `from ${summary.files} fixture file(s)`,\n );\n }\n } catch (err) {\n const message = err instanceof CaptureConfigError || err instanceof CaptureInputError ? err.message : sanitizeError(err);\n const sanitized = message.replace(/\\/\\S+/g, \"<path>\");\n spool.setMeta(\"replay_status\", `failed: ${sanitized}`);\n io.stderr(`replay ingestion failed: ${message}`);\n }\n}\n\nfunction cmdInit(paths: CapturePaths, flags: Record<string, string | boolean>, stdout: (l: string) => void): number {\n ensurePrivateDir(paths.baseDir);\n if (existsSync(paths.configPath) && flags.force !== true) {\n stdout(`config already exists at ${paths.configPath} (use --force to overwrite)`);\n } else {\n writeFileSync(paths.configPath, serializeDaemonConfig(defaultDaemonConfig()), \"utf8\");\n stdout(`wrote default config to ${paths.configPath}`);\n }\n const token = loadOrCreateToken(paths.tokenPath);\n stdout(`token ready at ${paths.tokenPath} (${token.length} chars, mode 0600)`);\n stdout(`set ${CAPTURE_TOKEN_ENV} to override the token file when starting the daemon`);\n stdout(`spool will be created at ${paths.spoolPath} on first start`);\n return 0;\n}\n\nasync function cmdStart(\n paths: CapturePaths,\n flags: Record<string, string | boolean>,\n env: NodeJS.ProcessEnv,\n stdout: (l: string) => void,\n stderr: (l: string) => void,\n): Promise<number> {\n const config = applyBindingOverrides(loadConfigOrDefault(paths, stderr), flags);\n if (!isLoopbackHost(config.host)) {\n stderr(\n `refusing to bind non-loopback host '${config.host}': capture-screen serves plain HTTP with no TLS contract; ` +\n \"use a loopback address (127.0.0.1 or ::1)\",\n );\n return 1;\n }\n const replayDir = typeof flags.replay === \"string\" ? expandTilde(flags.replay) : null;\n const previousRecord = readPidRecord(paths.pidPath);\n if (previousRecord !== null) {\n if (await recordedDaemonIsRunning(previousRecord, paths, env, stderr)) {\n stdout(`daemon already running (pid ${previousRecord.pid})`);\n return 0;\n }\n if (previousRecord.pid !== process.pid) removePidFile(paths.pidPath);\n }\n\n if (flags.foreground !== true) {\n const entry = process.argv[1];\n const forwarded = [\"start\", \"--foreground\"];\n if (replayDir) forwarded.push(\"--replay\", replayDir);\n if (typeof flags[\"base-dir\"] === \"string\") forwarded.push(\"--base-dir\", flags[\"base-dir\"]);\n if (typeof flags.spool === \"string\") forwarded.push(\"--spool\", flags.spool);\n if (typeof flags.host === \"string\") forwarded.push(\"--host\", flags.host);\n if (typeof flags.port === \"string\") forwarded.push(\"--port\", flags.port);\n if (typeof flags.listen === \"string\") forwarded.push(\"--listen\", flags.listen);\n ensurePrivateDir(paths.baseDir);\n const logFd = openSync(paths.logPath, \"a\");\n const child = spawn(process.execPath, [entry, ...forwarded], {\n detached: true,\n stdio: [\"ignore\", logFd, logFd],\n env: { ...process.env, ...env },\n });\n child.on(\"error\", (err) => stderr(`daemon failed to launch: ${sanitizeError(err)}`));\n child.unref();\n if (typeof child.pid !== \"number\") {\n stderr(\"failed to spawn daemon process\");\n return 1;\n }\n if (!recordChildPidOrTerminate(child.pid, paths, { host: config.host, port: config.port }, stderr)) return 1;\n const deadline = Date.now() + READINESS_TIMEOUT_MS;\n while (Date.now() < deadline) {\n if (!isProcessAlive(child.pid)) {\n removePidFileIfOwner(paths.pidPath, child.pid);\n stderr(`daemon exited during startup; see ${paths.logPath}`);\n return 1;\n }\n if (readPidRecord(paths.pidPath)?.instanceId) {\n stdout(`started daemon (pid ${child.pid}); listening; logs at ${paths.logPath}`);\n return 0;\n }\n await delay(100);\n }\n try {\n process.kill(child.pid, \"SIGTERM\");\n } catch {\n // already gone\n }\n removePidFileIfOwner(paths.pidPath, child.pid);\n stderr(`daemon did not become ready within ${READINESS_TIMEOUT_MS / 1000}s; terminated pid ${child.pid}. See ${paths.logPath}.`);\n return 1;\n }\n\n ensurePrivateDir(paths.baseDir);\n const token = resolveToken(paths, env, true);\n const helperRes = await resolveHelperBinaryPath(env);\n const axAvailable = helperRes.binaryPath !== null;\n // A custom --spool may live outside base-dir; prepare its parent (create 0700\n // if absent; refuse a symlinked or non-owner-only existing dir) without ever\n // chmod-ing an existing directory.\n ensureSpoolParentDir(paths.spoolPath);\n const spool = new Spool(paths.spoolPath);\n // Retention janitor: prune expired rows once on start so a long-idle spool is\n // trimmed even if the (native) capture loop never runs on this platform.\n spool.pruneOlderThan(config.spoolRetentionDays);\n let handle: DaemonHandle;\n try {\n handle = await startDaemon({\n spool,\n config,\n token,\n capturing: axAvailable,\n axAvailable,\n ocrAvailable: axAvailable,\n helperHint: helperRes.hint,\n });\n } catch (err) {\n spool.close();\n throw err;\n }\n try {\n writePidFile(paths.pidPath, process.pid, {\n instanceId: spool.meta(\"instance_id\"),\n host: handle.host,\n port: handle.port,\n });\n } catch (err) {\n await handle.close();\n spool.close();\n throw err;\n }\n stdout(`listening on ${handle.url}`);\n if (helperRes.hint) stdout(`note: ${helperRes.hint}`);\n const replayAbort = new AbortController();\n const replayTask: Promise<void> = replayDir\n ? superviseReplay(spool, replayDir, config, { stdout, stderr }, replayAbort.signal)\n : Promise.resolve();\n\n // Live capture loop (#1899 Part 1): when the native helper is available, poll\n // the frontmost window and store on change/settle/idle through the same\n // pipeline as replay. On Linux (no helper) the daemon serves + replays only.\n let scheduler: CaptureScheduler | null = null;\n if (helperRes.binaryPath !== null) {\n const processor = new CaptureProcessor(config);\n for (const fp of spool.latestFingerprints()) {\n processor.seed(fp.app, fp.windowTitle, fp.simhash, fp.capturedAtUtc);\n }\n scheduler = new CaptureScheduler(new NativeHelper(helperRes.binaryPath), processor, spool, config, {\n onError: (err) => stderr(`capture loop error: ${sanitizeError(err)}`),\n });\n scheduler.start();\n }\n\n return await new Promise<number>((resolve) => {\n let closing = false;\n const shutdown = () => {\n if (closing) return;\n closing = true;\n // Stop the capture loop and cancel replay, then drain BOTH before closing\n // the spool so no capture/ingestion write can ever hit a closed database.\n replayAbort.abort();\n void Promise.resolve(scheduler?.stop())\n .catch(() => undefined)\n .then(() => replayTask.catch(() => undefined))\n .then(() => handle.close().catch(() => undefined))\n .finally(() => {\n spool.close();\n removePidFileIfOwner(paths.pidPath, process.pid);\n resolve(0);\n });\n };\n process.once(\"SIGINT\", shutdown);\n process.once(\"SIGTERM\", shutdown);\n });\n}\n\nasync function cmdStop(\n paths: CapturePaths,\n flags: Record<string, string | boolean>,\n env: NodeJS.ProcessEnv,\n stdout: (l: string) => void,\n stderr: (l: string) => void,\n): Promise<number> {\n const record = readPidRecord(paths.pidPath);\n if (record === null || !isProcessAlive(record.pid)) {\n removePidFile(paths.pidPath);\n stdout(\"daemon not running\");\n return 0;\n }\n if (record.instanceId !== null) {\n const live = await probeIdentity(paths, env, recordHealthUrl(record, paths, stderr));\n if (live !== null && (live.instanceId !== record.instanceId || live.pid !== record.pid)) {\n stderr(\n `recorded pid ${record.pid} does not match the daemon serving this endpoint (identity/pid mismatch); ` +\n `not signalling and preserving ${paths.pidPath}.`,\n );\n return 1;\n }\n if (live === null && flags.force !== true) {\n stderr(\n `cannot confirm daemon identity for pid ${record.pid} (health unreachable); not signalling. ` +\n `Re-run \\`stop --force\\` to stop it anyway, or remove ${paths.pidPath}.`,\n );\n return 1;\n }\n } else if (flags.force !== true) {\n stderr(\n `cannot verify daemon identity for pid ${record.pid} (no recorded instance id); not signalling. ` +\n `Re-run \\`stop --force\\` to stop it anyway, or remove ${paths.pidPath}.`,\n );\n return 1;\n }\n try {\n process.kill(record.pid, \"SIGTERM\");\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === \"ESRCH\") {\n removePidFile(paths.pidPath);\n stdout(\"daemon not running\");\n return 0;\n }\n if (code === \"EPERM\") {\n stderr(`daemon (pid ${record.pid}) is running but not controllable from this user`);\n return 1;\n }\n throw err;\n }\n const deadline = Date.now() + STOP_TIMEOUT_MS;\n while (Date.now() < deadline) {\n if (!isProcessAlive(record.pid) || readPidRecord(paths.pidPath) === null) {\n stdout(`daemon (pid ${record.pid}) stopped`);\n return 0;\n }\n await delay(100);\n }\n stdout(`sent SIGTERM to daemon (pid ${record.pid}); still shutting down after ${STOP_TIMEOUT_MS / 1000}s`);\n return 0;\n}\n\nasync function cmdStatus(paths: CapturePaths, env: NodeJS.ProcessEnv, stdout: (l: string) => void, stderr: (l: string) => void): Promise<number> {\n const record = readPidRecord(paths.pidPath);\n if (record === null || !isProcessAlive(record.pid)) {\n stdout(\"status: not running\");\n return 0;\n }\n try {\n const res = await fetch(recordHealthUrl(record, paths, stderr), {\n headers: tokenHeader(paths, env),\n signal: AbortSignal.timeout(2000),\n });\n const body = await res.text();\n stdout(`status: running (pid ${record.pid}) — HTTP ${res.status} ${body}`);\n } catch (err) {\n stdout(`status: process alive (pid ${record.pid}) but health check failed (${sanitizeError(err)})`);\n }\n return 0;\n}\n\nfunction cmdInstallService(stdout: (l: string) => void): number {\n stdout(\n `install-service is not yet implemented for platform '${process.platform}'. ` +\n \"No service was installed. Run `remnic-capture-screen start` under your process manager \" +\n \"(launchd on macOS, systemd --user on Linux) once the native capture helper is installed.\",\n );\n return 0;\n}\n\nfunction cmdLogs(paths: CapturePaths, flags: Record<string, string | boolean>, stdout: (l: string) => void): number {\n if (!existsSync(paths.logPath)) {\n stdout(`no log file at ${paths.logPath}`);\n return 0;\n }\n const lines = typeof flags.lines === \"string\" ? coerceNumber(flags.lines, \"--lines\", { integer: true, min: 1 }) : 200;\n const all = readFileSync(paths.logPath, \"utf8\").split(\"\\n\");\n stdout(all.slice(Math.max(0, all.length - lines)).join(\"\\n\"));\n return 0;\n}\n\n/**\n * `test-snapshot`: report what WOULD be captured now and which deny rule (if\n * any) fired — WITHOUT storing. Honest about degradation: with no native helper\n * it reports the unavailable capabilities + install hint and captures nothing.\n */\nasync function cmdTestSnapshot(\n paths: CapturePaths,\n env: NodeJS.ProcessEnv,\n stdout: (l: string) => void,\n stderr: (l: string) => void,\n): Promise<number> {\n const config = loadConfigOrDefault(paths, stderr);\n const helperRes = await resolveHelperBinaryPath(env);\n if (helperRes.binaryPath === null) {\n stdout(\n JSON.stringify(\n {\n capturing: false,\n axAvailable: false,\n ocrAvailable: false,\n helperHint: helperRes.hint,\n note: \"no live snapshot: native capture helper unavailable\",\n },\n null,\n 2,\n ),\n );\n return 0;\n }\n const helper = new NativeHelper(helperRes.binaryPath);\n const processor = new CaptureProcessor(config);\n const decision = await captureViaHelper(helper, processor, config, new Date().toISOString());\n if (decision.action === \"denied\") {\n stdout(JSON.stringify({ action: \"denied\", rule: decision.rule }, null, 2));\n } else if (decision.action === \"skipped\") {\n stdout(JSON.stringify({ action: \"skipped\", reason: decision.reason }, null, 2));\n } else {\n const snap = decision.snapshot;\n stdout(\n JSON.stringify(\n {\n action: \"would-store\",\n app: snap.app,\n windowTitle: snap.windowTitle,\n textSource: snap.textSource,\n textPreview: snap.text.slice(0, 200),\n contentHash: snap.contentHash,\n simhash: snap.simhash,\n denyRule: null,\n },\n null,\n 2,\n ),\n );\n }\n return 0;\n}\n\nfunction usage(stdout: (l: string) => void): number {\n stdout(\n [\n `remnic-capture-screen v${CAPTURE_SCREEN_VERSION}`,\n \"usage: remnic-capture-screen <command> [flags]\",\n \"commands: init | start | stop | status | install-service | logs | test-snapshot\",\n \"start flags: --foreground --replay <dir> --host <h> --port <n> --listen <host:port> --spool <path> --base-dir <dir>\",\n `token: set ${CAPTURE_TOKEN_ENV} (never --auth-token)`,\n ].join(\"\\n\"),\n );\n return 0;\n}\n\nexport async function runCapture(io: CliIo): Promise<number> {\n const env = io.env ?? process.env;\n const stdout = io.stdout ?? ((line: string) => console.log(line));\n const stderr = io.stderr ?? ((line: string) => console.error(line));\n try {\n const parsed = parseArgs(io.argv);\n const paths = resolvePaths(parsed.flags, env);\n if (parsed.flags.help === true || parsed.positionals.includes(\"-h\") || parsed.positionals.includes(\"--help\")) {\n return usage(stdout);\n }\n if (parsed.positionals.length > 0) {\n stderr(`unexpected argument(s): ${parsed.positionals.join(\" \")}`);\n usage(stderr);\n return 2;\n }\n const allowedFlags = COMMAND_FLAGS[parsed.command];\n if (allowedFlags !== undefined) {\n for (const key of Object.keys(parsed.flags)) {\n if (!Object.hasOwn(GLOBAL_FLAGS, key) && !Object.hasOwn(allowedFlags, key)) {\n stderr(`flag --${key} is not valid for command '${parsed.command}'`);\n usage(stderr);\n return 2;\n }\n }\n }\n switch (parsed.command) {\n case \"init\":\n return cmdInit(paths, parsed.flags, stdout);\n case \"start\":\n return await cmdStart(paths, parsed.flags, env, stdout, stderr);\n case \"stop\":\n return await cmdStop(paths, parsed.flags, env, stdout, stderr);\n case \"status\":\n return await cmdStatus(paths, env, stdout, stderr);\n case \"install-service\":\n return cmdInstallService(stdout);\n case \"logs\":\n return cmdLogs(paths, parsed.flags, stdout);\n case \"test-snapshot\":\n return await cmdTestSnapshot(paths, env, stdout, stderr);\n case \"help\":\n case \"--help\":\n case \"-h\":\n return usage(stdout);\n default:\n stderr(`unknown command '${parsed.command}'`);\n usage(stderr);\n return 2;\n }\n } catch (err) {\n if (err instanceof CaptureConfigError || err instanceof CaptureInputError) {\n stderr(`error: ${err.message}`);\n return err instanceof CaptureInputError ? 2 : 1;\n }\n stderr(`error: ${sanitizeError(err)}`);\n return 1;\n }\n}\n","/**\n * Event-driven live capture loop (#1899 Part 1).\n *\n * Each poll fetches the frontmost AX snapshot. A change in the foreground\n * identity (app, windowTitle, browserUrl) opens a settle window; once the\n * foreground has been stable for `settleMs` the snapshot is run through the\n * pipeline and stored. A foreground that never changes is re-sampled at least\n * every `idleFallbackSeconds` (dedup drops it when unchanged). This is pure\n * orchestration — the native macOS helper supplies the snapshots, and the same\n * pipeline (deny-list, redaction, dedup, supersession) that `--replay` uses is\n * applied here, so behaviour is fully testable off-macOS with a fake helper.\n */\n\nimport type { CaptureProcessor } from \"./capture.js\";\nimport type { DaemonConfig } from \"./config.js\";\nimport type { NativeHelper } from \"./helper.js\";\nimport { captureFromSnapshot } from \"./live.js\";\nimport type { Spool } from \"./spool.js\";\n\n/** Injectable clock/timer so the loop is deterministically testable. */\nexport interface SchedulerClock {\n now(): number;\n setInterval(fn: () => void, ms: number): ReturnType<typeof setInterval>;\n clearInterval(handle: ReturnType<typeof setInterval>): void;\n}\n\nconst systemClock: SchedulerClock = {\n now: () => Date.now(),\n setInterval: (fn, ms) => setInterval(fn, ms),\n clearInterval: (handle) => clearInterval(handle),\n};\n\nexport interface SchedulerHooks {\n /** Called when a poll cycle throws (helper failure); the loop keeps running. */\n onError?: (err: unknown) => void;\n /** Called after a snapshot is stored. */\n onStore?: (app: string, windowTitle: string) => void;\n}\n\nexport class CaptureScheduler {\n readonly #helper: NativeHelper;\n readonly #processor: CaptureProcessor;\n readonly #spool: Spool;\n readonly #config: DaemonConfig;\n readonly #hooks: SchedulerHooks;\n readonly #clock: SchedulerClock;\n #timer: ReturnType<typeof setInterval> | null = null;\n #inflight = false;\n #current: Promise<void> | null = null;\n #lastKey: string | null = null;\n #changeAt = 0;\n #pending = false;\n #lastCaptureAt = Number.NEGATIVE_INFINITY;\n\n constructor(\n helper: NativeHelper,\n processor: CaptureProcessor,\n spool: Spool,\n config: DaemonConfig,\n hooks: SchedulerHooks = {},\n clock: SchedulerClock = systemClock,\n ) {\n this.#helper = helper;\n this.#processor = processor;\n this.#spool = spool;\n this.#config = config;\n this.#hooks = hooks;\n this.#clock = clock;\n }\n\n /** Begin polling. Idempotent; stops automatically when `signal` aborts. */\n start(signal?: AbortSignal): void {\n if (this.#timer !== null) return;\n this.#timer = this.#clock.setInterval(() => {\n this.#current = this.tick();\n }, this.#config.pollIntervalMs);\n signal?.addEventListener(\"abort\", () => void this.stop(), { once: true });\n }\n\n /** Stop polling and await any in-flight tick, so a caller can safely close\n * shared resources (the spool) once this resolves. */\n async stop(): Promise<void> {\n if (this.#timer !== null) {\n this.#clock.clearInterval(this.#timer);\n this.#timer = null;\n }\n if (this.#current !== null) {\n await this.#current.catch(() => undefined);\n }\n }\n\n /**\n * One poll cycle. Exposed (not private) so tests can drive the loop\n * deterministically with a fake clock instead of real timers. Overlapping\n * ticks are skipped so a slow helper never runs two captures at once.\n */\n async tick(): Promise<void> {\n if (this.#inflight) return;\n this.#inflight = true;\n try {\n const snap = await this.#helper.axSnapshot({ frontmost: true, maxNodes: this.#config.maxNodes });\n const key = `${snap.app}\\u0000${snap.windowTitle}\\u0000${snap.browserUrl ?? \"\"}`;\n const now = this.#clock.now();\n // Anchor the idle heartbeat to the first poll so the fallback measures from\n // startup, not since epoch — otherwise it would preempt the settle window.\n if (this.#lastCaptureAt === Number.NEGATIVE_INFINITY) {\n this.#lastCaptureAt = now;\n }\n\n if (key !== this.#lastKey) {\n // Foreground changed — open a settle window; capture only once stable.\n this.#lastKey = key;\n this.#changeAt = now;\n this.#pending = true;\n return;\n }\n\n const settled = this.#pending && now - this.#changeAt >= this.#config.settleMs;\n // Idle is a heartbeat for an UNCHANGED foreground; it must not preempt a\n // change that is still inside its settle window.\n const idle = !this.#pending && now - this.#lastCaptureAt >= this.#config.idleFallbackSeconds * 1000;\n if (!settled && !idle) return;\n\n const decision = await captureFromSnapshot(\n snap,\n this.#helper,\n this.#processor,\n this.#config,\n new Date(now).toISOString(),\n );\n this.#pending = false;\n this.#lastCaptureAt = now;\n if (decision.action === \"store\") {\n this.#spool.insertSnapshot(decision.snapshot, this.#config.sessionGapSeconds);\n this.#hooks.onStore?.(snap.app, snap.windowTitle);\n }\n } catch (err) {\n this.#hooks.onError?.(err);\n } finally {\n this.#inflight = false;\n }\n }\n}\n"],"mappings":"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","names":["readFileSync","Buffer","randomBytes","mkdirSync","readFileSync","writeFileSync","path","Buffer","Buffer","Buffer","Buffer","path","readFileSync","path","asObject","requireString","path","readFileSync","chmodSync","chmodSync","spawn","chmodSync","existsSync","lstatSync","mkdirSync","readFileSync","writeFileSync","existsSync","readFileSync","lstatSync","mkdirSync","chmodSync","writeFileSync","spawn"]}
|