One CLI to swap between multiple Claude Code (Anthropic) and Codex (OpenAI) accounts.
toguru (トグル) is Japanese for "toggle" — and that's exactly what it does: toggle between accounts.
Tired of logging out and back in to switch between your personal and work
Claude/Codex subscriptions? tg saves each signed-in session and lets you flip
between them in a second — from a single tool, on macOS, Linux, and Windows.
It is a thin, safe layer over the credentials your existing CLIs already write:
| Provider | Live credential location |
|---|---|
| Claude Code | macOS Keychain (Claude Code-credentials) · ~/.claude/.credentials.json elsewhere |
| Codex | ~/.codex/auth.json (override the dir with CODEX_HOME) |
Saved accounts live in ~/.toguru/store.json (override with
TOGURU_HOME), written atomically with 0600 permissions.
# with bun
bun add -g toguru-cli
# or npm / pnpm
npm install -g toguru-cliThe command is tg (with toguru as a longer-form alias — they're
identical). tg isn't a standard Unix/Linux command, so it won't clash with
anything already on your PATH.
That's the whole thing — run tg with no arguments and you drop straight
into the interactive hub: every saved account across Claude and Codex, arrow-key
to one to switch / re-authenticate / rename / remove, or log in to a new one.
$ tg
? Accounts — pick one to manage
Claude Code (Anthropic)
❯ you@example.com [active]
Codex (OpenAI)
work@company.com ⚠ re-auth
──────────────
🔑 Log in to a new account
🚪 Exit
No flags to memorize. Everything below is just the same actions as direct commands, for when you want them.
toguru flags each account's sign-in state — read locally from the saved token, no network calls:
| Tag | Meaning |
|---|---|
| (none) | Signed in, token still valid |
⟳ expired |
Access token expired — the provider refreshes it automatically on next use |
⚠ re-auth |
No refresh token — you must log in again (tg <provider> auth <name>) |
tg status spells this out per active account (e.g. "✓ signed in (token
expires in 8 hours)"), and tg switch tells you right after switching if the
session it activated has expired or needs re-authentication.
# 1. Log in AND save in one step (opens your browser):
tg claude auth work # or: tg codex auth work
# 2. Add another account the same way:
tg claude auth personal
# 3. Switch any time:
tg switch claude personal
tg switch claude # interactive picker
tg # …or just run tg and pick
# See where things stand:
tg list
tg statusIf a saved profile's token goes stale, re-authenticate it in place — same name, fresh session:
tg claude auth work # re-runs login and updates the "work" profileAlready logged in through the provider's own CLI? You can still snapshot the
current session without re-authenticating: tg add claude work.
Profiles default to your account email — log in as you@example.com
and the profile is named that (just press Enter at the prompt, or pass your own
name).
| Command | Description |
|---|---|
tg / tg status |
Interactive hub: arrow-key a profile → switch / re-auth / rename / remove (alias: current) |
tg <provider> auth [name] |
Log in (or re-authenticate a profile) and save it — e.g. tg codex auth |
tg auth [provider] [name] |
Same as above, with the provider as an argument |
tg switch [provider] [name] |
Activate a saved account (alias: use) |
tg add [provider] [name] |
Save the current live session as a named account (no re-login) |
tg list [provider] |
List saved accounts (alias: ls) |
tg current [provider] |
Show active account + live-sync status, then open the hub on a TTY (alias: status; use --json for plain output) |
tg sync [provider] |
Capture a token refresh / re-auth from the live session into the active profile |
tg rename [provider] [old] [new] |
Rename a saved account (alias: mv) |
tg remove [provider] [name] |
Forget a saved account (alias: rm) |
tg export [provider] |
Export accounts as JSON |
tg import <file> |
Import accounts from a JSON export |
tg update |
Update to the latest version (auto-detects npm/bun/pnpm/yarn) |
provider is claude or codex. Omit any positional argument and you'll be
prompted for it.
tg add --label <email>— set a custom label;--activateto switch to it immediately;--forceto overwrite.tg list --json/tg current --json— machine-readable output for scripts.tg remove --yes— skip the confirmation prompt.tg export --out accounts.json— write to a file;tg import --overwriteto replace existing entries.tg update --check— only report whether a newer version exists;--pm <npm|bun|pnpm|yarn>to force the package manager.-v, --version,-h, --helpeverywhere.
tg update # checks npm, then updates in place via the manager that installed it
tg update --check # just tell me if there's a newer versionupdate figures out whether toguru was installed with npm, bun, pnpm or yarn
(from where the binary lives) and runs the matching global install. From a source
checkout or npx, it prints the manual command instead.
tg add codex --label me@work.com --activate
tg switch codex # pick interactively
tg list --json | jq '.[0].accounts'
tg export --out backup.json # ⚠️ contains credentials — keep it private
tg import backup.jsonauthdelegates to the provider's own login (claude auth login/codex login), then captures the resulting session and saves it as a profile — all in one command. Re-authenticating an existing profile overwrites it in place (and, for Claude, pre-fills the profile's email on the login page). Note that the account you end up with is whichever one you sign into in the browser.addreads whatever session your provider CLI currently holds and stores a copy under a name you choose. Your live session is left untouched.switchwrites a saved credential back into the provider's live location, making that account active. On macOS, Claude credentials round-trip through the Keychain; everywhere else they are plain files. For Claude, toguru also restores the signed-in identity (oauthAccountin~/.claude.json) so the displayed account, email and org match the active tokens — not just the credentials.currentcompares the live session against what it last activated and warns if they have drifted (e.g. a token was refreshed by the provider).
Say you tg switch claude work, then run claude and it asks you to log in
again. The provider writes fresh credentials to its live location — and tg keeps
up: it treats the live session as the source of truth for the active profile
and copies those updated credentials back into it, so switching away and back
never restores a stale token. (We don't symlink the credential files — the
macOS Keychain can't be symlinked, and the atomic write-temp-then-rename most
CLIs use would break a symlink anyway.)
This reconcile happens automatically whenever tg looks: opening the hub (tg),
tg switch, and tg status. You can also force it with tg sync. If the live
session turns out to be a different account (you logged in elsewhere without
tg switch), tg won't overwrite your saved profile — it tells you to tg add
the new one instead.
Nothing ever calls a remote API. Labels and plan names shown for Codex are
parsed locally from the (unverified) id_token purely for display.
⚠️ Security: saved accounts and exports contain real OAuth tokens. The vault is created with0600permissions; treat exports as secrets.
bun install
bun run dev -- --help # run the CLI in dev
bun run build # produce dist/
bun test # run the test suiteThe CLI is built on a small, typed core you can drive directly:
import { Store, providers } from "toguru-cli";
const store = await Store.load();
const account = store.get("claude", "work");
if (account) {
await providers.claude.writeActive(account.credential);
await store.setActive("claude", "work");
}Exports: Store, providers, getProvider, providerIds, ToguruError,
createProgram, plus all related types.
src/
cli.ts # executable entrypoint (#!/usr/bin/env node)
program.ts # wires commander commands together
index.ts # public library surface
commands/ # one file per command (+ interactive menu, shared prompts)
providers/ # provider abstraction + claude/codex implementations
core/ # paths, keychain, account vault (Store)
utils/ # fs, jwt, logger, errors, strings, process helpers
tests/ # bun:test unit tests