@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,626 @@
1
+ import {
2
+ allTargets
3
+ } from "./chunk-QVK6VGCV.js";
4
+ import {
5
+ getDb
6
+ } from "./chunk-DZ5HJFB4.js";
7
+
8
+ // src/db/query.ts
9
+ function parseSince(since) {
10
+ if (!since) return null;
11
+ const match = since.match(/^(\d+)(h|d|m)$/);
12
+ if (match) {
13
+ const [, num, unit] = match;
14
+ const ms = unit === "h" ? 36e5 : unit === "d" ? 864e5 : 6e4;
15
+ return Date.now() - parseInt(num, 10) * ms;
16
+ }
17
+ const date = new Date(since);
18
+ return Number.isNaN(date.getTime()) ? null : date.getTime();
19
+ }
20
+ function toIso(ms) {
21
+ return new Date(ms).toISOString();
22
+ }
23
+ function buildOtelLogExprs() {
24
+ const defaultTs = "CAST(timestamp_ns / 1000000 AS INTEGER)";
25
+ const eventExprs = /* @__PURE__ */ new Set(["body"]);
26
+ const extraTsExprs = /* @__PURE__ */ new Set();
27
+ for (const target of allTargets()) {
28
+ const lf = target.otel?.logFields;
29
+ if (!lf) continue;
30
+ for (const expr of lf.eventTypeExprs ?? []) {
31
+ eventExprs.add(expr);
32
+ }
33
+ for (const expr of lf.timestampMsExprs ?? []) {
34
+ if (expr !== defaultTs) extraTsExprs.add(expr);
35
+ }
36
+ }
37
+ const eventExprArr = [...eventExprs];
38
+ const eventType = eventExprArr.length === 1 ? eventExprArr[0] : `COALESCE(${eventExprArr.join(", ")})`;
39
+ let timestampMs = defaultTs;
40
+ if (extraTsExprs.size > 0) {
41
+ const fallbackArr = [...extraTsExprs];
42
+ const fallbackExpr = fallbackArr.length === 1 ? fallbackArr[0] : `COALESCE(${fallbackArr.join(", ")})`;
43
+ timestampMs = `CASE WHEN timestamp_ns > 0 THEN ${defaultTs} ELSE ${fallbackExpr} END`;
44
+ }
45
+ return { eventType, timestampMs };
46
+ }
47
+ var _otelLogExprs = null;
48
+ function otelLogExprs() {
49
+ if (!_otelLogExprs) _otelLogExprs = buildOtelLogExprs();
50
+ return _otelLogExprs;
51
+ }
52
+ var SESSION_COST_SQL = `
53
+ COALESCE((
54
+ SELECT s.total_input_tokens * COALESCE(mp.input_per_m, 0) / 1000000.0
55
+ + s.total_output_tokens * COALESCE(mp.output_per_m, 0) / 1000000.0
56
+ + s.total_cache_read_tokens * COALESCE(mp.cache_read_per_m, 0) / 1000000.0
57
+ + s.total_cache_creation_tokens * COALESCE(mp.cache_write_per_m, 0) / 1000000.0
58
+ FROM model_pricing mp
59
+ WHERE s.model LIKE mp.model_id || '%'
60
+ ORDER BY LENGTH(mp.model_id) DESC, mp.updated_ms DESC
61
+ LIMIT 1
62
+ ), 0)`;
63
+ function listSessions(opts = {}) {
64
+ const db = getDb();
65
+ const limit = opts.limit ?? 20;
66
+ const sinceMs = parseSince(opts.since);
67
+ const conditions = [];
68
+ const params = [];
69
+ if (sinceMs) {
70
+ conditions.push("s.started_at_ms >= ?");
71
+ params.push(sinceMs);
72
+ }
73
+ const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
74
+ const sql = `
75
+ SELECT s.session_id, s.target, s.model, s.project,
76
+ s.started_at_ms, s.ended_at_ms, s.first_prompt, s.summary,
77
+ COALESCE(s.turn_count, 0) as turn_count,
78
+ COALESCE(s.message_count, 0) as message_count,
79
+ COALESCE(s.total_input_tokens, 0) as total_input_tokens,
80
+ COALESCE(s.total_output_tokens, 0) as total_output_tokens,
81
+ s.parent_session_id, s.relationship_type,
82
+ ${SESSION_COST_SQL} as total_cost
83
+ FROM sessions s
84
+ ${where}
85
+ ORDER BY s.started_at_ms DESC
86
+ LIMIT ?
87
+ `;
88
+ params.push(limit);
89
+ const rows = db.prepare(sql).all(...params);
90
+ const sessionIds = rows.map((r) => r.session_id);
91
+ const repoRows = sessionIds.length > 0 ? db.prepare(
92
+ `SELECT session_id, repository, git_user_name, git_user_email
93
+ FROM session_repositories
94
+ WHERE session_id IN (${sessionIds.map(() => "?").join(",")})`
95
+ ).all(...sessionIds) : [];
96
+ const reposBySession = /* @__PURE__ */ new Map();
97
+ for (const r of repoRows) {
98
+ const list = reposBySession.get(r.session_id) ?? [];
99
+ list.push(r);
100
+ reposBySession.set(r.session_id, list);
101
+ }
102
+ const sessions = rows.map((row) => ({
103
+ sessionId: row.session_id,
104
+ target: row.target,
105
+ model: row.model,
106
+ project: row.project,
107
+ startedAt: row.started_at_ms ? toIso(row.started_at_ms) : null,
108
+ endedAt: row.ended_at_ms ? toIso(row.ended_at_ms) : null,
109
+ firstPrompt: row.first_prompt,
110
+ turnCount: row.turn_count,
111
+ messageCount: row.message_count,
112
+ totalInputTokens: row.total_input_tokens,
113
+ totalOutputTokens: row.total_output_tokens,
114
+ totalCost: row.total_cost,
115
+ repositories: (reposBySession.get(row.session_id) ?? []).map((r) => ({
116
+ name: r.repository,
117
+ gitUserName: r.git_user_name,
118
+ gitUserEmail: r.git_user_email
119
+ })),
120
+ parentSessionId: row.parent_session_id,
121
+ relationshipType: row.relationship_type,
122
+ summary: row.summary
123
+ }));
124
+ return {
125
+ sessions,
126
+ totalCount: sessions.length,
127
+ source: "local"
128
+ };
129
+ }
130
+ function sessionTimeline(opts) {
131
+ const db = getDb();
132
+ const limit = opts.limit ?? 50;
133
+ const offset = opts.offset ?? 0;
134
+ const truncate = !opts.fullPayloads;
135
+ const sessionRow = db.prepare(
136
+ "SELECT session_id, target, model, project, parent_session_id, relationship_type FROM sessions WHERE session_id = ?"
137
+ ).get(opts.sessionId);
138
+ if (!sessionRow) {
139
+ return {
140
+ session: null,
141
+ messages: [],
142
+ totalMessages: 0,
143
+ hasMore: false,
144
+ source: "local"
145
+ };
146
+ }
147
+ const repoRows = db.prepare(
148
+ "SELECT repository, git_user_name, git_user_email FROM session_repositories WHERE session_id = ?"
149
+ ).all(opts.sessionId);
150
+ const childRows = db.prepare(
151
+ "SELECT session_id, relationship_type, model, COALESCE(turn_count, 0) as turn_count, first_prompt, started_at_ms FROM sessions WHERE parent_session_id = ?"
152
+ ).all(opts.sessionId);
153
+ const childSessions = childRows.map((r) => ({
154
+ sessionId: r.session_id,
155
+ relationshipType: r.relationship_type,
156
+ model: r.model,
157
+ turnCount: r.turn_count,
158
+ firstPrompt: r.first_prompt,
159
+ startedAtMs: r.started_at_ms
160
+ }));
161
+ const totalMessages = db.prepare("SELECT COUNT(*) as c FROM messages WHERE session_id = ?").get(opts.sessionId).c;
162
+ const contentCol = truncate ? "SUBSTR(m.content, 1, 500)" : "m.content";
163
+ const msgRows = db.prepare(
164
+ `SELECT m.id, m.ordinal, m.role, ${contentCol} as content, m.timestamp_ms,
165
+ m.model, m.is_system, m.has_thinking, m.has_tool_use,
166
+ m.content_length, m.uuid, m.parent_uuid,
167
+ m.token_usage, m.context_tokens, m.output_tokens
168
+ FROM messages m
169
+ WHERE m.session_id = ?
170
+ ORDER BY m.ordinal ASC
171
+ LIMIT ? OFFSET ?`
172
+ ).all(opts.sessionId, limit, offset);
173
+ const msgIds = msgRows.map((m) => m.id);
174
+ const tcRows = msgIds.length > 0 ? db.prepare(
175
+ `SELECT tc.message_id, tc.tool_name, tc.category, tc.tool_use_id,
176
+ tc.input_json, tc.skill_name, tc.result_content_length,
177
+ tc.duration_ms, tc.subagent_session_id
178
+ FROM tool_calls tc
179
+ WHERE tc.message_id IN (${msgIds.map(() => "?").join(",")})
180
+ ORDER BY tc.id ASC`
181
+ ).all(...msgIds) : [];
182
+ const subagentIds = [
183
+ ...new Set(
184
+ tcRows.map((tc) => tc.subagent_session_id).filter(Boolean)
185
+ )
186
+ ];
187
+ const subagentMap = /* @__PURE__ */ new Map();
188
+ if (subagentIds.length > 0) {
189
+ const subRows = db.prepare(
190
+ `SELECT session_id, model, COALESCE(turn_count, 0) as turn_count, first_prompt
191
+ FROM sessions WHERE session_id IN (${subagentIds.map(() => "?").join(",")})`
192
+ ).all(...subagentIds);
193
+ for (const r of subRows) {
194
+ subagentMap.set(r.session_id, {
195
+ model: r.model,
196
+ turn_count: r.turn_count,
197
+ first_prompt: r.first_prompt
198
+ });
199
+ }
200
+ }
201
+ const tcByMessage = /* @__PURE__ */ new Map();
202
+ for (const tc of tcRows) {
203
+ const list = tcByMessage.get(tc.message_id) ?? [];
204
+ list.push(tc);
205
+ tcByMessage.set(tc.message_id, list);
206
+ }
207
+ const messages = msgRows.map((m) => {
208
+ const tcs = tcByMessage.get(m.id) ?? [];
209
+ return {
210
+ id: m.id,
211
+ ordinal: m.ordinal,
212
+ role: m.role,
213
+ content: m.content,
214
+ timestampMs: m.timestamp_ms,
215
+ model: m.model,
216
+ isSystem: m.is_system === 1,
217
+ hasThinking: m.has_thinking === 1,
218
+ hasToolUse: m.has_tool_use === 1,
219
+ contentLength: m.content_length,
220
+ uuid: m.uuid,
221
+ parentUuid: m.parent_uuid,
222
+ tokenUsage: m.token_usage,
223
+ contextTokens: m.context_tokens,
224
+ outputTokens: m.output_tokens,
225
+ toolCalls: tcs.map((tc) => {
226
+ const sub = tc.subagent_session_id ? subagentMap.get(tc.subagent_session_id) : void 0;
227
+ return {
228
+ toolName: tc.tool_name,
229
+ category: tc.category,
230
+ toolUseId: tc.tool_use_id,
231
+ inputJson: truncate ? tc.input_json?.slice(0, 500) ?? null : tc.input_json,
232
+ skillName: tc.skill_name,
233
+ resultContentLength: tc.result_content_length,
234
+ durationMs: tc.duration_ms,
235
+ subagentSessionId: tc.subagent_session_id,
236
+ subagent: sub ? {
237
+ sessionId: tc.subagent_session_id,
238
+ model: sub.model,
239
+ turnCount: sub.turn_count,
240
+ firstPrompt: sub.first_prompt
241
+ } : null
242
+ };
243
+ })
244
+ };
245
+ });
246
+ return {
247
+ session: {
248
+ sessionId: opts.sessionId,
249
+ target: sessionRow.target,
250
+ model: sessionRow.model,
251
+ project: sessionRow.project,
252
+ parentSessionId: sessionRow.parent_session_id,
253
+ relationshipType: sessionRow.relationship_type,
254
+ repositories: repoRows.map((r) => ({
255
+ name: r.repository,
256
+ gitUserName: r.git_user_name,
257
+ gitUserEmail: r.git_user_email
258
+ })),
259
+ childSessions
260
+ },
261
+ messages,
262
+ totalMessages,
263
+ hasMore: offset + limit < totalMessages,
264
+ source: "local"
265
+ };
266
+ }
267
+ function costBreakdown(opts = {}) {
268
+ const db = getDb();
269
+ const sinceMs = parseSince(opts.since);
270
+ const groupBy = opts.groupBy ?? "session";
271
+ const conditions = [];
272
+ const params = [];
273
+ if (sinceMs) {
274
+ conditions.push("s.started_at_ms >= ?");
275
+ params.push(sinceMs);
276
+ }
277
+ const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
278
+ let groupExpr;
279
+ let selectExpr;
280
+ if (groupBy === "day") {
281
+ groupExpr = "date(s.started_at_ms / 1000, 'unixepoch')";
282
+ selectExpr = `${groupExpr} as group_key`;
283
+ } else if (groupBy === "model") {
284
+ groupExpr = "COALESCE(s.model, 'unknown')";
285
+ selectExpr = `${groupExpr} as group_key`;
286
+ } else {
287
+ groupExpr = "s.session_id";
288
+ selectExpr = "s.session_id as group_key";
289
+ }
290
+ const sql = `
291
+ SELECT ${selectExpr},
292
+ SUM(COALESCE(s.total_input_tokens, 0) + COALESCE(s.total_cache_read_tokens, 0) + COALESCE(s.total_cache_creation_tokens, 0)) as input_tokens,
293
+ SUM(COALESCE(s.total_output_tokens, 0)) as output_tokens,
294
+ SUM(COALESCE(s.total_input_tokens, 0) + COALESCE(s.total_output_tokens, 0) + COALESCE(s.total_cache_read_tokens, 0) + COALESCE(s.total_cache_creation_tokens, 0)) as total_tokens,
295
+ SUM(${SESSION_COST_SQL}) as total_cost,
296
+ COUNT(DISTINCT s.session_id) as session_count
297
+ FROM sessions s
298
+ ${where}
299
+ GROUP BY ${groupExpr}
300
+ ORDER BY ${groupBy === "day" ? "group_key DESC" : "total_tokens DESC"}
301
+ `;
302
+ const rows = db.prepare(sql).all(...params);
303
+ const groups = rows.map((row) => ({
304
+ key: row.group_key,
305
+ inputTokens: row.input_tokens,
306
+ outputTokens: row.output_tokens,
307
+ totalTokens: row.total_tokens,
308
+ totalCost: row.total_cost,
309
+ sessionCount: row.session_count
310
+ }));
311
+ const totals = {
312
+ inputTokens: groups.reduce((sum, g) => sum + g.inputTokens, 0),
313
+ outputTokens: groups.reduce((sum, g) => sum + g.outputTokens, 0),
314
+ totalTokens: groups.reduce((sum, g) => sum + g.totalTokens, 0),
315
+ totalCost: groups.reduce((sum, g) => sum + g.totalCost, 0)
316
+ };
317
+ return { groups, totals, groupBy, source: "local" };
318
+ }
319
+ function search(opts) {
320
+ const db = getDb();
321
+ const limit = opts.limit ?? 20;
322
+ const offset = opts.offset ?? 0;
323
+ const sinceMs = parseSince(opts.since);
324
+ const pattern = `%${opts.query}%`;
325
+ const truncate = !opts.fullPayloads;
326
+ const hookPayloadCol = truncate ? "SUBSTR(decompress(h.payload), 1, 500)" : "decompress(h.payload)";
327
+ const hookConditions = [];
328
+ const hookParams = [];
329
+ hookConditions.push(
330
+ "(h.id IN (SELECT rowid FROM hook_events_fts WHERE hook_events_fts MATCH ?) OR h.tool_name LIKE ? OR h.event_type LIKE ?)"
331
+ );
332
+ hookParams.push(opts.query, pattern, pattern);
333
+ if (opts.eventTypes?.length) {
334
+ hookConditions.push(
335
+ `h.event_type IN (${opts.eventTypes.map(() => "?").join(",")})`
336
+ );
337
+ hookParams.push(...opts.eventTypes);
338
+ }
339
+ if (sinceMs) {
340
+ hookConditions.push("h.timestamp_ms >= ?");
341
+ hookParams.push(sinceMs);
342
+ }
343
+ const hookSql = `
344
+ SELECT 'hook' as source, h.id, h.session_id, h.event_type, h.timestamp_ms,
345
+ h.tool_name, h.cwd, ${hookPayloadCol} as payload
346
+ FROM hook_events h
347
+ WHERE ${hookConditions.join(" AND ")}
348
+ `;
349
+ const otelConditions = ["(o.body LIKE ? OR o.attributes LIKE ?)"];
350
+ const otelParams = [pattern, pattern];
351
+ if (sinceMs) {
352
+ otelConditions.push("CAST(o.timestamp_ns / 1000000 AS INTEGER) >= ?");
353
+ otelParams.push(sinceMs);
354
+ }
355
+ const otelAttrsCol = truncate ? "SUBSTR(o.attributes, 1, 500)" : "o.attributes";
356
+ const otelEventTypeQ = otelLogExprs().eventType.replace(
357
+ /\b(body|timestamp_ns|attributes)\b/g,
358
+ "o.$1"
359
+ );
360
+ const otelTimestampMsQ = otelLogExprs().timestampMs.replace(
361
+ /\b(body|timestamp_ns|attributes)\b/g,
362
+ "o.$1"
363
+ );
364
+ const otelSql = `
365
+ SELECT 'otel' as source, o.id, o.session_id,
366
+ ${otelEventTypeQ} as event_type,
367
+ ${otelTimestampMsQ} as timestamp_ms,
368
+ NULL as tool_name, NULL as cwd, ${otelAttrsCol} as payload
369
+ FROM otel_logs o
370
+ WHERE ${otelConditions.join(" AND ")}
371
+ `;
372
+ const msgContentCol = truncate ? "SUBSTR(m.content, 1, 500)" : "m.content";
373
+ const msgConditions = [
374
+ "m.id IN (SELECT rowid FROM messages_fts WHERE messages_fts MATCH ?)"
375
+ ];
376
+ const msgParams = [opts.query];
377
+ if (sinceMs) {
378
+ msgConditions.push("m.timestamp_ms >= ?");
379
+ msgParams.push(sinceMs);
380
+ }
381
+ const msgSql = `
382
+ SELECT 'message' as source, m.id, m.session_id,
383
+ m.role as event_type, m.timestamp_ms,
384
+ NULL as tool_name, NULL as cwd, ${msgContentCol} as payload
385
+ FROM messages m
386
+ WHERE ${msgConditions.join(" AND ")}
387
+ `;
388
+ const summaryConditions = [
389
+ "s.summary IS NOT NULL",
390
+ "s.summary LIKE ?"
391
+ ];
392
+ const summaryParams = [pattern];
393
+ if (sinceMs) {
394
+ summaryConditions.push("s.started_at_ms >= ?");
395
+ summaryParams.push(sinceMs);
396
+ }
397
+ const summarySql = `
398
+ SELECT 'summary' as source, s.session_id as id, s.session_id,
399
+ 'summary' as event_type, s.started_at_ms as timestamp_ms,
400
+ NULL as tool_name, NULL as cwd, SUBSTR(s.summary, 1, 500) as payload
401
+ FROM sessions s
402
+ WHERE ${summaryConditions.join(" AND ")}
403
+ `;
404
+ const countSql = `SELECT COUNT(*) as total FROM (${hookSql} UNION ALL ${otelSql} UNION ALL ${msgSql} UNION ALL ${summarySql})`;
405
+ const total = db.prepare(countSql).get(...hookParams, ...otelParams, ...msgParams, ...summaryParams).total;
406
+ const sql = `
407
+ SELECT * FROM (${hookSql} UNION ALL ${otelSql} UNION ALL ${msgSql} UNION ALL ${summarySql})
408
+ ORDER BY timestamp_ms DESC
409
+ LIMIT ? OFFSET ?
410
+ `;
411
+ const rows = db.prepare(sql).all(
412
+ ...hookParams,
413
+ ...otelParams,
414
+ ...msgParams,
415
+ ...summaryParams,
416
+ limit,
417
+ offset
418
+ );
419
+ const results = rows.map((row) => {
420
+ const snippet = row.payload ?? row.event_type ?? "";
421
+ const matchType = row.source === "summary" ? "summary" : row.source === "message" ? "message" : row.event_type === "UserPromptSubmit" ? "prompt" : row.tool_name ? "tool_use" : "event";
422
+ return {
423
+ sessionId: row.session_id,
424
+ timestamp: toIso(row.timestamp_ms),
425
+ matchType,
426
+ matchSnippet: typeof snippet === "string" ? snippet.slice(0, 300) : String(snippet).slice(0, 300),
427
+ eventType: row.event_type,
428
+ toolName: row.tool_name
429
+ };
430
+ });
431
+ return {
432
+ results,
433
+ totalMatches: total,
434
+ query: opts.query,
435
+ source: "local"
436
+ };
437
+ }
438
+ function activitySummary(opts = {}) {
439
+ const db = getDb();
440
+ const sinceMs = parseSince(opts.since ?? "24h") ?? Date.now() - 864e5;
441
+ const now = Date.now();
442
+ const rawSessions = db.prepare(
443
+ `SELECT s.session_id, s.model, s.project,
444
+ s.started_at_ms, s.ended_at_ms,
445
+ COALESCE(s.total_input_tokens, 0) + COALESCE(s.total_output_tokens, 0) +
446
+ COALESCE(s.total_cache_read_tokens, 0) + COALESCE(s.total_cache_creation_tokens, 0) as total_tokens,
447
+ ${SESSION_COST_SQL} as total_cost
448
+ FROM sessions s
449
+ WHERE s.started_at_ms >= ?
450
+ ORDER BY s.started_at_ms ASC`
451
+ ).all(sinceMs);
452
+ let totalCost = 0;
453
+ let totalTokens = 0;
454
+ const sessions = [];
455
+ for (const s of rawSessions) {
456
+ totalCost += s.total_cost;
457
+ totalTokens += s.total_tokens;
458
+ const prompts = db.prepare(
459
+ "SELECT SUBSTR(content, 1, 100) as prompt FROM messages WHERE session_id = ? AND role = 'user' AND is_system = 0 ORDER BY ordinal ASC LIMIT 10"
460
+ ).all(s.session_id);
461
+ const tools = db.prepare(
462
+ "SELECT tool_name, COUNT(*) as count FROM tool_calls WHERE session_id = ? GROUP BY tool_name ORDER BY count DESC"
463
+ ).all(s.session_id);
464
+ const fileRows = db.prepare(
465
+ "SELECT DISTINCT json_extract(input_json, '$.file_path') as file_path FROM tool_calls WHERE session_id = ? AND tool_name IN ('Write', 'Edit') AND input_json IS NOT NULL"
466
+ ).all(s.session_id);
467
+ const repos = db.prepare(
468
+ "SELECT repository, git_user_name, git_user_email FROM session_repositories WHERE session_id = ?"
469
+ ).all(s.session_id);
470
+ const durationMs = s.started_at_ms && s.ended_at_ms ? s.ended_at_ms - s.started_at_ms : 0;
471
+ sessions.push({
472
+ sessionId: s.session_id,
473
+ startedAt: s.started_at_ms ? toIso(s.started_at_ms) : null,
474
+ durationMinutes: Math.round(durationMs / 6e4),
475
+ model: s.model,
476
+ project: s.project,
477
+ repositories: repos.map((r) => ({
478
+ name: r.repository,
479
+ gitUserName: r.git_user_name,
480
+ gitUserEmail: r.git_user_email
481
+ })),
482
+ userPrompts: prompts.map((p) => p.prompt),
483
+ toolsUsed: tools.map((t) => ({ tool: t.tool_name, count: t.count })),
484
+ filesModified: fileRows.map((f) => f.file_path).filter(Boolean),
485
+ totalCost: s.total_cost
486
+ });
487
+ }
488
+ const topTools = db.prepare(
489
+ `SELECT tc.tool_name, COUNT(*) as count
490
+ FROM tool_calls tc
491
+ INNER JOIN sessions s ON tc.session_id = s.session_id
492
+ WHERE s.started_at_ms >= ?
493
+ GROUP BY tc.tool_name ORDER BY count DESC LIMIT 10`
494
+ ).all(sinceMs);
495
+ return {
496
+ period: {
497
+ since: toIso(sinceMs),
498
+ until: toIso(now)
499
+ },
500
+ totalSessions: rawSessions.length,
501
+ totalTokens,
502
+ totalCost,
503
+ topTools: topTools.map((t) => ({ tool: t.tool_name, count: t.count })),
504
+ sessions,
505
+ source: "local"
506
+ };
507
+ }
508
+ function listPlans(opts = {}) {
509
+ const db = getDb();
510
+ const limit = opts.limit ?? 20;
511
+ const sinceMs = parseSince(opts.since);
512
+ const conditions = [
513
+ "tool_name = 'ExitPlanMode'",
514
+ "event_type = 'PreToolUse'"
515
+ ];
516
+ const params = [];
517
+ if (opts.session_id) {
518
+ conditions.push("session_id = ?");
519
+ params.push(opts.session_id);
520
+ }
521
+ if (sinceMs) {
522
+ conditions.push("timestamp_ms >= ?");
523
+ params.push(sinceMs);
524
+ }
525
+ const sql = `
526
+ SELECT id, session_id, timestamp_ms, plan, allowed_prompts
527
+ FROM hook_events
528
+ WHERE ${conditions.join(" AND ")}
529
+ ORDER BY timestamp_ms DESC
530
+ LIMIT ?
531
+ `;
532
+ params.push(limit);
533
+ const rows = db.prepare(sql).all(...params);
534
+ return rows.map((r) => ({
535
+ id: r.id,
536
+ session_id: r.session_id,
537
+ timestamp: toIso(r.timestamp_ms),
538
+ plan: r.plan,
539
+ allowed_prompts: r.allowed_prompts ? JSON.parse(r.allowed_prompts) : null
540
+ }));
541
+ }
542
+ function print(opts) {
543
+ const db = getDb();
544
+ if (opts.source === "hook") {
545
+ const sql2 = `
546
+ SELECT 'hook' as source, id, session_id, event_type, timestamp_ms,
547
+ tool_name, cwd, user_prompt, file_path, command, plan,
548
+ tool_result, allowed_prompts, decompress(payload) as payload
549
+ FROM hook_events
550
+ WHERE id = ?
551
+ `;
552
+ return db.prepare(sql2).get(opts.id) ?? null;
553
+ }
554
+ if (opts.source === "message") {
555
+ const msg = db.prepare(
556
+ `SELECT m.id, m.session_id, m.ordinal, m.role, m.content, m.timestamp_ms,
557
+ m.has_thinking, m.has_tool_use, m.content_length, m.is_system,
558
+ m.model, m.token_usage, m.context_tokens, m.output_tokens,
559
+ m.uuid, m.parent_uuid
560
+ FROM messages m WHERE m.id = ?`
561
+ ).get(opts.id);
562
+ if (!msg) return null;
563
+ const toolCalls = db.prepare(
564
+ `SELECT tool_name, category, tool_use_id, input_json, skill_name,
565
+ result_content_length, duration_ms, subagent_session_id
566
+ FROM tool_calls WHERE message_id = ?`
567
+ ).all(opts.id);
568
+ return { source: "message", ...msg, tool_calls: toolCalls };
569
+ }
570
+ const sql = `
571
+ SELECT 'otel' as source, id, session_id, ${otelLogExprs().eventType} as event_type,
572
+ ${otelLogExprs().timestampMs} as timestamp_ms,
573
+ NULL as tool_name, NULL as cwd, attributes, severity_text,
574
+ ${otelLogExprs().eventType} as body
575
+ FROM otel_logs
576
+ WHERE id = ?
577
+ `;
578
+ return db.prepare(sql).get(opts.id) ?? null;
579
+ }
580
+ function rawQuery(sql) {
581
+ const db = getDb();
582
+ const trimmed = sql.trim().toUpperCase();
583
+ if (!trimmed.startsWith("SELECT") && !trimmed.startsWith("WITH") && !trimmed.startsWith("PRAGMA")) {
584
+ throw new Error("Only SELECT, WITH, and PRAGMA statements are allowed");
585
+ }
586
+ if (!trimmed.startsWith("PRAGMA") && !trimmed.includes("LIMIT")) {
587
+ sql = `${sql.trimEnd().replace(/;$/, "")} LIMIT 1000`;
588
+ }
589
+ return db.prepare(sql).all();
590
+ }
591
+ function dbStats() {
592
+ const db = getDb();
593
+ const logs = db.prepare("SELECT COUNT(*) as count FROM otel_logs").get();
594
+ const metrics = db.prepare("SELECT COUNT(*) as count FROM otel_metrics").get();
595
+ const hooks = db.prepare("SELECT COUNT(*) as count FROM hook_events").get();
596
+ const spans = db.prepare("SELECT COUNT(*) as count FROM otel_spans").get();
597
+ const sessions = db.prepare("SELECT COUNT(*) as count FROM sessions").get();
598
+ const scannerTurns = db.prepare("SELECT COUNT(*) as count FROM scanner_turns").get();
599
+ const scannerEvents = db.prepare("SELECT COUNT(*) as count FROM scanner_events").get();
600
+ const messages = db.prepare("SELECT COUNT(*) as count FROM messages").get();
601
+ const toolCalls = db.prepare("SELECT COUNT(*) as count FROM tool_calls").get();
602
+ return {
603
+ sessions: sessions.count,
604
+ messages: messages.count,
605
+ tool_calls: toolCalls.count,
606
+ scanner_turns: scannerTurns.count,
607
+ scanner_events: scannerEvents.count,
608
+ hook_events: hooks.count,
609
+ otel_logs: logs.count,
610
+ otel_metrics: metrics.count,
611
+ otel_spans: spans.count
612
+ };
613
+ }
614
+
615
+ export {
616
+ listSessions,
617
+ sessionTimeline,
618
+ costBreakdown,
619
+ search,
620
+ activitySummary,
621
+ listPlans,
622
+ print,
623
+ rawQuery,
624
+ dbStats
625
+ };
626
+ //# sourceMappingURL=chunk-LWXF7YRG.js.map