@vibescore/tracker 0.1.0 → 0.1.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibescore/tracker",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Codex CLI token usage tracker (macOS-first, notify-driven).",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -469,49 +469,90 @@ async function enqueueTouchedBuckets({ queuePath, hourlyState, touchedBuckets })
469
469
  }
470
470
  if (touchedGroups.size === 0) return 0;
471
471
 
472
- const grouped = new Map();
473
- for (const [key, bucket] of Object.entries(hourlyState.buckets || {})) {
474
- if (!bucket || !bucket.totals) continue;
475
- const parsed = parseBucketKey(key);
476
- const hourStart = parsed.hourStart;
477
- if (!hourStart) continue;
478
- const groupKey = groupBucketKey(parsed.source, hourStart);
479
- if (!touchedGroups.has(groupKey)) continue;
480
-
481
- let group = grouped.get(groupKey);
482
- if (!group) {
483
- group = {
484
- source: normalizeSourceInput(parsed.source) || DEFAULT_SOURCE,
485
- hourStart,
486
- models: new Set(),
487
- totals: initTotals()
488
- };
489
- grouped.set(groupKey, group);
472
+ const groupQueued = hourlyState.groupQueued && typeof hourlyState.groupQueued === 'object' ? hourlyState.groupQueued : {};
473
+ const legacyGroups = new Set();
474
+ for (const groupKey of touchedGroups) {
475
+ if (Object.prototype.hasOwnProperty.call(groupQueued, groupKey)) {
476
+ legacyGroups.add(groupKey);
490
477
  }
491
- group.models.add(parsed.model || DEFAULT_MODEL);
492
- addTotals(group.totals, bucket.totals);
493
478
  }
494
479
 
495
480
  const toAppend = [];
496
- const groupQueued = hourlyState.groupQueued && typeof hourlyState.groupQueued === 'object' ? hourlyState.groupQueued : {};
497
- for (const group of grouped.values()) {
498
- const model = group.models.size === 1 ? [...group.models][0] : DEFAULT_MODEL;
499
- const key = totalsKey(group.totals);
500
- const groupKey = groupBucketKey(group.source, group.hourStart);
501
- if (groupQueued[groupKey] === key) continue;
481
+ for (const bucketStart of touchedBuckets) {
482
+ const parsed = parseBucketKey(bucketStart);
483
+ const hourStart = parsed.hourStart;
484
+ if (!hourStart) continue;
485
+ const groupKey = groupBucketKey(parsed.source, hourStart);
486
+ if (legacyGroups.has(groupKey)) continue;
487
+
488
+ const normalizedKey = bucketKey(parsed.source, parsed.model, hourStart);
489
+ const bucket = hourlyState.buckets ? hourlyState.buckets[normalizedKey] : null;
490
+ if (!bucket || !bucket.totals) continue;
491
+ if (bucket.queuedKey != null && typeof bucket.queuedKey !== 'string') {
492
+ bucket.queuedKey = null;
493
+ }
494
+ const key = totalsKey(bucket.totals);
495
+ if (bucket.queuedKey === key) continue;
496
+ const source = normalizeSourceInput(parsed.source) || DEFAULT_SOURCE;
497
+ const model = normalizeModelInput(parsed.model) || DEFAULT_MODEL;
502
498
  toAppend.push(
503
499
  JSON.stringify({
504
- source: group.source,
500
+ source,
505
501
  model,
506
- hour_start: group.hourStart,
507
- input_tokens: group.totals.input_tokens,
508
- cached_input_tokens: group.totals.cached_input_tokens,
509
- output_tokens: group.totals.output_tokens,
510
- reasoning_output_tokens: group.totals.reasoning_output_tokens,
511
- total_tokens: group.totals.total_tokens
502
+ hour_start: hourStart,
503
+ input_tokens: bucket.totals.input_tokens,
504
+ cached_input_tokens: bucket.totals.cached_input_tokens,
505
+ output_tokens: bucket.totals.output_tokens,
506
+ reasoning_output_tokens: bucket.totals.reasoning_output_tokens,
507
+ total_tokens: bucket.totals.total_tokens
512
508
  })
513
509
  );
514
- groupQueued[groupKey] = key;
510
+ bucket.queuedKey = key;
511
+ }
512
+
513
+ if (legacyGroups.size > 0) {
514
+ const grouped = new Map();
515
+ for (const [key, bucket] of Object.entries(hourlyState.buckets || {})) {
516
+ if (!bucket || !bucket.totals) continue;
517
+ const parsed = parseBucketKey(key);
518
+ const hourStart = parsed.hourStart;
519
+ if (!hourStart) continue;
520
+ const groupKey = groupBucketKey(parsed.source, hourStart);
521
+ if (!legacyGroups.has(groupKey)) continue;
522
+
523
+ let group = grouped.get(groupKey);
524
+ if (!group) {
525
+ group = {
526
+ source: normalizeSourceInput(parsed.source) || DEFAULT_SOURCE,
527
+ hourStart,
528
+ models: new Set(),
529
+ totals: initTotals()
530
+ };
531
+ grouped.set(groupKey, group);
532
+ }
533
+ group.models.add(parsed.model || DEFAULT_MODEL);
534
+ addTotals(group.totals, bucket.totals);
535
+ }
536
+
537
+ for (const group of grouped.values()) {
538
+ const model = group.models.size === 1 ? [...group.models][0] : DEFAULT_MODEL;
539
+ const key = totalsKey(group.totals);
540
+ const groupKey = groupBucketKey(group.source, group.hourStart);
541
+ if (groupQueued[groupKey] === key) continue;
542
+ toAppend.push(
543
+ JSON.stringify({
544
+ source: group.source,
545
+ model,
546
+ hour_start: group.hourStart,
547
+ input_tokens: group.totals.input_tokens,
548
+ cached_input_tokens: group.totals.cached_input_tokens,
549
+ output_tokens: group.totals.output_tokens,
550
+ reasoning_output_tokens: group.totals.reasoning_output_tokens,
551
+ total_tokens: group.totals.total_tokens
552
+ })
553
+ );
554
+ groupQueued[groupKey] = key;
555
+ }
515
556
  }
516
557
 
517
558
  hourlyState.groupQueued = groupQueued;
@@ -78,7 +78,7 @@ async function readBatch(queuePath, startOffset, maxBuckets) {
78
78
  const model = normalizeModel(bucket?.model) || DEFAULT_MODEL;
79
79
  bucket.source = source;
80
80
  bucket.model = model;
81
- bucketMap.set(bucketKey(source, hourStart), bucket);
81
+ bucketMap.set(bucketKey(source, model, hourStart), bucket);
82
82
  linesRead += 1;
83
83
  if (linesRead >= maxBuckets) break;
84
84
  }
@@ -97,8 +97,8 @@ async function safeFileSize(p) {
97
97
  }
98
98
  }
99
99
 
100
- function bucketKey(source, hourStart) {
101
- return `${source}${BUCKET_SEPARATOR}${hourStart}`;
100
+ function bucketKey(source, model, hourStart) {
101
+ return `${source}${BUCKET_SEPARATOR}${model}${BUCKET_SEPARATOR}${hourStart}`;
102
102
  }
103
103
 
104
104
  function normalizeSource(value) {