npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

kyora-switch

v0.2.0

Published

Hot-swap Claude Code, Codex, OpenCode, Muse and Grok Build logins between accounts, with live quota for every stored account

Readme

kyora switch

Hot-swap Claude Code, Codex, OpenCode, Muse and Grok Build logins. Save the account you are logged into as a named slot, then load it back later: no logging out, no re-auth, no browser round trip.

claude                        # /login as account 1
kyora-switch claude save work

claude                        # /login as account 2
kyora-switch claude save private

kyora-switch claude load work        # back to account 1
kyora-switch claude load private     # and back again

Codex works the same way, with its own slots:

codex login
kyora-switch codex save work
kyora-switch codex load private

So does OpenCode, for its subscription logins: OpenCode Go and its pay-as-you-go sibling Zen:

opencode auth login           # pick OpenCode Go, paste the key
kyora-switch opencode save work
kyora-switch opencode load private

Only the Go and Zen entries move. Everything else opencode is logged into: OpenAI, Z.AI, xAI: stays exactly as it is.

Muse works the same way again:

muse login
kyora-switch muse save work
kyora-switch muse load private

One exception: META_API_KEY always beats the account login in Muse, so while it is set, Muse runs on that key whatever slot is loaded. save, load, clear, status and doctor say so when it is set; unset it to use slots. kyora-switch muse usage reads each slot's five-hour and weekly windows from dev.meta.ai for the login's team. A login in several teams needs its team set once per slot, kyora-switch muse team work <teamId> (the id dev.meta.ai shows in ?team_id=; unset clears it), which is kept in the slot's slot.json, never in Muse's own files. Where dev.meta.ai does not take the CLI login, or the slot is an API key, usage reads a web session instead, or says to log one in. When auth.json and the macOS keychain hold different Muse logins, or the keychain holds one kyora-switch cannot read, kyora-switch follows the keychain, as Muse does on macOS, and save, load, clear and status say so.

And Grok Build (grok):

grok login
kyora-switch grok save work
kyora-switch grok load private

The account login in auth.json beats XAI_API_KEY (and the older GROK_CODE_XAI_API_KEY) in grok, so a loaded slot wins over an exported key; only GROK_AUTH, an inline login, beats every slot, and save, load, clear, status and doctor say so while it is set.

Install

Requires Bun on your PATH. npx is not supported.

bun add -g kyora-switch
kyora-switch status

Or run it without installing anything:

bunx kyora-switch status

The kyora CLI from @kyora-sh/vms runs it too: kyora switch … takes the same commands and flags.

Supports macOS and Linux. Windows is untested.

On macOS, Claude credentials are read from the keychain first, falling back to the file when the keychain item is absent. Loading a slot verifies the keychain write and updates the credentials file too.

To run from a checkout, add a symlink on your PATH:

ln -s "$PWD/packages/switch/cli/src/index.ts" ~/.local/bin/kyora-switch

Or run bun packages/switch/cli/src/index.ts directly.

Commands

Every provider takes the same verbs, under kyora-switch claude …, kyora-switch codex …, kyora-switch opencode …, kyora-switch muse … or kyora-switch grok …:

| command | what it does | | --- | --- | | save <slot> | store the account that provider is logged into right now | | load <slot> | log that provider back into a stored account | | list | slots for that provider, with the live one marked | | usage | how much quota each stored account has left | | tokens | token totals for each stored account (below) | | sync | copy the live login back into its slot | | clear | sign out locally, without revoking the account | | rm <slot> | delete a slot | | rename <old> <new> | rename a slot | | web-login <slot>, web-logout <slot> | keep or forget a browser session for the slot (below); Muse only so far |

A few commands span every provider:

| command | what it does | | --- | --- | | status | which account each CLI is logged into, and which slot it came from | | usage | remaining quota across every stored account, every provider | | tokens | token totals across every stored account, every provider | | doctor | where each CLI keeps its auth on this machine, and what it reads back |

If one provider's login cannot be read, status, usage, refresh and sync show an unreadable row for that provider, still show every other provider, and exit with code 1. With --json, that provider's entry is an error object instead.

Options: --json for list, status, usage and tokens, --no-renew for usage, --local and --detailed for tokens, -y to skip the rm confirmation, --force to load or save a slot kyora cloud owns (below).

Slots are namespaced per provider, so claude/work, codex/work, opencode/work, muse/work and grok/work are independent: you can save one without touching the others.

Which account has room left

usage shows remaining quota for each saved account:

$ kyora-switch usage
claude: Claude Code
* work     [email protected] · Acme · max
           38% left   session 50% used, resets in 3h 33m · weekly 32% used, resets in 4d · weekly Fable 62% used, resets in 4d
  private  [email protected] · max
           9% left    session 91% used, resets in 38m · weekly 44% used, resets in 3d

codex: Codex
* work     [email protected] · pro
           26% left   7d 74% used, resets in 6d · GPT-5.3-Codex-Spark 5h 0% used, resets in 5h

opencode: OpenCode
* work     key …a1b2 · Go
           37% left   5h 12% used, resets in 3h · weekly 41% used, resets in 4d · monthly 63% used, resets in 16d

* marks the current local login. Remaining quota uses the tightest window, including model-specific limits.

| provider | source | | --- | --- | | Claude Code | api.anthropic.com/api/oauth/usage, the endpoint /usage reads | | Codex | chatgpt.com/backend-api/wham/usage, the endpoint /status reads | | OpenCode Go | opencode.ai/zen/go/v1/usage, authenticated with the Go key itself | | Muse | dev.meta.ai/api/portal/teams/<team>/subscription-quota, the endpoint its usage page reads: the five-hour and weekly windows and the tier, for the team muse team set, else the login's only team | | Grok Build | cli-chat-proxy.grok.com/v1/billing?format=credits, the endpoint /usage reads: the share of included credits used this period |

Claude and Codex usage includes plan and model-specific limits.

If quota is unavailable for a saved login, try refresh:

kyora-switch claude refresh    # renew stale slots, then show usage
kyora-switch usage --refresh   # same, as part of a normal usage read

Refresh updates saved logins for accounts that are not in use locally. usage and refresh also renew the live Claude Code or Grok Build login once its access token runs out (or is inside the margin the CLI renews it at), but only while that CLI is not running: a running CLI renews its own login, and when kyora-switch cannot tell whether it runs, it leaves the login alone. usage --no-renew only reads. Expired or revoked logins require signing in again. Codex slots generally last around ten days. OpenCode Go keys do not expire; Zen has no quota windows.

Web sessions

Some usage pages take only a browser session, not the CLI's login. For those, a slot can keep one:

kyora-switch muse web-login work    # log in by hand in a throwaway browser window
kyora-switch muse usage             # reads work's windows with that session
kyora-switch muse web-logout work   # forget it

web-login opens Google Chrome or Chromium (else Playwright's Chromium, else Edge or Brave; KYORA_SWITCH_BROWSER picks one) on a fresh, temporary profile, never your own. You log in there yourself; kyora-switch never types into the page. Once the site answers as logged in, it reads that site's cookies over the DevTools protocol (on a pipe to the browser, never a network port), keeps only the ones the site's own requests carry, closes the browser and deletes the profile. Profiles left behind by a web-login that was killed are deleted by the next one. Firefox cannot hand the session back, so it is not used.

The cookies are kept per slot: in the macOS keychain (kyora-switch.<provider>-web.<slot>), elsewhere, or when it is too long for the keychain, in ~/.kyora/switch/web-sessions/<provider>/<slot>.json (0600). They are never printed, never passed on a command line, and never written into the provider's own files or the slot directory, so save, sync and kyora session creds sync never touch them. rename moves a slot's session, rm deletes it, and saving another account over the slot drops it. A session lasts as long as the site keeps it; once it lapses, usage says web session expired and names the web-login to run.

Usage tries the slot's account login first and the web session only when the site refuses it, or when the slot is an API key.

Token totals

tokens shows the overall total, then one line per tool with its input, output, cache read and cache write. --detailed adds the split by saved slot:

kyora-switch tokens
kyora-switch claude tokens
kyora-switch tokens --detailed
kyora-switch tokens --json
kyora-switch tokens --local     # count only records on this machine, including Codex

| provider | source | | --- | --- | | Claude Code | Assistant message usage in $CLAUDE_CONFIG_DIR/projects/**/*.jsonl (default ~/.claude) | | Codex | GET https://chatgpt.com/backend-api/wham/profiles/me, stats.lifetime_tokens, using each slot's bearer token and ChatGPT account ID | | Codex with --local | $CODEX_HOME/sessions/**/*.jsonl and archived_sessions/**/*.jsonl (default ~/.codex), completion usage records or older cumulative token counters | | OpenCode Go and Zen | $XDG_DATA_HOME/opencode/opencode.db (session_message under session_v2, and V1 message rows of sessions not migrated yet), or legacy storage/message/**/*.json when the database is absent (default ~/.local/share) | | Muse | $XDG_DATA_HOME/muse/sessions/**/session.jsonl (default ~/.local/share/muse/sessions), model_completed usage records | | Grok Build | $GROK_HOME/sessions/**/usage.json (default ~/.grok/sessions), one record per turn; a fork's copied turns count once and subagent sessions count through their parent |

Local rows are labelled counted on this machine. They cover retained logs, including subagents, across projects. OpenCode messages for other providers are excluded. Input excludes cache reads and writes; output includes reasoning where reported. Codex's account endpoint supplies only a total, so its rows show just that (the parts are null in JSON). Slots of the same login count once: the same ChatGPT account and user, by the ids in the slot's tokens, whatever emails they show. Two users of one workspace count apart. Slots whose tokens name no user can't be told apart within their workspace, so they count once and the total is partial. Unavailable accounts stay unknown, say why (no ChatGPT login in the slot, an expired stored token, or no answer), and the combined result is labelled partial. The overall figure combines the stated coverage of each provider; it is not a complete lifetime bill across machines. --local makes all three use local coverage.

Local usage is assigned to the slot active when each message was recorded. Earlier records, removed slots and signed-out periods appear as unattributed. Manual logins, overlapping CLI processes and sessions still using an old login can make attribution inaccurate. OpenCode Go and Zen usage is assigned only when the saved slot still holds the subscription key used at the time.

Claude Code logs a reply once per content block, each line with the whole reply's usage; tokens counts the reply once. Claude Code's own /stats (stats-cache.json) counts every line and keeps the days whose logs it deleted (cleanupPeriodDays), so it reads higher. tokens shows that figure under Claude's line, with the day its oldest retained log starts.

Older Codex logs may include estimates. Incomplete session or fork history, unreadable files, malformed records and records over 16 MB make totals partial. Running sessions are counted as they stand when read; run kyora-switch tokens again for updated totals. Use kyora-switch tokens --json to inspect coverage and partial results. Delete token-cache-v1.sqlite under KYORA_SWITCH_DIR to force a recount.

Use kyora-switch tokens --private to hide account emails. Cloud-owned slots are read without syncing or refreshing their credentials.

Slots never fall behind the CLI

status, list, usage and load update the slot for the current local login. sync does this explicitly. If several slots match the account, run save <slot> to select one. Updates that replace a different login are backed up under sync-backups/. Other accounts' slots are unchanged.

Slots kyora cloud owns

kyora session creds sync uploads logins to kyora cloud. kyora session creds return --slot <name> returns a login to local use.

While a slot is in use by kyora cloud:

  • Local sync and refresh skip it.
  • load and save require --force, which may sign out a cloud or local session. rename requires returning the slot first.
  • rm deletes only the local slot. Use kyora session creds rm --slot <name> to delete the cloud copy.
  • list and usage show (kyora cloud); JSON includes cloudOwned.

Signing out without losing the account

codex logout and Claude's /logout invalidate the saved login. Use clear to sign out locally while keeping saved slots usable.

kyora-switch codex clear     # remove the local credentials, nothing else
codex login                  # now log in as someone else

clear removes only what is on this machine: ~/.codex/auth.json for Codex, for Claude Code the OAuth blob plus the oauthAccount key, for OpenCode the opencode-go and opencode entries of its auth.json, leaving every other provider in that file logged in, and for Muse only the providers.meta entry of its auth.json, leaving its other keys and providers in place, plus its keychain item, and for Grok Build the login under the scope grok signs in with (the stored API key when it has no session), as grok logout does, leaving every other scope in place. Saved slots remain usable. The outgoing login is backed up.

config.toml, project history, settings and MCP config are untouched.

What actually gets swapped

Only the credentials and the account they belong to. Session history, project settings, and MCP config stay where they are.

Claude Code

  • the OAuth blob, from the macOS login keychain (service Claude Code-credentials) or ~/.claude/.credentials.json where there is no keychain
  • oauthAccount in ~/.claude.json, and nothing else in that file: userID and machineID identify the install, not the account, so they stay put

Project history, settings, marketplace configuration and entitlement caches stay unchanged.

Codex

  • ~/.codex/auth.json. config.toml is configuration, not auth, so it stays.

OpenCode

  • the opencode-go (Go) and opencode (Zen) entries of ~/.local/share/opencode/auth.json, or $XDG_DATA_HOME/opencode/auth.json. That one file holds every provider opencode is logged into, so a slot stores only those two entries and a load merges them back in, leaving OpenAI, Z.AI, xAI and the rest where they were.

OpenCode logins are shown as key …a1b2 #3f9c0e1d with their plan. load replaces the saved Go and Zen entries; separate logins for other providers stay unchanged. Outgoing credentials are backed up.

Muse

  • the providers.meta entry of auth.json in the muse config dir ($XDG_CONFIG_HOME/muse, else ~/.config/muse), plus the macOS keychain item (ai.meta.dev.credentials, account meta) as its own slot file where there is one: the two are backends for the same credential and are not assumed to hold the same bytes. A load merges the saved entry back, leaving every other key and provider in auth.json in place. settings.json and trust.json stay untouched.

Muse logins are shown by account email (or full name, which is never used to match a login to a slot) with their subscription tier, or "api key" for a key stored with muse auth set. Muse renews its own login when a slot is loaded, so slots are never refreshed from here, and a slot whose stored access token has expired shows no usage until it is loaded and muse renews it: see the usage table above. While META_API_KEY is set it wins over every slot, and save, load, clear, status and doctor all say so.

Grok Build

  • auth.json in the grok home ($GROK_HOME, else ~/.grok), or the file GROK_AUTH_PATH names. A slot holds only the login grok signs in with and the key grok login --api-key stored; load puts those two back and keeps every other scope, such as an enterprise OIDC login. A slot saved under another sign-in scope than the one grok now uses is refused. Writes go through a symlinked auth.json and hold grok's own auth.json.lock. Its config, sessions and caches stay untouched.

Grok Build logins are shown by account email with their team, or as key …a1b2 #3f9c0e1d for a key stored with grok login --api-key. A team login carries no email, so it is matched to its slot by its user and team ids. A stale stored xAI session is renewed with its refresh token, the same call grok makes, and written straight back to the slot, since xAI refresh tokens are single use.

How hot is "hot"

Restart claude, codex, opencode, muse or grok after loading a slot. load warns when one is running.

Safety

  • every load copies the outgoing login to ~/.kyora/switch/backups/<provider>/<timestamp>/ first, so a switch made before you saved the current account is still recoverable: the last 10 per provider are kept
  • load warns when the login it just replaced was in no slot
  • slots and backups are written 0600 under 0700 directories, and restoring a credentials file tightens its mode to 0600 even if it was laxer
  • files are replaced atomically, so a running CLI never reads a half-written config

Slots are plaintext credentials on disk, exactly like the files they came from. They are as sensitive as the logins themselves: do not sync ~/.kyora/switch anywhere.

Environment

| variable | effect | | --- | --- | | KYORA_SWITCH_DIR | where slots and backups live (default ~/.kyora/switch) | | KYORA_SWITCH_NO_KEYCHAIN=1 | keep Claude credentials in files only, never the keychain | | KYORA_SWITCH_BROWSER | the Chromium-family browser web-login opens | | KYORA_SWITCH_DEBUG=1 | say on stderr why a line of the switch history was not written | | CLAUDE_CONFIG_DIR | respected, same as Claude Code reads it | | CODEX_HOME | respected, same as Codex reads it | | XDG_DATA_HOME | respected, same as opencode reads it | | OPENCODE_AUTH_CONTENT | opencode reads its logins from this instead of auth.json when set; doctor warns, since a load cannot reach it | | XDG_CONFIG_HOME | respected for Muse's config dir, same as muse reads it | | META_API_KEY | beats every Muse slot while set; save, load, clear, status and doctor report it, and its value is never stored | | GROK_HOME, GROK_AUTH_PATH | respected, same as grok reads them | | GROK_AUTH | an inline grok login that beats every slot while set; reported like META_API_KEY, never stored | | XAI_API_KEY, GROK_CODE_XAI_API_KEY | used by grok only when no account login is loaded; reported when the live login is a stored key alone |

Development

bun test
bun run check-types

Tests never touch the real keychain or your real logins: they run against temp directories, and a preload (src/test-setup.ts) disables keychain access and points HOME and every login location at an empty sandbox before any test starts.

bun test also checks that every existing command still prints what the merge base with origin/main prints, byte for byte, and leaves the same files behind (bytes and permissions; the new history files, the token cache and timestamps aside), on the same fixture slots, through saves, loads and every provider's clear. It runs whenever origin/main is there. CI requires it (KYORA_SWITCH_DIFF_REQUIRED=1). Another base:

KYORA_SWITCH_DIFF_BASE=origin/main bun test src/differential.test.ts

License

Apache-2.0, see LICENSE.