@diogonzafe/tokenwatch 0.1.9 → 0.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -2,8 +2,9 @@
2
2
  "use strict";
3
3
 
4
4
  // bin/cli.ts
5
- var import_node_fs2 = require("fs");
6
- var import_node_path2 = require("path");
5
+ var import_node_fs3 = require("fs");
6
+ var import_node_path3 = require("path");
7
+ var import_node_os3 = require("os");
7
8
  var import_node_url = require("url");
8
9
 
9
10
  // src/core/sync.ts
@@ -27,6 +28,18 @@ async function fetchRemotePrices(url = REMOTE_URL) {
27
28
  return null;
28
29
  }
29
30
  }
31
+ async function loadCachedPrices() {
32
+ if (!(0, import_node_fs.existsSync)(CACHE_FILE)) return null;
33
+ try {
34
+ const raw = await (0, import_promises.readFile)(CACHE_FILE, "utf8");
35
+ const data = JSON.parse(raw);
36
+ const age = Date.now() - (data._cachedAt ?? 0);
37
+ if (age > CACHE_TTL_MS) return null;
38
+ return data.models ?? null;
39
+ } catch {
40
+ return null;
41
+ }
42
+ }
30
43
  async function persistCache(data) {
31
44
  try {
32
45
  await (0, import_promises.mkdir)(CACHE_DIR, { recursive: true });
@@ -35,13 +48,1469 @@ async function persistCache(data) {
35
48
  } catch {
36
49
  }
37
50
  }
51
+ async function getRemotePrices() {
52
+ const cached = await loadCachedPrices();
53
+ if (cached) return cached;
54
+ return fetchRemotePrices();
55
+ }
38
56
 
39
- // bin/cli.ts
57
+ // src/core/storage.ts
58
+ var import_node_module = require("module");
59
+ var import_node_os2 = require("os");
60
+ var import_node_path2 = require("path");
61
+ var import_node_fs2 = require("fs");
40
62
  var import_meta = {};
41
- var __dirname = (0, import_node_path2.dirname)((0, import_node_url.fileURLToPath)(import_meta.url));
63
+ var MemoryStorage = class {
64
+ entries = [];
65
+ record(entry) {
66
+ this.entries.push(entry);
67
+ }
68
+ getAll() {
69
+ return [...this.entries];
70
+ }
71
+ clearAll() {
72
+ this.entries = [];
73
+ }
74
+ clearSession(sessionId) {
75
+ this.entries = this.entries.filter((e) => e.sessionId !== sessionId);
76
+ }
77
+ };
78
+ var DB_DIR = (0, import_node_path2.join)((0, import_node_os2.homedir)(), ".tokenwatch");
79
+ var DB_PATH = (0, import_node_path2.join)(DB_DIR, "usage.db");
80
+ var SqliteStorage = class {
81
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
82
+ db;
83
+ constructor(dbPath = DB_PATH) {
84
+ let BetterSqlite3;
85
+ try {
86
+ const req = typeof globalThis.require === "function" ? (
87
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
88
+ globalThis.require
89
+ ) : (0, import_node_module.createRequire)(import_meta.url);
90
+ BetterSqlite3 = req("better-sqlite3");
91
+ } catch {
92
+ throw new Error(
93
+ "[tokenwatch] SQLite storage requires better-sqlite3. Run: npm install better-sqlite3"
94
+ );
95
+ }
96
+ (0, import_node_fs2.mkdirSync)(DB_DIR, { recursive: true });
97
+ this.db = new BetterSqlite3(dbPath);
98
+ this.migrate();
99
+ }
100
+ migrate() {
101
+ this.db.exec(`
102
+ CREATE TABLE IF NOT EXISTS usage (
103
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
104
+ model TEXT NOT NULL,
105
+ input_tokens INTEGER NOT NULL,
106
+ output_tokens INTEGER NOT NULL,
107
+ cost_usd REAL NOT NULL,
108
+ session_id TEXT,
109
+ user_id TEXT,
110
+ timestamp TEXT NOT NULL
111
+ )
112
+ `);
113
+ }
114
+ record(entry) {
115
+ this.db.prepare(
116
+ `INSERT INTO usage
117
+ (model, input_tokens, output_tokens, cost_usd, session_id, user_id, timestamp)
118
+ VALUES (?, ?, ?, ?, ?, ?, ?)`
119
+ ).run(
120
+ entry.model,
121
+ entry.inputTokens,
122
+ entry.outputTokens,
123
+ entry.costUSD,
124
+ entry.sessionId ?? null,
125
+ entry.userId ?? null,
126
+ entry.timestamp
127
+ );
128
+ }
129
+ getAll() {
130
+ const rows = this.db.prepare("SELECT * FROM usage").all();
131
+ return rows.map((r) => ({
132
+ model: r.model,
133
+ inputTokens: r.input_tokens,
134
+ outputTokens: r.output_tokens,
135
+ costUSD: r.cost_usd,
136
+ ...r.session_id != null && { sessionId: r.session_id },
137
+ ...r.user_id != null && { userId: r.user_id },
138
+ timestamp: r.timestamp
139
+ }));
140
+ }
141
+ clearAll() {
142
+ this.db.exec("DELETE FROM usage");
143
+ }
144
+ clearSession(sessionId) {
145
+ this.db.prepare("DELETE FROM usage WHERE session_id = ?").run(sessionId);
146
+ }
147
+ };
148
+ function createStorage(type) {
149
+ if (type === "sqlite") return new SqliteStorage();
150
+ return new MemoryStorage();
151
+ }
152
+
153
+ // src/core/tracker.ts
154
+ var import_zod = require("zod");
155
+
156
+ // src/core/pricing.ts
157
+ function resolvePrice(model, layers) {
158
+ const { customPrices, remotePrices, bundledPrices: bundledPrices2 } = layers;
159
+ const found = lookupInMap(model, customPrices) ?? lookupInMap(model, remotePrices) ?? lookupInMap(model, bundledPrices2);
160
+ if (found) return found;
161
+ console.warn(
162
+ `[tokenwatch] Unknown model "${model}". Cost will be recorded as $0. Add it via customPrices or update prices with: tokenwatch sync`
163
+ );
164
+ return { input: 0, output: 0 };
165
+ }
166
+ function findPrice(model, layers) {
167
+ const { customPrices, remotePrices, bundledPrices: bundledPrices2 } = layers;
168
+ return lookupInMap(model, customPrices) ?? lookupInMap(model, remotePrices) ?? lookupInMap(model, bundledPrices2);
169
+ }
170
+ function lookupInMap(model, map) {
171
+ if (!map) return void 0;
172
+ if (model in map) return map[model];
173
+ for (const key of Object.keys(map)) {
174
+ if (model.startsWith(key) || key.startsWith(model)) {
175
+ return map[key];
176
+ }
177
+ }
178
+ return void 0;
179
+ }
180
+ function calculateCost(inputTokens, outputTokens, price) {
181
+ return inputTokens / 1e6 * price.input + outputTokens / 1e6 * price.output;
182
+ }
183
+
184
+ // prices.json
185
+ var prices_default = {
186
+ updated_at: "2026-04-16",
187
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
188
+ models: {
189
+ "gpt-4o": {
190
+ input: 2.5,
191
+ output: 10,
192
+ maxInputTokens: 128e3
193
+ },
194
+ "gpt-4o-mini": {
195
+ input: 0.15,
196
+ output: 0.6,
197
+ maxInputTokens: 128e3
198
+ },
199
+ "gpt-5": {
200
+ input: 1.25,
201
+ output: 10,
202
+ maxInputTokens: 272e3
203
+ },
204
+ "gpt-5-mini": {
205
+ input: 0.25,
206
+ output: 2,
207
+ maxInputTokens: 272e3
208
+ },
209
+ "gpt-5-nano": {
210
+ input: 0.05,
211
+ output: 0.4,
212
+ maxInputTokens: 272e3
213
+ },
214
+ "claude-opus-4-6": {
215
+ input: 5,
216
+ output: 25,
217
+ maxInputTokens: 1e6
218
+ },
219
+ "claude-sonnet-4-6": {
220
+ input: 3,
221
+ output: 15,
222
+ maxInputTokens: 1e6
223
+ },
224
+ "claude-haiku-4-5": {
225
+ input: 1,
226
+ output: 5,
227
+ maxInputTokens: 2e5
228
+ },
229
+ "gemini-2.5-pro": {
230
+ input: 1.25,
231
+ output: 10,
232
+ maxInputTokens: 1048576
233
+ },
234
+ "gemini-2.5-flash": {
235
+ input: 0.3,
236
+ output: 2.5,
237
+ maxInputTokens: 1048576
238
+ },
239
+ "deepseek-chat": {
240
+ input: 0.28,
241
+ output: 0.42,
242
+ maxInputTokens: 131072
243
+ },
244
+ "deepseek-reasoner": {
245
+ input: 0.28,
246
+ output: 0.42,
247
+ maxInputTokens: 131072
248
+ },
249
+ "claude-opus-4-5": {
250
+ input: 5,
251
+ output: 25,
252
+ maxInputTokens: 2e5
253
+ },
254
+ "claude-opus-4-7": {
255
+ input: 5,
256
+ output: 25,
257
+ maxInputTokens: 1e6
258
+ },
259
+ "claude-opus-4-1": {
260
+ input: 15,
261
+ output: 75,
262
+ maxInputTokens: 2e5
263
+ },
264
+ "claude-sonnet-4-5": {
265
+ input: 3,
266
+ output: 15,
267
+ maxInputTokens: 2e5
268
+ },
269
+ "gpt-oss-120b": {
270
+ input: 3,
271
+ output: 4.5,
272
+ maxInputTokens: 131072
273
+ },
274
+ "gpt-3.5-turbo": {
275
+ input: 0.5,
276
+ output: 1.5,
277
+ maxInputTokens: 16385
278
+ },
279
+ "gpt-3.5-turbo-0125": {
280
+ input: 0.5,
281
+ output: 1.5,
282
+ maxInputTokens: 16385
283
+ },
284
+ "gpt-35-turbo": {
285
+ input: 0.5,
286
+ output: 1.5,
287
+ maxInputTokens: 4097
288
+ },
289
+ "gpt-35-turbo-0125": {
290
+ input: 0.5,
291
+ output: 1.5,
292
+ maxInputTokens: 16384
293
+ },
294
+ "gpt-35-turbo-1106": {
295
+ input: 1,
296
+ output: 2,
297
+ maxInputTokens: 16384
298
+ },
299
+ "gpt-35-turbo-16k": {
300
+ input: 3,
301
+ output: 4,
302
+ maxInputTokens: 16385
303
+ },
304
+ "gpt-35-turbo-16k-0613": {
305
+ input: 3,
306
+ output: 4,
307
+ maxInputTokens: 16385
308
+ },
309
+ "gpt-4": {
310
+ input: 30,
311
+ output: 60,
312
+ maxInputTokens: 8192
313
+ },
314
+ "gpt-4-0125-preview": {
315
+ input: 10,
316
+ output: 30,
317
+ maxInputTokens: 128e3
318
+ },
319
+ "gpt-4-0613": {
320
+ input: 30,
321
+ output: 60,
322
+ maxInputTokens: 8192
323
+ },
324
+ "gpt-4-1106-preview": {
325
+ input: 10,
326
+ output: 30,
327
+ maxInputTokens: 128e3
328
+ },
329
+ "gpt-4-32k": {
330
+ input: 60,
331
+ output: 120,
332
+ maxInputTokens: 32768
333
+ },
334
+ "gpt-4-32k-0613": {
335
+ input: 60,
336
+ output: 120,
337
+ maxInputTokens: 32768
338
+ },
339
+ "gpt-4-turbo": {
340
+ input: 10,
341
+ output: 30,
342
+ maxInputTokens: 128e3
343
+ },
344
+ "gpt-4-turbo-2024-04-09": {
345
+ input: 10,
346
+ output: 30,
347
+ maxInputTokens: 128e3
348
+ },
349
+ "gpt-4-turbo-vision-preview": {
350
+ input: 10,
351
+ output: 30,
352
+ maxInputTokens: 128e3
353
+ },
354
+ "gpt-4.1": {
355
+ input: 2,
356
+ output: 8,
357
+ maxInputTokens: 1047576
358
+ },
359
+ "gpt-4.1-2025-04-14": {
360
+ input: 2,
361
+ output: 8,
362
+ maxInputTokens: 1047576
363
+ },
364
+ "gpt-4.1-mini": {
365
+ input: 0.4,
366
+ output: 1.6,
367
+ maxInputTokens: 1047576
368
+ },
369
+ "gpt-4.1-mini-2025-04-14": {
370
+ input: 0.4,
371
+ output: 1.6,
372
+ maxInputTokens: 1047576
373
+ },
374
+ "gpt-4.1-nano": {
375
+ input: 0.1,
376
+ output: 0.4,
377
+ maxInputTokens: 1047576
378
+ },
379
+ "gpt-4.1-nano-2025-04-14": {
380
+ input: 0.1,
381
+ output: 0.4,
382
+ maxInputTokens: 1047576
383
+ },
384
+ "gpt-4.5-preview": {
385
+ input: 75,
386
+ output: 150,
387
+ maxInputTokens: 128e3
388
+ },
389
+ "gpt-4o-2024-05-13": {
390
+ input: 5,
391
+ output: 15,
392
+ maxInputTokens: 128e3
393
+ },
394
+ "gpt-4o-2024-08-06": {
395
+ input: 2.5,
396
+ output: 10,
397
+ maxInputTokens: 128e3
398
+ },
399
+ "gpt-4o-2024-11-20": {
400
+ input: 2.5,
401
+ output: 10,
402
+ maxInputTokens: 128e3
403
+ },
404
+ "gpt-audio-2025-08-28": {
405
+ input: 2.5,
406
+ output: 10,
407
+ maxInputTokens: 128e3
408
+ },
409
+ "gpt-audio-1.5-2026-02-23": {
410
+ input: 2.5,
411
+ output: 10,
412
+ maxInputTokens: 128e3
413
+ },
414
+ "gpt-audio-mini-2025-10-06": {
415
+ input: 0.6,
416
+ output: 2.4,
417
+ maxInputTokens: 128e3
418
+ },
419
+ "gpt-4o-audio-preview-2024-12-17": {
420
+ input: 2.5,
421
+ output: 10,
422
+ maxInputTokens: 128e3
423
+ },
424
+ "gpt-4o-mini-2024-07-18": {
425
+ input: 0.15,
426
+ output: 0.6,
427
+ maxInputTokens: 128e3
428
+ },
429
+ "gpt-4o-mini-audio-preview-2024-12-17": {
430
+ input: 0.15,
431
+ output: 0.6,
432
+ maxInputTokens: 128e3
433
+ },
434
+ "gpt-4o-mini-realtime-preview-2024-12-17": {
435
+ input: 0.6,
436
+ output: 2.4,
437
+ maxInputTokens: 128e3
438
+ },
439
+ "gpt-realtime-2025-08-28": {
440
+ input: 4,
441
+ output: 16,
442
+ maxInputTokens: 32e3
443
+ },
444
+ "gpt-realtime-1.5-2026-02-23": {
445
+ input: 4,
446
+ output: 16,
447
+ maxInputTokens: 32e3
448
+ },
449
+ "gpt-realtime-mini-2025-10-06": {
450
+ input: 0.6,
451
+ output: 2.4,
452
+ maxInputTokens: 128e3
453
+ },
454
+ "gpt-4o-mini-transcribe": {
455
+ input: 1.25,
456
+ output: 5,
457
+ maxInputTokens: 16e3
458
+ },
459
+ "gpt-4o-realtime-preview-2024-10-01": {
460
+ input: 5,
461
+ output: 20,
462
+ maxInputTokens: 128e3
463
+ },
464
+ "gpt-4o-realtime-preview-2024-12-17": {
465
+ input: 5,
466
+ output: 20,
467
+ maxInputTokens: 128e3
468
+ },
469
+ "gpt-4o-transcribe": {
470
+ input: 2.5,
471
+ output: 10,
472
+ maxInputTokens: 16e3
473
+ },
474
+ "gpt-4o-transcribe-diarize": {
475
+ input: 2.5,
476
+ output: 10,
477
+ maxInputTokens: 16e3
478
+ },
479
+ "gpt-5.1-2025-11-13": {
480
+ input: 1.25,
481
+ output: 10,
482
+ maxInputTokens: 272e3
483
+ },
484
+ "gpt-5.1-chat-2025-11-13": {
485
+ input: 1.25,
486
+ output: 10,
487
+ maxInputTokens: 128e3
488
+ },
489
+ "gpt-5.1-codex-2025-11-13": {
490
+ input: 1.25,
491
+ output: 10,
492
+ maxInputTokens: 272e3
493
+ },
494
+ "gpt-5.1-codex-mini-2025-11-13": {
495
+ input: 0.25,
496
+ output: 2,
497
+ maxInputTokens: 272e3
498
+ },
499
+ "gpt-5-2025-08-07": {
500
+ input: 1.25,
501
+ output: 10,
502
+ maxInputTokens: 272e3
503
+ },
504
+ "gpt-5-chat": {
505
+ input: 1.25,
506
+ output: 10,
507
+ maxInputTokens: 128e3
508
+ },
509
+ "gpt-5-chat-latest": {
510
+ input: 1.25,
511
+ output: 10,
512
+ maxInputTokens: 128e3
513
+ },
514
+ "gpt-5-codex": {
515
+ input: 1.25,
516
+ output: 10,
517
+ maxInputTokens: 272e3
518
+ },
519
+ "gpt-5-mini-2025-08-07": {
520
+ input: 0.25,
521
+ output: 2,
522
+ maxInputTokens: 272e3
523
+ },
524
+ "gpt-5-nano-2025-08-07": {
525
+ input: 0.05,
526
+ output: 0.4,
527
+ maxInputTokens: 272e3
528
+ },
529
+ "gpt-5-pro": {
530
+ input: 15,
531
+ output: 120,
532
+ maxInputTokens: 128e3
533
+ },
534
+ "gpt-5.1": {
535
+ input: 1.25,
536
+ output: 10,
537
+ maxInputTokens: 272e3
538
+ },
539
+ "gpt-5.1-chat": {
540
+ input: 1.25,
541
+ output: 10,
542
+ maxInputTokens: 128e3
543
+ },
544
+ "gpt-5.1-codex": {
545
+ input: 1.25,
546
+ output: 10,
547
+ maxInputTokens: 272e3
548
+ },
549
+ "gpt-5.1-codex-max": {
550
+ input: 1.25,
551
+ output: 10,
552
+ maxInputTokens: 272e3
553
+ },
554
+ "gpt-5.1-codex-mini": {
555
+ input: 0.25,
556
+ output: 2,
557
+ maxInputTokens: 272e3
558
+ },
559
+ "gpt-5.2": {
560
+ input: 1.75,
561
+ output: 14,
562
+ maxInputTokens: 272e3
563
+ },
564
+ "gpt-5.2-2025-12-11": {
565
+ input: 1.75,
566
+ output: 14,
567
+ maxInputTokens: 272e3
568
+ },
569
+ "gpt-5.2-chat": {
570
+ input: 1.75,
571
+ output: 14,
572
+ maxInputTokens: 128e3
573
+ },
574
+ "gpt-5.2-chat-2025-12-11": {
575
+ input: 1.75,
576
+ output: 14,
577
+ maxInputTokens: 128e3
578
+ },
579
+ "gpt-5.2-codex": {
580
+ input: 1.75,
581
+ output: 14,
582
+ maxInputTokens: 272e3
583
+ },
584
+ "gpt-5.3-chat": {
585
+ input: 1.75,
586
+ output: 14,
587
+ maxInputTokens: 128e3
588
+ },
589
+ "gpt-5.3-codex": {
590
+ input: 1.75,
591
+ output: 14,
592
+ maxInputTokens: 272e3
593
+ },
594
+ "gpt-5.2-pro": {
595
+ input: 21,
596
+ output: 168,
597
+ maxInputTokens: 272e3
598
+ },
599
+ "gpt-5.2-pro-2025-12-11": {
600
+ input: 21,
601
+ output: 168,
602
+ maxInputTokens: 272e3
603
+ },
604
+ "gpt-5.4": {
605
+ input: 2.5,
606
+ output: 15,
607
+ maxInputTokens: 105e4
608
+ },
609
+ "gpt-5.4-2026-03-05": {
610
+ input: 2.5,
611
+ output: 15,
612
+ maxInputTokens: 105e4
613
+ },
614
+ "gpt-5.4-pro": {
615
+ input: 30,
616
+ output: 180,
617
+ maxInputTokens: 105e4
618
+ },
619
+ "gpt-5.4-pro-2026-03-05": {
620
+ input: 30,
621
+ output: 180,
622
+ maxInputTokens: 105e4
623
+ },
624
+ "gpt-5.4-mini": {
625
+ input: 0.75,
626
+ output: 4.5,
627
+ maxInputTokens: 272e3
628
+ },
629
+ "gpt-5.4-nano": {
630
+ input: 0.2,
631
+ output: 1.25,
632
+ maxInputTokens: 272e3
633
+ },
634
+ "o1-2024-12-17": {
635
+ input: 15,
636
+ output: 60,
637
+ maxInputTokens: 2e5
638
+ },
639
+ "o1-mini": {
640
+ input: 1.21,
641
+ output: 4.84,
642
+ maxInputTokens: 128e3
643
+ },
644
+ "o1-mini-2024-09-12": {
645
+ input: 1.1,
646
+ output: 4.4,
647
+ maxInputTokens: 128e3
648
+ },
649
+ "o1-preview": {
650
+ input: 15,
651
+ output: 60,
652
+ maxInputTokens: 128e3
653
+ },
654
+ "o1-preview-2024-09-12": {
655
+ input: 15,
656
+ output: 60,
657
+ maxInputTokens: 128e3
658
+ },
659
+ "o3-2025-04-16": {
660
+ input: 2,
661
+ output: 8,
662
+ maxInputTokens: 2e5
663
+ },
664
+ "o3-mini": {
665
+ input: 1.1,
666
+ output: 4.4,
667
+ maxInputTokens: 2e5
668
+ },
669
+ "o3-mini-2025-01-31": {
670
+ input: 1.1,
671
+ output: 4.4,
672
+ maxInputTokens: 2e5
673
+ },
674
+ "o3-pro": {
675
+ input: 20,
676
+ output: 80,
677
+ maxInputTokens: 2e5
678
+ },
679
+ "o3-pro-2025-06-10": {
680
+ input: 20,
681
+ output: 80,
682
+ maxInputTokens: 2e5
683
+ },
684
+ "o4-mini": {
685
+ input: 1.1,
686
+ output: 4.4,
687
+ maxInputTokens: 2e5
688
+ },
689
+ "o4-mini-2025-04-16": {
690
+ input: 1.1,
691
+ output: 4.4,
692
+ maxInputTokens: 2e5
693
+ },
694
+ "deepseek-v3.2": {
695
+ input: 0.28,
696
+ output: 0.4,
697
+ maxInputTokens: 163840
698
+ },
699
+ "deepseek-v3.2-speciale": {
700
+ input: 0.58,
701
+ output: 1.68,
702
+ maxInputTokens: 163840
703
+ },
704
+ "deepseek-r1": {
705
+ input: 0.55,
706
+ output: 2.19,
707
+ maxInputTokens: 65536
708
+ },
709
+ "deepseek-v3": {
710
+ input: 0.27,
711
+ output: 1.1,
712
+ maxInputTokens: 65536
713
+ },
714
+ "deepseek-v3-0324": {
715
+ input: 0.2,
716
+ output: 0.6,
717
+ maxInputTokens: 131072
718
+ },
719
+ "chatgpt-4o-latest": {
720
+ input: 5,
721
+ output: 15,
722
+ maxInputTokens: 128e3
723
+ },
724
+ "claude-haiku-4-5-20251001": {
725
+ input: 1,
726
+ output: 5,
727
+ maxInputTokens: 2e5
728
+ },
729
+ "claude-3-7-sonnet-20250219": {
730
+ input: 3,
731
+ output: 15,
732
+ maxInputTokens: 2e5
733
+ },
734
+ "claude-3-haiku-20240307": {
735
+ input: 0.25,
736
+ output: 1.25,
737
+ maxInputTokens: 2e5
738
+ },
739
+ "claude-3-opus-20240229": {
740
+ input: 15,
741
+ output: 75,
742
+ maxInputTokens: 2e5
743
+ },
744
+ "claude-4-opus-20250514": {
745
+ input: 15,
746
+ output: 75,
747
+ maxInputTokens: 2e5
748
+ },
749
+ "claude-4-sonnet-20250514": {
750
+ input: 3,
751
+ output: 15,
752
+ maxInputTokens: 1e6
753
+ },
754
+ "claude-sonnet-4-5-20250929": {
755
+ input: 3,
756
+ output: 15,
757
+ maxInputTokens: 2e5
758
+ },
759
+ "claude-sonnet-4-5-20250929-v1:0": {
760
+ input: 3,
761
+ output: 15,
762
+ maxInputTokens: 2e5
763
+ },
764
+ "claude-opus-4-1-20250805": {
765
+ input: 15,
766
+ output: 75,
767
+ maxInputTokens: 2e5
768
+ },
769
+ "claude-opus-4-20250514": {
770
+ input: 15,
771
+ output: 75,
772
+ maxInputTokens: 2e5
773
+ },
774
+ "claude-opus-4-5-20251101": {
775
+ input: 5,
776
+ output: 25,
777
+ maxInputTokens: 2e5
778
+ },
779
+ "claude-opus-4-6-20260205": {
780
+ input: 5,
781
+ output: 25,
782
+ maxInputTokens: 1e6
783
+ },
784
+ "claude-opus-4-7-20260416": {
785
+ input: 5,
786
+ output: 25,
787
+ maxInputTokens: 1e6
788
+ },
789
+ "claude-sonnet-4-20250514": {
790
+ input: 3,
791
+ output: 15,
792
+ maxInputTokens: 1e6
793
+ },
794
+ "codex-mini-latest": {
795
+ input: 1.5,
796
+ output: 6,
797
+ maxInputTokens: 2e5
798
+ },
799
+ "deepseek-ai/deepseek-r1": {
800
+ input: 3,
801
+ output: 7,
802
+ maxInputTokens: 128e3
803
+ },
804
+ "deepseek-ai/deepseek-r1-0528": {
805
+ input: 135e3,
806
+ output: 54e4,
807
+ maxInputTokens: 161e3
808
+ },
809
+ "deepseek-ai/deepseek-r1-0528-turbo": {
810
+ input: 1,
811
+ output: 3,
812
+ maxInputTokens: 32768
813
+ },
814
+ "deepseek-ai/deepseek-r1-distill-llama-70b": {
815
+ input: 0.25,
816
+ output: 0.75,
817
+ maxInputTokens: 128e3
818
+ },
819
+ "deepseek-ai/deepseek-r1-distill-qwen-32b": {
820
+ input: 0.15,
821
+ output: 0.15
822
+ },
823
+ "deepseek-ai/deepseek-r1-turbo": {
824
+ input: 1,
825
+ output: 3,
826
+ maxInputTokens: 40960
827
+ },
828
+ "deepseek-ai/deepseek-v3": {
829
+ input: 1.25,
830
+ output: 1.25,
831
+ maxInputTokens: 65536
832
+ },
833
+ "deepseek-ai/deepseek-v3-0324": {
834
+ input: 114e3,
835
+ output: 275e3,
836
+ maxInputTokens: 161e3
837
+ },
838
+ "deepseek-ai/deepseek-v3.1": {
839
+ input: 55e3,
840
+ output: 165e3,
841
+ maxInputTokens: 128e3
842
+ },
843
+ "deepseek-ai/deepseek-v3.1-terminus": {
844
+ input: 0.27,
845
+ output: 1,
846
+ maxInputTokens: 163840
847
+ },
848
+ "deepseek-coder": {
849
+ input: 0.14,
850
+ output: 0.28,
851
+ maxInputTokens: 128e3
852
+ },
853
+ "gemini-2.0-flash": {
854
+ input: 0.1,
855
+ output: 0.4,
856
+ maxInputTokens: 1048576
857
+ },
858
+ "gemini-2.0-flash-001": {
859
+ input: 0.1,
860
+ output: 0.4,
861
+ maxInputTokens: 1048576
862
+ },
863
+ "gemini-2.0-flash-lite": {
864
+ input: 0.075,
865
+ output: 0.3,
866
+ maxInputTokens: 1048576
867
+ },
868
+ "gemini-2.0-flash-lite-001": {
869
+ input: 0.075,
870
+ output: 0.3,
871
+ maxInputTokens: 1048576
872
+ },
873
+ "gemini-2.5-flash-image": {
874
+ input: 0.3,
875
+ output: 2.5,
876
+ maxInputTokens: 32768
877
+ },
878
+ "gemini-3-pro-image-preview": {
879
+ input: 2,
880
+ output: 12,
881
+ maxInputTokens: 65536
882
+ },
883
+ "gemini-3.1-flash-image-preview": {
884
+ input: 0.5,
885
+ output: 3,
886
+ maxInputTokens: 65536
887
+ },
888
+ "gemini-3.1-flash-lite-preview": {
889
+ input: 0.25,
890
+ output: 1.5,
891
+ maxInputTokens: 1048576
892
+ },
893
+ "gemini-2.5-flash-lite": {
894
+ input: 0.1,
895
+ output: 0.4,
896
+ maxInputTokens: 1048576
897
+ },
898
+ "gemini-2.5-flash-lite-preview-09-2025": {
899
+ input: 0.1,
900
+ output: 0.4,
901
+ maxInputTokens: 1048576
902
+ },
903
+ "gemini-2.5-flash-preview-09-2025": {
904
+ input: 0.3,
905
+ output: 2.5,
906
+ maxInputTokens: 1048576
907
+ },
908
+ "gemini-live-2.5-flash-preview-native-audio-09-2025": {
909
+ input: 0.3,
910
+ output: 2,
911
+ maxInputTokens: 1048576
912
+ },
913
+ "gemini-2.5-flash-lite-preview-06-17": {
914
+ input: 0.1,
915
+ output: 0.4,
916
+ maxInputTokens: 1048576
917
+ },
918
+ "gemini-3-pro-preview": {
919
+ input: 2,
920
+ output: 12,
921
+ maxInputTokens: 1048576
922
+ },
923
+ "gemini-3.1-pro-preview": {
924
+ input: 2,
925
+ output: 12,
926
+ maxInputTokens: 1048576
927
+ },
928
+ "gemini-3.1-pro-preview-customtools": {
929
+ input: 2,
930
+ output: 12,
931
+ maxInputTokens: 1048576
932
+ },
933
+ "gemini-3-flash-preview": {
934
+ input: 0.5,
935
+ output: 3,
936
+ maxInputTokens: 1048576
937
+ },
938
+ "gemini-robotics-er-1.5-preview": {
939
+ input: 0.3,
940
+ output: 2.5,
941
+ maxInputTokens: 1048576
942
+ },
943
+ "gemini-2.5-computer-use-preview-10-2025": {
944
+ input: 1.25,
945
+ output: 10,
946
+ maxInputTokens: 128e3
947
+ },
948
+ "gemini-flash-latest": {
949
+ input: 0.3,
950
+ output: 2.5,
951
+ maxInputTokens: 1048576
952
+ },
953
+ "gemini-flash-lite-latest": {
954
+ input: 0.1,
955
+ output: 0.4,
956
+ maxInputTokens: 1048576
957
+ },
958
+ "gemini-gemma-2-27b-it": {
959
+ input: 0.35,
960
+ output: 1.05,
961
+ maxInputTokens: 8192
962
+ },
963
+ "gemini-gemma-2-9b-it": {
964
+ input: 0.35,
965
+ output: 1.05,
966
+ maxInputTokens: 8192
967
+ },
968
+ "deepseek-ai/deepseek-v3.2": {
969
+ input: 0.28,
970
+ output: 0.4,
971
+ maxInputTokens: 163840
972
+ },
973
+ "gpt-3.5-turbo-1106": {
974
+ input: 1,
975
+ output: 2,
976
+ maxInputTokens: 16385
977
+ },
978
+ "gpt-3.5-turbo-16k": {
979
+ input: 3,
980
+ output: 4,
981
+ maxInputTokens: 16385
982
+ },
983
+ "gpt-4-0314": {
984
+ input: 30,
985
+ output: 60,
986
+ maxInputTokens: 8192
987
+ },
988
+ "gpt-4-turbo-preview": {
989
+ input: 10,
990
+ output: 30,
991
+ maxInputTokens: 128e3
992
+ },
993
+ "gpt-4o-audio-preview": {
994
+ input: 2.5,
995
+ output: 10,
996
+ maxInputTokens: 128e3
997
+ },
998
+ "gpt-4o-audio-preview-2025-06-03": {
999
+ input: 2.5,
1000
+ output: 10,
1001
+ maxInputTokens: 128e3
1002
+ },
1003
+ "gpt-audio": {
1004
+ input: 2.5,
1005
+ output: 10,
1006
+ maxInputTokens: 128e3
1007
+ },
1008
+ "gpt-audio-1.5": {
1009
+ input: 2.5,
1010
+ output: 10,
1011
+ maxInputTokens: 128e3
1012
+ },
1013
+ "gpt-audio-mini": {
1014
+ input: 0.6,
1015
+ output: 2.4,
1016
+ maxInputTokens: 128e3
1017
+ },
1018
+ "gpt-audio-mini-2025-12-15": {
1019
+ input: 0.6,
1020
+ output: 2.4,
1021
+ maxInputTokens: 128e3
1022
+ },
1023
+ "gpt-4o-mini-audio-preview": {
1024
+ input: 0.15,
1025
+ output: 0.6,
1026
+ maxInputTokens: 128e3
1027
+ },
1028
+ "gpt-4o-mini-realtime-preview": {
1029
+ input: 0.6,
1030
+ output: 2.4,
1031
+ maxInputTokens: 128e3
1032
+ },
1033
+ "gpt-4o-realtime-preview": {
1034
+ input: 5,
1035
+ output: 20,
1036
+ maxInputTokens: 128e3
1037
+ },
1038
+ "gpt-4o-realtime-preview-2025-06-03": {
1039
+ input: 5,
1040
+ output: 20,
1041
+ maxInputTokens: 128e3
1042
+ },
1043
+ "gpt-image-1.5": {
1044
+ input: 5,
1045
+ output: 10
1046
+ },
1047
+ "gpt-image-1.5-2025-12-16": {
1048
+ input: 5,
1049
+ output: 10
1050
+ },
1051
+ "gpt-5.1-chat-latest": {
1052
+ input: 1.25,
1053
+ output: 10,
1054
+ maxInputTokens: 128e3
1055
+ },
1056
+ "gpt-5.2-chat-latest": {
1057
+ input: 1.75,
1058
+ output: 14,
1059
+ maxInputTokens: 128e3
1060
+ },
1061
+ "gpt-5.3-chat-latest": {
1062
+ input: 1.75,
1063
+ output: 14,
1064
+ maxInputTokens: 128e3
1065
+ },
1066
+ "gpt-5-pro-2025-10-06": {
1067
+ input: 15,
1068
+ output: 120,
1069
+ maxInputTokens: 128e3
1070
+ },
1071
+ "gpt-realtime": {
1072
+ input: 4,
1073
+ output: 16,
1074
+ maxInputTokens: 32e3
1075
+ },
1076
+ "gpt-realtime-1.5": {
1077
+ input: 4,
1078
+ output: 16,
1079
+ maxInputTokens: 32e3
1080
+ },
1081
+ "gpt-realtime-mini": {
1082
+ input: 0.6,
1083
+ output: 2.4,
1084
+ maxInputTokens: 128e3
1085
+ },
1086
+ "deepseek-r1-distill-llama-70b": {
1087
+ input: 0.99,
1088
+ output: 0.99,
1089
+ maxInputTokens: 8e3
1090
+ },
1091
+ "deepseek-llama3.3-70b": {
1092
+ input: 0.2,
1093
+ output: 0.6,
1094
+ maxInputTokens: 131072
1095
+ },
1096
+ "deepseek-r1-0528": {
1097
+ input: 0.2,
1098
+ output: 0.6,
1099
+ maxInputTokens: 131072
1100
+ },
1101
+ "deepseek-r1-671b": {
1102
+ input: 0.8,
1103
+ output: 0.8,
1104
+ maxInputTokens: 131072
1105
+ },
1106
+ "deepseek-ai/deepseek-r1-distill-llama-8b": {
1107
+ input: 0.025,
1108
+ output: 0.025
1109
+ },
1110
+ "deepseek-ai/deepseek-r1-distill-qwen-1.5b": {
1111
+ input: 0.09,
1112
+ output: 0.09
1113
+ },
1114
+ "deepseek-ai/deepseek-r1-distill-qwen-14b": {
1115
+ input: 0.07,
1116
+ output: 0.07
1117
+ },
1118
+ "deepseek-ai/deepseek-r1-distill-qwen-7b": {
1119
+ input: 0.2,
1120
+ output: 0.2
1121
+ },
1122
+ o1: {
1123
+ input: 15,
1124
+ output: 60,
1125
+ maxInputTokens: 2e5
1126
+ },
1127
+ "o1-pro": {
1128
+ input: 150,
1129
+ output: 600,
1130
+ maxInputTokens: 2e5
1131
+ },
1132
+ "o1-pro-2025-03-19": {
1133
+ input: 150,
1134
+ output: 600,
1135
+ maxInputTokens: 2e5
1136
+ },
1137
+ o3: {
1138
+ input: 2,
1139
+ output: 8,
1140
+ maxInputTokens: 2e5
1141
+ },
1142
+ "gpt-oss-20b": {
1143
+ input: 0.09,
1144
+ output: 0.36
1145
+ },
1146
+ "deepseek-ai/deepseek-r1-0528-tput": {
1147
+ input: 0.55,
1148
+ output: 2.19,
1149
+ maxInputTokens: 128e3
1150
+ },
1151
+ "claude-3-5-haiku": {
1152
+ input: 1,
1153
+ output: 5,
1154
+ maxInputTokens: 2e5
1155
+ },
1156
+ "claude-3-5-haiku@20241022": {
1157
+ input: 1,
1158
+ output: 5,
1159
+ maxInputTokens: 2e5
1160
+ },
1161
+ "claude-haiku-4-5@20251001": {
1162
+ input: 1,
1163
+ output: 5,
1164
+ maxInputTokens: 2e5
1165
+ },
1166
+ "claude-3-5-sonnet": {
1167
+ input: 3,
1168
+ output: 15,
1169
+ maxInputTokens: 2e5
1170
+ },
1171
+ "claude-3-5-sonnet@20240620": {
1172
+ input: 3,
1173
+ output: 15,
1174
+ maxInputTokens: 2e5
1175
+ },
1176
+ "claude-3-7-sonnet@20250219": {
1177
+ input: 3,
1178
+ output: 15,
1179
+ maxInputTokens: 2e5
1180
+ },
1181
+ "claude-3-haiku": {
1182
+ input: 0.25,
1183
+ output: 1.25,
1184
+ maxInputTokens: 2e5
1185
+ },
1186
+ "claude-3-haiku@20240307": {
1187
+ input: 0.25,
1188
+ output: 1.25,
1189
+ maxInputTokens: 2e5
1190
+ },
1191
+ "claude-3-opus": {
1192
+ input: 15,
1193
+ output: 75,
1194
+ maxInputTokens: 2e5
1195
+ },
1196
+ "claude-3-opus@20240229": {
1197
+ input: 15,
1198
+ output: 75,
1199
+ maxInputTokens: 2e5
1200
+ },
1201
+ "claude-3-sonnet": {
1202
+ input: 3,
1203
+ output: 15,
1204
+ maxInputTokens: 2e5
1205
+ },
1206
+ "claude-3-sonnet@20240229": {
1207
+ input: 3,
1208
+ output: 15,
1209
+ maxInputTokens: 2e5
1210
+ },
1211
+ "claude-opus-4": {
1212
+ input: 15,
1213
+ output: 75,
1214
+ maxInputTokens: 2e5
1215
+ },
1216
+ "claude-opus-4-1@20250805": {
1217
+ input: 15,
1218
+ output: 75,
1219
+ maxInputTokens: 2e5
1220
+ },
1221
+ "claude-opus-4-5@20251101": {
1222
+ input: 5,
1223
+ output: 25,
1224
+ maxInputTokens: 2e5
1225
+ },
1226
+ "claude-opus-4-6@default": {
1227
+ input: 5,
1228
+ output: 25,
1229
+ maxInputTokens: 1e6
1230
+ },
1231
+ "claude-opus-4-7@default": {
1232
+ input: 5,
1233
+ output: 25,
1234
+ maxInputTokens: 1e6
1235
+ },
1236
+ "claude-sonnet-4-5@20250929": {
1237
+ input: 3,
1238
+ output: 15,
1239
+ maxInputTokens: 2e5
1240
+ },
1241
+ "claude-opus-4@20250514": {
1242
+ input: 15,
1243
+ output: 75,
1244
+ maxInputTokens: 2e5
1245
+ },
1246
+ "claude-sonnet-4": {
1247
+ input: 3,
1248
+ output: 15,
1249
+ maxInputTokens: 1e6
1250
+ },
1251
+ "claude-sonnet-4@20250514": {
1252
+ input: 3,
1253
+ output: 15,
1254
+ maxInputTokens: 1e6
1255
+ },
1256
+ "deepseek-ai/deepseek-v3.1-maas": {
1257
+ input: 1.35,
1258
+ output: 5.4,
1259
+ maxInputTokens: 163840
1260
+ },
1261
+ "deepseek-ai/deepseek-v3.2-maas": {
1262
+ input: 0.56,
1263
+ output: 1.68,
1264
+ maxInputTokens: 163840
1265
+ },
1266
+ "deepseek-ai/deepseek-r1-0528-maas": {
1267
+ input: 1.35,
1268
+ output: 5.4,
1269
+ maxInputTokens: 65336
1270
+ },
1271
+ "deepseek-ai/deepseek-ocr-maas": {
1272
+ input: 0.3,
1273
+ output: 1.2
1274
+ },
1275
+ "deepseek-r1-8b": {
1276
+ input: 0.1,
1277
+ output: 0.2,
1278
+ maxInputTokens: 65536
1279
+ },
1280
+ "deepseek-r1-7b-qwen": {
1281
+ input: 0.08,
1282
+ output: 0.15,
1283
+ maxInputTokens: 131072
1284
+ },
1285
+ "deepseek-coder-6.7b": {
1286
+ input: 0.06,
1287
+ output: 0.12,
1288
+ maxInputTokens: 16384
1289
+ },
1290
+ "gpt-4o-mini-transcribe-2025-03-20": {
1291
+ input: 1.25,
1292
+ output: 5,
1293
+ maxInputTokens: 16e3
1294
+ },
1295
+ "gpt-4o-mini-transcribe-2025-12-15": {
1296
+ input: 1.25,
1297
+ output: 5,
1298
+ maxInputTokens: 16e3
1299
+ },
1300
+ "gpt-realtime-mini-2025-12-15": {
1301
+ input: 0.6,
1302
+ output: 2.4,
1303
+ maxInputTokens: 128e3
1304
+ },
1305
+ "gemini-2.5-flash-native-audio-latest": {
1306
+ input: 0.3,
1307
+ output: 2.5,
1308
+ maxInputTokens: 1048576
1309
+ },
1310
+ "gemini-2.5-flash-native-audio-preview-09-2025": {
1311
+ input: 0.3,
1312
+ output: 2.5,
1313
+ maxInputTokens: 1048576
1314
+ },
1315
+ "gemini-2.5-flash-native-audio-preview-12-2025": {
1316
+ input: 0.3,
1317
+ output: 2.5,
1318
+ maxInputTokens: 1048576
1319
+ },
1320
+ "gemini-3.1-flash-live-preview": {
1321
+ input: 0.75,
1322
+ output: 4.5,
1323
+ maxInputTokens: 131072
1324
+ },
1325
+ "gemini-pro-latest": {
1326
+ input: 1.25,
1327
+ output: 10,
1328
+ maxInputTokens: 1048576
1329
+ },
1330
+ "gemini-exp-1206": {
1331
+ input: 0.3,
1332
+ output: 2.5,
1333
+ maxInputTokens: 1048576
1334
+ },
1335
+ "claude-sonnet-4-6@default": {
1336
+ input: 3,
1337
+ output: 15,
1338
+ maxInputTokens: 1e6
1339
+ }
1340
+ }
1341
+ };
1342
+
1343
+ // src/core/tracker.ts
1344
+ var bundledPrices = prices_default.models;
1345
+ var ModelPriceSchema = import_zod.z.object({
1346
+ input: import_zod.z.number().nonnegative(),
1347
+ output: import_zod.z.number().nonnegative(),
1348
+ maxInputTokens: import_zod.z.number().positive().optional()
1349
+ });
1350
+ var TrackerConfigSchema = import_zod.z.object({
1351
+ storage: import_zod.z.union([import_zod.z.enum(["memory", "sqlite"]), import_zod.z.custom((v) => {
1352
+ return v !== null && typeof v === "object" && typeof v.record === "function" && typeof v.getAll === "function" && typeof v.clearAll === "function" && typeof v.clearSession === "function";
1353
+ })]).optional().default("memory"),
1354
+ alertThreshold: import_zod.z.number().positive().optional(),
1355
+ webhookUrl: import_zod.z.string().url().optional(),
1356
+ syncPrices: import_zod.z.boolean().optional().default(true),
1357
+ customPrices: import_zod.z.record(import_zod.z.string(), ModelPriceSchema).optional()
1358
+ });
1359
+ function createTracker(config = {}) {
1360
+ const parsed = TrackerConfigSchema.safeParse(config);
1361
+ if (!parsed.success) {
1362
+ const issues = parsed.error.issues.map((i) => ` ${i.path.join(".")}: ${i.message}`).join("\n");
1363
+ throw new Error(`[tokenwatch] Invalid config:
1364
+ ${issues}`);
1365
+ }
1366
+ const {
1367
+ storage: storageOption,
1368
+ alertThreshold,
1369
+ webhookUrl,
1370
+ syncPrices,
1371
+ customPrices
1372
+ } = parsed.data;
1373
+ const storage = typeof storageOption === "object" ? storageOption : createStorage(storageOption);
1374
+ let remotePrices;
1375
+ if (syncPrices) {
1376
+ getRemotePrices().then((result) => {
1377
+ if (result) remotePrices = result;
1378
+ }).catch(() => {
1379
+ });
1380
+ }
1381
+ let alertFired = false;
1382
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
1383
+ function resolveModelPrice(model) {
1384
+ return resolvePrice(model, {
1385
+ bundledPrices,
1386
+ ...customPrices !== void 0 && { customPrices },
1387
+ ...remotePrices !== void 0 && { remotePrices }
1388
+ });
1389
+ }
1390
+ function track(entry) {
1391
+ const price = resolveModelPrice(entry.model);
1392
+ const costUSD = calculateCost(entry.inputTokens, entry.outputTokens, price);
1393
+ const full = {
1394
+ ...entry,
1395
+ costUSD,
1396
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1397
+ };
1398
+ storage.record(full);
1399
+ maybeFireAlert();
1400
+ }
1401
+ function maybeFireAlert() {
1402
+ if (!alertThreshold || !webhookUrl || alertFired) return;
1403
+ alertFired = true;
1404
+ Promise.resolve(storage.getAll()).then((entries) => {
1405
+ const total = computeTotal(entries);
1406
+ if (total < alertThreshold) {
1407
+ alertFired = false;
1408
+ return;
1409
+ }
1410
+ const payload = {
1411
+ text: `[tokenwatch] Alert: total cost reached $${total.toFixed(4)} USD (threshold: $${alertThreshold})`
1412
+ };
1413
+ fetch(webhookUrl, {
1414
+ method: "POST",
1415
+ headers: { "Content-Type": "application/json" },
1416
+ body: JSON.stringify(payload)
1417
+ }).catch(() => {
1418
+ });
1419
+ }).catch(() => {
1420
+ alertFired = false;
1421
+ });
1422
+ }
1423
+ async function getReport() {
1424
+ const entries = await Promise.resolve(storage.getAll());
1425
+ const byModel = {};
1426
+ const bySession = {};
1427
+ const byUser = {};
1428
+ let totalInput = 0;
1429
+ let totalOutput = 0;
1430
+ let totalCost = 0;
1431
+ let lastTimestamp = startedAt;
1432
+ for (const e of entries) {
1433
+ totalInput += e.inputTokens;
1434
+ totalOutput += e.outputTokens;
1435
+ totalCost += e.costUSD;
1436
+ if (e.timestamp > lastTimestamp) lastTimestamp = e.timestamp;
1437
+ const m = byModel[e.model] ??= { costUSD: 0, calls: 0, tokens: { input: 0, output: 0 } };
1438
+ m.costUSD += e.costUSD;
1439
+ m.calls += 1;
1440
+ m.tokens.input += e.inputTokens;
1441
+ m.tokens.output += e.outputTokens;
1442
+ if (e.sessionId) {
1443
+ const s = bySession[e.sessionId] ??= { costUSD: 0, calls: 0 };
1444
+ s.costUSD += e.costUSD;
1445
+ s.calls += 1;
1446
+ }
1447
+ if (e.userId) {
1448
+ const u = byUser[e.userId] ??= { costUSD: 0, calls: 0 };
1449
+ u.costUSD += e.costUSD;
1450
+ u.calls += 1;
1451
+ }
1452
+ }
1453
+ return {
1454
+ totalCostUSD: totalCost,
1455
+ totalTokens: { input: totalInput, output: totalOutput },
1456
+ byModel,
1457
+ bySession,
1458
+ byUser,
1459
+ period: { from: startedAt, to: lastTimestamp }
1460
+ };
1461
+ }
1462
+ async function reset() {
1463
+ await Promise.resolve(storage.clearAll());
1464
+ alertFired = false;
1465
+ }
1466
+ async function resetSession(sessionId) {
1467
+ await Promise.resolve(storage.clearSession(sessionId));
1468
+ }
1469
+ async function exportJSON() {
1470
+ return JSON.stringify(await getReport(), null, 2);
1471
+ }
1472
+ async function exportCSV() {
1473
+ const entries = await Promise.resolve(storage.getAll());
1474
+ const header = "timestamp,model,inputTokens,outputTokens,costUSD,sessionId,userId";
1475
+ const rows = entries.map(
1476
+ (e) => [
1477
+ csvEscape(e.timestamp),
1478
+ csvEscape(e.model),
1479
+ e.inputTokens,
1480
+ e.outputTokens,
1481
+ e.costUSD.toFixed(8),
1482
+ csvEscape(e.sessionId ?? ""),
1483
+ csvEscape(e.userId ?? "")
1484
+ ].join(",")
1485
+ );
1486
+ return [header, ...rows].join("\n");
1487
+ }
1488
+ function getModelInfo(model) {
1489
+ return findPrice(model, {
1490
+ bundledPrices,
1491
+ ...customPrices !== void 0 && { customPrices },
1492
+ ...remotePrices !== void 0 && { remotePrices }
1493
+ }) ?? null;
1494
+ }
1495
+ return { track, getReport, reset, resetSession, exportJSON, exportCSV, getModelInfo };
1496
+ }
1497
+ function computeTotal(entries) {
1498
+ return entries.reduce((sum, e) => sum + e.costUSD, 0);
1499
+ }
1500
+ function csvEscape(value) {
1501
+ if (value.includes(",") || value.includes('"') || value.includes("\n")) {
1502
+ return `"${value.replace(/"/g, '""')}"`;
1503
+ }
1504
+ return value;
1505
+ }
1506
+
1507
+ // bin/cli.ts
1508
+ var import_meta2 = {};
1509
+ var __dirname = (0, import_node_path3.dirname)((0, import_node_url.fileURLToPath)(import_meta2.url));
1510
+ var DB_PATH2 = (0, import_node_path3.join)((0, import_node_os3.homedir)(), ".tokenwatch", "usage.db");
42
1511
  function loadBundledPrices() {
43
- const pricesPath = (0, import_node_path2.join)(__dirname, "..", "prices.json");
44
- const raw = (0, import_node_fs2.readFileSync)(pricesPath, "utf8");
1512
+ const pricesPath = (0, import_node_path3.join)(__dirname, "..", "prices.json");
1513
+ const raw = (0, import_node_fs3.readFileSync)(pricesPath, "utf8");
45
1514
  const data = JSON.parse(raw);
46
1515
  return data.models;
47
1516
  }
@@ -71,6 +1540,50 @@ function cmdPrices() {
71
1540
  console.log(`${row.model.padEnd(maxName)} ${row.input.padStart(12)} ${row.output.padStart(12)}`);
72
1541
  }
73
1542
  }
1543
+ async function cmdReport() {
1544
+ if (!(0, import_node_fs3.existsSync)(DB_PATH2)) {
1545
+ console.log(`No SQLite database found at ${DB_PATH2}`);
1546
+ console.log("Start your app with storage: 'sqlite' to begin recording usage.");
1547
+ return;
1548
+ }
1549
+ let storage;
1550
+ try {
1551
+ storage = new SqliteStorage(DB_PATH2);
1552
+ } catch {
1553
+ console.error("Failed to open SQLite database. Is better-sqlite3 installed?");
1554
+ console.error("Run: npm install better-sqlite3");
1555
+ process.exit(1);
1556
+ }
1557
+ const tracker = createTracker({ storage, syncPrices: false });
1558
+ const report = await tracker.getReport();
1559
+ if (report.totalCostUSD === 0 && Object.keys(report.byModel).length === 0) {
1560
+ console.log("No usage recorded yet.");
1561
+ return;
1562
+ }
1563
+ console.log("\n\u2500\u2500 tokenwatch report \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
1564
+ console.log(` Total cost: $${report.totalCostUSD.toFixed(6)} USD`);
1565
+ console.log(` Total tokens: ${report.totalTokens.input.toLocaleString()} in / ${report.totalTokens.output.toLocaleString()} out`);
1566
+ console.log(` Period: ${report.period.from} \u2192 ${report.period.to}`);
1567
+ if (Object.keys(report.byModel).length > 0) {
1568
+ console.log("\n By model:");
1569
+ for (const [model, stats] of Object.entries(report.byModel)) {
1570
+ console.log(` ${model.padEnd(30)} $${stats.costUSD.toFixed(6)} (${stats.calls} calls)`);
1571
+ }
1572
+ }
1573
+ if (Object.keys(report.byUser).length > 0) {
1574
+ console.log("\n By user:");
1575
+ for (const [user, stats] of Object.entries(report.byUser)) {
1576
+ console.log(` ${user.padEnd(30)} $${stats.costUSD.toFixed(6)} (${stats.calls} calls)`);
1577
+ }
1578
+ }
1579
+ if (Object.keys(report.bySession).length > 0) {
1580
+ console.log("\n By session:");
1581
+ for (const [session, stats] of Object.entries(report.bySession)) {
1582
+ console.log(` ${session.padEnd(30)} $${stats.costUSD.toFixed(6)} (${stats.calls} calls)`);
1583
+ }
1584
+ }
1585
+ console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
1586
+ }
74
1587
  function cmdHelp() {
75
1588
  console.log(`
76
1589
  tokenwatch \u2014 CLI
@@ -93,7 +1606,7 @@ async function main() {
93
1606
  cmdPrices();
94
1607
  break;
95
1608
  case "report":
96
- console.log("report command requires SQLite storage to be configured in your app.");
1609
+ await cmdReport();
97
1610
  break;
98
1611
  case "help":
99
1612
  case void 0: