claude-usage-limits 1.9.0 → 1.9.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.9.
|
|
4
|
+
"version": "1.9.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",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "usage-limits",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.2",
|
|
4
4
|
"description": "Reports how much of your Codex usage limit is left as turns of work rather than a percentage, prices a job before you start it, and counts the other agents sharing the same budget.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Ridelink",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-usage-limits",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.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",
|
|
@@ -118,6 +118,14 @@ really somewhere in 1.5 to 2.5. At low readings the projection can be off by
|
|
|
118
118
|
a quarter or more in either direction. The report flags this below 5 percent.
|
|
119
119
|
Above about 20 percent it tightens up considerably.
|
|
120
120
|
|
|
121
|
+
**Two files can claim to be the account state.** The meter lives in
|
|
122
|
+
`~/.claude.json`, but a Claude Code migration also writes a small
|
|
123
|
+
`~/.claude/.claude.json` holding machine ids and no meter at all. Whichever
|
|
124
|
+
one actually carries `cachedUsageUtilization` is the one read. Choosing on
|
|
125
|
+
existence alone found the stub, concluded there was no Claude snapshot, and
|
|
126
|
+
sent host detection off to Codex, which reported that agent's meter inside a
|
|
127
|
+
Claude session.
|
|
128
|
+
|
|
121
129
|
**One machine only.** Transcripts are local. Usage from another machine, from
|
|
122
130
|
claude.ai, or from a cloud session counts against the same limit but leaves no
|
|
123
131
|
local record. The percentages stay correct; the calibration reads low, which
|
|
@@ -130,7 +138,17 @@ calculation.
|
|
|
130
138
|
**List prices are a proxy.** The rate table is first-party API pricing. How a
|
|
131
139
|
subscription plan actually meters usage is not published, and the weighting
|
|
132
140
|
almost certainly is not exactly this. It is close enough for ratios, which is
|
|
133
|
-
all it is used for.
|
|
141
|
+
all it is used for. No published or community source shows the meter weighting
|
|
142
|
+
models differently from their dollar prices, so calibrating dollars against
|
|
143
|
+
your own meter remains the best method anyone outside Anthropic has.
|
|
144
|
+
|
|
145
|
+
**The snapshot is slow by design.** The percentages come from Claude Code's
|
|
146
|
+
own cache of the account meter, which refreshes on its own schedule - roughly
|
|
147
|
+
hourly in practice, because the endpoint behind it rate-limits aggressive
|
|
148
|
+
polling. Between refreshes every figure here is the last real reading plus
|
|
149
|
+
arithmetic. That is why an old snapshot is reported as a floor with its age
|
|
150
|
+
attached rather than dressed up as a current percentage, and why `/usage` is
|
|
151
|
+
the one way to force a fresh reading.
|
|
134
152
|
|
|
135
153
|
**A reset time can be in the past.** The cache refreshes when Claude Code
|
|
136
154
|
talks to the API, so an idle spell leaves it behind. A window whose `resets_at`
|
|
@@ -168,6 +186,13 @@ immediately. Re-run the report if the shape of the work changes.
|
|
|
168
186
|
The rate table in `scripts/usage.js` is a plain object at the top of the file.
|
|
169
187
|
When new models ship, add a row.
|
|
170
188
|
|
|
189
|
+
A bracketed suffix on a model id (`claude-sonnet-5[1m]`) is stripped before
|
|
190
|
+
the lookup: it marks a context-window variant of the same model, not a new
|
|
191
|
+
one. Cache reads price at a tenth of the input rate unless a row carries a
|
|
192
|
+
`cacheRead` figure of its own - Fable and Mythos 5.1 price reads outright at
|
|
193
|
+
$0.25 per million, far under the tenth rule, and reads are the dominant input
|
|
194
|
+
in exactly the long sessions where the difference matters.
|
|
195
|
+
|
|
171
196
|
Until someone does, a model this table has not seen is priced at the average of
|
|
172
197
|
the family its name contains: an unreleased `claude-opus-5-2` is charged at the
|
|
173
198
|
mean of every Opus rate on record. Averaging assumes nothing about which
|
|
@@ -38,15 +38,28 @@ function exists(file) {
|
|
|
38
38
|
|
|
39
39
|
// Claude Code only writes this once it has talked to the API, so its presence
|
|
40
40
|
// is a stronger signal than the directory existing.
|
|
41
|
-
|
|
41
|
+
//
|
|
42
|
+
// Look in both places rather than stopping at whichever exists. A migration
|
|
43
|
+
// leaves a small ~/.claude/.claude.json carrying machine ids and no meter,
|
|
44
|
+
// while the account state stays in the home directory file; stopping at the
|
|
45
|
+
// stub answered "no Claude snapshot" on a machine plainly running Claude Code,
|
|
46
|
+
// and detection then fell through to Codex and reported its meter instead.
|
|
47
|
+
function claudeSnapshotFile() {
|
|
42
48
|
const scoped = path.join(claudeConfigDir(), '.claude.json');
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
+
const home = path.join(os.homedir(), '.claude.json');
|
|
50
|
+
for (const file of scoped === home ? [home] : [scoped, home]) {
|
|
51
|
+
try {
|
|
52
|
+
const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
53
|
+
if (parsed && parsed.cachedUsageUtilization) return file;
|
|
54
|
+
} catch (err) {
|
|
55
|
+
// Missing or unreadable is just "not this one".
|
|
56
|
+
}
|
|
49
57
|
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function claudeHasSnapshot() {
|
|
62
|
+
return claudeSnapshotFile() !== null;
|
|
50
63
|
}
|
|
51
64
|
|
|
52
65
|
function codexHasSessions() {
|
|
@@ -91,6 +104,7 @@ module.exports = {
|
|
|
91
104
|
codexHome,
|
|
92
105
|
claudeConfigDir,
|
|
93
106
|
claudeHasSnapshot,
|
|
107
|
+
claudeSnapshotFile,
|
|
94
108
|
codexHasSessions,
|
|
95
109
|
exists,
|
|
96
110
|
};
|
|
@@ -47,8 +47,12 @@ const MINUTE = 60 * 1000;
|
|
|
47
47
|
const HOUR = 60 * MINUTE;
|
|
48
48
|
const DAY = 24 * HOUR;
|
|
49
49
|
|
|
50
|
-
// USD per million tokens, first-party API rates.
|
|
50
|
+
// USD per million tokens, first-party API rates. `cacheRead` is an absolute
|
|
51
|
+
// $/MTok override for the few models that price reads outright instead of at
|
|
52
|
+
// a tenth of input; everything else uses the CACHE_READ multiplier below.
|
|
51
53
|
const RATES = {
|
|
54
|
+
'claude-fable-5-1': { input: 10, output: 50, cacheRead: 0.25 },
|
|
55
|
+
'claude-mythos-5-1': { input: 10, output: 50, cacheRead: 0.25 },
|
|
52
56
|
'claude-fable-5': { input: 10, output: 50 },
|
|
53
57
|
'claude-mythos-5': { input: 10, output: 50 },
|
|
54
58
|
'claude-opus-5': { input: 5, output: 25 },
|
|
@@ -91,9 +95,21 @@ function familyAverage(family, table) {
|
|
|
91
95
|
return { input: input / members.length, output: output / members.length };
|
|
92
96
|
}
|
|
93
97
|
|
|
98
|
+
// Claude Code aliases and some transcript records carry a bracketed variant
|
|
99
|
+
// suffix - "fable[1m]" is the 1M-context toggle on the same model, not a
|
|
100
|
+
// different one. Left in place it misses the exact rate lookup and lands on
|
|
101
|
+
// the family average, which is wrong whenever a family's members price
|
|
102
|
+
// differently (sonnet 5 at $2 against sonnet 4.6 at $3).
|
|
103
|
+
function normalizeModel(model) {
|
|
104
|
+
return String(model || '')
|
|
105
|
+
.toLowerCase()
|
|
106
|
+
.replace(/\[[^\]]*\]\s*$/, '')
|
|
107
|
+
.trim();
|
|
108
|
+
}
|
|
109
|
+
|
|
94
110
|
// Whether the price came from the table or from an assumption.
|
|
95
111
|
function isKnownModel(model) {
|
|
96
|
-
return Object.prototype.hasOwnProperty.call(RATES,
|
|
112
|
+
return Object.prototype.hasOwnProperty.call(RATES, normalizeModel(model));
|
|
97
113
|
}
|
|
98
114
|
|
|
99
115
|
// Cache traffic is priced as a multiple of the input rate.
|
|
@@ -169,10 +185,35 @@ function configDir() {
|
|
|
169
185
|
|
|
170
186
|
// The CLI keeps its account state in ~/.claude.json, or next to the config
|
|
171
187
|
// directory when CLAUDE_CONFIG_DIR moves it.
|
|
172
|
-
|
|
188
|
+
//
|
|
189
|
+
// Both can exist at once, and the one in the config directory is not
|
|
190
|
+
// necessarily the one with the meter in it: a Claude Code migration writes a
|
|
191
|
+
// small ~/.claude/.claude.json holding machine ids and migration flags while
|
|
192
|
+
// the account state, including cachedUsageUtilization, stays in the home
|
|
193
|
+
// directory file. Picking on existence alone found that stub, reported no
|
|
194
|
+
// snapshot, and sent host detection off to Codex - which is how a Claude
|
|
195
|
+
// session ends up quoting another agent's meter entirely. So choose the file
|
|
196
|
+
// that actually carries a snapshot, and only fall back to existence.
|
|
197
|
+
function accountFiles() {
|
|
173
198
|
const scoped = path.join(configDir(), '.claude.json');
|
|
174
|
-
|
|
175
|
-
return
|
|
199
|
+
const home = path.join(os.homedir(), '.claude.json');
|
|
200
|
+
return scoped === home ? [home] : [scoped, home];
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function hasSnapshot(file) {
|
|
204
|
+
const parsed = readJson(file);
|
|
205
|
+
return Boolean(parsed && parsed.cachedUsageUtilization);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function accountFile() {
|
|
209
|
+
const candidates = accountFiles();
|
|
210
|
+
for (const file of candidates) {
|
|
211
|
+
if (hasSnapshot(file)) return file;
|
|
212
|
+
}
|
|
213
|
+
for (const file of candidates) {
|
|
214
|
+
if (fs.existsSync(file)) return file;
|
|
215
|
+
}
|
|
216
|
+
return candidates[candidates.length - 1];
|
|
176
217
|
}
|
|
177
218
|
|
|
178
219
|
function readJson(file) {
|
|
@@ -184,7 +225,7 @@ function readJson(file) {
|
|
|
184
225
|
}
|
|
185
226
|
|
|
186
227
|
function rateFor(model) {
|
|
187
|
-
const id =
|
|
228
|
+
const id = normalizeModel(model);
|
|
188
229
|
if (RATES[id]) return RATES[id];
|
|
189
230
|
return familyAverage(familyOf(id)) || FALLBACK_RATE;
|
|
190
231
|
}
|
|
@@ -199,16 +240,26 @@ function costOf(usage, model) {
|
|
|
199
240
|
|
|
200
241
|
let writeUnits = write5m * CACHE_WRITE_5M + write1h * CACHE_WRITE_1H;
|
|
201
242
|
if (writeUnits === 0) {
|
|
202
|
-
// Older records only carry the undifferentiated total.
|
|
243
|
+
// Older records only carry the undifferentiated total. Five minutes is
|
|
244
|
+
// the default TTL, so that is the assumption; an old-format one-hour
|
|
245
|
+
// session is under-priced by it, but assuming 2x would overcharge the
|
|
246
|
+
// common case to be right about the rare one.
|
|
203
247
|
writeUnits = (usage.cache_creation_input_tokens || 0) * CACHE_WRITE_5M;
|
|
204
248
|
}
|
|
205
249
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
250
|
+
// Reads price at a tenth of the input rate unless the model prices them
|
|
251
|
+
// outright. The distinction matters most exactly where reads dominate: a
|
|
252
|
+
// long session re-reads its whole context every turn, and pricing Fable
|
|
253
|
+
// 5.1's $0.25 reads by the tenth rule would overstate that spend fourfold.
|
|
254
|
+
const readTokens = usage.cache_read_input_tokens || 0;
|
|
255
|
+
const readCost = Number.isFinite(rate.cacheRead)
|
|
256
|
+
? readTokens * rate.cacheRead
|
|
257
|
+
: readTokens * CACHE_READ * rate.input;
|
|
210
258
|
|
|
211
|
-
|
|
259
|
+
const inputUnits = (usage.input_tokens || 0) + writeUnits;
|
|
260
|
+
return (
|
|
261
|
+
(inputUnits * rate.input + readCost + (usage.output_tokens || 0) * rate.output) / 1e6
|
|
262
|
+
);
|
|
212
263
|
}
|
|
213
264
|
|
|
214
265
|
function tokensOf(usage) {
|
|
@@ -1749,6 +1800,25 @@ function statusLine(collected) {
|
|
|
1749
1800
|
unreported: snapshot.utilization === 0 && !Number.isFinite(resetsAt),
|
|
1750
1801
|
});
|
|
1751
1802
|
}
|
|
1803
|
+
|
|
1804
|
+
// The per-model weeklies are not bucket keys, they are entries in the
|
|
1805
|
+
// account's own `limits` list, so a loop over the bucket table never saw
|
|
1806
|
+
// them. On a plan where the Fable weekly is the limit that actually binds,
|
|
1807
|
+
// that meant the status line quoting the shared weekly at 24% while the
|
|
1808
|
+
// window about to stop the work sat at 76, which is the wrong number in the
|
|
1809
|
+
// most convincing possible place.
|
|
1810
|
+
for (const limit of limitWindows(utilization)) {
|
|
1811
|
+
if (!limit.family) continue;
|
|
1812
|
+
const msToReset = Number.isFinite(limit.resetsAt) ? limit.resetsAt - now : null;
|
|
1813
|
+
parts.push({
|
|
1814
|
+
label: limit.family,
|
|
1815
|
+
percent: limit.percent,
|
|
1816
|
+
msToReset,
|
|
1817
|
+
stale: msToReset !== null && msToReset <= 0,
|
|
1818
|
+
unreported: false,
|
|
1819
|
+
});
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1752
1822
|
if (!parts.length) return '';
|
|
1753
1823
|
|
|
1754
1824
|
const trusted = parts.filter((part) => !part.stale && !part.unreported);
|
|
@@ -2350,6 +2420,7 @@ module.exports = {
|
|
|
2350
2420
|
SATURATION_LIMIT,
|
|
2351
2421
|
MIN_BASELINE_TURNS,
|
|
2352
2422
|
MIN_BASELINE_PERCENT,
|
|
2423
|
+
accountFile,
|
|
2353
2424
|
buildWindows,
|
|
2354
2425
|
limitWindows,
|
|
2355
2426
|
lastRejections,
|