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