tokentrace 0.8.0 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/app/api/prices/refresh/route.ts +18 -3
  3. package/app/api/prices/route.ts +39 -13
  4. package/app/api/repair-items/route.ts +6 -1
  5. package/app/api/scan/route.ts +17 -5
  6. package/app/api/settings/route.ts +11 -3
  7. package/app/evidence/page.tsx +3 -3
  8. package/app/page.tsx +7 -7
  9. package/dist/runtime/db-seed.mjs +10 -2
  10. package/dist/runtime/digest.mjs +507 -336
  11. package/dist/runtime/doctor.mjs +2181 -396
  12. package/dist/runtime/insights.mjs +783 -316
  13. package/dist/runtime/pricing-refresh.mjs +1159 -1029
  14. package/dist/runtime/reset.mjs +10 -2
  15. package/dist/runtime/scan.mjs +1259 -764
  16. package/dist/runtime/status.mjs +242 -86
  17. package/package.json +2 -2
  18. package/scripts/digest.ts +21 -3
  19. package/scripts/doctor.ts +22 -4
  20. package/scripts/insights.ts +18 -2
  21. package/scripts/pricing-refresh.ts +25 -9
  22. package/scripts/scan.ts +21 -8
  23. package/scripts/status.ts +33 -38
  24. package/src/db/settings.ts +5 -2
  25. package/src/ingestion/adapters/claude-code.ts +51 -20
  26. package/src/ingestion/adapters/codex-cli.ts +204 -17
  27. package/src/ingestion/adapters/generic-json.ts +1 -1
  28. package/src/ingestion/adapters/generic-jsonl.ts +1 -1
  29. package/src/ingestion/adapters/generic-log.ts +72 -10
  30. package/src/ingestion/adapters/helpers.ts +107 -42
  31. package/src/ingestion/discovery.ts +11 -2
  32. package/src/ingestion/persist.ts +43 -4
  33. package/src/ingestion/scan.ts +45 -18
  34. package/src/lib/analytics.ts +20 -19
  35. package/src/lib/api-json.ts +46 -0
  36. package/src/lib/format.ts +12 -1
  37. package/src/lib/pricing-manifest.ts +10 -2
  38. package/src/lib/pricing-refresh-cli.ts +57 -0
  39. package/src/lib/report-cli.ts +36 -0
  40. package/src/lib/scan-cli.ts +50 -0
  41. package/src/lib/status-cli.ts +150 -0
@@ -1,19 +1,94 @@
1
1
  import { createRequire as __tokentraceCreateRequire } from 'node:module'; const require = __tokentraceCreateRequire(import.meta.url);
2
2
  var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __esm = (fn, res) => function __init() {
5
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
+ };
3
7
  var __export = (target, all) => {
4
8
  for (var name in all)
5
9
  __defProp(target, name, { get: all[name], enumerable: true });
6
10
  };
7
11
 
8
- // src/db/client.ts
9
- import fs from "node:fs";
10
- import path from "node:path";
11
- import { fileURLToPath } from "node:url";
12
- import Database from "better-sqlite3";
13
- import { drizzle } from "drizzle-orm/better-sqlite3";
14
-
15
12
  // src/db/migrate-core.ts
16
- var ddl = `
13
+ function applyMigrations(sqlite2) {
14
+ sqlite2.exec(ddl);
15
+ const interactionColumns = sqlite2.prepare("PRAGMA table_info(interactions)").all();
16
+ if (!interactionColumns.some((column) => column.name === "token_confidence")) {
17
+ try {
18
+ sqlite2.exec("ALTER TABLE interactions ADD COLUMN token_confidence TEXT NOT NULL DEFAULT 'unknown'");
19
+ } catch (error) {
20
+ if (!(error instanceof Error) || !error.message.toLowerCase().includes("duplicate column")) {
21
+ throw error;
22
+ }
23
+ }
24
+ }
25
+ const modelColumns = sqlite2.prepare("PRAGMA table_info(models)").all();
26
+ if (!modelColumns.some((column) => column.name === "cache_write_token_price")) {
27
+ try {
28
+ sqlite2.exec("ALTER TABLE models ADD COLUMN cache_write_token_price REAL");
29
+ } catch (error) {
30
+ if (!(error instanceof Error) || !error.message.toLowerCase().includes("duplicate column")) {
31
+ throw error;
32
+ }
33
+ }
34
+ }
35
+ const reviewColumns = sqlite2.prepare("PRAGMA table_info(unknown_cost_reviews)").all();
36
+ const reviewColumnNames = new Set(reviewColumns.map((column) => column.name));
37
+ const addReviewColumn = (name, definition) => {
38
+ if (reviewColumnNames.has(name)) return;
39
+ try {
40
+ sqlite2.exec(`ALTER TABLE unknown_cost_reviews ADD COLUMN ${definition}`);
41
+ reviewColumnNames.add(name);
42
+ } catch (error) {
43
+ if (!(error instanceof Error) || !error.message.toLowerCase().includes("duplicate column")) {
44
+ throw error;
45
+ }
46
+ }
47
+ };
48
+ addReviewColumn("source_file", "source_file TEXT NOT NULL DEFAULT ''");
49
+ addReviewColumn("model", "model TEXT NOT NULL DEFAULT ''");
50
+ addReviewColumn("cause", "cause TEXT NOT NULL DEFAULT ''");
51
+ addReviewColumn("status", "status TEXT NOT NULL DEFAULT 'unresolved'");
52
+ addReviewColumn("notes", "notes TEXT NOT NULL DEFAULT ''");
53
+ addReviewColumn("created_at", "created_at INTEGER NOT NULL DEFAULT 0");
54
+ addReviewColumn("updated_at", "updated_at INTEGER NOT NULL DEFAULT 0");
55
+ const upgradedReviewColumns = sqlite2.prepare("PRAGMA table_info(unknown_cost_reviews)").all();
56
+ const upgradedReviewColumnNames = new Set(upgradedReviewColumns.map((column) => column.name));
57
+ if (upgradedReviewColumnNames.has("state")) {
58
+ sqlite2.exec("UPDATE unknown_cost_reviews SET status = state WHERE status = 'unresolved' AND state <> ''");
59
+ }
60
+ if (upgradedReviewColumnNames.has("note")) {
61
+ sqlite2.exec("UPDATE unknown_cost_reviews SET notes = note WHERE notes = '' AND note <> ''");
62
+ }
63
+ sqlite2.exec(`
64
+ UPDATE unknown_cost_reviews
65
+ SET cause = substr(key, 1, instr(key, ':') - 1)
66
+ WHERE cause = '' AND instr(key, ':') > 0;
67
+
68
+ UPDATE unknown_cost_reviews
69
+ SET created_at = updated_at
70
+ WHERE created_at = 0 AND updated_at > 0;
71
+
72
+ UPDATE unknown_cost_reviews
73
+ SET created_at = unixepoch() * 1000
74
+ WHERE created_at = 0;
75
+
76
+ UPDATE unknown_cost_reviews
77
+ SET updated_at = created_at
78
+ WHERE updated_at = 0;
79
+ `);
80
+ const repairRows = sqlite2.prepare("SELECT key, model FROM unknown_cost_reviews WHERE model = ''").all();
81
+ const updateModel = sqlite2.prepare("UPDATE unknown_cost_reviews SET model = ? WHERE key = ?");
82
+ for (const row of repairRows) {
83
+ const model = row.key.split(":").at(-1) ?? "";
84
+ updateModel.run(model, row.key);
85
+ }
86
+ }
87
+ var ddl;
88
+ var init_migrate_core = __esm({
89
+ "src/db/migrate-core.ts"() {
90
+ "use strict";
91
+ ddl = `
17
92
  PRAGMA journal_mode = WAL;
18
93
  PRAGMA foreign_keys = ON;
19
94
 
@@ -153,80 +228,8 @@ CREATE TABLE IF NOT EXISTS unknown_cost_reviews (
153
228
  updated_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
154
229
  );
155
230
  `;
156
- function applyMigrations(sqlite2) {
157
- sqlite2.exec(ddl);
158
- const interactionColumns = sqlite2.prepare("PRAGMA table_info(interactions)").all();
159
- if (!interactionColumns.some((column) => column.name === "token_confidence")) {
160
- try {
161
- sqlite2.exec("ALTER TABLE interactions ADD COLUMN token_confidence TEXT NOT NULL DEFAULT 'unknown'");
162
- } catch (error) {
163
- if (!(error instanceof Error) || !error.message.toLowerCase().includes("duplicate column")) {
164
- throw error;
165
- }
166
- }
167
- }
168
- const modelColumns = sqlite2.prepare("PRAGMA table_info(models)").all();
169
- if (!modelColumns.some((column) => column.name === "cache_write_token_price")) {
170
- try {
171
- sqlite2.exec("ALTER TABLE models ADD COLUMN cache_write_token_price REAL");
172
- } catch (error) {
173
- if (!(error instanceof Error) || !error.message.toLowerCase().includes("duplicate column")) {
174
- throw error;
175
- }
176
- }
177
- }
178
- const reviewColumns = sqlite2.prepare("PRAGMA table_info(unknown_cost_reviews)").all();
179
- const reviewColumnNames = new Set(reviewColumns.map((column) => column.name));
180
- const addReviewColumn = (name, definition) => {
181
- if (reviewColumnNames.has(name)) return;
182
- try {
183
- sqlite2.exec(`ALTER TABLE unknown_cost_reviews ADD COLUMN ${definition}`);
184
- reviewColumnNames.add(name);
185
- } catch (error) {
186
- if (!(error instanceof Error) || !error.message.toLowerCase().includes("duplicate column")) {
187
- throw error;
188
- }
189
- }
190
- };
191
- addReviewColumn("source_file", "source_file TEXT NOT NULL DEFAULT ''");
192
- addReviewColumn("model", "model TEXT NOT NULL DEFAULT ''");
193
- addReviewColumn("cause", "cause TEXT NOT NULL DEFAULT ''");
194
- addReviewColumn("status", "status TEXT NOT NULL DEFAULT 'unresolved'");
195
- addReviewColumn("notes", "notes TEXT NOT NULL DEFAULT ''");
196
- addReviewColumn("created_at", "created_at INTEGER NOT NULL DEFAULT 0");
197
- addReviewColumn("updated_at", "updated_at INTEGER NOT NULL DEFAULT 0");
198
- const upgradedReviewColumns = sqlite2.prepare("PRAGMA table_info(unknown_cost_reviews)").all();
199
- const upgradedReviewColumnNames = new Set(upgradedReviewColumns.map((column) => column.name));
200
- if (upgradedReviewColumnNames.has("state")) {
201
- sqlite2.exec("UPDATE unknown_cost_reviews SET status = state WHERE status = 'unresolved' AND state <> ''");
202
- }
203
- if (upgradedReviewColumnNames.has("note")) {
204
- sqlite2.exec("UPDATE unknown_cost_reviews SET notes = note WHERE notes = '' AND note <> ''");
205
- }
206
- sqlite2.exec(`
207
- UPDATE unknown_cost_reviews
208
- SET cause = substr(key, 1, instr(key, ':') - 1)
209
- WHERE cause = '' AND instr(key, ':') > 0;
210
-
211
- UPDATE unknown_cost_reviews
212
- SET created_at = updated_at
213
- WHERE created_at = 0 AND updated_at > 0;
214
-
215
- UPDATE unknown_cost_reviews
216
- SET created_at = unixepoch() * 1000
217
- WHERE created_at = 0;
218
-
219
- UPDATE unknown_cost_reviews
220
- SET updated_at = created_at
221
- WHERE updated_at = 0;
222
- `);
223
- const repairRows = sqlite2.prepare("SELECT key, model FROM unknown_cost_reviews WHERE model = ''").all();
224
- const updateModel = sqlite2.prepare("UPDATE unknown_cost_reviews SET model = ? WHERE key = ?");
225
- for (const row of repairRows) {
226
- const model = row.key.split(":").at(-1) ?? "";
227
- updateModel.run(model, row.key);
228
231
  }
229
- }
232
+ });
230
233
 
231
234
  // src/db/schema.ts
232
235
  var schema_exports = {};
@@ -258,220 +261,230 @@ import {
258
261
  text,
259
262
  uniqueIndex
260
263
  } from "drizzle-orm/sqlite-core";
261
- var providers = sqliteTable("providers", {
262
- id: text("id").primaryKey(),
263
- name: text("name").notNull(),
264
- type: text("type").notNull(),
265
- createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
266
- });
267
- var tools = sqliteTable(
268
- "tools",
269
- {
270
- id: text("id").primaryKey(),
271
- providerId: text("provider_id").notNull().references(() => providers.id, { onDelete: "cascade" }),
272
- name: text("name").notNull(),
273
- createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
274
- },
275
- (table) => ({
276
- providerNameIdx: uniqueIndex("tools_provider_name_idx").on(
277
- table.providerId,
278
- table.name
279
- )
280
- })
281
- );
282
- var models = sqliteTable(
283
- "models",
284
- {
285
- id: text("id").primaryKey(),
286
- providerId: text("provider_id").notNull().references(() => providers.id, { onDelete: "cascade" }),
287
- name: text("name").notNull(),
288
- inputTokenPrice: real("input_token_price"),
289
- outputTokenPrice: real("output_token_price"),
290
- cachedInputTokenPrice: real("cached_input_token_price"),
291
- cacheWriteTokenPrice: real("cache_write_token_price"),
292
- currency: text("currency").notNull().default("USD"),
293
- effectiveFrom: integer("effective_from", { mode: "timestamp_ms" }),
294
- rawMetadata: text("raw_metadata", { mode: "json" }).$type()
295
- },
296
- (table) => ({
297
- providerModelIdx: uniqueIndex("models_provider_name_idx").on(
298
- table.providerId,
299
- table.name
300
- )
301
- })
302
- );
303
- var projects = sqliteTable(
304
- "projects",
305
- {
306
- id: text("id").primaryKey(),
307
- name: text("name").notNull(),
308
- path: text("path").notNull(),
309
- createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
310
- },
311
- (table) => ({
312
- pathIdx: uniqueIndex("projects_path_idx").on(table.path)
313
- })
314
- );
315
- var sessions = sqliteTable(
316
- "sessions",
317
- {
318
- id: text("id").primaryKey(),
319
- sourceId: text("source_id").notNull(),
320
- toolId: text("tool_id").notNull().references(() => tools.id, { onDelete: "cascade" }),
321
- projectId: text("project_id").references(() => projects.id, {
322
- onDelete: "set null"
323
- }),
324
- startedAt: integer("started_at", { mode: "timestamp_ms" }),
325
- endedAt: integer("ended_at", { mode: "timestamp_ms" }),
326
- title: text("title"),
327
- sourceFile: text("source_file").notNull(),
328
- rawMetadata: text("raw_metadata", { mode: "json" }).$type()
329
- },
330
- (table) => ({
331
- sourceIdx: uniqueIndex("sessions_source_id_idx").on(table.sourceId),
332
- toolIdx: index("sessions_tool_idx").on(table.toolId),
333
- projectIdx: index("sessions_project_idx").on(table.projectId),
334
- startedIdx: index("sessions_started_idx").on(table.startedAt)
335
- })
336
- );
337
- var interactions = sqliteTable(
338
- "interactions",
339
- {
340
- id: text("id").primaryKey(),
341
- sourceId: text("source_id").notNull(),
342
- sessionId: text("session_id").notNull().references(() => sessions.id, { onDelete: "cascade" }),
343
- timestamp: integer("timestamp", { mode: "timestamp_ms" }),
344
- role: text("role").notNull(),
345
- modelId: text("model_id").references(() => models.id, {
346
- onDelete: "set null"
347
- }),
348
- inputTokens: integer("input_tokens").notNull().default(0),
349
- outputTokens: integer("output_tokens").notNull().default(0),
350
- cacheReadTokens: integer("cache_read_tokens").notNull().default(0),
351
- cacheWriteTokens: integer("cache_write_tokens").notNull().default(0),
352
- reasoningTokens: integer("reasoning_tokens").notNull().default(0),
353
- totalTokens: integer("total_tokens").notNull().default(0),
354
- estimatedTokens: integer("estimated_tokens", { mode: "boolean" }).notNull().default(false),
355
- tokenConfidence: text("token_confidence").notNull().default("unknown"),
356
- cost: real("cost"),
357
- costEstimated: integer("cost_estimated", { mode: "boolean" }).notNull().default(false),
358
- latencyMs: integer("latency_ms"),
359
- rawTextPreview: text("raw_text_preview"),
360
- rawText: text("raw_text"),
361
- rawMetadata: text("raw_metadata", { mode: "json" }).$type()
362
- },
363
- (table) => ({
364
- sourceIdx: uniqueIndex("interactions_source_id_idx").on(table.sourceId),
365
- sessionIdx: index("interactions_session_idx").on(table.sessionId),
366
- modelIdx: index("interactions_model_idx").on(table.modelId),
367
- timestampIdx: index("interactions_timestamp_idx").on(table.timestamp)
368
- })
369
- );
370
- var toolCalls = sqliteTable(
371
- "tool_calls",
372
- {
373
- id: text("id").primaryKey(),
374
- interactionId: text("interaction_id").notNull().references(() => interactions.id, { onDelete: "cascade" }),
375
- name: text("name").notNull(),
376
- status: text("status"),
377
- durationMs: integer("duration_ms"),
378
- rawMetadata: text("raw_metadata", { mode: "json" }).$type()
379
- },
380
- (table) => ({
381
- interactionIdx: index("tool_calls_interaction_idx").on(table.interactionId)
382
- })
383
- );
384
- var scanRuns = sqliteTable("scan_runs", {
385
- id: text("id").primaryKey(),
386
- startedAt: integer("started_at", { mode: "timestamp_ms" }).notNull(),
387
- completedAt: integer("completed_at", { mode: "timestamp_ms" }),
388
- filesScanned: integer("files_scanned").notNull().default(0),
389
- recordsImported: integer("records_imported").notNull().default(0),
390
- warnings: text("warnings", { mode: "json" }).$type().notNull().default(sql`'[]'`),
391
- errors: text("errors", { mode: "json" }).$type().notNull().default(sql`'[]'`)
392
- });
393
- var scanFiles = sqliteTable(
394
- "scan_files",
395
- {
396
- id: text("id").primaryKey(),
397
- scanRunId: text("scan_run_id").notNull().references(() => scanRuns.id, { onDelete: "cascade" }),
398
- path: text("path").notNull(),
399
- modifiedTime: integer("modified_time", { mode: "timestamp_ms" }),
400
- sizeBytes: integer("size_bytes").notNull().default(0),
401
- fileHash: text("file_hash"),
402
- parser: text("parser"),
403
- status: text("status").notNull(),
404
- recordsImported: integer("records_imported").notNull().default(0),
405
- warnings: text("warnings", { mode: "json" }).$type().notNull().default(sql`'[]'`),
406
- errors: text("errors", { mode: "json" }).$type().notNull().default(sql`'[]'`),
407
- rawMetadata: text("raw_metadata", { mode: "json" }).$type()
408
- },
409
- (table) => ({
410
- pathHashIdx: index("scan_files_path_hash_idx").on(table.path, table.fileHash),
411
- scanRunIdx: index("scan_files_run_idx").on(table.scanRunId)
412
- })
413
- );
414
- var settings = sqliteTable("settings", {
415
- key: text("key").primaryKey(),
416
- value: text("value", { mode: "json" }).$type().notNull(),
417
- updatedAt: integer("updated_at", { mode: "timestamp_ms" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
418
- });
419
- var unknownCostReviews = sqliteTable("unknown_cost_reviews", {
420
- key: text("key").primaryKey(),
421
- sourceFile: text("source_file").notNull().default(""),
422
- model: text("model").notNull().default(""),
423
- cause: text("cause").notNull().default(""),
424
- status: text("status").notNull().default("unresolved"),
425
- notes: text("notes").notNull().default(""),
426
- createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
427
- updatedAt: integer("updated_at", { mode: "timestamp_ms" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
264
+ var providers, tools, models, projects, sessions, interactions, toolCalls, scanRuns, scanFiles, settings, unknownCostReviews, providerRelations, toolRelations, modelRelations, projectRelations, sessionRelations, interactionRelations;
265
+ var init_schema = __esm({
266
+ "src/db/schema.ts"() {
267
+ "use strict";
268
+ providers = sqliteTable("providers", {
269
+ id: text("id").primaryKey(),
270
+ name: text("name").notNull(),
271
+ type: text("type").notNull(),
272
+ createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
273
+ });
274
+ tools = sqliteTable(
275
+ "tools",
276
+ {
277
+ id: text("id").primaryKey(),
278
+ providerId: text("provider_id").notNull().references(() => providers.id, { onDelete: "cascade" }),
279
+ name: text("name").notNull(),
280
+ createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
281
+ },
282
+ (table) => ({
283
+ providerNameIdx: uniqueIndex("tools_provider_name_idx").on(
284
+ table.providerId,
285
+ table.name
286
+ )
287
+ })
288
+ );
289
+ models = sqliteTable(
290
+ "models",
291
+ {
292
+ id: text("id").primaryKey(),
293
+ providerId: text("provider_id").notNull().references(() => providers.id, { onDelete: "cascade" }),
294
+ name: text("name").notNull(),
295
+ inputTokenPrice: real("input_token_price"),
296
+ outputTokenPrice: real("output_token_price"),
297
+ cachedInputTokenPrice: real("cached_input_token_price"),
298
+ cacheWriteTokenPrice: real("cache_write_token_price"),
299
+ currency: text("currency").notNull().default("USD"),
300
+ effectiveFrom: integer("effective_from", { mode: "timestamp_ms" }),
301
+ rawMetadata: text("raw_metadata", { mode: "json" }).$type()
302
+ },
303
+ (table) => ({
304
+ providerModelIdx: uniqueIndex("models_provider_name_idx").on(
305
+ table.providerId,
306
+ table.name
307
+ )
308
+ })
309
+ );
310
+ projects = sqliteTable(
311
+ "projects",
312
+ {
313
+ id: text("id").primaryKey(),
314
+ name: text("name").notNull(),
315
+ path: text("path").notNull(),
316
+ createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
317
+ },
318
+ (table) => ({
319
+ pathIdx: uniqueIndex("projects_path_idx").on(table.path)
320
+ })
321
+ );
322
+ sessions = sqliteTable(
323
+ "sessions",
324
+ {
325
+ id: text("id").primaryKey(),
326
+ sourceId: text("source_id").notNull(),
327
+ toolId: text("tool_id").notNull().references(() => tools.id, { onDelete: "cascade" }),
328
+ projectId: text("project_id").references(() => projects.id, {
329
+ onDelete: "set null"
330
+ }),
331
+ startedAt: integer("started_at", { mode: "timestamp_ms" }),
332
+ endedAt: integer("ended_at", { mode: "timestamp_ms" }),
333
+ title: text("title"),
334
+ sourceFile: text("source_file").notNull(),
335
+ rawMetadata: text("raw_metadata", { mode: "json" }).$type()
336
+ },
337
+ (table) => ({
338
+ sourceIdx: uniqueIndex("sessions_source_id_idx").on(table.sourceId),
339
+ toolIdx: index("sessions_tool_idx").on(table.toolId),
340
+ projectIdx: index("sessions_project_idx").on(table.projectId),
341
+ startedIdx: index("sessions_started_idx").on(table.startedAt)
342
+ })
343
+ );
344
+ interactions = sqliteTable(
345
+ "interactions",
346
+ {
347
+ id: text("id").primaryKey(),
348
+ sourceId: text("source_id").notNull(),
349
+ sessionId: text("session_id").notNull().references(() => sessions.id, { onDelete: "cascade" }),
350
+ timestamp: integer("timestamp", { mode: "timestamp_ms" }),
351
+ role: text("role").notNull(),
352
+ modelId: text("model_id").references(() => models.id, {
353
+ onDelete: "set null"
354
+ }),
355
+ inputTokens: integer("input_tokens").notNull().default(0),
356
+ outputTokens: integer("output_tokens").notNull().default(0),
357
+ cacheReadTokens: integer("cache_read_tokens").notNull().default(0),
358
+ cacheWriteTokens: integer("cache_write_tokens").notNull().default(0),
359
+ reasoningTokens: integer("reasoning_tokens").notNull().default(0),
360
+ totalTokens: integer("total_tokens").notNull().default(0),
361
+ estimatedTokens: integer("estimated_tokens", { mode: "boolean" }).notNull().default(false),
362
+ tokenConfidence: text("token_confidence").notNull().default("unknown"),
363
+ cost: real("cost"),
364
+ costEstimated: integer("cost_estimated", { mode: "boolean" }).notNull().default(false),
365
+ latencyMs: integer("latency_ms"),
366
+ rawTextPreview: text("raw_text_preview"),
367
+ rawText: text("raw_text"),
368
+ rawMetadata: text("raw_metadata", { mode: "json" }).$type()
369
+ },
370
+ (table) => ({
371
+ sourceIdx: uniqueIndex("interactions_source_id_idx").on(table.sourceId),
372
+ sessionIdx: index("interactions_session_idx").on(table.sessionId),
373
+ modelIdx: index("interactions_model_idx").on(table.modelId),
374
+ timestampIdx: index("interactions_timestamp_idx").on(table.timestamp)
375
+ })
376
+ );
377
+ toolCalls = sqliteTable(
378
+ "tool_calls",
379
+ {
380
+ id: text("id").primaryKey(),
381
+ interactionId: text("interaction_id").notNull().references(() => interactions.id, { onDelete: "cascade" }),
382
+ name: text("name").notNull(),
383
+ status: text("status"),
384
+ durationMs: integer("duration_ms"),
385
+ rawMetadata: text("raw_metadata", { mode: "json" }).$type()
386
+ },
387
+ (table) => ({
388
+ interactionIdx: index("tool_calls_interaction_idx").on(table.interactionId)
389
+ })
390
+ );
391
+ scanRuns = sqliteTable("scan_runs", {
392
+ id: text("id").primaryKey(),
393
+ startedAt: integer("started_at", { mode: "timestamp_ms" }).notNull(),
394
+ completedAt: integer("completed_at", { mode: "timestamp_ms" }),
395
+ filesScanned: integer("files_scanned").notNull().default(0),
396
+ recordsImported: integer("records_imported").notNull().default(0),
397
+ warnings: text("warnings", { mode: "json" }).$type().notNull().default(sql`'[]'`),
398
+ errors: text("errors", { mode: "json" }).$type().notNull().default(sql`'[]'`)
399
+ });
400
+ scanFiles = sqliteTable(
401
+ "scan_files",
402
+ {
403
+ id: text("id").primaryKey(),
404
+ scanRunId: text("scan_run_id").notNull().references(() => scanRuns.id, { onDelete: "cascade" }),
405
+ path: text("path").notNull(),
406
+ modifiedTime: integer("modified_time", { mode: "timestamp_ms" }),
407
+ sizeBytes: integer("size_bytes").notNull().default(0),
408
+ fileHash: text("file_hash"),
409
+ parser: text("parser"),
410
+ status: text("status").notNull(),
411
+ recordsImported: integer("records_imported").notNull().default(0),
412
+ warnings: text("warnings", { mode: "json" }).$type().notNull().default(sql`'[]'`),
413
+ errors: text("errors", { mode: "json" }).$type().notNull().default(sql`'[]'`),
414
+ rawMetadata: text("raw_metadata", { mode: "json" }).$type()
415
+ },
416
+ (table) => ({
417
+ pathHashIdx: index("scan_files_path_hash_idx").on(table.path, table.fileHash),
418
+ scanRunIdx: index("scan_files_run_idx").on(table.scanRunId)
419
+ })
420
+ );
421
+ settings = sqliteTable("settings", {
422
+ key: text("key").primaryKey(),
423
+ value: text("value", { mode: "json" }).$type().notNull(),
424
+ updatedAt: integer("updated_at", { mode: "timestamp_ms" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
425
+ });
426
+ unknownCostReviews = sqliteTable("unknown_cost_reviews", {
427
+ key: text("key").primaryKey(),
428
+ sourceFile: text("source_file").notNull().default(""),
429
+ model: text("model").notNull().default(""),
430
+ cause: text("cause").notNull().default(""),
431
+ status: text("status").notNull().default("unresolved"),
432
+ notes: text("notes").notNull().default(""),
433
+ createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
434
+ updatedAt: integer("updated_at", { mode: "timestamp_ms" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
435
+ });
436
+ providerRelations = relations(providers, ({ many }) => ({
437
+ tools: many(tools),
438
+ models: many(models)
439
+ }));
440
+ toolRelations = relations(tools, ({ one, many }) => ({
441
+ provider: one(providers, {
442
+ fields: [tools.providerId],
443
+ references: [providers.id]
444
+ }),
445
+ sessions: many(sessions)
446
+ }));
447
+ modelRelations = relations(models, ({ one, many }) => ({
448
+ provider: one(providers, {
449
+ fields: [models.providerId],
450
+ references: [providers.id]
451
+ }),
452
+ interactions: many(interactions)
453
+ }));
454
+ projectRelations = relations(projects, ({ many }) => ({
455
+ sessions: many(sessions)
456
+ }));
457
+ sessionRelations = relations(sessions, ({ one, many }) => ({
458
+ tool: one(tools, {
459
+ fields: [sessions.toolId],
460
+ references: [tools.id]
461
+ }),
462
+ project: one(projects, {
463
+ fields: [sessions.projectId],
464
+ references: [projects.id]
465
+ }),
466
+ interactions: many(interactions)
467
+ }));
468
+ interactionRelations = relations(interactions, ({ one, many }) => ({
469
+ session: one(sessions, {
470
+ fields: [interactions.sessionId],
471
+ references: [sessions.id]
472
+ }),
473
+ model: one(models, {
474
+ fields: [interactions.modelId],
475
+ references: [models.id]
476
+ }),
477
+ toolCalls: many(toolCalls)
478
+ }));
479
+ }
428
480
  });
429
- var providerRelations = relations(providers, ({ many }) => ({
430
- tools: many(tools),
431
- models: many(models)
432
- }));
433
- var toolRelations = relations(tools, ({ one, many }) => ({
434
- provider: one(providers, {
435
- fields: [tools.providerId],
436
- references: [providers.id]
437
- }),
438
- sessions: many(sessions)
439
- }));
440
- var modelRelations = relations(models, ({ one, many }) => ({
441
- provider: one(providers, {
442
- fields: [models.providerId],
443
- references: [providers.id]
444
- }),
445
- interactions: many(interactions)
446
- }));
447
- var projectRelations = relations(projects, ({ many }) => ({
448
- sessions: many(sessions)
449
- }));
450
- var sessionRelations = relations(sessions, ({ one, many }) => ({
451
- tool: one(tools, {
452
- fields: [sessions.toolId],
453
- references: [tools.id]
454
- }),
455
- project: one(projects, {
456
- fields: [sessions.projectId],
457
- references: [projects.id]
458
- }),
459
- interactions: many(interactions)
460
- }));
461
- var interactionRelations = relations(interactions, ({ one, many }) => ({
462
- session: one(sessions, {
463
- fields: [interactions.sessionId],
464
- references: [sessions.id]
465
- }),
466
- model: one(models, {
467
- fields: [interactions.modelId],
468
- references: [models.id]
469
- }),
470
- toolCalls: many(toolCalls)
471
- }));
472
481
 
473
482
  // src/db/client.ts
474
- var defaultDbPath = path.join(process.cwd(), ".tokentrace", "tokentrace.db");
483
+ import fs from "node:fs";
484
+ import path from "node:path";
485
+ import { fileURLToPath } from "node:url";
486
+ import Database from "better-sqlite3";
487
+ import { drizzle } from "drizzle-orm/better-sqlite3";
475
488
  function databaseUrlPath(value) {
476
489
  if (!value?.startsWith("file:")) return null;
477
490
  try {
@@ -480,13 +493,22 @@ function databaseUrlPath(value) {
480
493
  return value.slice("file:".length);
481
494
  }
482
495
  }
483
- var dbPath = process.env.TOKENTRACE_DB ?? databaseUrlPath(process.env.DATABASE_URL) ?? defaultDbPath;
484
- fs.mkdirSync(path.dirname(dbPath), { recursive: true });
485
- var sqlite = new Database(dbPath);
486
- sqlite.pragma("busy_timeout = 10000");
487
- sqlite.pragma("foreign_keys = ON");
488
- applyMigrations(sqlite);
489
- var db = drizzle(sqlite, { schema: schema_exports });
496
+ var defaultDbPath, dbPath, sqlite, db;
497
+ var init_client = __esm({
498
+ "src/db/client.ts"() {
499
+ "use strict";
500
+ init_migrate_core();
501
+ init_schema();
502
+ defaultDbPath = path.join(process.cwd(), ".tokentrace", "tokentrace.db");
503
+ dbPath = process.env.TOKENTRACE_DB ?? databaseUrlPath(process.env.DATABASE_URL) ?? defaultDbPath;
504
+ fs.mkdirSync(path.dirname(dbPath), { recursive: true });
505
+ sqlite = new Database(dbPath);
506
+ sqlite.pragma("busy_timeout = 10000");
507
+ sqlite.pragma("foreign_keys = ON");
508
+ applyMigrations(sqlite);
509
+ db = drizzle(sqlite, { schema: schema_exports });
510
+ }
511
+ });
490
512
 
491
513
  // src/lib/scan-health.ts
492
514
  function increment(target, key, amount = 1) {
@@ -538,7 +560,6 @@ function joinHealthParts(parts) {
538
560
  if (parts.length <= 1) return parts[0] ?? "";
539
561
  return `${parts.slice(0, -1).join(", ")} and ${parts[parts.length - 1]}`;
540
562
  }
541
- var staleAfterMs = 7 * 24 * 60 * 60 * 1e3;
542
563
  function isSuccessfulRun(scanRun) {
543
564
  return scanRun.completedAt != null && scanRun.recordsImported > 0 && scanRun.errors.length === 0;
544
565
  }
@@ -687,6 +708,13 @@ function buildScanHealth({
687
708
  actions
688
709
  };
689
710
  }
711
+ var staleAfterMs;
712
+ var init_scan_health = __esm({
713
+ "src/lib/scan-health.ts"() {
714
+ "use strict";
715
+ staleAfterMs = 7 * 24 * 60 * 60 * 1e3;
716
+ }
717
+ });
690
718
 
691
719
  // src/lib/model-aliases.ts
692
720
  function unique(values) {
@@ -738,11 +766,17 @@ function modelNameCandidates(modelName) {
738
766
  addSnapshotDateCandidates(trimmed, candidates);
739
767
  return unique(candidates);
740
768
  }
769
+ var init_model_aliases = __esm({
770
+ "src/lib/model-aliases.ts"() {
771
+ "use strict";
772
+ }
773
+ });
741
774
 
742
775
  // src/lib/format.ts
743
776
  function formatTokens(value) {
744
777
  const number3 = value ?? 0;
745
- if (number3 >= 1e6) return `${(number3 / 1e6).toFixed(2)}M`;
778
+ if (number3 >= 999995e3) return `${(number3 / 1e9).toFixed(2)}B`;
779
+ if (number3 >= 999950) return `${(number3 / 1e6).toFixed(2)}M`;
746
780
  if (number3 >= 1e3) return `${(number3 / 1e3).toFixed(1)}K`;
747
781
  return number3.toLocaleString();
748
782
  }
@@ -769,6 +803,11 @@ function formatDate(value) {
769
803
  function percent(value) {
770
804
  return `${Math.round((value ?? 0) * 100)}%`;
771
805
  }
806
+ var init_format = __esm({
807
+ "src/lib/format.ts"() {
808
+ "use strict";
809
+ }
810
+ });
772
811
 
773
812
  // src/lib/recommendations.ts
774
813
  function recommendation(id, severity, title, evidence, action2, href) {
@@ -897,17 +936,15 @@ function buildLocalRecommendations(input) {
897
936
  const rank = { high: 0, medium: 1, low: 2 };
898
937
  return recommendations.sort((a, b) => rank[a.severity] - rank[b.severity]).slice(0, 8);
899
938
  }
939
+ var init_recommendations = __esm({
940
+ "src/lib/recommendations.ts"() {
941
+ "use strict";
942
+ init_format();
943
+ }
944
+ });
900
945
 
901
946
  // src/db/settings.ts
902
947
  import { eq } from "drizzle-orm";
903
- var defaultSettings = {
904
- customFolders: [],
905
- storeRawMessageContent: false,
906
- usageGuardrails: {
907
- monthlyCostLimitUsd: null,
908
- monthlyTokenLimit: null
909
- }
910
- };
911
948
  function positiveLimit(value) {
912
949
  if (typeof value === "number" && Number.isFinite(value) && value > 0) return value;
913
950
  if (typeof value === "string" && value.trim()) {
@@ -928,8 +965,8 @@ function normalizeSettings(value) {
928
965
  if (!value || typeof value !== "object") return defaultSettings;
929
966
  const candidate = value;
930
967
  return {
931
- customFolders: Array.isArray(candidate.customFolders) ? candidate.customFolders.filter((item) => typeof item === "string") : [],
932
- storeRawMessageContent: Boolean(candidate.storeRawMessageContent),
968
+ customFolders: Array.isArray(candidate.customFolders) ? candidate.customFolders.filter((item) => typeof item === "string").map((item) => item.trim()).filter(Boolean) : [],
969
+ storeRawMessageContent: candidate.storeRawMessageContent === true,
933
970
  usageGuardrails: normalizeUsageGuardrails(candidate.usageGuardrails)
934
971
  };
935
972
  }
@@ -937,6 +974,22 @@ function getAppSettings() {
937
974
  const row = db.select().from(settings).where(eq(settings.key, "app")).get();
938
975
  return normalizeSettings(row?.value);
939
976
  }
977
+ var defaultSettings;
978
+ var init_settings = __esm({
979
+ "src/db/settings.ts"() {
980
+ "use strict";
981
+ init_client();
982
+ init_schema();
983
+ defaultSettings = {
984
+ customFolders: [],
985
+ storeRawMessageContent: false,
986
+ usageGuardrails: {
987
+ monthlyCostLimitUsd: null,
988
+ monthlyTokenLimit: null
989
+ }
990
+ };
991
+ }
992
+ });
940
993
 
941
994
  // src/lib/usage-guardrails.ts
942
995
  function number(value) {
@@ -1011,6 +1064,13 @@ function getUsageGuardrailProgress(now = /* @__PURE__ */ new Date()) {
1011
1064
  now
1012
1065
  });
1013
1066
  }
1067
+ var init_usage_guardrails = __esm({
1068
+ "src/lib/usage-guardrails.ts"() {
1069
+ "use strict";
1070
+ init_client();
1071
+ init_settings();
1072
+ }
1073
+ });
1014
1074
 
1015
1075
  // src/lib/review-queue.ts
1016
1076
  function slug(value) {
@@ -1166,6 +1226,12 @@ function buildReviewQueue(input) {
1166
1226
  };
1167
1227
  return items.sort((a, b) => severityRank[a.severity] - severityRank[b.severity] || categoryRank[a.category] - categoryRank[b.category]).slice(0, 8);
1168
1228
  }
1229
+ var init_review_queue = __esm({
1230
+ "src/lib/review-queue.ts"() {
1231
+ "use strict";
1232
+ init_format();
1233
+ }
1234
+ });
1169
1235
 
1170
1236
  // src/lib/session-comparison.ts
1171
1237
  function median(values) {
@@ -1227,6 +1293,12 @@ function buildSessionComparisons(sessions2) {
1227
1293
  const severityRank = { high: 0, medium: 1, low: 2 };
1228
1294
  return rows2.sort((a, b) => severityRank[a.severity] - severityRank[b.severity] || b.tokenMultiple - a.tokenMultiple).slice(0, 12);
1229
1295
  }
1296
+ var init_session_comparison = __esm({
1297
+ "src/lib/session-comparison.ts"() {
1298
+ "use strict";
1299
+ init_format();
1300
+ }
1301
+ });
1230
1302
 
1231
1303
  // src/lib/project-signals.ts
1232
1304
  function slug2(value) {
@@ -1335,6 +1407,12 @@ function buildProjectSignals(input) {
1335
1407
  };
1336
1408
  return signals.sort((a, b) => signalRank[a.signal] - signalRank[b.signal] || severityRank[a.severity] - severityRank[b.severity]).slice(0, 12);
1337
1409
  }
1410
+ var init_project_signals = __esm({
1411
+ "src/lib/project-signals.ts"() {
1412
+ "use strict";
1413
+ init_format();
1414
+ }
1415
+ });
1338
1416
 
1339
1417
  // src/lib/evidence-trail.ts
1340
1418
  function withQuery2(path2, params) {
@@ -1348,8 +1426,22 @@ function withQuery2(path2, params) {
1348
1426
  function evidenceHref(metric2, params = {}) {
1349
1427
  return withQuery2("/evidence", { metric: metric2, ...params });
1350
1428
  }
1429
+ var init_evidence_trail = __esm({
1430
+ "src/lib/evidence-trail.ts"() {
1431
+ "use strict";
1432
+ init_client();
1433
+ }
1434
+ });
1351
1435
 
1352
1436
  // src/lib/analytics.ts
1437
+ var analytics_exports = {};
1438
+ __export(analytics_exports, {
1439
+ getAnalyticsData: () => getAnalyticsData,
1440
+ getDebugData: () => getDebugData,
1441
+ getPricedModelCount: () => getPricedModelCount,
1442
+ getScanConfidenceSummary: () => getScanConfidenceSummary,
1443
+ getScanTrustData: () => getScanTrustData
1444
+ });
1353
1445
  function number2(value) {
1354
1446
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
1355
1447
  }
@@ -1476,7 +1568,6 @@ function getSummary(filters = {}) {
1476
1568
  mostUsedModel: model?.name ?? "No data"
1477
1569
  };
1478
1570
  }
1479
- var sevenDaysMs = 7 * 24 * 60 * 60 * 1e3;
1480
1571
  function comparisonSnapshot(summary) {
1481
1572
  return {
1482
1573
  totalTokens: summary.totalTokens,
@@ -2126,6 +2217,12 @@ function getAnalyticsData(filters = {}) {
2126
2217
  insights
2127
2218
  };
2128
2219
  }
2220
+ function getDebugData() {
2221
+ return {
2222
+ scanRuns: getScanRunRows(50),
2223
+ scanFiles: getScanFileRows(500)
2224
+ };
2225
+ }
2129
2226
  function getScanRunRows(limit) {
2130
2227
  return rows(
2131
2228
  `SELECT id, started_at AS startedAt, completed_at AS completedAt,
@@ -2175,26 +2272,27 @@ function getScanConfidenceSummary() {
2175
2272
  FROM interactions`
2176
2273
  ).get();
2177
2274
  const unknownCostCauses = sqlite.prepare(
2178
- `SELECT
2179
- COALESCE(SUM(CASE WHEN cost IS NULL AND lower(m.name) = 'unknown' THEN 1 ELSE 0 END), 0) AS missingModelName,
2180
- COALESCE(SUM(CASE WHEN cost IS NULL
2181
- AND COALESCE(i.total_tokens, 0) <= 0 THEN 1 ELSE 0 END), 0) AS missingTokenCount,
2182
- COALESCE(SUM(CASE WHEN cost IS NULL
2183
- AND lower(m.name) <> 'unknown'
2184
- AND COALESCE(i.total_tokens, 0) > 0
2185
- AND (m.input_token_price IS NULL OR m.output_token_price IS NULL)
2186
- THEN 1 ELSE 0 END), 0) AS missingPricing,
2187
- COALESCE(SUM(CASE WHEN cost IS NULL
2188
- AND NOT (
2189
- lower(m.name) = 'unknown'
2190
- OR COALESCE(i.total_tokens, 0) <= 0
2191
- OR (lower(m.name) <> 'unknown'
2275
+ `WITH unknown_costs AS (
2276
+ SELECT
2277
+ CASE
2278
+ WHEN lower(COALESCE(m.name, 'unknown')) = 'unknown' THEN 'missingModelName'
2279
+ WHEN COALESCE(i.total_tokens, 0) <= 0 THEN 'missingTokenCount'
2280
+ WHEN lower(COALESCE(m.name, 'unknown')) <> 'unknown'
2192
2281
  AND COALESCE(i.total_tokens, 0) > 0
2193
- AND (m.input_token_price IS NULL OR m.output_token_price IS NULL))
2194
- )
2195
- THEN 1 ELSE 0 END), 0) AS other
2196
- FROM interactions i
2197
- LEFT JOIN models m ON m.id = i.model_id`
2282
+ AND (m.input_token_price IS NULL OR m.output_token_price IS NULL)
2283
+ THEN 'missingPricing'
2284
+ ELSE 'other'
2285
+ END AS cause
2286
+ FROM interactions i
2287
+ LEFT JOIN models m ON m.id = i.model_id
2288
+ WHERE i.cost IS NULL
2289
+ )
2290
+ SELECT
2291
+ COALESCE(SUM(CASE WHEN cause = 'missingModelName' THEN 1 ELSE 0 END), 0) AS missingModelName,
2292
+ COALESCE(SUM(CASE WHEN cause = 'missingTokenCount' THEN 1 ELSE 0 END), 0) AS missingTokenCount,
2293
+ COALESCE(SUM(CASE WHEN cause = 'missingPricing' THEN 1 ELSE 0 END), 0) AS missingPricing,
2294
+ COALESCE(SUM(CASE WHEN cause = 'other' THEN 1 ELSE 0 END), 0) AS other
2295
+ FROM unknown_costs`
2198
2296
  ).get();
2199
2297
  return {
2200
2298
  interactions: number2(row.interactions),
@@ -2239,8 +2337,29 @@ function getScanTrustData() {
2239
2337
  })
2240
2338
  };
2241
2339
  }
2340
+ var sevenDaysMs;
2341
+ var init_analytics = __esm({
2342
+ "src/lib/analytics.ts"() {
2343
+ "use strict";
2344
+ init_client();
2345
+ init_scan_health();
2346
+ init_model_aliases();
2347
+ init_recommendations();
2348
+ init_usage_guardrails();
2349
+ init_review_queue();
2350
+ init_session_comparison();
2351
+ init_project_signals();
2352
+ init_evidence_trail();
2353
+ sevenDaysMs = 7 * 24 * 60 * 60 * 1e3;
2354
+ }
2355
+ });
2242
2356
 
2243
2357
  // src/lib/daily-digest.ts
2358
+ var daily_digest_exports = {};
2359
+ __export(daily_digest_exports, {
2360
+ buildDailyDigest: () => buildDailyDigest,
2361
+ renderDailyDigestText: () => renderDailyDigestText
2362
+ });
2244
2363
  function fallbackReviewItem() {
2245
2364
  return {
2246
2365
  id: "baseline",
@@ -2299,13 +2418,65 @@ function renderDailyDigestText(digest2) {
2299
2418
  digest2.latestScan ? `Latest scan: ${digest2.latestScan.headline}, ${digest2.latestScan.recordsImported.toLocaleString()} records from ${digest2.latestScan.filesScanned.toLocaleString()} files` : "Latest scan: no scan yet"
2300
2419
  ].join("\n");
2301
2420
  }
2421
+ var init_daily_digest = __esm({
2422
+ "src/lib/daily-digest.ts"() {
2423
+ "use strict";
2424
+ init_format();
2425
+ }
2426
+ });
2427
+
2428
+ // src/lib/report-cli.ts
2429
+ function jsonReportUsage(command) {
2430
+ return `Usage: tokentrace ${command} [--json]
2431
+
2432
+ Options:
2433
+ --json Print report as JSON
2434
+ -h, --help Print ${command} help`;
2435
+ }
2436
+ function parseJsonReportArgs(argv) {
2437
+ const options2 = {
2438
+ help: false,
2439
+ json: false
2440
+ };
2441
+ for (const arg of argv) {
2442
+ if (arg === "--help" || arg === "-h") {
2443
+ options2.help = true;
2444
+ continue;
2445
+ }
2446
+ if (arg === "--json") {
2447
+ options2.json = true;
2448
+ continue;
2449
+ }
2450
+ if (arg.startsWith("-")) {
2451
+ throw new Error(`Unknown option: ${arg}`);
2452
+ }
2453
+ throw new Error(`Unknown argument: ${arg}`);
2454
+ }
2455
+ return options2;
2456
+ }
2302
2457
 
2303
2458
  // scripts/digest.ts
2304
2459
  var args = process.argv.slice(2);
2305
- var data = getAnalyticsData();
2306
- var trust = getScanTrustData();
2460
+ var options;
2461
+ try {
2462
+ options = parseJsonReportArgs(args);
2463
+ } catch (error) {
2464
+ console.error(error instanceof Error ? error.message : "Invalid digest arguments.");
2465
+ console.error(jsonReportUsage("digest"));
2466
+ process.exit(1);
2467
+ }
2468
+ if (options.help) {
2469
+ console.log(jsonReportUsage("digest"));
2470
+ process.exit(0);
2471
+ }
2472
+ var [{ getAnalyticsData: getAnalyticsData2, getScanTrustData: getScanTrustData2 }, { buildDailyDigest: buildDailyDigest2, renderDailyDigestText: renderDailyDigestText2 }] = await Promise.all([
2473
+ Promise.resolve().then(() => (init_analytics(), analytics_exports)),
2474
+ Promise.resolve().then(() => (init_daily_digest(), daily_digest_exports))
2475
+ ]);
2476
+ var data = getAnalyticsData2();
2477
+ var trust = getScanTrustData2();
2307
2478
  var latestRun = trust.health.latestRun;
2308
- var digest = buildDailyDigest({
2479
+ var digest = buildDailyDigest2({
2309
2480
  summary: data.summary,
2310
2481
  usageGuardrails: data.usageGuardrails,
2311
2482
  reviewQueue: data.reviewQueue,
@@ -2317,8 +2488,8 @@ var digest = buildDailyDigest({
2317
2488
  filesScanned: latestRun.filesScanned
2318
2489
  } : null
2319
2490
  });
2320
- if (args.includes("--json")) {
2491
+ if (options.json) {
2321
2492
  console.log(JSON.stringify(digest, null, 2));
2322
2493
  } else {
2323
- console.log(renderDailyDigestText(digest));
2494
+ console.log(renderDailyDigestText2(digest));
2324
2495
  }