claude-usage-limits 1.15.0 → 1.16.1
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.
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/package.json +1 -1
- package/skills/usage-limits/scripts/brief.js +9 -1
- package/skills/usage-limits/scripts/feed.js +16 -0
- package/skills/usage-limits/scripts/usage.js +71 -0
- package/skills/usage-limits/scripts/view.js +3 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "usage-limits",
|
|
3
3
|
"displayName": "Usage Limits",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.16.1",
|
|
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.16.1",
|
|
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.16.1",
|
|
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",
|
|
@@ -717,7 +717,15 @@ function briefText(parts) {
|
|
|
717
717
|
// that is a real session: the Fable weekly hit 89 per cent, the line said
|
|
718
718
|
// the budget was nearly gone, and the work ended with the five-hour window
|
|
719
719
|
// at 46 and every other model untouched.
|
|
720
|
-
|
|
720
|
+
// Only a MODEL switch survives to 'gone'. It retires a scoped window
|
|
721
|
+
// outright, so the budget really is still there. Effort does not: a
|
|
722
|
+
// cheaper turn against an exhausted window is still a turn you cannot
|
|
723
|
+
// take, and telling a session at 100 per cent that it is "not out of
|
|
724
|
+
// budget" because medium is cheaper than high would be this plugin
|
|
725
|
+
// producing the exact failure it exists to prevent, in reverse - refusing
|
|
726
|
+
// to stop at the one moment stopping is right.
|
|
727
|
+
escapeText &&
|
|
728
|
+
(parts.pressure === 'tight' || (parts.pressure === 'gone' && escape && escape.kind === 'model'))
|
|
721
729
|
? 'This window is nearly gone, but you are not out of budget and you must ' +
|
|
722
730
|
'not stop as though you were. ' + escapeText + ' Do that, say in one ' +
|
|
723
731
|
'line that you switched and why, and carry on with the whole request at ' +
|
|
@@ -238,10 +238,25 @@ function line(built, options) {
|
|
|
238
238
|
Number.isFinite(built.othersWorking) && built.othersWorking > 0
|
|
239
239
|
? bars.paint('+' + built.othersWorking + ' working', bars.THEME.claude, mode)
|
|
240
240
|
: '';
|
|
241
|
+
// Agents spend the same window and were the one thing on it with no voice.
|
|
242
|
+
// A fan-out can empty half a five-hour window in five minutes while the line
|
|
243
|
+
// shows one session working, so the count goes on the line whenever any are
|
|
244
|
+
// live - and it is dropped first when the terminal is too narrow, because a
|
|
245
|
+
// percentage the user cannot see is worse than a count they cannot see.
|
|
246
|
+
const agents =
|
|
247
|
+
built.agents && built.agents.running > 0
|
|
248
|
+
? bars.paint(
|
|
249
|
+
'+' + built.agents.running + ' agent' + (built.agents.running === 1 ? '' : 's') +
|
|
250
|
+
(built.agents.runs > 1 ? ' (' + built.agents.runs + ' runs)' : ''),
|
|
251
|
+
bars.THEME.claude,
|
|
252
|
+
mode
|
|
253
|
+
)
|
|
254
|
+
: '';
|
|
241
255
|
let text = '';
|
|
242
256
|
for (const attempt of attempts) {
|
|
243
257
|
const parts = built.rows.map((row) => segment(row, attempt.width, attempt.shorter));
|
|
244
258
|
if (others) parts.push(others);
|
|
259
|
+
if (agents && !attempt.shorter) parts.push(agents);
|
|
245
260
|
if (attempt.head) parts.unshift(head);
|
|
246
261
|
text = parts.join(attempt.gap || ' ');
|
|
247
262
|
if (bars.visibleWidth(text) <= columns) return text;
|
|
@@ -445,6 +460,7 @@ async function main(argv) {
|
|
|
445
460
|
.filter((row) => row.state === 'working' && row.sessionId !== mine).length;
|
|
446
461
|
const built = view.build({
|
|
447
462
|
now,
|
|
463
|
+
agents: usage.liveAgents(now),
|
|
448
464
|
utilization: collected.utilization,
|
|
449
465
|
fetchedAtMs: collected.snapshotFetchedAt,
|
|
450
466
|
source: collected.snapshotSource,
|
|
@@ -534,6 +534,75 @@ function subagentTranscripts(dir, since, depth) {
|
|
|
534
534
|
return files;
|
|
535
535
|
}
|
|
536
536
|
|
|
537
|
+
// The agents working right now, and what they have cost.
|
|
538
|
+
//
|
|
539
|
+
// Their spend has always been counted - the scan walks subagents/ and
|
|
540
|
+
// workflows/ and every token lands in the window totals - but nothing ever
|
|
541
|
+
// SHOWED them. So a display could say one session was working while eighteen
|
|
542
|
+
// agents underneath it spent two million tokens, and the only sign was the
|
|
543
|
+
// percentage moving for no visible reason. One workflow in this plugin's own
|
|
544
|
+
// development did exactly that twice in an afternoon.
|
|
545
|
+
//
|
|
546
|
+
// Cheap on purpose: it stats files under the session's own subagent
|
|
547
|
+
// directories and never parses one. The panel redraws every second.
|
|
548
|
+
const AGENT_LIVE_MS = 90 * 1000;
|
|
549
|
+
|
|
550
|
+
// Measured at about 95 ms on a machine with a few hundred agent transcripts.
|
|
551
|
+
// The panel redraws every second, so without this it would spend a tenth of
|
|
552
|
+
// every frame stat-ing files that cannot have changed much.
|
|
553
|
+
let agentMemo = null;
|
|
554
|
+
const AGENT_MEMO_MS = 2000;
|
|
555
|
+
|
|
556
|
+
function liveAgents(now, windowMs) {
|
|
557
|
+
const at = Number.isFinite(now) ? now : Date.now();
|
|
558
|
+
const within = Number.isFinite(windowMs) ? windowMs : AGENT_LIVE_MS;
|
|
559
|
+
if (agentMemo && agentMemo.within === within && at - agentMemo.at < AGENT_MEMO_MS) return agentMemo.value;
|
|
560
|
+
const root = path.join(configDir(), 'projects');
|
|
561
|
+
let projects = [];
|
|
562
|
+
try {
|
|
563
|
+
projects = fs.readdirSync(root, { withFileTypes: true });
|
|
564
|
+
} catch (err) {
|
|
565
|
+
return { running: 0, runs: 0, newestAt: null };
|
|
566
|
+
}
|
|
567
|
+
let running = 0;
|
|
568
|
+
const runs = new Set();
|
|
569
|
+
// projects/<project>/<sessionId>/subagents/[workflows/<run>/]agent-*.jsonl -
|
|
570
|
+
// the session id is a level the first version of this walked straight past,
|
|
571
|
+
// which is why it counted nothing on a machine with a hundred and fifty
|
|
572
|
+
// agent transcripts an hour old.
|
|
573
|
+
for (const project of projects) {
|
|
574
|
+
if (!project.isDirectory()) continue;
|
|
575
|
+
let sessions = [];
|
|
576
|
+
try {
|
|
577
|
+
sessions = fs.readdirSync(path.join(root, project.name), { withFileTypes: true });
|
|
578
|
+
} catch (err) {
|
|
579
|
+
continue;
|
|
580
|
+
}
|
|
581
|
+
for (const session of sessions) {
|
|
582
|
+
if (!session.isDirectory()) continue;
|
|
583
|
+
const dir = path.join(root, project.name, session.name, 'subagents');
|
|
584
|
+
for (const file of subagentTranscripts(dir, at - within, 3)) {
|
|
585
|
+
let stat;
|
|
586
|
+
try {
|
|
587
|
+
stat = fs.statSync(file);
|
|
588
|
+
} catch (err) {
|
|
589
|
+
continue;
|
|
590
|
+
}
|
|
591
|
+
if (at - stat.mtimeMs > within) continue;
|
|
592
|
+
running += 1;
|
|
593
|
+
// .../workflows/<run>/agent-x.jsonl - the run is what a person
|
|
594
|
+
// recognises, so it is counted as well as the agents. An agent that is
|
|
595
|
+
// not in a workflow sits directly in subagents/ and is its own run.
|
|
596
|
+
const parent = path.basename(path.dirname(file));
|
|
597
|
+
runs.add(parent === 'subagents' ? 'agent:' + path.basename(file) : parent);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
const value = { running, runs: runs.size, newestAt: running ? at : null };
|
|
602
|
+
agentMemo = { at, within, value };
|
|
603
|
+
return value;
|
|
604
|
+
}
|
|
605
|
+
|
|
537
606
|
function claudeTranscriptFiles(since) {
|
|
538
607
|
const root = path.join(configDir(), 'projects');
|
|
539
608
|
let dirs = [];
|
|
@@ -3714,6 +3783,8 @@ module.exports = {
|
|
|
3714
3783
|
preferLive,
|
|
3715
3784
|
accountUuid,
|
|
3716
3785
|
subagentTranscripts,
|
|
3786
|
+
liveAgents,
|
|
3787
|
+
AGENT_LIVE_MS,
|
|
3717
3788
|
claudeTranscriptFiles,
|
|
3718
3789
|
readClaudeEvents,
|
|
3719
3790
|
SCAN_VERSION,
|
|
@@ -352,6 +352,9 @@ function build(input) {
|
|
|
352
352
|
rows,
|
|
353
353
|
fable,
|
|
354
354
|
hidden,
|
|
355
|
+
// The agents underneath this session. They spend the same window and had
|
|
356
|
+
// no voice on any display until now.
|
|
357
|
+
agents: opts.agents && Number.isFinite(opts.agents.running) ? opts.agents : { running: 0, runs: 0 },
|
|
355
358
|
model,
|
|
356
359
|
modelLabel: opts.modelName || bars.prettyModel(model),
|
|
357
360
|
effort,
|