# Zorua (formerly CodeX Switch / codex-switch; renamed in 0.4.0. Since 0.5.0 the command is `zorua`; the old name `cx` no longer exists. Re-running the installer migrates old installs. Environment variables are ZORUA_*; the old CX_CONFIG_DIR / CX_HOME / CX_COLOR are still honoured.) > Parallel multi-account manager for the OpenAI Codex CLI and Claude Code. Each account gets its own CODEX_HOME (Codex) or CLAUDE_CONFIG_DIR (Claude Code) directory; a shell function `zorua` picks one per terminal, so several accounts can be used at the same time. Works in zsh, bash and fish. MIT licensed. Repo: https://github.com/szupzj18/zorua Site: https://szupzj18.github.io/zorua/ This file: https://szupzj18.github.io/zorua/llms.txt Full docs: https://github.com/szupzj18/zorua/blob/main/README.md ## What it is (and is not) - One Python 3.8+ program (`zorua_core.py`, standard library only) plus a thin wrapper per shell (`zorua.zsh`, `.bash`, `.fish`). - Optional and separate: `web/` is a local dashboard (Next.js, Node 20+; `npm ci && npm run build && npm start` in `web/`, then http://127.0.0.1:4747, loopback only). It is not installed by `install.sh` and not in the release tarball. It shows accounts, 5h/7d usage, providers and bindings, and manages them by running the `zorua` commands above (add/remove/sign-in, provider add/remove, bind/unbind, and a provider editor built on `provider get` / `put`). Dark theme by default, light follows the system. Cmd/Ctrl+K (or /) opens a command palette and ? lists the single-key shortcuts. A live demo with made-up data (the real dashboard running in the browser, no server) is at https://szupzj18.github.io/zorua/demo/ and embedded on the landing page; build it with `sh web/demo/build.sh`. Zorua itself still has no daemon. - Isolation is done by setting the `CODEX_HOME` / `CLAUDE_CONFIG_DIR` environment variables. There is no global "active account", no daemon and no proxy. The shell also gets `claude` and `codex` functions that pass straight through to the real binary unless a provider is active in that shell. - It manages the Codex CLI and Claude Code (subscription logins). The Codex desktop app and the VS Code extension do not read CODEX_HOME. - Claude Code accounts use Claude Code's own multi-account mechanism: one CLAUDE_CONFIG_DIR per account (settings, history and the login are per directory; on macOS the Keychain entry name is derived from the directory path). - It never modifies anything inside an account's home directory. - The state of one terminal is four environment variables, one slot each: `CODEX_HOME` (Codex account), `CLAUDE_CONFIG_DIR` (Claude Code account), `ZORUA_CODEX_PROVIDER` and `ZORUA_CLAUDE_PROVIDER` (third-party providers). `zorua use` fills one slot and leaves the others alone; `zorua off providers` clears the two provider slots; `zorua off` clears all four. ## Requirements - zsh 5.3+, bash 3.2+ or fish 3+ - python3 3.8+ - The official Codex CLI and/or Claude Code on PATH (needed for sign-in and one-shot runs) ## Install curl -fsSL https://raw.githubusercontent.com/szupzj18/zorua/main/install.sh | sh Pin a version with `ZORUA_REF=v0.7.1` (a release tag; default is main). Each GitHub Release ships zorua-.tar.gz, install.sh and SHA256SUMS. This copies the files to ~/.zorua (override with ZORUA_HOME) and appends a marked `source` block to ~/.zshrc, ~/.bashrc and ~/.config/fish/conf.d/zorua.fish for the shells it finds. It is idempotent. A new terminal is required afterwards. Uninstall: `curl -fsSL https://raw.githubusercontent.com/szupzj18/zorua/main/uninstall.sh | sh -s -- --purge` (drop `--purge` to keep ~/.zorua). Account data is never deleted. ## Commands zorua list accounts (email, plan) and providers (endpoint, masked key, model) zorua usage [-v] same plus live limits (5h/7d windows, reset time); -v = detailed blocks (also: zorua ls -v) --json machine-readable `zorua ls` / `zorua usage`: {version, generated_at, accounts[{name, agent, home, state, email, plan, until, usage{windows, error, age_seconds, relay, shadowed_by[{file, dir}], expired[{window_seconds, reset_ago_seconds}]}}], providers[{name, agent, endpoint, models}], bindings[{name, dir, kind}]}. Never contains tokens or provider keys --expiry also show the Codex subscription end date (or ZORUA_EXPIRY=1); hidden by default because it comes from a cached token and can be stale zorua setup interactive first-run wizard (adopt existing homes, sign in, add accounts, bind a directory) zorua add create account (home ~/.codex-) and run `codex login` --device-auth headless device-code sign-in --no-login register without signing in (then: zorua login ) --home DIR register an existing directory instead of ~/.codex- zorua add --claude create a Claude Code subscription account (home ~/.claude-) and run `claude auth login`; if ~/.config/zorua/claude-settings.json exists it is copied to the new account's settings.json (never overwriting) --home DIR / --no-login also apply; --device-auth does not (Codex only) zorua hook install|remove|status [--dry-run] wrap that account's Claude Code status-line command in a relay that caches rate_limits (5h/7d, claude.ai Pro/Max) to /.zorua-usage.json so `zorua usage` can show them; reads no credentials, original command is preserved, settings.json is backed up. If the account has NO status line, install adds a minimal one and requires --yes (or an interactive confirmation) because Claude Code hides most footer keyboard hints while a status line is configured; remove deletes it again. The installed command uses $HOME-relative paths and falls back to the original command if the relay or python3 is missing; a status line in /.claude/settings{,.local}.json outranks the account's settings.json for sessions started in and hides the relay: `hook status` reports SHADOWED and `hook install --shadows` wraps those files too (`hook remove --shadows` undoes it); `zorua hook status` (no name) lists all Claude accounts and `zorua hook remove --all` restores them all (uninstall.sh --purge does this) zorua login (re)run `codex login` (or `claude auth login`) for one account zorua provider add --base-url URL [--key K | --key-env VAR] [--api-key] [--model ROLE=ID]... [--env VAR=VALUE]... [--force] add a third-party Claude Code provider (Anthropic-compatible). The key is prompted (hidden) if omitted. ROLE is one of default, opus, sonnet, haiku, subagent. --api-key sends the key as ANTHROPIC_API_KEY instead of ANTHROPIC_AUTH_TOKEN zorua provider add --codex --base-url URL --model ID [--wire-api responses] [--key K | --key-env VAR] [--force] add a third-party Codex provider; exactly one --model is required zorua provider ls | show | rm list (AGENT column), show (keys masked), remove zorua provider get [--reveal] the provider as JSON ({agent, env|base_url,key,model,wire_api, models}); keys masked unless --reveal zorua provider put < doc.json replace the provider with that JSON (a masked key keeps the stored one; agent cannot change; previous file kept as providers.json.bak) zorua provider check [--json] reachable and key accepted? GET the model list first; a Claude endpoint without one gets a one-token POST /v1/messages (negligible quota). Result: status ok|warn|fail, http, ms, via, detail (never the key); --json prints it plus checked_at and exits 0, otherwise exit 1 on fail zorua provider models [add [alias] | rm | fetch] a provider keeps a catalog of models (alias -> full id). `fetch` asks the endpoint (Claude: GET /v1/models, Codex: GET /models; answers {"data":[{"id":...}]}) and adds the new ones. `provider add` and `provider import cc-switch` seed the catalog from the models they already know. Aliases are derived from the id (last path segment, [1M]-style suffix dropped); the full id is what is sent zorua use : switch provider and pick a model of it (alias, full id or unique alias prefix); the pick is kept in ZORUA_CLAUDE_MODEL / ZORUA_CODEX_MODEL of that shell and dropped when another provider is selected zorua model [alias | -] list the active provider(s)' models / pick one / clear the pick. The pick replaces the main model only (Claude: ANTHROPIC_MODEL; Codex: the -c model override); a Claude provider's opus/sonnet/haiku mapping is unchanged zorua provider import cc-switch [--dry-run] [--force] [--db PATH] copy cc-switch's custom Claude and Codex providers that carry their own key; reads its database read-only, skips official logins and entries that point at cc-switch's local proxy zorua use set CODEX_HOME (Codex account), CLAUDE_CONFIG_DIR (Claude account) or the provider slot of its agent (ZORUA_CLAUDE_PROVIDER / ZORUA_CODEX_PROVIDER) for THIS shell only; one Claude and one Codex provider can be active together zorua use - | zorua off clear the switch (back to the default account ~/.codex) zorua off providers clear only the providers of this shell, keep the accounts zorua [args] one-shot: run `codex` (or `claude`, for a Claude account) under that account or provider, e.g. zorua work exec "summarize this repo"; for Claude accounts inherited ANTHROPIC_* / CLAUDE_CODE_USE_* / CLAUDE_CODE_OAUTH_TOKEN are removed for the run zorua bind [name] bind the current directory (and subdirectories) to an account; auto-switch on cd zorua unbind [dir] remove a binding zorua binds list bindings zorua rm [--purge] unregister an account (data kept unless --purge or confirmed) zorua prompt print the prompt marker, e.g. [codex:work] or [codex:work:auto] zorua version | zorua help Account and provider names: letters, digits, `-` and `_`; they must not collide with a command name, and are unique across Codex accounts, Claude accounts and providers (a provider belongs to exactly one agent). `default` is the built-in Codex account (~/.codex) and cannot be removed. ## Example output `zorua usage` groups accounts by agent (headings appear only when both agents have accounts). Colors only on a TTY. Codex NAME PLAN 5H 7D RESET ● default pro – ▓░░░░░ 6% 4d9h work promax – ░░░░░░ 0% 7d side team ░░░░░░ 0% ▓▓░░░░ 26% 1d15h Claude Code NAME PLAN 5H 7D RESET alt max ▓▓▓░░░ 42% ▓░░░░░ 7% 4d9h ● this shell ◆ auto-bound directory alt: Claude usage as of 3m ago (from its last session) Providers are listed after the accounts, one table per agent (for example "Providers (Claude Code)" with NAME, ENDPOINT, KEY, MODELS). `●` marks the account or provider active in this shell, `◆` one switched by a directory binding. Codex windows are live (queried now); Claude windows are the last values cached by the status-line relay and show their age. ## Guidance for agents - `zorua` is a shell FUNCTION, not an executable. It exists only in interactive shells that sourced the wrapper. In a non-interactive subprocess (typical for an agent tool call) use one of: - one-shot: `zorua work exec "..."` after sourcing the wrapper, or - set the variable directly, which is all `zorua use` does: `CODEX_HOME=~/.codex-work codex exec "..."`, or - call the core: `python3 ~/.zorua/zorua_core.py ` (read-only commands such as `ls`, `usage`, `binds`, `names`, `prompt` work this way; `use`/`off`/`bind` write shell statements to the file named by $ZORUA_EVAL_FILE, which only the wrapper consumes). - `zorua use` changes only the current shell. It will not persist across separate tool calls. - Providers are applied by the `claude` / `codex` shell functions, which exist only in interactive shells that sourced the wrapper. A tool that spawns `claude` or `codex` itself does not get the provider. For a non-interactive run use the one-shot form `zorua ...`, or `ZORUA_CLAUDE_PROVIDER= python3 ~/.zorua/zorua_core.py launch claude ...` (`launch codex` with `ZORUA_CODEX_PROVIDER` for Codex). - API keys of providers live in ~/.config/zorua/providers.json (mode 0600). `zorua provider ls|show` mask them; never print the file `zorua provider get` masks them too unless `--reveal`; do not run `--reveal` to display a key. The web dashboard sends a key to the page only when its "show keys" or "copy" button is pressed. - `zorua add`, `zorua login` and `zorua setup` open a browser or wait for input (use `--device-auth` or `--no-login` for headless/non-interactive use). `zorua setup` and `zorua rm` prompts take defaults on EOF, so they will not hang on closed stdin. - To list account names non-interactively: `python3 ~/.zorua/zorua_core.py names`. - Never print or upload `auth.json` contents. `zorua` itself only decodes the email, plan and expiry from the id_token locally. ## Files and configuration ~/.config/zorua/accounts.tsv registered accounts: \t ~/.config/zorua/claude-accounts.tsv Claude Code accounts: \t ~/.config/zorua/claude-settings.json optional template copied to a new Claude account's settings.json (for example proxy env); never overwrites an existing file ~/.config/zorua/bindings.tsv directory bindings: \t (longest-prefix match, per tool; accounts and providers) ~/.config/zorua/providers.json Claude Code and Codex providers incl. API keys (mode 0600) ~/.config/zorua/providers.json.bak the previous providers.json, written by `provider put` (mode 0600) ~/.config/zorua/run/.settings.json generated per launch for Claude providers (mode 0600) ~/.codex, ~/.codex-/ per-account Codex homes (auth.json, sessions, config) ~/.zorua/ installed program and wrappers Environment variables: ZORUA_CONFIG_DIR (registry location), ZORUA_HOME (install dir), NO_COLOR (disable colors), ZORUA_COLOR=always (force colors), ZORUA_EXPIRY=1 (show Codex subscription end dates), ZORUA_CLAUDE_PROVIDER / ZORUA_CODEX_PROVIDER (active provider of each agent, set by Zorua), ZORUA_CLAUDE_MODEL / ZORUA_CODEX_MODEL (model alias picked inside it, set by Zorua), ZORUA_AUTO_ACTIVE and the other ZORUA_AUTO_* / ZORUA_PRE_AUTO_* variables (internal binding state). On first run `default` is seeded and any ~/.codex-* directory that already contains an auth.json is registered automatically. ## Behavior worth knowing - Auto-switch on cd: zsh uses the chpwd hook, bash uses PROMPT_COMMAND, fish uses --on-variable PWD. Leaving a bound directory restores the account that was active before entering it. - Prompt marker: zsh shows it in the right prompt automatically; in bash and fish it is exposed as $ZORUA_PROMPT_TEXT. - Output is plain text (no colors) when not a TTY. - `zorua usage` sends each account's own access token to https://chatgpt.com/backend-api/wham/usage (the endpoint Codex itself uses) and nowhere else. If it fails it prints the reason, e.g. "token expired (run codex once to refresh)" or "network error: ... (check proxy)". - The Codex subscription end date comes from the cached sign-in token and can be stale after a renewal, so it is hidden unless you pass `--expiry` (or set ZORUA_EXPIRY=1); the live limits are authoritative. - Providers: Claude Code is started as `claude --settings ` because a plain ANTHROPIC_BASE_URL in the shell loses to the `env` block of ~/.claude/settings.json while --settings outranks it; ~/.claude/settings.json is never written. Variables that settings.json sets and the provider does not (ANTHROPIC_*, CLAUDE_CODE_SUBAGENT_MODEL, ...) are blanked. Codex is started as `codex -c model_provider=... -c model_providers.=... -c model=...` with the key in the ZORUA_CODEX_KEY environment variable; config.toml is never written. Keys do not appear in `ps`. - `zorua provider check ` sends the provider's own key to that provider's endpoint (one GET, or one one-token POST when there is no model list) and nowhere else. The web dashboard keeps the last result of each check in memory until it restarts and forgets it when the provider is saved. - `zorua provider models fetch` sends the provider's own key to that provider's endpoint (one GET) and nowhere else. Directory bindings cover providers but not a model pick: `zorua bind` binds the provider, and a bound directory starts on the provider's default model. - Only provider settings are overridden. Skills, plugins, MCP servers, hooks, permissions, CLAUDE.md / AGENTS.md, reasoning effort and all other settings behave as without a provider (verified against the real claude and codex); unrelated variables in settings.json such as CLAUDE_CODE_MAX_OUTPUT_TOKENS are left as the user set them (override with `provider add --env`). - Claude Code accounts: account and plan come from `claude auth status`. Usage windows come from the status-line relay cache (see `zorua hook`), shown with their age; they only exist after the account has been used once with the relay installed. `zorua use` on a Claude account warns if ANTHROPIC_AUTH_TOKEN, ANTHROPIC_API_KEY, CLAUDE_CODE_OAUTH_TOKEN, CLAUDE_CODE_USE_* or ANTHROPIC_BASE_URL is set, because they outrank or redirect the subscription login. Console sign-ins without an API key are stored outside the config directory and are not isolated. - Not supported: API-key login for the official services, importing a token or auth.json with a single command, a local proxy, failover or per-request cost tracking for providers, Windows.