@fml-inc/panopticon 0.1.1 → 0.1.2

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 (63) hide show
  1. package/dist/api/client.d.ts +2 -2
  2. package/dist/api/client.js +1 -1
  3. package/dist/{chunk-3BUJ7URA.js → chunk-3ILOOWUF.js} +66 -2
  4. package/dist/chunk-3ILOOWUF.js.map +1 -0
  5. package/dist/{chunk-YZBYEULL.js → chunk-3ZT3V7FP.js} +5 -5
  6. package/dist/{chunk-VXQ33OYT.js → chunk-BKGQJ76N.js} +47 -19
  7. package/dist/chunk-BKGQJ76N.js.map +1 -0
  8. package/dist/{chunk-3TZAKV3M.js → chunk-FMAHQRIU.js} +2 -2
  9. package/dist/{chunk-LWXF7YRG.js → chunk-GPTBERQD.js} +2 -2
  10. package/dist/{chunk-4SM2H22C.js → chunk-HO443ZQM.js} +1 -1
  11. package/dist/{chunk-4SM2H22C.js.map → chunk-HO443ZQM.js.map} +1 -1
  12. package/dist/{chunk-L7G27XWF.js → chunk-HRNZUHTA.js} +3 -3
  13. package/dist/{chunk-XO5NQRTD.js → chunk-J3HVD4VI.js} +2 -2
  14. package/dist/{chunk-SEXU2WYG.js → chunk-MEVW27U4.js} +5 -4
  15. package/dist/chunk-MEVW27U4.js.map +1 -0
  16. package/dist/{chunk-SUGSQ4YI.js → chunk-N7NCNJZU.js} +4 -4
  17. package/dist/{chunk-KLXRBD4N.js → chunk-NE7VBLQD.js} +6 -5
  18. package/dist/{chunk-KLXRBD4N.js.map → chunk-NE7VBLQD.js.map} +1 -1
  19. package/dist/{chunk-NXH7AONS.js → chunk-OROLSIWZ.js} +8 -6
  20. package/dist/chunk-OROLSIWZ.js.map +1 -0
  21. package/dist/{chunk-TCKL7E4K.js → chunk-OW52TNVA.js} +4 -4
  22. package/dist/{chunk-DZ5HJFB4.js → chunk-SKZHAYNF.js} +53 -2
  23. package/dist/chunk-SKZHAYNF.js.map +1 -0
  24. package/dist/{chunk-BVOE7A2Z.js → chunk-V3XR2TAN.js} +8 -6
  25. package/dist/chunk-V3XR2TAN.js.map +1 -0
  26. package/dist/{chunk-HRCEIYKU.js → chunk-WXPT6KG7.js} +2 -2
  27. package/dist/cli.js +6 -6
  28. package/dist/cli.js.map +1 -1
  29. package/dist/db.js +1 -1
  30. package/dist/doctor.js +4 -4
  31. package/dist/hooks/handler.js +4 -4
  32. package/dist/index.d.ts +2 -2
  33. package/dist/index.js +15 -15
  34. package/dist/mcp/server.js +1 -1
  35. package/dist/otlp/server.js +5 -5
  36. package/dist/pricing.js +2 -2
  37. package/dist/proxy/server.js +5 -5
  38. package/dist/prune.js +2 -2
  39. package/dist/query.js +2 -2
  40. package/dist/{reparse-636YZCE3.js → reparse-VHUSGCPN.js} +5 -5
  41. package/dist/scanner.d.ts +7 -1
  42. package/dist/scanner.js +1 -1
  43. package/dist/server.js +13 -13
  44. package/dist/setup.js +3 -3
  45. package/dist/sync/index.d.ts +2 -2
  46. package/dist/sync/index.js +4 -4
  47. package/dist/{types-D-MYCBol.d.ts → types-DrhrWbWe.d.ts} +1 -0
  48. package/package.json +1 -1
  49. package/dist/chunk-3BUJ7URA.js.map +0 -1
  50. package/dist/chunk-BVOE7A2Z.js.map +0 -1
  51. package/dist/chunk-DZ5HJFB4.js.map +0 -1
  52. package/dist/chunk-NXH7AONS.js.map +0 -1
  53. package/dist/chunk-SEXU2WYG.js.map +0 -1
  54. package/dist/chunk-VXQ33OYT.js.map +0 -1
  55. /package/dist/{chunk-YZBYEULL.js.map → chunk-3ZT3V7FP.js.map} +0 -0
  56. /package/dist/{chunk-3TZAKV3M.js.map → chunk-FMAHQRIU.js.map} +0 -0
  57. /package/dist/{chunk-LWXF7YRG.js.map → chunk-GPTBERQD.js.map} +0 -0
  58. /package/dist/{chunk-L7G27XWF.js.map → chunk-HRNZUHTA.js.map} +0 -0
  59. /package/dist/{chunk-XO5NQRTD.js.map → chunk-J3HVD4VI.js.map} +0 -0
  60. /package/dist/{chunk-SUGSQ4YI.js.map → chunk-N7NCNJZU.js.map} +0 -0
  61. /package/dist/{chunk-TCKL7E4K.js.map → chunk-OW52TNVA.js.map} +0 -0
  62. /package/dist/{chunk-HRCEIYKU.js.map → chunk-WXPT6KG7.js.map} +0 -0
  63. /package/dist/{reparse-636YZCE3.js.map → reparse-VHUSGCPN.js.map} +0 -0
@@ -50,8 +50,8 @@ declare function syncPending(target: string): Promise<{
50
50
  target: string;
51
51
  totalPending: number;
52
52
  tables: Record<string, {
53
- maxId: number;
54
- watermark: number;
53
+ total: number;
54
+ synced: number;
55
55
  pending: number;
56
56
  }>;
57
57
  }>;
@@ -20,7 +20,7 @@ import {
20
20
  syncTargetRemove,
21
21
  syncWatermarkGet,
22
22
  syncWatermarkSet
23
- } from "../chunk-4SM2H22C.js";
23
+ } from "../chunk-HO443ZQM.js";
24
24
  import "../chunk-K7YUPLES.js";
25
25
  export {
26
26
  activitySummary,
@@ -139,6 +139,61 @@ function parseEnabledPlugins(settings) {
139
139
  }
140
140
  return result;
141
141
  }
142
+ function resolvePluginVersionDir(pluginCacheDir) {
143
+ let entries;
144
+ try {
145
+ entries = fs.readdirSync(pluginCacheDir, { withFileTypes: true });
146
+ } catch {
147
+ return null;
148
+ }
149
+ const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name).filter((n) => n !== "unknown").sort((a, b) => {
150
+ const pa = a.split(".").map(Number);
151
+ const pb = b.split(".").map(Number);
152
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
153
+ const na = pa[i] ?? 0;
154
+ const nb = pb[i] ?? 0;
155
+ if (Number.isNaN(na) || Number.isNaN(nb)) return a.localeCompare(b);
156
+ if (na !== nb) return na - nb;
157
+ }
158
+ return 0;
159
+ });
160
+ return dirs.length > 0 ? path.join(pluginCacheDir, dirs[dirs.length - 1]) : null;
161
+ }
162
+ function readPluginHooks(enabledPlugins) {
163
+ const claudeHome = path.join(os.homedir(), ".claude");
164
+ const results = [];
165
+ for (const plugin of enabledPlugins) {
166
+ if (!plugin.marketplace) continue;
167
+ const pluginCacheDir = path.join(
168
+ claudeHome,
169
+ "plugins",
170
+ "cache",
171
+ plugin.marketplace,
172
+ plugin.pluginName
173
+ );
174
+ const versionDir = resolvePluginVersionDir(pluginCacheDir);
175
+ if (!versionDir) continue;
176
+ const hooksJsonPath = path.join(versionDir, "hooks", "hooks.json");
177
+ const raw = readFileOrNull(hooksJsonPath);
178
+ if (!raw) continue;
179
+ try {
180
+ const parsed = JSON.parse(raw);
181
+ const hooksObj = parsed.hooks;
182
+ if (!hooksObj || typeof hooksObj !== "object" || Array.isArray(hooksObj))
183
+ continue;
184
+ const hooks = parseHooks({ hooks: hooksObj });
185
+ if (hooks.length > 0) {
186
+ results.push({
187
+ pluginName: plugin.pluginName,
188
+ marketplace: plugin.marketplace,
189
+ hooks
190
+ });
191
+ }
192
+ } catch {
193
+ }
194
+ }
195
+ return results;
196
+ }
142
197
  function buildLayer(settingsPath, dirs, mcpJsonPath) {
143
198
  const settings = readJsonOrNull(settingsPath);
144
199
  const mcpFromSettings = settings ? parseMcpServers(settings) : [];
@@ -339,7 +394,16 @@ function readConfig(cwd) {
339
394
  }
340
395
  }
341
396
  }
342
- return { managed, user, project, projectLocal, instructions, enabledPlugins };
397
+ const pluginHooks = readPluginHooks(enabledPlugins);
398
+ return {
399
+ managed,
400
+ user,
401
+ project,
402
+ projectLocal,
403
+ instructions,
404
+ enabledPlugins,
405
+ pluginHooks
406
+ };
343
407
  }
344
408
  function writeSettings(level, patch, cwd) {
345
409
  const root = cwd ? path.resolve(cwd) : process.cwd();
@@ -384,4 +448,4 @@ export {
384
448
  writeSettings,
385
449
  writeFile
386
450
  };
387
- //# sourceMappingURL=chunk-3BUJ7URA.js.map
451
+ //# sourceMappingURL=chunk-3ILOOWUF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/scanner.ts"],"sourcesContent":["import { execFileSync } from \"node:child_process\";\nimport fs from \"node:fs\";\nimport os from \"node:os\";\nimport path from \"node:path\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface ConfigLayer {\n settings: Record<string, unknown> | null;\n hooks: Array<{ event: string; matcher: string | null; type: string }>;\n mcpServers: Array<{ name: string; command: string }>;\n commands: Array<{ name: string; content: string }>;\n agents: Array<{ name: string; content: string }>;\n rules: Array<{ name: string; content: string }>;\n skills: Array<{ name: string; content: string }>;\n permissions: { allow: string[]; ask: string[]; deny: string[] };\n}\n\nexport interface PluginHooksSummary {\n pluginName: string;\n marketplace: string;\n hooks: ConfigLayer[\"hooks\"];\n}\n\nexport interface ClaudeCodeConfig {\n managed: ConfigLayer | null;\n user: ConfigLayer;\n project: ConfigLayer | null;\n projectLocal: ConfigLayer | null;\n instructions: Array<{ path: string; content: string; lineCount: number }>;\n enabledPlugins: Array<{ pluginName: string; marketplace: string }>;\n pluginHooks: PluginHooksSummary[];\n}\n\n// ---------------------------------------------------------------------------\n// File-system helpers\n// ---------------------------------------------------------------------------\n\nfunction readFileOrNull(filePath: string): string | null {\n try {\n return fs.readFileSync(filePath, \"utf-8\");\n } catch {\n return null;\n }\n}\n\nfunction readJsonOrNull(filePath: string): Record<string, unknown> | null {\n const raw = readFileOrNull(filePath);\n if (raw === null) return null;\n try {\n return JSON.parse(raw) as Record<string, unknown>;\n } catch {\n return null;\n }\n}\n\nfunction readMdFiles(dir: string): Array<{ name: string; content: string }> {\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(dir, { withFileTypes: true });\n } catch {\n return [];\n }\n const results: Array<{ name: string; content: string }> = [];\n for (const entry of entries) {\n if (!entry.isFile() || !entry.name.endsWith(\".md\")) continue;\n const content = readFileOrNull(path.join(dir, entry.name));\n if (content === null) continue;\n results.push({ name: entry.name.replace(/\\.md$/, \"\"), content });\n }\n return results;\n}\n\nfunction readSkills(dir: string): Array<{ name: string; content: string }> {\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(dir, { withFileTypes: true });\n } catch {\n return [];\n }\n const results: Array<{ name: string; content: string }> = [];\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n const content = readFileOrNull(path.join(dir, entry.name, \"SKILL.md\"));\n if (content !== null) {\n results.push({ name: entry.name, content });\n }\n }\n return results;\n}\n\n// ---------------------------------------------------------------------------\n// Settings parsers\n// ---------------------------------------------------------------------------\n\nfunction parseHooks(json: Record<string, unknown>): ConfigLayer[\"hooks\"] {\n const hooks: ConfigLayer[\"hooks\"] = [];\n const raw = json.hooks;\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) return hooks;\n for (const [event, entries] of Object.entries(\n raw as Record<string, unknown>,\n )) {\n if (!Array.isArray(entries)) continue;\n for (const entry of entries) {\n if (typeof entry !== \"object\" || entry === null) continue;\n const e = entry as Record<string, unknown>;\n const matcher = typeof e.matcher === \"string\" ? e.matcher : null;\n\n // Current format: entry has a nested `hooks` array\n if (Array.isArray(e.hooks)) {\n for (const hook of e.hooks) {\n if (typeof hook !== \"object\" || hook === null) continue;\n const h = hook as Record<string, unknown>;\n hooks.push({\n event,\n matcher,\n type: h.type === \"command\" ? \"command\" : \"script\",\n });\n }\n } else {\n // Legacy format: entry IS a hook directly\n hooks.push({\n event,\n matcher,\n type: e.command ? \"command\" : \"script\",\n });\n }\n }\n }\n return hooks;\n}\n\nfunction parseMcpServers(\n json: Record<string, unknown>,\n): ConfigLayer[\"mcpServers\"] {\n const servers: ConfigLayer[\"mcpServers\"] = [];\n const raw = json.mcpServers;\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) return servers;\n for (const [name, def] of Object.entries(raw as Record<string, unknown>)) {\n if (typeof def !== \"object\" || def === null) continue;\n const s = def as Record<string, unknown>;\n servers.push({\n name,\n command: typeof s.command === \"string\" ? s.command : \"\",\n });\n }\n return servers;\n}\n\nfunction parsePermissions(\n json: Record<string, unknown>,\n): ConfigLayer[\"permissions\"] {\n const allow: string[] = [];\n const ask: string[] = [];\n const deny: string[] = [];\n\n function collectStrings(arr: unknown): string[] {\n if (!Array.isArray(arr)) return [];\n return arr.filter((v): v is string => typeof v === \"string\");\n }\n\n // Current format: permissions.allow / permissions.ask / permissions.deny\n const perms = json.permissions;\n if (perms && typeof perms === \"object\" && !Array.isArray(perms)) {\n const p = perms as Record<string, unknown>;\n allow.push(...collectStrings(p.allow));\n ask.push(...collectStrings(p.ask));\n deny.push(...collectStrings(p.deny));\n }\n\n // Legacy format: allowedTools / deniedTools (fall back if permissions empty)\n if (allow.length === 0 && deny.length === 0) {\n allow.push(...collectStrings(json.allowedTools));\n deny.push(...collectStrings(json.deniedTools));\n }\n\n return { allow, ask, deny };\n}\n\nfunction parseEnabledPlugins(\n settings: Record<string, unknown> | null,\n): Array<{ pluginName: string; marketplace: string }> {\n if (!settings) return [];\n const raw = settings.enabledPlugins;\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) return [];\n const result: Array<{ pluginName: string; marketplace: string }> = [];\n for (const [key, enabled] of Object.entries(raw as Record<string, unknown>)) {\n if (!enabled) continue;\n const atIndex = key.indexOf(\"@\");\n if (atIndex > 0) {\n result.push({\n pluginName: key.slice(0, atIndex),\n marketplace: key.slice(atIndex + 1),\n });\n } else {\n result.push({ pluginName: key, marketplace: \"\" });\n }\n }\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Plugin hook discovery\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve the active version directory for a plugin in the cache.\n * Picks the highest semver directory, falling back to lexicographic sort.\n */\nfunction resolvePluginVersionDir(pluginCacheDir: string): string | null {\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(pluginCacheDir, { withFileTypes: true });\n } catch {\n return null;\n }\n const dirs = entries\n .filter((e) => e.isDirectory())\n .map((e) => e.name)\n .filter((n) => n !== \"unknown\")\n .sort((a, b) => {\n // Simple semver comparison: split on dots, compare numerically\n const pa = a.split(\".\").map(Number);\n const pb = b.split(\".\").map(Number);\n for (let i = 0; i < Math.max(pa.length, pb.length); i++) {\n const na = pa[i] ?? 0;\n const nb = pb[i] ?? 0;\n if (Number.isNaN(na) || Number.isNaN(nb)) return a.localeCompare(b);\n if (na !== nb) return na - nb;\n }\n return 0;\n });\n return dirs.length > 0\n ? path.join(pluginCacheDir, dirs[dirs.length - 1])\n : null;\n}\n\n/**\n * Read hooks from all enabled plugins' cache directories.\n */\nfunction readPluginHooks(\n enabledPlugins: Array<{ pluginName: string; marketplace: string }>,\n): PluginHooksSummary[] {\n const claudeHome = path.join(os.homedir(), \".claude\");\n const results: PluginHooksSummary[] = [];\n\n for (const plugin of enabledPlugins) {\n if (!plugin.marketplace) continue;\n const pluginCacheDir = path.join(\n claudeHome,\n \"plugins\",\n \"cache\",\n plugin.marketplace,\n plugin.pluginName,\n );\n const versionDir = resolvePluginVersionDir(pluginCacheDir);\n if (!versionDir) continue;\n\n const hooksJsonPath = path.join(versionDir, \"hooks\", \"hooks.json\");\n const raw = readFileOrNull(hooksJsonPath);\n if (!raw) continue;\n\n try {\n const parsed = JSON.parse(raw) as Record<string, unknown>;\n const hooksObj = parsed.hooks;\n if (!hooksObj || typeof hooksObj !== \"object\" || Array.isArray(hooksObj))\n continue;\n // Reuse parseHooks by wrapping in the shape it expects\n const hooks = parseHooks({ hooks: hooksObj });\n if (hooks.length > 0) {\n results.push({\n pluginName: plugin.pluginName,\n marketplace: plugin.marketplace,\n hooks,\n });\n }\n } catch {\n // Malformed hooks.json — skip\n }\n }\n\n return results;\n}\n\n// ---------------------------------------------------------------------------\n// Layer builder\n// ---------------------------------------------------------------------------\n\nfunction buildLayer(\n settingsPath: string,\n dirs: {\n commands?: string;\n agents?: string;\n rules?: string;\n skills?: string;\n },\n mcpJsonPath?: string,\n): ConfigLayer {\n const settings = readJsonOrNull(settingsPath);\n\n // MCP servers: merge settings.json and .mcp.json (settings wins on dupes)\n const mcpFromSettings = settings ? parseMcpServers(settings) : [];\n const mcpFromFile = mcpJsonPath\n ? parseMcpServers(readJsonOrNull(mcpJsonPath) ?? {})\n : [];\n const mcpNames = new Set(mcpFromSettings.map((s) => s.name));\n const mcpServers = [\n ...mcpFromSettings,\n ...mcpFromFile.filter((s) => !mcpNames.has(s.name)),\n ];\n\n return {\n settings,\n hooks: settings ? parseHooks(settings) : [],\n mcpServers,\n permissions: settings\n ? parsePermissions(settings)\n : { allow: [], ask: [], deny: [] },\n commands: dirs.commands ? readMdFiles(dirs.commands) : [],\n agents: dirs.agents ? readMdFiles(dirs.agents) : [],\n rules: dirs.rules ? readMdFiles(dirs.rules) : [],\n skills: dirs.skills ? readSkills(dirs.skills) : [],\n };\n}\n\n// ---------------------------------------------------------------------------\n// Git helpers\n// ---------------------------------------------------------------------------\n\nconst gitRootCache = new Map<string, string | null>();\n\n/**\n * Resolve the git repository root for a directory.\n * Cached per cwd for the lifetime of the process.\n */\nexport function resolveGitRoot(cwd: string): string | null {\n if (gitRootCache.has(cwd)) return gitRootCache.get(cwd)!;\n let root: string | null = null;\n try {\n root = execFileSync(\"git\", [\"-C\", cwd, \"rev-parse\", \"--show-toplevel\"], {\n encoding: \"utf-8\",\n timeout: 5000,\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n }).trim();\n } catch {\n // Not a git repo\n }\n gitRootCache.set(cwd, root);\n return root;\n}\n\n/**\n * Check if a file path is gitignored.\n */\nexport function isGitignored(filePath: string, cwd: string): boolean {\n try {\n execFileSync(\"git\", [\"-C\", cwd, \"check-ignore\", \"-q\", filePath], {\n timeout: 3000,\n stdio: [\"ignore\", \"ignore\", \"ignore\"],\n });\n return true; // exit code 0 = ignored\n } catch {\n return false; // exit code 1 = not ignored, or not a git repo\n }\n}\n\n// ---------------------------------------------------------------------------\n// Instruction discovery\n// ---------------------------------------------------------------------------\n\nfunction findPerDirectoryClaudeMd(\n root: string,\n excludePaths: Set<string>,\n): string[] {\n // Try git ls-files first (fast, respects .gitignore)\n const gitRoot = resolveGitRoot(root);\n if (gitRoot) {\n try {\n const output = execFileSync(\n \"git\",\n [\"-C\", gitRoot, \"ls-files\", \"--full-name\", \"*/CLAUDE.md\", \"CLAUDE.md\"],\n {\n encoding: \"utf-8\",\n timeout: 5000,\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n },\n ).trim();\n if (output) {\n return output\n .split(\"\\n\")\n .map((rel) => path.resolve(gitRoot, rel))\n .filter((p) => !excludePaths.has(p));\n }\n return [];\n } catch {\n // Fall through to find\n }\n }\n\n // Fallback: use find command\n try {\n const output = execFileSync(\n \"find\",\n [\n root,\n \"-name\",\n \"CLAUDE.md\",\n \"-not\",\n \"-path\",\n \"*/node_modules/*\",\n \"-not\",\n \"-path\",\n \"*/.git/*\",\n ],\n {\n encoding: \"utf-8\",\n timeout: 10000,\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n },\n ).trim();\n if (output) {\n return output\n .split(\"\\n\")\n .map((p) => path.resolve(p))\n .filter((p) => !excludePaths.has(p));\n }\n } catch {\n // Fall through\n }\n\n return [];\n}\n\nfunction buildInstruction(\n filePath: string,\n): { path: string; content: string; lineCount: number } | null {\n const content = readFileOrNull(filePath);\n if (content === null) return null;\n return { path: filePath, content, lineCount: content.split(\"\\n\").length };\n}\n\nfunction getManagedDir(): string {\n if (process.platform === \"darwin\") {\n return \"/Library/Application Support/ClaudeCode\";\n }\n // Linux, WSL\n return \"/etc/claude-code\";\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Read all Claude Code config from the filesystem.\n * Returns structured layers (managed, user, project, projectLocal) plus\n * instruction files.\n */\nexport function readConfig(cwd?: string): ClaudeCodeConfig {\n const rawCwd = cwd ? path.resolve(cwd) : process.cwd();\n // Use git root if available so project config is found even from subdirs\n const root = resolveGitRoot(rawCwd) ?? rawCwd;\n const home = os.homedir();\n const claudeHome = path.join(home, \".claude\");\n const dotClaude = path.join(root, \".claude\");\n const managedDir = getManagedDir();\n\n // Managed (enterprise) layer\n let managed: ConfigLayer | null = null;\n const managedSettings = readJsonOrNull(\n path.join(managedDir, \"managed-settings.json\"),\n );\n if (managedSettings) {\n managed = {\n settings: managedSettings,\n hooks: parseHooks(managedSettings),\n mcpServers: parseMcpServers(managedSettings),\n permissions: parsePermissions(managedSettings),\n commands: [],\n agents: [],\n rules: [],\n skills: [],\n };\n }\n\n // User layer\n const user = buildLayer(\n path.join(claudeHome, \"settings.json\"),\n {\n commands: path.join(claudeHome, \"commands\"),\n rules: path.join(claudeHome, \"rules\"),\n skills: path.join(claudeHome, \"skills\"),\n },\n path.join(claudeHome, \".mcp.json\"),\n );\n\n // Project layer — null if no .claude directory\n let project: ConfigLayer | null = null;\n try {\n if (fs.statSync(dotClaude).isDirectory()) {\n project = buildLayer(\n path.join(dotClaude, \"settings.json\"),\n {\n commands: path.join(dotClaude, \"commands\"),\n agents: path.join(dotClaude, \"agents\"),\n rules: path.join(dotClaude, \"rules\"),\n skills: path.join(dotClaude, \"skills\"),\n },\n path.join(dotClaude, \".mcp.json\"),\n );\n }\n } catch {\n // no .claude directory\n }\n\n // Project local layer — null if no settings.local.json\n let projectLocal: ConfigLayer | null = null;\n const localSettings = readJsonOrNull(\n path.join(dotClaude, \"settings.local.json\"),\n );\n if (localSettings) {\n projectLocal = {\n settings: localSettings,\n hooks: parseHooks(localSettings),\n mcpServers: parseMcpServers(localSettings),\n permissions: parsePermissions(localSettings),\n commands: [],\n agents: [],\n rules: [],\n skills: [],\n };\n }\n\n // Instructions — fixed candidates + per-directory discovery\n const instructions: ClaudeCodeConfig[\"instructions\"] = [];\n const knownPaths = new Set<string>();\n\n const instructionCandidates = [\n path.join(managedDir, \"CLAUDE.md\"),\n path.join(claudeHome, \"CLAUDE.md\"),\n path.join(root, \"CLAUDE.md\"),\n path.join(dotClaude, \"CLAUDE.md\"),\n path.join(root, \"AGENTS.md\"),\n ];\n\n for (const p of instructionCandidates) {\n const resolved = path.resolve(p);\n const inst = buildInstruction(resolved);\n if (inst) {\n instructions.push(inst);\n knownPaths.add(resolved);\n }\n }\n\n for (const p of findPerDirectoryClaudeMd(root, knownPaths)) {\n const inst = buildInstruction(p);\n if (inst) instructions.push(inst);\n }\n\n // Enabled plugins — merge user + project, deduplicate\n const pluginSet = new Set<string>();\n const enabledPlugins: ClaudeCodeConfig[\"enabledPlugins\"] = [];\n for (const layer of [project, user]) {\n for (const p of parseEnabledPlugins(layer?.settings ?? null)) {\n const key = `${p.pluginName}@${p.marketplace}`;\n if (!pluginSet.has(key)) {\n pluginSet.add(key);\n enabledPlugins.push(p);\n }\n }\n }\n\n // Plugin hooks — scan enabled plugins' cache dirs for hooks.json\n const pluginHooks = readPluginHooks(enabledPlugins);\n\n return {\n managed,\n user,\n project,\n projectLocal,\n instructions,\n enabledPlugins,\n pluginHooks,\n };\n}\n\n/**\n * Merge-write a settings patch into the given layer's settings.json.\n */\nexport function writeSettings(\n level: \"project\" | \"projectLocal\" | \"user\",\n patch: Record<string, unknown>,\n cwd?: string,\n): void {\n const root = cwd ? path.resolve(cwd) : process.cwd();\n const home = os.homedir();\n\n let filePath: string;\n switch (level) {\n case \"project\":\n filePath = path.join(root, \".claude\", \"settings.json\");\n break;\n case \"projectLocal\":\n filePath = path.join(root, \".claude\", \"settings.local.json\");\n break;\n case \"user\":\n filePath = path.join(home, \".claude\", \"settings.json\");\n break;\n }\n\n const existing = readJsonOrNull(filePath) ?? {};\n const merged = { ...existing, ...patch };\n\n fs.mkdirSync(path.dirname(filePath), { recursive: true });\n fs.writeFileSync(filePath, `${JSON.stringify(merged, null, 2)}\\n`);\n}\n\n/**\n * Write a config file (command, agent, rule, or skill) to the given layer.\n */\nexport function writeFile(\n level: \"project\" | \"user\",\n type: \"command\" | \"agent\" | \"rule\" | \"skill\",\n name: string,\n content: string,\n cwd?: string,\n): void {\n const root = cwd ? path.resolve(cwd) : process.cwd();\n const home = os.homedir();\n\n const base =\n level === \"project\"\n ? path.join(root, \".claude\")\n : path.join(home, \".claude\");\n\n if (type === \"skill\") {\n const dir = path.join(base, \"skills\", name);\n fs.mkdirSync(dir, { recursive: true });\n fs.writeFileSync(path.join(dir, \"SKILL.md\"), content);\n } else {\n const dir = path.join(base, `${type}s`);\n fs.mkdirSync(dir, { recursive: true });\n fs.writeFileSync(path.join(dir, `${name}.md`), content);\n }\n}\n"],"mappings":"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","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  captureException
3
- } from "./chunk-XO5NQRTD.js";
3
+ } from "./chunk-J3HVD4VI.js";
4
4
  import {
5
5
  log
6
6
  } from "./chunk-7Q3BJMLG.js";
@@ -10,14 +10,14 @@ import {
10
10
  readWatermark,
11
11
  watermarkKey,
12
12
  writeWatermark
13
- } from "./chunk-SEXU2WYG.js";
13
+ } from "./chunk-MEVW27U4.js";
14
14
  import {
15
15
  loadUnifiedConfig,
16
16
  saveUnifiedConfig
17
17
  } from "./chunk-QK5442ZP.js";
18
18
  import {
19
19
  getDb
20
- } from "./chunk-DZ5HJFB4.js";
20
+ } from "./chunk-SKZHAYNF.js";
21
21
 
22
22
  // src/sync/config.ts
23
23
  function loadSyncConfig() {
@@ -182,7 +182,7 @@ function createSyncLoop(opts) {
182
182
  const postBatchSize = opts.postBatchSize ?? DEFAULT_POST_BATCH_SIZE;
183
183
  const idleMs = opts.idleIntervalMs ?? DEFAULT_IDLE_MS;
184
184
  const catchUpMs = opts.catchUpIntervalMs ?? DEFAULT_CATCHUP_MS;
185
- const panopticonVersion = true ? "0.1.1+29c570b" : "dev";
185
+ const panopticonVersion = true ? "0.1.2+430c018" : "dev";
186
186
  function resolveHeaders(target) {
187
187
  const headers = { ...target.headers };
188
188
  headers["X-Panopticon-Version"] = panopticonVersion;
@@ -425,4 +425,4 @@ export {
425
425
  listTargets,
426
426
  createSyncLoop
427
427
  };
428
- //# sourceMappingURL=chunk-YZBYEULL.js.map
428
+ //# sourceMappingURL=chunk-3ZT3V7FP.js.map
@@ -1,45 +1,45 @@
1
1
  import {
2
2
  handleOtlpRequest
3
- } from "./chunk-TCKL7E4K.js";
3
+ } from "./chunk-OW52TNVA.js";
4
4
  import {
5
5
  handleProxyRequest,
6
6
  processHookEvent,
7
7
  tunnelWebSocket
8
- } from "./chunk-KLXRBD4N.js";
8
+ } from "./chunk-NE7VBLQD.js";
9
9
  import {
10
10
  addTarget,
11
11
  createSyncLoop,
12
12
  listTargets,
13
13
  removeTarget
14
- } from "./chunk-YZBYEULL.js";
14
+ } from "./chunk-3ZT3V7FP.js";
15
15
  import {
16
16
  createScannerLoop
17
- } from "./chunk-NXH7AONS.js";
17
+ } from "./chunk-OROLSIWZ.js";
18
18
  import {
19
19
  autoPrune,
20
20
  pruneEstimate,
21
21
  pruneExecute
22
- } from "./chunk-HRCEIYKU.js";
22
+ } from "./chunk-WXPT6KG7.js";
23
23
  import {
24
24
  addBreadcrumb,
25
25
  captureException,
26
26
  flushSentry,
27
27
  initSentry,
28
28
  setTag
29
- } from "./chunk-XO5NQRTD.js";
29
+ } from "./chunk-J3HVD4VI.js";
30
30
  import {
31
31
  log
32
32
  } from "./chunk-7Q3BJMLG.js";
33
33
  import {
34
34
  refreshPricing
35
- } from "./chunk-3TZAKV3M.js";
35
+ } from "./chunk-FMAHQRIU.js";
36
36
  import {
37
37
  TABLE_SYNC_REGISTRY,
38
38
  readWatermark,
39
39
  resetWatermarks,
40
40
  watermarkKey,
41
41
  writeWatermark
42
- } from "./chunk-SEXU2WYG.js";
42
+ } from "./chunk-MEVW27U4.js";
43
43
  import {
44
44
  loadUnifiedConfig
45
45
  } from "./chunk-QK5442ZP.js";
@@ -53,10 +53,10 @@ import {
53
53
  rawQuery,
54
54
  search,
55
55
  sessionTimeline
56
- } from "./chunk-LWXF7YRG.js";
56
+ } from "./chunk-GPTBERQD.js";
57
57
  import {
58
58
  getDb
59
- } from "./chunk-DZ5HJFB4.js";
59
+ } from "./chunk-SKZHAYNF.js";
60
60
  import {
61
61
  config
62
62
  } from "./chunk-K7YUPLES.js";
@@ -132,16 +132,44 @@ var EXEC = {
132
132
  const target = p.target;
133
133
  if (!target) throw new Error("target is required");
134
134
  const db = getDb();
135
+ const WM_COLUMNS = {
136
+ messages: "wm_messages",
137
+ tool_calls: "wm_tool_calls",
138
+ scanner_turns: "wm_scanner_turns",
139
+ scanner_events: "wm_scanner_events",
140
+ hook_events: "wm_hook_events",
141
+ otel_logs: "wm_otel_logs",
142
+ otel_metrics: "wm_otel_metrics",
143
+ otel_spans: "wm_otel_spans"
144
+ };
135
145
  const pending = {};
136
146
  for (const desc of TABLE_SYNC_REGISTRY) {
137
- const key = watermarkKey(desc.table, target);
138
- const wm = readWatermark(key);
139
- const maxId = db.prepare(
140
- `SELECT MAX(${desc.table === "sessions" ? "sync_seq" : "id"}) as m FROM ${desc.table}`
141
- ).get()?.m ?? 0;
142
- const count = Math.max(0, maxId - wm);
143
- if (count > 0) {
144
- pending[desc.table] = { maxId, watermark: wm, pending: count };
147
+ const wmCol = WM_COLUMNS[desc.table];
148
+ if (desc.sessionLinked && wmCol) {
149
+ const total = db.prepare(`SELECT COUNT(*) as c FROM ${desc.table}`).get()?.c ?? 0;
150
+ const pendingCount = db.prepare(
151
+ `SELECT COUNT(*) as c FROM ${desc.table} t
152
+ LEFT JOIN target_session_sync tss
153
+ ON tss.session_id = t.session_id AND tss.target = ?
154
+ WHERE tss.${wmCol} IS NULL OR t.id > tss.${wmCol}`
155
+ ).get(target)?.c ?? 0;
156
+ if (pendingCount > 0) {
157
+ pending[desc.table] = {
158
+ total,
159
+ synced: total - pendingCount,
160
+ pending: pendingCount
161
+ };
162
+ }
163
+ } else {
164
+ const key = watermarkKey(desc.table, target);
165
+ const wm = readWatermark(key);
166
+ const maxId = db.prepare(
167
+ `SELECT MAX(${desc.table === "sessions" ? "sync_seq" : "id"}) as m FROM ${desc.table}`
168
+ ).get()?.m ?? 0;
169
+ const count = Math.max(0, maxId - wm);
170
+ if (count > 0) {
171
+ pending[desc.table] = { total: maxId, synced: wm, pending: count };
172
+ }
145
173
  }
146
174
  }
147
175
  const totalPending = Object.values(pending).reduce(
@@ -444,4 +472,4 @@ export {
444
472
  syncAwarePrune,
445
473
  createUnifiedServer
446
474
  };
447
- //# sourceMappingURL=chunk-VXQ33OYT.js.map
475
+ //# sourceMappingURL=chunk-BKGQJ76N.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server.ts","../src/api/routes.ts","../src/db/sync-prune.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport http from \"node:http\";\nimport { handleApiRequest } from \"./api/routes.js\";\nimport { config } from \"./config.js\";\nimport { autoPrune } from \"./db/prune.js\";\nimport { syncAwarePrune } from \"./db/sync-prune.js\";\nimport { type HookInput, processHookEvent } from \"./hooks/ingest.js\";\nimport { log } from \"./log.js\";\nimport { handleOtlpRequest } from \"./otlp/server.js\";\nimport { handleProxyRequest, tunnelWebSocket } from \"./proxy/server.js\";\nimport { createScannerLoop } from \"./scanner/index.js\";\nimport type { ScannerHandle } from \"./scanner/types.js\";\nimport {\n addBreadcrumb,\n captureException,\n flushSentry,\n initSentry,\n setTag,\n} from \"./sentry.js\";\nimport { createSyncLoop } from \"./sync/loop.js\";\nimport type { SyncHandle } from \"./sync/types.js\";\nimport { loadUnifiedConfig } from \"./unified-config.js\";\n\nfunction collectBody(req: http.IncomingMessage): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n req.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n req.on(\"end\", () => resolve(Buffer.concat(chunks)));\n req.on(\"error\", reject);\n });\n}\n\nexport function createUnifiedServer(): http.Server {\n const server = http.createServer(async (req, res) => {\n const url = req.url ?? \"\";\n const method = req.method ?? \"\";\n\n // Health check\n if (url === \"/health\" && method === \"GET\") {\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ status: \"ok\", port: config.port }));\n return;\n }\n\n // Hook event ingest\n if (url === \"/hooks\" && method === \"POST\") {\n try {\n const body = await collectBody(req);\n const data: HookInput = JSON.parse(body.toString(\"utf-8\"));\n addBreadcrumb(\"hooks\", `${data.hook_event_name ?? \"unknown\"} event`, {\n session_id: data.session_id,\n tool_name: data.tool_name,\n target: data.target ?? data.source,\n });\n const result = processHookEvent(data);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(result));\n } catch (err) {\n log.hooks.error(\"Hook ingest error:\", err);\n captureException(err, { component: \"hooks\" });\n if (!res.headersSent) {\n res.writeHead(500, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"hook ingest failed\" }));\n }\n }\n return;\n }\n\n // OTLP ingest — /v1/logs, /v1/metrics, /v1/traces, or bare \"/\" (Gemini)\n if (\n method === \"POST\" &&\n (url.startsWith(\"/v1/\") || url === \"/\" || url === \"\")\n ) {\n await handleOtlpRequest(req, res);\n return;\n }\n\n // Proxy routes — /proxy/anthropic/*, /proxy/openai/*, /proxy/codex/*, /proxy/google/*\n if (url.startsWith(\"/proxy/\")) {\n if (method !== \"POST\") {\n res.writeHead(405);\n res.end();\n return;\n }\n addBreadcrumb(\"proxy\", `Proxy ${url}`);\n // Strip /proxy prefix so the proxy handler sees /anthropic/*, /openai/*, etc.\n req.url = url.slice(6);\n await handleProxyRequest(req, res);\n return;\n }\n\n // API routes — /api/tool, /api/exec\n if (url.startsWith(\"/api/\") && method === \"POST\") {\n await handleApiRequest(req, res);\n return;\n }\n\n // 404\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"not_found\" }));\n });\n\n // WebSocket upgrades for proxy routes\n server.on(\"upgrade\", (req, socket, head) => {\n const url = req.url ?? \"\";\n if (url.startsWith(\"/proxy/\")) {\n req.url = url.slice(6);\n tunnelWebSocket(req, socket, head);\n } else {\n socket.end(\"HTTP/1.1 404 Not Found\\r\\n\\r\\n\");\n }\n });\n\n return server;\n}\n\n// When run directly, start the unified server\nconst entryScript = process.argv[1]?.replaceAll(\"\\\\\", \"/\") ?? \"\";\nif (entryScript.endsWith(\"/server.js\") || entryScript.endsWith(\"/server.ts\")) {\n const PRUNE_INTERVAL_MS = 60 * 60 * 1000; // 1 hour\n\n function runPrune(): void {\n try {\n const cfg = loadUnifiedConfig();\n addBreadcrumb(\"prune\", \"Running scheduled prune\");\n autoPrune(cfg.retention.maxAgeDays, cfg.retention.maxSizeMb);\n if (cfg.sync.targets.length > 0 && cfg.retention.syncedMaxAgeDays) {\n syncAwarePrune(cfg.sync.targets, cfg.retention);\n }\n } catch (err) {\n log.server.error(\"Prune error:\", err);\n captureException(err, { component: \"prune\" });\n }\n }\n\n const sentryActive = initSentry();\n if (sentryActive) log.server.info(\"Sentry: enabled\");\n\n const server = createUnifiedServer();\n let syncHandle: SyncHandle | null = null;\n let scannerHandle: ScannerHandle | null = null;\n let pruneTimer: ReturnType<typeof setInterval> | null = null;\n\n let takeoverAttempted = false;\n server.on(\"error\", (err: NodeJS.ErrnoException) => {\n if (err.code === \"EADDRINUSE\") {\n if (takeoverAttempted) {\n log.server.warn(`Already running on ${config.host}:${config.port}`);\n process.exit(0);\n }\n takeoverAttempted = true;\n log.server.warn(`Port ${config.port} in use, attempting takeover...`);\n try {\n // Only kill the old panopticon server via PID file, not all\n // processes on the port (which could include Claude Code CLI)\n const pidFile = config.serverPidFile;\n const pidStr = fs.readFileSync(pidFile, \"utf-8\").trim();\n const oldPid = parseInt(pidStr, 10);\n if (oldPid && oldPid !== process.pid) {\n try {\n process.kill(oldPid, \"SIGTERM\");\n } catch {}\n }\n setTimeout(() => server.listen(config.port, config.host), 1500);\n } catch {\n log.server.warn(`Already running on ${config.host}:${config.port}`);\n process.exit(0);\n }\n return;\n }\n captureException(err, { component: \"server\" });\n throw err;\n });\n server.listen(config.port, config.host, () => {\n log.server.info(`Listening on ${config.host}:${config.port}`);\n\n const cfg = loadUnifiedConfig();\n\n // Start session file scanner first — sync is deferred until scanner\n // finishes any initial resync so we don't sync stale/partial data.\n scannerHandle = createScannerLoop({\n onReady: () => {\n if (cfg.sync.targets.length > 0) {\n log.sync.info(\n `Targets: ${cfg.sync.targets.map((t) => t.name).join(\", \")}`,\n );\n setTag(\"sync_targets\", cfg.sync.targets.length);\n syncHandle = createSyncLoop({\n targets: cfg.sync.targets,\n filter: cfg.sync.filter,\n });\n syncHandle.start();\n }\n },\n });\n scannerHandle.start();\n\n // Run prune on startup, then hourly\n runPrune();\n pruneTimer = setInterval(runPrune, PRUNE_INTERVAL_MS);\n pruneTimer.unref();\n });\n\n const shutdown = async () => {\n if (pruneTimer) clearInterval(pruneTimer);\n scannerHandle?.stop();\n syncHandle?.stop();\n await flushSentry();\n server.close();\n process.exit(0);\n };\n process.on(\"SIGTERM\", shutdown);\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGHUP\", shutdown);\n}\n","/**\n * Server-side API route handler.\n *\n * Two endpoints:\n * POST /api/tool — read-only query dispatch (CLI + MCP)\n * POST /api/exec — write command dispatch (CLI only)\n */\nimport type http from \"node:http\";\nimport { refreshPricing } from \"../db/pricing.js\";\nimport { pruneEstimate, pruneExecute } from \"../db/prune.js\";\nimport {\n activitySummary,\n costBreakdown,\n dbStats,\n listPlans,\n listSessions,\n print,\n rawQuery,\n search,\n sessionTimeline,\n} from \"../db/query.js\";\nimport { getDb } from \"../db/schema.js\";\nimport { log } from \"../log.js\";\nimport { addTarget, listTargets, removeTarget } from \"../sync/config.js\";\nimport { TABLE_SYNC_REGISTRY } from \"../sync/registry.js\";\nimport type { SyncTarget } from \"../sync/types.js\";\nimport {\n readWatermark,\n resetWatermarks,\n watermarkKey,\n writeWatermark,\n} from \"../sync/watermark.js\";\n\n// ── Tool dispatch ────────────────────────────────────────────────────────────\n\ntype ToolFn = (params: Record<string, unknown>) => unknown;\n\nconst TOOLS: Record<string, ToolFn> = {\n sessions: (p) => listSessions(p as Parameters<typeof listSessions>[0]),\n timeline: (p) => sessionTimeline(p as Parameters<typeof sessionTimeline>[0]),\n costs: (p) => costBreakdown(p as Parameters<typeof costBreakdown>[0]),\n summary: (p) => activitySummary(p as Parameters<typeof activitySummary>[0]),\n plans: (p) => listPlans(p as Parameters<typeof listPlans>[0]),\n search: (p) => search(p as Parameters<typeof search>[0]),\n get: (p) => print(p as Parameters<typeof print>[0]),\n query: (p) => rawQuery((p as { sql: string }).sql),\n status: () => dbStats(),\n};\n\n// ── Exec dispatch ────────────────────────────────────────────────────────────\n\ntype ExecFn = (params: Record<string, unknown>) => unknown;\n\nconst EXEC: Record<string, ExecFn> = {\n prune: (p) => {\n const cutoffMs = p.cutoffMs as number;\n if (typeof cutoffMs !== \"number\") {\n throw new Error(\"cutoffMs is required and must be a number\");\n }\n if (p.dryRun) {\n return pruneEstimate(cutoffMs);\n }\n const result = pruneExecute(cutoffMs);\n if (p.vacuum) {\n const db = getDb();\n db.pragma(\"wal_checkpoint(TRUNCATE)\");\n db.exec(\"VACUUM\");\n }\n return result;\n },\n \"refresh-pricing\": () => refreshPricing(),\n \"sync-reset\": (p) => {\n const target = p.target as string | undefined;\n resetWatermarks(target);\n return { ok: true, target: target ?? \"all\" };\n },\n \"sync-watermark-get\": (p) => {\n const target = p.target as string;\n if (!target) throw new Error(\"target is required\");\n const table = p.table as string | undefined;\n if (table) {\n return {\n key: watermarkKey(table, target),\n value: readWatermark(watermarkKey(table, target)),\n };\n }\n // Return all watermarks for this target\n const watermarks: Record<string, number> = {};\n for (const desc of TABLE_SYNC_REGISTRY) {\n const key = watermarkKey(desc.table, target);\n watermarks[desc.table] = readWatermark(key);\n }\n return { target, watermarks };\n },\n \"sync-watermark-set\": (p) => {\n const target = p.target as string;\n const table = p.table as string;\n const value = p.value as number;\n if (!target) throw new Error(\"target is required\");\n if (!table) throw new Error(\"table is required\");\n if (typeof value !== \"number\") throw new Error(\"value must be a number\");\n const key = watermarkKey(table, target);\n writeWatermark(key, value);\n return { key, value };\n },\n \"sync-pending\": (p) => {\n const target = p.target as string;\n if (!target) throw new Error(\"target is required\");\n const db = getDb();\n\n /** Maps table name → wm column in target_session_sync. */\n const WM_COLUMNS: Record<string, string> = {\n messages: \"wm_messages\",\n tool_calls: \"wm_tool_calls\",\n scanner_turns: \"wm_scanner_turns\",\n scanner_events: \"wm_scanner_events\",\n hook_events: \"wm_hook_events\",\n otel_logs: \"wm_otel_logs\",\n otel_metrics: \"wm_otel_metrics\",\n otel_spans: \"wm_otel_spans\",\n };\n\n const pending: Record<\n string,\n { total: number; synced: number; pending: number }\n > = {};\n for (const desc of TABLE_SYNC_REGISTRY) {\n const wmCol = WM_COLUMNS[desc.table];\n if (desc.sessionLinked && wmCol) {\n // Session-linked tables: count rows beyond each session's per-session watermark,\n // plus rows belonging to sessions not yet tracked in target_session_sync.\n const total =\n (\n db.prepare(`SELECT COUNT(*) as c FROM ${desc.table}`).get() as {\n c: number;\n }\n )?.c ?? 0;\n const pendingCount =\n (\n db\n .prepare(\n `SELECT COUNT(*) as c FROM ${desc.table} t\n LEFT JOIN target_session_sync tss\n ON tss.session_id = t.session_id AND tss.target = ?\n WHERE tss.${wmCol} IS NULL OR t.id > tss.${wmCol}`,\n )\n .get(target) as { c: number }\n )?.c ?? 0;\n if (pendingCount > 0) {\n pending[desc.table] = {\n total,\n synced: total - pendingCount,\n pending: pendingCount,\n };\n }\n } else {\n // Sessions table and non-session-linked tables: use global watermark.\n const key = watermarkKey(desc.table, target);\n const wm = readWatermark(key);\n const maxId =\n (\n db\n .prepare(\n `SELECT MAX(${desc.table === \"sessions\" ? \"sync_seq\" : \"id\"}) as m FROM ${desc.table}`,\n )\n .get() as { m: number | null }\n )?.m ?? 0;\n const count = Math.max(0, maxId - wm);\n if (count > 0) {\n pending[desc.table] = { total: maxId, synced: wm, pending: count };\n }\n }\n }\n const totalPending = Object.values(pending).reduce(\n (s, v) => s + v.pending,\n 0,\n );\n return { target, totalPending, tables: pending };\n },\n \"sync-target-list\": () => {\n return { targets: listTargets() };\n },\n \"sync-target-add\": (p) => {\n const target = p as unknown as SyncTarget;\n if (!target.name) throw new Error(\"name is required\");\n if (!target.url) throw new Error(\"url is required\");\n addTarget(target);\n return { ok: true, name: target.name, url: target.url };\n },\n \"sync-target-remove\": (p) => {\n const name = p.name as string;\n if (!name) throw new Error(\"name is required\");\n const removed = removeTarget(name);\n return { ok: removed, name };\n },\n};\n\n// ── Request handler ──────────────────────────────────────────────────────────\n\nfunction collectBody(req: http.IncomingMessage): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n req.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n req.on(\"end\", () => resolve(Buffer.concat(chunks)));\n req.on(\"error\", reject);\n });\n}\n\nfunction jsonResponse(\n res: http.ServerResponse,\n status: number,\n data: unknown,\n): void {\n res.writeHead(status, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(data));\n}\n\nexport async function handleApiRequest(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n): Promise<void> {\n const url = req.url ?? \"\";\n\n let body: Record<string, unknown>;\n try {\n const raw = await collectBody(req);\n body = raw.length > 0 ? JSON.parse(raw.toString(\"utf-8\")) : {};\n } catch {\n jsonResponse(res, 400, { error: \"Invalid JSON body\" });\n return;\n }\n\n if (url === \"/api/tool\") {\n const name = body.name as string | undefined;\n if (!name || !(name in TOOLS)) {\n jsonResponse(res, 404, {\n error: `Unknown tool: ${name}`,\n available: Object.keys(TOOLS),\n });\n return;\n }\n try {\n const params = (body.params as Record<string, unknown>) ?? {};\n const result = TOOLS[name](params);\n jsonResponse(res, 200, result);\n } catch (err) {\n log.server.error(`API tool \"${name}\" error:`, err);\n jsonResponse(res, 500, {\n error: err instanceof Error ? err.message : String(err),\n });\n }\n return;\n }\n\n if (url === \"/api/exec\") {\n const command = body.command as string | undefined;\n if (!command || !(command in EXEC)) {\n jsonResponse(res, 404, {\n error: `Unknown command: ${command}`,\n available: Object.keys(EXEC),\n });\n return;\n }\n try {\n const params = (body.params as Record<string, unknown>) ?? {};\n const result = await EXEC[command](params);\n jsonResponse(res, 200, result ?? { ok: true });\n } catch (err) {\n log.server.error(`API exec \"${command}\" error:`, err);\n jsonResponse(res, 500, {\n error: err instanceof Error ? err.message : String(err),\n });\n }\n return;\n }\n\n jsonResponse(res, 404, { error: \"Unknown API endpoint\", url });\n}\n","import type { SyncTarget } from \"../sync/types.js\";\nimport { readWatermark, watermarkKey } from \"../sync/watermark.js\";\nimport type { RetentionConfig } from \"../unified-config.js\";\nimport { getDb } from \"./schema.js\";\n\nexport interface SyncPruneResult {\n hook_events: number;\n otel_logs: number;\n otel_metrics: number;\n}\n\n/**\n * Compute the minimum watermark for a table across all sync targets.\n * Returns 0 if no targets exist or any target has watermark 0 (hasn't synced yet).\n */\nexport function minWatermarkForTable(\n table: string,\n targets: SyncTarget[],\n): number {\n if (targets.length === 0) return 0;\n\n let min = Infinity;\n for (const t of targets) {\n const wm = readWatermark(watermarkKey(table, t.name));\n if (wm === 0) return 0;\n if (wm < min) min = wm;\n }\n return min === Infinity ? 0 : min;\n}\n\n/**\n * Aggressively prune rows that have been confirmed synced to ALL targets\n * and are older than `syncedMaxAgeDays`.\n *\n * No-op when:\n * - No sync targets configured\n * - `syncedMaxAgeDays` is not set\n * - Any target has watermark 0 for a given table (hasn't completed first sync)\n */\nexport function syncAwarePrune(\n targets: SyncTarget[],\n retention: RetentionConfig,\n): SyncPruneResult {\n const result: SyncPruneResult = {\n hook_events: 0,\n otel_logs: 0,\n otel_metrics: 0,\n };\n\n if (!retention.syncedMaxAgeDays || targets.length === 0) {\n return result;\n }\n\n const cutoffMs =\n Date.now() - retention.syncedMaxAgeDays * 24 * 60 * 60 * 1000;\n const cutoffNs = cutoffMs * 1_000_000;\n const db = getDb();\n\n const tx = db.transaction(() => {\n // -- hook_events --\n const hookMinWm = minWatermarkForTable(\"hook_events\", targets);\n if (hookMinWm > 0) {\n db.prepare(\n \"DELETE FROM hook_events_fts WHERE rowid IN (SELECT id FROM hook_events WHERE id <= ? AND timestamp_ms < ?)\",\n ).run(hookMinWm, cutoffMs);\n\n result.hook_events = db\n .prepare(\"DELETE FROM hook_events WHERE id <= ? AND timestamp_ms < ?\")\n .run(hookMinWm, cutoffMs).changes;\n }\n\n // -- otel_logs --\n const logsMinWm = minWatermarkForTable(\"otel_logs\", targets);\n if (logsMinWm > 0) {\n result.otel_logs = db\n .prepare(\"DELETE FROM otel_logs WHERE id <= ? AND timestamp_ns < ?\")\n .run(logsMinWm, cutoffNs).changes;\n }\n\n // -- otel_metrics --\n const metricsMinWm = minWatermarkForTable(\"otel_metrics\", targets);\n if (metricsMinWm > 0) {\n result.otel_metrics = db\n .prepare(\"DELETE FROM otel_metrics WHERE id <= ? AND timestamp_ns < ?\")\n .run(metricsMinWm, cutoffNs).changes;\n }\n\n // Session metadata (session_repositories, session_cwds) is local-only —\n // not synced to remote targets. Left to regular time/size-based pruneExecute.\n });\n\n tx();\n return result;\n}\n"],"mappings":"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","names":["collectBody","runPrune"]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  getDb
3
- } from "./chunk-DZ5HJFB4.js";
3
+ } from "./chunk-SKZHAYNF.js";
4
4
  import {
5
5
  config
6
6
  } from "./chunk-K7YUPLES.js";
@@ -155,4 +155,4 @@ export {
155
155
  refreshIfStale,
156
156
  COST_EXPR
157
157
  };
158
- //# sourceMappingURL=chunk-3TZAKV3M.js.map
158
+ //# sourceMappingURL=chunk-FMAHQRIU.js.map
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-QVK6VGCV.js";
4
4
  import {
5
5
  getDb
6
- } from "./chunk-DZ5HJFB4.js";
6
+ } from "./chunk-SKZHAYNF.js";
7
7
 
8
8
  // src/db/query.ts
9
9
  function parseSince(since) {
@@ -623,4 +623,4 @@ export {
623
623
  rawQuery,
624
624
  dbStats
625
625
  };
626
- //# sourceMappingURL=chunk-LWXF7YRG.js.map
626
+ //# sourceMappingURL=chunk-GPTBERQD.js.map
@@ -166,4 +166,4 @@ export {
166
166
  syncTargetAdd,
167
167
  syncTargetRemove
168
168
  };
169
- //# sourceMappingURL=chunk-4SM2H22C.js.map
169
+ //# sourceMappingURL=chunk-HO443ZQM.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/api/client.ts"],"sourcesContent":["/**\n * HTTP client for the panopticon server API.\n *\n * Provides typed wrappers around POST /api/tool and POST /api/exec\n * so that CLI and MCP can query the server instead of opening the\n * database directly.\n */\nimport http from \"node:http\";\nimport { config } from \"../config.js\";\nimport type {\n ActivitySummaryResult,\n SearchResult,\n SessionListResult,\n SessionTimelineResult,\n SpendingResult,\n} from \"../types.js\";\n\n// ── Core transport ───────────────────────────────────────────────────────────\n\nfunction post(\n path: string,\n body: unknown,\n timeoutMs: number,\n): Promise<unknown> {\n return new Promise((resolve, reject) => {\n const json = JSON.stringify(body);\n const req = http.request(\n {\n hostname: config.host,\n port: config.port,\n path,\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Content-Length\": Buffer.byteLength(json),\n },\n timeout: timeoutMs,\n },\n (res) => {\n const chunks: Buffer[] = [];\n res.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n res.on(\"end\", () => {\n const text = Buffer.concat(chunks).toString(\"utf-8\");\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n reject(\n new Error(\n `Invalid JSON response from server: ${text.slice(0, 200)}`,\n ),\n );\n return;\n }\n if (\n res.statusCode &&\n res.statusCode >= 400 &&\n typeof parsed === \"object\" &&\n parsed !== null &&\n \"error\" in parsed\n ) {\n reject(new Error((parsed as { error: string }).error));\n return;\n }\n resolve(parsed);\n });\n },\n );\n req.on(\"error\", (err: NodeJS.ErrnoException) => {\n if (err.code === \"ECONNREFUSED\") {\n reject(\n new Error(\n \"Panopticon server is not running. Start with: panopticon start\",\n ),\n );\n return;\n }\n reject(err);\n });\n req.on(\"timeout\", () => {\n req.destroy();\n reject(new Error(\"Request to panopticon server timed out\"));\n });\n req.write(json);\n req.end();\n });\n}\n\n// ── Tool calls (read-only) ──────────────────────────────────────────────────\n\nconst TOOL_TIMEOUT = 30_000;\n\nexport function callTool(\n name: string,\n params?: Record<string, unknown>,\n): Promise<unknown> {\n return post(\"/api/tool\", { name, params }, TOOL_TIMEOUT);\n}\n\nexport function listSessions(opts?: {\n limit?: number;\n since?: string;\n}): Promise<SessionListResult> {\n return callTool(\n \"sessions\",\n opts as Record<string, unknown>,\n ) as Promise<SessionListResult>;\n}\n\nexport function sessionTimeline(opts: {\n sessionId: string;\n limit?: number;\n offset?: number;\n fullPayloads?: boolean;\n}): Promise<SessionTimelineResult> {\n return callTool(\n \"timeline\",\n opts as Record<string, unknown>,\n ) as Promise<SessionTimelineResult>;\n}\n\nexport function costBreakdown(opts?: {\n since?: string;\n groupBy?: \"session\" | \"model\" | \"day\";\n}): Promise<SpendingResult> {\n return callTool(\n \"costs\",\n opts as Record<string, unknown>,\n ) as Promise<SpendingResult>;\n}\n\nexport function activitySummary(opts?: {\n since?: string;\n}): Promise<ActivitySummaryResult> {\n return callTool(\n \"summary\",\n opts as Record<string, unknown>,\n ) as Promise<ActivitySummaryResult>;\n}\n\nexport function listPlans(opts?: {\n session_id?: string;\n since?: string;\n limit?: number;\n}): Promise<unknown> {\n return callTool(\"plans\", opts as Record<string, unknown>);\n}\n\nexport function search(opts: {\n query: string;\n eventTypes?: string[];\n since?: string;\n limit?: number;\n offset?: number;\n fullPayloads?: boolean;\n}): Promise<SearchResult> {\n return callTool(\n \"search\",\n opts as Record<string, unknown>,\n ) as Promise<SearchResult>;\n}\n\nexport function print(opts: {\n source: \"hook\" | \"otel\" | \"message\";\n id: number;\n}): Promise<unknown> {\n return callTool(\"get\", opts as Record<string, unknown>);\n}\n\nexport function rawQuery(sql: string): Promise<unknown> {\n return callTool(\"query\", { sql });\n}\n\nexport function dbStats(): Promise<unknown> {\n return callTool(\"status\");\n}\n\n// ── Exec calls (write operations, CLI only) ─────────────────────────────────\n\nconst EXEC_TIMEOUT = 60_000;\n\nexport function callExec(\n command: string,\n params?: Record<string, unknown>,\n): Promise<unknown> {\n return post(\"/api/exec\", { command, params }, EXEC_TIMEOUT);\n}\n\nexport function pruneEstimate(cutoffMs: number): Promise<unknown> {\n return callExec(\"prune\", { cutoffMs, dryRun: true });\n}\n\nexport function pruneExecute(\n cutoffMs: number,\n opts?: { vacuum?: boolean },\n): Promise<unknown> {\n return callExec(\"prune\", { cutoffMs, ...opts });\n}\n\nexport function refreshPricing(): Promise<unknown> {\n return callExec(\"refresh-pricing\");\n}\n\nexport function syncReset(target?: string): Promise<unknown> {\n return callExec(\"sync-reset\", target ? { target } : {});\n}\n\nexport function syncWatermarkGet(\n target: string,\n table?: string,\n): Promise<unknown> {\n return callExec(\"sync-watermark-get\", { target, table });\n}\n\nexport function syncWatermarkSet(\n target: string,\n table: string,\n value: number,\n): Promise<unknown> {\n return callExec(\"sync-watermark-set\", { target, table, value });\n}\n\nexport function syncPending(target: string): Promise<{\n target: string;\n totalPending: number;\n tables: Record<string, { maxId: number; watermark: number; pending: number }>;\n}> {\n return callExec(\"sync-pending\", { target }) as Promise<{\n target: string;\n totalPending: number;\n tables: Record<\n string,\n { maxId: number; watermark: number; pending: number }\n >;\n }>;\n}\n\nexport function syncTargetList(): Promise<unknown> {\n return callExec(\"sync-target-list\");\n}\n\nexport function syncTargetAdd(target: {\n name: string;\n url: string;\n token?: string;\n tokenCommand?: string;\n}): Promise<unknown> {\n return callExec(\"sync-target-add\", target as Record<string, unknown>);\n}\n\nexport function syncTargetRemove(name: string): Promise<unknown> {\n return callExec(\"sync-target-remove\", { name });\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/api/client.ts"],"sourcesContent":["/**\n * HTTP client for the panopticon server API.\n *\n * Provides typed wrappers around POST /api/tool and POST /api/exec\n * so that CLI and MCP can query the server instead of opening the\n * database directly.\n */\nimport http from \"node:http\";\nimport { config } from \"../config.js\";\nimport type {\n ActivitySummaryResult,\n SearchResult,\n SessionListResult,\n SessionTimelineResult,\n SpendingResult,\n} from \"../types.js\";\n\n// ── Core transport ───────────────────────────────────────────────────────────\n\nfunction post(\n path: string,\n body: unknown,\n timeoutMs: number,\n): Promise<unknown> {\n return new Promise((resolve, reject) => {\n const json = JSON.stringify(body);\n const req = http.request(\n {\n hostname: config.host,\n port: config.port,\n path,\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Content-Length\": Buffer.byteLength(json),\n },\n timeout: timeoutMs,\n },\n (res) => {\n const chunks: Buffer[] = [];\n res.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n res.on(\"end\", () => {\n const text = Buffer.concat(chunks).toString(\"utf-8\");\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n reject(\n new Error(\n `Invalid JSON response from server: ${text.slice(0, 200)}`,\n ),\n );\n return;\n }\n if (\n res.statusCode &&\n res.statusCode >= 400 &&\n typeof parsed === \"object\" &&\n parsed !== null &&\n \"error\" in parsed\n ) {\n reject(new Error((parsed as { error: string }).error));\n return;\n }\n resolve(parsed);\n });\n },\n );\n req.on(\"error\", (err: NodeJS.ErrnoException) => {\n if (err.code === \"ECONNREFUSED\") {\n reject(\n new Error(\n \"Panopticon server is not running. Start with: panopticon start\",\n ),\n );\n return;\n }\n reject(err);\n });\n req.on(\"timeout\", () => {\n req.destroy();\n reject(new Error(\"Request to panopticon server timed out\"));\n });\n req.write(json);\n req.end();\n });\n}\n\n// ── Tool calls (read-only) ──────────────────────────────────────────────────\n\nconst TOOL_TIMEOUT = 30_000;\n\nexport function callTool(\n name: string,\n params?: Record<string, unknown>,\n): Promise<unknown> {\n return post(\"/api/tool\", { name, params }, TOOL_TIMEOUT);\n}\n\nexport function listSessions(opts?: {\n limit?: number;\n since?: string;\n}): Promise<SessionListResult> {\n return callTool(\n \"sessions\",\n opts as Record<string, unknown>,\n ) as Promise<SessionListResult>;\n}\n\nexport function sessionTimeline(opts: {\n sessionId: string;\n limit?: number;\n offset?: number;\n fullPayloads?: boolean;\n}): Promise<SessionTimelineResult> {\n return callTool(\n \"timeline\",\n opts as Record<string, unknown>,\n ) as Promise<SessionTimelineResult>;\n}\n\nexport function costBreakdown(opts?: {\n since?: string;\n groupBy?: \"session\" | \"model\" | \"day\";\n}): Promise<SpendingResult> {\n return callTool(\n \"costs\",\n opts as Record<string, unknown>,\n ) as Promise<SpendingResult>;\n}\n\nexport function activitySummary(opts?: {\n since?: string;\n}): Promise<ActivitySummaryResult> {\n return callTool(\n \"summary\",\n opts as Record<string, unknown>,\n ) as Promise<ActivitySummaryResult>;\n}\n\nexport function listPlans(opts?: {\n session_id?: string;\n since?: string;\n limit?: number;\n}): Promise<unknown> {\n return callTool(\"plans\", opts as Record<string, unknown>);\n}\n\nexport function search(opts: {\n query: string;\n eventTypes?: string[];\n since?: string;\n limit?: number;\n offset?: number;\n fullPayloads?: boolean;\n}): Promise<SearchResult> {\n return callTool(\n \"search\",\n opts as Record<string, unknown>,\n ) as Promise<SearchResult>;\n}\n\nexport function print(opts: {\n source: \"hook\" | \"otel\" | \"message\";\n id: number;\n}): Promise<unknown> {\n return callTool(\"get\", opts as Record<string, unknown>);\n}\n\nexport function rawQuery(sql: string): Promise<unknown> {\n return callTool(\"query\", { sql });\n}\n\nexport function dbStats(): Promise<unknown> {\n return callTool(\"status\");\n}\n\n// ── Exec calls (write operations, CLI only) ─────────────────────────────────\n\nconst EXEC_TIMEOUT = 60_000;\n\nexport function callExec(\n command: string,\n params?: Record<string, unknown>,\n): Promise<unknown> {\n return post(\"/api/exec\", { command, params }, EXEC_TIMEOUT);\n}\n\nexport function pruneEstimate(cutoffMs: number): Promise<unknown> {\n return callExec(\"prune\", { cutoffMs, dryRun: true });\n}\n\nexport function pruneExecute(\n cutoffMs: number,\n opts?: { vacuum?: boolean },\n): Promise<unknown> {\n return callExec(\"prune\", { cutoffMs, ...opts });\n}\n\nexport function refreshPricing(): Promise<unknown> {\n return callExec(\"refresh-pricing\");\n}\n\nexport function syncReset(target?: string): Promise<unknown> {\n return callExec(\"sync-reset\", target ? { target } : {});\n}\n\nexport function syncWatermarkGet(\n target: string,\n table?: string,\n): Promise<unknown> {\n return callExec(\"sync-watermark-get\", { target, table });\n}\n\nexport function syncWatermarkSet(\n target: string,\n table: string,\n value: number,\n): Promise<unknown> {\n return callExec(\"sync-watermark-set\", { target, table, value });\n}\n\nexport function syncPending(target: string): Promise<{\n target: string;\n totalPending: number;\n tables: Record<string, { total: number; synced: number; pending: number }>;\n}> {\n return callExec(\"sync-pending\", { target }) as Promise<{\n target: string;\n totalPending: number;\n tables: Record<string, { total: number; synced: number; pending: number }>;\n }>;\n}\n\nexport function syncTargetList(): Promise<unknown> {\n return callExec(\"sync-target-list\");\n}\n\nexport function syncTargetAdd(target: {\n name: string;\n url: string;\n token?: string;\n tokenCommand?: string;\n}): Promise<unknown> {\n return callExec(\"sync-target-add\", target as Record<string, unknown>);\n}\n\nexport function syncTargetRemove(name: string): Promise<unknown> {\n return callExec(\"sync-target-remove\", { name });\n}\n"],"mappings":"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","names":[]}
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  refreshPricing
3
- } from "./chunk-3TZAKV3M.js";
3
+ } from "./chunk-FMAHQRIU.js";
4
4
  import {
5
5
  allTargets
6
6
  } from "./chunk-QVK6VGCV.js";
7
7
  import {
8
8
  closeDb,
9
9
  getDb
10
- } from "./chunk-DZ5HJFB4.js";
10
+ } from "./chunk-SKZHAYNF.js";
11
11
  import {
12
12
  config,
13
13
  ensureDataDir
@@ -127,4 +127,4 @@ export {
127
127
  fetchPricing,
128
128
  configureShellEnv
129
129
  };
130
- //# sourceMappingURL=chunk-L7G27XWF.js.map
130
+ //# sourceMappingURL=chunk-HRNZUHTA.js.map
@@ -19,7 +19,7 @@ import {
19
19
  } from "@sentry/core";
20
20
  var initialized = false;
21
21
  function getVersion() {
22
- return true ? "0.1.1+29c570b" : "dev";
22
+ return true ? "0.1.2+430c018" : "dev";
23
23
  }
24
24
  var SCRUBBED_BREADCRUMB_FIELDS = [
25
25
  "prompt",
@@ -167,4 +167,4 @@ export {
167
167
  setTag,
168
168
  flushSentry
169
169
  };
170
- //# sourceMappingURL=chunk-XO5NQRTD.js.map
170
+ //# sourceMappingURL=chunk-J3HVD4VI.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  getDb
3
- } from "./chunk-DZ5HJFB4.js";
3
+ } from "./chunk-SKZHAYNF.js";
4
4
 
5
5
  // src/sync/reader.ts
6
6
  function parseJson(raw) {
@@ -239,7 +239,7 @@ function parseJsonObject(raw) {
239
239
  }
240
240
  var USER_CONFIG_SQL = `
241
241
  SELECT id, device_name, snapshot_at_ms, content_hash,
242
- permissions, enabled_plugins, hooks, commands, rules, skills
242
+ permissions, enabled_plugins, hooks, commands, rules, skills, plugin_hooks
243
243
  FROM user_config_snapshots
244
244
  WHERE id > ?
245
245
  ORDER BY id
@@ -258,7 +258,8 @@ function readUserConfigSnapshots(afterId, limit) {
258
258
  hooks: parseJsonArray(r.hooks),
259
259
  commands: parseJsonArray(r.commands),
260
260
  rules: parseJsonArray(r.rules),
261
- skills: parseJsonArray(r.skills)
261
+ skills: parseJsonArray(r.skills),
262
+ pluginHooks: parseJsonArray(r.plugin_hooks)
262
263
  }));
263
264
  const maxId = rows.length > 0 ? rows[rows.length - 1].id : afterId;
264
265
  return { rows, maxId };
@@ -785,4 +786,4 @@ export {
785
786
  writeWatermark,
786
787
  resetWatermarks
787
788
  };
788
- //# sourceMappingURL=chunk-SEXU2WYG.js.map
789
+ //# sourceMappingURL=chunk-MEVW27U4.js.map