zozul-cli 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 (139) hide show
  1. package/.env.example +44 -0
  2. package/.github/workflows/publish.yml +26 -0
  3. package/DEVELOPMENT.md +288 -0
  4. package/LICENSE +201 -0
  5. package/README.md +178 -0
  6. package/dist/cli/commands.d.ts +3 -0
  7. package/dist/cli/commands.d.ts.map +1 -0
  8. package/dist/cli/commands.js +307 -0
  9. package/dist/cli/commands.js.map +1 -0
  10. package/dist/cli/format.d.ts +5 -0
  11. package/dist/cli/format.d.ts.map +1 -0
  12. package/dist/cli/format.js +115 -0
  13. package/dist/cli/format.js.map +1 -0
  14. package/dist/context/index.d.ts +8 -0
  15. package/dist/context/index.d.ts.map +1 -0
  16. package/dist/context/index.js +37 -0
  17. package/dist/context/index.js.map +1 -0
  18. package/dist/dashboard/html.d.ts +17 -0
  19. package/dist/dashboard/html.d.ts.map +1 -0
  20. package/dist/dashboard/html.js +79 -0
  21. package/dist/dashboard/html.js.map +1 -0
  22. package/dist/dashboard/index.html +1245 -0
  23. package/dist/hooks/config.d.ts +19 -0
  24. package/dist/hooks/config.d.ts.map +1 -0
  25. package/dist/hooks/config.js +106 -0
  26. package/dist/hooks/config.js.map +1 -0
  27. package/dist/hooks/git.d.ts +6 -0
  28. package/dist/hooks/git.d.ts.map +1 -0
  29. package/dist/hooks/git.js +73 -0
  30. package/dist/hooks/git.js.map +1 -0
  31. package/dist/hooks/index.d.ts +4 -0
  32. package/dist/hooks/index.d.ts.map +1 -0
  33. package/dist/hooks/index.js +3 -0
  34. package/dist/hooks/index.js.map +1 -0
  35. package/dist/hooks/server.d.ts +16 -0
  36. package/dist/hooks/server.d.ts.map +1 -0
  37. package/dist/hooks/server.js +349 -0
  38. package/dist/hooks/server.js.map +1 -0
  39. package/dist/index.d.ts +3 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +6 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/otel/config.d.ts +36 -0
  44. package/dist/otel/config.d.ts.map +1 -0
  45. package/dist/otel/config.js +109 -0
  46. package/dist/otel/config.js.map +1 -0
  47. package/dist/otel/index.d.ts +4 -0
  48. package/dist/otel/index.d.ts.map +1 -0
  49. package/dist/otel/index.js +3 -0
  50. package/dist/otel/index.js.map +1 -0
  51. package/dist/otel/receiver.d.ts +10 -0
  52. package/dist/otel/receiver.d.ts.map +1 -0
  53. package/dist/otel/receiver.js +155 -0
  54. package/dist/otel/receiver.js.map +1 -0
  55. package/dist/parser/index.d.ts +4 -0
  56. package/dist/parser/index.d.ts.map +1 -0
  57. package/dist/parser/index.js +3 -0
  58. package/dist/parser/index.js.map +1 -0
  59. package/dist/parser/ingest.d.ts +20 -0
  60. package/dist/parser/ingest.d.ts.map +1 -0
  61. package/dist/parser/ingest.js +98 -0
  62. package/dist/parser/ingest.js.map +1 -0
  63. package/dist/parser/jsonl.d.ts +14 -0
  64. package/dist/parser/jsonl.d.ts.map +1 -0
  65. package/dist/parser/jsonl.js +202 -0
  66. package/dist/parser/jsonl.js.map +1 -0
  67. package/dist/parser/types.d.ts +81 -0
  68. package/dist/parser/types.d.ts.map +1 -0
  69. package/dist/parser/types.js +9 -0
  70. package/dist/parser/types.js.map +1 -0
  71. package/dist/parser/watcher.d.ts +16 -0
  72. package/dist/parser/watcher.d.ts.map +1 -0
  73. package/dist/parser/watcher.js +103 -0
  74. package/dist/parser/watcher.js.map +1 -0
  75. package/dist/pricing/index.d.ts +2 -0
  76. package/dist/pricing/index.d.ts.map +1 -0
  77. package/dist/pricing/index.js +37 -0
  78. package/dist/pricing/index.js.map +1 -0
  79. package/dist/service/index.d.ts +31 -0
  80. package/dist/service/index.d.ts.map +1 -0
  81. package/dist/service/index.js +252 -0
  82. package/dist/service/index.js.map +1 -0
  83. package/dist/storage/db.d.ts +75 -0
  84. package/dist/storage/db.d.ts.map +1 -0
  85. package/dist/storage/db.js +117 -0
  86. package/dist/storage/db.js.map +1 -0
  87. package/dist/storage/index.d.ts +4 -0
  88. package/dist/storage/index.d.ts.map +1 -0
  89. package/dist/storage/index.js +3 -0
  90. package/dist/storage/index.js.map +1 -0
  91. package/dist/storage/repo.d.ts +162 -0
  92. package/dist/storage/repo.d.ts.map +1 -0
  93. package/dist/storage/repo.js +472 -0
  94. package/dist/storage/repo.js.map +1 -0
  95. package/dist/sync/client.d.ts +24 -0
  96. package/dist/sync/client.d.ts.map +1 -0
  97. package/dist/sync/client.js +41 -0
  98. package/dist/sync/client.js.map +1 -0
  99. package/dist/sync/index.d.ts +18 -0
  100. package/dist/sync/index.d.ts.map +1 -0
  101. package/dist/sync/index.js +135 -0
  102. package/dist/sync/index.js.map +1 -0
  103. package/dist/sync/sync.test.d.ts +2 -0
  104. package/dist/sync/sync.test.d.ts.map +1 -0
  105. package/dist/sync/sync.test.js +412 -0
  106. package/dist/sync/sync.test.js.map +1 -0
  107. package/dist/sync/transform.d.ts +80 -0
  108. package/dist/sync/transform.d.ts.map +1 -0
  109. package/dist/sync/transform.js +90 -0
  110. package/dist/sync/transform.js.map +1 -0
  111. package/package.json +50 -0
  112. package/src/cli/commands.ts +332 -0
  113. package/src/cli/format.ts +133 -0
  114. package/src/context/index.ts +42 -0
  115. package/src/dashboard/html.ts +97 -0
  116. package/src/dashboard/index.html +1245 -0
  117. package/src/hooks/config.ts +119 -0
  118. package/src/hooks/git.ts +77 -0
  119. package/src/hooks/index.ts +7 -0
  120. package/src/hooks/server.ts +397 -0
  121. package/src/index.ts +6 -0
  122. package/src/otel/config.ts +141 -0
  123. package/src/otel/index.ts +8 -0
  124. package/src/otel/receiver.ts +183 -0
  125. package/src/parser/index.ts +3 -0
  126. package/src/parser/ingest.ts +119 -0
  127. package/src/parser/jsonl.ts +241 -0
  128. package/src/parser/types.ts +89 -0
  129. package/src/parser/watcher.ts +116 -0
  130. package/src/pricing/index.ts +51 -0
  131. package/src/service/index.ts +272 -0
  132. package/src/storage/db.ts +198 -0
  133. package/src/storage/index.ts +3 -0
  134. package/src/storage/repo.ts +601 -0
  135. package/src/sync/client.ts +63 -0
  136. package/src/sync/index.ts +207 -0
  137. package/src/sync/sync.test.ts +447 -0
  138. package/src/sync/transform.ts +184 -0
  139. package/tsconfig.json +19 -0
@@ -0,0 +1,472 @@
1
+ export class SessionRepo {
2
+ db;
3
+ constructor(db) {
4
+ this.db = db;
5
+ }
6
+ upsertSession(session) {
7
+ this.db.prepare(`
8
+ INSERT INTO sessions (id, project_path, started_at, ended_at, total_input_tokens,
9
+ total_output_tokens, total_cache_read_tokens, total_cache_creation_tokens,
10
+ total_cost_usd, total_turns, total_duration_ms, model)
11
+ VALUES (@id, @project_path, @started_at, @ended_at, @total_input_tokens,
12
+ @total_output_tokens, @total_cache_read_tokens, @total_cache_creation_tokens,
13
+ @total_cost_usd, @total_turns, @total_duration_ms, @model)
14
+ ON CONFLICT(id) DO UPDATE SET
15
+ started_at = MIN(started_at, @started_at),
16
+ project_path = COALESCE(@project_path, project_path),
17
+ ended_at = COALESCE(@ended_at, ended_at),
18
+ total_turns = @total_turns,
19
+ model = COALESCE(@model, model),
20
+ -- Use MAX so that OTEL-accumulated values are never clobbered by a JSONL re-ingest
21
+ total_input_tokens = MAX(total_input_tokens, @total_input_tokens),
22
+ total_output_tokens = MAX(total_output_tokens, @total_output_tokens),
23
+ total_cache_read_tokens = MAX(total_cache_read_tokens, @total_cache_read_tokens),
24
+ total_cache_creation_tokens = MAX(total_cache_creation_tokens, @total_cache_creation_tokens),
25
+ total_cost_usd = MAX(total_cost_usd, @total_cost_usd),
26
+ total_duration_ms = MAX(total_duration_ms, @total_duration_ms)
27
+ `).run({
28
+ ...session,
29
+ ended_at: session.ended_at ?? null,
30
+ });
31
+ }
32
+ /**
33
+ * Accumulate metric deltas from a single OTEL export batch into the sessions row.
34
+ * Creates a stub session row if none exists yet (OTEL may arrive before JSONL ingest).
35
+ * All numeric fields are additive deltas — never a full replacement.
36
+ */
37
+ updateSessionFromOtel(sessionId, deltas) {
38
+ this.db.prepare(`
39
+ INSERT INTO sessions (id, started_at, ended_at, total_cost_usd, total_input_tokens,
40
+ total_output_tokens, total_cache_read_tokens, total_cache_creation_tokens,
41
+ total_duration_ms, model)
42
+ VALUES (@id, @ts, @ts, @cost, @input, @output, @cache_read, @cache_creation, @duration_ms, @model)
43
+ ON CONFLICT(id) DO UPDATE SET
44
+ total_cost_usd = total_cost_usd + @cost,
45
+ total_input_tokens = total_input_tokens + @input,
46
+ total_output_tokens = total_output_tokens + @output,
47
+ total_cache_read_tokens = total_cache_read_tokens + @cache_read,
48
+ total_cache_creation_tokens = total_cache_creation_tokens + @cache_creation,
49
+ total_duration_ms = total_duration_ms + @duration_ms,
50
+ ended_at = CASE WHEN @ts > COALESCE(ended_at, '') THEN @ts ELSE ended_at END,
51
+ model = COALESCE(@model, model)
52
+ `).run({
53
+ id: sessionId,
54
+ ts: deltas.latestTimestamp,
55
+ cost: deltas.costDelta,
56
+ input: deltas.inputDelta,
57
+ output: deltas.outputDelta,
58
+ cache_read: deltas.cacheReadDelta,
59
+ cache_creation: deltas.cacheCreationDelta,
60
+ duration_ms: deltas.durationMsDelta,
61
+ model: deltas.model,
62
+ });
63
+ }
64
+ insertTurn(turn) {
65
+ // Use RETURNING so we get the correct id on both INSERT and ON CONFLICT UPDATE
66
+ const row = this.db.prepare(`
67
+ INSERT INTO turns (session_id, turn_index, role, timestamp, input_tokens, output_tokens,
68
+ cache_read_tokens, cache_creation_tokens, cost_usd, duration_ms, model, content_text, tool_calls, is_real_user)
69
+ VALUES (@session_id, @turn_index, @role, @timestamp, @input_tokens, @output_tokens,
70
+ @cache_read_tokens, @cache_creation_tokens, @cost_usd, @duration_ms, @model, @content_text, @tool_calls, @is_real_user)
71
+ ON CONFLICT(session_id, turn_index) DO UPDATE SET
72
+ content_text = @content_text,
73
+ tool_calls = @tool_calls,
74
+ input_tokens = @input_tokens,
75
+ output_tokens = @output_tokens,
76
+ cost_usd = MAX(cost_usd, @cost_usd),
77
+ duration_ms = MAX(duration_ms, @duration_ms),
78
+ is_real_user = @is_real_user
79
+ RETURNING id
80
+ `).get(turn);
81
+ return row?.id ?? 0;
82
+ }
83
+ insertToolUse(toolUse) {
84
+ this.db.prepare(`
85
+ INSERT INTO tool_uses (session_id, turn_id, tool_name, tool_input, tool_result, success, duration_ms, timestamp)
86
+ VALUES (@session_id, @turn_id, @tool_name, @tool_input, @tool_result, @success, @duration_ms, @timestamp)
87
+ `).run(toolUse);
88
+ }
89
+ /**
90
+ * Replace all tool uses for a given turn in a single transaction.
91
+ * Deletes existing rows first to prevent duplication on re-ingest.
92
+ */
93
+ replaceToolUsesForTurn(turnId, toolUses) {
94
+ const del = this.db.prepare(`DELETE FROM tool_uses WHERE turn_id = ?`);
95
+ const ins = this.db.prepare(`
96
+ INSERT INTO tool_uses (session_id, turn_id, tool_name, tool_input, tool_result, success, duration_ms, timestamp)
97
+ VALUES (@session_id, @turn_id, @tool_name, @tool_input, @tool_result, @success, @duration_ms, @timestamp)
98
+ `);
99
+ this.db.transaction(() => {
100
+ del.run(turnId);
101
+ for (const row of toolUses)
102
+ ins.run(row);
103
+ })();
104
+ }
105
+ insertHookEvent(event) {
106
+ this.db.prepare(`
107
+ INSERT INTO hook_events (session_id, event_name, timestamp, payload)
108
+ VALUES (@session_id, @event_name, @timestamp, @payload)
109
+ `).run(event);
110
+ }
111
+ listSessions(limit = 50, offset = 0) {
112
+ return this.db.prepare(`
113
+ SELECT * FROM sessions ORDER BY started_at DESC LIMIT ? OFFSET ?
114
+ `).all(limit, offset);
115
+ }
116
+ countSessions() {
117
+ const row = this.db.prepare(`SELECT COUNT(*) as n FROM sessions`).get();
118
+ return row.n;
119
+ }
120
+ getSession(id) {
121
+ return this.db.prepare(`SELECT * FROM sessions WHERE id = ?`).get(id);
122
+ }
123
+ getSessionTurns(sessionId) {
124
+ return this.db.prepare(`
125
+ SELECT * FROM turns WHERE session_id = ? ORDER BY turn_index ASC
126
+ `).all(sessionId);
127
+ }
128
+ getSessionToolUses(sessionId) {
129
+ return this.db.prepare(`
130
+ SELECT * FROM tool_uses WHERE session_id = ? ORDER BY timestamp ASC
131
+ `).all(sessionId);
132
+ }
133
+ getSessionHookEvents(sessionId) {
134
+ return this.db.prepare(`
135
+ SELECT * FROM hook_events WHERE session_id = ? ORDER BY timestamp ASC
136
+ `).all(sessionId);
137
+ }
138
+ getAggregateStats() {
139
+ return this.db.prepare(`
140
+ SELECT
141
+ (SELECT COUNT(*) FROM sessions) as total_sessions,
142
+ (SELECT SUM(total_input_tokens) FROM sessions) as total_input_tokens,
143
+ (SELECT SUM(total_output_tokens) FROM sessions) as total_output_tokens,
144
+ (SELECT SUM(total_cache_read_tokens) FROM sessions) as total_cache_read_tokens,
145
+ (SELECT SUM(total_cost_usd) FROM sessions) as total_cost_usd,
146
+ (SELECT SUM(total_turns) FROM sessions) as total_turns,
147
+ (SELECT SUM(total_duration_ms) FROM sessions) as total_duration_ms,
148
+ (SELECT COUNT(*) FROM hook_events WHERE event_name = 'UserPromptSubmit') as total_user_prompts,
149
+ (SELECT COUNT(*) FROM hook_events WHERE event_name = 'Stop') as total_interruptions
150
+ `).get();
151
+ }
152
+ // ── OTEL data ──
153
+ insertOtelMetric(metric) {
154
+ this.db.prepare(`
155
+ INSERT INTO otel_metrics (name, value, attributes, session_id, model, timestamp)
156
+ VALUES (@name, @value, @attributes, @session_id, @model, @timestamp)
157
+ `).run(metric);
158
+ }
159
+ insertOtelMetricBatch(metrics) {
160
+ const stmt = this.db.prepare(`
161
+ INSERT INTO otel_metrics (name, value, attributes, session_id, model, timestamp)
162
+ VALUES (@name, @value, @attributes, @session_id, @model, @timestamp)
163
+ `);
164
+ const tx = this.db.transaction((rows) => {
165
+ for (const row of rows)
166
+ stmt.run(row);
167
+ });
168
+ tx(metrics);
169
+ }
170
+ insertOtelEvent(event) {
171
+ this.db.prepare(`
172
+ INSERT INTO otel_events (event_name, attributes, session_id, prompt_id, timestamp)
173
+ VALUES (@event_name, @attributes, @session_id, @prompt_id, @timestamp)
174
+ `).run(event);
175
+ }
176
+ insertOtelEventBatch(events) {
177
+ const stmt = this.db.prepare(`
178
+ INSERT INTO otel_events (event_name, attributes, session_id, prompt_id, timestamp)
179
+ VALUES (@event_name, @attributes, @session_id, @prompt_id, @timestamp)
180
+ `);
181
+ const tx = this.db.transaction((rows) => {
182
+ for (const row of rows)
183
+ stmt.run(row);
184
+ });
185
+ tx(events);
186
+ }
187
+ // ── Dashboard queries ──
188
+ getTokenTimeSeries(from, to, stepSeconds) {
189
+ return this.db.prepare(`
190
+ SELECT
191
+ datetime((CAST(strftime('%s', timestamp) AS INTEGER) / CAST(? AS INTEGER)) * CAST(? AS INTEGER), 'unixepoch') as timestamp,
192
+ SUM(CASE WHEN json_extract(attributes, '$.type') = 'input' THEN value ELSE 0 END) as input,
193
+ SUM(CASE WHEN json_extract(attributes, '$.type') = 'output' THEN value ELSE 0 END) as output,
194
+ SUM(CASE WHEN json_extract(attributes, '$.type') = 'cacheRead' THEN value ELSE 0 END) as cache_read
195
+ FROM otel_metrics
196
+ WHERE name = 'claude_code.token.usage'
197
+ AND timestamp >= ? AND timestamp <= ?
198
+ GROUP BY 1
199
+ ORDER BY 1 ASC
200
+ `).all(stepSeconds, stepSeconds, from, to);
201
+ }
202
+ getCostTimeSeries(from, to, stepSeconds) {
203
+ return this.db.prepare(`
204
+ SELECT
205
+ datetime((CAST(strftime('%s', timestamp) AS INTEGER) / CAST(? AS INTEGER)) * CAST(? AS INTEGER), 'unixepoch') as timestamp,
206
+ SUM(value) as cost
207
+ FROM otel_metrics
208
+ WHERE name = 'claude_code.cost.usage'
209
+ AND timestamp >= ? AND timestamp <= ?
210
+ GROUP BY 1
211
+ ORDER BY 1 ASC
212
+ `).all(stepSeconds, stepSeconds, from, to);
213
+ }
214
+ getToolUsageBreakdown() {
215
+ return this.db.prepare(`
216
+ SELECT tool_name, COUNT(*) as count
217
+ FROM tool_uses
218
+ GROUP BY tool_name
219
+ ORDER BY count DESC
220
+ LIMIT 20
221
+ `).all();
222
+ }
223
+ getModelBreakdown() {
224
+ return this.db.prepare(`
225
+ SELECT
226
+ model,
227
+ SUM(CASE WHEN name = 'claude_code.cost.usage' THEN value ELSE 0 END) as cost,
228
+ SUM(CASE WHEN name = 'claude_code.token.usage' THEN value ELSE 0 END) as tokens
229
+ FROM otel_metrics
230
+ WHERE model IS NOT NULL
231
+ GROUP BY model
232
+ ORDER BY cost DESC
233
+ `).all();
234
+ }
235
+ getRecentOtelEvents(limit = 50) {
236
+ return this.db.prepare(`
237
+ SELECT event_name, attributes, session_id, prompt_id, timestamp
238
+ FROM otel_events
239
+ ORDER BY timestamp DESC
240
+ LIMIT ?
241
+ `).all(limit);
242
+ }
243
+ // ── Task tags ──
244
+ tagTurn(turnId, task) {
245
+ this.db.prepare(`
246
+ INSERT OR IGNORE INTO task_tags (turn_id, task, tagged_at)
247
+ VALUES (?, ?, ?)
248
+ `).run(turnId, task, new Date().toISOString());
249
+ }
250
+ tagTurnsBatch(turnIds, task) {
251
+ const stmt = this.db.prepare(`
252
+ INSERT OR IGNORE INTO task_tags (turn_id, task, tagged_at)
253
+ VALUES (?, ?, ?)
254
+ `);
255
+ const taggedAt = new Date().toISOString();
256
+ this.db.transaction(() => {
257
+ for (const turnId of turnIds)
258
+ stmt.run(turnId, task, taggedAt);
259
+ })();
260
+ }
261
+ getTasksForTurn(turnId) {
262
+ const rows = this.db.prepare(`
263
+ SELECT task FROM task_tags WHERE turn_id = ? ORDER BY tagged_at ASC
264
+ `).all(turnId);
265
+ return rows.map(r => r.task);
266
+ }
267
+ getTaskTagsForTurn(turnId) {
268
+ return this.db.prepare(`SELECT * FROM task_tags WHERE turn_id = ? ORDER BY tagged_at ASC`).all(turnId);
269
+ }
270
+ getTurnsByTask(task, limit = 50, offset = 0) {
271
+ return this.db.prepare(`
272
+ SELECT t.* FROM turns t
273
+ JOIN task_tags tt ON tt.turn_id = t.id
274
+ WHERE tt.task = ?
275
+ ORDER BY t.timestamp DESC
276
+ LIMIT ? OFFSET ?
277
+ `).all(task, limit, offset);
278
+ }
279
+ getTaggedTurns(opts = {}) {
280
+ const { tags, mode = "any", from, to, limit = 50, offset = 0 } = opts;
281
+ const conditions = [];
282
+ const params = [];
283
+ if (tags && tags.length > 0) {
284
+ const placeholders = tags.map(() => "?").join(",");
285
+ if (mode === "all" && tags.length > 1) {
286
+ conditions.push(`t.id IN (
287
+ SELECT turn_id FROM task_tags WHERE task IN (${placeholders})
288
+ GROUP BY turn_id HAVING COUNT(DISTINCT task) = ?
289
+ )`);
290
+ params.push(...tags, tags.length);
291
+ }
292
+ else {
293
+ conditions.push(`t.id IN (SELECT DISTINCT turn_id FROM task_tags WHERE task IN (${placeholders}))`);
294
+ params.push(...tags);
295
+ }
296
+ }
297
+ else {
298
+ conditions.push("t.id IN (SELECT DISTINCT turn_id FROM task_tags)");
299
+ }
300
+ if (from && to) {
301
+ conditions.push("t.timestamp >= ? AND t.timestamp <= ?");
302
+ params.push(from, to);
303
+ }
304
+ // Only show real user turns (each row = one interaction block)
305
+ conditions.push("t.is_real_user = 1");
306
+ const where = conditions.length > 0 ? "WHERE " + conditions.join(" AND ") : "";
307
+ params.push(limit, offset);
308
+ // Aggregate block stats: sum tokens/cost from this turn to the next real user turn
309
+ return this.db.prepare(`
310
+ SELECT t.*,
311
+ (SELECT GROUP_CONCAT(tt2.task, ', ') FROM task_tags tt2 WHERE tt2.turn_id = t.id) as tags,
312
+ (SELECT COALESCE(SUM(b.input_tokens), 0) FROM turns b
313
+ WHERE b.session_id = t.session_id AND b.turn_index >= t.turn_index
314
+ AND b.turn_index < COALESCE(
315
+ (SELECT MIN(n.turn_index) FROM turns n WHERE n.session_id = t.session_id AND n.turn_index > t.turn_index AND n.is_real_user = 1),
316
+ 999999)
317
+ ) as block_input_tokens,
318
+ (SELECT COALESCE(SUM(b.output_tokens), 0) FROM turns b
319
+ WHERE b.session_id = t.session_id AND b.turn_index >= t.turn_index
320
+ AND b.turn_index < COALESCE(
321
+ (SELECT MIN(n.turn_index) FROM turns n WHERE n.session_id = t.session_id AND n.turn_index > t.turn_index AND n.is_real_user = 1),
322
+ 999999)
323
+ ) as block_output_tokens,
324
+ (SELECT COALESCE(SUM(b.cost_usd), 0) FROM turns b
325
+ WHERE b.session_id = t.session_id AND b.turn_index >= t.turn_index
326
+ AND b.turn_index < COALESCE(
327
+ (SELECT MIN(n.turn_index) FROM turns n WHERE n.session_id = t.session_id AND n.turn_index > t.turn_index AND n.is_real_user = 1),
328
+ 999999)
329
+ ) as block_cost_usd
330
+ FROM turns t
331
+ ${where}
332
+ ORDER BY t.timestamp DESC
333
+ LIMIT ? OFFSET ?
334
+ `).all(...params);
335
+ }
336
+ getTurnBlock(turnId) {
337
+ const turn = this.db.prepare(`SELECT * FROM turns WHERE id = ?`).get(turnId);
338
+ if (!turn)
339
+ return [];
340
+ // Find the next real user turn in the same session
341
+ const nextRealUser = this.db.prepare(`
342
+ SELECT turn_index FROM turns
343
+ WHERE session_id = ? AND turn_index > ? AND is_real_user = 1
344
+ ORDER BY turn_index ASC LIMIT 1
345
+ `).get(turn.session_id, turn.turn_index);
346
+ const maxIndex = nextRealUser ? nextRealUser.turn_index : 999999;
347
+ return this.db.prepare(`
348
+ SELECT * FROM turns
349
+ WHERE session_id = ? AND turn_index >= ? AND turn_index < ?
350
+ ORDER BY turn_index ASC
351
+ `).all(turn.session_id, turn.turn_index, maxIndex);
352
+ }
353
+ getStatsByTask(task, from, to) {
354
+ const timeFilter = from && to ? " AND t.timestamp >= ? AND t.timestamp <= ?" : "";
355
+ const params = [task];
356
+ if (from && to)
357
+ params.push(from, to);
358
+ return this.db.prepare(`
359
+ SELECT
360
+ COUNT(*) as total_turns,
361
+ SUM(t.is_real_user) as user_turns,
362
+ SUM(t.input_tokens) as total_input_tokens,
363
+ SUM(t.output_tokens) as total_output_tokens,
364
+ SUM(t.cache_read_tokens) as total_cache_read_tokens,
365
+ SUM(t.cost_usd) as total_cost_usd,
366
+ SUM(t.duration_ms) as total_duration_ms
367
+ FROM turns t
368
+ JOIN task_tags tt ON tt.turn_id = t.id
369
+ WHERE tt.task = ?${timeFilter}
370
+ `).get(...params);
371
+ }
372
+ getStatsByTasks(tasks, mode = "all", from, to) {
373
+ if (tasks.length === 0)
374
+ return null;
375
+ if (tasks.length === 1)
376
+ return this.getStatsByTask(tasks[0], from, to);
377
+ const placeholders = tasks.map(() => "?").join(",");
378
+ const timeFilter = from && to ? " AND t.timestamp >= ? AND t.timestamp <= ?" : "";
379
+ const timeParams = from && to ? [from, to] : [];
380
+ if (mode === "any") {
381
+ return this.db.prepare(`
382
+ SELECT
383
+ COUNT(DISTINCT t.id) as total_turns,
384
+ SUM(t.is_real_user) as user_turns,
385
+ SUM(t.input_tokens) as total_input_tokens,
386
+ SUM(t.output_tokens) as total_output_tokens,
387
+ SUM(t.cache_read_tokens) as total_cache_read_tokens,
388
+ SUM(t.cost_usd) as total_cost_usd,
389
+ SUM(t.duration_ms) as total_duration_ms
390
+ FROM turns t
391
+ WHERE t.id IN (
392
+ SELECT DISTINCT turn_id FROM task_tags WHERE task IN (${placeholders})
393
+ )${timeFilter}
394
+ `).get(...tasks, ...timeParams);
395
+ }
396
+ return this.db.prepare(`
397
+ SELECT
398
+ COUNT(*) as total_turns,
399
+ SUM(t.is_real_user) as user_turns,
400
+ SUM(t.input_tokens) as total_input_tokens,
401
+ SUM(t.output_tokens) as total_output_tokens,
402
+ SUM(t.cache_read_tokens) as total_cache_read_tokens,
403
+ SUM(t.cost_usd) as total_cost_usd,
404
+ SUM(t.duration_ms) as total_duration_ms
405
+ FROM turns t
406
+ WHERE t.id IN (
407
+ SELECT turn_id FROM task_tags
408
+ WHERE task IN (${placeholders})
409
+ GROUP BY turn_id
410
+ HAVING COUNT(DISTINCT task) = ?
411
+ )${timeFilter}
412
+ `).get(...tasks, tasks.length, ...timeParams);
413
+ }
414
+ listTasks() {
415
+ return this.db.prepare(`
416
+ SELECT task, COUNT(*) as turn_count,
417
+ MIN(tagged_at) as first_tagged, MAX(tagged_at) as last_tagged
418
+ FROM task_tags
419
+ GROUP BY task
420
+ ORDER BY last_tagged DESC
421
+ `).all();
422
+ }
423
+ // ── Sync watermarks ──
424
+ getSyncWatermark(tableName) {
425
+ const row = this.db.prepare(`SELECT last_synced_id FROM sync_watermarks WHERE table_name = ?`).get(tableName);
426
+ return row?.last_synced_id ?? 0;
427
+ }
428
+ setSyncWatermark(tableName, lastSyncedId) {
429
+ this.db.prepare(`
430
+ INSERT INTO sync_watermarks (table_name, last_synced_id, last_synced_at)
431
+ VALUES (?, ?, datetime('now'))
432
+ ON CONFLICT(table_name) DO UPDATE SET
433
+ last_synced_id = excluded.last_synced_id,
434
+ last_synced_at = excluded.last_synced_at
435
+ `).run(tableName, lastSyncedId);
436
+ }
437
+ // ── Bulk reads for sync ──
438
+ getUnsyncedSessions(minRowid) {
439
+ return this.db.prepare(`
440
+ SELECT *, rowid as _rowid FROM sessions WHERE rowid > ? ORDER BY rowid ASC
441
+ `).all(minRowid);
442
+ }
443
+ getSessionsByIds(ids) {
444
+ if (ids.length === 0)
445
+ return [];
446
+ const placeholders = ids.map(() => "?").join(",");
447
+ return this.db.prepare(`SELECT * FROM sessions WHERE id IN (${placeholders})`).all(...ids);
448
+ }
449
+ getTurnsAfter(minId, limit) {
450
+ return this.db.prepare(`SELECT * FROM turns WHERE id > ? ORDER BY id ASC LIMIT ?`).all(minId, limit);
451
+ }
452
+ getToolUsesAfter(minId, limit) {
453
+ return this.db.prepare(`SELECT * FROM tool_uses WHERE id > ? ORDER BY id ASC LIMIT ?`).all(minId, limit);
454
+ }
455
+ getHookEventsAfter(minId, limit) {
456
+ return this.db.prepare(`SELECT * FROM hook_events WHERE id > ? ORDER BY id ASC LIMIT ?`).all(minId, limit);
457
+ }
458
+ getOtelMetricsAfter(minId, limit) {
459
+ return this.db.prepare(`SELECT * FROM otel_metrics WHERE id > ? ORDER BY id ASC LIMIT ?`).all(minId, limit);
460
+ }
461
+ getOtelEventsAfter(minId, limit) {
462
+ return this.db.prepare(`SELECT * FROM otel_events WHERE id > ? ORDER BY id ASC LIMIT ?`).all(minId, limit);
463
+ }
464
+ getTaskTagsAfter(minId, limit) {
465
+ return this.db.prepare(`SELECT * FROM task_tags WHERE id > ? ORDER BY id ASC LIMIT ?`).all(minId, limit);
466
+ }
467
+ getTurnLookup() {
468
+ const rows = this.db.prepare(`SELECT id, session_id, turn_index FROM turns`).all();
469
+ return new Map(rows.map(r => [r.id, { session_id: r.session_id, turn_index: r.turn_index }]));
470
+ }
471
+ }
472
+ //# sourceMappingURL=repo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repo.js","sourceRoot":"","sources":["../../src/storage/repo.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import type { SessionSyncPayload, ApiOtelMetric, ApiOtelEvent } from "./transform.js";
2
+ export interface SyncClientConfig {
3
+ apiUrl: string;
4
+ apiKey: string;
5
+ timeout?: number;
6
+ }
7
+ export interface SessionSyncResponse {
8
+ session_id: string;
9
+ turns_synced: number;
10
+ tool_uses_synced: number;
11
+ task_tags_synced: number;
12
+ hook_events_synced: number;
13
+ }
14
+ export declare class ZozulApiClient {
15
+ private baseUrl;
16
+ private apiKey;
17
+ private timeout;
18
+ constructor(config: SyncClientConfig);
19
+ syncSession(sessionId: string, payload: SessionSyncPayload): Promise<SessionSyncResponse>;
20
+ postOtelMetricsBulk(metrics: ApiOtelMetric[]): Promise<void>;
21
+ postOtelEventsBulk(events: ApiOtelEvent[]): Promise<void>;
22
+ private post;
23
+ }
24
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/sync/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEtF,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,OAAO,CAAS;gBAEZ,MAAM,EAAE,gBAAgB;IAM9B,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAIzF,mBAAmB,CAAC,OAAO,EAAE,aAAa,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAI5D,kBAAkB,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;YAIjD,IAAI;CAuBnB"}
@@ -0,0 +1,41 @@
1
+ export class ZozulApiClient {
2
+ baseUrl;
3
+ apiKey;
4
+ timeout;
5
+ constructor(config) {
6
+ this.baseUrl = config.apiUrl.replace(/\/+$/, "");
7
+ this.apiKey = config.apiKey;
8
+ this.timeout = config.timeout ?? 30_000;
9
+ }
10
+ async syncSession(sessionId, payload) {
11
+ return this.post(`/api/v1/sessions/${sessionId}/sync`, payload);
12
+ }
13
+ async postOtelMetricsBulk(metrics) {
14
+ await this.post("/api/v1/otel/metrics/bulk", metrics);
15
+ }
16
+ async postOtelEventsBulk(events) {
17
+ await this.post("/api/v1/otel/events/bulk", events);
18
+ }
19
+ async post(path, body) {
20
+ const url = `${this.baseUrl}${path}`;
21
+ const res = await fetch(url, {
22
+ method: "POST",
23
+ headers: {
24
+ "Content-Type": "application/json",
25
+ "X-API-Key": this.apiKey,
26
+ },
27
+ body: JSON.stringify(body),
28
+ signal: AbortSignal.timeout(this.timeout),
29
+ });
30
+ if (!res.ok) {
31
+ const text = await res.text().catch(() => "");
32
+ throw new Error(`POST ${path} failed: ${res.status} ${res.statusText} — ${text}`);
33
+ }
34
+ const contentType = res.headers.get("content-type") ?? "";
35
+ if (contentType.includes("application/json")) {
36
+ return (await res.json());
37
+ }
38
+ return undefined;
39
+ }
40
+ }
41
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/sync/client.ts"],"names":[],"mappings":"AAgBA,MAAM,OAAO,cAAc;IACjB,OAAO,CAAS;IAChB,MAAM,CAAS;IACf,OAAO,CAAS;IAExB,YAAY,MAAwB;QAClC,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QACjD,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;QAC5B,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC;IAC1C,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,SAAiB,EAAE,OAA2B;QAC9D,OAAO,IAAI,CAAC,IAAI,CAAC,oBAAoB,SAAS,OAAO,EAAE,OAAO,CAAC,CAAC;IAClE,CAAC;IAED,KAAK,CAAC,mBAAmB,CAAC,OAAwB;QAChD,MAAM,IAAI,CAAC,IAAI,CAAC,2BAA2B,EAAE,OAAO,CAAC,CAAC;IACxD,CAAC;IAED,KAAK,CAAC,kBAAkB,CAAC,MAAsB;QAC7C,MAAM,IAAI,CAAC,IAAI,CAAC,0BAA0B,EAAE,MAAM,CAAC,CAAC;IACtD,CAAC;IAEO,KAAK,CAAC,IAAI,CAAW,IAAY,EAAE,IAAa;QACtD,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,OAAO,GAAG,IAAI,EAAE,CAAC;QACrC,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAC3B,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,cAAc,EAAE,kBAAkB;gBAClC,WAAW,EAAE,IAAI,CAAC,MAAM;aACzB;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;YAC1B,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC;SAC1C,CAAC,CAAC;QAEH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;YAC9C,MAAM,IAAI,KAAK,CAAC,QAAQ,IAAI,YAAY,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,UAAU,MAAM,IAAI,EAAE,CAAC,CAAC;QACpF,CAAC;QAED,MAAM,WAAW,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC;QAC1D,IAAI,WAAW,CAAC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,CAAC;YAC7C,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAM,CAAC;QACjC,CAAC;QACD,OAAO,SAAc,CAAC;IACxB,CAAC;CACF"}
@@ -0,0 +1,18 @@
1
+ import type { SessionRepo } from "../storage/repo.js";
2
+ import type { ZozulApiClient } from "./client.js";
3
+ export interface SyncCounts {
4
+ synced: number;
5
+ failed: number;
6
+ }
7
+ export interface SyncResult {
8
+ sessions: SyncCounts;
9
+ otel_metrics: SyncCounts;
10
+ otel_events: SyncCounts;
11
+ }
12
+ interface SyncOptions {
13
+ verbose?: boolean;
14
+ dryRun?: boolean;
15
+ }
16
+ export declare function runSync(repo: SessionRepo, client: ZozulApiClient, opts?: SyncOptions): Promise<SyncResult>;
17
+ export {};
18
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sync/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAUlD,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,UAAU,CAAC;IACrB,YAAY,EAAE,UAAU,CAAC;IACzB,WAAW,EAAE,UAAU,CAAC;CACzB;AAED,UAAU,WAAW;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAqCD,wBAAsB,OAAO,CAC3B,IAAI,EAAE,WAAW,EACjB,MAAM,EAAE,cAAc,EACtB,IAAI,GAAE,WAAgB,GACrB,OAAO,CAAC,UAAU,CAAC,CA6FrB"}