oh-my-subscriptions
Track and safely route Claude Code, Codex, Gemini, and Kiro subscription quotas across multiple accounts
Package details
Install oh-my-subscriptions from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:oh-my-subscriptions- Package
oh-my-subscriptions- Version
0.33.1- Published
- Sep 18, 2026
- Downloads
- 947/mo · 947/wk
- Author
- 2060won
- License
- MIT
- Types
- extension
- Size
- 168.3 KB
- Dependencies
- 0 dependencies · 3 peers
Pi manifest JSON
{
"extensions": [
"./extensions/oms.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
oh-my-subscriptions
One terminal for Claude Code, OpenAI Codex, Google Gemini / Antigravity, and Kiro subscriptions.
Track AI coding subscription quotas, manage multiple OAuth accounts, choose models and reasoning effort, route Pi to a safe account, and block accidental paid overage.
Quick start · Pi integration · Providers · Safety · Commands · Contributing
What is oh-my-subscriptions?
oh-my-subscriptions (OMS) is an open-source AI subscription manager and quota-aware router for developers who use more than one coding agent plan. It answers three practical questions before work starts:
- Which Claude, Codex, Gemini, or Kiro account resets next?
- Which account still has safe subscription quota?
- Could this request spill into paid credits or metered overage?
$ oms status
ACCOUNT VENDOR PLAN USED RESETS ON PACE FOR AS OF BILLS?
codex-main codex team 5h 18% 7d 11% (142/5000 cr) 3h 2m full in 41m 0m at risk
codex-alt codex pro 7d 60% 4d 15h 71% by reset 0m no
kiro-1 kiro - 30d >=2% (21/1000 cr, cli only) 16d 7h - 0m no
Why use it?
- One quota dashboard: see usage, reset time, freshness, pace, and billing risk.
- Multiple accounts: isolate work, personal, and client subscriptions without mixing sessions.
- Safe routing: select the next usable account by actual reset time and fail closed.
- Paid-overage protection: block an account before subscription usage becomes a charge.
- Shared OAuth core: connect Claude, Codex, and Google credentials once for supported hosts.
- Pi TUI: choose an account, model, and reasoning effort without editing JSON.
- Local-first: the standalone CLI is one Python standard-library file; the core binds to loopback.
OMS does not create free quota, bypass provider limits, pool strangers' accounts, or resell access. It organizes subscriptions you are authorized to use and keeps routing decisions explicit.
Quick start
Standalone quota CLI
One file, Python 3 standard library only, no dependencies:
curl -fsSL https://raw.githubusercontent.com/goosull/oh-my-subscriptions/main/bin/oms \
-o ~/.local/bin/oms && chmod +x ~/.local/bin/oms
Then add your accounts. oms login installs the provider's CLI if it is missing, signs
you in, names the account and spends one throwaway turn so it reports usage right away:
oms login codex # repeat for a second account on the same provider
oms login kiro my-kiro
oms status
Claude Code plugin
The same repository is also a Claude Code plugin, which adds slash commands and the
status line. It is not required to use oms, and oms does not need Claude Code.
claude plugin marketplace add goosull/oh-my-subscriptions
claude plugin install oms@oh-my-subscriptions
oms install # wires usage recording into ~/.claude/settings.json
oms install sets statusLine in ~/.claude/settings.json, chaining whatever status
line you already had. It is also how a Claude account gets its usage numbers at all —
that figure comes from Claude Code and exists nowhere else.
With the status line installed, every account stays visible while you work — the one you're in with both its windows, the rest with their hottest one:
Opus 5 high ctx ▬▬▬▬▬▬──── 61% left of 1M
claude-main ▬▬▬▬──── 44% ▬▬────── 27%
claude-alt ──────── 0% ▬─────── 8%
codex-main ! ▬▬▬▬▬▬▬▬ 97%
kiro-1 kiro reports no usage
One account per row, one column per window ordered shortest first — so the column a
meter sits in says which window it is, and the labels that used to repeat against every
account are gone. An account with no short window leaves that column empty rather than
sliding into it. The account you are in is the bright one, ! marks an account that
would bill you, and an account with nothing to meter keeps its row and says why instead
of vanishing from the list.
Every row starts with its name rather than with indentation, because Claude Code strips leading whitespace from a status line and a leading marker column would collapse on exactly the rows that lack one.
The first line is the session itself — model, reasoning effort, and context left — so a
model swap or a mid-session /effort change shows up immediately. Every bar fills in the
direction of its number: account meters fill as quota is spent, the context meter drains
as context is used.
Accounts appear in the order oms auto would pick them — the one you're in first, then
its own vendor's alternatives, so the next account to switch to is always the next chip.
Every window is shown and labelled, so a number is never ambiguous about which quota
it describes. The status line cannot scroll, so rather than overrun and get truncated
it wraps at your terminal width (COLUMNS, which Claude Code sets for the script) and
stops after three rows, collapsing any remainder to +N more so a long account list
can never swallow the screen.
Green under 60%, amber above. Red, with a leading !, means money and only money —
a plan with no credit attached can sit at 99% without turning red, because it stops
rather than charges. The bar is drawn with a filled ━ and a lighter ─, so it still
reads on a terminal with no colour.
Installed as a Claude Code plugin, two skills keep it out of the terminal:
/oms:status answers "which account should I use?" mid-session, /oms:priority shows
or changes the pick order, /oms:config sets an account's model or effort,
/oms:rename renames one, and /oms:handoff writes the brief that carries your work
to another account.
Inside Pi
Requires Pi 0.85.1+, Node.js 22+, Python 3, and the OMS CLI from the install section above. Install the stable Pi package and verified provider core:
pi install npm:oh-my-subscriptions
oms core install
The core is a loopback-only sidecar built on CLIProxyAPI v7.3.4. It owns OAuth,
token refresh, credential storage, provider protocols and exact-account execution.
OMS owns account names, priority, quota/billing guards and active/next selection.
Pi, Claude Code and Codex are thin clients of the same core; they do not implement
provider login independently.
Add accounts through the core. Supplying the OMS account name makes login, binding, quota refresh and guard sync one operation:
oms core login codex codex-personal
oms core login claude claude-work
oms core login google google-work
google uses the core's supported Antigravity/Google OAuth path. After browser
authorization, the credential is automatically mapped and synced; no separate sync
command is required. Existing direct oms login profiles remain readable for migration.
Install or change accounts, then restart Pi (or /reload). Enable global automatic
selection with:
oms set pi_auto=yes
Before every turn OMS picks the first fresh, unblocked mapped account, pins its exact core credential, and refuses hidden fallback. Runtime 429/cooldown state overrides stale quota summaries. The exact selection persists across core restarts.
Pi's normal footer stays in place. The right side below provider/model shows:
MCP … ponytail … Current Account: codex-personal (44% used 5d 20h)
Next Account: google-work (0% used 6d 23h)
BLOCKED, STALE, and unknown states are explicit. /oms shows the full table.
All settings live in ~/.oms/config.json (or $OMS_HOME/config.json) and apply across
working folders and supported host adapters. Global pi_auto controls automatic routing.
usage_refresh_seconds controls refresh cadence. The optional usage_display can be
widget, status, or off; usage_widget is its legacy yes/no compatibility alias.
Open the interactive account/model/effort picker inside Pi:
/oms
Navigate with arrows, type to search, press Enter to select, and Escape to cancel. The
same picker is available with Ctrl+Shift+O. It only saves the account's global default
to ~/.oms/config.json; it does not change the provider/model of the current Pi
session. The CLI remains available for scripts:
oms models google-work
oms set google-work model=gemini-3.7-flash-high effort=high
oms config
The same account configuration is then used by Pi selection and terminal launchers. Switching the current Pi session is a separate action:
/oms use # opens the available-account switcher
/oms use google-work # direct session switch
/oms use auto # resume automatic session selection
Or launch a new Pi session with that account's saved model and effort:
oms pi google-work
oms pi gemini # alias when exactly one Gemini account is mapped
Core lifecycle and host commands:
oms core status
oms core start
oms core stop
oms core run codex codex-personal
oms core run claude claude-work
The core binary is downloaded from the matching GitHub release, verified by SHA-256,
and stored privately under ~/.oms. Provider credentials never pass through the Python
CLI or Pi extension.
How quota and safety signals work
ON PACE FOR projects where the current quota window lands if the recent burn rate
continues. Under 100% means subscription quota is likely to expire unused; over 100%
means the account is on course to run out early. OMS waits for two readings far enough
apart before projecting—a single sample is not a trend.
AS OF reports reading age. A stale reading is treated as unknown for automatic routing,
not as a conveniently low number. Claude readings naturally age between Claude Code
sessions because its supported usage signal arrives through the status-line input.
BILLS? distinguishes a hard plan ceiling from paid overflow: no means the provider
stops, at risk means crossing the ceiling can charge, and BLOCKED means OMS refuses
to launch that account unless explicitly forced.
Releases and package publishing
.github/workflows/npm-release.yml publishes on GitHub Release published, or
manual dispatch with an existing v<version> tag. It validates and tests that exact
checkout before publishing with provenance. Prerelease versions use npm's beta tag;
stable versions use latest. Tag and package.json version must match.
The npm package is bootstrapped and its Trusted Publisher is configured as:
- GitHub owner:
goosull - Repository:
oh-my-subscriptions - Workflow filename:
npm-release.yml - Environment: leave empty
- Allowed actions: enable direct
npm publish(not only staged publishing)
The Actions workflow uses OIDC, not an NPM_TOKEN secret. Create a new matching
version/tag/release or manually dispatch an unpublished version. Bootstrap version
0.32.0-beta.2 was published locally; v0.32.0-beta.1 is GitHub-only.
Working on it
git config core.hooksPath .githooks # once: refuses a commit the checks would fail
./bin/oms --selftest # the tool, including that the guard stops a launch
python3 .github/check-docs.py # that nothing documents a command the CLI lacks
python3 .github/check-wired.py # that nothing was built and left unconnected
cd agent && bun run typecheck # the agent, actually typechecked
cd agent && bun test # the loop
cd core && go test -race ./... # shared OAuth/account core
The same checks run in CI. The hook exists because they found a red selftest after it was already pushed, which is a broken branch and a second commit apologising for the first.
Providers
| isolation | where the numbers come from | |
|---|---|---|
| claude | CLAUDE_CONFIG_DIR=<dir> |
rate_limits in the statusLine stdin JSON |
| codex | CODEX_HOME=<dir> |
account/rateLimits/read over the JSON-RPC its app-server speaks |
| kiro | KIRO_HOME=<dir> — settings and sessions only |
credits metered per turn in its session files — a floor |
Kiro is a partial fit and oms says so rather than pretending. KIRO_HOME moves its
settings and sessions, but not its login: the credential is a single machine-wide
keychain entry, and a brand new KIRO_HOME still reports the account already signed in.
So Kiro holds one login at a time — registering a second Kiro account warns that
logging into it signs the other out.
For codex that answers for an account you have not touched in days, and carries the
absolute credit allowance too, so size sets itself. Claude has no equivalent, so its
figure is only as fresh as its last session — which is what AS OF is for.
Kiro has no usage command — user is only login/logout/whoami, profile is for IdC
accounts, and the real balance lives server-side behind kiro-cli dashboard, which just
opens a browser. What it does leave on disk is the cost of each turn, in credits, so
oms sums those for the current billing period. That needs the plan's allowance and
reset day, which only you know:
oms set kiro size=1000 resets_on=1
The result is reported as >=, because it is a floor rather than a balance: it counts
only CLI turns made through this profile, so Kiro IDE and web spend is invisible to it,
and it goes stale if sessions are pruned. Clear size to stop tracking it at all.
A provider you have not installed is still a provider. oms login kiro offers to
install the CLI first and then logs you in:
$ oms login kiro kiro-1
installing kiro-cli
$ brew install --cask kiro-cli
It picks the first install route whose own tool is on your machine — Homebrew before
the curl script, for instance — and echoes the command before running it. You named the
provider, so installing its CLI is the prerequisite of what you asked for rather than a
surprise; --no-install refuses instead.
Naming the account up front logs straight into it, adopting one you already registered.
Leave the name off and oms asks once the login succeeds, so a failed login never leaves
a named half-account behind. oms remove <name> unregisters one, and --purge also
deletes the profile directory, but only ever one oms created itself.
All three environment variables are documented, supported configuration. oms never calls a vendor
API, never reads or writes a credential, and never handles a token. It reads a
documented extension point and files the vendors' own CLIs wrote on your disk, and
it launches the vendors' own CLIs to do the work.
Non-default claude profiles symlink skills/ plugins/ agents/ commands/ settings.json CLAUDE.md output-styles back to ~/.claude, so a second account isn't a bare install.
The catch
Usage is only as fresh as the last time you actually used that account — that's what
the AS OF column is for. There is no supported way to poll a plan you aren't using,
and oms does not try to invent one.
Switching also happens at launch, not mid-session: a running CLI holds its own session.
oms status tells you when it's worth restarting somewhere else.
Which account gets used first
oms auto picks by next actual reset first, then smallest pool. The account whose
current quota period ends soonest goes first, regardless of whether its window is 5 hours,
7 days, or 30 days. If reset times match, the smaller pool goes first; if those also match,
the lower-used account goes first.
$ oms priority
priority: automatic - next reset first, then smallest pool
codex 1. codex-main resets in 3h 2m, 18% used, pool 5000 can bill
codex 2. codex-alt resets in 4d 15h, 60% used cannot bill
The order and what it costs you sit on the same line on purpose: reading one off
oms priority and the other off oms status is how a reserve gets burned while an
expiring window evaporates.
Pool size is whatever you set: oms set codex-alt size=100. It is any number you can
compare across your own accounts — oms cannot read it, because neither vendor reports an
absolute quota, only a percentage. An account with no size set sorts after ones that
have it.
Override the whole thing with an explicit order, oms priority codex-alt codex-main,
and go back with oms priority --auto. Accounts you leave out still get used, just
after every listed one. Priority is only about order — an account that could bill you is
skipped by the guard below no matter where it sits.
When something stops working
Most of this is wiring into other programs — a status line path in Claude Code's settings, a hook in a plugin, a CLI per provider — and each of those is built to fail quietly, because a status line must not throw and a hook must not stand between you and your prompt. Quiet failure needs somewhere loud to ask:
$ oms doctor
ok oms /Users/you/.local/bin/oms
ok on PATH /Users/you/.local/bin/oms
FAIL status line /gone/oms is gone - run `oms install`
ok codex-main 5h 27% 7d 12% (142/5000 cr)
It exits non-zero when anything is unreachable, so it works in a script too.
Before you hit the wall
Claude Code offers to switch accounts once you have already hit the limit, by which
point there is no context budget left to write a handoff with. The plugin's
UserPromptSubmit hook fires earlier: ten points below the block threshold, or whenever
the last few hours' rate puts the ceiling under 45 minutes away, whichever comes first.
A percentage alone is the wrong trigger — 85% that has not moved all day is nothing to act on, and 70% burning 40% an hour is forty minutes from the wall. It names the account with room, says why it spoke, and then stays quiet for an hour unless usage moves another ten points. The warning asks you to act, so repeating it every prompt would eat the context it is warning you about.
Move the percentage trigger with "warn_at": N in ~/.oms/config.json.
Switching without losing the thread
A session cannot move between accounts: its transcript is written with the account that
owns it, and codex and claude share no transcript format. So oms carries a brief rather
than pretending to resume a conversation.
/oms:handoff # Claude writes a brief from the current session
oms run claude-alt --handoff # it becomes the first prompt over there
oms auto codex --handoff # works across vendors too, it is only text
oms handoff - takes a brief on stdin if you would rather write it yourself.
Settings
One vocabulary, translated into each provider's own spelling at launch. oms set is the
only thing that writes a setting; oms config shows them all and what each account will
launch as.
oms set all effort=high # --effort high for claude, -c model_reasoning_effort for codex
oms set codex-main model=gpt-5.3-codex
oms set google-work model=gemini-3.1-pro-low effort=high
oms models google-work # * marks its currently selected core model
oms set codex-main size=5000 # so the pick order knows which pool is smaller
oms set block_at=90 # global
oms set codex-main args= # an empty value clears any key
| per account | |
|---|---|
model |
that provider's own model name |
effort |
low medium high xhigh max, and minimal on codex |
context |
context window in tokens, where the provider takes one |
size |
how big the plan's pool is, for ordering and for percentages |
resets_on |
day of month the plan's credits reset |
paid_overflow |
yes if hitting the ceiling on this account is charged |
block_at |
override the global threshold for this account |
args |
extra provider flags, appended raw, for anything not covered |
name |
what the account is called, same as oms rename |
| global | |
|---|---|
block_at |
percentage at which an account that can bill is refused |
warn_at |
percentage at which the hook offers a handoff |
priority |
explicit pick order, or auto |
A key the provider has no flag for is refused when you set it, rather than stored and
silently dropped at launch — claude takes its context window from the model, so
context is a codex setting only.
The paid-credit guard
An account is blocked when paid overflow is on for it AND usage is at or above
block_at (default 95%).
- A plan with no credit attached can't charge you — it just stops at the ceiling.
omsnever blocks those, so you use the whole thing. - A plan with credit attached can charge you the moment the window fills. That's
what the guard is for. Codex reports its own credit balance; for Claude, set it
yourself with
--paid-overflow yes|no, since that flag is org-side.
oms also strips OPENAI_API_KEY / ANTHROPIC_API_KEY / ANTHROPIC_AUTH_TOKEN from
the launched process, so a stray key in your shell can't silently convert plan usage
into metered usage.
Per-account override: "block_at": N in ~/.oms/config.json. One-off: oms run --force.
What this is not
It does not pool, share, or resell accounts, and it does not lift any account above
its own limits. Every account is one you signed up for, it logs in through the vendor's
own flow, and each plan's limit applies to it exactly as before. oms only tells you
which of your plans has room and keeps you from running one into paid overage.
Any account you register should be one you are entitled to use on your own terms. If an account belongs to an employer or is billed to someone else, their policy governs it, not this tool.
FAQ
Is this a Claude Code account switcher or a Codex account switcher?
It covers those workflows, but its primary job is broader: subscription quota tracking, billing guards, exact-account routing, model defaults, and host integration across Claude Code, Codex CLI, Google Gemini / Antigravity, Pi, and partial Kiro support.
Does OMS upload OAuth tokens or subscription data?
The Python CLI does not read provider token bytes. The optional shared core owns OAuth and
token refresh, stores its files privately under ~/.oms, binds to 127.0.0.1, and exposes
a bearer-authenticated local facade to host adapters.
Can I configure models without switching my current Pi session?
Yes. /oms opens the global configuration picker. /oms use is the separate action that
switches the current Pi session.
Why is a usage reading marked stale?
OMS only treats evidence as current while it is young relative to the quota window. It will not silently route based on an old number that may no longer describe the account.
Commands
oms login <provider> [<name>] install if missing, log in, name, prime it
oms remove <name> [--purge] unregister an account
oms add <name> <provider> [--dir PATH] [--paid-overflow yes|no]
oms install wire usage recording into ~/.claude/settings.json
oms status [--json] what every account has left
oms doctor check the wiring that fails silently
oms run [--force] <name> [args...] launch one account
oms auto <provider> [args...] launch the best account that can't bill you
oms auto <provider> --dry-run say which one, and launch nothing
oms rename <old> <new> rename an account, keeping its usage history
oms priority [<name>... | --auto] show or set the order `oms auto` picks in
oms config every setting, global and per account
oms models [account|filter] list available core models
oms set [<account>] key=value ... change any setting
oms handoff [- | --clear] carry a brief into the next session
oms run <name> --handoff launch with that brief as the first prompt
oms env <name> print the export line for a shell
oms pi <account|gemini> [args...] launch Pi with saved model and effort
oms core [install|start|stop|status] manage the shared provider core
oms core login <provider> [account] authenticate and bind a core account
oms core sync apply quota and billing policy to credentials
Community and support
- Read CONTRIBUTING.md before proposing code or documentation changes.
- Use the issue templates for bugs and feature requests.
- Report vulnerabilities privately through SECURITY.md, not a public issue.
- Participation is governed by the Code of Conduct.
Released under the MIT License.