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.1",
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.1",
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.1",
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
- function claudeHasSnapshot() {
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 file = exists(scoped) ? scoped : path.join(os.homedir(), '.claude.json');
44
- try {
45
- const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
46
- return Boolean(parsed && parsed.cachedUsageUtilization);
47
- } catch (err) {
48
- return false;
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
- function accountFile() {
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
- if (fs.existsSync(scoped)) return scoped;
191
- return path.join(os.homedir(), '.claude.json');
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,