@hasna/economy 0.2.19 → 0.2.20

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 (88) hide show
  1. package/dist/cli/commands/menubar.d.ts.map +1 -1
  2. package/dist/cli/commands/watch.d.ts +0 -1
  3. package/dist/cli/commands/watch.d.ts.map +1 -1
  4. package/dist/cli/index.js +707 -5200
  5. package/dist/db/database.d.ts +1 -41
  6. package/dist/db/database.d.ts.map +1 -1
  7. package/dist/db/pg-migrations.d.ts.map +1 -1
  8. package/dist/index.d.ts +0 -2
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +135 -1202
  11. package/dist/ingest/claude.d.ts +2 -13
  12. package/dist/ingest/claude.d.ts.map +1 -1
  13. package/dist/ingest/codex.d.ts +1 -2
  14. package/dist/ingest/codex.d.ts.map +1 -1
  15. package/dist/ingest/gemini.d.ts +1 -2
  16. package/dist/ingest/gemini.d.ts.map +1 -1
  17. package/dist/lib/config.d.ts.map +1 -1
  18. package/dist/lib/gatherer.d.ts.map +1 -1
  19. package/dist/lib/model-config.d.ts.map +1 -1
  20. package/dist/lib/pricing.d.ts +3 -3
  21. package/dist/lib/pricing.d.ts.map +1 -1
  22. package/dist/lib/webhooks.d.ts +1 -1
  23. package/dist/lib/webhooks.d.ts.map +1 -1
  24. package/dist/mcp/index.js +487 -2749
  25. package/dist/server/index.d.ts +0 -1
  26. package/dist/server/index.js +196 -3090
  27. package/dist/server/serve.d.ts +2 -10
  28. package/dist/server/serve.d.ts.map +1 -1
  29. package/dist/types/index.d.ts +6 -59
  30. package/dist/types/index.d.ts.map +1 -1
  31. package/package.json +1 -1
  32. package/dist/cli/commands/completion.d.ts +0 -2
  33. package/dist/cli/commands/completion.d.ts.map +0 -1
  34. package/dist/cli/commands/extras.d.ts +0 -4
  35. package/dist/cli/commands/extras.d.ts.map +0 -1
  36. package/dist/cli/commands/notification.d.ts +0 -8
  37. package/dist/cli/commands/notification.d.ts.map +0 -1
  38. package/dist/cli/commands/todos.d.ts +0 -26
  39. package/dist/cli/commands/todos.d.ts.map +0 -1
  40. package/dist/cli/commands/tui.d.ts +0 -10
  41. package/dist/cli/commands/tui.d.ts.map +0 -1
  42. package/dist/ingest/billing.d.ts +0 -27
  43. package/dist/ingest/billing.d.ts.map +0 -1
  44. package/dist/ingest/claude-quota.d.ts +0 -5
  45. package/dist/ingest/claude-quota.d.ts.map +0 -1
  46. package/dist/ingest/codex-quota.d.ts +0 -5
  47. package/dist/ingest/codex-quota.d.ts.map +0 -1
  48. package/dist/ingest/cursor.d.ts +0 -6
  49. package/dist/ingest/cursor.d.ts.map +0 -1
  50. package/dist/ingest/hermes.d.ts +0 -6
  51. package/dist/ingest/hermes.d.ts.map +0 -1
  52. package/dist/ingest/opencode.d.ts +0 -7
  53. package/dist/ingest/opencode.d.ts.map +0 -1
  54. package/dist/ingest/otel.d.ts +0 -20
  55. package/dist/ingest/otel.d.ts.map +0 -1
  56. package/dist/ingest/pi.d.ts +0 -7
  57. package/dist/ingest/pi.d.ts.map +0 -1
  58. package/dist/ingest/plugin.d.ts +0 -17
  59. package/dist/ingest/plugin.d.ts.map +0 -1
  60. package/dist/lib/agents.d.ts +0 -11
  61. package/dist/lib/agents.d.ts.map +0 -1
  62. package/dist/lib/billing-diff.d.ts +0 -22
  63. package/dist/lib/billing-diff.d.ts.map +0 -1
  64. package/dist/lib/cloud-sync.d.ts +0 -35
  65. package/dist/lib/cloud-sync.d.ts.map +0 -1
  66. package/dist/lib/open-projects.d.ts +0 -19
  67. package/dist/lib/open-projects.d.ts.map +0 -1
  68. package/dist/lib/package-metadata.d.ts +0 -8
  69. package/dist/lib/package-metadata.d.ts.map +0 -1
  70. package/dist/lib/paths.d.ts +0 -20
  71. package/dist/lib/paths.d.ts.map +0 -1
  72. package/dist/lib/savings.d.ts +0 -17
  73. package/dist/lib/savings.d.ts.map +0 -1
  74. package/dist/lib/serve-auth.d.ts +0 -4
  75. package/dist/lib/serve-auth.d.ts.map +0 -1
  76. package/dist/lib/spikes.d.ts +0 -18
  77. package/dist/lib/spikes.d.ts.map +0 -1
  78. package/dist/lib/sync-all.d.ts +0 -28
  79. package/dist/lib/sync-all.d.ts.map +0 -1
  80. package/dist/lib/watch-paths.d.ts +0 -3
  81. package/dist/lib/watch-paths.d.ts.map +0 -1
  82. package/dist/mcp/http.d.ts +0 -12
  83. package/dist/mcp/http.d.ts.map +0 -1
  84. package/dist/mcp/server.d.ts +0 -4
  85. package/dist/mcp/server.d.ts.map +0 -1
  86. package/dist/otel/index.d.ts +0 -3
  87. package/dist/otel/index.d.ts.map +0 -1
  88. package/dist/otel/index.js +0 -1372
package/dist/index.js CHANGED
@@ -14,7 +14,6 @@ var __export = (target, all) => {
14
14
  });
15
15
  };
16
16
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
17
- var __require = import.meta.require;
18
17
 
19
18
  // src/lib/pricing.ts
20
19
  var exports_pricing = {};
@@ -28,503 +27,88 @@ __export(exports_pricing, {
28
27
  DEFAULT_PRICING: () => DEFAULT_PRICING
29
28
  });
30
29
  function normalizeModelName(raw) {
31
- return raw.trim().toLowerCase().replace(/^models\//, "").replace(/^[a-z0-9_.-]+\//, "").replace(/:.+$/, "").replace(/-\d{8}$/, "").replace(/-\d{4}-\d{2}-\d{2}$/, "");
32
- }
33
- function normalizeModelNamePreservingProvider(raw) {
34
- return raw.trim().toLowerCase().replace(/^models\//, "").replace(/:.+$/, "").replace(/-\d{8}$/, "").replace(/-\d{4}-\d{2}-\d{2}$/, "");
35
- }
36
- function modelLookupKeys(raw) {
37
- const withProvider = normalizeModelNamePreservingProvider(raw);
38
- const withoutProvider = normalizeModelName(raw);
39
- return withProvider === withoutProvider ? [withoutProvider] : [withProvider, withoutProvider];
40
- }
41
- function bestPrefixMatch(normalized, entries) {
42
- let best = null;
43
- for (const entry of entries) {
44
- const [key] = entry;
45
- if (normalized !== key && !normalized.startsWith(`${key}-`))
46
- continue;
47
- if (!best || key.length > best[0].length)
48
- best = entry;
49
- }
50
- return best?.[1] ?? null;
51
- }
52
- function bestModelMatch(model, entries) {
53
- for (const key of modelLookupKeys(model)) {
54
- const match = bestPrefixMatch(key, entries);
55
- if (match)
56
- return match;
57
- }
58
- return null;
59
- }
60
- function exactModelMatch(model, entries) {
61
- for (const key of modelLookupKeys(model)) {
62
- const match = entries.find(([entryKey]) => entryKey === key);
63
- if (match)
64
- return match[1];
65
- }
66
- return null;
30
+ return raw.replace(/-\d{8}$/, "").replace(/-\d{4}-\d{2}-\d{2}$/, "").toLowerCase();
67
31
  }
68
32
  function ensurePricingSeeded(db) {
69
33
  seedModelPricing(db, DEFAULT_PRICING);
70
- repairLegacySeededPricing(db);
71
- repairMissingDefaultCacheWrite1h(db);
72
- repairMissingDefaultCacheStorage(db);
73
- removeDeprecatedDefaultPricing(db);
74
- }
75
- function repairLegacySeededPricing(db) {
76
- const now = new Date().toISOString();
77
- const legacyModels = new Set([
78
- ...Object.keys(LEGACY_DEFAULT_PRICING),
79
- ...Object.keys(ADDITIONAL_LEGACY_DEFAULT_PRICING)
80
- ]);
81
- for (const model of legacyModels) {
82
- const current = getModelPricing(db, model);
83
- const next = DEFAULT_PRICING[model];
84
- if (!current || !next)
85
- continue;
86
- const legacy = LEGACY_DEFAULT_PRICING[model];
87
- const legacyRows = [
88
- ...legacy ? [legacy] : [],
89
- ...ADDITIONAL_LEGACY_DEFAULT_PRICING[model] ?? []
90
- ];
91
- if (!legacyRows.some((row) => samePricing(current, row)))
92
- continue;
93
- upsertModelPricing(db, {
94
- model,
95
- input_per_1m: next.inputPer1M,
96
- output_per_1m: next.outputPer1M,
97
- cache_read_per_1m: next.cacheReadPer1M,
98
- cache_write_per_1m: next.cacheWritePer1M,
99
- cache_write_1h_per_1m: next.cacheWrite1hPer1M ?? 0,
100
- cache_storage_per_1m_hour: next.cacheStoragePer1MHour ?? 0,
101
- updated_at: now
102
- });
103
- }
104
- }
105
- function repairMissingDefaultCacheWrite1h(db) {
106
- const now = new Date().toISOString();
107
- for (const [model, next] of Object.entries(DEFAULT_PRICING)) {
108
- if (!next.cacheWrite1hPer1M)
109
- continue;
110
- const current = getModelPricing(db, model);
111
- if (!current)
112
- continue;
113
- if ((current.cache_write_1h_per_1m ?? 0) !== 0)
114
- continue;
115
- if (!sameBasePricing(current, next))
116
- continue;
117
- upsertModelPricing(db, {
118
- model,
119
- input_per_1m: current.input_per_1m,
120
- output_per_1m: current.output_per_1m,
121
- cache_read_per_1m: current.cache_read_per_1m,
122
- cache_write_per_1m: current.cache_write_per_1m,
123
- cache_write_1h_per_1m: next.cacheWrite1hPer1M,
124
- cache_storage_per_1m_hour: current.cache_storage_per_1m_hour ?? next.cacheStoragePer1MHour ?? 0,
125
- updated_at: now
126
- });
127
- }
128
- }
129
- function repairMissingDefaultCacheStorage(db) {
130
- const now = new Date().toISOString();
131
- for (const [model, next] of Object.entries(DEFAULT_PRICING)) {
132
- if (!next.cacheStoragePer1MHour)
133
- continue;
134
- const current = getModelPricing(db, model);
135
- if (!current)
136
- continue;
137
- if ((current.cache_storage_per_1m_hour ?? 0) !== 0)
138
- continue;
139
- if (!sameBasePricing(current, next))
140
- continue;
141
- upsertModelPricing(db, {
142
- model,
143
- input_per_1m: current.input_per_1m,
144
- output_per_1m: current.output_per_1m,
145
- cache_read_per_1m: current.cache_read_per_1m,
146
- cache_write_per_1m: current.cache_write_per_1m,
147
- cache_write_1h_per_1m: current.cache_write_1h_per_1m ?? next.cacheWrite1hPer1M ?? 0,
148
- cache_storage_per_1m_hour: next.cacheStoragePer1MHour,
149
- updated_at: now
150
- });
151
- }
152
- }
153
- function removeDeprecatedDefaultPricing(db) {
154
- for (const [model, removedRows] of Object.entries(REMOVED_DEFAULT_PRICING)) {
155
- const current = getModelPricing(db, model);
156
- if (!current)
157
- continue;
158
- if (!removedRows.some((row) => samePricing(current, row)))
159
- continue;
160
- deleteModelPricing(db, model);
161
- }
162
- }
163
- function sameBasePricing(row, pricing) {
164
- return row.input_per_1m === pricing.inputPer1M && row.output_per_1m === pricing.outputPer1M && row.cache_read_per_1m === pricing.cacheReadPer1M && row.cache_write_per_1m === pricing.cacheWritePer1M;
165
- }
166
- function samePricing(row, pricing) {
167
- return row.input_per_1m === pricing.inputPer1M && row.output_per_1m === pricing.outputPer1M && row.cache_read_per_1m === pricing.cacheReadPer1M && row.cache_write_per_1m === pricing.cacheWritePer1M && (row.cache_write_1h_per_1m ?? 0) === (pricing.cacheWrite1hPer1M ?? 0) && (row.cache_storage_per_1m_hour ?? 0) === (pricing.cacheStoragePer1MHour ?? 0);
168
34
  }
169
35
  function getPricingFromDb(db, model) {
170
- if (isFreeModel(model))
171
- return FREE_PRICING;
172
- for (const key of modelLookupKeys(model)) {
173
- const row = getModelPricing(db, key);
174
- if (row)
175
- return modelPricingFromDbRow(row);
36
+ const normalized = normalizeModelName(model);
37
+ const row = getModelPricing(db, normalized);
38
+ if (row) {
39
+ return {
40
+ inputPer1M: row.input_per_1m,
41
+ outputPer1M: row.output_per_1m,
42
+ cacheReadPer1M: row.cache_read_per_1m,
43
+ cacheWritePer1M: row.cache_write_per_1m
44
+ };
176
45
  }
177
46
  const allRows = db.prepare(`SELECT * FROM model_pricing`).all();
178
- const match = bestModelMatch(model, allRows.map((r) => [r.model, r]));
179
- if (!match)
180
- return null;
181
- return modelPricingFromDbRow(match);
182
- }
183
- function modelPricingFromDbRow(row) {
184
- const seeded = DEFAULT_PRICING[row.model];
185
- const cacheWrite1hPer1M = seeded?.cacheWrite1hPer1M && (row.cache_write_1h_per_1m ?? 0) === 0 && sameBasePricing(row, seeded) ? seeded.cacheWrite1hPer1M : row.cache_write_1h_per_1m ?? 0;
186
- return {
187
- inputPer1M: row.input_per_1m,
188
- outputPer1M: row.output_per_1m,
189
- cacheReadPer1M: row.cache_read_per_1m,
190
- cacheWritePer1M: row.cache_write_per_1m,
191
- cacheWrite1hPer1M,
192
- cacheStoragePer1MHour: row.cache_storage_per_1m_hour ?? seeded?.cacheStoragePer1MHour ?? 0
193
- };
47
+ for (const r of allRows) {
48
+ if (normalized.startsWith(r.model)) {
49
+ return { inputPer1M: r.input_per_1m, outputPer1M: r.output_per_1m, cacheReadPer1M: r.cache_read_per_1m, cacheWritePer1M: r.cache_write_per_1m };
50
+ }
51
+ }
52
+ return null;
194
53
  }
195
54
  function getPricing(model) {
196
- if (isFreeModel(model))
197
- return FREE_PRICING;
198
- return bestModelMatch(model, Object.entries(DEFAULT_PRICING));
199
- }
200
- function isFreeModel(model) {
201
- return model.trim().toLowerCase().endsWith(":free");
55
+ const normalized = normalizeModelName(model);
56
+ if (DEFAULT_PRICING[normalized])
57
+ return DEFAULT_PRICING[normalized] ?? null;
58
+ for (const key of Object.keys(DEFAULT_PRICING)) {
59
+ if (normalized.startsWith(key))
60
+ return DEFAULT_PRICING[key] ?? null;
61
+ }
62
+ return null;
202
63
  }
203
- function computeCost(model, inputTokens, outputTokens, cacheReadTokens = 0, cacheWriteTokens = 0, cacheWrite1hTokens = 0, cacheStorageTokenHours = 0) {
64
+ function computeCost(model, inputTokens, outputTokens, cacheReadTokens = 0, cacheWriteTokens = 0) {
204
65
  const pricing = getPricing(model);
205
66
  if (!pricing)
206
67
  return 0;
207
- return computeCostWithPricing(model, pricing, inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens, cacheWrite1hTokens, cacheStorageTokenHours);
68
+ return (inputTokens * pricing.inputPer1M + outputTokens * pricing.outputPer1M + cacheReadTokens * pricing.cacheReadPer1M + cacheWriteTokens * pricing.cacheWritePer1M) / 1e6;
208
69
  }
209
- function computeCostFromDb(db, model, inputTokens, outputTokens, cacheReadTokens = 0, cacheWriteTokens = 0, cacheWrite1hTokens = 0, cacheStorageTokenHours = 0) {
70
+ function computeCostFromDb(db, model, inputTokens, outputTokens, cacheReadTokens = 0, cacheWriteTokens = 0) {
210
71
  const pricing = getPricingFromDb(db, model) ?? getPricing(model);
211
72
  if (!pricing)
212
73
  return 0;
213
- return computeCostWithPricing(model, pricing, inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens, cacheWrite1hTokens, cacheStorageTokenHours);
74
+ return (inputTokens * pricing.inputPer1M + outputTokens * pricing.outputPer1M + cacheReadTokens * pricing.cacheReadPer1M + cacheWriteTokens * pricing.cacheWritePer1M) / 1e6;
214
75
  }
215
- function computeCostWithPricing(model, pricing, inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens, cacheWrite1hTokens, cacheStorageTokenHours) {
216
- if (isFreeModel(model))
217
- return 0;
218
- let effective = pricing;
219
- const promptTier = bestModelMatch(model, Object.entries(GEMINI_PROMPT_TIERS)) ?? bestModelMatch(model, Object.entries(QWEN_PROMPT_TIERS)) ?? bestModelMatch(model, Object.entries(MINIMAX_PROMPT_TIERS)) ?? bestModelMatch(model, Object.entries(XAI_PROMPT_TIERS)) ?? exactModelMatch(model, Object.entries(OPENAI_PROMPT_TIERS));
220
- if (promptTier) {
221
- const billablePromptTokens = inputTokens + cacheReadTokens + cacheWriteTokens + cacheWrite1hTokens;
222
- if (billablePromptTokens > promptTier.threshold) {
223
- effective = {
224
- ...pricing,
225
- inputPer1M: promptTier.inputPer1M ?? pricing.inputPer1M * (promptTier.inputMultiplier ?? 1),
226
- outputPer1M: promptTier.outputPer1M ?? pricing.outputPer1M * (promptTier.outputMultiplier ?? 1),
227
- cacheReadPer1M: promptTier.cacheReadPer1M ?? pricing.cacheReadPer1M * (promptTier.cacheReadMultiplier ?? 1),
228
- cacheWritePer1M: promptTier.cacheWritePer1M ?? pricing.cacheWritePer1M * (promptTier.cacheWriteMultiplier ?? 1)
229
- };
230
- }
231
- }
232
- return (inputTokens * effective.inputPer1M + outputTokens * effective.outputPer1M + cacheReadTokens * effective.cacheReadPer1M + cacheWriteTokens * effective.cacheWritePer1M + cacheWrite1hTokens * (effective.cacheWrite1hPer1M ?? effective.cacheWritePer1M) + cacheStorageTokenHours * (effective.cacheStoragePer1MHour ?? 0)) / 1e6;
233
- }
234
- var DEFAULT_PRICING, LEGACY_DEFAULT_PRICING, ADDITIONAL_LEGACY_DEFAULT_PRICING, REMOVED_DEFAULT_PRICING, FREE_PRICING, GEMINI_PROMPT_TIERS, OPENAI_PROMPT_TIERS, QWEN_PROMPT_TIERS, MINIMAX_PROMPT_TIERS, XAI_PROMPT_TIERS;
76
+ var DEFAULT_PRICING;
235
77
  var init_pricing = __esm(() => {
236
78
  init_database();
237
79
  DEFAULT_PRICING = {
238
- "claude-opus-4-7": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25, cacheWrite1hPer1M: 10 },
239
- "claude-opus-4-6": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25, cacheWrite1hPer1M: 10 },
240
- "claude-opus-4-5": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25, cacheWrite1hPer1M: 10 },
241
- "claude-opus-4-1": { inputPer1M: 15, outputPer1M: 75, cacheReadPer1M: 1.5, cacheWritePer1M: 18.75, cacheWrite1hPer1M: 30 },
242
- "claude-opus-4": { inputPer1M: 15, outputPer1M: 75, cacheReadPer1M: 1.5, cacheWritePer1M: 18.75, cacheWrite1hPer1M: 30 },
243
- "claude-sonnet-4-6": { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.3, cacheWritePer1M: 3.75, cacheWrite1hPer1M: 6 },
244
- "claude-sonnet-4-5": { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.3, cacheWritePer1M: 3.75, cacheWrite1hPer1M: 6 },
245
- "claude-sonnet-4": { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.3, cacheWritePer1M: 3.75, cacheWrite1hPer1M: 6 },
246
- "claude-3-7-sonnet": { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.3, cacheWritePer1M: 3.75, cacheWrite1hPer1M: 6 },
247
- "claude-haiku-4-5": { inputPer1M: 1, outputPer1M: 5, cacheReadPer1M: 0.1, cacheWritePer1M: 1.25, cacheWrite1hPer1M: 2 },
248
- "claude-3-5-haiku": { inputPer1M: 0.8, outputPer1M: 4, cacheReadPer1M: 0.08, cacheWritePer1M: 1, cacheWrite1hPer1M: 1.6 },
249
- "claude-3-opus": { inputPer1M: 15, outputPer1M: 75, cacheReadPer1M: 1.5, cacheWritePer1M: 18.75, cacheWrite1hPer1M: 30 },
250
- "claude-3-haiku": { inputPer1M: 0.25, outputPer1M: 1.25, cacheReadPer1M: 0.03, cacheWritePer1M: 0.3, cacheWrite1hPer1M: 0.5 },
251
- "gemini-3.1-pro-preview": { inputPer1M: 2, outputPer1M: 12, cacheReadPer1M: 0.2, cacheWritePer1M: 0, cacheStoragePer1MHour: 4.5 },
252
- "gemini-3.1-flash-lite-preview": { inputPer1M: 0.25, outputPer1M: 1.5, cacheReadPer1M: 0.025, cacheWritePer1M: 0, cacheStoragePer1MHour: 1 },
253
- "gemini-3.1-flash-lite": { inputPer1M: 0.25, outputPer1M: 1.5, cacheReadPer1M: 0.025, cacheWritePer1M: 0, cacheStoragePer1MHour: 1 },
254
- "gemini-3-flash-preview": { inputPer1M: 0.5, outputPer1M: 3, cacheReadPer1M: 0.05, cacheWritePer1M: 0, cacheStoragePer1MHour: 1 },
255
- "gemini-2.5-pro": { inputPer1M: 1.25, outputPer1M: 10, cacheReadPer1M: 0.125, cacheWritePer1M: 0, cacheStoragePer1MHour: 4.5 },
256
- "gemini-2.5-flash": { inputPer1M: 0.3, outputPer1M: 2.5, cacheReadPer1M: 0.03, cacheWritePer1M: 0, cacheStoragePer1MHour: 1 },
257
- "gemini-2.5-flash-lite": { inputPer1M: 0.1, outputPer1M: 0.4, cacheReadPer1M: 0.01, cacheWritePer1M: 0, cacheStoragePer1MHour: 1 },
258
- "gemini-2.0-flash": { inputPer1M: 0.1, outputPer1M: 0.4, cacheReadPer1M: 0.025, cacheWritePer1M: 0, cacheStoragePer1MHour: 1 },
259
- "gemini-2.0-flash-lite": { inputPer1M: 0.075, outputPer1M: 0.3, cacheReadPer1M: 0, cacheWritePer1M: 0 },
260
- "google/gemini-3.1-pro-preview": { inputPer1M: 2, outputPer1M: 12, cacheReadPer1M: 0.2, cacheWritePer1M: 0.375 },
261
- "google/gemini-3.1-flash-lite-preview": { inputPer1M: 0.25, outputPer1M: 1.5, cacheReadPer1M: 0.025, cacheWritePer1M: 0.08333333333333334 },
262
- "google/gemini-3.1-flash-lite": { inputPer1M: 0.25, outputPer1M: 1.5, cacheReadPer1M: 0.025, cacheWritePer1M: 0.08333333333333334 },
263
- "google/gemini-3-flash-preview": { inputPer1M: 0.5, outputPer1M: 3, cacheReadPer1M: 0.05, cacheWritePer1M: 0.08333333333333334 },
264
- "google/gemini-2.5-pro": { inputPer1M: 1.25, outputPer1M: 10, cacheReadPer1M: 0.125, cacheWritePer1M: 0.375 },
265
- "google/gemini-2.5-flash": { inputPer1M: 0.3, outputPer1M: 2.5, cacheReadPer1M: 0.03, cacheWritePer1M: 0.08333333333333334 },
266
- "google/gemini-2.5-flash-lite": { inputPer1M: 0.1, outputPer1M: 0.4, cacheReadPer1M: 0.01, cacheWritePer1M: 0.08333333333333334 },
267
- "gpt-5.5": { inputPer1M: 5, outputPer1M: 30, cacheReadPer1M: 0.5, cacheWritePer1M: 0 },
268
- "gpt-5.5-pro": { inputPer1M: 30, outputPer1M: 180, cacheReadPer1M: 0, cacheWritePer1M: 0 },
269
- "gpt-5.4": { inputPer1M: 2.5, outputPer1M: 15, cacheReadPer1M: 0.25, cacheWritePer1M: 0 },
270
- "gpt-5.4-pro": { inputPer1M: 30, outputPer1M: 180, cacheReadPer1M: 0, cacheWritePer1M: 0 },
271
- "gpt-5.4-mini": { inputPer1M: 0.75, outputPer1M: 4.5, cacheReadPer1M: 0.075, cacheWritePer1M: 0 },
272
- "gpt-5.4-nano": { inputPer1M: 0.2, outputPer1M: 1.25, cacheReadPer1M: 0.02, cacheWritePer1M: 0 },
273
- "gpt-5.3-codex": { inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.175, cacheWritePer1M: 0 },
274
- "gpt-5.2-codex": { inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.175, cacheWritePer1M: 0 },
275
- "gpt-5.2-chat-latest": { inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.175, cacheWritePer1M: 0 },
276
- "gpt-5.2": { inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.175, cacheWritePer1M: 0 },
277
- "gpt-5-codex": { inputPer1M: 1.25, outputPer1M: 10, cacheReadPer1M: 0.125, cacheWritePer1M: 0 },
278
- "gpt-5-mini": { inputPer1M: 0.25, outputPer1M: 2, cacheReadPer1M: 0.025, cacheWritePer1M: 0 },
279
- "gpt-5": { inputPer1M: 1.25, outputPer1M: 10, cacheReadPer1M: 0.125, cacheWritePer1M: 0 },
280
- "gpt-4o": { inputPer1M: 2.5, outputPer1M: 10, cacheReadPer1M: 1.25, cacheWritePer1M: 0 },
281
- "gpt-4o-mini": { inputPer1M: 0.15, outputPer1M: 0.6, cacheReadPer1M: 0.075, cacheWritePer1M: 0 },
282
- o1: { inputPer1M: 15, outputPer1M: 60, cacheReadPer1M: 7.5, cacheWritePer1M: 0 },
283
- "o1-mini": { inputPer1M: 1.1, outputPer1M: 4.4, cacheReadPer1M: 0.55, cacheWritePer1M: 0 },
284
- o3: { inputPer1M: 2, outputPer1M: 8, cacheReadPer1M: 0.5, cacheWritePer1M: 0 },
285
- "o3-mini": { inputPer1M: 1.1, outputPer1M: 4.4, cacheReadPer1M: 0.55, cacheWritePer1M: 0 },
286
- "o4-mini": { inputPer1M: 1.1, outputPer1M: 4.4, cacheReadPer1M: 0.275, cacheWritePer1M: 0 },
287
- "qwen3.6-plus": { inputPer1M: 0.325, outputPer1M: 1.95, cacheReadPer1M: 0.0325, cacheWritePer1M: 0.40625 },
288
- "qwen3.6-flash": { inputPer1M: 0.25, outputPer1M: 1.5, cacheReadPer1M: 0.025, cacheWritePer1M: 0.3125 },
289
- "qwen3.6-35b-a3b": { inputPer1M: 0.15, outputPer1M: 1, cacheReadPer1M: 0.05, cacheWritePer1M: 0 },
290
- "qwen3.6-max-preview": { inputPer1M: 1.04, outputPer1M: 6.24, cacheReadPer1M: 0.104, cacheWritePer1M: 1.3 },
291
- "qwen3.6-27b": { inputPer1M: 0.32, outputPer1M: 3.2, cacheReadPer1M: 0, cacheWritePer1M: 0 },
292
- "qwen/qwen3.6-plus": { inputPer1M: 0.325, outputPer1M: 1.95, cacheReadPer1M: 0.0325, cacheWritePer1M: 0.40625 },
293
- "qwen/qwen3.6-flash": { inputPer1M: 0.25, outputPer1M: 1.5, cacheReadPer1M: 0.025, cacheWritePer1M: 0.3125 },
294
- "qwen/qwen3.6-35b-a3b": { inputPer1M: 0.15, outputPer1M: 1, cacheReadPer1M: 0.05, cacheWritePer1M: 0 },
295
- "qwen/qwen3.6-max-preview": { inputPer1M: 1.04, outputPer1M: 6.24, cacheReadPer1M: 0.104, cacheWritePer1M: 1.3 },
296
- "qwen/qwen3.6-27b": { inputPer1M: 0.32, outputPer1M: 3.2, cacheReadPer1M: 0, cacheWritePer1M: 0 },
297
- "minimax-m2.7": { inputPer1M: 0.3, outputPer1M: 1.2, cacheReadPer1M: 0.06, cacheWritePer1M: 0.375 },
298
- "minimax-m2.7-highspeed": { inputPer1M: 0.6, outputPer1M: 2.4, cacheReadPer1M: 0.06, cacheWritePer1M: 0.375 },
299
- "minimax/minimax-m2.7": { inputPer1M: 0.299, outputPer1M: 1.2, cacheReadPer1M: 0, cacheWritePer1M: 0 },
300
- "minimax-m1": { inputPer1M: 0.4, outputPer1M: 2.2, cacheReadPer1M: 0, cacheWritePer1M: 0 },
301
- "minimax/minimax-m1": { inputPer1M: 0.4, outputPer1M: 2.2, cacheReadPer1M: 0, cacheWritePer1M: 0 },
302
- "grok-4.3": { inputPer1M: 1.25, outputPer1M: 2.5, cacheReadPer1M: 0.2, cacheWritePer1M: 0 },
303
- "grok-latest": { inputPer1M: 1.25, outputPer1M: 2.5, cacheReadPer1M: 0.2, cacheWritePer1M: 0 },
304
- "grok-4.20": { inputPer1M: 1.25, outputPer1M: 2.5, cacheReadPer1M: 0.2, cacheWritePer1M: 0 },
305
- "grok-4-1-fast": { inputPer1M: 0.2, outputPer1M: 0.5, cacheReadPer1M: 0.05, cacheWritePer1M: 0 },
306
- "grok-4-fast": { inputPer1M: 0.2, outputPer1M: 0.5, cacheReadPer1M: 0.05, cacheWritePer1M: 0 },
307
- "grok-4": { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.75, cacheWritePer1M: 0 },
308
- "grok-code-fast-1": { inputPer1M: 0.2, outputPer1M: 1.5, cacheReadPer1M: 0.02, cacheWritePer1M: 0 },
309
- "grok-code-fast": { inputPer1M: 0.2, outputPer1M: 1.5, cacheReadPer1M: 0.02, cacheWritePer1M: 0 },
310
- "grok-3": { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.75, cacheWritePer1M: 0 },
311
- "grok-3-mini": { inputPer1M: 0.3, outputPer1M: 0.5, cacheReadPer1M: 0.07, cacheWritePer1M: 0 },
312
- "glm-5.1": { inputPer1M: 1.4, outputPer1M: 4.4, cacheReadPer1M: 0.26, cacheWritePer1M: 0 },
313
- "glm-5": { inputPer1M: 1, outputPer1M: 3.2, cacheReadPer1M: 0.2, cacheWritePer1M: 0 },
314
- "z-ai/glm-5.1": { inputPer1M: 1.05, outputPer1M: 3.5, cacheReadPer1M: 0.525, cacheWritePer1M: 0 },
315
- "z-ai/glm-5": { inputPer1M: 0.6, outputPer1M: 1.92, cacheReadPer1M: 0.12, cacheWritePer1M: 0 },
316
- "kimi-k2.6": { inputPer1M: 0.95, outputPer1M: 4, cacheReadPer1M: 0.16, cacheWritePer1M: 0 },
317
- "kimi-k2.5": { inputPer1M: 0.6, outputPer1M: 3, cacheReadPer1M: 0.1, cacheWritePer1M: 0 },
318
- "kimi-k2": { inputPer1M: 0.6, outputPer1M: 2.5, cacheReadPer1M: 0.15, cacheWritePer1M: 0 },
319
- "moonshotai/kimi-k2.6": { inputPer1M: 0.75, outputPer1M: 3.5, cacheReadPer1M: 0.15, cacheWritePer1M: 0 },
320
- "moonshotai/kimi-k2.5": { inputPer1M: 0.44, outputPer1M: 2, cacheReadPer1M: 0.22, cacheWritePer1M: 0 },
321
- "moonshotai/kimi-k2": { inputPer1M: 0.57, outputPer1M: 2.3, cacheReadPer1M: 0, cacheWritePer1M: 0 }
322
- };
323
- LEGACY_DEFAULT_PRICING = {
80
+ "claude-opus-4-6": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25 },
81
+ "claude-opus-4-5": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25 },
82
+ "claude-sonnet-4-6": { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.3, cacheWritePer1M: 3.75 },
83
+ "claude-sonnet-4-5": { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.3, cacheWritePer1M: 3.75 },
84
+ "claude-haiku-4-5": { inputPer1M: 1, outputPer1M: 5, cacheReadPer1M: 0.1, cacheWritePer1M: 1.25 },
85
+ "claude-3-5-sonnet": { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.3, cacheWritePer1M: 3.75 },
324
86
  "claude-3-5-haiku": { inputPer1M: 1, outputPer1M: 5, cacheReadPer1M: 0.1, cacheWritePer1M: 1.25 },
325
- "claude-opus-4": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25 },
326
- "gemini-2.5-pro": { inputPer1M: 1.25, outputPer1M: 10, cacheReadPer1M: 0.31, cacheWritePer1M: 0 },
327
- "gemini-2.5-flash": { inputPer1M: 0.15, outputPer1M: 0.6, cacheReadPer1M: 0, cacheWritePer1M: 0 },
87
+ "claude-3-opus": { inputPer1M: 15, outputPer1M: 75, cacheReadPer1M: 1.5, cacheWritePer1M: 18.75 },
88
+ "claude-3-sonnet": { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.3, cacheWritePer1M: 3.75 },
89
+ "claude-3-haiku": { inputPer1M: 0.25, outputPer1M: 1.25, cacheReadPer1M: 0.03, cacheWritePer1M: 0.3 },
328
90
  "gemini-2.0-flash": { inputPer1M: 0.075, outputPer1M: 0.3, cacheReadPer1M: 0, cacheWritePer1M: 0 },
91
+ "gemini-2.5-pro": { inputPer1M: 1.25, outputPer1M: 10, cacheReadPer1M: 0, cacheWritePer1M: 0 },
92
+ "gemini-1.5-pro": { inputPer1M: 1.25, outputPer1M: 5, cacheReadPer1M: 0, cacheWritePer1M: 0 },
93
+ "gemini-1.5-flash": { inputPer1M: 0.075, outputPer1M: 0.3, cacheReadPer1M: 0, cacheWritePer1M: 0 },
329
94
  "gpt-5.3-codex": { inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.44, cacheWritePer1M: 0 },
330
95
  "gpt-5.2-codex": { inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.44, cacheWritePer1M: 0 },
331
96
  "gpt-5-codex": { inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.44, cacheWritePer1M: 0 },
332
- "gpt-5-mini": { inputPer1M: 0.3, outputPer1M: 1.2, cacheReadPer1M: 0.075, cacheWritePer1M: 0 },
333
- "gpt-5.2": { inputPer1M: 2, outputPer1M: 8, cacheReadPer1M: 0.5, cacheWritePer1M: 0 },
97
+ "gpt-4o": { inputPer1M: 2.5, outputPer1M: 10, cacheReadPer1M: 1.25, cacheWritePer1M: 0 },
98
+ "gpt-4o-mini": { inputPer1M: 0.15, outputPer1M: 0.6, cacheReadPer1M: 0.075, cacheWritePer1M: 0 },
99
+ o1: { inputPer1M: 15, outputPer1M: 60, cacheReadPer1M: 7.5, cacheWritePer1M: 0 },
334
100
  "o1-mini": { inputPer1M: 3, outputPer1M: 12, cacheReadPer1M: 1.5, cacheWritePer1M: 0 },
335
- "grok-3": { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0, cacheWritePer1M: 0 },
336
- "grok-3-mini": { inputPer1M: 0.3, outputPer1M: 0.5, cacheReadPer1M: 0, cacheWritePer1M: 0 },
337
- "qwen3.6-plus": { inputPer1M: 0.8, outputPer1M: 2, cacheReadPer1M: 0, cacheWritePer1M: 0 },
338
- "minimax-m2.7": { inputPer1M: 0.7, outputPer1M: 0.7, cacheReadPer1M: 0, cacheWritePer1M: 0 },
339
- "minimax-m2.7-highspeed": { inputPer1M: 0.7, outputPer1M: 0.7, cacheReadPer1M: 0, cacheWritePer1M: 0 },
340
- "minimax-m1": { inputPer1M: 0.2, outputPer1M: 1.1, cacheReadPer1M: 0, cacheWritePer1M: 0 },
341
- "glm-5.1": { inputPer1M: 0.7, outputPer1M: 0.7, cacheReadPer1M: 0, cacheWritePer1M: 0 },
342
- "glm-5": { inputPer1M: 0.7, outputPer1M: 0.7, cacheReadPer1M: 0, cacheWritePer1M: 0 },
343
- "kimi-k2": { inputPer1M: 0.6, outputPer1M: 0.6, cacheReadPer1M: 0, cacheWritePer1M: 0 },
344
- o3: { inputPer1M: 10, outputPer1M: 40, cacheReadPer1M: 2.5, cacheWritePer1M: 0 }
345
- };
346
- ADDITIONAL_LEGACY_DEFAULT_PRICING = {
347
- "gemini-2.5-pro": [
348
- { inputPer1M: 1.25, outputPer1M: 10, cacheReadPer1M: 0, cacheWritePer1M: 0 }
349
- ],
350
- "qwen3.6-plus": [
351
- { inputPer1M: 0.325, outputPer1M: 1.95, cacheReadPer1M: 0, cacheWritePer1M: 0.40625 },
352
- { inputPer1M: 0.325, outputPer1M: 1.95, cacheReadPer1M: 0.05, cacheWritePer1M: 0.40625 }
353
- ],
354
- "qwen3.6-flash": [
355
- { inputPer1M: 0.25, outputPer1M: 1.5, cacheReadPer1M: 0, cacheWritePer1M: 0.3125 }
356
- ],
357
- "qwen3.6-max-preview": [
358
- { inputPer1M: 1.04, outputPer1M: 6.24, cacheReadPer1M: 0, cacheWritePer1M: 1.3 },
359
- { inputPer1M: 1.04, outputPer1M: 6.24, cacheReadPer1M: 0.13, cacheWritePer1M: 1.3 }
360
- ],
361
- "qwen/qwen3.6-plus": [
362
- { inputPer1M: 0.325, outputPer1M: 1.95, cacheReadPer1M: 0, cacheWritePer1M: 0.40625 },
363
- { inputPer1M: 0.325, outputPer1M: 1.95, cacheReadPer1M: 0.05, cacheWritePer1M: 0.40625 }
364
- ],
365
- "qwen/qwen3.6-flash": [
366
- { inputPer1M: 0.25, outputPer1M: 1.5, cacheReadPer1M: 0, cacheWritePer1M: 0.3125 }
367
- ],
368
- "qwen/qwen3.6-max-preview": [
369
- { inputPer1M: 1.04, outputPer1M: 6.24, cacheReadPer1M: 0, cacheWritePer1M: 1.3 },
370
- { inputPer1M: 1.04, outputPer1M: 6.24, cacheReadPer1M: 0.13, cacheWritePer1M: 1.3 }
371
- ]
372
- };
373
- REMOVED_DEFAULT_PRICING = {
374
- "claude-3-5-sonnet": [
375
- { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.3, cacheWritePer1M: 3.75, cacheWrite1hPer1M: 6 },
376
- { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.3, cacheWritePer1M: 3.75, cacheWrite1hPer1M: 0 }
377
- ],
378
- "claude-3-sonnet": [
379
- { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.3, cacheWritePer1M: 3.75, cacheWrite1hPer1M: 6 },
380
- { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.3, cacheWritePer1M: 3.75, cacheWrite1hPer1M: 0 }
381
- ],
382
- "gemini-3.1-pro": [
383
- { inputPer1M: 2, outputPer1M: 12, cacheReadPer1M: 0.2, cacheWritePer1M: 0 },
384
- { inputPer1M: 1.25, outputPer1M: 10, cacheReadPer1M: 0.31, cacheWritePer1M: 0 }
385
- ],
386
- "gemini-1.5-pro": [
387
- { inputPer1M: 1.25, outputPer1M: 5, cacheReadPer1M: 0, cacheWritePer1M: 0 }
388
- ],
389
- "gemini-1.5-flash": [
390
- { inputPer1M: 0.075, outputPer1M: 0.3, cacheReadPer1M: 0, cacheWritePer1M: 0 }
391
- ],
392
- "gpt-5.3-chat": [
393
- { inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.175, cacheWritePer1M: 0 },
394
- { inputPer1M: 2, outputPer1M: 8, cacheReadPer1M: 0.5, cacheWritePer1M: 0 }
395
- ],
396
- "qwen3.6": [
397
- { inputPer1M: 0.3, outputPer1M: 0.6, cacheReadPer1M: 0, cacheWritePer1M: 0 }
398
- ]
399
- };
400
- FREE_PRICING = {
401
- inputPer1M: 0,
402
- outputPer1M: 0,
403
- cacheReadPer1M: 0,
404
- cacheWritePer1M: 0,
405
- cacheWrite1hPer1M: 0,
406
- cacheStoragePer1MHour: 0
407
- };
408
- GEMINI_PROMPT_TIERS = {
409
- "gemini-3.1-pro-preview": {
410
- threshold: 200000,
411
- inputPer1M: 4,
412
- outputPer1M: 18,
413
- cacheReadPer1M: 0.4
414
- },
415
- "gemini-2.5-pro": {
416
- threshold: 200000,
417
- inputPer1M: 2.5,
418
- outputPer1M: 15,
419
- cacheReadPer1M: 0.25
420
- }
421
- };
422
- OPENAI_PROMPT_TIERS = {
423
- "gpt-5.5": {
424
- threshold: 272000,
425
- inputMultiplier: 2,
426
- outputMultiplier: 1.5,
427
- cacheReadMultiplier: 2
428
- },
429
- "gpt-5.4-pro": {
430
- threshold: 272000,
431
- inputMultiplier: 2,
432
- outputMultiplier: 1.5,
433
- cacheReadMultiplier: 2
434
- },
435
- "gpt-5.4": {
436
- threshold: 272000,
437
- inputMultiplier: 2,
438
- outputMultiplier: 1.5,
439
- cacheReadMultiplier: 2
440
- }
441
- };
442
- QWEN_PROMPT_TIERS = {
443
- "qwen3.6-plus": {
444
- threshold: 256000,
445
- inputPer1M: 1.3,
446
- outputPer1M: 3.9,
447
- cacheReadPer1M: 0.13,
448
- cacheWritePer1M: 1.625
449
- },
450
- "qwen3.6-flash": {
451
- threshold: 256000,
452
- inputPer1M: 1,
453
- outputPer1M: 4,
454
- cacheReadPer1M: 0.1,
455
- cacheWritePer1M: 1.25
456
- },
457
- "qwen3.6-max-preview": {
458
- threshold: 128000,
459
- inputPer1M: 1.6,
460
- outputPer1M: 9.6,
461
- cacheReadPer1M: 0.16,
462
- cacheWritePer1M: 2
463
- }
464
- };
465
- MINIMAX_PROMPT_TIERS = {
466
- "minimax/minimax-m1": {
467
- threshold: Number.POSITIVE_INFINITY
468
- },
469
- "minimax-m1": {
470
- threshold: 200000,
471
- inputPer1M: 1.3
472
- }
473
- };
474
- XAI_PROMPT_TIERS = {
475
- "grok-4.3": {
476
- threshold: 200000,
477
- inputPer1M: 2.5,
478
- outputPer1M: 5,
479
- cacheReadPer1M: 0.4
480
- },
481
- "grok-latest": {
482
- threshold: 200000,
483
- inputPer1M: 2.5,
484
- outputPer1M: 5,
485
- cacheReadPer1M: 0.4
486
- },
487
- "grok-4.20": {
488
- threshold: 200000,
489
- inputPer1M: 2.5,
490
- outputPer1M: 5,
491
- cacheReadPer1M: 0.4
492
- },
493
- "grok-4-1-fast": {
494
- threshold: 128000,
495
- inputPer1M: 0.4,
496
- outputPer1M: 1,
497
- cacheReadPer1M: 0
498
- },
499
- "grok-4-fast": {
500
- threshold: 128000,
501
- inputPer1M: 0.4,
502
- outputPer1M: 1,
503
- cacheReadPer1M: 0
504
- },
505
- "grok-4": {
506
- threshold: 128000,
507
- inputPer1M: 6,
508
- outputPer1M: 30,
509
- cacheReadPer1M: 0
510
- }
101
+ o3: { inputPer1M: 10, outputPer1M: 40, cacheReadPer1M: 2.5, cacheWritePer1M: 0 },
102
+ "o3-mini": { inputPer1M: 1.1, outputPer1M: 4.4, cacheReadPer1M: 0.55, cacheWritePer1M: 0 },
103
+ "o4-mini": { inputPer1M: 1.1, outputPer1M: 4.4, cacheReadPer1M: 0.275, cacheWritePer1M: 0 }
511
104
  };
512
105
  });
513
106
 
514
107
  // src/db/database.ts
515
108
  import { SqliteAdapter as Database } from "@hasna/cloud";
516
109
  import { copyFileSync, existsSync, mkdirSync, readdirSync, statSync } from "fs";
517
- import { hostname } from "os";
518
110
  import { homedir } from "os";
519
111
  import { join } from "path";
520
- function getMachineId() {
521
- if (process.env["ECONOMY_MACHINE_ID"])
522
- return process.env["ECONOMY_MACHINE_ID"];
523
- const h = hostname().toLowerCase();
524
- if (h.startsWith("spark") || h.startsWith("apple"))
525
- return h.split(".")[0];
526
- return h.split(".")[0];
527
- }
528
112
  function getDataDir() {
529
113
  const home = process.env["HOME"] || process.env["USERPROFILE"] || homedir();
530
114
  const newDir = join(home, ".hasna", "economy");
@@ -557,7 +141,6 @@ function openDatabase(dbPath, skipSeed = false) {
557
141
  }
558
142
  const db = new Database(path);
559
143
  db.exec("PRAGMA journal_mode = WAL");
560
- db.exec("PRAGMA busy_timeout = 5000");
561
144
  db.exec("PRAGMA foreign_keys = ON");
562
145
  initSchema(db);
563
146
  if (!skipSeed) {
@@ -576,13 +159,10 @@ function initSchema(db) {
576
159
  output_tokens INTEGER DEFAULT 0,
577
160
  cache_read_tokens INTEGER DEFAULT 0,
578
161
  cache_create_tokens INTEGER DEFAULT 0,
579
- cache_create_5m_tokens INTEGER DEFAULT 0,
580
- cache_create_1h_tokens INTEGER DEFAULT 0,
581
162
  cost_usd REAL NOT NULL DEFAULT 0,
582
163
  duration_ms INTEGER DEFAULT 0,
583
164
  timestamp TEXT NOT NULL,
584
- source_request_id TEXT,
585
- machine_id TEXT DEFAULT ''
165
+ source_request_id TEXT
586
166
  );
587
167
 
588
168
  CREATE TABLE IF NOT EXISTS sessions (
@@ -594,8 +174,7 @@ function initSchema(db) {
594
174
  ended_at TEXT,
595
175
  total_cost_usd REAL DEFAULT 0,
596
176
  total_tokens INTEGER DEFAULT 0,
597
- request_count INTEGER DEFAULT 0,
598
- machine_id TEXT DEFAULT ''
177
+ request_count INTEGER DEFAULT 0
599
178
  );
600
179
 
601
180
  CREATE TABLE IF NOT EXISTS projects (
@@ -648,8 +227,6 @@ function initSchema(db) {
648
227
  output_per_1m REAL NOT NULL DEFAULT 0,
649
228
  cache_read_per_1m REAL NOT NULL DEFAULT 0,
650
229
  cache_write_per_1m REAL NOT NULL DEFAULT 0,
651
- cache_write_1h_per_1m REAL NOT NULL DEFAULT 0,
652
- cache_storage_per_1m_hour REAL NOT NULL DEFAULT 0,
653
230
  updated_at TEXT NOT NULL
654
231
  );
655
232
 
@@ -662,115 +239,6 @@ function initSchema(db) {
662
239
  machine_id TEXT,
663
240
  created_at TEXT NOT NULL DEFAULT (datetime('now'))
664
241
  );
665
-
666
- CREATE TABLE IF NOT EXISTS billing_daily (
667
- date TEXT NOT NULL,
668
- provider TEXT NOT NULL,
669
- description TEXT DEFAULT '',
670
- cost_usd REAL NOT NULL DEFAULT 0,
671
- updated_at TEXT NOT NULL,
672
- PRIMARY KEY (date, provider, description)
673
- );
674
-
675
- CREATE INDEX IF NOT EXISTS idx_billing_date ON billing_daily(date);
676
- CREATE INDEX IF NOT EXISTS idx_billing_provider ON billing_daily(provider);
677
-
678
- CREATE TABLE IF NOT EXISTS subscriptions (
679
- id TEXT PRIMARY KEY,
680
- agent TEXT,
681
- provider TEXT NOT NULL,
682
- plan TEXT NOT NULL,
683
- monthly_fee_usd REAL NOT NULL DEFAULT 0,
684
- included_usage_usd REAL NOT NULL DEFAULT 0,
685
- billing_cycle_start TEXT,
686
- reset_policy TEXT DEFAULT 'monthly',
687
- active INTEGER NOT NULL DEFAULT 1,
688
- created_at TEXT NOT NULL,
689
- updated_at TEXT NOT NULL
690
- );
691
-
692
- CREATE TABLE IF NOT EXISTS usage_snapshots (
693
- id TEXT PRIMARY KEY,
694
- agent TEXT NOT NULL,
695
- date TEXT NOT NULL,
696
- metric TEXT NOT NULL,
697
- value REAL NOT NULL DEFAULT 0,
698
- unit TEXT DEFAULT '',
699
- machine_id TEXT DEFAULT '',
700
- updated_at TEXT NOT NULL
701
- );
702
-
703
- CREATE TABLE IF NOT EXISTS savings_daily (
704
- date TEXT NOT NULL,
705
- agent TEXT DEFAULT '',
706
- api_equivalent_usd REAL NOT NULL DEFAULT 0,
707
- subscription_fee_usd REAL NOT NULL DEFAULT 0,
708
- included_consumed_usd REAL NOT NULL DEFAULT 0,
709
- on_demand_usd REAL NOT NULL DEFAULT 0,
710
- saved_usd REAL NOT NULL DEFAULT 0,
711
- updated_at TEXT NOT NULL,
712
- PRIMARY KEY (date, agent)
713
- );
714
-
715
- CREATE TABLE IF NOT EXISTS machines (
716
- machine_id TEXT PRIMARY KEY,
717
- hostname TEXT NOT NULL,
718
- last_seen_at TEXT,
719
- last_push_at TEXT,
720
- last_pull_at TEXT,
721
- economy_version TEXT,
722
- updated_at TEXT NOT NULL
723
- );
724
-
725
- CREATE INDEX IF NOT EXISTS idx_usage_agent_date ON usage_snapshots(agent, date);
726
- CREATE INDEX IF NOT EXISTS idx_savings_date ON savings_daily(date);
727
- `);
728
- const cols = db.prepare(`PRAGMA table_info(requests)`).all();
729
- if (!cols.some((c) => c.name === "machine_id")) {
730
- db.exec(`ALTER TABLE requests ADD COLUMN machine_id TEXT DEFAULT ''`);
731
- db.exec(`ALTER TABLE sessions ADD COLUMN machine_id TEXT DEFAULT ''`);
732
- }
733
- if (!cols.some((c) => c.name === "cache_create_5m_tokens")) {
734
- db.exec(`ALTER TABLE requests ADD COLUMN cache_create_5m_tokens INTEGER DEFAULT 0`);
735
- db.exec(`UPDATE requests SET cache_create_5m_tokens = cache_create_tokens WHERE cache_create_5m_tokens = 0`);
736
- }
737
- if (!cols.some((c) => c.name === "cache_create_1h_tokens")) {
738
- db.exec(`ALTER TABLE requests ADD COLUMN cache_create_1h_tokens INTEGER DEFAULT 0`);
739
- }
740
- if (!cols.some((c) => c.name === "cost_basis")) {
741
- db.exec(`ALTER TABLE requests ADD COLUMN cost_basis TEXT DEFAULT 'estimated'`);
742
- }
743
- if (!cols.some((c) => c.name === "attribution_tag")) {
744
- db.exec(`ALTER TABLE requests ADD COLUMN attribution_tag TEXT DEFAULT ''`);
745
- }
746
- if (!cols.some((c) => c.name === "updated_at")) {
747
- db.exec(`ALTER TABLE requests ADD COLUMN updated_at TEXT DEFAULT ''`);
748
- db.exec(`UPDATE requests SET updated_at = timestamp WHERE updated_at = '' OR updated_at IS NULL`);
749
- }
750
- if (!cols.some((c) => c.name === "synced_at")) {
751
- db.exec(`ALTER TABLE requests ADD COLUMN synced_at TEXT DEFAULT ''`);
752
- }
753
- const sessionCols = db.prepare(`PRAGMA table_info(sessions)`).all();
754
- if (!sessionCols.some((c) => c.name === "attribution_tag")) {
755
- db.exec(`ALTER TABLE sessions ADD COLUMN attribution_tag TEXT DEFAULT ''`);
756
- }
757
- if (!sessionCols.some((c) => c.name === "updated_at")) {
758
- db.exec(`ALTER TABLE sessions ADD COLUMN updated_at TEXT DEFAULT ''`);
759
- db.exec(`UPDATE sessions SET updated_at = started_at WHERE updated_at = '' OR updated_at IS NULL`);
760
- }
761
- if (!sessionCols.some((c) => c.name === "synced_at")) {
762
- db.exec(`ALTER TABLE sessions ADD COLUMN synced_at TEXT DEFAULT ''`);
763
- }
764
- const pricingCols = db.prepare(`PRAGMA table_info(model_pricing)`).all();
765
- if (!pricingCols.some((c) => c.name === "cache_write_1h_per_1m")) {
766
- db.exec(`ALTER TABLE model_pricing ADD COLUMN cache_write_1h_per_1m REAL NOT NULL DEFAULT 0`);
767
- }
768
- if (!pricingCols.some((c) => c.name === "cache_storage_per_1m_hour")) {
769
- db.exec(`ALTER TABLE model_pricing ADD COLUMN cache_storage_per_1m_hour REAL NOT NULL DEFAULT 0`);
770
- }
771
- db.exec(`
772
- CREATE INDEX IF NOT EXISTS idx_requests_machine ON requests(machine_id);
773
- CREATE INDEX IF NOT EXISTS idx_sessions_machine ON sessions(machine_id);
774
242
  `);
775
243
  }
776
244
  function periodWhere(period) {
@@ -780,11 +248,11 @@ function periodWhere(period) {
780
248
  case "yesterday":
781
249
  return `DATE(timestamp) = DATE('now', '-1 day')`;
782
250
  case "week":
783
- return `timestamp >= DATE('now', 'weekday 0', '-7 days')`;
251
+ return `timestamp >= DATE('now', '-7 days')`;
784
252
  case "month":
785
- return `timestamp >= DATE('now', 'start of month')`;
253
+ return `timestamp >= DATE('now', '-30 days')`;
786
254
  case "year":
787
- return `timestamp >= DATE('now', 'start of year')`;
255
+ return `timestamp >= DATE('now', '-365 days')`;
788
256
  case "all":
789
257
  return "1=1";
790
258
  }
@@ -796,34 +264,31 @@ function sessionPeriodWhere(period) {
796
264
  case "yesterday":
797
265
  return `DATE(started_at) = DATE('now', '-1 day')`;
798
266
  case "week":
799
- return `started_at >= DATE('now', 'weekday 0', '-7 days')`;
267
+ return `started_at >= DATE('now', '-7 days')`;
800
268
  case "month":
801
- return `started_at >= DATE('now', 'start of month')`;
269
+ return `started_at >= DATE('now', '-30 days')`;
802
270
  case "year":
803
- return `started_at >= DATE('now', 'start of year')`;
271
+ return `started_at >= DATE('now', '-365 days')`;
804
272
  case "all":
805
273
  return "1=1";
806
274
  }
807
275
  }
808
276
  function upsertRequest(db, req) {
809
- const now = req.updated_at ?? new Date().toISOString();
810
277
  db.prepare(`
811
278
  INSERT OR REPLACE INTO requests
812
279
  (id, agent, session_id, model, input_tokens, output_tokens,
813
- cache_read_tokens, cache_create_tokens, cache_create_5m_tokens,
814
- cache_create_1h_tokens, cost_usd, cost_basis, duration_ms, timestamp,
815
- source_request_id, machine_id, attribution_tag, updated_at, synced_at)
816
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
817
- `).run(req.id, req.agent, req.session_id, req.model, req.input_tokens, req.output_tokens, req.cache_read_tokens, req.cache_create_tokens, req.cache_create_5m_tokens ?? req.cache_create_tokens, req.cache_create_1h_tokens ?? 0, req.cost_usd, req.cost_basis ?? "estimated", req.duration_ms, req.timestamp, req.source_request_id, req.machine_id ?? "", req.attribution_tag ?? process.env["ECONOMY_TAG"] ?? "", now, req.synced_at ?? "");
280
+ cache_read_tokens, cache_create_tokens, cost_usd, duration_ms,
281
+ timestamp, source_request_id)
282
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
283
+ `).run(req.id, req.agent, req.session_id, req.model, req.input_tokens, req.output_tokens, req.cache_read_tokens, req.cache_create_tokens, req.cost_usd, req.duration_ms, req.timestamp, req.source_request_id);
818
284
  }
819
285
  function upsertSession(db, session) {
820
- const now = session.updated_at ?? new Date().toISOString();
821
286
  db.prepare(`
822
287
  INSERT OR REPLACE INTO sessions
823
288
  (id, agent, project_path, project_name, started_at, ended_at,
824
- total_cost_usd, total_tokens, request_count, machine_id, attribution_tag, updated_at, synced_at)
825
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
826
- `).run(session.id, session.agent, session.project_path, session.project_name, session.started_at, session.ended_at ?? null, session.total_cost_usd, session.total_tokens, session.request_count, session.machine_id ?? "", session.attribution_tag ?? process.env["ECONOMY_TAG"] ?? "", now, session.synced_at ?? "");
289
+ total_cost_usd, total_tokens, request_count)
290
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
291
+ `).run(session.id, session.agent, session.project_path, session.project_name, session.started_at, session.ended_at ?? null, session.total_cost_usd, session.total_tokens, session.request_count);
827
292
  }
828
293
  function rollupSession(db, sessionId) {
829
294
  db.prepare(`
@@ -853,10 +318,6 @@ function querySessions(db, filter = {}) {
853
318
  conditions.push("started_at >= ?");
854
319
  params.push(filter.since);
855
320
  }
856
- if (filter.machine) {
857
- conditions.push("machine_id = ?");
858
- params.push(filter.machine);
859
- }
860
321
  if (filter.search) {
861
322
  const q = `%${filter.search}%`;
862
323
  conditions.push("(project_name LIKE ? OR agent LIKE ? OR id LIKE ?)");
@@ -875,25 +336,24 @@ function queryTopSessions(db, n = 10, agent) {
875
336
  }
876
337
  return db.prepare(`SELECT * FROM sessions ORDER BY total_cost_usd DESC LIMIT ?`).all(n);
877
338
  }
878
- function querySummary(db, period, machine, allMachines = false) {
339
+ function querySummary(db, period) {
879
340
  const rWhere = periodWhere(period);
880
341
  const sWhere = sessionPeriodWhere(period);
881
- const machineClause = !allMachines && machine ? ` AND machine_id = '${machine.replace(/'/g, "''")}'` : "";
882
342
  const r = db.prepare(`
883
343
  SELECT COALESCE(SUM(cost_usd), 0) as total_usd,
884
344
  COUNT(*) as requests,
885
345
  COALESCE(SUM(input_tokens + output_tokens + cache_read_tokens + cache_create_tokens), 0) as tokens
886
- FROM requests WHERE ${rWhere}${machineClause}
346
+ FROM requests WHERE ${rWhere}
887
347
  `).get();
888
348
  const codexTotals = db.prepare(`
889
349
  SELECT COALESCE(SUM(total_cost_usd), 0) as cost_usd,
890
350
  COALESCE(SUM(total_tokens), 0) as tokens,
891
351
  COUNT(*) as sessions
892
352
  FROM sessions
893
- WHERE ${sWhere}${machineClause}
353
+ WHERE ${sWhere}
894
354
  AND id NOT IN (SELECT DISTINCT session_id FROM requests)
895
355
  `).get();
896
- const sessionCount = db.prepare(`SELECT COUNT(*) as sessions FROM sessions WHERE ${sWhere}${machineClause}`).get();
356
+ const sessionCount = db.prepare(`SELECT COUNT(*) as sessions FROM sessions WHERE ${sWhere}`).get();
897
357
  return {
898
358
  total_usd: r.total_usd + codexTotals.cost_usd,
899
359
  requests: r.requests,
@@ -913,66 +373,23 @@ function queryModelBreakdown(db) {
913
373
  FROM requests GROUP BY model, agent ORDER BY cost_usd DESC
914
374
  `).all();
915
375
  }
916
- function labelForPath(projectPath, projectName) {
917
- if (projectName && projectName.trim() !== "")
918
- return projectName;
919
- if (!projectPath)
920
- return "";
921
- const segments = projectPath.split("/").filter(Boolean);
922
- const projectPrefix = /^(open|skill|hook|service|connect|platform|agent|tool|iapp|project|scaffold|capp)-/;
923
- for (const seg of segments) {
924
- if (projectPrefix.test(seg))
925
- return seg;
926
- }
927
- const generic = new Set(["web", "app", "apps", "packages", "src", "lib", "server", "client", "api", "frontend", "backend"]);
928
- for (let i = segments.length - 1;i >= 0; i--) {
929
- if (!generic.has(segments[i].toLowerCase()))
930
- return segments[i];
931
- }
932
- return segments[segments.length - 1] ?? projectPath;
933
- }
934
376
  function queryProjectBreakdown(db) {
935
- const sessions = db.prepare(`
936
- SELECT id, project_path, project_name, total_cost_usd, started_at
937
- FROM sessions
938
- WHERE project_path != '' OR project_name != ''
377
+ return db.prepare(`
378
+ SELECT
379
+ s.project_path,
380
+ COALESCE(p.name, s.project_name) as project_name,
381
+ COUNT(DISTINCT s.id) as sessions,
382
+ COUNT(r.id) as requests,
383
+ COALESCE(SUM(r.cost_usd), COALESCE(SUM(s.total_cost_usd), 0)) as cost_usd,
384
+ COALESCE(SUM(r.input_tokens + r.output_tokens + r.cache_read_tokens + r.cache_create_tokens), 0) as total_tokens,
385
+ MAX(s.started_at) as last_active
386
+ FROM sessions s
387
+ LEFT JOIN projects p ON p.path = s.project_path OR p.name = s.project_name
388
+ LEFT JOIN requests r ON r.session_id = s.id
389
+ WHERE s.project_path != '' OR s.project_name != ''
390
+ GROUP BY s.project_path
391
+ ORDER BY cost_usd DESC
939
392
  `).all();
940
- const groups = new Map;
941
- for (const s of sessions) {
942
- const label = labelForPath(s.project_path, s.project_name);
943
- if (!label)
944
- continue;
945
- const g = groups.get(label) ?? { sessionIds: [], samplePath: s.project_path, totalCost: 0, lastActive: "" };
946
- g.sessionIds.push(s.id);
947
- g.totalCost += s.total_cost_usd || 0;
948
- if (!g.lastActive || s.started_at > g.lastActive)
949
- g.lastActive = s.started_at;
950
- if (!g.samplePath)
951
- g.samplePath = s.project_path;
952
- groups.set(label, g);
953
- }
954
- const result = [];
955
- for (const [label, g] of groups.entries()) {
956
- const placeholders = g.sessionIds.map(() => "?").join(",");
957
- const reqStats = placeholders.length ? db.prepare(`
958
- SELECT
959
- COUNT(*) as requests,
960
- COALESCE(SUM(cost_usd), 0) as cost_usd,
961
- COALESCE(SUM(input_tokens + output_tokens + cache_read_tokens + cache_create_tokens), 0) as total_tokens
962
- FROM requests WHERE session_id IN (${placeholders})
963
- `).get(...g.sessionIds) : { requests: 0, cost_usd: 0, total_tokens: 0 };
964
- result.push({
965
- project_path: g.samplePath,
966
- project_name: label,
967
- sessions: g.sessionIds.length,
968
- requests: reqStats.requests,
969
- total_tokens: reqStats.total_tokens,
970
- cost_usd: reqStats.cost_usd > 0 ? reqStats.cost_usd : g.totalCost,
971
- last_active: g.lastActive
972
- });
973
- }
974
- result.sort((a, b) => b.cost_usd - a.cost_usd);
975
- return result;
976
393
  }
977
394
  function queryDailyBreakdown(db, days = 30) {
978
395
  return db.prepare(`
@@ -1090,46 +507,12 @@ function setIngestState(db, source, key, value) {
1090
507
  function queryRequestsSince(db, since) {
1091
508
  return db.prepare(`SELECT * FROM requests WHERE timestamp > ? ORDER BY timestamp ASC`).all(since);
1092
509
  }
1093
- function upsertBillingDaily(db, row) {
1094
- db.prepare(`
1095
- INSERT OR REPLACE INTO billing_daily (date, provider, description, cost_usd, updated_at)
1096
- VALUES (?, ?, ?, ?, ?)
1097
- `).run(row.date, row.provider, row.description, row.cost_usd, row.updated_at);
1098
- }
1099
- function clearBillingRange(db, provider, fromDate, toDate) {
1100
- db.prepare(`DELETE FROM billing_daily WHERE provider = ? AND date >= ? AND date <= ?`).run(provider, fromDate, toDate);
1101
- }
1102
- function queryBillingSummary(db, period) {
1103
- const where = period === "today" ? `date = DATE('now')` : period === "yesterday" ? `date = DATE('now', '-1 day')` : period === "week" ? `date >= DATE('now', 'weekday 0', '-7 days')` : period === "month" ? `date >= DATE('now', 'start of month')` : period === "year" ? `date >= DATE('now', 'start of year')` : "1=1";
1104
- const rows = db.prepare(`SELECT provider, SUM(cost_usd) as cost FROM billing_daily WHERE ${where} GROUP BY provider`).all();
1105
- const by_provider = {};
1106
- let total = 0;
1107
- for (const r of rows) {
1108
- by_provider[r.provider] = r.cost;
1109
- total += r.cost;
1110
- }
1111
- return { total_usd: total, by_provider };
1112
- }
1113
- function listMachines(db) {
1114
- return db.prepare(`
1115
- SELECT
1116
- s.machine_id,
1117
- COUNT(DISTINCT s.id) as sessions,
1118
- COALESCE((SELECT COUNT(*) FROM requests r WHERE r.machine_id = s.machine_id), 0) as requests,
1119
- COALESCE(SUM(s.total_cost_usd), 0) as total_cost_usd,
1120
- MAX(s.started_at) as last_active
1121
- FROM sessions s
1122
- WHERE s.machine_id != ''
1123
- GROUP BY s.machine_id
1124
- ORDER BY total_cost_usd DESC
1125
- `).all();
1126
- }
1127
510
  function upsertModelPricing(db, p) {
1128
511
  db.prepare(`
1129
512
  INSERT OR REPLACE INTO model_pricing
1130
- (model, input_per_1m, output_per_1m, cache_read_per_1m, cache_write_per_1m, cache_write_1h_per_1m, cache_storage_per_1m_hour, updated_at)
1131
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
1132
- `).run(p.model, p.input_per_1m, p.output_per_1m, p.cache_read_per_1m, p.cache_write_per_1m, p.cache_write_1h_per_1m ?? 0, p.cache_storage_per_1m_hour ?? 0, p.updated_at);
513
+ (model, input_per_1m, output_per_1m, cache_read_per_1m, cache_write_per_1m, updated_at)
514
+ VALUES (?, ?, ?, ?, ?, ?)
515
+ `).run(p.model, p.input_per_1m, p.output_per_1m, p.cache_read_per_1m, p.cache_write_per_1m, p.updated_at);
1133
516
  }
1134
517
  function getModelPricing(db, model) {
1135
518
  return db.prepare(`SELECT * FROM model_pricing WHERE model = ?`).get(model);
@@ -1141,104 +524,23 @@ function deleteModelPricing(db, model) {
1141
524
  db.prepare(`DELETE FROM model_pricing WHERE model = ?`).run(model);
1142
525
  }
1143
526
  function seedModelPricing(db, defaults) {
1144
- const existing = new Set(db.prepare(`SELECT model FROM model_pricing`).all().map((r) => r.model));
527
+ const existing = db.prepare(`SELECT COUNT(*) as count FROM model_pricing`).get();
528
+ if (existing.count > 0)
529
+ return;
1145
530
  const now = new Date().toISOString();
1146
531
  for (const [model, p] of Object.entries(defaults)) {
1147
- if (existing.has(model))
1148
- continue;
1149
532
  upsertModelPricing(db, {
1150
533
  model,
1151
534
  input_per_1m: p.inputPer1M,
1152
535
  output_per_1m: p.outputPer1M,
1153
536
  cache_read_per_1m: p.cacheReadPer1M,
1154
537
  cache_write_per_1m: p.cacheWritePer1M,
1155
- cache_write_1h_per_1m: p.cacheWrite1hPer1M ?? 0,
1156
- cache_storage_per_1m_hour: p.cacheStoragePer1MHour ?? 0,
1157
538
  updated_at: now
1158
539
  });
1159
540
  }
1160
541
  }
1161
- function upsertSubscription(db, sub) {
1162
- db.prepare(`
1163
- INSERT OR REPLACE INTO subscriptions
1164
- (id, agent, provider, plan, monthly_fee_usd, included_usage_usd, billing_cycle_start, reset_policy, active, created_at, updated_at)
1165
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1166
- `).run(sub.id, sub.agent, sub.provider, sub.plan, sub.monthly_fee_usd, sub.included_usage_usd, sub.billing_cycle_start, sub.reset_policy, sub.active, sub.created_at, sub.updated_at);
1167
- }
1168
- function listSubscriptions(db) {
1169
- return db.prepare(`SELECT * FROM subscriptions ORDER BY provider, plan`).all();
1170
- }
1171
- function deleteSubscription(db, id) {
1172
- db.prepare(`DELETE FROM subscriptions WHERE id = ?`).run(id);
1173
- }
1174
- function upsertUsageSnapshot(db, snap) {
1175
- const now = snap.updated_at ?? new Date().toISOString();
1176
- const id = snap.id ?? `${snap.agent}-${snap.date}-${snap.metric}-${snap.machine_id}`;
1177
- db.prepare(`
1178
- INSERT OR REPLACE INTO usage_snapshots (id, agent, date, metric, value, unit, machine_id, updated_at)
1179
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
1180
- `).run(id, snap.agent, snap.date, snap.metric, snap.value, snap.unit, snap.machine_id, now);
1181
- }
1182
- function queryUsageSnapshots(db, opts = {}) {
1183
- const conditions = [];
1184
- const params = [];
1185
- if (opts.agent) {
1186
- conditions.push("agent = ?");
1187
- params.push(opts.agent);
1188
- }
1189
- if (opts.date) {
1190
- conditions.push("date = ?");
1191
- params.push(opts.date);
1192
- }
1193
- if (opts.since) {
1194
- conditions.push("date >= ?");
1195
- params.push(opts.since);
1196
- }
1197
- const where = conditions.length ? `WHERE ${conditions.join(" AND ")}` : "";
1198
- return db.prepare(`SELECT * FROM usage_snapshots ${where} ORDER BY date DESC, agent, metric`).all(...params);
1199
- }
1200
- function listMachineRegistry(db) {
1201
- return db.prepare(`SELECT * FROM machines ORDER BY last_seen_at DESC`).all();
1202
- }
1203
- function dedupeRequests(db) {
1204
- const dupes = db.prepare(`
1205
- SELECT source_request_id, agent, MIN(id) as keep_id, COUNT(*) as cnt
1206
- FROM requests
1207
- WHERE source_request_id != '' AND source_request_id IS NOT NULL
1208
- GROUP BY source_request_id, agent
1209
- HAVING cnt > 1
1210
- `).all();
1211
- let removed = 0;
1212
- for (const row of dupes) {
1213
- const result = db.prepare(`
1214
- DELETE FROM requests WHERE source_request_id = ? AND agent = ? AND id != ?
1215
- `).run(row.source_request_id, row.agent, row.keep_id);
1216
- removed += result.changes;
1217
- }
1218
- return removed;
1219
- }
1220
542
  var init_database = () => {};
1221
543
 
1222
- // src/lib/agents.ts
1223
- var AGENTS = [
1224
- "claude",
1225
- "takumi",
1226
- "codex",
1227
- "gemini",
1228
- "opencode",
1229
- "cursor",
1230
- "pi",
1231
- "hermes"
1232
- ];
1233
- var COST_BASIS = [
1234
- "metered_api",
1235
- "subscription_included",
1236
- "estimated",
1237
- "unknown"
1238
- ];
1239
- function isAgent(value) {
1240
- return AGENTS.includes(value);
1241
- }
1242
544
  // src/index.ts
1243
545
  init_database();
1244
546
  init_pricing();
@@ -1246,9 +548,6 @@ init_pricing();
1246
548
  // src/lib/gatherer.ts
1247
549
  init_database();
1248
550
  var SYSTEM_PROMPT = "You are a cost-aware AI assistant that tracks API usage, identifies expensive patterns, and helps optimize spending.";
1249
- function hasCostData(summary) {
1250
- return summary.total_usd > 0 || summary.sessions > 0 || summary.requests > 0 || summary.tokens > 0;
1251
- }
1252
551
  var gatherTrainingData = async (options = {}) => {
1253
552
  const limit = options.limit ?? 500;
1254
553
  const examples = [];
@@ -1258,8 +557,6 @@ var gatherTrainingData = async (options = {}) => {
1258
557
  for (const period of periods) {
1259
558
  try {
1260
559
  const s = querySummary(db, period);
1261
- if (!hasCostData(s))
1262
- continue;
1263
560
  examples.push({
1264
561
  messages: [
1265
562
  { role: "system", content: SYSTEM_PROMPT },
@@ -1432,26 +729,22 @@ ${goals.map((g) => `- ${g.period} goal (${g.project_path ?? g.agent ?? "global"}
1432
729
  // src/lib/model-config.ts
1433
730
  init_database();
1434
731
  import { existsSync as existsSync2, readFileSync, writeFileSync, mkdirSync as mkdirSync2 } from "fs";
1435
- import { dirname, join as join2 } from "path";
732
+ import { join as join2 } from "path";
1436
733
  var DEFAULT_MODEL = "gpt-4o-mini";
1437
- function getModelConfigPath() {
1438
- return process.env["HASNA_ECONOMY_CONFIG_PATH"] ?? join2(getDataDir(), "config.json");
1439
- }
734
+ var CONFIG_PATH = join2(getDataDir(), "config.json");
1440
735
  function loadConfig() {
1441
736
  try {
1442
- const configPath = getModelConfigPath();
1443
- if (existsSync2(configPath)) {
1444
- return JSON.parse(readFileSync(configPath, "utf-8"));
737
+ if (existsSync2(CONFIG_PATH)) {
738
+ return JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
1445
739
  }
1446
740
  } catch {}
1447
741
  return {};
1448
742
  }
1449
743
  function saveConfig(config) {
1450
- const configPath = getModelConfigPath();
1451
- const dir = dirname(configPath);
744
+ const dir = getDataDir();
1452
745
  if (!existsSync2(dir))
1453
746
  mkdirSync2(dir, { recursive: true });
1454
- writeFileSync(configPath, JSON.stringify(config, null, 2) + `
747
+ writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + `
1455
748
  `);
1456
749
  }
1457
750
  function getActiveModel() {
@@ -1467,56 +760,16 @@ function clearActiveModel() {
1467
760
  delete config.activeModel;
1468
761
  saveConfig(config);
1469
762
  }
1470
- // src/lib/open-projects.ts
1471
- init_database();
1472
- async function syncOpenProjectsRegistry(db, listActiveProjects) {
1473
- let listProjects2 = listActiveProjects;
1474
- if (!listProjects2) {
1475
- const projectsApi = await import("@hasna/projects");
1476
- listProjects2 = projectsApi.listProjects;
1477
- }
1478
- const projects = listProjects2({ status: "active", limit: 5000 });
1479
- let imported = 0;
1480
- let skipped = 0;
1481
- for (const project of projects) {
1482
- if (!project.path) {
1483
- skipped++;
1484
- continue;
1485
- }
1486
- upsertProject(db, {
1487
- id: project.id,
1488
- path: project.path,
1489
- name: project.name,
1490
- description: project.description,
1491
- tags: project.tags ?? [],
1492
- created_at: project.created_at
1493
- });
1494
- imported++;
1495
- }
1496
- return { imported, skipped };
1497
- }
1498
763
  // src/ingest/claude.ts
1499
764
  init_database();
1500
765
  init_pricing();
1501
766
  import { readdirSync as readdirSync2, readFileSync as readFileSync2, existsSync as existsSync3, statSync as statSync2 } from "fs";
1502
767
  import { homedir as homedir2 } from "os";
1503
768
  import { join as join3, basename } from "path";
1504
-
1505
- // src/lib/savings.ts
1506
- function defaultCostBasisForAgent(agent) {
1507
- if (agent === "claude")
1508
- return "metered_api";
1509
- if (agent === "cursor")
1510
- return "subscription_included";
1511
- return "estimated";
1512
- }
1513
-
1514
- // src/ingest/claude.ts
1515
769
  function autoDetectProject(cwd, projects) {
1516
770
  return projects.find((p) => cwd === p.path || cwd.startsWith(p.path + "/"));
1517
771
  }
1518
- var CLAUDE_PROJECTS_DIR = join3(homedir2(), ".claude", "projects");
1519
- var TAKUMI_PROJECTS_DIR = join3(homedir2(), ".takumi", "projects");
772
+ var PROJECTS_DIR = join3(homedir2(), ".claude", "projects");
1520
773
  function dirNameToPath(dirName) {
1521
774
  return dirName.replace(/^-/, "/").replace(/-/g, "/").replace(/\/\//g, "/-");
1522
775
  }
@@ -1535,37 +788,30 @@ function collectJsonlFiles(projectDir) {
1535
788
  walk(projectDir);
1536
789
  return files;
1537
790
  }
1538
- async function ingestClaude(db, verbose = false, projectsDir = CLAUDE_PROJECTS_DIR) {
1539
- return ingestJsonlProjects(db, projectsDir, "claude", verbose);
1540
- }
1541
- async function ingestTakumi(db, verbose = false, projectsDir = TAKUMI_PROJECTS_DIR) {
1542
- return ingestJsonlProjects(db, projectsDir, "takumi", verbose);
1543
- }
1544
- async function ingestJsonlProjects(db, projectsDir, agentName, verbose = false) {
1545
- if (!existsSync3(projectsDir)) {
791
+ async function ingestClaude(db, verbose = false, _telemetryDir) {
792
+ if (!existsSync3(PROJECTS_DIR)) {
1546
793
  if (verbose)
1547
- console.log(`${agentName} projects dir not found:`, projectsDir);
794
+ console.log("Claude projects dir not found:", PROJECTS_DIR);
1548
795
  return { files: 0, requests: 0, sessions: 0 };
1549
796
  }
1550
- const machineId = getMachineId();
1551
797
  let totalFiles = 0;
1552
798
  let totalRequests = 0;
1553
799
  const touchedSessions = new Set;
1554
800
  const registeredProjects = db.prepare(`SELECT path, name FROM projects ORDER BY LENGTH(path) DESC`).all();
1555
- const projectDirs = readdirSync2(projectsDir, { withFileTypes: true }).filter((d) => d.isDirectory());
801
+ const projectDirs = readdirSync2(PROJECTS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory());
1556
802
  for (const projectDirEntry of projectDirs) {
1557
- const projectDirPath = join3(projectsDir, projectDirEntry.name);
803
+ const projectDirPath = join3(PROJECTS_DIR, projectDirEntry.name);
1558
804
  const projectPath = dirNameToPath(projectDirEntry.name);
1559
805
  const jsonlFiles = collectJsonlFiles(projectDirPath);
1560
806
  for (const filePath of jsonlFiles) {
1561
- const stateKey = filePath.replace(projectsDir, "");
807
+ const stateKey = filePath.replace(PROJECTS_DIR, "");
1562
808
  let fileMtime = "0";
1563
809
  try {
1564
810
  fileMtime = statSync2(filePath).mtimeMs.toString();
1565
811
  } catch {
1566
812
  continue;
1567
813
  }
1568
- const processed = getIngestState(db, agentName, stateKey);
814
+ const processed = getIngestState(db, "claude", stateKey);
1569
815
  if (processed === fileMtime)
1570
816
  continue;
1571
817
  let lines;
@@ -1600,38 +846,26 @@ async function ingestJsonlProjects(db, projectsDir, agentName, verbose = false)
1600
846
  continue;
1601
847
  const inputTokens = usage.input_tokens ?? 0;
1602
848
  const outputTokens = usage.output_tokens ?? 0;
1603
- const cacheWrite5mTokens = usage.cache_creation?.ephemeral_5m_input_tokens ?? usage.cache_creation_input_tokens ?? 0;
1604
- const cacheWrite1hTokens = usage.cache_creation?.ephemeral_1h_input_tokens ?? 0;
1605
- const cacheWriteTokens = cacheWrite5mTokens + cacheWrite1hTokens;
849
+ const cacheWriteTokens = usage.cache_creation_input_tokens ?? 0;
1606
850
  const cacheReadTokens = usage.cache_read_input_tokens ?? 0;
1607
851
  const timestamp = entry.timestamp ?? new Date().toISOString();
1608
- if (inputTokens + outputTokens + cacheWriteTokens + cacheReadTokens === 0)
852
+ if (inputTokens + outputTokens + cacheWriteTokens === 0)
1609
853
  continue;
1610
- let costUsd = computeCostFromDb(db, model, inputTokens, outputTokens, cacheReadTokens, cacheWrite5mTokens, cacheWrite1hTokens);
1611
- costUsd = applyClaudeModifiers(costUsd, model, usage, entry);
1612
- const serverToolUse = usage.server_tool_use;
1613
- if (serverToolUse?.web_search_requests) {
1614
- costUsd += serverToolUse.web_search_requests * 0.01;
1615
- }
1616
- const sourceRequestId = entry.requestId ?? entry.request_id ?? entry.message.id ?? entry.uuid ?? `${sessionId}-${timestamp}`;
1617
- const reqId = `${agentName}-${sourceRequestId}`;
854
+ const costUsd = computeCostFromDb(db, model, inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens);
855
+ const reqId = `claude-${sessionId}-${timestamp}`;
1618
856
  upsertRequest(db, {
1619
857
  id: reqId,
1620
- agent: agentName,
858
+ agent: "claude",
1621
859
  session_id: sessionId,
1622
860
  model,
1623
861
  input_tokens: inputTokens,
1624
862
  output_tokens: outputTokens,
1625
863
  cache_read_tokens: cacheReadTokens,
1626
864
  cache_create_tokens: cacheWriteTokens,
1627
- cache_create_5m_tokens: cacheWrite5mTokens,
1628
- cache_create_1h_tokens: cacheWrite1hTokens,
1629
865
  cost_usd: costUsd,
1630
- cost_basis: defaultCostBasisForAgent(agentName),
1631
866
  duration_ms: 0,
1632
867
  timestamp,
1633
- source_request_id: sourceRequestId,
1634
- machine_id: machineId
868
+ source_request_id: reqId
1635
869
  });
1636
870
  if (!touchedSessions.has(sessionId)) {
1637
871
  const existing = db.prepare(`SELECT id FROM sessions WHERE id = ?`).get(sessionId);
@@ -1640,15 +874,14 @@ async function ingestJsonlProjects(db, projectsDir, agentName, verbose = false)
1640
874
  const detectedProject = autoDetectProject(effectiveCwd, registeredProjects);
1641
875
  const session = {
1642
876
  id: sessionId,
1643
- agent: agentName,
877
+ agent: "claude",
1644
878
  project_path: detectedProject ? detectedProject.path : effectiveCwd,
1645
879
  project_name: detectedProject ? detectedProject.name : "",
1646
880
  started_at: timestamp,
1647
881
  ended_at: null,
1648
882
  total_cost_usd: 0,
1649
883
  total_tokens: 0,
1650
- request_count: 0,
1651
- machine_id: machineId
884
+ request_count: 0
1652
885
  };
1653
886
  upsertSession(db, session);
1654
887
  }
@@ -1656,7 +889,7 @@ async function ingestJsonlProjects(db, projectsDir, agentName, verbose = false)
1656
889
  }
1657
890
  totalRequests++;
1658
891
  }
1659
- setIngestState(db, agentName, stateKey, fileMtime);
892
+ setIngestState(db, "claude", stateKey, fileMtime);
1660
893
  totalFiles++;
1661
894
  }
1662
895
  }
@@ -1665,365 +898,79 @@ async function ingestJsonlProjects(db, projectsDir, agentName, verbose = false)
1665
898
  }
1666
899
  return { files: totalFiles, requests: totalRequests, sessions: touchedSessions.size };
1667
900
  }
1668
- function applyClaudeModifiers(costUsd, model, usage, entry) {
1669
- let multiplier = 1;
1670
- const speed = usage.speed ?? entry.message?.speed ?? entry.speed;
1671
- if (speed === "fast" && model.includes("opus-4-6")) {
1672
- multiplier *= 6;
1673
- }
1674
- const inferenceGeo = usage.inference_geo ?? entry.message?.inference_geo ?? entry.inference_geo;
1675
- if (inferenceGeo && ["us", "us-only", "us_only"].includes(inferenceGeo) && supportsClaudeDataResidencyPricing(model)) {
1676
- multiplier *= 1.1;
1677
- }
1678
- return costUsd * multiplier;
1679
- }
1680
- function supportsClaudeDataResidencyPricing(model) {
1681
- const normalized = normalizeModelName(model);
1682
- const match = normalized.match(/^claude-(opus|sonnet|haiku)-(\d+)(?:-(\d+))?(?:-|$)/);
1683
- if (!match)
1684
- return false;
1685
- const major = Number(match[2]);
1686
- const minor = match[3] ? Number(match[3]) : 0;
1687
- return major > 4 || major === 4 && minor >= 6;
1688
- }
1689
901
  // src/ingest/codex.ts
1690
902
  init_database();
1691
- init_pricing();
1692
903
  import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
1693
904
  import { homedir as homedir3 } from "os";
1694
905
  import { join as join4, basename as basename2 } from "path";
1695
- import { Database as BunDatabase } from "bun:sqlite";
1696
- var DEFAULT_CODEX_DB_PATH = join4(homedir3(), ".codex", "state_5.sqlite");
1697
- var DEFAULT_CODEX_CONFIG_PATH = join4(homedir3(), ".codex", "config.toml");
1698
- var CODEX_INGEST_VERSION = "rollout-token-dedupe-v2";
1699
- function codexDbPath() {
1700
- return process.env["HASNA_ECONOMY_CODEX_DB_PATH"] ?? DEFAULT_CODEX_DB_PATH;
1701
- }
1702
- function codexConfigPath() {
1703
- return process.env["HASNA_ECONOMY_CODEX_CONFIG_PATH"] ?? DEFAULT_CODEX_CONFIG_PATH;
1704
- }
906
+ import { Database as Database2 } from "bun:sqlite";
907
+ var CODEX_DB_PATH = join4(homedir3(), ".codex", "state_5.sqlite");
908
+ var CODEX_CONFIG_PATH = join4(homedir3(), ".codex", "config.toml");
1705
909
  function readCodexModel() {
1706
- const configPath = codexConfigPath();
1707
- if (!existsSync4(configPath))
1708
- return "gpt-5-codex";
910
+ if (!existsSync4(CODEX_CONFIG_PATH))
911
+ return "gpt-5.3-codex";
1709
912
  try {
1710
- const content = readFileSync3(configPath, "utf-8");
913
+ const content = readFileSync3(CODEX_CONFIG_PATH, "utf-8");
1711
914
  const match = content.match(/^model\s*=\s*"([^"]+)"/m);
1712
- return match?.[1] ?? "gpt-5-codex";
915
+ return match?.[1] ?? "gpt-5.3-codex";
1713
916
  } catch {
1714
- return "gpt-5-codex";
1715
- }
1716
- }
1717
- function buildThreadQuery(codexDb) {
1718
- const cols = new Set(codexDb.prepare(`PRAGMA table_info(threads)`).all().map((c) => c.name));
1719
- const modelSelect = cols.has("model") ? "model" : "NULL AS model";
1720
- const rolloutSelect = cols.has("rollout_path") ? "rollout_path" : "NULL AS rollout_path";
1721
- const providerSelect = cols.has("model_provider") ? "model_provider" : "NULL AS model_provider";
1722
- return `
1723
- SELECT id, ${rolloutSelect}, cwd, created_at, updated_at, tokens_used, title,
1724
- ${providerSelect}, ${modelSelect}
1725
- FROM threads WHERE tokens_used > 0
1726
- `;
1727
- }
1728
- function readTokenEvents(rolloutPath) {
1729
- if (!rolloutPath || !existsSync4(rolloutPath))
1730
- return [];
1731
- const events = [];
1732
- const seen = new Set;
1733
- for (const line of readFileSync3(rolloutPath, "utf-8").split(`
1734
- `)) {
1735
- if (!line.trim())
1736
- continue;
1737
- let entry;
1738
- try {
1739
- entry = JSON.parse(line);
1740
- } catch {
1741
- continue;
1742
- }
1743
- if (!entry || typeof entry !== "object")
1744
- continue;
1745
- const payload = entry["payload"];
1746
- if (!payload || payload["type"] !== "token_count")
1747
- continue;
1748
- const info = payload["info"];
1749
- const usage = info?.["last_token_usage"];
1750
- if (!usage)
1751
- continue;
1752
- const total = usage.total_tokens ?? (usage.input_tokens ?? 0) + (usage.output_tokens ?? 0);
1753
- if (total <= 0)
1754
- continue;
1755
- const key = JSON.stringify(usage);
1756
- if (seen.has(key))
1757
- continue;
1758
- seen.add(key);
1759
- const timestamp = entry["timestamp"];
1760
- events.push({ usage, timestamp: typeof timestamp === "string" ? timestamp : undefined });
917
+ return "gpt-5.3-codex";
1761
918
  }
1762
- return events;
1763
- }
1764
- function fallbackEvents(totalTokens) {
1765
- const inputTokens = Math.floor(totalTokens * 0.6);
1766
- return [{
1767
- usage: {
1768
- input_tokens: inputTokens,
1769
- cached_input_tokens: 0,
1770
- output_tokens: totalTokens - inputTokens,
1771
- total_tokens: totalTokens
1772
- }
1773
- }];
1774
919
  }
1775
920
  async function ingestCodex(db, verbose = false) {
1776
- const dbPath = codexDbPath();
1777
- if (!existsSync4(dbPath)) {
921
+ if (!existsSync4(CODEX_DB_PATH)) {
1778
922
  if (verbose)
1779
- console.log("Codex DB not found:", dbPath);
1780
- return { sessions: 0, requests: 0 };
923
+ console.log("Codex DB not found:", CODEX_DB_PATH);
924
+ return { sessions: 0 };
1781
925
  }
1782
- const machineId = getMachineId();
1783
926
  let codexDb = null;
1784
927
  let ingested = 0;
1785
- let requests = 0;
1786
928
  try {
1787
- codexDb = new BunDatabase(dbPath, { readonly: true });
1788
- const threads = codexDb.prepare(buildThreadQuery(codexDb)).all();
929
+ codexDb = new Database2(CODEX_DB_PATH, { readonly: true });
930
+ const threads = codexDb.prepare(`SELECT id, cwd, created_at, updated_at, tokens_used, title FROM threads WHERE tokens_used > 0`).all();
1789
931
  for (const thread of threads) {
1790
- const model = thread.model ?? readCodexModel();
1791
- const stateValue = `${CODEX_INGEST_VERSION}:${thread.updated_at}:${thread.tokens_used}:${model}`;
1792
- const processed = getIngestState(db, "codex", thread.id);
1793
- if (processed === stateValue)
932
+ const stateKey = thread.id;
933
+ const processed = getIngestState(db, "codex", stateKey);
934
+ if (processed === "done")
1794
935
  continue;
936
+ const costUsd = 0;
1795
937
  const projectPath = thread.cwd ?? "";
1796
938
  const projectName = projectPath ? basename2(projectPath) : "unknown";
1797
- const sessionId = `codex-${thread.id}`;
1798
939
  const startedAt = thread.created_at ? new Date(thread.created_at * 1000).toISOString() : new Date().toISOString();
1799
940
  const endedAt = thread.updated_at ? new Date(thread.updated_at * 1000).toISOString() : null;
1800
941
  upsertSession(db, {
1801
- id: sessionId,
942
+ id: `codex-${thread.id}`,
1802
943
  agent: "codex",
1803
944
  project_path: projectPath,
1804
945
  project_name: projectName,
1805
946
  started_at: startedAt,
1806
947
  ended_at: endedAt,
1807
- total_cost_usd: 0,
1808
- total_tokens: 0,
1809
- request_count: 0,
1810
- machine_id: machineId
948
+ total_cost_usd: costUsd,
949
+ total_tokens: thread.tokens_used,
950
+ request_count: 1
1811
951
  });
1812
- const events = readTokenEvents(thread.rollout_path);
1813
- const tokenEvents = events.length > 0 ? events : fallbackEvents(thread.tokens_used);
1814
- db.prepare(`DELETE FROM requests WHERE session_id = ?`).run(sessionId);
1815
- tokenEvents.forEach((event, index) => {
1816
- const usage = event.usage;
1817
- const inputTotal = usage.input_tokens ?? 0;
1818
- const cacheReadTokens = usage.cached_input_tokens ?? 0;
1819
- const inputTokens = Math.max(inputTotal - cacheReadTokens, 0);
1820
- const outputTokens = usage.output_tokens ?? Math.max((usage.total_tokens ?? 0) - inputTotal, 0);
1821
- const costUsd = computeCostFromDb(db, model, inputTokens, outputTokens, cacheReadTokens, 0);
1822
- const timestamp = event.timestamp ?? (thread.created_at ? new Date(thread.created_at * 1000 + index).toISOString() : new Date().toISOString());
1823
- const requestId = `${sessionId}-${index}`;
1824
- upsertRequest(db, {
1825
- id: requestId,
1826
- agent: "codex",
1827
- session_id: sessionId,
1828
- model,
1829
- input_tokens: inputTokens,
1830
- output_tokens: outputTokens,
1831
- cache_read_tokens: cacheReadTokens,
1832
- cache_create_tokens: 0,
1833
- cost_usd: costUsd,
1834
- cost_basis: defaultCostBasisForAgent("codex"),
1835
- duration_ms: 0,
1836
- timestamp,
1837
- source_request_id: requestId,
1838
- machine_id: machineId
1839
- });
1840
- requests++;
1841
- });
1842
- rollupSession(db, sessionId);
1843
- setIngestState(db, "codex", thread.id, stateValue);
952
+ setIngestState(db, "codex", stateKey, "done");
1844
953
  ingested++;
1845
954
  if (verbose)
1846
- console.log(`Codex session ${thread.id}: ${thread.tokens_used} tokens on ${model}`);
955
+ console.log(`Codex session ${thread.id}: ${thread.tokens_used} tokens \u2192 $${costUsd.toFixed(4)}`);
1847
956
  }
1848
957
  } finally {
1849
958
  codexDb?.close();
1850
959
  }
1851
- return { sessions: ingested, requests };
1852
- }
1853
- // src/ingest/gemini.ts
1854
- init_database();
1855
- init_pricing();
1856
- import { readdirSync as readdirSync3, readFileSync as readFileSync4, existsSync as existsSync5, statSync as statSync3 } from "fs";
1857
- import { homedir as homedir4 } from "os";
1858
- import { join as join5, basename as basename3 } from "path";
1859
- var DEFAULT_GEMINI_TMP_DIR = join5(homedir4(), ".gemini", "tmp");
1860
- var DEFAULT_GEMINI_HISTORY_DIR = join5(homedir4(), ".gemini", "history");
1861
- function geminiTmpDir() {
1862
- return process.env["HASNA_ECONOMY_GEMINI_TMP_DIR"] ?? DEFAULT_GEMINI_TMP_DIR;
1863
- }
1864
- function geminiHistoryDir() {
1865
- return process.env["HASNA_ECONOMY_GEMINI_HISTORY_DIR"] ?? DEFAULT_GEMINI_HISTORY_DIR;
1866
- }
1867
- function numberField(...values) {
1868
- for (const value of values) {
1869
- if (typeof value === "number" && Number.isFinite(value))
1870
- return value;
1871
- }
1872
- return 0;
1873
- }
1874
- function listProjectDirs(...roots) {
1875
- const dirs = new Set;
1876
- for (const root of roots) {
1877
- if (!existsSync5(root))
1878
- continue;
1879
- try {
1880
- for (const entry of readdirSync3(root, { withFileTypes: true })) {
1881
- if (entry.isDirectory())
1882
- dirs.add(join5(root, entry.name));
1883
- }
1884
- } catch {}
1885
- }
1886
- return [...dirs];
1887
- }
1888
- function projectRoot(projectDir, chatData) {
1889
- if (chatData.projectPath)
1890
- return chatData.projectPath;
1891
- if (chatData.project_path)
1892
- return chatData.project_path;
1893
- const rootFile = join5(projectDir, ".project_root");
1894
- try {
1895
- if (existsSync5(rootFile))
1896
- return readFileSync4(rootFile, "utf-8").trim();
1897
- } catch {}
1898
- return "";
1899
- }
1900
- async function ingestGemini(db, verbose) {
1901
- const tmpDir = geminiTmpDir();
1902
- const historyDir = geminiHistoryDir();
1903
- if (!existsSync5(tmpDir) && !existsSync5(historyDir)) {
1904
- if (verbose)
1905
- console.log("Gemini tmp/history dirs not found:", tmpDir, historyDir);
1906
- return { sessions: 0, requests: 0 };
1907
- }
1908
- const machineId = getMachineId();
1909
- let totalSessions = 0;
1910
- let totalRequests = 0;
1911
- const touchedSessions = new Set;
1912
- const projectDirs = listProjectDirs(tmpDir, historyDir);
1913
- for (const projectDir of projectDirs) {
1914
- const chatsDir = join5(projectDir, "chats");
1915
- if (!existsSync5(chatsDir))
1916
- continue;
1917
- let chatFiles = [];
1918
- try {
1919
- chatFiles = readdirSync3(chatsDir).filter((f) => f.endsWith(".json")).map((f) => join5(chatsDir, f));
1920
- } catch {
1921
- continue;
1922
- }
1923
- for (const filePath of chatFiles) {
1924
- const stateKey = filePath.replace(homedir4(), "~");
1925
- let fileMtime = "0";
1926
- try {
1927
- fileMtime = statSync3(filePath).mtimeMs.toString();
1928
- } catch {
1929
- continue;
1930
- }
1931
- const processed = getIngestState(db, "gemini", stateKey);
1932
- if (processed === fileMtime)
1933
- continue;
1934
- let chatData;
1935
- try {
1936
- chatData = JSON.parse(readFileSync4(filePath, "utf-8"));
1937
- } catch {
1938
- continue;
1939
- }
1940
- const sessionId = chatData.sessionId ?? chatData.id ?? basename3(filePath, ".json");
1941
- if (!sessionId)
1942
- continue;
1943
- const startTime = chatData.startTime ?? new Date().toISOString();
1944
- const projectPath = projectRoot(projectDir, chatData);
1945
- const projectName = projectPath ? basename3(projectPath) : "";
1946
- const existing = db.prepare(`SELECT id FROM sessions WHERE id = ?`).get(sessionId);
1947
- if (!existing) {
1948
- const session = {
1949
- id: sessionId,
1950
- agent: "gemini",
1951
- project_path: projectPath,
1952
- project_name: projectName,
1953
- started_at: startTime,
1954
- ended_at: chatData.lastUpdated ?? null,
1955
- total_cost_usd: 0,
1956
- total_tokens: 0,
1957
- request_count: 0,
1958
- machine_id: machineId
1959
- };
1960
- upsertSession(db, session);
1961
- totalSessions++;
1962
- }
1963
- touchedSessions.add(sessionId);
1964
- for (const [index, message] of (chatData.messages ?? []).entries()) {
1965
- const usage = message.usage ?? message.usageMetadata ?? message.response?.usageMetadata;
1966
- if (!usage)
1967
- continue;
1968
- const model = message.model ?? message.response?.modelVersion ?? message.response?.model ?? chatData.model;
1969
- if (!model)
1970
- continue;
1971
- const toolUsePromptTokens = numberField(usage.toolUsePromptTokenCount, usage.tool_use_prompt_token_count);
1972
- const inputTotal = numberField(usage.inputTokens, usage.input_tokens, usage.promptTokenCount, usage.prompt_token_count) + toolUsePromptTokens;
1973
- const cacheReadTokens = numberField(usage.cachedInputTokens, usage.cache_read_tokens, usage.cachedContentTokenCount, usage.cached_content_token_count);
1974
- const inputTokens = Math.max(inputTotal - cacheReadTokens, 0);
1975
- const thoughtsTokens = numberField(usage.thoughtsTokenCount, usage.thoughts_token_count);
1976
- const outputTokens = numberField(usage.outputTokens, usage.output_tokens, usage.candidatesTokenCount, usage.candidates_token_count) + thoughtsTokens;
1977
- const totalTokens = numberField(usage.totalTokens, usage.total_tokens, usage.totalTokenCount, usage.total_token_count);
1978
- if (inputTokens + outputTokens + cacheReadTokens + totalTokens === 0)
1979
- continue;
1980
- const computedCost = computeCostFromDb(db, model, inputTokens, outputTokens, cacheReadTokens, 0);
1981
- const costUsd = numberField(message.costUsd, message.cost_usd) || computedCost;
1982
- const timestamp = message.timestamp ?? chatData.lastUpdated ?? startTime;
1983
- const requestId = `gemini-${sessionId}-${message.id ?? index}`;
1984
- upsertRequest(db, {
1985
- id: requestId,
1986
- agent: "gemini",
1987
- session_id: sessionId,
1988
- model,
1989
- input_tokens: inputTokens,
1990
- output_tokens: outputTokens,
1991
- cache_read_tokens: cacheReadTokens,
1992
- cache_create_tokens: 0,
1993
- cost_usd: costUsd,
1994
- cost_basis: defaultCostBasisForAgent("gemini"),
1995
- duration_ms: 0,
1996
- timestamp,
1997
- source_request_id: message.id ?? requestId,
1998
- machine_id: machineId
1999
- });
2000
- totalRequests++;
2001
- }
2002
- setIngestState(db, "gemini", stateKey, fileMtime);
2003
- }
2004
- }
2005
- for (const sessionId of touchedSessions) {
2006
- rollupSession(db, sessionId);
2007
- }
2008
- return { sessions: totalSessions, requests: totalRequests };
960
+ return { sessions: ingested };
2009
961
  }
2010
962
  export {
2011
- upsertUsageSnapshot,
2012
- upsertSubscription,
2013
963
  upsertSession,
2014
964
  upsertRequest,
2015
965
  upsertProject,
2016
966
  upsertModelPricing,
2017
967
  upsertGoal,
2018
968
  upsertBudget,
2019
- upsertBillingDaily,
2020
- syncOpenProjectsRegistry,
2021
969
  setIngestState,
2022
970
  setActiveModel,
2023
971
  seedModelPricing,
2024
972
  rollupSession,
2025
973
  readCodexModel,
2026
- queryUsageSnapshots,
2027
974
  queryTopSessions,
2028
975
  querySummary,
2029
976
  querySessions,
@@ -2031,27 +978,18 @@ export {
2031
978
  queryProjectBreakdown,
2032
979
  queryModelBreakdown,
2033
980
  queryDailyBreakdown,
2034
- queryBillingSummary,
2035
981
  openDatabase,
2036
982
  normalizeModelName,
2037
- listSubscriptions,
2038
983
  listProjects,
2039
984
  listModelPricing,
2040
- listMachines,
2041
- listMachineRegistry,
2042
985
  listGoals,
2043
986
  listBudgets,
2044
- isAgent,
2045
- ingestTakumi,
2046
- ingestJsonlProjects,
2047
- ingestGemini,
2048
987
  ingestCodex,
2049
988
  ingestClaude,
2050
989
  getProject,
2051
990
  getPricingFromDb,
2052
991
  getPricing,
2053
992
  getModelPricing,
2054
- getMachineId,
2055
993
  getIngestState,
2056
994
  getGoalStatuses,
2057
995
  getDbPath,
@@ -2060,18 +998,13 @@ export {
2060
998
  getActiveModel,
2061
999
  gatherTrainingData,
2062
1000
  ensurePricingSeeded,
2063
- deleteSubscription,
2064
1001
  deleteProject,
2065
1002
  deleteModelPricing,
2066
1003
  deleteGoal,
2067
1004
  deleteBudget,
2068
- dedupeRequests,
2069
1005
  computeCostFromDb,
2070
1006
  computeCost,
2071
- clearBillingRange,
2072
1007
  clearActiveModel,
2073
1008
  DEFAULT_PRICING,
2074
- DEFAULT_MODEL,
2075
- COST_BASIS,
2076
- AGENTS
1009
+ DEFAULT_MODEL
2077
1010
  };