@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,387 @@
1
+ // src/scanner.ts
2
+ import { execFileSync } from "child_process";
3
+ import fs from "fs";
4
+ import os from "os";
5
+ import path from "path";
6
+ function readFileOrNull(filePath) {
7
+ try {
8
+ return fs.readFileSync(filePath, "utf-8");
9
+ } catch {
10
+ return null;
11
+ }
12
+ }
13
+ function readJsonOrNull(filePath) {
14
+ const raw = readFileOrNull(filePath);
15
+ if (raw === null) return null;
16
+ try {
17
+ return JSON.parse(raw);
18
+ } catch {
19
+ return null;
20
+ }
21
+ }
22
+ function readMdFiles(dir) {
23
+ let entries;
24
+ try {
25
+ entries = fs.readdirSync(dir, { withFileTypes: true });
26
+ } catch {
27
+ return [];
28
+ }
29
+ const results = [];
30
+ for (const entry of entries) {
31
+ if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
32
+ const content = readFileOrNull(path.join(dir, entry.name));
33
+ if (content === null) continue;
34
+ results.push({ name: entry.name.replace(/\.md$/, ""), content });
35
+ }
36
+ return results;
37
+ }
38
+ function readSkills(dir) {
39
+ let entries;
40
+ try {
41
+ entries = fs.readdirSync(dir, { withFileTypes: true });
42
+ } catch {
43
+ return [];
44
+ }
45
+ const results = [];
46
+ for (const entry of entries) {
47
+ if (!entry.isDirectory()) continue;
48
+ const content = readFileOrNull(path.join(dir, entry.name, "SKILL.md"));
49
+ if (content !== null) {
50
+ results.push({ name: entry.name, content });
51
+ }
52
+ }
53
+ return results;
54
+ }
55
+ function parseHooks(json) {
56
+ const hooks = [];
57
+ const raw = json.hooks;
58
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return hooks;
59
+ for (const [event, entries] of Object.entries(
60
+ raw
61
+ )) {
62
+ if (!Array.isArray(entries)) continue;
63
+ for (const entry of entries) {
64
+ if (typeof entry !== "object" || entry === null) continue;
65
+ const e = entry;
66
+ const matcher = typeof e.matcher === "string" ? e.matcher : null;
67
+ if (Array.isArray(e.hooks)) {
68
+ for (const hook of e.hooks) {
69
+ if (typeof hook !== "object" || hook === null) continue;
70
+ const h = hook;
71
+ hooks.push({
72
+ event,
73
+ matcher,
74
+ type: h.type === "command" ? "command" : "script"
75
+ });
76
+ }
77
+ } else {
78
+ hooks.push({
79
+ event,
80
+ matcher,
81
+ type: e.command ? "command" : "script"
82
+ });
83
+ }
84
+ }
85
+ }
86
+ return hooks;
87
+ }
88
+ function parseMcpServers(json) {
89
+ const servers = [];
90
+ const raw = json.mcpServers;
91
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return servers;
92
+ for (const [name, def] of Object.entries(raw)) {
93
+ if (typeof def !== "object" || def === null) continue;
94
+ const s = def;
95
+ servers.push({
96
+ name,
97
+ command: typeof s.command === "string" ? s.command : ""
98
+ });
99
+ }
100
+ return servers;
101
+ }
102
+ function parsePermissions(json) {
103
+ const allow = [];
104
+ const ask = [];
105
+ const deny = [];
106
+ function collectStrings(arr) {
107
+ if (!Array.isArray(arr)) return [];
108
+ return arr.filter((v) => typeof v === "string");
109
+ }
110
+ const perms = json.permissions;
111
+ if (perms && typeof perms === "object" && !Array.isArray(perms)) {
112
+ const p = perms;
113
+ allow.push(...collectStrings(p.allow));
114
+ ask.push(...collectStrings(p.ask));
115
+ deny.push(...collectStrings(p.deny));
116
+ }
117
+ if (allow.length === 0 && deny.length === 0) {
118
+ allow.push(...collectStrings(json.allowedTools));
119
+ deny.push(...collectStrings(json.deniedTools));
120
+ }
121
+ return { allow, ask, deny };
122
+ }
123
+ function parseEnabledPlugins(settings) {
124
+ if (!settings) return [];
125
+ const raw = settings.enabledPlugins;
126
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return [];
127
+ const result = [];
128
+ for (const [key, enabled] of Object.entries(raw)) {
129
+ if (!enabled) continue;
130
+ const atIndex = key.indexOf("@");
131
+ if (atIndex > 0) {
132
+ result.push({
133
+ pluginName: key.slice(0, atIndex),
134
+ marketplace: key.slice(atIndex + 1)
135
+ });
136
+ } else {
137
+ result.push({ pluginName: key, marketplace: "" });
138
+ }
139
+ }
140
+ return result;
141
+ }
142
+ function buildLayer(settingsPath, dirs, mcpJsonPath) {
143
+ const settings = readJsonOrNull(settingsPath);
144
+ const mcpFromSettings = settings ? parseMcpServers(settings) : [];
145
+ const mcpFromFile = mcpJsonPath ? parseMcpServers(readJsonOrNull(mcpJsonPath) ?? {}) : [];
146
+ const mcpNames = new Set(mcpFromSettings.map((s) => s.name));
147
+ const mcpServers = [
148
+ ...mcpFromSettings,
149
+ ...mcpFromFile.filter((s) => !mcpNames.has(s.name))
150
+ ];
151
+ return {
152
+ settings,
153
+ hooks: settings ? parseHooks(settings) : [],
154
+ mcpServers,
155
+ permissions: settings ? parsePermissions(settings) : { allow: [], ask: [], deny: [] },
156
+ commands: dirs.commands ? readMdFiles(dirs.commands) : [],
157
+ agents: dirs.agents ? readMdFiles(dirs.agents) : [],
158
+ rules: dirs.rules ? readMdFiles(dirs.rules) : [],
159
+ skills: dirs.skills ? readSkills(dirs.skills) : []
160
+ };
161
+ }
162
+ var gitRootCache = /* @__PURE__ */ new Map();
163
+ function resolveGitRoot(cwd) {
164
+ if (gitRootCache.has(cwd)) return gitRootCache.get(cwd);
165
+ let root = null;
166
+ try {
167
+ root = execFileSync("git", ["-C", cwd, "rev-parse", "--show-toplevel"], {
168
+ encoding: "utf-8",
169
+ timeout: 5e3,
170
+ stdio: ["ignore", "pipe", "ignore"]
171
+ }).trim();
172
+ } catch {
173
+ }
174
+ gitRootCache.set(cwd, root);
175
+ return root;
176
+ }
177
+ function isGitignored(filePath, cwd) {
178
+ try {
179
+ execFileSync("git", ["-C", cwd, "check-ignore", "-q", filePath], {
180
+ timeout: 3e3,
181
+ stdio: ["ignore", "ignore", "ignore"]
182
+ });
183
+ return true;
184
+ } catch {
185
+ return false;
186
+ }
187
+ }
188
+ function findPerDirectoryClaudeMd(root, excludePaths) {
189
+ const gitRoot = resolveGitRoot(root);
190
+ if (gitRoot) {
191
+ try {
192
+ const output = execFileSync(
193
+ "git",
194
+ ["-C", gitRoot, "ls-files", "--full-name", "*/CLAUDE.md", "CLAUDE.md"],
195
+ {
196
+ encoding: "utf-8",
197
+ timeout: 5e3,
198
+ stdio: ["ignore", "pipe", "ignore"]
199
+ }
200
+ ).trim();
201
+ if (output) {
202
+ return output.split("\n").map((rel) => path.resolve(gitRoot, rel)).filter((p) => !excludePaths.has(p));
203
+ }
204
+ return [];
205
+ } catch {
206
+ }
207
+ }
208
+ try {
209
+ const output = execFileSync(
210
+ "find",
211
+ [
212
+ root,
213
+ "-name",
214
+ "CLAUDE.md",
215
+ "-not",
216
+ "-path",
217
+ "*/node_modules/*",
218
+ "-not",
219
+ "-path",
220
+ "*/.git/*"
221
+ ],
222
+ {
223
+ encoding: "utf-8",
224
+ timeout: 1e4,
225
+ stdio: ["ignore", "pipe", "ignore"]
226
+ }
227
+ ).trim();
228
+ if (output) {
229
+ return output.split("\n").map((p) => path.resolve(p)).filter((p) => !excludePaths.has(p));
230
+ }
231
+ } catch {
232
+ }
233
+ return [];
234
+ }
235
+ function buildInstruction(filePath) {
236
+ const content = readFileOrNull(filePath);
237
+ if (content === null) return null;
238
+ return { path: filePath, content, lineCount: content.split("\n").length };
239
+ }
240
+ function getManagedDir() {
241
+ if (process.platform === "darwin") {
242
+ return "/Library/Application Support/ClaudeCode";
243
+ }
244
+ return "/etc/claude-code";
245
+ }
246
+ function readConfig(cwd) {
247
+ const rawCwd = cwd ? path.resolve(cwd) : process.cwd();
248
+ const root = resolveGitRoot(rawCwd) ?? rawCwd;
249
+ const home = os.homedir();
250
+ const claudeHome = path.join(home, ".claude");
251
+ const dotClaude = path.join(root, ".claude");
252
+ const managedDir = getManagedDir();
253
+ let managed = null;
254
+ const managedSettings = readJsonOrNull(
255
+ path.join(managedDir, "managed-settings.json")
256
+ );
257
+ if (managedSettings) {
258
+ managed = {
259
+ settings: managedSettings,
260
+ hooks: parseHooks(managedSettings),
261
+ mcpServers: parseMcpServers(managedSettings),
262
+ permissions: parsePermissions(managedSettings),
263
+ commands: [],
264
+ agents: [],
265
+ rules: [],
266
+ skills: []
267
+ };
268
+ }
269
+ const user = buildLayer(
270
+ path.join(claudeHome, "settings.json"),
271
+ {
272
+ commands: path.join(claudeHome, "commands"),
273
+ rules: path.join(claudeHome, "rules"),
274
+ skills: path.join(claudeHome, "skills")
275
+ },
276
+ path.join(claudeHome, ".mcp.json")
277
+ );
278
+ let project = null;
279
+ try {
280
+ if (fs.statSync(dotClaude).isDirectory()) {
281
+ project = buildLayer(
282
+ path.join(dotClaude, "settings.json"),
283
+ {
284
+ commands: path.join(dotClaude, "commands"),
285
+ agents: path.join(dotClaude, "agents"),
286
+ rules: path.join(dotClaude, "rules"),
287
+ skills: path.join(dotClaude, "skills")
288
+ },
289
+ path.join(dotClaude, ".mcp.json")
290
+ );
291
+ }
292
+ } catch {
293
+ }
294
+ let projectLocal = null;
295
+ const localSettings = readJsonOrNull(
296
+ path.join(dotClaude, "settings.local.json")
297
+ );
298
+ if (localSettings) {
299
+ projectLocal = {
300
+ settings: localSettings,
301
+ hooks: parseHooks(localSettings),
302
+ mcpServers: parseMcpServers(localSettings),
303
+ permissions: parsePermissions(localSettings),
304
+ commands: [],
305
+ agents: [],
306
+ rules: [],
307
+ skills: []
308
+ };
309
+ }
310
+ const instructions = [];
311
+ const knownPaths = /* @__PURE__ */ new Set();
312
+ const instructionCandidates = [
313
+ path.join(managedDir, "CLAUDE.md"),
314
+ path.join(claudeHome, "CLAUDE.md"),
315
+ path.join(root, "CLAUDE.md"),
316
+ path.join(dotClaude, "CLAUDE.md"),
317
+ path.join(root, "AGENTS.md")
318
+ ];
319
+ for (const p of instructionCandidates) {
320
+ const resolved = path.resolve(p);
321
+ const inst = buildInstruction(resolved);
322
+ if (inst) {
323
+ instructions.push(inst);
324
+ knownPaths.add(resolved);
325
+ }
326
+ }
327
+ for (const p of findPerDirectoryClaudeMd(root, knownPaths)) {
328
+ const inst = buildInstruction(p);
329
+ if (inst) instructions.push(inst);
330
+ }
331
+ const pluginSet = /* @__PURE__ */ new Set();
332
+ const enabledPlugins = [];
333
+ for (const layer of [project, user]) {
334
+ for (const p of parseEnabledPlugins(layer?.settings ?? null)) {
335
+ const key = `${p.pluginName}@${p.marketplace}`;
336
+ if (!pluginSet.has(key)) {
337
+ pluginSet.add(key);
338
+ enabledPlugins.push(p);
339
+ }
340
+ }
341
+ }
342
+ return { managed, user, project, projectLocal, instructions, enabledPlugins };
343
+ }
344
+ function writeSettings(level, patch, cwd) {
345
+ const root = cwd ? path.resolve(cwd) : process.cwd();
346
+ const home = os.homedir();
347
+ let filePath;
348
+ switch (level) {
349
+ case "project":
350
+ filePath = path.join(root, ".claude", "settings.json");
351
+ break;
352
+ case "projectLocal":
353
+ filePath = path.join(root, ".claude", "settings.local.json");
354
+ break;
355
+ case "user":
356
+ filePath = path.join(home, ".claude", "settings.json");
357
+ break;
358
+ }
359
+ const existing = readJsonOrNull(filePath) ?? {};
360
+ const merged = { ...existing, ...patch };
361
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
362
+ fs.writeFileSync(filePath, `${JSON.stringify(merged, null, 2)}
363
+ `);
364
+ }
365
+ function writeFile(level, type, name, content, cwd) {
366
+ const root = cwd ? path.resolve(cwd) : process.cwd();
367
+ const home = os.homedir();
368
+ const base = level === "project" ? path.join(root, ".claude") : path.join(home, ".claude");
369
+ if (type === "skill") {
370
+ const dir = path.join(base, "skills", name);
371
+ fs.mkdirSync(dir, { recursive: true });
372
+ fs.writeFileSync(path.join(dir, "SKILL.md"), content);
373
+ } else {
374
+ const dir = path.join(base, `${type}s`);
375
+ fs.mkdirSync(dir, { recursive: true });
376
+ fs.writeFileSync(path.join(dir, `${name}.md`), content);
377
+ }
378
+ }
379
+
380
+ export {
381
+ resolveGitRoot,
382
+ isGitignored,
383
+ readConfig,
384
+ writeSettings,
385
+ writeFile
386
+ };
387
+ //# sourceMappingURL=chunk-3BUJ7URA.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 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}\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// 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 return { managed, user, project, projectLocal, instructions, enabledPlugins };\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":[]}
@@ -0,0 +1,158 @@
1
+ import {
2
+ getDb
3
+ } from "./chunk-DZ5HJFB4.js";
4
+ import {
5
+ config
6
+ } from "./chunk-K7YUPLES.js";
7
+
8
+ // src/db/pricing.ts
9
+ import fs from "fs";
10
+ import path from "path";
11
+ var PRICING_PATH = path.join(config.dataDir, "pricing.json");
12
+ var STALE_MS = 24 * 60 * 60 * 1e3;
13
+ var LITELLM_URL = "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json";
14
+ var FALLBACK_PRICING = {
15
+ "claude-opus-4-6": {
16
+ input: 5,
17
+ output: 25,
18
+ cache_read: 0.5,
19
+ cache_write: 6.25
20
+ },
21
+ "claude-sonnet-4-6": {
22
+ input: 3,
23
+ output: 15,
24
+ cache_read: 0.3,
25
+ cache_write: 3.75
26
+ },
27
+ "claude-haiku-4-5": {
28
+ input: 1,
29
+ output: 5,
30
+ cache_read: 0.1,
31
+ cache_write: 1.25
32
+ }
33
+ };
34
+ async function refreshPricing() {
35
+ try {
36
+ const res = await fetch(LITELLM_URL);
37
+ if (!res.ok) return null;
38
+ const data = await res.json();
39
+ if (!data || typeof data !== "object") return null;
40
+ const models = {};
41
+ for (const [modelId, entry] of Object.entries(data)) {
42
+ if (!entry.input_cost_per_token || !entry.output_cost_per_token) continue;
43
+ if (entry.mode && entry.mode !== "chat") continue;
44
+ const input = entry.input_cost_per_token * 1e6;
45
+ const output = entry.output_cost_per_token * 1e6;
46
+ if (input === 0 && output === 0) continue;
47
+ models[modelId] = {
48
+ input,
49
+ output,
50
+ cache_read: entry.cache_read_input_token_cost ? entry.cache_read_input_token_cost * 1e6 : 0,
51
+ cache_write: entry.cache_creation_input_token_cost ? entry.cache_creation_input_token_cost * 1e6 : 0
52
+ };
53
+ }
54
+ const cache = {
55
+ updated: (/* @__PURE__ */ new Date()).toISOString(),
56
+ models
57
+ };
58
+ fs.mkdirSync(path.dirname(PRICING_PATH), { recursive: true });
59
+ fs.writeFileSync(PRICING_PATH, `${JSON.stringify(cache, null, 2)}
60
+ `);
61
+ insertPricingChanges(models);
62
+ return cache;
63
+ } catch {
64
+ ensureFallbacks();
65
+ return null;
66
+ }
67
+ }
68
+ function readCurrentPrices(db) {
69
+ const rows = db.prepare(
70
+ `SELECT model_id, input_per_m, output_per_m, cache_read_per_m, cache_write_per_m
71
+ FROM model_pricing mp
72
+ WHERE updated_ms = (SELECT MAX(updated_ms) FROM model_pricing WHERE model_id = mp.model_id)`
73
+ ).all();
74
+ const map = /* @__PURE__ */ new Map();
75
+ for (const r of rows) {
76
+ map.set(r.model_id, {
77
+ input: r.input_per_m,
78
+ output: r.output_per_m,
79
+ cache_read: r.cache_read_per_m,
80
+ cache_write: r.cache_write_per_m
81
+ });
82
+ }
83
+ return map;
84
+ }
85
+ function insertPricingChanges(models) {
86
+ const db = getDb();
87
+ const now = Date.now();
88
+ const current = readCurrentPrices(db);
89
+ const insert = db.prepare(`
90
+ INSERT INTO model_pricing
91
+ (model_id, input_per_m, output_per_m, cache_read_per_m, cache_write_per_m, updated_ms)
92
+ VALUES (?, ?, ?, ?, ?, ?)
93
+ `);
94
+ let changed = 0;
95
+ const tx = db.transaction(() => {
96
+ for (const [modelId, pricing] of Object.entries(models)) {
97
+ const existing = current.get(modelId);
98
+ if (existing && existing.input === pricing.input && existing.output === pricing.output && existing.cache_read === pricing.cache_read && existing.cache_write === pricing.cache_write) {
99
+ continue;
100
+ }
101
+ insert.run(
102
+ modelId,
103
+ pricing.input,
104
+ pricing.output,
105
+ pricing.cache_read,
106
+ pricing.cache_write,
107
+ now
108
+ );
109
+ changed++;
110
+ }
111
+ });
112
+ tx();
113
+ return changed;
114
+ }
115
+ function ensureFallbacks() {
116
+ try {
117
+ insertPricingChanges(FALLBACK_PRICING);
118
+ } catch {
119
+ }
120
+ }
121
+ async function refreshIfStale() {
122
+ try {
123
+ const cache = (() => {
124
+ try {
125
+ return JSON.parse(fs.readFileSync(PRICING_PATH, "utf-8"));
126
+ } catch {
127
+ return null;
128
+ }
129
+ })();
130
+ if (cache) {
131
+ const age = Date.now() - new Date(cache.updated).getTime();
132
+ if (age < STALE_MS) return;
133
+ }
134
+ await refreshPricing();
135
+ } catch {
136
+ }
137
+ }
138
+ var COST_EXPR = `
139
+ tokens * COALESCE((
140
+ SELECT CASE token_type
141
+ WHEN 'input' THEN mp.input_per_m
142
+ WHEN 'output' THEN mp.output_per_m
143
+ WHEN 'cacheRead' THEN mp.cache_read_per_m
144
+ WHEN 'cacheWrite' THEN mp.cache_write_per_m
145
+ ELSE 0
146
+ END
147
+ FROM model_pricing mp
148
+ WHERE model LIKE mp.model_id || '%'
149
+ ORDER BY LENGTH(mp.model_id) DESC, mp.updated_ms DESC
150
+ LIMIT 1
151
+ ), 0) / 1000000.0`;
152
+
153
+ export {
154
+ refreshPricing,
155
+ refreshIfStale,
156
+ COST_EXPR
157
+ };
158
+ //# sourceMappingURL=chunk-3TZAKV3M.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/db/pricing.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { config } from \"../config.js\";\nimport { getDb } from \"./schema.js\";\n\nconst PRICING_PATH = path.join(config.dataDir, \"pricing.json\");\nconst STALE_MS = 24 * 60 * 60 * 1000; // 24 hours\nconst LITELLM_URL =\n \"https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json\";\n\ninterface ModelPricing {\n input: number;\n output: number;\n cache_read: number;\n cache_write: number;\n}\n\ninterface PricingCache {\n updated: string;\n models: Record<string, ModelPricing>;\n}\n\n/**\n * Hardcoded fallback pricing for common models (per million tokens).\n * Used when LiteLLM fetch fails or cache is empty.\n */\nconst FALLBACK_PRICING: Record<string, ModelPricing> = {\n \"claude-opus-4-6\": {\n input: 5,\n output: 25,\n cache_read: 0.5,\n cache_write: 6.25,\n },\n \"claude-sonnet-4-6\": {\n input: 3,\n output: 15,\n cache_read: 0.3,\n cache_write: 3.75,\n },\n \"claude-haiku-4-5\": {\n input: 1,\n output: 5,\n cache_read: 0.1,\n cache_write: 1.25,\n },\n};\n\ninterface LiteLLMEntry {\n input_cost_per_token?: number;\n output_cost_per_token?: number;\n cache_read_input_token_cost?: number;\n cache_creation_input_token_cost?: number;\n mode?: string;\n}\n\n/**\n * Fetch pricing from LiteLLM, save to disk cache, and upsert into SQLite.\n * Models are keyed by their LiteLLM ID (e.g. \"claude-opus-4-6\").\n */\nexport async function refreshPricing(): Promise<PricingCache | null> {\n try {\n const res = await fetch(LITELLM_URL);\n if (!res.ok) return null;\n\n const data = (await res.json()) as Record<string, LiteLLMEntry>;\n if (!data || typeof data !== \"object\") return null;\n\n const models: Record<string, ModelPricing> = {};\n for (const [modelId, entry] of Object.entries(data)) {\n if (!entry.input_cost_per_token || !entry.output_cost_per_token) continue;\n if (entry.mode && entry.mode !== \"chat\") continue;\n\n const input = entry.input_cost_per_token * 1_000_000;\n const output = entry.output_cost_per_token * 1_000_000;\n if (input === 0 && output === 0) continue;\n\n models[modelId] = {\n input,\n output,\n cache_read: entry.cache_read_input_token_cost\n ? entry.cache_read_input_token_cost * 1_000_000\n : 0,\n cache_write: entry.cache_creation_input_token_cost\n ? entry.cache_creation_input_token_cost * 1_000_000\n : 0,\n };\n }\n\n const cache: PricingCache = {\n updated: new Date().toISOString(),\n models,\n };\n\n // Save JSON cache for debugging/visibility\n fs.mkdirSync(path.dirname(PRICING_PATH), { recursive: true });\n fs.writeFileSync(PRICING_PATH, `${JSON.stringify(cache, null, 2)}\\n`);\n\n // Insert only changed prices (append-only time series)\n insertPricingChanges(models);\n\n return cache;\n } catch {\n // On fetch failure, ensure fallbacks are in the DB\n ensureFallbacks();\n return null;\n }\n}\n\n/**\n * Read the latest price for each model into a Map for diffing.\n * Uses the most recent row per model_id (highest updated_ms).\n */\nfunction readCurrentPrices(\n db: ReturnType<typeof getDb>,\n): Map<string, ModelPricing> {\n const rows = db\n .prepare(\n `SELECT model_id, input_per_m, output_per_m, cache_read_per_m, cache_write_per_m\n FROM model_pricing mp\n WHERE updated_ms = (SELECT MAX(updated_ms) FROM model_pricing WHERE model_id = mp.model_id)`,\n )\n .all() as Array<{\n model_id: string;\n input_per_m: number;\n output_per_m: number;\n cache_read_per_m: number;\n cache_write_per_m: number;\n }>;\n\n const map = new Map<string, ModelPricing>();\n for (const r of rows) {\n map.set(r.model_id, {\n input: r.input_per_m,\n output: r.output_per_m,\n cache_read: r.cache_read_per_m,\n cache_write: r.cache_write_per_m,\n });\n }\n return map;\n}\n\n/**\n * Insert only models whose pricing has changed.\n * Append-only: each insert is a price-change event forming a time series.\n */\nfunction insertPricingChanges(models: Record<string, ModelPricing>): number {\n const db = getDb();\n const now = Date.now();\n const current = readCurrentPrices(db);\n\n const insert = db.prepare(`\n INSERT INTO model_pricing\n (model_id, input_per_m, output_per_m, cache_read_per_m, cache_write_per_m, updated_ms)\n VALUES (?, ?, ?, ?, ?, ?)\n `);\n\n let changed = 0;\n const tx = db.transaction(() => {\n for (const [modelId, pricing] of Object.entries(models)) {\n const existing = current.get(modelId);\n if (\n existing &&\n existing.input === pricing.input &&\n existing.output === pricing.output &&\n existing.cache_read === pricing.cache_read &&\n existing.cache_write === pricing.cache_write\n ) {\n continue;\n }\n insert.run(\n modelId,\n pricing.input,\n pricing.output,\n pricing.cache_read,\n pricing.cache_write,\n now,\n );\n changed++;\n }\n });\n tx();\n return changed;\n}\n\n/**\n * Ensure fallback pricing exists in the DB for common models.\n * Called when LiteLLM fetch fails so cost queries still work.\n */\nfunction ensureFallbacks(): void {\n try {\n insertPricingChanges(FALLBACK_PRICING);\n } catch {\n // Non-blocking\n }\n}\n\n/** Refresh pricing if cache is missing or older than 24h. */\nexport async function refreshIfStale(): Promise<void> {\n try {\n const cache: PricingCache | null = (() => {\n try {\n return JSON.parse(fs.readFileSync(PRICING_PATH, \"utf-8\"));\n } catch {\n return null;\n }\n })();\n if (cache) {\n const age = Date.now() - new Date(cache.updated).getTime();\n if (age < STALE_MS) return;\n }\n await refreshPricing();\n } catch {\n // Non-blocking — never fail the hook\n }\n}\n\n/**\n * SQL expression that computes cost for a row with (model, token_type, tokens) columns.\n * Looks up the best matching model in the model_pricing table by longest prefix match.\n */\n/**\n * SQL expression that computes cost for a row with (model, token_type, tokens) columns.\n * Looks up the best matching model in the model_pricing table by longest prefix match,\n * using the most recent price entry (highest updated_ms) for that model.\n */\nexport const COST_EXPR = `\n tokens * COALESCE((\n SELECT CASE token_type\n WHEN 'input' THEN mp.input_per_m\n WHEN 'output' THEN mp.output_per_m\n WHEN 'cacheRead' THEN mp.cache_read_per_m\n WHEN 'cacheWrite' THEN mp.cache_write_per_m\n ELSE 0\n END\n FROM model_pricing mp\n WHERE model LIKE mp.model_id || '%'\n ORDER BY LENGTH(mp.model_id) DESC, mp.updated_ms DESC\n LIMIT 1\n ), 0) / 1000000.0`;\n"],"mappings":"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","names":[]}