claude-usage-limits 1.17.0 → 1.19.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.
|
|
4
|
+
"version": "1.19.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",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "usage-limits",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.19.0",
|
|
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.
|
|
3
|
+
"version": "1.19.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",
|
|
@@ -395,6 +395,30 @@ and cannot change one already running. The budget line prints whichever
|
|
|
395
395
|
vocabulary the host it is running in actually understands, so use the command
|
|
396
396
|
it gives you rather than the one you remember.
|
|
397
397
|
|
|
398
|
+
**Check the setting against the work, not against the window.** Once there is
|
|
399
|
+
evidence for it, the budget line tells you what your current effort actually
|
|
400
|
+
costs against the cheapest level measured on this account - "ultra, measured at
|
|
401
|
+
6 times the cost of medium a turn (8 turns against 6)". It says this from the
|
|
402
|
+
first prompt, at any percentage, because a mechanical hour at the top setting is
|
|
403
|
+
waste at 10 per cent used exactly as much as at 80; the only difference is that
|
|
404
|
+
at 80 somebody notices.
|
|
405
|
+
|
|
406
|
+
It cannot judge how hard your work is. You can. So when you see it, look at what
|
|
407
|
+
is actually in front of you and take one of three levers yourself, saying which
|
|
408
|
+
in one line:
|
|
409
|
+
|
|
410
|
+
1. **Drop the effort** for that stretch - a rename, a docs pass, running tests,
|
|
411
|
+
applying a fix you have already worked out.
|
|
412
|
+
2. **Hand the stretch to a cheaper model**, and keep the dear one for the
|
|
413
|
+
decisions that need it.
|
|
414
|
+
3. **Do less of it at this setting** - a fan-out multiplies the setting across
|
|
415
|
+
every agent, so six agents at ultra is six ultra turns, not one.
|
|
416
|
+
|
|
417
|
+
Put it back when the work gets hard again. The question is asked once per
|
|
418
|
+
setting per session, not every prompt, and never at all unless there is a real
|
|
419
|
+
measured comparison behind it - a ratio from a price list would have you drop
|
|
420
|
+
effort on a hunch and call it evidence.
|
|
421
|
+
|
|
398
422
|
**This is a lever you may pull yourself, not only an emergency exit.** Use it
|
|
399
423
|
whenever the current setting is dearer than the work in front of you needs,
|
|
400
424
|
without being asked and long before any window is tight: a mechanical rename,
|
|
@@ -666,6 +666,27 @@ function briefText(parts) {
|
|
|
666
666
|
);
|
|
667
667
|
}
|
|
668
668
|
|
|
669
|
+
// Is the setting bigger than the work needs? Asked from the setting, not
|
|
670
|
+
// from the budget: a mechanical hour at the top setting is waste at 10 per
|
|
671
|
+
// cent used exactly as much as at 80. The plugin supplies the measured half
|
|
672
|
+
// and asks for the judgement, because only the reader knows what is coming.
|
|
673
|
+
//
|
|
674
|
+
// Said once per setting per session. Every prompt would be nagging, and the
|
|
675
|
+
// question only changes when the setting does.
|
|
676
|
+
if (parts.fit) {
|
|
677
|
+
sentences.push(
|
|
678
|
+
'Your effort is ' + parts.fit.effort + ', measured at ' + parts.fit.multiple +
|
|
679
|
+
' times the cost of ' + parts.fit.cheaper + ' a turn on this account (' +
|
|
680
|
+
parts.fit.sample + ' turns against ' + parts.fit.cheaperSample + '). Judge what is ' +
|
|
681
|
+
'actually in front of you before the next stretch: mechanical work - a rename, a docs ' +
|
|
682
|
+
'pass, running tests, applying a fix you have already worked out - does not need it. ' +
|
|
683
|
+
'If this stretch is that, take one of the three levers yourself and say which in one ' +
|
|
684
|
+
'line: drop the effort (' + parts.fit.command + '), hand the stretch to a cheaper ' +
|
|
685
|
+
'model, or do less of it at this setting - a fan-out multiplies the setting across ' +
|
|
686
|
+
'every agent. Put it back when the work gets hard again.'
|
|
687
|
+
);
|
|
688
|
+
}
|
|
689
|
+
|
|
669
690
|
if (parts.voiceNote) sentences.push('How this user wants to be written to: ' + parts.voiceNote);
|
|
670
691
|
|
|
671
692
|
// What the relay changes about all of this.
|
|
@@ -969,6 +990,10 @@ async function run(now, hookInput) {
|
|
|
969
990
|
if (!base.utilization) return '';
|
|
970
991
|
|
|
971
992
|
const all = readCache();
|
|
993
|
+
// Which setting the fit question was already asked for, read BEFORE anything
|
|
994
|
+
// rewrites the slot. Reading it afterwards meant comparing the new answer
|
|
995
|
+
// against itself, so the question could never be asked at all.
|
|
996
|
+
const askedFitFor = all && all[sessionId || '_'] ? all[sessionId || '_'].fitFor || null : null;
|
|
972
997
|
let view = pickCached(all, sessionId, now, config.cacheSeconds * SECOND);
|
|
973
998
|
|
|
974
999
|
// Everything shown has to come from one pass. Deriving the turns from a
|
|
@@ -1003,7 +1028,9 @@ async function run(now, hookInput) {
|
|
|
1003
1028
|
snapshotAge: usage.formatDuration(data.snapshotAgeMs),
|
|
1004
1029
|
binding: cacheableBinding(binding),
|
|
1005
1030
|
effortWarning: data.effortWarning || null,
|
|
1031
|
+
fit: usage.settingFit(data.events || null, data.effortNow || null, usage.currentHost()),
|
|
1006
1032
|
};
|
|
1033
|
+
view.fitFor = view.fit ? view.fit.effort : askedFitFor;
|
|
1007
1034
|
writeCache(mergeCache(all, sessionId, view, KEEP_SESSIONS));
|
|
1008
1035
|
}
|
|
1009
1036
|
|
|
@@ -1070,6 +1097,9 @@ async function run(now, hookInput) {
|
|
|
1070
1097
|
host: usage.currentHost(),
|
|
1071
1098
|
turnsLeft: view.turnsLeft,
|
|
1072
1099
|
effortWarning: view.effortWarning || null,
|
|
1100
|
+
// Once per setting. The slot below records which effort it was said for,
|
|
1101
|
+
// so a change of setting asks the question again and a repeat does not.
|
|
1102
|
+
fit: view.fit && askedFitFor !== view.fit.effort ? view.fit : null,
|
|
1073
1103
|
// Outside the cache: it is cheap, and it belongs to the other agent's
|
|
1074
1104
|
// clock rather than this session's.
|
|
1075
1105
|
codex: codexSummary(now),
|
|
@@ -2160,6 +2160,133 @@ function criticalOthers(windows, bindingKey, threshold) {
|
|
|
2160
2160
|
);
|
|
2161
2161
|
}
|
|
2162
2162
|
|
|
2163
|
+
// What effort is ACTUALLY running, and how we know.
|
|
2164
|
+
//
|
|
2165
|
+
// This looked like a plugin bug and is not one. Ultracode is not a separate
|
|
2166
|
+
// effort level: Claude Code's own words are "ultracode: xhigh + dynamic
|
|
2167
|
+
// workflow orchestration (this session only)". So a session in ultracode has
|
|
2168
|
+
// effortLevel xhigh in settings.json and writes effort "xhigh" into every
|
|
2169
|
+
// transcript line - both correct, and both unable to say which MODE is on.
|
|
2170
|
+
// "This session only" means it is never written to disk at all.
|
|
2171
|
+
//
|
|
2172
|
+
// So the honest answer is a chain, and every reading says where it came from,
|
|
2173
|
+
// because "xhigh" from a stale settings file and "xhigh" from a live override
|
|
2174
|
+
// deserve different confidence:
|
|
2175
|
+
//
|
|
2176
|
+
// 1. CLAUDE_CODE_EFFORT_LEVEL - Claude Code's own session override. Hooks
|
|
2177
|
+
// inherit the environment, so this is live and authoritative.
|
|
2178
|
+
// 2. An override recorded here by hand, for the one case nothing can see:
|
|
2179
|
+
// /effort ultracode typed in the UI, which touches neither disk nor env.
|
|
2180
|
+
// 3. settings.effortLevel - the persisted default.
|
|
2181
|
+
// 4. The transcript - stamped at session start and never re-stamped.
|
|
2182
|
+
const EFFORT_LEVELS = ['low', 'medium', 'high', 'xhigh', 'ultracode'];
|
|
2183
|
+
|
|
2184
|
+
function effortOverrideFile() {
|
|
2185
|
+
return path.join(configDir(), 'usage-limits-effort.json');
|
|
2186
|
+
}
|
|
2187
|
+
|
|
2188
|
+
function readEffortOverride() {
|
|
2189
|
+
try {
|
|
2190
|
+
const parsed = JSON.parse(fs.readFileSync(effortOverrideFile(), 'utf8'));
|
|
2191
|
+
if (!parsed || !EFFORT_LEVELS.includes(parsed.effort)) return null;
|
|
2192
|
+
// A hand-set override describes one session. Left lying around it would
|
|
2193
|
+
// outlive the session it was true for, so it expires.
|
|
2194
|
+
if (!Number.isFinite(parsed.at) || Date.now() - parsed.at > 12 * 60 * 60 * 1000) return null;
|
|
2195
|
+
return parsed;
|
|
2196
|
+
} catch (err) {
|
|
2197
|
+
return null;
|
|
2198
|
+
}
|
|
2199
|
+
}
|
|
2200
|
+
|
|
2201
|
+
function writeEffortOverride(level, now) {
|
|
2202
|
+
if (level === null) {
|
|
2203
|
+
try {
|
|
2204
|
+
fs.unlinkSync(effortOverrideFile());
|
|
2205
|
+
} catch (err) {
|
|
2206
|
+
// Already gone is the outcome asked for.
|
|
2207
|
+
}
|
|
2208
|
+
return { ok: true, cleared: true };
|
|
2209
|
+
}
|
|
2210
|
+
if (!EFFORT_LEVELS.includes(level)) return { ok: false, error: 'effort must be one of: ' + EFFORT_LEVELS.join(', ') };
|
|
2211
|
+
try {
|
|
2212
|
+
fs.mkdirSync(configDir(), { recursive: true });
|
|
2213
|
+
writeJsonAtomic(effortOverrideFile(), { effort: level, at: Number.isFinite(now) ? now : Date.now() });
|
|
2214
|
+
return { ok: true, effort: level };
|
|
2215
|
+
} catch (err) {
|
|
2216
|
+
return { ok: false, error: err.message };
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
|
|
2220
|
+
function effortNow(sessionId, env) {
|
|
2221
|
+
const environment = env || process.env;
|
|
2222
|
+
const fromEnv = String(environment.CLAUDE_CODE_EFFORT_LEVEL || '').trim().toLowerCase();
|
|
2223
|
+
if (EFFORT_LEVELS.includes(fromEnv)) return { effort: fromEnv, source: 'environment', live: true };
|
|
2224
|
+
|
|
2225
|
+
const override = readEffortOverride();
|
|
2226
|
+
if (override) return { effort: override.effort, source: 'set by hand', live: true };
|
|
2227
|
+
|
|
2228
|
+
let configured = null;
|
|
2229
|
+
try {
|
|
2230
|
+
configured = collect(Date.now()).settings;
|
|
2231
|
+
} catch (err) {
|
|
2232
|
+
configured = null;
|
|
2233
|
+
}
|
|
2234
|
+
const level = configured && configured.effortLevel;
|
|
2235
|
+
if (EFFORT_LEVELS.includes(level)) return { effort: level, source: 'settings', live: false };
|
|
2236
|
+
|
|
2237
|
+
const seen = liveEffort(sessionId);
|
|
2238
|
+
if (seen && EFFORT_LEVELS.includes(seen.effort)) return { effort: seen.effort, source: 'transcript', live: false };
|
|
2239
|
+
return null;
|
|
2240
|
+
}
|
|
2241
|
+
|
|
2242
|
+
// Is the setting bigger than the work needs?
|
|
2243
|
+
//
|
|
2244
|
+
// Everything else here is budget-triggered: it speaks when a window is filling.
|
|
2245
|
+
// That is the wrong trigger for this question, because a mechanical hour at the
|
|
2246
|
+
// top setting is waste at 10 per cent used exactly as much as at 80 - the only
|
|
2247
|
+
// difference is that at 80 somebody notices. The trigger is the SETTING, not
|
|
2248
|
+
// the window.
|
|
2249
|
+
//
|
|
2250
|
+
// The plugin cannot judge how hard the work is; only the agent reading this
|
|
2251
|
+
// can. So this supplies the half that is measurable - what the current effort
|
|
2252
|
+
// actually costs against the cheapest level with real evidence behind it, on
|
|
2253
|
+
// this account - and the brief asks for the judgement.
|
|
2254
|
+
//
|
|
2255
|
+
// Returns null unless there is a measured alternative. A ratio invented from a
|
|
2256
|
+
// price list would be worse than silence: it would have an agent drop effort on
|
|
2257
|
+
// a hunch and call it evidence.
|
|
2258
|
+
function settingFit(events, effortNow, which) {
|
|
2259
|
+
// Guarded here rather than trusted from the caller: a cache hit has no event
|
|
2260
|
+
// list to hand over, and dominantEffort iterates without checking.
|
|
2261
|
+
const list = Array.isArray(events) ? events : [];
|
|
2262
|
+
const current = effortNow || dominantEffort(list);
|
|
2263
|
+
if (!current) return null;
|
|
2264
|
+
const rates = effortRates(list);
|
|
2265
|
+
const here = rates.find((row) => row.effort === current);
|
|
2266
|
+
if (!here || here.turns < MIN_EFFORT_SAMPLE) return null;
|
|
2267
|
+
const measure = (row) => (Number.isFinite(row.outputPerTurn) && row.outputPerTurn > 0 ? row.outputPerTurn : row.perTurn);
|
|
2268
|
+
const mine = measure(here);
|
|
2269
|
+
if (!Number.isFinite(mine) || mine <= 0) return null;
|
|
2270
|
+
let best = null;
|
|
2271
|
+
for (const row of rates) {
|
|
2272
|
+
if (row.effort === current || row.turns < MIN_EFFORT_SAMPLE) continue;
|
|
2273
|
+
const theirs = measure(row);
|
|
2274
|
+
if (!Number.isFinite(theirs) || theirs <= 0) continue;
|
|
2275
|
+
const multiple = mine / theirs;
|
|
2276
|
+
if (multiple < EFFORT_DEARER_BY) continue;
|
|
2277
|
+
if (!best || multiple > best.multiple) best = { effort: row.effort, multiple, sample: row.turns };
|
|
2278
|
+
}
|
|
2279
|
+
if (!best) return null;
|
|
2280
|
+
return {
|
|
2281
|
+
effort: current,
|
|
2282
|
+
sample: here.turns,
|
|
2283
|
+
cheaper: best.effort,
|
|
2284
|
+
cheaperSample: best.sample,
|
|
2285
|
+
multiple: Number(best.multiple.toFixed(1)),
|
|
2286
|
+
command: levers(which || currentHost()).effort(best.effort),
|
|
2287
|
+
};
|
|
2288
|
+
}
|
|
2289
|
+
|
|
2163
2290
|
// The way out that is not stopping.
|
|
2164
2291
|
//
|
|
2165
2292
|
// This exists because of a session that ended for no reason. The Fable weekly
|
|
@@ -3746,6 +3873,42 @@ async function main(argv) {
|
|
|
3746
3873
|
}
|
|
3747
3874
|
return 0;
|
|
3748
3875
|
}
|
|
3876
|
+
// `--effort ultracode` records what nothing on the machine can see: the
|
|
3877
|
+
// picker's choice is session-only and touches neither disk nor environment.
|
|
3878
|
+
// `--effort clear` forgets it again.
|
|
3879
|
+
const effortAt = argv.indexOf('--effort');
|
|
3880
|
+
if (effortAt !== -1) {
|
|
3881
|
+
const wanted = String(argv[effortAt + 1] || '').trim().toLowerCase();
|
|
3882
|
+
const say = (text) => {
|
|
3883
|
+
process.stdout.write(text + '\n');
|
|
3884
|
+
return 0;
|
|
3885
|
+
};
|
|
3886
|
+
if (!wanted) {
|
|
3887
|
+
const found = effortNow();
|
|
3888
|
+
if (!found) return say('No effort reading available.');
|
|
3889
|
+
const guess = found.source === 'settings' || found.source === 'transcript';
|
|
3890
|
+
return say(
|
|
3891
|
+
'Effort reads as ' + found.effort + ' (' + found.source + ').' +
|
|
3892
|
+
(guess
|
|
3893
|
+
? '\nIf you picked ultracode in the session, nothing on disk says so - Claude Code calls it' +
|
|
3894
|
+
' "xhigh + dynamic workflow orchestration (this session only)".' +
|
|
3895
|
+
' Tell this: usage.js --effort ultracode'
|
|
3896
|
+
: '')
|
|
3897
|
+
);
|
|
3898
|
+
}
|
|
3899
|
+
if (wanted === 'clear' || wanted === 'off') {
|
|
3900
|
+
writeEffortOverride(null);
|
|
3901
|
+
return say('Effort override cleared; back to the environment, then settings.');
|
|
3902
|
+
}
|
|
3903
|
+
const done = writeEffortOverride(wanted, Date.now());
|
|
3904
|
+
return say(
|
|
3905
|
+
done.ok
|
|
3906
|
+
? 'Effort recorded as ' + done.effort +
|
|
3907
|
+
'. It expires after twelve hours, and CLAUDE_CODE_EFFORT_LEVEL still wins.'
|
|
3908
|
+
: done.error
|
|
3909
|
+
);
|
|
3910
|
+
}
|
|
3911
|
+
|
|
3749
3912
|
const recommendAt = argv.indexOf('--recommend');
|
|
3750
3913
|
if (recommendAt !== -1) {
|
|
3751
3914
|
// The turn count is optional: with one the verdict is about that job,
|
|
@@ -3837,6 +4000,12 @@ module.exports = {
|
|
|
3837
4000
|
bindingWindow,
|
|
3838
4001
|
criticalOthers,
|
|
3839
4002
|
escapeRoute,
|
|
4003
|
+
settingFit,
|
|
4004
|
+
effortNow,
|
|
4005
|
+
readEffortOverride,
|
|
4006
|
+
writeEffortOverride,
|
|
4007
|
+
effortOverrideFile,
|
|
4008
|
+
EFFORT_LEVELS,
|
|
3840
4009
|
levers,
|
|
3841
4010
|
betterCalibration,
|
|
3842
4011
|
calibrationForPlan,
|