claude-usage-limits 1.6.0 → 1.6.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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "usage-limits",
3
3
  "displayName": "Usage Limits",
4
- "version": "1.6.0",
4
+ "version": "1.6.1",
5
5
  "description": "Puts your remaining Claude Code usage limit into Claude's context before every prompt, so it opens with what fits in the budget instead of starting work that gets cut off. Reports headroom as turns rather than percentages, prices a job before you start it, and detects your plan tier.",
6
6
  "author": {
7
7
  "name": "Ridelink",
package/README.md CHANGED
@@ -473,6 +473,11 @@ Good enough to plan with, not a bill. The honest caveats:
473
473
  to the meter rather than from an assumption about the clock.
474
474
  - The turn cost behind "turns of headroom" is a median over at least five
475
475
  turns, so one compaction cannot define your pace.
476
+ - What a point of a window costs is learned once from the best sample seen and
477
+ remembered, rather than re-derived each time from whatever slice is to hand.
478
+ A thin baseline prices a point badly and every correction built on it
479
+ inherits the error, which is how a window truly at 70 percent once came out
480
+ at 82.
476
481
  - The cache only refreshes when Claude Code talks to the API, so after a gap it
477
482
  can be hours old and its 5-hour window long since rolled over. Dropping that
478
483
  window would hide the limit that actually stops short work, so it gets rebuilt
@@ -515,7 +520,7 @@ test/ node --test, no dependencies
515
520
  node --test
516
521
  ```
517
522
 
518
- 186 tests over the pricing, the window arithmetic, plan and credit detection,
523
+ 190 tests over the pricing, the window arithmetic, plan and credit detection,
519
524
  the status line, the before-prompt line, job forecasting, per-project
520
525
  attribution, the CLI, packaging, and the settings save/restore.
521
526
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-usage-limits",
3
- "version": "1.6.0",
3
+ "version": "1.6.1",
4
4
  "description": "Puts your remaining Claude Code usage limit into Claude's context before every prompt, so it opens with what fits in the budget instead of starting work that gets cut off. Reports headroom as turns rather than percentages, prices a job before you start it, and detects your plan tier.",
5
5
  "keywords": [
6
6
  "claude",
@@ -532,6 +532,43 @@ function shareOf(sessions, sessionId) {
532
532
  return mine.share > 0 ? mine.share : 1 / sessions.length;
533
533
  }
534
534
 
535
+ // What a point of a window costs is a property of the plan, not of the moment,
536
+ // so it should be learned once from a good sample rather than re-derived from
537
+ // whatever slice happens to be to hand. A thin baseline prices a point badly
538
+ // and every correction built on it inherits the error: a 24 minute old
539
+ // snapshot once turned a window truly at 70 per cent into a confident 82.
540
+ function calibrationFile() {
541
+ return path.join(configDir(), 'usage-limits-calibration.json');
542
+ }
543
+
544
+ function readCalibration() {
545
+ try {
546
+ const parsed = JSON.parse(fs.readFileSync(calibrationFile(), 'utf8'));
547
+ return parsed && typeof parsed === 'object' ? parsed : {};
548
+ } catch (err) {
549
+ return {};
550
+ }
551
+ }
552
+
553
+ function writeCalibration(all) {
554
+ try {
555
+ fs.mkdirSync(path.dirname(calibrationFile()), { recursive: true });
556
+ fs.writeFileSync(calibrationFile(), JSON.stringify(all), 'utf8');
557
+ } catch (err) {
558
+ // Losing it costs accuracy on the next thin baseline, nothing more.
559
+ }
560
+ }
561
+
562
+ // A sample is better when it rests on more turns. Percentages read in whole
563
+ // numbers, so a bigger percentage also divides more precisely.
564
+ function betterCalibration(current, candidate) {
565
+ if (!candidate || !Number.isFinite(candidate.usdPerPercent) || candidate.usdPerPercent <= 0) {
566
+ return current || null;
567
+ }
568
+ if (!current || !Number.isFinite(current.turns)) return candidate;
569
+ return candidate.turns > current.turns ? candidate : current;
570
+ }
571
+
535
572
  // Everything the report needs about one limit window.
536
573
  function buildWindow(spec, snapshot, events, now, options) {
537
574
  const extra = options || {};
@@ -584,6 +621,8 @@ function buildWindow(spec, snapshot, events, now, options) {
584
621
  pointsSinceSnapshot: 0,
585
622
  // Set when spend since the snapshot could not be priced sensibly.
586
623
  correctionUnreliable: false,
624
+ // The price-per-point this window derived from its own baseline.
625
+ calibration: null,
587
626
  verdict: 'unknown',
588
627
  };
589
628
 
@@ -618,7 +657,13 @@ function buildWindow(spec, snapshot, events, now, options) {
618
657
  // divided by that, which is how a window truly at 55% got corrected all the
619
658
  // way to a confident 100.
620
659
  if (upTo.cost > 0 && after.cost > 0 && upTo.turns >= MIN_BASELINE_TURNS) {
621
- const pricePerPoint = upTo.cost / rawPercent;
660
+ const mine = { usdPerPercent: upTo.cost / rawPercent, turns: upTo.turns, percent: rawPercent };
661
+ const known = extra.knownCalibration;
662
+ // Trust the better-sampled of the two, whichever that is.
663
+ const chosen =
664
+ known && Number.isFinite(known.turns) && known.turns > upTo.turns ? known : mine;
665
+ window.calibration = mine;
666
+ const pricePerPoint = chosen.usdPerPercent;
622
667
  sinceSnapshot = after.cost / pricePerPoint;
623
668
 
624
669
  // Same rule as a rebuild: past this it is the calibration that is full,
@@ -887,14 +932,17 @@ function reconstructWindow(spec, snapshot, events, now) {
887
932
 
888
933
  // No snapshot at all means no windows, which is what tells the report to
889
934
  // explain itself rather than print a table of dashes.
890
- function buildWindows(utilization, events, now, fetchedAt) {
935
+ function buildWindows(utilization, events, now, fetchedAt, learned) {
891
936
  if (!utilization) return [];
892
937
  return WINDOWS.map((spec) => {
893
938
  const snapshot = utilization[spec.key];
894
939
  // The per-model weekly windows only exist on some plans.
895
940
  if (spec.key !== 'five_hour' && spec.key !== 'seven_day' && !snapshot) return null;
896
941
 
897
- const window = buildWindow(spec, snapshot, events, now, { fetchedAt });
942
+ const window = buildWindow(spec, snapshot, events, now, {
943
+ fetchedAt,
944
+ knownCalibration: learned ? learned[spec.key] : null,
945
+ });
898
946
  if (!window.stale) return window;
899
947
 
900
948
  // Rolled over. Rebuild from local history rather than going blind on it.
@@ -934,7 +982,27 @@ async function report(now, options) {
934
982
  const earliest = now - 8 * DAY;
935
983
  const events = await readEvents(earliest);
936
984
 
937
- const windows = buildWindows(base.utilization, events, now, base.snapshotFetchedAt);
985
+ const learned = readCalibration();
986
+ const windows = buildWindows(
987
+ base.utilization,
988
+ events,
989
+ now,
990
+ base.snapshotFetchedAt,
991
+ learned
992
+ );
993
+
994
+ // Keep the best sample seen so far, so a thin baseline never has to guess.
995
+ const updated = Object.assign({}, learned);
996
+ let changed = false;
997
+ for (const window of windows) {
998
+ if (!window.calibration) continue;
999
+ const best = betterCalibration(learned[window.key], window.calibration);
1000
+ if (best && best !== learned[window.key]) {
1001
+ updated[window.key] = best;
1002
+ changed = true;
1003
+ }
1004
+ }
1005
+ if (changed) writeCalibration(updated);
938
1006
 
939
1007
  const recentEvents = events.filter((event) => event.at >= now - HOUR);
940
1008
  const recent = totals(recentEvents);
@@ -1359,6 +1427,8 @@ module.exports = {
1359
1427
  buildWindows,
1360
1428
  bindingWindow,
1361
1429
  criticalOthers,
1430
+ betterCalibration,
1431
+ calibrationFile,
1362
1432
  CRITICAL_PERCENT,
1363
1433
  dominantEffort,
1364
1434
  typicalTurnCost,