claude-usage-limits 1.9.1 → 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
|
|
@@ -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
|
};
|
|
@@ -185,10 +185,35 @@ function configDir() {
|
|
|
185
185
|
|
|
186
186
|
// The CLI keeps its account state in ~/.claude.json, or next to the config
|
|
187
187
|
// directory when CLAUDE_CONFIG_DIR moves it.
|
|
188
|
-
|
|
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() {
|
|
189
198
|
const scoped = path.join(configDir(), '.claude.json');
|
|
190
|
-
|
|
191
|
-
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];
|
|
192
217
|
}
|
|
193
218
|
|
|
194
219
|
function readJson(file) {
|
|
@@ -1775,6 +1800,25 @@ function statusLine(collected) {
|
|
|
1775
1800
|
unreported: snapshot.utilization === 0 && !Number.isFinite(resetsAt),
|
|
1776
1801
|
});
|
|
1777
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
|
+
|
|
1778
1822
|
if (!parts.length) return '';
|
|
1779
1823
|
|
|
1780
1824
|
const trusted = parts.filter((part) => !part.stale && !part.unreported);
|
|
@@ -2376,6 +2420,7 @@ module.exports = {
|
|
|
2376
2420
|
SATURATION_LIMIT,
|
|
2377
2421
|
MIN_BASELINE_TURNS,
|
|
2378
2422
|
MIN_BASELINE_PERCENT,
|
|
2423
|
+
accountFile,
|
|
2379
2424
|
buildWindows,
|
|
2380
2425
|
limitWindows,
|
|
2381
2426
|
lastRejections,
|