claude-usage-limits 1.2.0 → 1.3.0
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.
|
|
4
|
+
"version": "1.3.0",
|
|
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
|
@@ -167,6 +167,21 @@ node skills/usage-limits/scripts/usage.js
|
|
|
167
167
|
node skills/usage-limits/scripts/usage.js --json
|
|
168
168
|
```
|
|
169
169
|
|
|
170
|
+
## Which limit it watches
|
|
171
|
+
|
|
172
|
+
Two windows run at once and the 5-hour one is usually what actually stops you,
|
|
173
|
+
so it gets picked whenever it is tighter, and it wins a tie against the weekly
|
|
174
|
+
window because the shorter window is the one hit first in practice.
|
|
175
|
+
|
|
176
|
+
It is not forced, though. When the weekly window is genuinely the wall, at 99
|
|
177
|
+
percent with minutes left, that is what gets reported. Forcing the 5-hour there
|
|
178
|
+
would hide the limit about to stop the work, which is the same failure as
|
|
179
|
+
ignoring it.
|
|
180
|
+
|
|
181
|
+
A window with no recent spend to measure is ranked by how full it is rather
|
|
182
|
+
than being skipped, so a 5-hour window sitting at 95 percent is never passed
|
|
183
|
+
over just because nothing has gone through it in the last few minutes.
|
|
184
|
+
|
|
170
185
|
## When you keep typing
|
|
171
186
|
|
|
172
187
|
Every message sent while work is already running starts another turn, and each
|
|
@@ -296,6 +311,7 @@ minute, so it costs about 400ms cold and 120ms warm.
|
|
|
296
311
|
| `USAGE_LIMITS_FLOOR` | 40 | Below this, pace is ignored. |
|
|
297
312
|
| `USAGE_LIMITS_AHEAD` | 15 | Points ahead of pace that count as burning fast. |
|
|
298
313
|
| `USAGE_LIMITS_CACHE` | 60 | Seconds the measured half stays good for. |
|
|
314
|
+
| `USAGE_LIMITS_FEW_TURNS` | 20 | Turn count at or below which the budget counts as tight. |
|
|
299
315
|
|
|
300
316
|
## What would this job cost
|
|
301
317
|
|
|
@@ -448,7 +464,7 @@ test/ node --test, no dependencies
|
|
|
448
464
|
node --test
|
|
449
465
|
```
|
|
450
466
|
|
|
451
|
-
|
|
467
|
+
142 tests over the pricing, the window arithmetic, plan and credit detection,
|
|
452
468
|
the status line, the before-prompt line, job forecasting, per-project
|
|
453
469
|
attribution, the CLI, packaging, and the settings save/restore.
|
|
454
470
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-usage-limits",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
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",
|
|
@@ -28,6 +28,8 @@ const DEFAULTS = {
|
|
|
28
28
|
// How long the measured part stays good for. Prompts often arrive in
|
|
29
29
|
// bursts, and a transcript scan per prompt would be wasteful.
|
|
30
30
|
cacheSeconds: 60,
|
|
31
|
+
// Few enough turns that the count itself is the warning.
|
|
32
|
+
fewTurns: 20,
|
|
31
33
|
};
|
|
32
34
|
|
|
33
35
|
function configDir() {
|
|
@@ -104,6 +106,7 @@ function settings() {
|
|
|
104
106
|
floor: number(env.USAGE_LIMITS_FLOOR, DEFAULTS.floor),
|
|
105
107
|
ahead: number(env.USAGE_LIMITS_AHEAD, DEFAULTS.ahead),
|
|
106
108
|
cacheSeconds: number(env.USAGE_LIMITS_CACHE, DEFAULTS.cacheSeconds),
|
|
109
|
+
fewTurns: number(env.USAGE_LIMITS_FEW_TURNS, DEFAULTS.fewTurns),
|
|
107
110
|
};
|
|
108
111
|
}
|
|
109
112
|
|
|
@@ -117,16 +120,32 @@ function aheadOfPace(window, now) {
|
|
|
117
120
|
}
|
|
118
121
|
|
|
119
122
|
// Not whether to speak, which is always, but how hard to lean on it.
|
|
120
|
-
|
|
123
|
+
// Not whether to speak, which is always, but how hard to lean on it.
|
|
124
|
+
function pressure(window, now, config, turnsLeft) {
|
|
121
125
|
if (!window || window.percentUsed === null || window.stale) return 'unknown';
|
|
122
|
-
if (window.verdict === 'exhausted') return 'gone';
|
|
126
|
+
if (window.verdict === 'exhausted' || window.percentUsed >= 100) return 'gone';
|
|
123
127
|
if (window.verdict === 'runs-out') return 'tight';
|
|
124
|
-
if (window.percentUsed >= config.near) return 'tight';
|
|
125
128
|
|
|
126
|
-
|
|
127
|
-
|
|
129
|
+
// A rebuilt figure only counts this machine, so it reads low. React to it
|
|
130
|
+
// sooner than to a figure the API actually reported.
|
|
131
|
+
const near = window.estimated ? Math.min(config.near, 70) : config.near;
|
|
132
|
+
if (window.percentUsed >= near) return 'tight';
|
|
133
|
+
|
|
134
|
+
// Turns are the number the work is planned in, so a short count is tight
|
|
135
|
+
// whatever the percentage says.
|
|
136
|
+
if (Number.isFinite(turnsLeft) && turnsLeft <= config.fewTurns) {
|
|
128
137
|
return 'tight';
|
|
129
138
|
}
|
|
139
|
+
|
|
140
|
+
// Pace only means something for a window with a real start. A rebuilt one
|
|
141
|
+
// is anchored at now minus its span, so it is always "fully elapsed" and
|
|
142
|
+
// the comparison can never fire.
|
|
143
|
+
if (!window.estimated) {
|
|
144
|
+
const lead = aheadOfPace(window, now);
|
|
145
|
+
if (window.percentUsed >= config.floor && lead !== null && lead >= config.ahead) {
|
|
146
|
+
return 'tight';
|
|
147
|
+
}
|
|
148
|
+
}
|
|
130
149
|
return 'roomy';
|
|
131
150
|
}
|
|
132
151
|
|
|
@@ -292,7 +311,7 @@ async function run(now, hookInput) {
|
|
|
292
311
|
session: view.session,
|
|
293
312
|
rebuilt: Boolean(binding && binding.estimated),
|
|
294
313
|
snapshotAge: usage.formatDuration(base.snapshotAgeMs),
|
|
295
|
-
pressure: pressure(binding, now, config),
|
|
314
|
+
pressure: pressure(binding, now, config, view.turnsLeft),
|
|
296
315
|
});
|
|
297
316
|
}
|
|
298
317
|
|
|
@@ -571,11 +571,27 @@ function bindingWindow(windows) {
|
|
|
571
571
|
const live = fresh.length ? fresh : known;
|
|
572
572
|
if (!live.length) return null;
|
|
573
573
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
574
|
+
// How soon this window stops the work. A window with no pace estimate is
|
|
575
|
+
// ranked by how full it is instead, because a nearly full window must never
|
|
576
|
+
// be passed over merely because nothing has been spent in it lately.
|
|
577
|
+
const soonest = (w) => {
|
|
578
|
+
if (Number.isFinite(w.headroomMs)) return w.headroomMs;
|
|
579
|
+
return w.percentUsed >= 90 ? 0 : Infinity;
|
|
580
|
+
};
|
|
581
|
+
|
|
582
|
+
return live.reduce((best, w) => {
|
|
583
|
+
const mine = soonest(w);
|
|
584
|
+
const theirs = soonest(best);
|
|
585
|
+
if (mine !== theirs) return mine < theirs ? w : best;
|
|
586
|
+
|
|
587
|
+
// Equally urgent: the shorter window is the one hit first in practice, so
|
|
588
|
+
// the 5-hour limit wins a tie against the weekly one.
|
|
589
|
+
const myspan = Number.isFinite(w.spanMs) ? w.spanMs : Infinity;
|
|
590
|
+
const theirspan = Number.isFinite(best.spanMs) ? best.spanMs : Infinity;
|
|
591
|
+
if (myspan !== theirspan) return myspan < theirspan ? w : best;
|
|
592
|
+
|
|
593
|
+
return w.percentUsed > best.percentUsed ? w : best;
|
|
594
|
+
});
|
|
579
595
|
}
|
|
580
596
|
|
|
581
597
|
function formatDuration(ms) {
|