claude-usage-limits 1.5.5 → 1.6.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.5.5",
4
+ "version": "1.6.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
@@ -207,6 +207,12 @@ A window with no recent spend to measure is ranked by how full it is rather
207
207
  than being skipped, so a 5-hour window sitting at 95 percent is never passed
208
208
  over just because nothing has gone through it in the last few minutes.
209
209
 
210
+ Binding is about what stops you soonest, though, not what stopping costs, and
211
+ those are different: a 5-hour window returns in hours, the weekly one in days.
212
+ So a window above 85 percent gets called out even when something shorter binds,
213
+ with its own reset time, because spending the weekly window to save a few turns
214
+ of the 5-hour one is a bad trade.
215
+
210
216
  ## When you keep typing
211
217
 
212
218
  Every message sent while work is already running starts another turn, and each
@@ -509,7 +515,7 @@ test/ node --test, no dependencies
509
515
  node --test
510
516
  ```
511
517
 
512
- 181 tests over the pricing, the window arithmetic, plan and credit detection,
518
+ 186 tests over the pricing, the window arithmetic, plan and credit detection,
513
519
  the status line, the before-prompt line, job forecasting, per-project
514
520
  attribution, the CLI, packaging, and the settings save/restore.
515
521
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-usage-limits",
3
- "version": "1.5.5",
3
+ "version": "1.6.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",
@@ -171,6 +171,12 @@ Keep it to one line unless the work genuinely does not fit. The budget note is
171
171
  a header, not a section, and it must never push the actual answer down the
172
172
  page.
173
173
 
174
+ Which window binds is about what stops you soonest, not what stopping costs.
175
+ Those differ: a 5-hour window comes back in hours, the weekly one in days. So
176
+ when a window that is not binding sits near its wall, say so and weigh it. The
177
+ hook flags those. Running the weekly out to save a few turns of the 5-hour
178
+ window is a bad trade even though the 5-hour is what runs out first.
179
+
174
180
  One thing to get right: **quote the binding window, not the roomiest one.**
175
181
  Two windows run at once and they are rarely in the same place. The turns of
176
182
  headroom and the reset time belong to whichever runs out first. Putting the
@@ -242,6 +242,17 @@ function briefText(parts) {
242
242
  );
243
243
  }
244
244
  if (parts.othersSummary) sentences.push('Other windows: ' + parts.othersSummary + '.');
245
+
246
+ // A window that is not binding can still be the expensive one to exhaust.
247
+ if (parts.critical && parts.critical.length) {
248
+ for (const other of parts.critical) {
249
+ sentences.push(
250
+ 'Note that ' + other.label + ' is at ' + other.percentUsed + '% and resets in ' +
251
+ other.resetsIn + ', so running that one out stops work for far longer than the ' +
252
+ 'binding window would. Weigh it even though it is not what runs out first.'
253
+ );
254
+ }
255
+ }
245
256
  if (parts.session) {
246
257
  sentences.push(
247
258
  'This session: ' + parts.session.turns + ' turns, ' +
@@ -295,6 +306,11 @@ async function run(now, hookInput) {
295
306
  othersSummary: summariseOthers(data.windows, binding && binding.key),
296
307
  sessions: data.sessions,
297
308
  staleWindows: data.staleWindows,
309
+ critical: usage.criticalOthers(data.windows, binding && binding.key).map((w) => ({
310
+ label: w.label,
311
+ percentUsed: w.percentUsed,
312
+ resetsIn: Number.isFinite(w.msToReset) ? usage.formatDuration(w.msToReset) : 'an unknown time',
313
+ })),
298
314
  snapshotAge: usage.formatDuration(data.snapshotAgeMs),
299
315
  binding: binding
300
316
  ? {
@@ -334,6 +350,7 @@ async function run(now, hookInput) {
334
350
  session: view.session,
335
351
  rebuilt: Boolean(binding && binding.estimated),
336
352
  staleWindows: view.staleWindows || 0,
353
+ critical: view.critical || [],
337
354
  pointsSinceSnapshot: (binding && binding.pointsSinceSnapshot) || 0,
338
355
  snapshotAge: view.snapshotAge,
339
356
  pressure: pressure(binding, now, config, view.turnsLeft),
@@ -685,6 +685,24 @@ function buildWindow(spec, snapshot, events, now, options) {
685
685
  return window;
686
686
  }
687
687
 
688
+ // Which window binds is about what stops you soonest. It says nothing about
689
+ // what stopping costs. Running out of a 5-hour window waits hours; running out
690
+ // of the weekly one waits days. So a weekly window near the wall is worth
691
+ // hearing about even while a shorter window binds.
692
+ const CRITICAL_PERCENT = 85;
693
+
694
+ function criticalOthers(windows, bindingKey, threshold) {
695
+ const limit = Number.isFinite(threshold) ? threshold : CRITICAL_PERCENT;
696
+ return (windows || []).filter(
697
+ (w) =>
698
+ w &&
699
+ w.key !== bindingKey &&
700
+ !w.stale &&
701
+ w.percentUsed !== null &&
702
+ w.percentUsed >= limit
703
+ );
704
+ }
705
+
688
706
  // The window that will stop the work first.
689
707
  function bindingWindow(windows) {
690
708
  const known = windows.filter((w) => w.percentUsed !== null);
@@ -1340,6 +1358,8 @@ module.exports = {
1340
1358
  MIN_BASELINE_TURNS,
1341
1359
  buildWindows,
1342
1360
  bindingWindow,
1361
+ criticalOthers,
1362
+ CRITICAL_PERCENT,
1343
1363
  dominantEffort,
1344
1364
  typicalTurnCost,
1345
1365
  activeSessions,