@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.
- package/.claude-plugin/plugin.json +10 -0
- package/LICENSE +5 -0
- package/README.md +363 -0
- package/bin/hook-handler +3 -0
- package/bin/mcp-server +3 -0
- package/bin/panopticon +3 -0
- package/bin/proxy +3 -0
- package/bin/server +3 -0
- package/dist/api/client.d.ts +67 -0
- package/dist/api/client.js +48 -0
- package/dist/api/client.js.map +1 -0
- package/dist/chunk-3BUJ7URA.js +387 -0
- package/dist/chunk-3BUJ7URA.js.map +1 -0
- package/dist/chunk-3TZAKV3M.js +158 -0
- package/dist/chunk-3TZAKV3M.js.map +1 -0
- package/dist/chunk-4SM2H22C.js +169 -0
- package/dist/chunk-4SM2H22C.js.map +1 -0
- package/dist/chunk-7Q3BJMLG.js +62 -0
- package/dist/chunk-7Q3BJMLG.js.map +1 -0
- package/dist/chunk-BVOE7A2Z.js +412 -0
- package/dist/chunk-BVOE7A2Z.js.map +1 -0
- package/dist/chunk-CF4GPWLI.js +170 -0
- package/dist/chunk-CF4GPWLI.js.map +1 -0
- package/dist/chunk-DZ5HJFB4.js +467 -0
- package/dist/chunk-DZ5HJFB4.js.map +1 -0
- package/dist/chunk-HQCY722C.js +428 -0
- package/dist/chunk-HQCY722C.js.map +1 -0
- package/dist/chunk-HRCEIYKU.js +134 -0
- package/dist/chunk-HRCEIYKU.js.map +1 -0
- package/dist/chunk-K7YUPLES.js +76 -0
- package/dist/chunk-K7YUPLES.js.map +1 -0
- package/dist/chunk-L7G27XWF.js +130 -0
- package/dist/chunk-L7G27XWF.js.map +1 -0
- package/dist/chunk-LWXF7YRG.js +626 -0
- package/dist/chunk-LWXF7YRG.js.map +1 -0
- package/dist/chunk-NXH7AONS.js +1120 -0
- package/dist/chunk-NXH7AONS.js.map +1 -0
- package/dist/chunk-QK5442ZP.js +55 -0
- package/dist/chunk-QK5442ZP.js.map +1 -0
- package/dist/chunk-QVK6VGCV.js +1703 -0
- package/dist/chunk-QVK6VGCV.js.map +1 -0
- package/dist/chunk-RX2RXHBH.js +1699 -0
- package/dist/chunk-RX2RXHBH.js.map +1 -0
- package/dist/chunk-SEXU2WYG.js +788 -0
- package/dist/chunk-SEXU2WYG.js.map +1 -0
- package/dist/chunk-SUGSQ4YI.js +264 -0
- package/dist/chunk-SUGSQ4YI.js.map +1 -0
- package/dist/chunk-TGXFVAID.js +138 -0
- package/dist/chunk-TGXFVAID.js.map +1 -0
- package/dist/chunk-WLBNFVIG.js +447 -0
- package/dist/chunk-WLBNFVIG.js.map +1 -0
- package/dist/chunk-XLTCUH5A.js +1072 -0
- package/dist/chunk-XLTCUH5A.js.map +1 -0
- package/dist/chunk-YVRWVDIA.js +146 -0
- package/dist/chunk-YVRWVDIA.js.map +1 -0
- package/dist/chunk-ZEC4LRKS.js +176 -0
- package/dist/chunk-ZEC4LRKS.js.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +1084 -0
- package/dist/cli.js.map +1 -0
- package/dist/config-NwoZC-GM.d.ts +20 -0
- package/dist/db.d.ts +46 -0
- package/dist/db.js +15 -0
- package/dist/db.js.map +1 -0
- package/dist/doctor.d.ts +37 -0
- package/dist/doctor.js +14 -0
- package/dist/doctor.js.map +1 -0
- package/dist/hooks/handler.d.ts +23 -0
- package/dist/hooks/handler.js +295 -0
- package/dist/hooks/handler.js.map +1 -0
- package/dist/index.d.ts +57 -0
- package/dist/index.js +101 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/server.d.ts +1 -0
- package/dist/mcp/server.js +243 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/otlp/server.d.ts +7 -0
- package/dist/otlp/server.js +17 -0
- package/dist/otlp/server.js.map +1 -0
- package/dist/permissions.d.ts +33 -0
- package/dist/permissions.js +14 -0
- package/dist/permissions.js.map +1 -0
- package/dist/pricing.d.ts +29 -0
- package/dist/pricing.js +13 -0
- package/dist/pricing.js.map +1 -0
- package/dist/proxy/server.d.ts +10 -0
- package/dist/proxy/server.js +20 -0
- package/dist/proxy/server.js.map +1 -0
- package/dist/prune.d.ts +18 -0
- package/dist/prune.js +13 -0
- package/dist/prune.js.map +1 -0
- package/dist/query.d.ts +56 -0
- package/dist/query.js +27 -0
- package/dist/query.js.map +1 -0
- package/dist/reparse-636YZCE3.js +14 -0
- package/dist/reparse-636YZCE3.js.map +1 -0
- package/dist/repo.d.ts +17 -0
- package/dist/repo.js +9 -0
- package/dist/repo.js.map +1 -0
- package/dist/scanner.d.ts +73 -0
- package/dist/scanner.js +15 -0
- package/dist/scanner.js.map +1 -0
- package/dist/sdk.d.ts +82 -0
- package/dist/sdk.js +208 -0
- package/dist/sdk.js.map +1 -0
- package/dist/server.d.ts +5 -0
- package/dist/server.js +25 -0
- package/dist/server.js.map +1 -0
- package/dist/setup.d.ts +35 -0
- package/dist/setup.js +19 -0
- package/dist/setup.js.map +1 -0
- package/dist/sync/index.d.ts +29 -0
- package/dist/sync/index.js +32 -0
- package/dist/sync/index.js.map +1 -0
- package/dist/targets.d.ts +279 -0
- package/dist/targets.js +20 -0
- package/dist/targets.js.map +1 -0
- package/dist/types-D-MYCBol.d.ts +128 -0
- package/dist/types.d.ts +164 -0
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -0
- package/hooks/hooks.json +274 -0
- package/package.json +124 -0
- 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
|