@tokentop/ttop 0.2.2 → 0.4.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 (160) hide show
  1. package/README.md +2 -5
  2. package/package.json +8 -5
  3. package/src/agents/aggregator.ts +39 -19
  4. package/src/agents/costing.ts +11 -9
  5. package/src/agents/index.ts +7 -9
  6. package/src/agents/types.ts +2 -2
  7. package/src/cli.ts +28 -23
  8. package/src/config/schema.test.ts +42 -40
  9. package/src/config/schema.ts +26 -26
  10. package/src/demo/simulator.ts +161 -122
  11. package/src/plugins/agents/index.ts +2 -2
  12. package/src/plugins/auth-sources.ts +42 -38
  13. package/src/plugins/lifecycle.ts +42 -38
  14. package/src/plugins/loader.test.ts +49 -49
  15. package/src/plugins/loader.ts +67 -63
  16. package/src/plugins/notification-bus.ts +21 -16
  17. package/src/plugins/notifications/index.ts +2 -2
  18. package/src/plugins/notifications/terminal-bell.ts +36 -23
  19. package/src/plugins/notifications/visual-flash.ts +31 -19
  20. package/src/plugins/npm-installer.ts +23 -25
  21. package/src/plugins/plugin-context-factory.ts +10 -10
  22. package/src/plugins/plugin-host.ts +12 -5
  23. package/src/plugins/providers/anthropic.ts +64 -57
  24. package/src/plugins/providers/antigravity.ts +81 -69
  25. package/src/plugins/providers/codex.ts +42 -42
  26. package/src/plugins/providers/gemini.ts +106 -87
  27. package/src/plugins/providers/github-copilot.ts +49 -45
  28. package/src/plugins/providers/index.ts +10 -12
  29. package/src/plugins/providers/minimax.ts +50 -44
  30. package/src/plugins/providers/openai-api.ts +82 -62
  31. package/src/plugins/providers/opencode-zen.ts +46 -39
  32. package/src/plugins/providers/perplexity.ts +55 -46
  33. package/src/plugins/providers/zai.ts +68 -57
  34. package/src/plugins/registry.ts +80 -71
  35. package/src/plugins/sandbox-guard.test.ts +45 -48
  36. package/src/plugins/sandbox-guard.ts +20 -25
  37. package/src/plugins/sandbox.ts +26 -48
  38. package/src/plugins/themes/catppuccin-latte.ts +29 -29
  39. package/src/plugins/themes/catppuccin-mocha.ts +29 -29
  40. package/src/plugins/themes/claude-code.ts +29 -29
  41. package/src/plugins/themes/dracula.ts +29 -29
  42. package/src/plugins/themes/github-light.ts +29 -29
  43. package/src/plugins/themes/gruvbox-dark.ts +29 -29
  44. package/src/plugins/themes/gruvbox-light.ts +29 -29
  45. package/src/plugins/themes/index.ts +15 -15
  46. package/src/plugins/themes/kanagawa.ts +29 -29
  47. package/src/plugins/themes/nord.ts +29 -29
  48. package/src/plugins/themes/one-dark.ts +29 -29
  49. package/src/plugins/themes/opencode.ts +31 -31
  50. package/src/plugins/themes/rose-pine-dawn.ts +29 -29
  51. package/src/plugins/themes/rose-pine.ts +29 -29
  52. package/src/plugins/themes/solarized-light.ts +29 -29
  53. package/src/plugins/themes/tokyo-night.ts +29 -29
  54. package/src/plugins/types/agent.ts +5 -5
  55. package/src/plugins/types/base.ts +35 -24
  56. package/src/plugins/types/index.ts +9 -9
  57. package/src/plugins/types/notification.ts +12 -12
  58. package/src/plugins/types/provider.ts +12 -8
  59. package/src/plugins/types/theme.ts +31 -23
  60. package/src/plugins/update-checker.ts +6 -6
  61. package/src/pricing/estimator.test.ts +39 -44
  62. package/src/pricing/estimator.ts +7 -13
  63. package/src/pricing/fallback.test.ts +30 -34
  64. package/src/pricing/fallback.ts +54 -27
  65. package/src/pricing/index.ts +18 -12
  66. package/src/pricing/models-dev.ts +6 -6
  67. package/src/storage/database.ts +56 -46
  68. package/src/storage/db.ts +12 -12
  69. package/src/storage/index.ts +44 -45
  70. package/src/storage/migrations/index.ts +2 -2
  71. package/src/storage/paths.ts +13 -13
  72. package/src/storage/persistence-service.ts +206 -0
  73. package/src/storage/repos/agentSessions.ts +33 -23
  74. package/src/storage/repos/providerSnapshots.ts +5 -5
  75. package/src/storage/repos/usageEvents.ts +40 -28
  76. package/src/storage/retention.ts +146 -0
  77. package/src/storage/types.ts +5 -5
  78. package/src/tui/App.tsx +180 -137
  79. package/src/tui/components/CommandPalette.tsx +46 -34
  80. package/src/tui/components/DebugConsole.tsx +39 -33
  81. package/src/tui/components/DebugPanel.tsx +314 -183
  82. package/src/tui/components/FooterHints.tsx +2 -2
  83. package/src/tui/components/GhostProviderCard.tsx +37 -35
  84. package/src/tui/components/Header.tsx +68 -35
  85. package/src/tui/components/HelpOverlay.tsx +99 -29
  86. package/src/tui/components/InlineGauge.tsx +10 -7
  87. package/src/tui/components/InlineSparkline.tsx +17 -11
  88. package/src/tui/components/KpiStrip.tsx +128 -98
  89. package/src/tui/components/LimitGauge.tsx +96 -59
  90. package/src/tui/components/ModalBackdrop.tsx +4 -4
  91. package/src/tui/components/ProviderAggregateStrip.tsx +69 -38
  92. package/src/tui/components/ProviderCard.tsx +232 -226
  93. package/src/tui/components/ProviderDetailPanel.tsx +133 -58
  94. package/src/tui/components/ProviderLimitsPanel.tsx +126 -89
  95. package/src/tui/components/ProvidersList.tsx +215 -126
  96. package/src/tui/components/SessionDetailsDrawer.tsx +191 -94
  97. package/src/tui/components/SessionsTable.tsx +320 -169
  98. package/src/tui/components/SettingsModal.tsx +593 -269
  99. package/src/tui/components/SidebarBreakdown.tsx +30 -16
  100. package/src/tui/components/Skeleton.tsx +24 -17
  101. package/src/tui/components/SmartSidebar.tsx +360 -258
  102. package/src/tui/components/Sparkline.tsx +64 -52
  103. package/src/tui/components/Spinner.tsx +3 -3
  104. package/src/tui/components/StatusBar.tsx +27 -27
  105. package/src/tui/components/ThemePicker.tsx +67 -66
  106. package/src/tui/components/Toast.tsx +9 -6
  107. package/src/tui/components/UsageGauge.tsx +10 -13
  108. package/src/tui/components/index.ts +5 -5
  109. package/src/tui/contexts/AgentSessionContext.tsx +164 -151
  110. package/src/tui/contexts/ConfigContext.tsx +15 -13
  111. package/src/tui/contexts/DashboardRuntimeContext.tsx +26 -26
  112. package/src/tui/contexts/DemoModeContext.tsx +22 -15
  113. package/src/tui/contexts/DrawerContext.tsx +11 -9
  114. package/src/tui/contexts/InputContext.tsx +2 -2
  115. package/src/tui/contexts/LogContext.tsx +62 -47
  116. package/src/tui/contexts/PluginContext.tsx +238 -183
  117. package/src/tui/contexts/RealTimeActivityContext.tsx +70 -36
  118. package/src/tui/contexts/StorageContext.tsx +95 -149
  119. package/src/tui/contexts/ThemeContext.tsx +21 -16
  120. package/src/tui/contexts/TimeWindowContext.tsx +72 -61
  121. package/src/tui/contexts/ToastContext.tsx +10 -7
  122. package/src/tui/contexts/index.ts +9 -5
  123. package/src/tui/createApp.tsx +8 -12
  124. package/src/tui/debug/captureFrame.ts +25 -28
  125. package/src/tui/debug/snapshot.tsx +179 -162
  126. package/src/tui/driver/assertions.ts +30 -22
  127. package/src/tui/driver/cli.ts +231 -221
  128. package/src/tui/driver/coverage.ts +27 -44
  129. package/src/tui/driver/demo-snapshot.ts +33 -33
  130. package/src/tui/driver/diff.ts +26 -32
  131. package/src/tui/driver/driver.ts +43 -48
  132. package/src/tui/driver/index.ts +36 -40
  133. package/src/tui/driver/recorder.ts +37 -48
  134. package/src/tui/driver/test-workflow.ts +51 -51
  135. package/src/tui/driver/test.ts +25 -25
  136. package/src/tui/hooks/index.ts +24 -20
  137. package/src/tui/hooks/useAnimatedValue.ts +13 -18
  138. package/src/tui/hooks/useDashboardKeyboard.ts +118 -98
  139. package/src/tui/hooks/useDashboardState.ts +26 -24
  140. package/src/tui/hooks/useEmaActivity.ts +82 -74
  141. package/src/tui/hooks/useEntranceAnimation.ts +11 -11
  142. package/src/tui/hooks/useExitAnimation.ts +7 -3
  143. package/src/tui/hooks/usePulse.ts +4 -4
  144. package/src/tui/hooks/useSafeRenderer.ts +5 -5
  145. package/src/tui/hooks/useSessionEnrichedProviders.ts +150 -0
  146. package/src/tui/hooks/useValueFlash.ts +10 -14
  147. package/src/tui/index.tsx +29 -6
  148. package/src/tui/shutdown.ts +19 -0
  149. package/src/tui/utils/providerColor.ts +1 -1
  150. package/src/tui/views/Dashboard.tsx +164 -102
  151. package/src/tui/views/HistoricalTrendsView.tsx +579 -250
  152. package/src/tui/views/ProjectsView.tsx +495 -243
  153. package/src/tui/views/RealTimeDashboard.tsx +176 -116
  154. package/src/tui/views/SettingsView.tsx +225 -189
  155. package/src/tui/views/index.ts +2 -2
  156. package/src/types/opentui-test-utils.d.ts +7 -7
  157. package/src/utils/clipboard.ts +29 -29
  158. package/src/utils/currency.test.ts +53 -53
  159. package/src/utils/currency.ts +35 -32
  160. package/src/version.ts +1 -1
@@ -1,5 +1,5 @@
1
- import { getDatabase } from '../db.ts';
2
- import type { UsageEventInsert, TimeSeriesPoint, TimeSeriesFilters } from '../types.ts';
1
+ import { getDatabase } from "../db.ts";
2
+ import type { TimeSeriesFilters, TimeSeriesPoint, UsageEventInsert } from "../types.ts";
3
3
 
4
4
  const INSERT_SQL = `
5
5
  INSERT INTO usage_events (
@@ -27,7 +27,7 @@ export function insertUsageEvent(event: UsageEventInsert): number {
27
27
  event.cacheWriteTokens,
28
28
  event.costUsd,
29
29
  event.requestCount,
30
- event.pricingSource ?? null
30
+ event.pricingSource ?? null,
31
31
  );
32
32
 
33
33
  return Number(result.lastInsertRowid);
@@ -55,7 +55,7 @@ export function insertUsageEventBatch(events: UsageEventInsert[]): void {
55
55
  e.cacheWriteTokens,
56
56
  e.costUsd,
57
57
  e.requestCount,
58
- e.pricingSource ?? null
58
+ e.pricingSource ?? null,
59
59
  );
60
60
  }
61
61
  });
@@ -67,31 +67,31 @@ export function queryUsageTimeSeries(
67
67
  startMs: number,
68
68
  endMs: number,
69
69
  bucketMs: number,
70
- filters: TimeSeriesFilters = {}
70
+ filters: TimeSeriesFilters = {},
71
71
  ): TimeSeriesPoint[] {
72
72
  const db = getDatabase();
73
73
 
74
- const conditions: string[] = ['timestamp >= ?', 'timestamp <= ?'];
74
+ const conditions: string[] = ["timestamp >= ?", "timestamp <= ?"];
75
75
  const params: (string | number)[] = [startMs, endMs];
76
76
 
77
77
  if (filters.provider) {
78
- conditions.push('provider = ?');
78
+ conditions.push("provider = ?");
79
79
  params.push(filters.provider);
80
80
  }
81
81
  if (filters.model) {
82
- conditions.push('model = ?');
82
+ conditions.push("model = ?");
83
83
  params.push(filters.model);
84
84
  }
85
85
  if (filters.agentId) {
86
- conditions.push('agent_id = ?');
86
+ conditions.push("agent_id = ?");
87
87
  params.push(filters.agentId);
88
88
  }
89
89
  if (filters.sessionId) {
90
- conditions.push('session_id = ?');
90
+ conditions.push("session_id = ?");
91
91
  params.push(filters.sessionId);
92
92
  }
93
93
  if (filters.projectPath) {
94
- conditions.push('project_path = ?');
94
+ conditions.push("project_path = ?");
95
95
  params.push(filters.projectPath);
96
96
  }
97
97
 
@@ -102,7 +102,7 @@ export function queryUsageTimeSeries(
102
102
  COALESCE(SUM(cost_usd), 0) AS cost_usd,
103
103
  COALESCE(SUM(request_count), 0) AS request_count
104
104
  FROM usage_events
105
- WHERE ${conditions.join(' AND ')}
105
+ WHERE ${conditions.join(" AND ")}
106
106
  GROUP BY bucket_start
107
107
  ORDER BY bucket_start ASC
108
108
  `;
@@ -114,7 +114,7 @@ export function queryUsageTimeSeries(
114
114
  request_count: number;
115
115
  }>;
116
116
 
117
- return rows.map(r => ({
117
+ return rows.map((r) => ({
118
118
  bucketStart: r.bucket_start,
119
119
  tokens: r.tokens,
120
120
  costUsd: r.cost_usd,
@@ -122,17 +122,22 @@ export function queryUsageTimeSeries(
122
122
  }));
123
123
  }
124
124
 
125
- export function calculateBurnRate(windowMs: number, nowMs = Date.now()): { costPerHour: number; tokensPerMinute: number } {
125
+ export function calculateBurnRate(
126
+ windowMs: number,
127
+ nowMs = Date.now(),
128
+ ): { costPerHour: number; tokensPerMinute: number } {
126
129
  const db = getDatabase();
127
130
  const startMs = nowMs - windowMs;
128
131
 
129
- const row = db.prepare(`
132
+ const row = db
133
+ .prepare(`
130
134
  SELECT
131
135
  COALESCE(SUM(cost_usd), 0) AS cost,
132
136
  COALESCE(SUM(input_tokens + output_tokens), 0) AS tokens
133
137
  FROM usage_events
134
138
  WHERE timestamp >= ? AND timestamp <= ?
135
- `).get(startMs, nowMs) as { cost: number; tokens: number };
139
+ `)
140
+ .get(startMs, nowMs) as { cost: number; tokens: number };
136
141
 
137
142
  const hours = windowMs / (1000 * 60 * 60);
138
143
  const minutes = windowMs / (1000 * 60);
@@ -143,7 +148,10 @@ export function calculateBurnRate(windowMs: number, nowMs = Date.now()): { costP
143
148
  };
144
149
  }
145
150
 
146
- export function getTotalUsageInWindow(startMs: number, endMs: number): {
151
+ export function getTotalUsageInWindow(
152
+ startMs: number,
153
+ endMs: number,
154
+ ): {
147
155
  inputTokens: number;
148
156
  outputTokens: number;
149
157
  costUsd: number;
@@ -151,7 +159,8 @@ export function getTotalUsageInWindow(startMs: number, endMs: number): {
151
159
  } {
152
160
  const db = getDatabase();
153
161
 
154
- const row = db.prepare(`
162
+ const row = db
163
+ .prepare(`
155
164
  SELECT
156
165
  COALESCE(SUM(input_tokens), 0) AS input_tokens,
157
166
  COALESCE(SUM(output_tokens), 0) AS output_tokens,
@@ -159,7 +168,8 @@ export function getTotalUsageInWindow(startMs: number, endMs: number): {
159
168
  COALESCE(SUM(request_count), 0) AS request_count
160
169
  FROM usage_events
161
170
  WHERE timestamp >= ? AND timestamp <= ?
162
- `).get(startMs, endMs) as {
171
+ `)
172
+ .get(startMs, endMs) as {
163
173
  input_tokens: number;
164
174
  output_tokens: number;
165
175
  cost_usd: number;
@@ -190,7 +200,7 @@ export interface ProviderDailyCost {
190
200
  export function queryProviderDailyCosts(
191
201
  startMs: number,
192
202
  endMs: number,
193
- bucketMs: number
203
+ bucketMs: number,
194
204
  ): ProviderDailyCost[] {
195
205
  const db = getDatabase();
196
206
 
@@ -216,7 +226,7 @@ export function queryProviderDailyCosts(
216
226
  request_count: number;
217
227
  }>;
218
228
 
219
- return rows.map(r => ({
229
+ return rows.map((r) => ({
220
230
  provider: r.provider,
221
231
  bucketStart: r.bucket_start,
222
232
  costUsd: r.cost_usd,
@@ -236,7 +246,7 @@ export interface ModelDailyCost {
236
246
  export function queryModelDailyCosts(
237
247
  startMs: number,
238
248
  endMs: number,
239
- bucketMs: number
249
+ bucketMs: number,
240
250
  ): ModelDailyCost[] {
241
251
  const db = getDatabase();
242
252
 
@@ -262,7 +272,7 @@ export function queryModelDailyCosts(
262
272
  request_count: number;
263
273
  }>;
264
274
 
265
- return rows.map(r => ({
275
+ return rows.map((r) => ({
266
276
  model: r.model,
267
277
  bucketStart: r.bucket_start,
268
278
  costUsd: r.cost_usd,
@@ -282,7 +292,7 @@ export interface ProjectDailyCost {
282
292
  export function queryProjectDailyCosts(
283
293
  startMs: number,
284
294
  endMs: number,
285
- bucketMs: number
295
+ bucketMs: number,
286
296
  ): ProjectDailyCost[] {
287
297
  const db = getDatabase();
288
298
 
@@ -308,7 +318,7 @@ export function queryProjectDailyCosts(
308
318
  request_count: number;
309
319
  }>;
310
320
 
311
- return rows.map(r => ({
321
+ return rows.map((r) => ({
312
322
  projectPath: r.project_path,
313
323
  bucketStart: r.bucket_start,
314
324
  costUsd: r.cost_usd,
@@ -320,14 +330,16 @@ export function queryProjectDailyCosts(
320
330
  export function getSessionActivityTimeline(sessionId: string): SessionActivityPoint[] {
321
331
  const db = getDatabase();
322
332
 
323
- const rows = db.prepare(`
333
+ const rows = db
334
+ .prepare(`
324
335
  SELECT timestamp, (input_tokens + output_tokens) AS tokens
325
336
  FROM usage_events
326
337
  WHERE session_id = ?
327
338
  ORDER BY timestamp ASC
328
- `).all(sessionId) as Array<{ timestamp: number; tokens: number }>;
339
+ `)
340
+ .all(sessionId) as Array<{ timestamp: number; tokens: number }>;
329
341
 
330
- return rows.map(r => ({
342
+ return rows.map((r) => ({
331
343
  timestamp: r.timestamp,
332
344
  tokens: r.tokens,
333
345
  }));
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Data retention policy — prevents unbounded database growth.
3
+ *
4
+ * Detailed snapshot data (agent_session_snapshots, agent_session_stream_snapshots,
5
+ * provider_snapshots) is pruned after a retention window. Aggregated data in
6
+ * usage_events, hourly_aggregates, and daily_aggregates is kept longer since
7
+ * those tables grow much more slowly.
8
+ */
9
+
10
+ import { getDatabase, isDatabaseInitialized } from "./db.ts";
11
+
12
+ /** Keep detailed snapshots for 7 days */
13
+ const SNAPSHOT_RETENTION_MS = 7 * 24 * 60 * 60 * 1000;
14
+
15
+ /** Keep provider snapshots for 7 days */
16
+ const PROVIDER_SNAPSHOT_RETENTION_MS = 7 * 24 * 60 * 60 * 1000;
17
+
18
+ /** Keep usage events for 90 days */
19
+ const USAGE_EVENT_RETENTION_MS = 90 * 24 * 60 * 60 * 1000;
20
+
21
+ /** Maximum rows to delete in a single batch (prevents long-running transactions) */
22
+ const PRUNE_BATCH_SIZE = 10_000;
23
+
24
+ export interface PruneResult {
25
+ agentSessionSnapshots: number;
26
+ providerSnapshots: number;
27
+ usageEvents: number;
28
+ durationMs: number;
29
+ }
30
+
31
+ /**
32
+ * Prune old data from the database. Deletes in batches to avoid
33
+ * holding a write lock for too long.
34
+ */
35
+ export function pruneOldData(nowMs: number = Date.now()): PruneResult {
36
+ if (!isDatabaseInitialized()) {
37
+ return { agentSessionSnapshots: 0, providerSnapshots: 0, usageEvents: 0, durationMs: 0 };
38
+ }
39
+
40
+ const db = getDatabase();
41
+ const start = performance.now();
42
+
43
+ let agentSessionSnapshots = 0;
44
+ let providerSnapshots = 0;
45
+ let usageEvents = 0;
46
+
47
+ const snapshotCutoff = nowMs - SNAPSHOT_RETENTION_MS;
48
+ const providerCutoff = nowMs - PROVIDER_SNAPSHOT_RETENTION_MS;
49
+ const usageEventCutoff = nowMs - USAGE_EVENT_RETENTION_MS;
50
+
51
+ // Prune agent_session_snapshots in batches.
52
+ // CASCADE delete handles agent_session_stream_snapshots automatically.
53
+ {
54
+ const stmt = db.prepare(`
55
+ DELETE FROM agent_session_snapshots
56
+ WHERE id IN (
57
+ SELECT id FROM agent_session_snapshots
58
+ WHERE timestamp < ?
59
+ LIMIT ?
60
+ )
61
+ `);
62
+
63
+ let deleted: number;
64
+ do {
65
+ const result = stmt.run(snapshotCutoff, PRUNE_BATCH_SIZE);
66
+ deleted = result.changes;
67
+ agentSessionSnapshots += deleted;
68
+ } while (deleted === PRUNE_BATCH_SIZE);
69
+ }
70
+
71
+ // Prune old provider_snapshots in batches
72
+ {
73
+ const stmt = db.prepare(`
74
+ DELETE FROM provider_snapshots
75
+ WHERE id IN (
76
+ SELECT id FROM provider_snapshots
77
+ WHERE timestamp < ?
78
+ LIMIT ?
79
+ )
80
+ `);
81
+
82
+ let deleted: number;
83
+ do {
84
+ const result = stmt.run(providerCutoff, PRUNE_BATCH_SIZE);
85
+ deleted = result.changes;
86
+ providerSnapshots += deleted;
87
+ } while (deleted === PRUNE_BATCH_SIZE);
88
+ }
89
+
90
+ // Prune old usage_events in batches
91
+ {
92
+ const stmt = db.prepare(`
93
+ DELETE FROM usage_events
94
+ WHERE id IN (
95
+ SELECT id FROM usage_events
96
+ WHERE timestamp < ?
97
+ LIMIT ?
98
+ )
99
+ `);
100
+
101
+ let deleted: number;
102
+ do {
103
+ const result = stmt.run(usageEventCutoff, PRUNE_BATCH_SIZE);
104
+ deleted = result.changes;
105
+ usageEvents += deleted;
106
+ } while (deleted === PRUNE_BATCH_SIZE);
107
+ }
108
+
109
+ const durationMs = Math.round(performance.now() - start);
110
+
111
+ return { agentSessionSnapshots, providerSnapshots, usageEvents, durationMs };
112
+ }
113
+
114
+ /**
115
+ * Run incremental auto_vacuum if supported (WAL mode).
116
+ * This reclaims free pages without blocking readers.
117
+ */
118
+ export function incrementalVacuum(pages = 1000): void {
119
+ if (!isDatabaseInitialized()) return;
120
+
121
+ const db = getDatabase();
122
+ try {
123
+ db.exec(`PRAGMA incremental_vacuum(${pages})`);
124
+ } catch {
125
+ // Not all builds support incremental vacuum — silently skip
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Get the database file size in bytes. Returns 0 if unable to determine.
131
+ */
132
+ export function getDatabaseSizeBytes(): number {
133
+ if (!isDatabaseInitialized()) return 0;
134
+
135
+ const db = getDatabase();
136
+ try {
137
+ const row = db.prepare("PRAGMA page_count").get() as { page_count: number } | null;
138
+ const sizeRow = db.prepare("PRAGMA page_size").get() as { page_size: number } | null;
139
+ if (row && sizeRow) {
140
+ return row.page_count * sizeRow.page_size;
141
+ }
142
+ } catch {
143
+ // ignore
144
+ }
145
+ return 0;
146
+ }
@@ -7,10 +7,10 @@
7
7
  // Enums & Literal Types
8
8
  // ============================================================================
9
9
 
10
- export type UsageEventSource = 'agent' | 'provider';
11
- export type SessionStatus = 'active' | 'idle';
12
- export type CostSource = 'api' | 'estimated';
13
- export type PricingSource = 'models.dev' | 'fallback' | 'unknown';
10
+ export type UsageEventSource = "agent" | "provider";
11
+ export type SessionStatus = "active" | "idle";
12
+ export type CostSource = "api" | "estimated";
13
+ export type PricingSource = "models.dev" | "fallback" | "unknown";
14
14
 
15
15
  // ============================================================================
16
16
  // App Runs Table
@@ -253,7 +253,7 @@ export interface StreamTotals {
253
253
  */
254
254
  export function computeStreamDelta(
255
255
  current: StreamTotals,
256
- previous: StreamTotals | undefined
256
+ previous: StreamTotals | undefined,
257
257
  ): StreamTotals | null {
258
258
  if (!previous) {
259
259
  // First observation - treat as a delta from zero