claude-usage-limits 1.5.1 → 1.5.3
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.5.
|
|
4
|
+
"version": "1.5.3",
|
|
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
|
@@ -187,6 +187,11 @@ The split comes from measured spend rather than an assumption that everyone is
|
|
|
187
187
|
working equally hard, because they usually are not. A session that has gone
|
|
188
188
|
quiet for a quarter of an hour is not counted as competing.
|
|
189
189
|
|
|
190
|
+
Every session's spend also feeds the reading itself, not just the split. The
|
|
191
|
+
percentage is corrected using all spend recorded since the snapshot was taken,
|
|
192
|
+
whichever window produced it, so another Claude burning budget in the next
|
|
193
|
+
terminal moves your number too.
|
|
194
|
+
|
|
190
195
|
## Which limit it watches
|
|
191
196
|
|
|
192
197
|
Two windows run at once and the 5-hour one is usually what actually stops you,
|
|
@@ -497,7 +502,7 @@ test/ node --test, no dependencies
|
|
|
497
502
|
node --test
|
|
498
503
|
```
|
|
499
504
|
|
|
500
|
-
|
|
505
|
+
179 tests over the pricing, the window arithmetic, plan and credit detection,
|
|
501
506
|
the status line, the before-prompt line, job forecasting, per-project
|
|
502
507
|
attribution, the CLI, packaging, and the settings save/restore.
|
|
503
508
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-usage-limits",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.3",
|
|
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",
|
|
@@ -195,7 +195,7 @@ function sessionSpend(events, sessionId) {
|
|
|
195
195
|
function describeWindow(window) {
|
|
196
196
|
if (!window) return null;
|
|
197
197
|
if (window.stale) return window.label + ' rolling over';
|
|
198
|
-
const about = window.estimated ? ' about ' : ' ';
|
|
198
|
+
const about = window.estimated || window.adjusted ? ' about ' : ' ';
|
|
199
199
|
return window.label + about + window.percentUsed + '%';
|
|
200
200
|
}
|
|
201
201
|
|
|
@@ -237,6 +237,11 @@ function briefText(parts) {
|
|
|
237
237
|
'That figure was rebuilt from local history because the ' +
|
|
238
238
|
'snapshot is ' + parts.snapshotAge + ' old; run /usage to refresh it.'
|
|
239
239
|
);
|
|
240
|
+
} else if (parts.pointsSinceSnapshot) {
|
|
241
|
+
sentences.push(
|
|
242
|
+
'That includes about ' + parts.pointsSinceSnapshot + ' points spent since the ' +
|
|
243
|
+
'snapshot was taken ' + parts.snapshotAge + ' ago, which it does not know about yet.'
|
|
244
|
+
);
|
|
240
245
|
} else if (parts.staleWindows) {
|
|
241
246
|
// Do not quietly carry on with a window we know is wrong and could not
|
|
242
247
|
// rebuild. Say it is unknown and point at the one command that fixes it.
|
|
@@ -306,6 +311,8 @@ async function run(now, hookInput) {
|
|
|
306
311
|
percentUsed: binding.percentUsed,
|
|
307
312
|
stale: binding.stale,
|
|
308
313
|
estimated: binding.estimated,
|
|
314
|
+
adjusted: binding.adjusted,
|
|
315
|
+
pointsSinceSnapshot: binding.pointsSinceSnapshot,
|
|
309
316
|
resetsAt: binding.resetsAt,
|
|
310
317
|
verdict: binding.verdict,
|
|
311
318
|
windowStart: binding.windowStart,
|
|
@@ -334,6 +341,7 @@ async function run(now, hookInput) {
|
|
|
334
341
|
session: view.session,
|
|
335
342
|
rebuilt: Boolean(binding && binding.estimated),
|
|
336
343
|
staleWindows: view.staleWindows || 0,
|
|
344
|
+
pointsSinceSnapshot: (binding && binding.pointsSinceSnapshot) || 0,
|
|
337
345
|
snapshotAge: usage.formatDuration(base.snapshotAgeMs),
|
|
338
346
|
pressure: pressure(binding, now, config, view.turnsLeft),
|
|
339
347
|
});
|
|
@@ -535,7 +535,7 @@ function shareOf(sessions, sessionId) {
|
|
|
535
535
|
// Everything the report needs about one limit window.
|
|
536
536
|
function buildWindow(spec, snapshot, events, now, options) {
|
|
537
537
|
const extra = options || {};
|
|
538
|
-
const
|
|
538
|
+
const rawPercent =
|
|
539
539
|
snapshot && typeof snapshot.utilization === 'number' ? snapshot.utilization : null;
|
|
540
540
|
const resetsAt = snapshot && snapshot.resets_at ? Date.parse(snapshot.resets_at) : null;
|
|
541
541
|
const hasReset = Number.isFinite(resetsAt);
|
|
@@ -556,8 +556,8 @@ function buildWindow(spec, snapshot, events, now, options) {
|
|
|
556
556
|
const window = {
|
|
557
557
|
key: spec.key,
|
|
558
558
|
label: spec.label,
|
|
559
|
-
percentUsed:
|
|
560
|
-
percentLeft:
|
|
559
|
+
percentUsed: rawPercent,
|
|
560
|
+
percentLeft: rawPercent === null ? null : Math.max(0, 100 - rawPercent),
|
|
561
561
|
resetsAt: hasReset ? resetsAt : null,
|
|
562
562
|
msToReset: hasReset ? resetsAt - now : null,
|
|
563
563
|
windowStart: start,
|
|
@@ -579,13 +579,50 @@ function buildWindow(spec, snapshot, events, now, options) {
|
|
|
579
579
|
// True when the percentage was rebuilt from local history because the
|
|
580
580
|
// snapshot had gone stale, rather than read from the snapshot itself.
|
|
581
581
|
estimated: Boolean(extra.estimated),
|
|
582
|
+
// True when spend since the snapshot was added to its reading.
|
|
583
|
+
adjusted: false,
|
|
584
|
+
pointsSinceSnapshot: 0,
|
|
582
585
|
verdict: 'unknown',
|
|
583
586
|
};
|
|
584
587
|
|
|
588
|
+
// Worked out before anything reads it: the adjustment below and the verdict
|
|
589
|
+
// chain both branch on whether this window has already rolled over.
|
|
590
|
+
window.stale = hasReset && resetsAt <= now;
|
|
591
|
+
|
|
585
592
|
// Calibrate against this account: how many dollars of measured traffic
|
|
586
593
|
// moved the meter one point. A rebuilt window hands its own figure in,
|
|
587
594
|
// because rounding to 0% would otherwise leave it unpriced and drop it out
|
|
588
595
|
// of the binding choice just after a reset.
|
|
596
|
+
// The snapshot is a reading from a moment in the past, not from now. Spend
|
|
597
|
+
// since then is real and uncounted, and with several sessions running it adds
|
|
598
|
+
// up fast: forty points went missing in nine minutes once, so a window that
|
|
599
|
+
// was truly at 88% was reported at 49%. Calibrate on spend up to the reading
|
|
600
|
+
// only, or the very spend being accounted for inflates the price per point
|
|
601
|
+
// and shrinks its own correction.
|
|
602
|
+
let percent = rawPercent;
|
|
603
|
+
let sinceSnapshot = 0;
|
|
604
|
+
if (
|
|
605
|
+
rawPercent !== null &&
|
|
606
|
+
rawPercent > 0 &&
|
|
607
|
+
!window.stale &&
|
|
608
|
+
Number.isFinite(extra.fetchedAt) &&
|
|
609
|
+
extra.fetchedAt > start
|
|
610
|
+
) {
|
|
611
|
+
const upTo = totals(inWindow.filter((e) => e.at <= extra.fetchedAt));
|
|
612
|
+
const after = totals(inWindow.filter((e) => e.at > extra.fetchedAt));
|
|
613
|
+
if (upTo.cost > 0 && after.cost > 0) {
|
|
614
|
+
const pricePerPoint = upTo.cost / rawPercent;
|
|
615
|
+
sinceSnapshot = after.cost / pricePerPoint;
|
|
616
|
+
if (sinceSnapshot >= 1) {
|
|
617
|
+
percent = Math.min(100, Math.round(rawPercent + sinceSnapshot));
|
|
618
|
+
window.adjusted = true;
|
|
619
|
+
window.pointsSinceSnapshot = Math.round(sinceSnapshot);
|
|
620
|
+
window.percentUsed = percent;
|
|
621
|
+
window.percentLeft = Math.max(0, 100 - percent);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
589
626
|
const derived = percent !== null && percent > 0 && spent.cost > 0 ? spent.cost / percent : null;
|
|
590
627
|
const priced =
|
|
591
628
|
derived !== null
|
|
@@ -615,7 +652,6 @@ function buildWindow(spec, snapshot, events, now, options) {
|
|
|
615
652
|
// A reset time in the past means the window already turned over and the
|
|
616
653
|
// cached percentage describes a window that no longer exists. Reporting it
|
|
617
654
|
// as current would claim the budget is gone when it has just come back.
|
|
618
|
-
window.stale = hasReset && resetsAt <= now;
|
|
619
655
|
if (window.stale) {
|
|
620
656
|
window.remainingUSD = null;
|
|
621
657
|
window.turnsLeft = null;
|
|
@@ -754,6 +790,7 @@ function collect(now) {
|
|
|
754
790
|
planTier: plan.tier,
|
|
755
791
|
planAdvice: plan.advice,
|
|
756
792
|
snapshotAgeMs: cache && cache.fetchedAtMs ? now - cache.fetchedAtMs : null,
|
|
793
|
+
snapshotFetchedAt: cache && cache.fetchedAtMs ? cache.fetchedAtMs : null,
|
|
757
794
|
utilization,
|
|
758
795
|
settings: {
|
|
759
796
|
model: settings.model || 'default',
|
|
@@ -787,7 +824,12 @@ function reconstructWindow(spec, snapshot, events, now) {
|
|
|
787
824
|
if (past.cost <= 0) return null;
|
|
788
825
|
|
|
789
826
|
const usdPerPercent = past.cost / snapshot.utilization;
|
|
790
|
-
|
|
827
|
+
|
|
828
|
+
// The window running now began when the old one reset, not five hours ago.
|
|
829
|
+
// Summing a rolling span sweeps in the window that already expired: three
|
|
830
|
+
// minutes after a reset that meant counting 103 turns instead of 6, and
|
|
831
|
+
// reporting a fresh window as completely full.
|
|
832
|
+
const liveStart = Math.max(now - spec.span, resetsAt);
|
|
791
833
|
const live = totals(events.filter((e) => e.at >= liveStart && e.at <= now));
|
|
792
834
|
const raw = live.cost / usdPerPercent;
|
|
793
835
|
|
|
@@ -810,14 +852,14 @@ function reconstructWindow(spec, snapshot, events, now) {
|
|
|
810
852
|
|
|
811
853
|
// No snapshot at all means no windows, which is what tells the report to
|
|
812
854
|
// explain itself rather than print a table of dashes.
|
|
813
|
-
function buildWindows(utilization, events, now) {
|
|
855
|
+
function buildWindows(utilization, events, now, fetchedAt) {
|
|
814
856
|
if (!utilization) return [];
|
|
815
857
|
return WINDOWS.map((spec) => {
|
|
816
858
|
const snapshot = utilization[spec.key];
|
|
817
859
|
// The per-model weekly windows only exist on some plans.
|
|
818
860
|
if (spec.key !== 'five_hour' && spec.key !== 'seven_day' && !snapshot) return null;
|
|
819
861
|
|
|
820
|
-
const window = buildWindow(spec, snapshot, events, now);
|
|
862
|
+
const window = buildWindow(spec, snapshot, events, now, { fetchedAt });
|
|
821
863
|
if (!window.stale) return window;
|
|
822
864
|
|
|
823
865
|
// Rolled over. Rebuild from local history rather than going blind on it.
|
|
@@ -844,7 +886,7 @@ async function report(now) {
|
|
|
844
886
|
const earliest = now - 8 * DAY;
|
|
845
887
|
const events = await readEvents(earliest);
|
|
846
888
|
|
|
847
|
-
const windows = buildWindows(base.utilization, events, now);
|
|
889
|
+
const windows = buildWindows(base.utilization, events, now, base.snapshotFetchedAt);
|
|
848
890
|
|
|
849
891
|
const recentEvents = events.filter((event) => event.at >= now - HOUR);
|
|
850
892
|
const recent = totals(recentEvents);
|
|
@@ -1005,7 +1047,7 @@ function render(data) {
|
|
|
1005
1047
|
? 'stale'
|
|
1006
1048
|
: window.percentUsed === null
|
|
1007
1049
|
? '-'
|
|
1008
|
-
: (window.estimated ? '~' : '') + window.percentUsed + '%',
|
|
1050
|
+
: (window.estimated || window.adjusted ? '~' : '') + window.percentUsed + '%',
|
|
1009
1051
|
6
|
|
1010
1052
|
) +
|
|
1011
1053
|
padLeft(formatDuration(window.msToReset), 12) +
|
|
@@ -1083,6 +1125,15 @@ function render(data) {
|
|
|
1083
1125
|
}
|
|
1084
1126
|
lines.push(' Measured ' + formatCount(data.measuredTurns) + ' turns of local transcript');
|
|
1085
1127
|
|
|
1128
|
+
if (data.windows.some((window) => window.adjusted)) {
|
|
1129
|
+
lines.push(
|
|
1130
|
+
' Note ~ includes spend since the snapshot was taken, which its own'
|
|
1131
|
+
);
|
|
1132
|
+
lines.push(
|
|
1133
|
+
' reading does not cover yet. Run /usage for a fresh one.'
|
|
1134
|
+
);
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1086
1137
|
if (data.windows.some((window) => window.estimated)) {
|
|
1087
1138
|
lines.push(
|
|
1088
1139
|
' Note ~ means the snapshot had gone stale and that window was rebuilt'
|