claude-usage-limits 1.15.0 → 1.16.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.16.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.16.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.16.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",
|
|
@@ -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,
|