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.
|
|
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
|
-
|
|
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.
|
|
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
|
|
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, {
|
|
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
|
|
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,
|