claude-usage-limits 1.1.2 → 1.1.4
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.
|
@@ -9,9 +9,14 @@
|
|
|
9
9
|
{
|
|
10
10
|
"name": "usage-limits",
|
|
11
11
|
"source": "./",
|
|
12
|
-
"description": "
|
|
12
|
+
"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.",
|
|
13
13
|
"category": "productivity",
|
|
14
|
-
"tags": [
|
|
14
|
+
"tags": [
|
|
15
|
+
"usage",
|
|
16
|
+
"limits",
|
|
17
|
+
"cost",
|
|
18
|
+
"planning"
|
|
19
|
+
],
|
|
15
20
|
"license": "MIT"
|
|
16
21
|
}
|
|
17
22
|
]
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "usage-limits",
|
|
3
3
|
"displayName": "Usage Limits",
|
|
4
|
-
"version": "1.1.
|
|
5
|
-
"description": "
|
|
4
|
+
"version": "1.1.4",
|
|
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",
|
|
8
8
|
"url": "https://github.com/ridelink0"
|
package/README.md
CHANGED
|
@@ -2,14 +2,46 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/claude-usage-limits)
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
Most usage tools for Claude Code show **you** the numbers. This one shows them
|
|
6
|
+
to **Claude**, before every prompt, and changes what it does about them.
|
|
7
7
|
|
|
8
|
-
Claude Code already knows how much of your 5-hour and weekly limit
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
Claude Code already knows how much of your 5-hour and weekly limit is gone. It
|
|
9
|
+
caches those numbers locally and will show them if you ask. What it does not do
|
|
10
|
+
is notice that the job in front of it is larger than the budget behind it. So
|
|
11
|
+
it starts anyway, and stops halfway through an edit.
|
|
12
|
+
|
|
13
|
+
This puts the budget in front of Claude before your prompt lands, so the reply
|
|
14
|
+
opens with the answer instead:
|
|
15
|
+
|
|
16
|
+
> The weekly window has about 22 turns left. That covers the parser change and
|
|
17
|
+
> its tests, but not the migration or the docs pass, so I will do the first two
|
|
18
|
+
> and leave the rest for after the reset at 09:00.
|
|
19
|
+
|
|
20
|
+
Nobody read a chart to get that. The numbers reached the model, not you.
|
|
21
|
+
|
|
22
|
+
## How this differs from a usage dashboard
|
|
23
|
+
|
|
24
|
+
There are a lot of good tools that read the same local files this does and draw
|
|
25
|
+
you a picture: status lines, menu bar apps, terminal dashboards. They are worth
|
|
26
|
+
having, and this is not trying to replace them. The difference is who the
|
|
27
|
+
output is for.
|
|
28
|
+
|
|
29
|
+
| A usage dashboard | This |
|
|
30
|
+
| --- | --- |
|
|
31
|
+
| Renders numbers for a person to read | Puts numbers in the model's context |
|
|
32
|
+
| You notice, then you interrupt | Claude notices, and adjusts on its own |
|
|
33
|
+
| Tells you 78 percent is gone | Tells you 22 turns are left, and whether what you asked for fits in them |
|
|
34
|
+
| Runs beside Claude Code | Runs inside it, as a skill and a hook |
|
|
35
|
+
| Shows what already happened | Says what to do now, and what to drop |
|
|
36
|
+
|
|
37
|
+
A percentage is a fact about the past. The useful question is whether the thing
|
|
38
|
+
you just asked for is going to finish, and answering that needs the request and
|
|
39
|
+
the budget in the same place. That place is the model's context, which is where
|
|
40
|
+
this puts them.
|
|
41
|
+
|
|
42
|
+
So: if you want to watch your usage, install a status line. This ships one too.
|
|
43
|
+
If you want the thing spending the budget to know it is spending the budget,
|
|
44
|
+
that is what this is for.
|
|
13
45
|
|
|
14
46
|
## What it prints
|
|
15
47
|
|
|
@@ -206,13 +238,19 @@ Installed as a plugin, a hook measures the budget before each prompt and puts
|
|
|
206
238
|
one line into Claude's context:
|
|
207
239
|
|
|
208
240
|
```
|
|
209
|
-
[usage-limits] 5-hour 47
|
|
210
|
-
3h 52m
|
|
241
|
+
[usage-limits] binding window is 5-hour 47% used, about 75 turns of headroom,
|
|
242
|
+
resets in 3h 52m. Other windows: weekly 16%. This session: 229 turns, $64.16.
|
|
211
243
|
```
|
|
212
244
|
|
|
245
|
+
It names the window that will stop the work first and hangs the figures off
|
|
246
|
+
that one. Two windows run at once and they are rarely in the same place, so
|
|
247
|
+
"weekly 16%" sitting next to "75 turns" would read as far more room than
|
|
248
|
+
exists.
|
|
249
|
+
|
|
213
250
|
Claude opens with it. When there is room that is a single line and it moves on:
|
|
214
251
|
|
|
215
|
-
>
|
|
252
|
+
> The 5-hour window is the binding one: 47% used, about 75 turns of headroom.
|
|
253
|
+
> This fits easily.
|
|
216
254
|
|
|
217
255
|
When there is not, the line becomes a plan rather than a status:
|
|
218
256
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-usage-limits",
|
|
3
|
-
"version": "1.1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.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",
|
|
7
7
|
"claude-code",
|
|
@@ -100,7 +100,8 @@ there is nothing to go and look up.
|
|
|
100
100
|
|
|
101
101
|
When there is room, one line, then get on with it:
|
|
102
102
|
|
|
103
|
-
>
|
|
103
|
+
> The 5-hour window is the binding one: 47% used, about 75 turns of headroom.
|
|
104
|
+
> This fits easily.
|
|
104
105
|
|
|
105
106
|
When it does not fit, that line becomes the plan:
|
|
106
107
|
|
|
@@ -116,6 +117,13 @@ Keep it to one line unless the work genuinely does not fit. The budget note is
|
|
|
116
117
|
a header, not a section, and it must never push the actual answer down the
|
|
117
118
|
page.
|
|
118
119
|
|
|
120
|
+
One thing to get right: **quote the binding window, not the roomiest one.**
|
|
121
|
+
Two windows run at once and they are rarely in the same place. The turns of
|
|
122
|
+
headroom and the reset time belong to whichever runs out first. Putting the
|
|
123
|
+
other window's percentage next to those figures claims far more room than
|
|
124
|
+
exists, and that is how a session ends mid-edit while the weekly number still
|
|
125
|
+
looks comfortable.
|
|
126
|
+
|
|
119
127
|
### Pricing the job before starting it
|
|
120
128
|
|
|
121
129
|
Size the work in turns using the table above, then price it:
|
|
@@ -43,10 +43,25 @@ function cacheFile() {
|
|
|
43
43
|
// ever got a hit and both paid for a full scan each time.
|
|
44
44
|
const KEEP_SESSIONS = 5;
|
|
45
45
|
|
|
46
|
+
// A cache written before slots were keyed by session keeps its fields at the
|
|
47
|
+
// top level, so upgrading would carry "at", "turnsLeft", "session" and
|
|
48
|
+
// "sessionId" forward as if each were a session, crowding out real slots and
|
|
49
|
+
// quietly undoing the per-session caching. Anything that is not a slot goes.
|
|
50
|
+
function keepSlots(parsed) {
|
|
51
|
+
if (!parsed || typeof parsed !== 'object') return {};
|
|
52
|
+
const slots = {};
|
|
53
|
+
for (const key of Object.keys(parsed)) {
|
|
54
|
+
const value = parsed[key];
|
|
55
|
+
if (value && typeof value === 'object' && Number.isFinite(value.at)) {
|
|
56
|
+
slots[key] = value;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return slots;
|
|
60
|
+
}
|
|
61
|
+
|
|
46
62
|
function readCache() {
|
|
47
63
|
try {
|
|
48
|
-
|
|
49
|
-
return parsed && typeof parsed === 'object' ? parsed : {};
|
|
64
|
+
return keepSlots(JSON.parse(fs.readFileSync(cacheFile(), 'utf8')));
|
|
50
65
|
} catch (err) {
|
|
51
66
|
return {};
|
|
52
67
|
}
|
|
@@ -158,33 +173,47 @@ function sessionSpend(events, sessionId) {
|
|
|
158
173
|
return turns ? { turns, cost } : null;
|
|
159
174
|
}
|
|
160
175
|
|
|
161
|
-
function
|
|
176
|
+
function describeWindow(window) {
|
|
177
|
+
if (!window) return null;
|
|
178
|
+
return window.stale
|
|
179
|
+
? window.label + ' rolling over'
|
|
180
|
+
: window.label + ' ' + window.percentUsed + '%';
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Everything except the window that will actually stop the work.
|
|
184
|
+
function summariseOthers(windows, bindingKey) {
|
|
162
185
|
return windows
|
|
163
|
-
.filter((window) => window.percentUsed !== null)
|
|
164
|
-
.map(
|
|
165
|
-
window.stale
|
|
166
|
-
? window.label + ' rolling over'
|
|
167
|
-
: window.label + ' ' + window.percentUsed + '%'
|
|
168
|
-
)
|
|
186
|
+
.filter((window) => window.percentUsed !== null && window.key !== bindingKey)
|
|
187
|
+
.map(describeWindow)
|
|
169
188
|
.join(', ');
|
|
170
189
|
}
|
|
171
190
|
|
|
172
191
|
function briefText(parts) {
|
|
173
|
-
|
|
174
|
-
|
|
192
|
+
// The turns and the reset time belong to one specific window. Listing every
|
|
193
|
+
// window and then the numbers invites reading them against the wrong one, so
|
|
194
|
+
// the binding window is named and its figures are attached to it.
|
|
195
|
+
const bound = [];
|
|
196
|
+
const described = describeWindow(parts.binding);
|
|
197
|
+
if (described) bound.push(described + (parts.binding.stale ? '' : ' used'));
|
|
175
198
|
if (Number.isFinite(parts.turnsLeft)) {
|
|
176
|
-
|
|
199
|
+
bound.push('about ' + parts.turnsLeft + ' turns of headroom');
|
|
177
200
|
}
|
|
178
|
-
if (parts.resetsIn)
|
|
201
|
+
if (parts.resetsIn) bound.push('resets in ' + parts.resetsIn);
|
|
202
|
+
|
|
203
|
+
const sentences = [];
|
|
204
|
+
sentences.push(
|
|
205
|
+
bound.length
|
|
206
|
+
? '[usage-limits] binding window is ' + bound.join(', ') + '.'
|
|
207
|
+
: '[usage-limits] no usable window reading.'
|
|
208
|
+
);
|
|
209
|
+
if (parts.othersSummary) sentences.push('Other windows: ' + parts.othersSummary + '.');
|
|
179
210
|
if (parts.session) {
|
|
180
|
-
|
|
181
|
-
'
|
|
182
|
-
usage.formatUSD(parts.session.cost)
|
|
211
|
+
sentences.push(
|
|
212
|
+
'This session: ' + parts.session.turns + ' turns, ' +
|
|
213
|
+
usage.formatUSD(parts.session.cost) + '.'
|
|
183
214
|
);
|
|
184
215
|
}
|
|
185
216
|
|
|
186
|
-
const head = '[usage-limits] ' + facts.join(', ') + '.';
|
|
187
|
-
|
|
188
217
|
const instruction =
|
|
189
218
|
parts.pressure === 'tight' || parts.pressure === 'gone'
|
|
190
219
|
? 'Open your reply with one line on where this leaves the budget, then say ' +
|
|
@@ -193,7 +222,14 @@ function briefText(parts) {
|
|
|
193
222
|
: 'Open your reply with one short line stating this and confirming the ' +
|
|
194
223
|
'request fits, then get on with the work. Keep it to a single line.';
|
|
195
224
|
|
|
196
|
-
|
|
225
|
+
// The mistake this guards against: quoting the roomiest window and pinning
|
|
226
|
+
// the binding window figures to it.
|
|
227
|
+
const care =
|
|
228
|
+
' Quote the binding window, not whichever one has the most left. The turns ' +
|
|
229
|
+
'and reset time above belong to the binding window alone; do not read them ' +
|
|
230
|
+
'against another window percentage.';
|
|
231
|
+
|
|
232
|
+
return sentences.join(' ') + '\n' + instruction + care;
|
|
197
233
|
}
|
|
198
234
|
|
|
199
235
|
async function run(now, hookInput) {
|
|
@@ -228,7 +264,8 @@ async function run(now, hookInput) {
|
|
|
228
264
|
const binding = usage.bindingWindow(windows) || windows[0];
|
|
229
265
|
|
|
230
266
|
return briefText({
|
|
231
|
-
|
|
267
|
+
binding,
|
|
268
|
+
othersSummary: summariseOthers(windows, binding && binding.key),
|
|
232
269
|
turnsLeft,
|
|
233
270
|
resetsIn:
|
|
234
271
|
binding && !binding.stale && binding.msToReset !== null
|
|
@@ -259,9 +296,11 @@ module.exports = {
|
|
|
259
296
|
aheadOfPace,
|
|
260
297
|
pressure,
|
|
261
298
|
sessionSpend,
|
|
262
|
-
|
|
299
|
+
describeWindow,
|
|
300
|
+
summariseOthers,
|
|
263
301
|
briefText,
|
|
264
302
|
settings,
|
|
303
|
+
keepSlots,
|
|
265
304
|
pickCached,
|
|
266
305
|
mergeCache,
|
|
267
306
|
KEEP_SESSIONS,
|