musingfox/cc-plugins/tree/main/omp-quota
omp-quota
[Experimental] Every omp provider's remaining quota in-session — a /quota band above the prompt, /quota refresh, and a toast when a provider worsens from ok (Claude Mod; needs Claude Code 2.1.287+)
About this mod
omp-quota
Shows every omp provider's remaining quota inside a Claude Code session, as one Claude Mod
(a function-hooks module, hooks/register.ts).
/quota: toggles a compact table above the prompt, one line per provider./quota refresh: drops omp's cache and fetches fresh quota, without a model turn.- Toast: one in-session toast when a provider's status worsens from
ok.
Needs Claude Code 2.1.287 or later, where Claude Mods are on by default. Built and tested against Claude Code 2.1.287.
How omp is run
Each fetch runs omp usage --json with PI_CODING_AGENT_DIR set to $HOME/.omp/agent
and a 30 s limit (a cold fetch across all providers took about 10 s on 2026-09-25). The override matters: Claude Code may pass down a
PI_CODING_AGENT_DIR of its own (pi-dispatch sets one), and omp reading that home answers
with exit 0 and no providers. omp is found on Claude Code's PATH (it usually lives in
~/.bun/bin). With HOME unset, omp is not run, the band reads
Unavailable: HOME is unset, and /quota refresh answers
omp quota refresh failed: HOME is unset.
Quota is fetched once at session start and then every 5 minutes. A failed fetch leaves the
poll running; there is no other retry. A refused /quota registration leaves it running
too. When a reload re-fires session start, the previous poll is cancelled first, so the
cadence never doubles.
Nothing waits on omp. Session start (which Claude Code awaits before the first prompt) starts the fetch without awaiting it, and the module hooks no tool-call or prompt event, so no tool call or prompt is ever held up by a slow omp.
How quota is read
- Remaining share of a limit: omp's
remainingFractionwhen it is a number, else1 − usedFraction, else none; clamped to 0–100%. A provider's share is the lowest of its limits' shares. Shown as a rounded percentage, or—when none is computable. - Status of a provider: the worst status among its limits (
exhausted>warning>ok); limits without one are ignored, and a provider with none has no status. Reports with the same provider name merge into one provider; a report without one is listed as(unnamed). - Worsened provider: one whose status was
okin the previous good fetch and iswarningorexhaustednow. The first good fetch has no previous;warning→exhausted, a provider without a status on either side, and a newly appearing provider do not count. - Failed fetch: omp did not answer (could not start, or still running after 30 s), exited non-zero, printed something that is not a JSON report list, or reported no providers — the last is how omp answers when it reads the wrong home, so it never wipes the display. omp's error output is never shown.
- Account data: only provider names, limit ids and labels, shares, statuses, and reset
times are kept. omp's
metadata(email, account id, endpoint) and each limit'sscopenever reach a toast, the transcript, or the band.
The toast
When a good fetch finds providers that worsened since the previous good fetch (see
Worsened provider above), one in-session toast names them all:
omp quota: openai-codex now warning, anthropic now exhausted. Failed fetches in between
do not reset the comparison. There is no OS notification; a worsening that happens while
you are in another window shows in the band, if it is on, when you return.
Commands
/quotatoggles the band above the prompt on or off and prints nothing in the transcript. Only this command changes the band — a poll or a worsening never does. The choice is kept in the plugin's store (keyband) and read at session start, so the band stays as you left it; with nothing stored, or a store that fails, it starts off. A store that fails on the toggle still flips the band for this session./quota refreshrunsomp usage invalidate, then fetches, both against the same omp home, and answers one line:omp quota refreshedoromp quota refresh failed: <reason>(the band then shows the failure in its notice line). When the invalidate fails, the fetch still runs and the answer ends in(cache not invalidated). It does not change the band and needs no model turn./quota <anything else>answersusage: /quota [refresh]and runs nothing.
The band
The band is the strip directly above the prompt input. While /quota has it on, it shows
a dim notice line while fetching (Fetching omp usage), when no fetch has succeeded
(Unavailable: <reason>), or when the latest fetch failed
(Stale: <reason>; showing data from <age> ago). Then one line per provider that reports
limits, from the least share left to the most, for example:
cursor Monthly 0% 11h 25m exhausted
openai-codex 5 hours 100% 4h 59m 7 days 6% 1d 11h warning
anthropic 5 Hour 86% 1h 44m 7 Day 94% 5d 15h
Each line holds one cell per window (omp's window label, or the limit label when it has
none), soonest reset first: <window> <share> <time to reset>, plus the window's status
only when it is warning or exhausted. A window's share and reset come from its limit
with the least share left (the first one when none has a share), so repeated pools such as
antigravity's shared Claude & GPT limits collapse into one cell. Cells are padded so the
windows line up across providers. A provider's order uses its lowest share across all
limits; providers without a share go last, and one reporting no limits (such as
ollama-cloud) is left out. Time to reset reads Xd Yh, Xh Ym, or Ym, now when due,
and — when omp gives none. Each share is colored by the percentage shown: red for
0–30%, orange for 31–60%, green for 61–100%; — stays uncolored.
Every line is cut at the band's width with an ellipsis rather than wrapped. The band yields
to a survey while one holds it, shares its space with any band another mod draws there (/cal,
for one) instead of hiding it, and redraws after every settled fetch, failed ones
included, so it always shows the latest data the module holds. Collapse it with
ctrl+x ctrl+a (or its [-] mark) without turning it off. It is drawn from Box and Text
only, with the props flexDirection, color, dimColor, and wrap.
Tests
claude plugin test omp-quota
The test kit is hermetic: no filesystem, network, or process; every $ call a test makes
is answered by a stub beneath the plugin.
tests/fixtures/snapshot.ts is a redacted copy of a live omp usage --json taken on
2026-09-18. To refresh it, capture a new snapshot, remove every metadata object, every
scope.projectId and scope.accountId, and resetCredits, then paste it in. The first
test in tests/snapshot.test.ts fails while any account key or any string holding @
remains.
Installation
Check the author's README for the marketplace and plugin name first. Commands may change as the repository evolves.
claude plugin marketplace add musingfox/cc-plugins claude plugin install omp-quota
Original text / README
omp-quota
Shows every omp provider's remaining quota inside a Claude Code session, as one Claude Mod
(a function-hooks module, hooks/register.ts).
/quota: toggles a compact table above the prompt, one line per provider./quota refresh: drops omp's cache and fetches fresh quota, without a model turn.- Toast: one in-session toast when a provider's status worsens from
ok.
Needs Claude Code 2.1.287 or later, where Claude Mods are on by default. Built and tested against Claude Code 2.1.287.
How omp is run
Each fetch runs omp usage --json with PI_CODING_AGENT_DIR set to $HOME/.omp/agent
and a 30 s limit (a cold fetch across all providers took about 10 s on 2026-09-25). The override matters: Claude Code may pass down a
PI_CODING_AGENT_DIR of its own (pi-dispatch sets one), and omp reading that home answers
with exit 0 and no providers. omp is found on Claude Code's PATH (it usually lives in
~/.bun/bin). With HOME unset, omp is not run, the band reads
Unavailable: HOME is unset, and /quota refresh answers
omp quota refresh failed: HOME is unset.
Quota is fetched once at session start and then every 5 minutes. A failed fetch leaves the
poll running; there is no other retry. A refused /quota registration leaves it running
too. When a reload re-fires session start, the previous poll is cancelled first, so the
cadence never doubles.
Nothing waits on omp. Session start (which Claude Code awaits before the first prompt) starts the fetch without awaiting it, and the module hooks no tool-call or prompt event, so no tool call or prompt is ever held up by a slow omp.
How quota is read
- Remaining share of a limit: omp's
remainingFractionwhen it is a number, else1 − usedFraction, else none; clamped to 0–100%. A provider's share is the lowest of its limits' shares. Shown as a rounded percentage, or—when none is computable. - Status of a provider: the worst status among its limits (
exhausted>warning>ok); limits without one are ignored, and a provider with none has no status. Reports with the same provider name merge into one provider; a report without one is listed as(unnamed). - Worsened provider: one whose status was
okin the previous good fetch and iswarningorexhaustednow. The first good fetch has no previous;warning→exhausted, a provider without a status on either side, and a newly appearing provider do not count. - Failed fetch: omp did not answer (could not start, or still running after 30 s), exited non-zero, printed something that is not a JSON report list, or reported no providers — the last is how omp answers when it reads the wrong home, so it never wipes the display. omp's error output is never shown.
- Account data: only provider names, limit ids and labels, shares, statuses, and reset
times are kept. omp's
metadata(email, account id, endpoint) and each limit'sscopenever reach a toast, the transcript, or the band.
The toast
When a good fetch finds providers that worsened since the previous good fetch (see
Worsened provider above), one in-session toast names them all:
omp quota: openai-codex now warning, anthropic now exhausted. Failed fetches in between
do not reset the comparison. There is no OS notification; a worsening that happens while
you are in another window shows in the band, if it is on, when you return.
Commands
/quotatoggles the band above the prompt on or off and prints nothing in the transcript. Only this command changes the band — a poll or a worsening never does. The choice is kept in the plugin's store (keyband) and read at session start, so the band stays as you left it; with nothing stored, or a store that fails, it starts off. A store that fails on the toggle still flips the band for this session./quota refreshrunsomp usage invalidate, then fetches, both against the same omp home, and answers one line:omp quota refreshedoromp quota refresh failed: <reason>(the band then shows the failure in its notice line). When the invalidate fails, the fetch still runs and the answer ends in(cache not invalidated). It does not change the band and needs no model turn./quota <anything else>answersusage: /quota [refresh]and runs nothing.
The band
The band is the strip directly above the prompt input. While /quota has it on, it shows
a dim notice line while fetching (Fetching omp usage), when no fetch has succeeded
(Unavailable: <reason>), or when the latest fetch failed
(Stale: <reason>; showing data from <age> ago). Then one line per provider that reports
limits, from the least share left to the most, for example:
cursor Monthly 0% 11h 25m exhausted
openai-codex 5 hours 100% 4h 59m 7 days 6% 1d 11h warning
anthropic 5 Hour 86% 1h 44m 7 Day 94% 5d 15h
Each line holds one cell per window (omp's window label, or the limit label when it has
none), soonest reset first: <window> <share> <time to reset>, plus the window's status
only when it is warning or exhausted. A window's share and reset come from its limit
with the least share left (the first one when none has a share), so repeated pools such as
antigravity's shared Claude & GPT limits collapse into one cell. Cells are padded so the
windows line up across providers. A provider's order uses its lowest share across all
limits; providers without a share go last, and one reporting no limits (such as
ollama-cloud) is left out. Time to reset reads Xd Yh, Xh Ym, or Ym, now when due,
and — when omp gives none. Each share is colored by the percentage shown: red for
0–30%, orange for 31–60%, green for 61–100%; — stays uncolored.
Every line is cut at the band's width with an ellipsis rather than wrapped. The band yields
to a survey while one holds it, shares its space with any band another mod draws there (/cal,
for one) instead of hiding it, and redraws after every settled fetch, failed ones
included, so it always shows the latest data the module holds. Collapse it with
ctrl+x ctrl+a (or its [-] mark) without turning it off. It is drawn from Box and Text
only, with the props flexDirection, color, dimColor, and wrap.
Tests
claude plugin test omp-quota
The test kit is hermetic: no filesystem, network, or process; every $ call a test makes
is answered by a stub beneath the plugin.
tests/fixtures/snapshot.ts is a redacted copy of a live omp usage --json taken on
2026-09-18. To refresh it, capture a new snapshot, remove every metadata object, every
scope.projectId and scope.accountId, and resetCredits, then paste it in. The first
test in tests/snapshot.test.ts fails while any account key or any string holding @
remains.
