Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "usage-limits",
"displayName": "Usage Limits",
"version": "1.41.1",
"version": "1.43.0",
"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.",
"author": {
"name": "Ridelink",
Expand Down
2 changes: 1 addition & 1 deletion .codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "usage-limits",
"version": "1.41.1",
"version": "1.43.0",
"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.",
"author": {
"name": "Ridelink",
Expand Down
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,31 @@ The 5-hour limit is the binding one. At the current pace it runs out in about
The clock time matters more than the countdown. "Resume after 03:00" is a
plan; "4h 12m" is a number you still have to do arithmetic on.

**The cloud-session credit is a third case.** Claude Code can offer a dollar
credit for cloud sessions (claude.ai/code), on top of the plan, and names it at
the wall itself: "While you wait, start a new cloud session with a $250
credit". It is not usage credits - the account this was built on has those off
at the org level and has this - and a cloud session on it was seen still
working with its 5-hour window "rejected". When the account carries the offer
the report says so on a `Cloud credit` line, with the amount read from the
offer, and at the wall adds the way on:

```
Work stops when it does. Nothing carries on into paid credits.
To carry on now instead of waiting, start a cloud session on the $250
cloud-session credit: relay cloud hands it this work as claude --cloud.
Land what exists, write the handoff, and resume after 03:00.
```

`relay cloud` builds the hand-off the wake would deliver and prints the
`claude --cloud` command with it inline; the cloud session clones from GitHub,
so it warns about anything not pushed. On Windows it also warns when the
`claude` on PATH is a `.cmd` shim, because cmd.exe cuts the hand-off at its
first line break even when the command is pasted into PowerShell. Inside a cloud session the report does
not ask for `/usage` - the container keeps no snapshot - and says what the
session has cost so far at list prices instead. What is left of the credit is
not readable anywhere, so it is never claimed.

The skill also requires Claude to say up front when a job will not fit in what
is left, name what it is doing now, what it is leaving, and when the rest can
happen, rather than starting and stopping halfway through an edit.
Expand Down Expand Up @@ -498,6 +523,15 @@ re-checks the meter and hands your continuation back.
/usage-limits:relay permission acceptEdits
```

With `mode resume` the wake resumes the same conversation (`claude --resume`).
`relay fresh on` makes it start a new one instead: `claude` in the project
with the whole hand-off as its first prompt, Remote Control on as
`usage-limits relay <project> (new session)`, and the relay's permission mode -
so a session that has grown long hands its work on rather than carrying all of
it into the next window. It is off by default, `/usage-limits:defer` fires
through the same wake, and in both modes the wake stands down if the old
session is still open and working again.

What changes while it is armed is what Claude is told at the wall. Instead of
"write the handoff and stop", the budget line says the relay has it, that being
cut off now costs the wait rather than the work, and that the continuation is a
Expand Down
32 changes: 29 additions & 3 deletions commands/defer.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,35 @@ Other forms:

The work is saved as a continuation and a real scheduled task is registered
(Windows Task Scheduler, or `at`/launchd elsewhere). At the named time the same
wake script the usage relay uses starts a fresh session in the original
directory and hands it the saved plan. If the launch fails - a machine whose
network is not up yet is the common one - it retries rather than giving up.
wake script the usage relay uses runs, and what it does depends on the relay's
settings (`/usage-limits:relay`). The one-line reply names which of these it
will be:

- `relay mode notify` - the default. It raises a notification with the plan
ready to pick up and **starts nothing**. With `relay fresh on` the
notification names the hand-off file to give a new session.
- `relay mode resume` with `relay fresh off` (the default) - it
resumes **this same conversation** (`claude --resume <session id>`) in a
window, with Remote Control on, and the first prompt points it at the
hand-off file. This needs the session id this command ran under; a deferral
made where none is known has nothing to resume, and the reply says so.
- `relay mode resume` with `relay fresh on` - it starts a **new conversation**:
`claude` in the original directory, with the whole hand-off (this work, and
the relay's thinking, voice and bug-check lines) as its first prompt, Remote
Control on under the name `usage-limits relay <project> (new session)`, and
the relay's `permission` mode and `model`. It needs no session id. On Windows
the hand-off goes inline only through the native `claude.exe`; with only the
npm `claude.cmd` shim, the first prompt points at the hand-off file instead,
because cmd.exe cuts an argument at its first line break.
- `relay show off` - either kind runs headless (`claude -p`, the prompt on
stdin) with no window and no Remote Control; its output is kept in
`relay log --run`.

Either way one terminal: if the original session is still open and working
again when the time comes, the wake stands down and opens nothing. If the
launch fails - a machine whose network is not up yet is the common one - it
retries rather than giving up. Under Codex the thread is resumed whatever
`fresh` says.

If the time is unreadable or ambiguous, the script refuses and says so. It never
picks a reading: a deferral that fires at the wrong hour while nobody is awake
Expand Down
37 changes: 31 additions & 6 deletions commands/relay.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,27 @@ The rest:
when the window opens; a request one second later has been refused before.
- `mode notify` - raise a notification with the continuation ready to open.
This is the default and it starts nothing by itself.
- `mode resume` - at the wake, resume the same conversation in a window you can
see, with Remote Control on, so it is also on claude.ai/code and the phone;
the first prompt points it at the hand-off file. Say `permission acceptEdits`
(or whichever mode you want) as well: a resume does **not** inherit the
session's permission mode, so without one it will sit waiting for an
approval nobody is there to give. `show off` makes it a headless run instead.
- `mode resume` - at the wake, run the CLI itself in a window you can see,
with Remote Control on, so it is also on claude.ai/code and the phone. What
it runs is `fresh`'s choice (below). Say `permission acceptEdits` (or
whichever mode you want) as well: neither a resume nor a new session inherits
the session's permission mode, so without one it will sit waiting for an
approval nobody is there to give. `show off` makes it a headless `claude -p`
run instead, the prompt on stdin, with no window and no Remote Control.
- `fresh off` / `fresh on` - which conversation `mode resume` continues in.
Off, the default: the **same** conversation, `claude --resume <id>`, named
`usage-limits relay <project>` on Remote Control, and its first prompt points
at the hand-off file. On: a **new** conversation, `claude` in the project
directory with the whole hand-off as its first prompt, inline, named
`usage-limits relay <project> (new session)`, with the same permission mode
and model - for a session that has grown long, so the next window starts
clean instead of carrying all of it. The old conversation is left as it was.
On Windows the inline hand-off needs the native `claude.exe`; with only the
npm `claude.cmd` shim the first prompt points at the file instead. Both
modes keep one terminal: if the old session is open and working again at the
wake, the wake stands down; if it is open but quiet for three minutes, the
wake opens its one window anyway. Under `mode notify` neither applies, and
under Codex the thread is resumed either way.
- `model <id>` - the `--model` a resumed run starts with; `model` alone goes
back to the default. It is separate from `/model` in the session, so a wake
pinned to an older model stays on it until this is changed.
Expand All @@ -47,6 +62,16 @@ The rest:
- `note "<text>"` (or `note --file <path>`) - store the continuation. This is
the text that gets delivered, so write it to be acted on: what is done, what
is next in order, which files are mid-change, what to verify first.
- `cloud [--session <id>] [--go] ["<text>"]` - the same hand-off, into a
cloud session now instead of this one after the reset. It prints the exact
`claude --cloud '...'` command with the hand-off inline (a cloud session
cannot read a file here), and warns about uncommitted changes and unpushed
commits, because the cloud session clones from GitHub. On Windows, if the
`claude` there is a `.cmd`/`.bat` shim, it also warns that cmd.exe cuts the
hand-off at its first line break, even from PowerShell, and to run it with
the native `claude.exe`. `--go` starts it,
detached, and names the log file its output goes to. It runs on the account's
cloud-session credit when there is one, so starting it is the user's call.
- `cancel` - drop the wake and remove the scheduled task.
- `log` - the last twenty lines of what the relay actually did.

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "claude-usage-limits",
"version": "1.41.1",
"version": "1.43.0",
"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.",
"keywords": [
"claude",
Expand Down
18 changes: 16 additions & 2 deletions skills/usage-limits/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,15 @@ yourself: Claude Code announces it and asks before drawing on credits, and
repeating that only adds noise. Just factor it into the plan, and take the
user's answer to that prompt as the decision.

**Cloud credit.** A separate line, when the account is offered a credit for
cloud sessions. It is not usage credits and it can be there while they are
off. At the wall it is the way on now rather than after the reset: `relay
cloud` prints the `claude --cloud` command carrying the hand-off, and warns
about anything not pushed, because the cloud session clones from GitHub. Offer
it in one line; it spends the user's credit, so starting it is their call.
Inside a cloud session there is no usage snapshot, so do not send the user to
`/usage`; the report gives this session's spend at list prices instead.

## Opening with the budget

Start every reply with one line saying where the budget stands and whether what
Expand Down Expand Up @@ -734,6 +743,11 @@ them, and do not promise behaviour it does not have:
Control on (named `usage-limits relay <project>` on claude.ai/code and the
phone), and its first prompt points it at the hand-off file. `relay show off`
makes it a headless `claude -p` run instead; the output is kept either way.
`relay fresh on` makes the wake start a **new** conversation instead of
resuming the old one: `claude` in the project with the whole hand-off as its
first prompt, named `usage-limits relay <project> (new session)`. Off, the
default, it resumes the same conversation. `defer` fires through the same
wake, so the same settings decide what a deferral does.
macOS and Linux open the same window in Terminal (unverified there). A saved
continuation counts as work to carry, so a session with a note but no todo
list still arms; and a wake that never reports back is reaped as "lost".
Expand Down Expand Up @@ -919,11 +933,11 @@ stop.
| `scripts/live.js` | The usage reading itself, taken the way Claude Code takes it for `/usage`, kept in `usage-limits-live.json` where `collect()` prefers it when newer than the cache. |
| `scripts/drift.js` | How wrong the reading was: each correction written down against the one it replaced. Run it with no arguments for the median and worst gap measured so far, `--json` for the fields. Answer "how far behind does this plugin actually run" from here rather than from memory. |
| `scripts/view.js`, `scripts/bars.js`, `scripts/activity.js` | The display model, the drawing in Claude's colours, and the working/idle marks the hooks leave for the panel. Not meant to be called by hand. |
| `scripts/relay.js` | The relay: `status`, `on`/`off`, `at N`, `grace N`, `mode notify\|resume`, `permission MODE`, `thinking off\|resume\|always`, `armon threshold\|completion`, `backstop N`, `show on\|off`, `onfailure rearm\|stop`, `rearms N`, `offline N`, `doctor`, `note "<text>"`, `cancel`, `log [--run]`. |
| `scripts/relay.js` | The relay: `status`, `on`/`off`, `at N`, `grace N`, `mode notify\|resume`, `permission MODE`, `thinking off\|resume\|always`, `armon threshold\|completion`, `backstop N`, `show on\|off`, `fresh on\|off`, `onfailure rearm\|stop`, `rearms N`, `offline N`, `doctor`, `note "<text>"`, `cancel`, `log [--run]`. |
| `scripts/net.js` | Can this machine reach the API, and was a failed run the network's fault. Three probes, TLS-interception detection, and the backoff the offline retries use. |
| `scripts/wake.js` | What the scheduler runs after the reset: re-checks the meter, then notifies or resumes. Never called by hand. |
| `scripts/voice.js` | The local writing profile: `show`, `card`, `set "<instruction>"`, `clear`, `off`/`on`, `forget`. |
| `scripts/lowpri.js` | What Claude Code's own features at the wall are readable as: whether this account is provisioned for `/low-priority`, whether a manual session reset grant exists, whether the CLI injects its own wrap-up note here, whether usage credits are available, whether `autoContinueAtUsageLimit` is on, and the record of the user saying they switched low-priority on. Reads only; never writes anything but that one record. |
| `scripts/lowpri.js` | What Claude Code's own features at the wall are readable as: whether this account is provisioned for `/low-priority`, whether a manual session reset grant exists, whether the CLI injects its own wrap-up note here, whether usage credits are available, the cloud-session credit on offer and whether this is a cloud session, whether `autoContinueAtUsageLimit` is on, and the record of the user saying they switched low-priority on. Reads only; never writes anything but that one record. |
| `references/how-it-works.md` | Where the numbers come from and where they are soft. |

## Codex controls
Expand Down
37 changes: 35 additions & 2 deletions skills/usage-limits/scripts/brief.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ function readSaid() {
}
}
function keepSaidFor(key) {
return /#(standing|cachemiss|stale|relaylast|newer)$/.test(key) ? 24 * 60 * 60 * 1000 : 60 * 60 * 1000;
return /#(standing|cachemiss|stale|relaylast|newer|cloud)$/.test(key) ? 24 * 60 * 60 * 1000 : 60 * 60 * 1000;
}

// A plugin update takes effect when Claude Code restarts, so a session that
Expand Down Expand Up @@ -218,6 +218,21 @@ function newerModelFor(sessionId, advice, now) {
writeSaid(all, at);
return advice.text;
}
// The cloud-session credit, said once a session near the wall, by the same
// rule: the offer does not change from prompt to prompt. Keyed on the amount,
// so a different offer is news.
function cloudCreditFor(sessionId, credit, now) {
const amount = lowpri.creditText(credit);
if (!amount) return null;
const at = Number.isFinite(now) ? now : Date.now();
const key = String(sessionId || '_') + '#cloud';
const all = readSaid();
const entry = all[key];
if (entry && entry.seen === amount && Number.isFinite(entry.at) && at - entry.at < keepSaidFor(key)) return null;
all[key] = { at, seen: amount };
writeSaid(all, at);
return amount;
}
// The model the relay resumes with, when one is set: wake.js passes it as --model.
function relayModelNow() {
try {
Expand Down Expand Up @@ -1148,6 +1163,18 @@ function briefText(input) {
' Like /low-priority, only the user can type it; you cannot run a slash command.'
);
}
// The cloud-session credit: the one lever that carries work past the wall
// on an account with usage credits off. Only near the wall, only once a
// session (run() decides), and it is the user's credit, so it is offered,
// never taken.
const cloudSentence =
parts.cloudCredit && (parts.pressure === 'tight' || parts.pressure === 'gone')
? 'A cloud session is the way past this wall now rather than after the reset: the account has a ' +
parts.cloudCredit + ' cloud-session credit on top of the plan, and relay cloud prints the claude --cloud ' +
'command that hands it this work (it clones from GitHub, so push first). Offer it in one line; it spends ' +
"the user's credit, so starting it is their call."
: null;
if (cloudSentence) sentences.push(cloudSentence);
if (parts.session) {
sentences.push(
'This session: ' + parts.session.turns + ' turns, ' +
Expand Down Expand Up @@ -1438,6 +1465,7 @@ function briefText(input) {
sentences[0] + caveat + (parts.tier ? ' ' + parts.tier : '') + (bounded ? ' ' + bounded : '') + adviceText +
(escapeSentence ? ' ' + escapeSentence : '') +
(lowPrioritySentence ? ' ' + lowPrioritySentence : '') +
(cloudSentence ? ' ' + cloudSentence : '') +
(parts.pressure !== 'roomy' && relaySentences.length ? ' ' + relaySentences.join(' ') : '') +
'\n' + instruction + directive
);
Expand Down Expand Up @@ -1595,6 +1623,9 @@ function wallFeatures(now, binding, windows, hostName) {
out.lowPriority = info;
out.hostWrapsUp = lowpri.wrapUp(account).hostWrapsUp;
out.autoContinue = info.autoContinue;
// Only where it is the way past the wall: usage credits not on, and not
// already inside a cloud session, which is running on it.
out.cloudCredit = info.cloudCredit && !info.inCloud && info.credits.available !== true ? info.cloudCredit : null;
if (info.state === 'acknowledged' && binding && binding.key === 'five_hour') {
const weekly = lowpri.weeklyOf(windows);
if (weekly && Number.isFinite(weekly.percentUsed) && !weekly.stale && Number.isFinite(weekly.resetsAt)) {
Expand Down Expand Up @@ -1950,6 +1981,7 @@ async function run(now, hookInput, opts) {
if (offering) mode.adviceOffer(advice.id, sessionId, now);

const pressureNow = pressure(binding, now, config, Number.isFinite(yourTurnsLeft) ? yourTurnsLeft : turnsLeftNow);
const cloudCredit = pressureNow === 'tight' || pressureNow === 'gone' ? cloudCreditFor(sessionId, wall.cloudCredit, now) : null;
// Fast mode changes what the window's figures mean, so a toggle is a change
// worth saying even when nothing else has moved.
const fastMode = fastModeFor(sessionId);
Expand Down Expand Up @@ -2045,6 +2077,7 @@ async function run(now, hookInput, opts) {
lowPriority: wall.lowPriority,
hostWrapsUp: wall.hostWrapsUp,
autoContinue: wall.autoContinue || null,
cloudCredit,
// The turn count that matters for this session is its share of a shared
// budget, not the whole window's. Escalating on the whole window meant a
// count that looked comfortable while the part actually available here was
Expand Down Expand Up @@ -2099,7 +2132,7 @@ function withBugcheck(text) {
return text;
}

module.exports = { wallFeatures, sweepDebris, withBugcheck, sayOnce, shapeOf, saidFile, REPEAT_MS, staleVersionFor, newerModelFor, relayNewsFor, installedVersion, runningVersion,
module.exports = { wallFeatures, sweepDebris, withBugcheck, sayOnce, shapeOf, saidFile, REPEAT_MS, staleVersionFor, newerModelFor, cloudCreditFor, relayNewsFor, installedVersion, runningVersion,
readSaid, standingSaid, markStanding, standingShortFor, STANDING_SHORT, cacheMissWhyFor, missReason, MISS_RECENT_MS,
DEFAULTS,
HOOK_BUDGET_MS,
Expand Down
Loading
Loading