@fml-inc/panopticon 0.1.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.
Files changed (124) hide show
  1. package/.claude-plugin/plugin.json +10 -0
  2. package/LICENSE +5 -0
  3. package/README.md +363 -0
  4. package/bin/hook-handler +3 -0
  5. package/bin/mcp-server +3 -0
  6. package/bin/panopticon +3 -0
  7. package/bin/proxy +3 -0
  8. package/bin/server +3 -0
  9. package/dist/api/client.d.ts +67 -0
  10. package/dist/api/client.js +48 -0
  11. package/dist/api/client.js.map +1 -0
  12. package/dist/chunk-3BUJ7URA.js +387 -0
  13. package/dist/chunk-3BUJ7URA.js.map +1 -0
  14. package/dist/chunk-3TZAKV3M.js +158 -0
  15. package/dist/chunk-3TZAKV3M.js.map +1 -0
  16. package/dist/chunk-4SM2H22C.js +169 -0
  17. package/dist/chunk-4SM2H22C.js.map +1 -0
  18. package/dist/chunk-7Q3BJMLG.js +62 -0
  19. package/dist/chunk-7Q3BJMLG.js.map +1 -0
  20. package/dist/chunk-BVOE7A2Z.js +412 -0
  21. package/dist/chunk-BVOE7A2Z.js.map +1 -0
  22. package/dist/chunk-CF4GPWLI.js +170 -0
  23. package/dist/chunk-CF4GPWLI.js.map +1 -0
  24. package/dist/chunk-DZ5HJFB4.js +467 -0
  25. package/dist/chunk-DZ5HJFB4.js.map +1 -0
  26. package/dist/chunk-HQCY722C.js +428 -0
  27. package/dist/chunk-HQCY722C.js.map +1 -0
  28. package/dist/chunk-HRCEIYKU.js +134 -0
  29. package/dist/chunk-HRCEIYKU.js.map +1 -0
  30. package/dist/chunk-K7YUPLES.js +76 -0
  31. package/dist/chunk-K7YUPLES.js.map +1 -0
  32. package/dist/chunk-L7G27XWF.js +130 -0
  33. package/dist/chunk-L7G27XWF.js.map +1 -0
  34. package/dist/chunk-LWXF7YRG.js +626 -0
  35. package/dist/chunk-LWXF7YRG.js.map +1 -0
  36. package/dist/chunk-NXH7AONS.js +1120 -0
  37. package/dist/chunk-NXH7AONS.js.map +1 -0
  38. package/dist/chunk-QK5442ZP.js +55 -0
  39. package/dist/chunk-QK5442ZP.js.map +1 -0
  40. package/dist/chunk-QVK6VGCV.js +1703 -0
  41. package/dist/chunk-QVK6VGCV.js.map +1 -0
  42. package/dist/chunk-RX2RXHBH.js +1699 -0
  43. package/dist/chunk-RX2RXHBH.js.map +1 -0
  44. package/dist/chunk-SEXU2WYG.js +788 -0
  45. package/dist/chunk-SEXU2WYG.js.map +1 -0
  46. package/dist/chunk-SUGSQ4YI.js +264 -0
  47. package/dist/chunk-SUGSQ4YI.js.map +1 -0
  48. package/dist/chunk-TGXFVAID.js +138 -0
  49. package/dist/chunk-TGXFVAID.js.map +1 -0
  50. package/dist/chunk-WLBNFVIG.js +447 -0
  51. package/dist/chunk-WLBNFVIG.js.map +1 -0
  52. package/dist/chunk-XLTCUH5A.js +1072 -0
  53. package/dist/chunk-XLTCUH5A.js.map +1 -0
  54. package/dist/chunk-YVRWVDIA.js +146 -0
  55. package/dist/chunk-YVRWVDIA.js.map +1 -0
  56. package/dist/chunk-ZEC4LRKS.js +176 -0
  57. package/dist/chunk-ZEC4LRKS.js.map +1 -0
  58. package/dist/cli.d.ts +1 -0
  59. package/dist/cli.js +1084 -0
  60. package/dist/cli.js.map +1 -0
  61. package/dist/config-NwoZC-GM.d.ts +20 -0
  62. package/dist/db.d.ts +46 -0
  63. package/dist/db.js +15 -0
  64. package/dist/db.js.map +1 -0
  65. package/dist/doctor.d.ts +37 -0
  66. package/dist/doctor.js +14 -0
  67. package/dist/doctor.js.map +1 -0
  68. package/dist/hooks/handler.d.ts +23 -0
  69. package/dist/hooks/handler.js +295 -0
  70. package/dist/hooks/handler.js.map +1 -0
  71. package/dist/index.d.ts +57 -0
  72. package/dist/index.js +101 -0
  73. package/dist/index.js.map +1 -0
  74. package/dist/mcp/server.d.ts +1 -0
  75. package/dist/mcp/server.js +243 -0
  76. package/dist/mcp/server.js.map +1 -0
  77. package/dist/otlp/server.d.ts +7 -0
  78. package/dist/otlp/server.js +17 -0
  79. package/dist/otlp/server.js.map +1 -0
  80. package/dist/permissions.d.ts +33 -0
  81. package/dist/permissions.js +14 -0
  82. package/dist/permissions.js.map +1 -0
  83. package/dist/pricing.d.ts +29 -0
  84. package/dist/pricing.js +13 -0
  85. package/dist/pricing.js.map +1 -0
  86. package/dist/proxy/server.d.ts +10 -0
  87. package/dist/proxy/server.js +20 -0
  88. package/dist/proxy/server.js.map +1 -0
  89. package/dist/prune.d.ts +18 -0
  90. package/dist/prune.js +13 -0
  91. package/dist/prune.js.map +1 -0
  92. package/dist/query.d.ts +56 -0
  93. package/dist/query.js +27 -0
  94. package/dist/query.js.map +1 -0
  95. package/dist/reparse-636YZCE3.js +14 -0
  96. package/dist/reparse-636YZCE3.js.map +1 -0
  97. package/dist/repo.d.ts +17 -0
  98. package/dist/repo.js +9 -0
  99. package/dist/repo.js.map +1 -0
  100. package/dist/scanner.d.ts +73 -0
  101. package/dist/scanner.js +15 -0
  102. package/dist/scanner.js.map +1 -0
  103. package/dist/sdk.d.ts +82 -0
  104. package/dist/sdk.js +208 -0
  105. package/dist/sdk.js.map +1 -0
  106. package/dist/server.d.ts +5 -0
  107. package/dist/server.js +25 -0
  108. package/dist/server.js.map +1 -0
  109. package/dist/setup.d.ts +35 -0
  110. package/dist/setup.js +19 -0
  111. package/dist/setup.js.map +1 -0
  112. package/dist/sync/index.d.ts +29 -0
  113. package/dist/sync/index.js +32 -0
  114. package/dist/sync/index.js.map +1 -0
  115. package/dist/targets.d.ts +279 -0
  116. package/dist/targets.js +20 -0
  117. package/dist/targets.js.map +1 -0
  118. package/dist/types-D-MYCBol.d.ts +128 -0
  119. package/dist/types.d.ts +164 -0
  120. package/dist/types.js +1 -0
  121. package/dist/types.js.map +1 -0
  122. package/hooks/hooks.json +274 -0
  123. package/package.json +124 -0
  124. package/skills/panopticon-optimize/SKILL.md +222 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/sync/reader.ts","../src/sync/registry.ts","../src/sync/watermark.ts"],"sourcesContent":["import { getDb } from \"../db/schema.js\";\nimport type {\n HookEventRecord,\n MessageSyncRecord,\n MetricRow,\n OtelLogRecord,\n OtelSpanRecord,\n RepoConfigSnapshotRecord,\n ScannerEventRecord,\n ScannerTurnRecord,\n SessionSyncRecord,\n ToolCallSyncRecord,\n UserConfigSnapshotRecord,\n} from \"./types.js\";\n\nfunction parseJson(raw: string | null): Record<string, unknown> | null {\n if (!raw) return null;\n try {\n const parsed = JSON.parse(raw);\n return typeof parsed === \"object\" && parsed !== null ? parsed : null;\n } catch {\n return null;\n }\n}\n\n// ── Hook events ──────────────────────────────────────────────────────────────\n\nconst HOOK_EVENTS_SQL = `\n SELECT h.id, h.session_id, h.sync_id, h.event_type, h.timestamp_ms, h.cwd, h.repository,\n h.tool_name, decompress(h.payload) as payload,\n h.user_prompt, h.file_path, h.command, h.tool_result,\n s.target\n FROM hook_events h\n LEFT JOIN sessions s ON s.session_id = h.session_id\n WHERE h.id > ?\n ORDER BY h.id\n LIMIT ?\n`;\n\nexport function readHookEvents(\n afterId: number,\n limit: number,\n): { rows: HookEventRecord[]; maxId: number } {\n const db = getDb();\n const rawRows = db.prepare(HOOK_EVENTS_SQL).all(afterId, limit) as Array<{\n id: number;\n session_id: string;\n sync_id: string | null;\n event_type: string;\n timestamp_ms: number;\n cwd: string | null;\n repository: string | null;\n tool_name: string | null;\n payload: string | null;\n user_prompt: string | null;\n file_path: string | null;\n command: string | null;\n tool_result: string | null;\n target: string | null;\n }>;\n\n const rows: HookEventRecord[] = rawRows.map((r) => ({\n hookId: r.id,\n sessionId: r.session_id,\n syncId: r.sync_id,\n eventType: r.event_type,\n timestampMs: r.timestamp_ms,\n cwd: r.cwd,\n repository: r.repository,\n toolName: r.tool_name,\n payload: parseJson(r.payload),\n userPrompt: r.user_prompt,\n filePath: r.file_path,\n command: r.command,\n toolResult: r.tool_result,\n target: r.target,\n }));\n\n const maxId = rows.length > 0 ? rows[rows.length - 1].hookId : afterId;\n return { rows, maxId };\n}\n\n// ── OTLP logs ────────────────────────────────────────────────────────────────\n\n/** Body types that hooks already cover — filtered out when hooks are installed. */\nconst HOOK_COVERED_BODIES = [\n // Claude Code\n \"claude_code.user_prompt\",\n \"claude_code.tool_decision\",\n \"claude_code.tool_result\",\n // Gemini CLI\n \"gemini_cli.user_prompt\",\n \"gemini_cli.tool_call\",\n \"gemini_cli.hook_call\",\n];\n\nconst ALL_LOGS_SQL = `\n SELECT id, sync_id, timestamp_ns, body, attributes, resource_attributes,\n severity_text, session_id, prompt_id, trace_id, span_id\n FROM otel_logs\n WHERE id > ?\n ORDER BY id\n LIMIT ?\n`;\n\ninterface RawOtelLogRow {\n id: number;\n sync_id: string | null;\n timestamp_ns: number;\n body: string | null;\n attributes: string | null;\n resource_attributes: string | null;\n severity_text: string | null;\n session_id: string | null;\n prompt_id: string | null;\n trace_id: string | null;\n span_id: string | null;\n}\n\nfunction mapOtelRows(rawRows: RawOtelLogRow[]): OtelLogRecord[] {\n return rawRows.map((r) => ({\n id: r.id,\n syncId: r.sync_id,\n timestampNs: r.timestamp_ns,\n body: r.body,\n attributes: parseJson(r.attributes),\n resourceAttributes: parseJson(r.resource_attributes),\n severityText: r.severity_text,\n sessionId: r.session_id,\n promptId: r.prompt_id,\n traceId: r.trace_id,\n spanId: r.span_id,\n }));\n}\n\n/**\n * Read OTLP logs in batches.\n * When hooksInstalled is true, filters out body types that hooks cover\n * (tool_decision, tool_result, user_prompt) to avoid double-counting.\n */\n/**\n * Read OTLP logs in batches.\n * When hooksInstalled is true, filters out body types that hooks cover\n * (tool_decision, tool_result, user_prompt) to avoid double-counting.\n *\n * maxId advances to the highest id scanned (not just the highest returned),\n * so the watermark skips past blocks of filtered rows without stalling.\n */\nexport function readOtelLogs(\n afterId: number,\n limit: number,\n hooksInstalled: boolean,\n): { rows: OtelLogRecord[]; maxId: number } {\n const db = getDb();\n\n if (!hooksInstalled) {\n const rawRows = db\n .prepare(ALL_LOGS_SQL)\n .all(afterId, limit) as RawOtelLogRow[];\n const rows = mapOtelRows(rawRows);\n const maxId = rows.length > 0 ? rows[rows.length - 1].id : afterId;\n return { rows, maxId };\n }\n\n // When filtering, we need the max id from the unfiltered range so the\n // watermark advances past blocks of hook-covered rows.\n const scanMaxId = (\n db\n .prepare(\n \"SELECT MAX(id) as m FROM (SELECT id FROM otel_logs WHERE id > ? ORDER BY id LIMIT ?)\",\n )\n .get(afterId, limit) as { m: number | null }\n ).m;\n\n if (scanMaxId == null) return { rows: [], maxId: afterId };\n\n // Only return filtered rows within the scanned range (id <= scanMaxId),\n // not beyond it — otherwise we'd skip ahead of the scan window.\n const rawRows = db\n .prepare(\n `SELECT id, sync_id, timestamp_ns, body, attributes, resource_attributes,\n severity_text, session_id, prompt_id, trace_id, span_id\n FROM otel_logs\n WHERE id > ? AND id <= ?\n AND body NOT IN (${HOOK_COVERED_BODIES.map(() => \"?\").join(\", \")})\n ORDER BY id`,\n )\n .all(afterId, scanMaxId, ...HOOK_COVERED_BODIES) as RawOtelLogRow[];\n\n const rows = mapOtelRows(rawRows);\n return { rows, maxId: scanMaxId };\n}\n\n// ── Metrics ──────────────────────────────────────────────────────────────────\n\nconst METRICS_SQL = `\n SELECT id, sync_id, timestamp_ns, name, value, metric_type, unit,\n attributes, resource_attributes, session_id\n FROM otel_metrics\n WHERE id > ?\n ORDER BY id\n LIMIT ?\n`;\n\nexport function readMetrics(\n afterId: number,\n limit: number,\n): { rows: MetricRow[]; maxId: number } {\n const db = getDb();\n const rawRows = db.prepare(METRICS_SQL).all(afterId, limit) as Array<{\n id: number;\n sync_id: string | null;\n timestamp_ns: number;\n name: string;\n value: number;\n metric_type: string | null;\n unit: string | null;\n attributes: string | null;\n resource_attributes: string | null;\n session_id: string | null;\n }>;\n\n const rows: MetricRow[] = rawRows.map((r) => ({\n id: r.id,\n syncId: r.sync_id,\n timestampNs: r.timestamp_ns,\n name: r.name,\n value: r.value,\n metricType: r.metric_type,\n unit: r.unit,\n attributes: parseJson(r.attributes),\n resourceAttributes: parseJson(r.resource_attributes),\n sessionId: r.session_id,\n }));\n\n const maxId = rows.length > 0 ? rows[rows.length - 1].id : afterId;\n return { rows, maxId };\n}\n\n// ── Scanner turns ───────────────────────────────────────────────────────────\n\nconst SCANNER_TURNS_SQL = `\n SELECT t.id, t.session_id, t.sync_id, t.source, t.turn_index, t.timestamp_ms,\n t.model, t.role, t.content_preview,\n t.input_tokens, t.output_tokens, t.cache_read_tokens,\n t.cache_creation_tokens, t.reasoning_tokens,\n s.cli_version\n FROM scanner_turns t\n LEFT JOIN sessions s ON s.session_id = t.session_id\n WHERE t.id > ?\n ORDER BY t.id\n LIMIT ?\n`;\n\nexport function readScannerTurns(\n afterId: number,\n limit: number,\n): { rows: ScannerTurnRecord[]; maxId: number } {\n const db = getDb();\n const rawRows = db.prepare(SCANNER_TURNS_SQL).all(afterId, limit) as Array<{\n id: number;\n session_id: string;\n sync_id: string | null;\n source: string;\n turn_index: number;\n timestamp_ms: number;\n model: string | null;\n role: string | null;\n content_preview: string | null;\n input_tokens: number;\n output_tokens: number;\n cache_read_tokens: number;\n cache_creation_tokens: number;\n reasoning_tokens: number;\n cli_version: string | null;\n }>;\n\n const rows: ScannerTurnRecord[] = rawRows.map((r) => ({\n id: r.id,\n sessionId: r.session_id,\n syncId: r.sync_id,\n source: r.source,\n turnIndex: r.turn_index,\n timestampMs: r.timestamp_ms,\n model: r.model,\n role: r.role,\n contentPreview: r.content_preview,\n inputTokens: r.input_tokens,\n outputTokens: r.output_tokens,\n cacheReadTokens: r.cache_read_tokens,\n cacheCreationTokens: r.cache_creation_tokens,\n reasoningTokens: r.reasoning_tokens,\n cliVersion: r.cli_version,\n }));\n\n const maxId = rows.length > 0 ? rows[rows.length - 1].id : afterId;\n return { rows, maxId };\n}\n\n// ── Scanner events ──────────────────────────────────────────────────────────\n\nconst SCANNER_EVENTS_SQL = `\n SELECT id, session_id, sync_id, source, event_type, timestamp_ms,\n tool_name, tool_input, tool_output, content, metadata\n FROM scanner_events\n WHERE id > ?\n ORDER BY id\n LIMIT ?\n`;\n\nexport function readScannerEvents(\n afterId: number,\n limit: number,\n): { rows: ScannerEventRecord[]; maxId: number } {\n const db = getDb();\n const rawRows = db.prepare(SCANNER_EVENTS_SQL).all(afterId, limit) as Array<{\n id: number;\n session_id: string;\n sync_id: string | null;\n source: string;\n event_type: string;\n timestamp_ms: number;\n tool_name: string | null;\n tool_input: string | null;\n tool_output: string | null;\n content: string | null;\n metadata: string | null;\n }>;\n\n const rows: ScannerEventRecord[] = rawRows.map((r) => ({\n id: r.id,\n sessionId: r.session_id,\n syncId: r.sync_id,\n source: r.source,\n eventType: r.event_type,\n timestampMs: r.timestamp_ms,\n toolName: r.tool_name,\n toolInput: r.tool_input,\n toolOutput: r.tool_output,\n content: r.content,\n metadata: parseJson(r.metadata),\n }));\n\n const maxId = rows.length > 0 ? rows[rows.length - 1].id : afterId;\n return { rows, maxId };\n}\n\n// ── OTLP spans ─────────────────────────────────────────────────────────────\n\nconst SPANS_SQL = `\n SELECT id, trace_id, span_id, parent_span_id, name, kind,\n start_time_ns, end_time_ns, status_code, status_message,\n attributes, resource_attributes, session_id\n FROM otel_spans\n WHERE id > ?\n ORDER BY id\n LIMIT ?\n`;\n\nexport function readOtelSpans(\n afterId: number,\n limit: number,\n): { rows: OtelSpanRecord[]; maxId: number } {\n const db = getDb();\n const rawRows = db.prepare(SPANS_SQL).all(afterId, limit) as Array<{\n id: number;\n trace_id: string;\n span_id: string;\n parent_span_id: string | null;\n name: string;\n kind: number | null;\n start_time_ns: number;\n end_time_ns: number;\n status_code: number | null;\n status_message: string | null;\n attributes: string | null;\n resource_attributes: string | null;\n session_id: string | null;\n }>;\n\n const rows: OtelSpanRecord[] = rawRows.map((r) => ({\n id: r.id,\n traceId: r.trace_id,\n spanId: r.span_id,\n parentSpanId: r.parent_span_id,\n name: r.name,\n kind: r.kind,\n startTimeNs: r.start_time_ns,\n endTimeNs: r.end_time_ns,\n statusCode: r.status_code,\n statusMessage: r.status_message,\n attributes: parseJson(r.attributes),\n resourceAttributes: parseJson(r.resource_attributes),\n sessionId: r.session_id,\n }));\n\n const maxId = rows.length > 0 ? rows[rows.length - 1].id : afterId;\n return { rows, maxId };\n}\n\n// ── JSON parse helpers for config snapshots ─────────────────────────────────\n\nfunction parseJsonArray(raw: string | null): unknown[] {\n if (!raw) return [];\n try {\n const parsed = JSON.parse(raw);\n return Array.isArray(parsed) ? parsed : [];\n } catch {\n return [];\n }\n}\n\nfunction parseJsonObject(raw: string | null): Record<string, unknown> {\n if (!raw) return {};\n try {\n const parsed = JSON.parse(raw);\n return typeof parsed === \"object\" &&\n parsed !== null &&\n !Array.isArray(parsed)\n ? parsed\n : {};\n } catch {\n return {};\n }\n}\n\n// ── User config snapshots ──────────────────────────────────────────────────\n\nconst USER_CONFIG_SQL = `\n SELECT id, device_name, snapshot_at_ms, content_hash,\n permissions, enabled_plugins, hooks, commands, rules, skills\n FROM user_config_snapshots\n WHERE id > ?\n ORDER BY id\n LIMIT ?\n`;\n\nexport function readUserConfigSnapshots(\n afterId: number,\n limit: number,\n): { rows: UserConfigSnapshotRecord[]; maxId: number } {\n const db = getDb();\n const rawRows = db.prepare(USER_CONFIG_SQL).all(afterId, limit) as Array<{\n id: number;\n device_name: string;\n snapshot_at_ms: number;\n content_hash: string;\n permissions: string | null;\n enabled_plugins: string | null;\n hooks: string | null;\n commands: string | null;\n rules: string | null;\n skills: string | null;\n }>;\n\n const rows: UserConfigSnapshotRecord[] = rawRows.map((r) => ({\n id: r.id,\n deviceName: r.device_name,\n snapshotAtMs: r.snapshot_at_ms,\n contentHash: r.content_hash,\n permissions: parseJsonObject(r.permissions),\n enabledPlugins: parseJsonArray(r.enabled_plugins),\n hooks: parseJsonArray(r.hooks),\n commands: parseJsonArray(r.commands),\n rules: parseJsonArray(r.rules),\n skills: parseJsonArray(r.skills),\n }));\n\n const maxId = rows.length > 0 ? rows[rows.length - 1].id : afterId;\n return { rows, maxId };\n}\n\n// ── Repo config snapshots ──────────────────────────────────────────────────\n\nconst REPO_CONFIG_SQL = `\n SELECT id, repository, cwd, session_id, snapshot_at_ms, content_hash,\n hooks, mcp_servers, commands, agents, rules,\n local_hooks, local_mcp_servers, local_permissions,\n local_is_gitignored, instructions\n FROM repo_config_snapshots\n WHERE id > ?\n ORDER BY id\n LIMIT ?\n`;\n\nexport function readRepoConfigSnapshots(\n afterId: number,\n limit: number,\n): { rows: RepoConfigSnapshotRecord[]; maxId: number } {\n const db = getDb();\n const rawRows = db.prepare(REPO_CONFIG_SQL).all(afterId, limit) as Array<{\n id: number;\n repository: string;\n cwd: string;\n session_id: string | null;\n snapshot_at_ms: number;\n content_hash: string;\n hooks: string | null;\n mcp_servers: string | null;\n commands: string | null;\n agents: string | null;\n rules: string | null;\n local_hooks: string | null;\n local_mcp_servers: string | null;\n local_permissions: string | null;\n local_is_gitignored: number;\n instructions: string | null;\n }>;\n\n const rows: RepoConfigSnapshotRecord[] = rawRows.map((r) => ({\n id: r.id,\n repository: r.repository,\n cwd: r.cwd,\n sessionId: r.session_id,\n snapshotAtMs: r.snapshot_at_ms,\n contentHash: r.content_hash,\n hooks: parseJsonArray(r.hooks),\n mcpServers: parseJsonArray(r.mcp_servers),\n commands: parseJsonArray(r.commands),\n agents: parseJsonArray(r.agents),\n rules: parseJsonArray(r.rules),\n localHooks: parseJsonArray(r.local_hooks),\n localMcpServers: parseJsonArray(r.local_mcp_servers),\n localPermissions: parseJsonObject(r.local_permissions),\n localIsGitignored: r.local_is_gitignored === 1,\n instructions: parseJsonArray(r.instructions),\n }));\n\n const maxId = rows.length > 0 ? rows[rows.length - 1].id : afterId;\n return { rows, maxId };\n}\n\n// ── Messages ────────────────────────────────────────────────────────────────\n\nconst MESSAGES_SQL = `\n SELECT id, session_id, ordinal, role, content, timestamp_ms,\n has_thinking, has_tool_use, content_length, is_system,\n model, token_usage, context_tokens, output_tokens,\n has_context_tokens, has_output_tokens\n FROM messages\n WHERE id > ?\n ORDER BY id\n LIMIT ?\n`;\n\nexport function readMessages(\n afterId: number,\n limit: number,\n): { rows: MessageSyncRecord[]; maxId: number } {\n const db = getDb();\n const rawRows = db.prepare(MESSAGES_SQL).all(afterId, limit) as Array<{\n id: number;\n session_id: string;\n ordinal: number;\n role: string;\n content: string;\n timestamp_ms: number | null;\n has_thinking: number;\n has_tool_use: number;\n content_length: number;\n is_system: number;\n model: string;\n token_usage: string;\n context_tokens: number;\n output_tokens: number;\n has_context_tokens: number;\n has_output_tokens: number;\n }>;\n\n const rows: MessageSyncRecord[] = rawRows.map((r) => ({\n id: r.id,\n sessionId: r.session_id,\n ordinal: r.ordinal,\n role: r.role,\n content: r.content,\n timestampMs: r.timestamp_ms,\n hasThinking: r.has_thinking === 1,\n hasToolUse: r.has_tool_use === 1,\n contentLength: r.content_length,\n isSystem: r.is_system === 1,\n model: r.model,\n tokenUsage: r.token_usage,\n contextTokens: r.context_tokens,\n outputTokens: r.output_tokens,\n hasContextTokens: r.has_context_tokens === 1,\n hasOutputTokens: r.has_output_tokens === 1,\n }));\n\n const maxId = rows.length > 0 ? rows[rows.length - 1].id : afterId;\n return { rows, maxId };\n}\n\n// ── Tool calls ──────────────────────────────────────────────────────────────\n\nconst TOOL_CALLS_SQL = `\n SELECT id, message_id, session_id, sync_id, tool_name, category, tool_use_id,\n input_json, skill_name, result_content_length, result_content,\n subagent_session_id\n FROM tool_calls\n WHERE id > ?\n ORDER BY id\n LIMIT ?\n`;\n\nexport function readToolCalls(\n afterId: number,\n limit: number,\n): { rows: ToolCallSyncRecord[]; maxId: number } {\n const db = getDb();\n const rawRows = db.prepare(TOOL_CALLS_SQL).all(afterId, limit) as Array<{\n id: number;\n message_id: number;\n session_id: string;\n sync_id: string | null;\n tool_name: string;\n category: string;\n tool_use_id: string | null;\n input_json: string | null;\n skill_name: string | null;\n result_content_length: number | null;\n result_content: string | null;\n subagent_session_id: string | null;\n }>;\n\n const rows: ToolCallSyncRecord[] = rawRows.map((r) => ({\n id: r.id,\n messageId: r.message_id,\n sessionId: r.session_id,\n syncId: r.sync_id,\n toolName: r.tool_name,\n category: r.category,\n toolUseId: r.tool_use_id,\n inputJson: r.input_json,\n skillName: r.skill_name,\n resultContentLength: r.result_content_length,\n resultContent: r.result_content,\n subagentSessionId: r.subagent_session_id,\n }));\n\n const maxId = rows.length > 0 ? rows[rows.length - 1].id : afterId;\n return { rows, maxId };\n}\n\n// ── Sessions (watermark on sync_seq) ────────────────────────────────────────\n\nconst SESSIONS_SQL = `\n SELECT session_id, target, started_at_ms, ended_at_ms, cwd, first_prompt,\n permission_mode, agent_version,\n total_input_tokens, total_output_tokens, total_cache_read_tokens,\n total_cache_creation_tokens, total_reasoning_tokens, turn_count,\n models, summary, tool_counts, hook_tool_counts, event_type_counts, hook_event_type_counts, sync_seq,\n project, machine, message_count, user_message_count,\n parent_session_id, relationship_type, is_automated, created_at\n FROM sessions\n WHERE sync_seq > ?\n ORDER BY sync_seq\n LIMIT ?\n`;\n\nexport function readSessions(\n afterSeq: number,\n limit: number,\n): { rows: SessionSyncRecord[]; maxId: number } {\n const db = getDb();\n const rawRows = db.prepare(SESSIONS_SQL).all(afterSeq, limit) as Array<{\n session_id: string;\n target: string | null;\n started_at_ms: number | null;\n ended_at_ms: number | null;\n cwd: string | null;\n first_prompt: string | null;\n permission_mode: string | null;\n agent_version: string | null;\n total_input_tokens: number | null;\n total_output_tokens: number | null;\n total_cache_read_tokens: number | null;\n total_cache_creation_tokens: number | null;\n total_reasoning_tokens: number | null;\n turn_count: number | null;\n models: string | null;\n summary: string | null;\n tool_counts: string | null;\n hook_tool_counts: string | null;\n event_type_counts: string | null;\n hook_event_type_counts: string | null;\n sync_seq: number;\n project: string | null;\n machine: string;\n message_count: number;\n user_message_count: number;\n parent_session_id: string | null;\n relationship_type: string;\n is_automated: number;\n created_at: number | null;\n }>;\n\n if (rawRows.length === 0) return { rows: [], maxId: afterSeq };\n\n const sessionIds = rawRows.map((r) => r.session_id);\n const placeholders = sessionIds.map(() => \"?\").join(\", \");\n\n const repoRows = db\n .prepare(\n `SELECT session_id, repository, first_seen_ms, git_user_name, git_user_email, branch\n FROM session_repositories\n WHERE session_id IN (${placeholders})`,\n )\n .all(...sessionIds) as Array<{\n session_id: string;\n repository: string;\n first_seen_ms: number;\n git_user_name: string | null;\n git_user_email: string | null;\n branch: string | null;\n }>;\n\n const cwdRows = db\n .prepare(\n `SELECT session_id, cwd, first_seen_ms\n FROM session_cwds\n WHERE session_id IN (${placeholders})`,\n )\n .all(...sessionIds) as Array<{\n session_id: string;\n cwd: string;\n first_seen_ms: number;\n }>;\n\n const reposBySession = new Map<string, SessionSyncRecord[\"repositories\"]>();\n for (const r of repoRows) {\n const list = reposBySession.get(r.session_id) ?? [];\n list.push({\n repository: r.repository,\n firstSeenMs: r.first_seen_ms,\n gitUserName: r.git_user_name,\n gitUserEmail: r.git_user_email,\n branch: r.branch,\n });\n reposBySession.set(r.session_id, list);\n }\n\n const cwdsBySession = new Map<string, SessionSyncRecord[\"cwds\"]>();\n for (const r of cwdRows) {\n const list = cwdsBySession.get(r.session_id) ?? [];\n list.push({ cwd: r.cwd, firstSeenMs: r.first_seen_ms });\n cwdsBySession.set(r.session_id, list);\n }\n\n const rows: SessionSyncRecord[] = rawRows.map((r) => ({\n sessionId: r.session_id,\n target: r.target,\n startedAtMs: r.started_at_ms,\n endedAtMs: r.ended_at_ms,\n cwd: r.cwd,\n firstPrompt: r.first_prompt,\n permissionMode: r.permission_mode,\n agentVersion: r.agent_version,\n totalInputTokens: r.total_input_tokens,\n totalOutputTokens: r.total_output_tokens,\n totalCacheReadTokens: r.total_cache_read_tokens,\n totalCacheCreationTokens: r.total_cache_creation_tokens,\n totalReasoningTokens: r.total_reasoning_tokens,\n turnCount: r.turn_count,\n models: r.models,\n summary: r.summary,\n toolCounts: parseJsonObject(r.tool_counts) as Record<string, number>,\n hookToolCounts: parseJsonObject(r.hook_tool_counts) as Record<\n string,\n number\n >,\n eventTypeCounts: parseJsonObject(r.event_type_counts) as Record<\n string,\n number\n >,\n hookEventTypeCounts: parseJsonObject(r.hook_event_type_counts) as Record<\n string,\n number\n >,\n project: r.project,\n machine: r.machine,\n messageCount: r.message_count,\n userMessageCount: r.user_message_count,\n parentSessionId: r.parent_session_id,\n relationshipType: r.relationship_type,\n isAutomated: r.is_automated === 1,\n createdAt: r.created_at,\n repositories: reposBySession.get(r.session_id) ?? [],\n cwds: cwdsBySession.get(r.session_id) ?? [],\n }));\n\n const maxId = rawRows[rawRows.length - 1].sync_seq;\n return { rows, maxId };\n}\n\n// ── Per-session readers (for gated sync) ──────────────────────────────────\n\nexport function readSessionMessages(\n sessionId: string,\n afterId: number,\n limit: number,\n): { rows: MessageSyncRecord[]; maxId: number } {\n const db = getDb();\n const rawRows = db\n .prepare(\n `SELECT id, session_id, ordinal, role, content, timestamp_ms,\n has_thinking, has_tool_use, content_length, is_system,\n model, token_usage, context_tokens, output_tokens,\n has_context_tokens, has_output_tokens\n FROM messages\n WHERE session_id = ? AND id > ?\n ORDER BY id\n LIMIT ?`,\n )\n .all(sessionId, afterId, limit) as Array<{\n id: number;\n session_id: string;\n ordinal: number;\n role: string;\n content: string;\n timestamp_ms: number | null;\n has_thinking: number;\n has_tool_use: number;\n content_length: number;\n is_system: number;\n model: string;\n token_usage: string;\n context_tokens: number;\n output_tokens: number;\n has_context_tokens: number;\n has_output_tokens: number;\n }>;\n\n const rows: MessageSyncRecord[] = rawRows.map((r) => ({\n id: r.id,\n sessionId: r.session_id,\n ordinal: r.ordinal,\n role: r.role,\n content: r.content,\n timestampMs: r.timestamp_ms,\n hasThinking: r.has_thinking === 1,\n hasToolUse: r.has_tool_use === 1,\n contentLength: r.content_length,\n isSystem: r.is_system === 1,\n model: r.model,\n tokenUsage: r.token_usage,\n contextTokens: r.context_tokens,\n outputTokens: r.output_tokens,\n hasContextTokens: r.has_context_tokens === 1,\n hasOutputTokens: r.has_output_tokens === 1,\n }));\n\n const maxId = rows.length > 0 ? rows[rows.length - 1].id : afterId;\n return { rows, maxId };\n}\n\nexport function readSessionToolCalls(\n sessionId: string,\n afterId: number,\n limit: number,\n): { rows: ToolCallSyncRecord[]; maxId: number } {\n const db = getDb();\n const rawRows = db\n .prepare(\n `SELECT id, message_id, session_id, sync_id, tool_name, category, tool_use_id,\n input_json, skill_name, result_content_length, result_content,\n subagent_session_id\n FROM tool_calls\n WHERE session_id = ? AND id > ?\n ORDER BY id\n LIMIT ?`,\n )\n .all(sessionId, afterId, limit) as Array<{\n id: number;\n message_id: number;\n session_id: string;\n sync_id: string | null;\n tool_name: string;\n category: string;\n tool_use_id: string | null;\n input_json: string | null;\n skill_name: string | null;\n result_content_length: number | null;\n result_content: string | null;\n subagent_session_id: string | null;\n }>;\n\n const rows: ToolCallSyncRecord[] = rawRows.map((r) => ({\n id: r.id,\n messageId: r.message_id,\n sessionId: r.session_id,\n syncId: r.sync_id,\n toolName: r.tool_name,\n category: r.category,\n toolUseId: r.tool_use_id,\n inputJson: r.input_json,\n skillName: r.skill_name,\n resultContentLength: r.result_content_length,\n resultContent: r.result_content,\n subagentSessionId: r.subagent_session_id,\n }));\n\n const maxId = rows.length > 0 ? rows[rows.length - 1].id : afterId;\n return { rows, maxId };\n}\n\nexport function readSessionScannerTurns(\n sessionId: string,\n afterId: number,\n limit: number,\n): { rows: ScannerTurnRecord[]; maxId: number } {\n const db = getDb();\n const rawRows = db\n .prepare(\n `SELECT t.id, t.session_id, t.sync_id, t.source, t.turn_index, t.timestamp_ms,\n t.model, t.role, t.content_preview,\n t.input_tokens, t.output_tokens, t.cache_read_tokens,\n t.cache_creation_tokens, t.reasoning_tokens,\n s.cli_version\n FROM scanner_turns t\n LEFT JOIN sessions s ON s.session_id = t.session_id\n WHERE t.session_id = ? AND t.id > ?\n ORDER BY t.id\n LIMIT ?`,\n )\n .all(sessionId, afterId, limit) as Array<{\n id: number;\n session_id: string;\n sync_id: string | null;\n source: string;\n turn_index: number;\n timestamp_ms: number;\n model: string | null;\n role: string | null;\n content_preview: string | null;\n input_tokens: number;\n output_tokens: number;\n cache_read_tokens: number;\n cache_creation_tokens: number;\n reasoning_tokens: number;\n cli_version: string | null;\n }>;\n\n const rows: ScannerTurnRecord[] = rawRows.map((r) => ({\n id: r.id,\n sessionId: r.session_id,\n syncId: r.sync_id,\n source: r.source,\n turnIndex: r.turn_index,\n timestampMs: r.timestamp_ms,\n model: r.model,\n role: r.role,\n contentPreview: r.content_preview,\n inputTokens: r.input_tokens,\n outputTokens: r.output_tokens,\n cacheReadTokens: r.cache_read_tokens,\n cacheCreationTokens: r.cache_creation_tokens,\n reasoningTokens: r.reasoning_tokens,\n cliVersion: r.cli_version,\n }));\n\n const maxId = rows.length > 0 ? rows[rows.length - 1].id : afterId;\n return { rows, maxId };\n}\n\nexport function readSessionScannerEvents(\n sessionId: string,\n afterId: number,\n limit: number,\n): { rows: ScannerEventRecord[]; maxId: number } {\n const db = getDb();\n const rawRows = db\n .prepare(\n `SELECT id, session_id, sync_id, source, event_type, timestamp_ms,\n tool_name, tool_input, tool_output, content, metadata\n FROM scanner_events\n WHERE session_id = ? AND id > ?\n ORDER BY id\n LIMIT ?`,\n )\n .all(sessionId, afterId, limit) as Array<{\n id: number;\n session_id: string;\n sync_id: string | null;\n source: string;\n event_type: string;\n timestamp_ms: number;\n tool_name: string | null;\n tool_input: string | null;\n tool_output: string | null;\n content: string | null;\n metadata: string | null;\n }>;\n\n const rows: ScannerEventRecord[] = rawRows.map((r) => ({\n id: r.id,\n sessionId: r.session_id,\n syncId: r.sync_id,\n source: r.source,\n eventType: r.event_type,\n timestampMs: r.timestamp_ms,\n toolName: r.tool_name,\n toolInput: r.tool_input,\n toolOutput: r.tool_output,\n content: r.content,\n metadata: parseJson(r.metadata),\n }));\n\n const maxId = rows.length > 0 ? rows[rows.length - 1].id : afterId;\n return { rows, maxId };\n}\n\nexport function readSessionHookEvents(\n sessionId: string,\n afterId: number,\n limit: number,\n): { rows: HookEventRecord[]; maxId: number } {\n const db = getDb();\n const rawRows = db\n .prepare(\n `SELECT h.id, h.session_id, h.sync_id, h.event_type, h.timestamp_ms, h.cwd, h.repository,\n h.tool_name, decompress(h.payload) as payload,\n h.user_prompt, h.file_path, h.command, h.tool_result,\n s.target\n FROM hook_events h\n LEFT JOIN sessions s ON s.session_id = h.session_id\n WHERE h.session_id = ? AND h.id > ?\n ORDER BY h.id\n LIMIT ?`,\n )\n .all(sessionId, afterId, limit) as Array<{\n id: number;\n session_id: string;\n sync_id: string | null;\n event_type: string;\n timestamp_ms: number;\n cwd: string | null;\n repository: string | null;\n tool_name: string | null;\n payload: string | null;\n user_prompt: string | null;\n file_path: string | null;\n command: string | null;\n tool_result: string | null;\n target: string | null;\n }>;\n\n const rows: HookEventRecord[] = rawRows.map((r) => ({\n hookId: r.id,\n sessionId: r.session_id,\n syncId: r.sync_id,\n eventType: r.event_type,\n timestampMs: r.timestamp_ms,\n cwd: r.cwd,\n repository: r.repository,\n toolName: r.tool_name,\n payload: parseJson(r.payload),\n userPrompt: r.user_prompt,\n filePath: r.file_path,\n command: r.command,\n toolResult: r.tool_result,\n target: r.target,\n }));\n\n const maxId = rows.length > 0 ? rows[rows.length - 1].hookId : afterId;\n return { rows, maxId };\n}\n\nexport function readSessionOtelLogs(\n sessionId: string,\n afterId: number,\n limit: number,\n): { rows: OtelLogRecord[]; maxId: number } {\n const db = getDb();\n const rawRows = db\n .prepare(\n `SELECT id, sync_id, timestamp_ns, body, attributes, resource_attributes,\n severity_text, session_id, prompt_id, trace_id, span_id\n FROM otel_logs\n WHERE session_id = ? AND id > ?\n ORDER BY id\n LIMIT ?`,\n )\n .all(sessionId, afterId, limit) as RawOtelLogRow[];\n\n const rows = mapOtelRows(rawRows);\n const maxId = rows.length > 0 ? rows[rows.length - 1].id : afterId;\n return { rows, maxId };\n}\n\nexport function readSessionOtelMetrics(\n sessionId: string,\n afterId: number,\n limit: number,\n): { rows: MetricRow[]; maxId: number } {\n const db = getDb();\n const rawRows = db\n .prepare(\n `SELECT id, sync_id, timestamp_ns, name, value, metric_type, unit,\n attributes, resource_attributes, session_id\n FROM otel_metrics\n WHERE session_id = ? AND id > ?\n ORDER BY id\n LIMIT ?`,\n )\n .all(sessionId, afterId, limit) as Array<{\n id: number;\n sync_id: string | null;\n timestamp_ns: number;\n name: string;\n value: number;\n metric_type: string | null;\n unit: string | null;\n attributes: string | null;\n resource_attributes: string | null;\n session_id: string | null;\n }>;\n\n const rows: MetricRow[] = rawRows.map((r) => ({\n id: r.id,\n syncId: r.sync_id,\n timestampNs: r.timestamp_ns,\n name: r.name,\n value: r.value,\n metricType: r.metric_type,\n unit: r.unit,\n attributes: parseJson(r.attributes),\n resourceAttributes: parseJson(r.resource_attributes),\n sessionId: r.session_id,\n }));\n\n const maxId = rows.length > 0 ? rows[rows.length - 1].id : afterId;\n return { rows, maxId };\n}\n\nexport function readSessionOtelSpans(\n sessionId: string,\n afterId: number,\n limit: number,\n): { rows: OtelSpanRecord[]; maxId: number } {\n const db = getDb();\n const rawRows = db\n .prepare(\n `SELECT id, trace_id, span_id, parent_span_id, name, kind,\n start_time_ns, end_time_ns, status_code, status_message,\n attributes, resource_attributes, session_id\n FROM otel_spans\n WHERE session_id = ? AND id > ?\n ORDER BY id\n LIMIT ?`,\n )\n .all(sessionId, afterId, limit) as Array<{\n id: number;\n trace_id: string;\n span_id: string;\n parent_span_id: string | null;\n name: string;\n kind: number | null;\n start_time_ns: number;\n end_time_ns: number;\n status_code: number | null;\n status_message: string | null;\n attributes: string | null;\n resource_attributes: string | null;\n session_id: string | null;\n }>;\n\n const rows: OtelSpanRecord[] = rawRows.map((r) => ({\n id: r.id,\n traceId: r.trace_id,\n spanId: r.span_id,\n parentSpanId: r.parent_span_id,\n name: r.name,\n kind: r.kind,\n startTimeNs: r.start_time_ns,\n endTimeNs: r.end_time_ns,\n statusCode: r.status_code,\n statusMessage: r.status_message,\n attributes: parseJson(r.attributes),\n resourceAttributes: parseJson(r.resource_attributes),\n sessionId: r.session_id,\n }));\n\n const maxId = rows.length > 0 ? rows[rows.length - 1].id : afterId;\n return { rows, maxId };\n}\n\n/** Map of table name → per-session reader for gated sync. */\nexport const SESSION_READERS: Record<\n string,\n (\n sessionId: string,\n afterId: number,\n limit: number,\n ) => { rows: unknown[]; maxId: number }\n> = {\n messages: readSessionMessages,\n tool_calls: readSessionToolCalls,\n scanner_turns: readSessionScannerTurns,\n scanner_events: readSessionScannerEvents,\n hook_events: readSessionHookEvents,\n otel_logs: readSessionOtelLogs,\n otel_metrics: readSessionOtelMetrics,\n otel_spans: readSessionOtelSpans,\n};\n","import {\n readHookEvents,\n readMessages,\n readMetrics,\n readOtelLogs,\n readOtelSpans,\n readRepoConfigSnapshots,\n readScannerEvents,\n readScannerTurns,\n readSessions,\n readToolCalls,\n readUserConfigSnapshots,\n} from \"./reader.js\";\nimport type { TableSyncDescriptor } from \"./types.js\";\n\n/**\n * Ordered list of table sync descriptors. The order matches the\n * round-robin execution order in the sync loop.\n *\n * All tables sync via POST /v1/sync with {table, rows} payload.\n * Session-linked tables are filtered by repo attribution in the sync loop.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport const TABLE_SYNC_REGISTRY: TableSyncDescriptor<any>[] = [\n // ── Session-linked tables (filtered by repo attribution) ─────────────────\n\n {\n table: \"sessions\",\n logNoun: \"sessions\",\n read: (afterId, limit) => readSessions(afterId, limit),\n sessionLinked: true,\n },\n\n {\n table: \"messages\",\n logNoun: \"messages\",\n read: (afterId, limit) => readMessages(afterId, limit),\n sessionLinked: true,\n },\n\n {\n table: \"tool_calls\",\n logNoun: \"tool calls\",\n read: (afterId, limit) => readToolCalls(afterId, limit),\n sessionLinked: true,\n },\n\n {\n table: \"scanner_turns\",\n logNoun: \"turns\",\n read: (afterId, limit) => readScannerTurns(afterId, limit),\n sessionLinked: true,\n },\n\n {\n table: \"scanner_events\",\n logNoun: \"events\",\n read: (afterId, limit) => readScannerEvents(afterId, limit),\n sessionLinked: true,\n },\n\n {\n table: \"hook_events\",\n logNoun: \"events\",\n read: (afterId, limit) => readHookEvents(afterId, limit),\n sessionLinked: true,\n },\n\n {\n table: \"otel_logs\",\n logNoun: \"logs\",\n read: (afterId, limit) => readOtelLogs(afterId, limit, false),\n sessionLinked: true,\n },\n\n {\n table: \"otel_metrics\",\n logNoun: \"metrics\",\n read: (afterId, limit) => readMetrics(afterId, limit),\n sessionLinked: true,\n },\n\n {\n table: \"otel_spans\",\n logNoun: \"spans\",\n read: (afterId, limit) => readOtelSpans(afterId, limit),\n sessionLinked: true,\n },\n\n // ── Non-session tables (always synced) ────────────────────────────────────\n\n {\n table: \"user_config_snapshots\",\n logNoun: \"snapshots\",\n read: (afterId, limit) => readUserConfigSnapshots(afterId, limit),\n sessionLinked: false,\n },\n\n {\n table: \"repo_config_snapshots\",\n logNoun: \"snapshots\",\n read: (afterId, limit) => readRepoConfigSnapshots(afterId, limit),\n sessionLinked: false,\n },\n];\n","import { getDb } from \"../db/schema.js\";\nimport { TABLE_SYNC_REGISTRY } from \"./registry.js\";\n\nexport function watermarkKey(table: string, targetName: string): string {\n return `${table}:${targetName}`;\n}\n\nexport function readWatermark(key: string): number {\n const db = getDb();\n const row = db\n .prepare(\"SELECT value FROM watermarks WHERE key = ?\")\n .get(key) as { value: number } | undefined;\n return row?.value ?? 0;\n}\n\nexport function writeWatermark(key: string, value: number): void {\n const db = getDb();\n db.prepare(\n \"INSERT INTO watermarks (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value\",\n ).run(key, value);\n}\n\nexport function resetWatermarks(targetName?: string): void {\n const db = getDb();\n if (targetName) {\n const stmt = db.prepare(\"DELETE FROM watermarks WHERE key = ?\");\n for (const desc of TABLE_SYNC_REGISTRY) {\n stmt.run(watermarkKey(desc.table, targetName));\n }\n db.prepare(\"DELETE FROM target_session_sync WHERE target = ?\").run(\n targetName,\n );\n } else {\n db.prepare(\"DELETE FROM watermarks\").run();\n db.prepare(\"DELETE FROM target_session_sync\").run();\n }\n}\n"],"mappings":"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","names":["rawRows","rows"]}
@@ -0,0 +1,264 @@
1
+ import {
2
+ readWatermark,
3
+ watermarkKey
4
+ } from "./chunk-SEXU2WYG.js";
5
+ import {
6
+ loadUnifiedConfig
7
+ } from "./chunk-QK5442ZP.js";
8
+ import {
9
+ dbStats
10
+ } from "./chunk-LWXF7YRG.js";
11
+ import {
12
+ allTargets
13
+ } from "./chunk-QVK6VGCV.js";
14
+ import {
15
+ closeDb,
16
+ getDb
17
+ } from "./chunk-DZ5HJFB4.js";
18
+ import {
19
+ config
20
+ } from "./chunk-K7YUPLES.js";
21
+
22
+ // src/doctor.ts
23
+ import fs from "fs";
24
+ import http from "http";
25
+ import os from "os";
26
+ import path from "path";
27
+ function checkHealth(port, host) {
28
+ return new Promise((resolve) => {
29
+ const req = http.request(
30
+ { hostname: host, port, path: "/health", method: "GET", timeout: 2e3 },
31
+ (res) => {
32
+ res.resume();
33
+ resolve(res.statusCode === 200);
34
+ }
35
+ );
36
+ req.on("error", () => resolve(false));
37
+ req.on("timeout", () => {
38
+ req.destroy();
39
+ resolve(false);
40
+ });
41
+ req.end();
42
+ });
43
+ }
44
+ async function doctor() {
45
+ const checks = [];
46
+ const isSandbox = process.env.SANDBOX !== void 0 || fs.existsSync(path.join(os.homedir(), ".sandbox-home"));
47
+ if (!fs.existsSync(config.dbPath)) {
48
+ checks.push({
49
+ label: "Database",
50
+ status: "fail",
51
+ detail: `Not found at ${config.dbPath}. Run install to initialize.`
52
+ });
53
+ } else {
54
+ try {
55
+ const stats = dbStats();
56
+ const total = stats.hook_events + stats.otel_logs + stats.otel_metrics;
57
+ checks.push({
58
+ label: "Database",
59
+ status: "ok",
60
+ detail: `${total} rows (${stats.hook_events} hooks, ${stats.otel_logs} logs, ${stats.otel_metrics} metrics)`
61
+ });
62
+ closeDb();
63
+ } catch (err) {
64
+ checks.push({
65
+ label: "Database",
66
+ status: "fail",
67
+ detail: `Error: ${err instanceof Error ? err.message : String(err)}`
68
+ });
69
+ }
70
+ }
71
+ const serverUp = await checkHealth(config.port, "127.0.0.1");
72
+ if (serverUp) {
73
+ checks.push({
74
+ label: "Server",
75
+ status: "ok",
76
+ detail: `Listening on 127.0.0.1:${config.port}`
77
+ });
78
+ } else {
79
+ checks.push({
80
+ label: "Server",
81
+ status: "warn",
82
+ detail: `Not responding on port ${config.port}`
83
+ });
84
+ }
85
+ const shellRc = path.join(
86
+ os.homedir(),
87
+ process.env.SHELL?.includes("zsh") ? ".zshrc" : ".bashrc"
88
+ );
89
+ const rcContent = fs.existsSync(shellRc) ? fs.readFileSync(shellRc, "utf-8") : "";
90
+ const hasTelemetry = rcContent.includes("CLAUDE_CODE_ENABLE_TELEMETRY");
91
+ const hasEndpoint = rcContent.includes("OTEL_EXPORTER_OTLP_ENDPOINT");
92
+ if (hasTelemetry && hasEndpoint) {
93
+ checks.push({
94
+ label: "Shell Env",
95
+ status: "ok",
96
+ detail: `Telemetry configured in ${path.basename(shellRc)}`
97
+ });
98
+ } else if (hasTelemetry || hasEndpoint) {
99
+ checks.push({
100
+ label: "Shell Env",
101
+ status: "warn",
102
+ detail: `Partial config in ${path.basename(shellRc)} \u2014 re-run install`
103
+ });
104
+ } else {
105
+ checks.push({
106
+ label: "Shell Env",
107
+ status: "warn",
108
+ detail: `Not configured. Run install to set up telemetry.`
109
+ });
110
+ }
111
+ const tools = [];
112
+ for (const t of allTargets()) {
113
+ if (t.detect.isInstalled()) {
114
+ tools.push({
115
+ name: t.detect.displayName,
116
+ dir: t.config.dir,
117
+ configured: t.detect.isConfigured()
118
+ });
119
+ }
120
+ }
121
+ if (tools.length === 0) {
122
+ checks.push({
123
+ label: "Tools",
124
+ status: "warn",
125
+ detail: "No supported coding tools found"
126
+ });
127
+ } else {
128
+ const configured = tools.filter((t) => t.configured);
129
+ const unconfigured = tools.filter((t) => !t.configured);
130
+ if (unconfigured.length === 0) {
131
+ checks.push({
132
+ label: "Tools",
133
+ status: "ok",
134
+ detail: configured.map((t) => t.name).join(", ")
135
+ });
136
+ } else {
137
+ const detail = tools.map((t) => `${t.name} ${t.configured ? "\u2713" : "(not configured)"}`).join(", ");
138
+ checks.push({
139
+ label: "Tools",
140
+ status: "warn",
141
+ detail: `${detail} \u2014 re-run install`
142
+ });
143
+ }
144
+ }
145
+ if (fs.existsSync(config.dbPath)) {
146
+ try {
147
+ const db = getDb();
148
+ const latest = db.prepare("SELECT MAX(timestamp_ms) as ts FROM hook_events").get();
149
+ closeDb();
150
+ if (latest?.ts) {
151
+ const ago = Date.now() - latest.ts;
152
+ const minutes = Math.floor(ago / 6e4);
153
+ if (minutes < 60) {
154
+ checks.push({
155
+ label: "Data Flow",
156
+ status: "ok",
157
+ detail: `Last event ${minutes}m ago`
158
+ });
159
+ } else if (minutes < 1440) {
160
+ checks.push({
161
+ label: "Data Flow",
162
+ status: "ok",
163
+ detail: `Last event ${Math.floor(minutes / 60)}h ago`
164
+ });
165
+ } else {
166
+ checks.push({
167
+ label: "Data Flow",
168
+ status: "warn",
169
+ detail: `Last event ${Math.floor(minutes / 1440)}d ago`
170
+ });
171
+ }
172
+ } else {
173
+ checks.push({
174
+ label: "Data Flow",
175
+ status: "warn",
176
+ detail: "No events recorded yet"
177
+ });
178
+ }
179
+ } catch {
180
+ }
181
+ }
182
+ try {
183
+ const cfg = loadUnifiedConfig();
184
+ const targets = cfg.sync.targets;
185
+ if (targets.length === 0) {
186
+ checks.push({
187
+ label: "Sync",
188
+ status: "ok",
189
+ detail: "No targets configured"
190
+ });
191
+ } else {
192
+ const tables = ["hook_events", "otel_logs", "otel_metrics"];
193
+ const targetDetails = [];
194
+ for (const t of targets) {
195
+ const watermarks = tables.map(
196
+ (table) => readWatermark(watermarkKey(table, t.name))
197
+ );
198
+ const minWm = Math.min(...watermarks);
199
+ const wmLabel = minWm > 0 ? `synced to #${minWm}` : "not synced yet";
200
+ targetDetails.push(`${t.name} \u2192 ${t.url} (${wmLabel})`);
201
+ }
202
+ checks.push({
203
+ label: "Sync",
204
+ status: "ok",
205
+ detail: `${targets.length} target${targets.length > 1 ? "s" : ""}: ${targetDetails.join("; ")}`
206
+ });
207
+ }
208
+ } catch {
209
+ checks.push({
210
+ label: "Sync",
211
+ status: "warn",
212
+ detail: "Could not read sync config"
213
+ });
214
+ }
215
+ try {
216
+ const cfg = loadUnifiedConfig();
217
+ const dsn = process.env.PANOPTICON_SENTRY_DSN ?? cfg.sentryDsn;
218
+ checks.push({
219
+ label: "Sentry",
220
+ status: dsn ? "ok" : "ok",
221
+ detail: dsn ? "Configured" : "Not configured (optional)"
222
+ });
223
+ } catch {
224
+ }
225
+ let recentEvents = [];
226
+ let recentErrors = [];
227
+ if (fs.existsSync(config.dbPath)) {
228
+ try {
229
+ const db = getDb();
230
+ const events = db.prepare(
231
+ "SELECT event_type, tool_name, timestamp_ms FROM hook_events ORDER BY id DESC LIMIT 3"
232
+ ).all();
233
+ recentEvents = events.map((e) => ({
234
+ eventType: e.event_type,
235
+ toolName: e.tool_name,
236
+ timestamp: new Date(e.timestamp_ms).toISOString()
237
+ }));
238
+ const errors = db.prepare(
239
+ "SELECT id, body FROM otel_logs WHERE severity_text = 'ERROR' ORDER BY id DESC LIMIT 3"
240
+ ).all();
241
+ recentErrors = errors.map((e) => ({
242
+ id: e.id,
243
+ body: (e.body ?? "").slice(0, 200)
244
+ }));
245
+ closeDb();
246
+ } catch {
247
+ }
248
+ }
249
+ return {
250
+ checks,
251
+ system: {
252
+ os: `${os.platform()} (${os.release()} ${os.arch()})`,
253
+ node: process.version,
254
+ sandbox: isSandbox
255
+ },
256
+ recentEvents,
257
+ recentErrors
258
+ };
259
+ }
260
+
261
+ export {
262
+ doctor
263
+ };
264
+ //# sourceMappingURL=chunk-SUGSQ4YI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/doctor.ts"],"sourcesContent":["/**\n * Doctor — diagnostic checks for panopticon health.\n *\n * Exported for use by fml-plugin and other integrators.\n */\n\nimport fs from \"node:fs\";\nimport http from \"node:http\";\nimport os from \"node:os\";\nimport { config } from \"./config.js\";\nimport { dbStats } from \"./db/query.js\";\nimport { closeDb, getDb } from \"./db/schema.js\";\nimport { readWatermark, watermarkKey } from \"./sync/watermark.js\";\nimport { allTargets } from \"./targets/index.js\";\nimport { loadUnifiedConfig } from \"./unified-config.js\";\n\nexport interface CheckResult {\n label: string;\n status: \"ok\" | \"warn\" | \"fail\";\n detail: string;\n}\n\nexport interface RecentEvent {\n eventType: string;\n toolName: string | null;\n timestamp: string;\n}\n\nexport interface RecentError {\n id: number;\n body: string;\n}\n\nexport interface DoctorResult {\n checks: CheckResult[];\n system: {\n os: string;\n node: string;\n sandbox: boolean;\n };\n recentEvents: RecentEvent[];\n recentErrors: RecentError[];\n}\n\nfunction checkHealth(port: number, host: string): Promise<boolean> {\n return new Promise((resolve) => {\n const req = http.request(\n { hostname: host, port, path: \"/health\", method: \"GET\", timeout: 2000 },\n (res) => {\n res.resume();\n resolve(res.statusCode === 200);\n },\n );\n req.on(\"error\", () => resolve(false));\n req.on(\"timeout\", () => {\n req.destroy();\n resolve(false);\n });\n req.end();\n });\n}\n\n/**\n * Run panopticon diagnostic checks.\n *\n * Returns structured results — callers handle display.\n */\nexport async function doctor(): Promise<DoctorResult> {\n const checks: CheckResult[] = [];\n\n const isSandbox =\n process.env.SANDBOX !== undefined ||\n fs.existsSync(path.join(os.homedir(), \".sandbox-home\"));\n\n // 1. Database\n if (!fs.existsSync(config.dbPath)) {\n checks.push({\n label: \"Database\",\n status: \"fail\",\n detail: `Not found at ${config.dbPath}. Run install to initialize.`,\n });\n } else {\n try {\n const stats = dbStats();\n const total = stats.hook_events + stats.otel_logs + stats.otel_metrics;\n checks.push({\n label: \"Database\",\n status: \"ok\",\n detail: `${total} rows (${stats.hook_events} hooks, ${stats.otel_logs} logs, ${stats.otel_metrics} metrics)`,\n });\n closeDb();\n } catch (err) {\n checks.push({\n label: \"Database\",\n status: \"fail\",\n detail: `Error: ${err instanceof Error ? err.message : String(err)}`,\n });\n }\n }\n\n // 2. Server\n const serverUp = await checkHealth(config.port, \"127.0.0.1\");\n if (serverUp) {\n checks.push({\n label: \"Server\",\n status: \"ok\",\n detail: `Listening on 127.0.0.1:${config.port}`,\n });\n } else {\n checks.push({\n label: \"Server\",\n status: \"warn\",\n detail: `Not responding on port ${config.port}`,\n });\n }\n\n // 3. Shell environment\n const shellRc = path.join(\n os.homedir(),\n process.env.SHELL?.includes(\"zsh\") ? \".zshrc\" : \".bashrc\",\n );\n const rcContent = fs.existsSync(shellRc)\n ? fs.readFileSync(shellRc, \"utf-8\")\n : \"\";\n\n const hasTelemetry = rcContent.includes(\"CLAUDE_CODE_ENABLE_TELEMETRY\");\n const hasEndpoint = rcContent.includes(\"OTEL_EXPORTER_OTLP_ENDPOINT\");\n\n if (hasTelemetry && hasEndpoint) {\n checks.push({\n label: \"Shell Env\",\n status: \"ok\",\n detail: `Telemetry configured in ${path.basename(shellRc)}`,\n });\n } else if (hasTelemetry || hasEndpoint) {\n checks.push({\n label: \"Shell Env\",\n status: \"warn\",\n detail: `Partial config in ${path.basename(shellRc)} — re-run install`,\n });\n } else {\n checks.push({\n label: \"Shell Env\",\n status: \"warn\",\n detail: `Not configured. Run install to set up telemetry.`,\n });\n }\n\n // 4. Coding tool integration\n const tools: Array<{ name: string; dir: string; configured: boolean }> = [];\n\n for (const t of allTargets()) {\n if (t.detect.isInstalled()) {\n tools.push({\n name: t.detect.displayName,\n dir: t.config.dir,\n configured: t.detect.isConfigured(),\n });\n }\n }\n\n if (tools.length === 0) {\n checks.push({\n label: \"Tools\",\n status: \"warn\",\n detail: \"No supported coding tools found\",\n });\n } else {\n const configured = tools.filter((t) => t.configured);\n const unconfigured = tools.filter((t) => !t.configured);\n if (unconfigured.length === 0) {\n checks.push({\n label: \"Tools\",\n status: \"ok\",\n detail: configured.map((t) => t.name).join(\", \"),\n });\n } else {\n const detail = tools\n .map((t) => `${t.name} ${t.configured ? \"✓\" : \"(not configured)\"}`)\n .join(\", \");\n checks.push({\n label: \"Tools\",\n status: \"warn\",\n detail: `${detail} — re-run install`,\n });\n }\n }\n\n // 5. Recent data\n if (fs.existsSync(config.dbPath)) {\n try {\n const db = getDb();\n const latest = db\n .prepare(\"SELECT MAX(timestamp_ms) as ts FROM hook_events\")\n .get() as { ts: number | null } | undefined;\n closeDb();\n\n if (latest?.ts) {\n const ago = Date.now() - latest.ts;\n const minutes = Math.floor(ago / 60000);\n if (minutes < 60) {\n checks.push({\n label: \"Data Flow\",\n status: \"ok\",\n detail: `Last event ${minutes}m ago`,\n });\n } else if (minutes < 1440) {\n checks.push({\n label: \"Data Flow\",\n status: \"ok\",\n detail: `Last event ${Math.floor(minutes / 60)}h ago`,\n });\n } else {\n checks.push({\n label: \"Data Flow\",\n status: \"warn\",\n detail: `Last event ${Math.floor(minutes / 1440)}d ago`,\n });\n }\n } else {\n checks.push({\n label: \"Data Flow\",\n status: \"warn\",\n detail: \"No events recorded yet\",\n });\n }\n } catch {\n // Already reported DB error above\n }\n }\n\n // 6. Sync targets\n try {\n const cfg = loadUnifiedConfig();\n const targets = cfg.sync.targets;\n if (targets.length === 0) {\n checks.push({\n label: \"Sync\",\n status: \"ok\",\n detail: \"No targets configured\",\n });\n } else {\n const tables = [\"hook_events\", \"otel_logs\", \"otel_metrics\"];\n const targetDetails: string[] = [];\n for (const t of targets) {\n const watermarks = tables.map((table) =>\n readWatermark(watermarkKey(table, t.name)),\n );\n const minWm = Math.min(...watermarks);\n const wmLabel = minWm > 0 ? `synced to #${minWm}` : \"not synced yet\";\n targetDetails.push(`${t.name} → ${t.url} (${wmLabel})`);\n }\n checks.push({\n label: \"Sync\",\n status: \"ok\",\n detail: `${targets.length} target${targets.length > 1 ? \"s\" : \"\"}: ${targetDetails.join(\"; \")}`,\n });\n }\n } catch {\n checks.push({\n label: \"Sync\",\n status: \"warn\",\n detail: \"Could not read sync config\",\n });\n }\n\n // 7. Sentry\n try {\n const cfg = loadUnifiedConfig();\n const dsn = process.env.PANOPTICON_SENTRY_DSN ?? cfg.sentryDsn;\n checks.push({\n label: \"Sentry\",\n status: dsn ? \"ok\" : \"ok\",\n detail: dsn ? \"Configured\" : \"Not configured (optional)\",\n });\n } catch {\n // Non-critical\n }\n\n // 8. Recent events and errors (informational, not checks)\n let recentEvents: RecentEvent[] = [];\n let recentErrors: RecentError[] = [];\n\n if (fs.existsSync(config.dbPath)) {\n try {\n const db = getDb();\n\n const events = db\n .prepare(\n \"SELECT event_type, tool_name, timestamp_ms FROM hook_events ORDER BY id DESC LIMIT 3\",\n )\n .all() as {\n event_type: string;\n tool_name: string | null;\n timestamp_ms: number;\n }[];\n\n recentEvents = events.map((e) => ({\n eventType: e.event_type,\n toolName: e.tool_name,\n timestamp: new Date(e.timestamp_ms).toISOString(),\n }));\n\n const errors = db\n .prepare(\n \"SELECT id, body FROM otel_logs WHERE severity_text = 'ERROR' ORDER BY id DESC LIMIT 3\",\n )\n .all() as { id: number; body: string | null }[];\n\n recentErrors = errors.map((e) => ({\n id: e.id,\n body: (e.body ?? \"\").slice(0, 200),\n }));\n\n closeDb();\n } catch {\n // DB errors already reported above\n }\n }\n\n return {\n checks,\n system: {\n os: `${os.platform()} (${os.release()} ${os.arch()})`,\n node: process.version,\n sandbox: isSandbox,\n },\n recentEvents,\n recentErrors,\n };\n}\n\nimport path from \"node:path\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAMA,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,OAAO,QAAQ;AAoUf,OAAO,UAAU;AAhSjB,SAAS,YAAY,MAAc,MAAgC;AACjE,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,UAAM,MAAM,KAAK;AAAA,MACf,EAAE,UAAU,MAAM,MAAM,MAAM,WAAW,QAAQ,OAAO,SAAS,IAAK;AAAA,MACtE,CAAC,QAAQ;AACP,YAAI,OAAO;AACX,gBAAQ,IAAI,eAAe,GAAG;AAAA,MAChC;AAAA,IACF;AACA,QAAI,GAAG,SAAS,MAAM,QAAQ,KAAK,CAAC;AACpC,QAAI,GAAG,WAAW,MAAM;AACtB,UAAI,QAAQ;AACZ,cAAQ,KAAK;AAAA,IACf,CAAC;AACD,QAAI,IAAI;AAAA,EACV,CAAC;AACH;AAOA,eAAsB,SAAgC;AACpD,QAAM,SAAwB,CAAC;AAE/B,QAAM,YACJ,QAAQ,IAAI,YAAY,UACxB,GAAG,WAAW,KAAK,KAAK,GAAG,QAAQ,GAAG,eAAe,CAAC;AAGxD,MAAI,CAAC,GAAG,WAAW,OAAO,MAAM,GAAG;AACjC,WAAO,KAAK;AAAA,MACV,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,QAAQ,gBAAgB,OAAO,MAAM;AAAA,IACvC,CAAC;AAAA,EACH,OAAO;AACL,QAAI;AACF,YAAM,QAAQ,QAAQ;AACtB,YAAM,QAAQ,MAAM,cAAc,MAAM,YAAY,MAAM;AAC1D,aAAO,KAAK;AAAA,QACV,OAAO;AAAA,QACP,QAAQ;AAAA,QACR,QAAQ,GAAG,KAAK,UAAU,MAAM,WAAW,WAAW,MAAM,SAAS,UAAU,MAAM,YAAY;AAAA,MACnG,CAAC;AACD,cAAQ;AAAA,IACV,SAAS,KAAK;AACZ,aAAO,KAAK;AAAA,QACV,OAAO;AAAA,QACP,QAAQ;AAAA,QACR,QAAQ,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,MACpE,CAAC;AAAA,IACH;AAAA,EACF;AAGA,QAAM,WAAW,MAAM,YAAY,OAAO,MAAM,WAAW;AAC3D,MAAI,UAAU;AACZ,WAAO,KAAK;AAAA,MACV,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,QAAQ,0BAA0B,OAAO,IAAI;AAAA,IAC/C,CAAC;AAAA,EACH,OAAO;AACL,WAAO,KAAK;AAAA,MACV,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,QAAQ,0BAA0B,OAAO,IAAI;AAAA,IAC/C,CAAC;AAAA,EACH;AAGA,QAAM,UAAU,KAAK;AAAA,IACnB,GAAG,QAAQ;AAAA,IACX,QAAQ,IAAI,OAAO,SAAS,KAAK,IAAI,WAAW;AAAA,EAClD;AACA,QAAM,YAAY,GAAG,WAAW,OAAO,IACnC,GAAG,aAAa,SAAS,OAAO,IAChC;AAEJ,QAAM,eAAe,UAAU,SAAS,8BAA8B;AACtE,QAAM,cAAc,UAAU,SAAS,6BAA6B;AAEpE,MAAI,gBAAgB,aAAa;AAC/B,WAAO,KAAK;AAAA,MACV,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,QAAQ,2BAA2B,KAAK,SAAS,OAAO,CAAC;AAAA,IAC3D,CAAC;AAAA,EACH,WAAW,gBAAgB,aAAa;AACtC,WAAO,KAAK;AAAA,MACV,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,QAAQ,qBAAqB,KAAK,SAAS,OAAO,CAAC;AAAA,IACrD,CAAC;AAAA,EACH,OAAO;AACL,WAAO,KAAK;AAAA,MACV,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,QAAQ;AAAA,IACV,CAAC;AAAA,EACH;AAGA,QAAM,QAAmE,CAAC;AAE1E,aAAW,KAAK,WAAW,GAAG;AAC5B,QAAI,EAAE,OAAO,YAAY,GAAG;AAC1B,YAAM,KAAK;AAAA,QACT,MAAM,EAAE,OAAO;AAAA,QACf,KAAK,EAAE,OAAO;AAAA,QACd,YAAY,EAAE,OAAO,aAAa;AAAA,MACpC,CAAC;AAAA,IACH;AAAA,EACF;AAEA,MAAI,MAAM,WAAW,GAAG;AACtB,WAAO,KAAK;AAAA,MACV,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,QAAQ;AAAA,IACV,CAAC;AAAA,EACH,OAAO;AACL,UAAM,aAAa,MAAM,OAAO,CAAC,MAAM,EAAE,UAAU;AACnD,UAAM,eAAe,MAAM,OAAO,CAAC,MAAM,CAAC,EAAE,UAAU;AACtD,QAAI,aAAa,WAAW,GAAG;AAC7B,aAAO,KAAK;AAAA,QACV,OAAO;AAAA,QACP,QAAQ;AAAA,QACR,QAAQ,WAAW,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,IAAI;AAAA,MACjD,CAAC;AAAA,IACH,OAAO;AACL,YAAM,SAAS,MACZ,IAAI,CAAC,MAAM,GAAG,EAAE,IAAI,IAAI,EAAE,aAAa,WAAM,kBAAkB,EAAE,EACjE,KAAK,IAAI;AACZ,aAAO,KAAK;AAAA,QACV,OAAO;AAAA,QACP,QAAQ;AAAA,QACR,QAAQ,GAAG,MAAM;AAAA,MACnB,CAAC;AAAA,IACH;AAAA,EACF;AAGA,MAAI,GAAG,WAAW,OAAO,MAAM,GAAG;AAChC,QAAI;AACF,YAAM,KAAK,MAAM;AACjB,YAAM,SAAS,GACZ,QAAQ,iDAAiD,EACzD,IAAI;AACP,cAAQ;AAER,UAAI,QAAQ,IAAI;AACd,cAAM,MAAM,KAAK,IAAI,IAAI,OAAO;AAChC,cAAM,UAAU,KAAK,MAAM,MAAM,GAAK;AACtC,YAAI,UAAU,IAAI;AAChB,iBAAO,KAAK;AAAA,YACV,OAAO;AAAA,YACP,QAAQ;AAAA,YACR,QAAQ,cAAc,OAAO;AAAA,UAC/B,CAAC;AAAA,QACH,WAAW,UAAU,MAAM;AACzB,iBAAO,KAAK;AAAA,YACV,OAAO;AAAA,YACP,QAAQ;AAAA,YACR,QAAQ,cAAc,KAAK,MAAM,UAAU,EAAE,CAAC;AAAA,UAChD,CAAC;AAAA,QACH,OAAO;AACL,iBAAO,KAAK;AAAA,YACV,OAAO;AAAA,YACP,QAAQ;AAAA,YACR,QAAQ,cAAc,KAAK,MAAM,UAAU,IAAI,CAAC;AAAA,UAClD,CAAC;AAAA,QACH;AAAA,MACF,OAAO;AACL,eAAO,KAAK;AAAA,UACV,OAAO;AAAA,UACP,QAAQ;AAAA,UACR,QAAQ;AAAA,QACV,CAAC;AAAA,MACH;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAGA,MAAI;AACF,UAAM,MAAM,kBAAkB;AAC9B,UAAM,UAAU,IAAI,KAAK;AACzB,QAAI,QAAQ,WAAW,GAAG;AACxB,aAAO,KAAK;AAAA,QACV,OAAO;AAAA,QACP,QAAQ;AAAA,QACR,QAAQ;AAAA,MACV,CAAC;AAAA,IACH,OAAO;AACL,YAAM,SAAS,CAAC,eAAe,aAAa,cAAc;AAC1D,YAAM,gBAA0B,CAAC;AACjC,iBAAW,KAAK,SAAS;AACvB,cAAM,aAAa,OAAO;AAAA,UAAI,CAAC,UAC7B,cAAc,aAAa,OAAO,EAAE,IAAI,CAAC;AAAA,QAC3C;AACA,cAAM,QAAQ,KAAK,IAAI,GAAG,UAAU;AACpC,cAAM,UAAU,QAAQ,IAAI,cAAc,KAAK,KAAK;AACpD,sBAAc,KAAK,GAAG,EAAE,IAAI,WAAM,EAAE,GAAG,KAAK,OAAO,GAAG;AAAA,MACxD;AACA,aAAO,KAAK;AAAA,QACV,OAAO;AAAA,QACP,QAAQ;AAAA,QACR,QAAQ,GAAG,QAAQ,MAAM,UAAU,QAAQ,SAAS,IAAI,MAAM,EAAE,KAAK,cAAc,KAAK,IAAI,CAAC;AAAA,MAC/F,CAAC;AAAA,IACH;AAAA,EACF,QAAQ;AACN,WAAO,KAAK;AAAA,MACV,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,QAAQ;AAAA,IACV,CAAC;AAAA,EACH;AAGA,MAAI;AACF,UAAM,MAAM,kBAAkB;AAC9B,UAAM,MAAM,QAAQ,IAAI,yBAAyB,IAAI;AACrD,WAAO,KAAK;AAAA,MACV,OAAO;AAAA,MACP,QAAQ,MAAM,OAAO;AAAA,MACrB,QAAQ,MAAM,eAAe;AAAA,IAC/B,CAAC;AAAA,EACH,QAAQ;AAAA,EAER;AAGA,MAAI,eAA8B,CAAC;AACnC,MAAI,eAA8B,CAAC;AAEnC,MAAI,GAAG,WAAW,OAAO,MAAM,GAAG;AAChC,QAAI;AACF,YAAM,KAAK,MAAM;AAEjB,YAAM,SAAS,GACZ;AAAA,QACC;AAAA,MACF,EACC,IAAI;AAMP,qBAAe,OAAO,IAAI,CAAC,OAAO;AAAA,QAChC,WAAW,EAAE;AAAA,QACb,UAAU,EAAE;AAAA,QACZ,WAAW,IAAI,KAAK,EAAE,YAAY,EAAE,YAAY;AAAA,MAClD,EAAE;AAEF,YAAM,SAAS,GACZ;AAAA,QACC;AAAA,MACF,EACC,IAAI;AAEP,qBAAe,OAAO,IAAI,CAAC,OAAO;AAAA,QAChC,IAAI,EAAE;AAAA,QACN,OAAO,EAAE,QAAQ,IAAI,MAAM,GAAG,GAAG;AAAA,MACnC,EAAE;AAEF,cAAQ;AAAA,IACV,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA,QAAQ;AAAA,MACN,IAAI,GAAG,GAAG,SAAS,CAAC,KAAK,GAAG,QAAQ,CAAC,IAAI,GAAG,KAAK,CAAC;AAAA,MAClD,MAAM,QAAQ;AAAA,MACd,SAAS;AAAA,IACX;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,138 @@
1
+ import {
2
+ getTarget
3
+ } from "./chunk-QVK6VGCV.js";
4
+ import {
5
+ config
6
+ } from "./chunk-K7YUPLES.js";
7
+
8
+ // src/mcp/permissions.ts
9
+ import fs from "fs";
10
+ import path from "path";
11
+ import { z } from "zod";
12
+ var PERMISSIONS_DIR = path.join(config.dataDir, "permissions");
13
+ var APPROVALS_PATH = path.join(PERMISSIONS_DIR, "approvals.json");
14
+ var ALLOWED_PATH = path.join(PERMISSIONS_DIR, "allowed.json");
15
+ var BACKUPS_DIR = path.join(PERMISSIONS_DIR, "backups");
16
+ var DEFAULT_APPROVALS = {
17
+ approved_categories: ["safe"],
18
+ denied_categories: [],
19
+ custom_overrides: {},
20
+ last_run: null
21
+ };
22
+ function readJson(filePath) {
23
+ try {
24
+ return JSON.parse(fs.readFileSync(filePath, "utf-8"));
25
+ } catch {
26
+ return null;
27
+ }
28
+ }
29
+ function permissionsShow() {
30
+ const approvals = readJson(APPROVALS_PATH) ?? DEFAULT_APPROVALS;
31
+ const allowed = readJson(ALLOWED_PATH) ?? {
32
+ bash_commands: [],
33
+ tools: []
34
+ };
35
+ return {
36
+ approvals,
37
+ approvals_path: APPROVALS_PATH,
38
+ allowed,
39
+ allowed_path: ALLOWED_PATH
40
+ };
41
+ }
42
+ var categorySchema = z.object({
43
+ status: z.enum(["approved", "denied", "skipped"]),
44
+ patterns: z.array(z.string()),
45
+ observed_commands: z.array(z.string()),
46
+ call_count: z.number()
47
+ });
48
+ function permissionsApply({
49
+ repository,
50
+ approved_categories,
51
+ denied_categories,
52
+ custom_overrides,
53
+ permissions,
54
+ categories
55
+ }) {
56
+ const now = (/* @__PURE__ */ new Date()).toISOString();
57
+ const results = [];
58
+ const bashPattern = /^Bash\((.+?)[\s:]\*\)$/;
59
+ const bashCommands = [];
60
+ const tools = [];
61
+ for (const p of permissions) {
62
+ const match = p.match(bashPattern);
63
+ if (match) {
64
+ bashCommands.push(match[1]);
65
+ } else {
66
+ tools.push(p);
67
+ }
68
+ }
69
+ fs.mkdirSync(PERMISSIONS_DIR, { recursive: true });
70
+ fs.writeFileSync(
71
+ ALLOWED_PATH,
72
+ `${JSON.stringify({ bash_commands: bashCommands, tools, updated: now }, null, 2)}
73
+ `
74
+ );
75
+ results.push(
76
+ `Allowed list written to ${ALLOWED_PATH} (${bashCommands.length} Bash commands, ${tools.length} tools)`
77
+ );
78
+ const approvals = {
79
+ approved_categories: [.../* @__PURE__ */ new Set(["safe", ...approved_categories])],
80
+ denied_categories: [...new Set(denied_categories)],
81
+ custom_overrides: custom_overrides ?? {},
82
+ last_run: now
83
+ };
84
+ fs.writeFileSync(APPROVALS_PATH, `${JSON.stringify(approvals, null, 2)}
85
+ `);
86
+ results.push(`Approvals saved to ${APPROVALS_PATH}`);
87
+ try {
88
+ const codexTarget = getTarget("codex");
89
+ if (!codexTarget || !fs.existsSync(codexTarget.config.dir))
90
+ throw new Error("Codex not installed");
91
+ const codexRulesDir = path.join(codexTarget.config.dir, "rules");
92
+ const codexRulesPath = path.join(codexRulesDir, "panopticon.rules");
93
+ const lines = [
94
+ "# Auto-generated by panopticon \u2014 do not edit manually.",
95
+ `# Updated: ${now}`,
96
+ "#",
97
+ "# Run /panopticon-optimize to regenerate from observed tool usage.",
98
+ ""
99
+ ];
100
+ for (const cmd of bashCommands) {
101
+ const tokens = cmd.split(/\s+/);
102
+ const pattern = tokens.map((t) => JSON.stringify(t)).join(", ");
103
+ lines.push(
104
+ `prefix_rule(pattern = [${pattern}], decision = "allow", justification = "Approved by panopticon")`
105
+ );
106
+ }
107
+ fs.mkdirSync(codexRulesDir, { recursive: true });
108
+ fs.writeFileSync(codexRulesPath, `${lines.join("\n")}
109
+ `);
110
+ results.push(
111
+ `Codex rules written to ${codexRulesPath} (${bashCommands.length} rules)`
112
+ );
113
+ } catch {
114
+ }
115
+ fs.mkdirSync(BACKUPS_DIR, { recursive: true });
116
+ const dateStr = now.slice(0, 10);
117
+ const timeStr = now.slice(11, 19).replace(/:/g, "");
118
+ const backupPath = path.join(BACKUPS_DIR, `${dateStr}_${timeStr}.json`);
119
+ const backup = {
120
+ timestamp: now,
121
+ repository,
122
+ skill_version: "4",
123
+ categories,
124
+ generated_permissions: permissions,
125
+ approvals_state: approvals
126
+ };
127
+ fs.writeFileSync(backupPath, `${JSON.stringify(backup, null, 2)}
128
+ `);
129
+ results.push(`Backup saved to ${backupPath}`);
130
+ return { success: true, details: results };
131
+ }
132
+
133
+ export {
134
+ permissionsShow,
135
+ categorySchema,
136
+ permissionsApply
137
+ };
138
+ //# sourceMappingURL=chunk-TGXFVAID.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/mcp/permissions.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { z } from \"zod\";\nimport { config } from \"../config.js\";\nimport { getTarget } from \"../targets/index.js\";\n\nconst PERMISSIONS_DIR = path.join(config.dataDir, \"permissions\");\nconst APPROVALS_PATH = path.join(PERMISSIONS_DIR, \"approvals.json\");\nconst ALLOWED_PATH = path.join(PERMISSIONS_DIR, \"allowed.json\");\nconst BACKUPS_DIR = path.join(PERMISSIONS_DIR, \"backups\");\n\nconst DEFAULT_APPROVALS = {\n approved_categories: [\"safe\"],\n denied_categories: [] as string[],\n custom_overrides: {} as Record<string, string>,\n last_run: null as string | null,\n};\n\nfunction readJson(filePath: string): any {\n try {\n return JSON.parse(fs.readFileSync(filePath, \"utf-8\"));\n } catch {\n return null;\n }\n}\n\nexport function permissionsShow() {\n const approvals = readJson(APPROVALS_PATH) ?? DEFAULT_APPROVALS;\n const allowed = readJson(ALLOWED_PATH) ?? {\n bash_commands: [],\n tools: [],\n };\n\n return {\n approvals,\n approvals_path: APPROVALS_PATH,\n allowed,\n allowed_path: ALLOWED_PATH,\n };\n}\n\nexport const categorySchema = z.object({\n status: z.enum([\"approved\", \"denied\", \"skipped\"]),\n patterns: z.array(z.string()),\n observed_commands: z.array(z.string()),\n call_count: z.number(),\n});\n\nexport type CategoryEntry = z.infer<typeof categorySchema>;\n\nexport interface PermissionsApplyParams {\n repository?: string;\n approved_categories: string[];\n denied_categories: string[];\n custom_overrides?: Record<string, string>;\n permissions: string[];\n categories: Record<string, CategoryEntry>;\n}\n\nexport function permissionsApply({\n repository,\n approved_categories,\n denied_categories,\n custom_overrides,\n permissions,\n categories,\n}: PermissionsApplyParams) {\n const now = new Date().toISOString();\n const results: string[] = [];\n\n // Split permissions into Bash commands and non-Bash tool names\n const bashPattern = /^Bash\\((.+?)[\\s:]\\*\\)$/;\n const bashCommands: string[] = [];\n const tools: string[] = [];\n\n for (const p of permissions) {\n const match = p.match(bashPattern);\n if (match) {\n bashCommands.push(match[1]);\n } else {\n tools.push(p);\n }\n }\n\n // 1. Write allowed.json — single file for all hook-based enforcement\n fs.mkdirSync(PERMISSIONS_DIR, { recursive: true });\n fs.writeFileSync(\n ALLOWED_PATH,\n `${JSON.stringify({ bash_commands: bashCommands, tools, updated: now }, null, 2)}\\n`,\n );\n results.push(\n `Allowed list written to ${ALLOWED_PATH} (${bashCommands.length} Bash commands, ${tools.length} tools)`,\n );\n\n // 2. Save approvals state\n const approvals = {\n approved_categories: [...new Set([\"safe\", ...approved_categories])],\n denied_categories: [...new Set(denied_categories)],\n custom_overrides: custom_overrides ?? {},\n last_run: now,\n };\n fs.writeFileSync(APPROVALS_PATH, `${JSON.stringify(approvals, null, 2)}\\n`);\n results.push(`Approvals saved to ${APPROVALS_PATH}`);\n\n // 3. Write Codex .rules file (Starlark prefix_rule format) — only if Codex is installed\n try {\n const codexTarget = getTarget(\"codex\");\n if (!codexTarget || !fs.existsSync(codexTarget.config.dir))\n throw new Error(\"Codex not installed\");\n const codexRulesDir = path.join(codexTarget.config.dir, \"rules\");\n const codexRulesPath = path.join(codexRulesDir, \"panopticon.rules\");\n const lines = [\n \"# Auto-generated by panopticon — do not edit manually.\",\n `# Updated: ${now}`,\n \"#\",\n \"# Run /panopticon-optimize to regenerate from observed tool usage.\",\n \"\",\n ];\n for (const cmd of bashCommands) {\n const tokens = cmd.split(/\\s+/);\n const pattern = tokens.map((t) => JSON.stringify(t)).join(\", \");\n lines.push(\n `prefix_rule(pattern = [${pattern}], decision = \"allow\", justification = \"Approved by panopticon\")`,\n );\n }\n fs.mkdirSync(codexRulesDir, { recursive: true });\n fs.writeFileSync(codexRulesPath, `${lines.join(\"\\n\")}\\n`);\n results.push(\n `Codex rules written to ${codexRulesPath} (${bashCommands.length} rules)`,\n );\n } catch {\n // Codex may not be installed — non-fatal\n }\n\n // 4. Create backup\n fs.mkdirSync(BACKUPS_DIR, { recursive: true });\n const dateStr = now.slice(0, 10);\n const timeStr = now.slice(11, 19).replace(/:/g, \"\");\n const backupPath = path.join(BACKUPS_DIR, `${dateStr}_${timeStr}.json`);\n const backup = {\n timestamp: now,\n repository,\n skill_version: \"4\",\n categories,\n generated_permissions: permissions,\n approvals_state: approvals,\n };\n fs.writeFileSync(backupPath, `${JSON.stringify(backup, null, 2)}\\n`);\n results.push(`Backup saved to ${backupPath}`);\n\n return { success: true as const, details: results };\n}\n"],"mappings":"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","names":[]}