Switch Claude Code and Codex accounts and billing without re-running login flows.
Ride a subscription until it hits its session/weekly limit, then flip to API-key billing — or bounce between multiple subscription accounts — and every new claude/codex run picks up the change. macOS only, Bun + TypeScript.
TOOL PROFILE KIND PLAN 5H WEEK FABLE RESET IN RESETS
● claude work sub max · me@co.com 12% 40% 88% 3h10m —
claude work api API billing — — — — —
claude personal sub pro · me@gmail — — — — —
● codex work sub team · me@co.com 88% 61% — 42m 2
codex work api API billing — — — — —
The two tools store credentials differently, so hop uses a different mechanism for each — behind one uniform CLI.
- Codex keeps its login in
~/.codex/auth.jsonand rewrites it in place (truncate, no rename).hopturnsauth.jsoninto a symlink to~/.codex/accounts/<name>.json; switching just retargets the link. Because Codex writes through the link, background token refreshes (which rotate the refresh token) land in the active profile automatically — nothing to sync back. - Claude Code keeps its login in the macOS Keychain (
Claude Code-credentials), with account identity cached in~/.claude.json. Switching subscription accounts is a journaled, backup-first swap: it syncs the live (possibly rotated) token back into the outgoing profile, replaces onlyclaudeAiOauthin the keychain blob (preservingmcpOAuth), patches the identity in~/.claude.json, then verifies and commits — rolling back on any mismatch. - Claude API billing is a separate layer: switching to a claude
apiprofile writes anapiKeyHelperinto~/.claude/settings.jsonthat outranks the subscription OAuth. It never touches the Keychain, so flipping to API billing (and back) is instant and safe even mid-session.
hop never calls claude /logout or codex logout (those revoke server-side). The worst case for any single fault is re-logging-in exactly one account.
bun install
bun link # or: alias hop="bun run /path/to/src/cli.ts"
hop completions fish > ~/.config/fish/completions/hop.fishA profile is (tool, name, kind) — the same name can exist as sub and api for each tool, so "work" can have up to 4 variations. Switching to an api profile flips billing automatically (Claude: apiKeyHelper toggle; Codex: symlink to an API-key auth.json); switching to a sub profile swaps the login and clears any API override.
Kind selection:
hop <tool> <profile>
|-- --sub / --api present -> switch to that kind
`-- no kind flag
|-- same tool+profile is active -> toggle sub <-> api
`-- different profile
|-- sub has headroom -> switch to sub
`-- sub is exhausted -> switch to api
after switch
`-- landed on an exhausted sub -> warn
Claude subscriptions carry a separate model-scoped weekly limit for the Fable 5 family, reported in the usage endpoint's newer limits array rather than the legacy top-level windows (which now return null on some plans). The FABLE column shows it; the 5H/WEEK columns fall back to the array's unscoped session/weekly entries when the legacy fields are empty.
hop # status table: active account, usage headroom, and (codex) on-demand
# usage-limit RESET credits — available & unexpired, as CodexBar counts them
hop claude work # on work (sub) and hit the limit? this toggles to work (api)
hop claude work # ...and again to toggle back to the subscription
hop codex other # different profile: defaults to sub (api if that sub is tapped out)
hop claude work --sub # explicit kind always wins
hop - # switch to the previous profile
hop next codex # rotate to the next codex profile
hop add work --tool codex # capture work (sub) from the current codex login
hop add work --tool claude # capture work (sub) from the current claude subscription login
hop add work --tool claude --api # store work (api) alongside work (sub) (key from the keychain, or --key sk-ant-…)
hop add work --tool codex --api # store work (api) alongside work (sub) (key from the live `codex login --api-key`, or --key sk-proj-…)
hop which # active profile per tool
hop rm codex work --api # delete a stored profile snapshot
hop doctor # health checksAdding a new account is a one-time capture (hop can't fabricate a login):
# Codex
codex logout && codex login # log into the new account
hop add personal --tool codex # capture it
# Claude
# run /login inside claude for the new account, then:
hop add personal --tool claude--safe— refuse a Keychain/symlink swap whileclaude/codexis running (default is warn-and-proceed).--json— machine-readable output forstatus/which.NO_COLOR— disable colored output.
- Switching between subscription accounts (or capturing/restoring one) rewrites the Keychain / retargets the symlink. By default
hopwarns ifclaude/codexis running but proceeds; it always backs up first and verifies, so a botched swap is recoverable from~/.config/hop/backups/. - Switching to a Claude subscription profile removes a leftover console/API-key credential (the
Claude Codekeychain item andprimaryApiKeyin~/.claude.json) so it can't shadow the subscription — mirroring what Claude's own/logindoes. The removed key is backed up to~/.config/hop/backups/and can be re-minted from the Console.hop doctorflags the shadow state if both are ever present at once. - Codex's OS-keyring credential store (
cli_auth_credentials_store = keyring|auto) is unsupported;hoprefuses rather than silently no-op. Set it tofileand re-login.
bun test # e2e tests — isolated via CODEX_HOME + a hashed CLAUDE_CONFIG_DIR keychain service; never touches real logins
bun run typecheck