claude-usage-limits 1.5.0 → 1.5.2
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.2",
|
|
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
|
@@ -466,6 +466,12 @@ Good enough to plan with, not a bill. The honest caveats:
|
|
|
466
466
|
- A rebuilt figure only counts what this machine did. If you also worked on
|
|
467
467
|
another device it reads low, which is the dangerous direction, so treat it as
|
|
468
468
|
a floor until you refresh.
|
|
469
|
+
- If a rebuild comes out above a full window, it is refused rather than capped.
|
|
470
|
+
Local transcripts only see this machine, so a window mostly spent elsewhere
|
|
471
|
+
makes a point look far too cheap and any live spend divides to hundreds of
|
|
472
|
+
percent. Capping that at 100 would tell someone sitting at half their budget
|
|
473
|
+
that it was gone. The window is reported as unknown instead, with a nudge to
|
|
474
|
+
run `/usage`.
|
|
469
475
|
|
|
470
476
|
[how-it-works.md](skills/usage-limits/references/how-it-works.md) has the field
|
|
471
477
|
names, the formulas, and the rest of it.
|
|
@@ -491,7 +497,7 @@ test/ node --test, no dependencies
|
|
|
491
497
|
node --test
|
|
492
498
|
```
|
|
493
499
|
|
|
494
|
-
|
|
500
|
+
164 tests over the pricing, the window arithmetic, plan and credit detection,
|
|
495
501
|
the status line, the before-prompt line, job forecasting, per-project
|
|
496
502
|
attribution, the CLI, packaging, and the settings save/restore.
|
|
497
503
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-usage-limits",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.2",
|
|
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,19 @@ 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
|
+
);
|
|
245
|
+
} else if (parts.staleWindows) {
|
|
246
|
+
// Do not quietly carry on with a window we know is wrong and could not
|
|
247
|
+
// rebuild. Say it is unknown and point at the one command that fixes it.
|
|
248
|
+
sentences.push(
|
|
249
|
+
'A window is past its reset and could not be rebuilt from local history, ' +
|
|
250
|
+
'so its reading is unknown rather than current; the snapshot is ' +
|
|
251
|
+
parts.snapshotAge + ' old, so run /usage before trusting the rest.'
|
|
252
|
+
);
|
|
240
253
|
}
|
|
241
254
|
if (parts.othersSummary) sentences.push('Other windows: ' + parts.othersSummary + '.');
|
|
242
255
|
if (parts.session) {
|
|
@@ -290,6 +303,7 @@ async function run(now, hookInput) {
|
|
|
290
303
|
session: sessionSpend(events, sessionId),
|
|
291
304
|
othersSummary: summariseOthers(windows, binding && binding.key),
|
|
292
305
|
sessions: usage.activeSessions(events, now, usage.CONCURRENT_WINDOW_MS),
|
|
306
|
+
staleWindows: windows.filter((w) => w.stale).length,
|
|
293
307
|
binding: binding
|
|
294
308
|
? {
|
|
295
309
|
key: binding.key,
|
|
@@ -297,6 +311,8 @@ async function run(now, hookInput) {
|
|
|
297
311
|
percentUsed: binding.percentUsed,
|
|
298
312
|
stale: binding.stale,
|
|
299
313
|
estimated: binding.estimated,
|
|
314
|
+
adjusted: binding.adjusted,
|
|
315
|
+
pointsSinceSnapshot: binding.pointsSinceSnapshot,
|
|
300
316
|
resetsAt: binding.resetsAt,
|
|
301
317
|
verdict: binding.verdict,
|
|
302
318
|
windowStart: binding.windowStart,
|
|
@@ -324,6 +340,8 @@ async function run(now, hookInput) {
|
|
|
324
340
|
: null,
|
|
325
341
|
session: view.session,
|
|
326
342
|
rebuilt: Boolean(binding && binding.estimated),
|
|
343
|
+
staleWindows: view.staleWindows || 0,
|
|
344
|
+
pointsSinceSnapshot: (binding && binding.pointsSinceSnapshot) || 0,
|
|
327
345
|
snapshotAge: usage.formatDuration(base.snapshotAgeMs),
|
|
328
346
|
pressure: pressure(binding, now, config, view.turnsLeft),
|
|
329
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',
|
|
@@ -772,6 +809,9 @@ function collect(now) {
|
|
|
772
809
|
// was spent inside the window it describes equalled its percentage. That
|
|
773
810
|
// dollars-per-point figure is a property of the plan, not of the moment, so it
|
|
774
811
|
// still prices the window running now.
|
|
812
|
+
// Anything above this and the calibration, not the budget, is what is full.
|
|
813
|
+
const SATURATION_LIMIT = 105;
|
|
814
|
+
|
|
775
815
|
function reconstructWindow(spec, snapshot, events, now) {
|
|
776
816
|
if (!snapshot || typeof snapshot.utilization !== 'number') return null;
|
|
777
817
|
if (snapshot.utilization <= 0) return null;
|
|
@@ -784,11 +824,25 @@ function reconstructWindow(spec, snapshot, events, now) {
|
|
|
784
824
|
if (past.cost <= 0) return null;
|
|
785
825
|
|
|
786
826
|
const usdPerPercent = past.cost / snapshot.utilization;
|
|
787
|
-
|
|
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);
|
|
788
833
|
const live = totals(events.filter((e) => e.at >= liveStart && e.at <= now));
|
|
834
|
+
const raw = live.cost / usdPerPercent;
|
|
835
|
+
|
|
836
|
+
// A rebuild that overflows the window is not a full window, it is a broken
|
|
837
|
+
// calibration. Local transcripts only see this machine, so if the closed
|
|
838
|
+
// window was mostly spent elsewhere its price per point comes out far too
|
|
839
|
+
// small and any live spend divides to hundreds of percent. Capping that at
|
|
840
|
+
// 100 would report a full budget to someone sitting at half, which is worse
|
|
841
|
+
// than admitting the reading cannot be rebuilt.
|
|
842
|
+
if (raw > SATURATION_LIMIT) return null;
|
|
789
843
|
|
|
790
844
|
return {
|
|
791
|
-
percentUsed: Math.min(100, Math.round(
|
|
845
|
+
percentUsed: Math.min(100, Math.round(raw)),
|
|
792
846
|
usdPerPercent,
|
|
793
847
|
spentUSD: live.cost,
|
|
794
848
|
turns: live.turns,
|
|
@@ -798,14 +852,14 @@ function reconstructWindow(spec, snapshot, events, now) {
|
|
|
798
852
|
|
|
799
853
|
// No snapshot at all means no windows, which is what tells the report to
|
|
800
854
|
// explain itself rather than print a table of dashes.
|
|
801
|
-
function buildWindows(utilization, events, now) {
|
|
855
|
+
function buildWindows(utilization, events, now, fetchedAt) {
|
|
802
856
|
if (!utilization) return [];
|
|
803
857
|
return WINDOWS.map((spec) => {
|
|
804
858
|
const snapshot = utilization[spec.key];
|
|
805
859
|
// The per-model weekly windows only exist on some plans.
|
|
806
860
|
if (spec.key !== 'five_hour' && spec.key !== 'seven_day' && !snapshot) return null;
|
|
807
861
|
|
|
808
|
-
const window = buildWindow(spec, snapshot, events, now);
|
|
862
|
+
const window = buildWindow(spec, snapshot, events, now, { fetchedAt });
|
|
809
863
|
if (!window.stale) return window;
|
|
810
864
|
|
|
811
865
|
// Rolled over. Rebuild from local history rather than going blind on it.
|
|
@@ -832,7 +886,7 @@ async function report(now) {
|
|
|
832
886
|
const earliest = now - 8 * DAY;
|
|
833
887
|
const events = await readEvents(earliest);
|
|
834
888
|
|
|
835
|
-
const windows = buildWindows(base.utilization, events, now);
|
|
889
|
+
const windows = buildWindows(base.utilization, events, now, base.snapshotFetchedAt);
|
|
836
890
|
|
|
837
891
|
const recentEvents = events.filter((event) => event.at >= now - HOUR);
|
|
838
892
|
const recent = totals(recentEvents);
|
|
@@ -993,7 +1047,7 @@ function render(data) {
|
|
|
993
1047
|
? 'stale'
|
|
994
1048
|
: window.percentUsed === null
|
|
995
1049
|
? '-'
|
|
996
|
-
: (window.estimated ? '~' : '') + window.percentUsed + '%',
|
|
1050
|
+
: (window.estimated || window.adjusted ? '~' : '') + window.percentUsed + '%',
|
|
997
1051
|
6
|
|
998
1052
|
) +
|
|
999
1053
|
padLeft(formatDuration(window.msToReset), 12) +
|
|
@@ -1071,6 +1125,15 @@ function render(data) {
|
|
|
1071
1125
|
}
|
|
1072
1126
|
lines.push(' Measured ' + formatCount(data.measuredTurns) + ' turns of local transcript');
|
|
1073
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
|
+
|
|
1074
1137
|
if (data.windows.some((window) => window.estimated)) {
|
|
1075
1138
|
lines.push(
|
|
1076
1139
|
' Note ~ means the snapshot had gone stale and that window was rebuilt'
|
|
@@ -1236,6 +1299,7 @@ module.exports = {
|
|
|
1236
1299
|
readEvents,
|
|
1237
1300
|
buildWindow,
|
|
1238
1301
|
reconstructWindow,
|
|
1302
|
+
SATURATION_LIMIT,
|
|
1239
1303
|
buildWindows,
|
|
1240
1304
|
bindingWindow,
|
|
1241
1305
|
dominantEffort,
|