cux

module
v0.3.13 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 12, 2026 License: GPL-3.0

README

Claude Code account switcher for uninterrupted sessions.

latest release   npm version   license   support   docs


cux is a CLI tool for Claude Code that pools multiple Pro/Max accounts behind a single live session. When the active account hits a rate limit, cux switches to a healthy account and continues the same conversation. For proactive threshold swaps, it waits for the current turn to finish first. No logout, no reload, no lost context.

$ cux
cux: rate limit on alice@example.com → swapped to bob@example.com, resuming…
> What number did I tell you to remember?
4729.

Contents

Install

Three install methods. Pick the one that fits your platform — they all install the same cux binary.

Option 1 — npm

Works on Linux, macOS and Windows. Requires Node.js 18 or newer.

npm install -g @inulute/cux

Option 2 — shell installer

Works on Linux, macOS, WSL and Git Bash on Windows.

curl -fsSL https://raw.githubusercontent.com/inulute/cux/main/scripts/install.sh | sh

Option 3 — manual binary

Works everywhere. Useful if you don't want Node.js and can't run shell scripts (e.g. native Windows PowerShell or cmd.exe).

  1. Download the matching artefact from the releases page:
    • cux-linux-amd64, cux-linux-arm64
    • cux-darwin-amd64, cux-darwin-arm64
    • cux-windows-amd64.exe
  2. On Linux/macOS, chmod +x cux-<os>-<arch> and rename to cux.
  3. Move it somewhere on your PATH:
    • Linux/macOS: ~/.local/bin/cux
    • Windows: any directory listed in your Path environment variable.

After install

Run cux setup once. That installs the /switch and /cux:* slash commands plus the four Claude Code hooks. Restart Claude Code afterwards so it picks them up.

What works on which platform

Linux macOS WSL / Git Bash native Windows
Account commands (add, list, switch, status, …) ✅ ✅ ✅ ✅
Credential storage file (0600) Keychain file (0600) Credential Manager
Hooks + /switch and /cux:* slash commands ✅ ✅ ✅ ✅
Auto-resume on swap ✅ ✅ ✅ ✅ †
npm install -g @inulute/cux ✅ ✅ ✅ ✅
curl … | sh shell installer ✅ ✅ ✅ ❌
Manual binary download ✅ ✅ ✅ ✅

† On native Windows the wrapper hard-terminates claude on swap (Go can't send SIGINT cross-process there). The Stop hook still flushes the transcript before the wrapper acts, so the resumed conversation is intact — but if you see anything unexpected, please open an issue.

Quick start

Website: https://cux.inulute.com

cux setup           # install /switch, /cux:* + Claude Code hooks
cux add             # register the currently-logged-in account
claude login        # log into your second account — just log in again, no logout
cux add             # register it
cux                 # launch claude under cux instead of `claude` directly

Don't run claude logout to switch accounts. claude login on its own re-authenticates and replaces the active credentials — that's all cux needs to capture the next account. claude logout clears and revokes the stored token, which invalidates the backup cux keeps for that account (it will show up as EXPRD in cux list). Once both accounts are added, let cux do all switching — it swaps credentials directly and never logs out, so nothing gets revoked.

After cux setup, restart Claude Code so it picks up the newly installed hooks. From then on:

  • /switch from inside a Claude Code session rotates accounts.
  • /switch <slot|email> switches to a specific one.
  • /cux:add, /cux:list, /cux:status, /cux:config, /cux:remove, /cux:switch, /cux:usage-refresh, and /cux:support run account-management commands in-session.
  • A rate-limit response from the API auto-triggers the same flow and does not wait for another Stop hook before reconnecting.

Verify your setup once

A 30-second check that proves end-to-end context preservation:

  1. Send: "Please remember the number 4729."
  2. Wait for the reply.
  3. Send /switch.
  4. After the ~2-second reconnect, ask: "What number did I tell you to remember?"

If the answer is 4729, swap-and-resume is working.

How it works

   user types     ┌────── claude (running, account A) ──────┐
   /switch ──────►│  hooks: UserPromptSubmit, Stop,         │
   or rate-limit  │         SessionStart, PostToolUseFailure│
   ───────────────┴──┬──────────────────────────────────────┘
                     │ writes signal files
                     │ runtime/signals/{wrapperPID}-{name}
                     ▼
             ┌──────────────────────────────────────┐
             │  cux wrapper                         │  polls signals
             │   on rate-limit OR threshold OR      │  every 100 ms
             │   /switch:                           │
             │     rate-limit/manual: exit now      │  avoids hard-limit stall
             │     threshold: wait for Stop signal  │  guarantees flush
             │     ask claude to exit cleanly       │
             │     swap creds (transactional)       │
             │     append history.Entry             │
             │     relaunch claude <orig. flags>    │
             │       --resume <id> [auto_message]   │
             └──────────────────────────────────────┘

cux writes its hooks into ~/.claude/settings.json by signature, so it never modifies entries owned by other tools and cux uninstall-hooks removes only its own. Every cux-owned file goes through atomic writes (tmp + fsync + rename) and state mutations are serialised with file locks (flock / LockFileEx).

Daily usage

cux                          # launch claude under the wrapper
cux list                     # accounts with 5h / 7d utilisation
cux list --refresh           # refresh usage before listing
cux status                   # current login + cux state
cux switch <slot|email>      # manual swap (no auto-resume)
cux remove <slot|email>      # forget an account
cux history                  # recent swaps with reasons
cux sessions                 # running sessions, plus recently ended ones and how to resume them
cux attach [pid]             # attach to a running session — watch and control it
cux usage refresh            # poll all account usage
cux config show              # current settings
cux config edit              # interactive settings editor
cux upgrade                  # update cux (npm or installer; auto-detects)

From inside a session started with cux:

/switch                      # rotate per the configured strategy
/switch 2                    # by slot number
/switch alt@example.com      # by email
/cux:switch 2                # same switch flow under the /cux namespace
/cux:add                     # add/refresh the current login
/cux:list --refresh          # list accounts from inside Claude Code
/cux:status                  # show live login + cux state
/cux:support                 # show support URL
/cux:config show             # show cux configuration
/cux:remove 2                # remove an account
/cux:usage-refresh           # refresh account usage

If Claude is already hard-blocked, /switch is handled by cux's UserPromptSubmit hook before Claude processes the prompt. If you are on an older session that was started before that hook was installed, run this from another terminal:

cux force-switch             # rotate the active cux-wrapped session
cux force-switch 2           # force a specific slot/email

Watch & attach to running sessions

cux sessions lists every running cux session on the machine — the ones you started in other terminals or left running overnight:

$ cux sessions
[73820] review the pull request and tell me if its safe…
    /Users/you/code/app
    seat you@example.com   session 1634fada   running
    up 6h 12m, last change 0m ago

Recently ended:
  fix the flaky integration test on CI…
    /Users/you/code/api  ·  seat you@example.com  ·  ended 2h 4m ago
    cux --resume 8f31c0a2-5d77-4c1e-9b0e-2a61d4e0c8bb

Claude Code shows a session ID only inside its running UI, so a session that ends when you were not looking — a crash, a closed tab, a machine that rebooted — used to take the way back with it. cux keeps a record of each ended session for 30 days, so the resume line is always somewhere other than your scrollback.

cux attach [pid] connects your terminal to one of them, tmux-style — you see its live output and can type into it (Ctrl+C, everything). With no pid it attaches to the only session, or shows a picker when several are running. Detach with Ctrl+\ and the session keeps running. Multiple terminals can attach to the same session at once. Requires a session started with a build that has attach support (the wrapper serves the mirror over a local socket).

Configuration

cux config keys                                      # discover everything
cux config show
cux config set thresholds.five_hour 85
cux config set strategy.kind balanced
cux config set strategy.order alice@x,bob@x         # drain priority
cux config set auto_message ""                      # silent resume
cux config set model_fallback opus,sonnet           # model cap? change model, keep the seat
cux config set update_check.enabled true            # opt in to update checks
Key Default Description
thresholds.five_hour 100 Auto-swap when 5h utilisation hits this %. 100 = reactive only.
thresholds.seven_day 100 Auto-swap when 7d utilisation hits this %. 100 = reactive only.
strategy.kind drain drain / balanced / manual
strategy.order [] Drain mode priority (emails); empty = auto by highest 7d
auto_switch_on_threshold true Master toggle for pre-emptive threshold swap
auto_switch_on_rate_limit true Master toggle for swap on rate-limit hook
model_fallback [] On a model-specific cap ("You've reached your Opus limit"), try these models in order on the same account before rotating to another one. Empty = always rotate the account.
poll_interval_seconds 60 How often a quiet session re-checks its seat against the shared usage cache. Evaluation only — it does not change how often cux calls the usage API.
auto_swap_idle_after_seconds 900 Migrate a session idle this long off an over-threshold account, instead of waiting for you to come back and type. 0 = off. Needs auto_resume.
auto_resume true Pass --resume <id> to the relaunched claude
auto_message Go continue. First user turn after auto-swap; "" = silent
wait_for_reset true When every account is exhausted, sleep until the earliest reset and resume
retry_on_api_error true Relaunch and auto-continue after a non-rate-limit API failure (fibonacci backoff, capped at 2 min)
update_check.enabled false Check GitHub for newer cux releases on startup
update_check.cadence_hours 6 Minimum hours between update checks (cached locally)

Config file: ~/.config/cux/config.json (XDG-aware).

Strategies

  • drain — Use one account until its 7-day cap is near, then move on. Set order for explicit priority, or leave empty to auto-drain the highest-7d account first.
  • balanced — Always pick the account with the lowest 7-day utilisation (tiebreak by lowest 5h).
  • manual — Never swap automatically. /switch and cux switch still work.

Both automatic strategies also prefer accounts whose model-specific weekly windows (Opus/Sonnet, reported on some plans) still have room. A model-capped account is never made ineligible — cux cannot know which model the session will ask for next — it just sorts behind model-clear candidates, so a heavy-Opus session is not swapped onto a seat that would rate-limit its very next call.

Projects

A machine that hosts several codebases often wants them on different seats — one client's work billed to their org, a personal side project kept off the company pool — while related projects share accounts. Projects bind a directory to a subset of seats:

cux project create clientwork --dir ~/code/client
cux project assign clientwork 1 2        # seats by slot, email, or alias
cux project create side --dir ~/code/side
cux project assign side 2 3              # seat 2 is shared between both
cux project list --refresh               # projects + live usage per seat
cux project unassign side 3
cux project stats clientwork --days 7    # tokens & time from Claude Code transcripts
cux project remove side                  # unbind only; accounts untouched

When cux starts, the working directory picks the project (longest, path-boundary-aware match — nested projects work) and every automatic decision draws from that project's seats only: threshold and rate-limit swaps, rotation, and wait_for_reset's availability math.

  • No projects, or an unclaimed directory → the full pool. Existing setups behave exactly as before.
  • A project with no seats assigned yet → the full pool, until you assign some.
  • Explicit targets are never restricted — /switch <seat> and cux switch <seat> go wherever you point them; a human naming a seat outranks the project boundary.
  • Removing an account (cux remove) also removes it from every project pool.

cux project stats reads the session transcripts Claude Code already writes under ~/.claude/projects — cux collects nothing itself — and sums sessions, active time, turns, and input/output/cache tokens for the project's directory tree, optionally windowed with --days N.

Swap history

$ cux history
2026-05-01 14:12:33  alice@x → bob@x  [threshold]
    reason: 7d utilization 95% ≥ threshold 95%
    usage: alice@x 5h:34% 7d:95% → bob@x 5h:8% 7d:30%
    session: 143eec0f-277e-4ce1-95f1-58eb56331874

2026-05-01 13:55:08  bob@x → alice@x  [manual]
    reason: user requested via /switch

cux history -n 5 for the last five, cux history --json to pipe, cux history --clear to wipe. History is capped at 1000 entries.

Data layout

~/.local/share/cux/                  (~/.cux/ on macOS/Windows)
├── state.json                      # accounts, sequence, active slot
├── .lock                           # flock target for state mutations
├── accounts/
│   └── 01-user@example.com/
│       ├── credentials.json        # Linux only; macOS/Win uses keystore
│       └── oauth.json              # the oauthAccount block, raw JSON
└── runtime/
    ├── signals/                    # hook → wrapper signal files
    ├── usage-cache.json            # per-account 5h / 7d snapshot
    ├── update-cache.json           # update-check cadence cache
    └── swap-history.json           # capped at 1000 entries

~/.config/cux/config.json           # XDG_CONFIG_HOME-aware
~/.claude/settings.json             # hooks upserted here
~/.claude/commands/switch.md        # /switch slash command
~/.claude/commands/cux/*.md         # /cux:* account commands

Security

  • Tokens are never logged. Credential blobs are opaque to logging; the helper that extracts a token never surfaces it in any error message.
  • All cux-owned directories and files are 0700 / 0600.
  • The installer refuses to run as root unless inside a container.
  • /switch is gated by CUX_WRAPPED — the slash command refuses to act unless cux is the parent process, so it cannot accidentally signal an unrelated claude.
  • Hook upsert is signature-keyed. cux install-hooks only ever modifies entries whose command field contains the literal string cux or /cux — every other tool's hooks are preserved.
  • cux attach exposes a local control socket. A session's mirror lives at ~/.cux/runtime/attach/<pid>.sock (mode 0600 — your user only). Anything that can read it can control that session's terminal. Third-party tools that bridge this socket beyond your machine (tunnels, remote dashboards) are outside cux's trust boundary — review them before use. cux itself ships no such bridge.

Building from source

git clone https://github.com/inulute/cux
cd cux
go build -o cux ./cmd/cux
./cux help

Requires Go 1.21+.

go test ./... is safe to run next to a real cux setup: the suite redirects HOME/XDG into temp dirs and forces the file credential backend (CUX_CREDS_BACKEND=file), so it never touches your actual state, usage cache, or OS keychain.

License

GPL-3.0-only. Modifying and redistributing cux is welcome; if you do, your changes need to ship under GPL-3.0 too.

Socials

All inulute social channels live at socials.inulute.com — one place for updates, other projects, and how to reach me.


If cux saves you time, you can support development at support.inulute.com.

Directories

Path Synopsis
cmd
cux command
cux — Run multiple Claude Code Pro/Max accounts as one.
cux — Run multiple Claude Code Pro/Max accounts as one.
internal
atomicfile
Package atomicfile writes files in a way that survives crashes and concurrent readers.
Package atomicfile writes files in a way that survives crashes and concurrent readers.
branding
Package branding holds the cux ASCII banner shown at install / setup time.
Package branding holds the cux ASCII banner shown at install / setup time.
claudecfg
Package claudecfg manipulates the oauthAccount block inside Claude Code's global config (~/.claude/.claude.json), without touching any of the surrounding settings (themes, MCP servers, history, etc.).
Package claudecfg manipulates the oauthAccount block inside Claude Code's global config (~/.claude/.claude.json), without touching any of the surrounding settings (themes, MCP servers, history, etc.).
config
Package config persists user preferences for cux at $XDG_CONFIG_HOME/cux/config.json (or $HOME/.config/cux/config.json when XDG_CONFIG_HOME is unset).
Package config persists user preferences for cux at $XDG_CONFIG_HOME/cux/config.json (or $HOME/.config/cux/config.json when XDG_CONFIG_HOME is unset).
creds
Package creds reads and writes OAuth tokens for Claude Code.
Package creds reads and writes OAuth tokens for Claude Code.
history
Package history is the persistent log of every account swap cux performs.
Package history is the persistent log of every account swap cux performs.
hookinstall
Package hookinstall manages cux's entries in Claude Code's per-user settings file (~/.claude/settings.json).
Package hookinstall manages cux's entries in Claude Code's per-user settings file (~/.claude/settings.json).
hooks
Package hooks implements the bodies of `cux hook stop`, `cux hook session-start`, and `cux hook rate-limit`.
Package hooks implements the bodies of `cux hook stop`, `cux hook session-start`, and `cux hook rate-limit`.
lockfile
Package lockfile provides an advisory exclusive lock keyed to a file path.
Package lockfile provides an advisory exclusive lock keyed to a file path.
monitor
Package monitor stitches store, creds, and usage together to keep the on-disk usage cache fresh.
Package monitor stitches store, creds, and usage together to keep the on-disk usage cache fresh.
paths
Package paths centralises every on-disk location cux reads or writes, so the rest of the codebase never builds a path string by hand.
Package paths centralises every on-disk location cux reads or writes, so the rest of the codebase never builds a path string by hand.
ptyhost
The attach socket's framed protocol, shared by the Unix (ptyhost.go) and Windows (ptyhost_windows.go) hosts and by `cux attach` / bridges:
The attach socket's framed protocol, shared by the Unix (ptyhost.go) and Windows (ptyhost_windows.go) hosts and by `cux attach` / bridges:
registry
Package registry is the wrapper's heartbeat: every running cux wrapper self-reports what it is doing into one small JSON file under the runtime directory.
Package registry is the wrapper's heartbeat: every running cux wrapper self-reports what it is doing into one small JSON file under the runtime directory.
session
Package session captures the running Claude Code session ID so the wrapper can call `claude --resume <id>` after an account swap.
Package session captures the running Claude Code session ID so the wrapper can call `claude --resume <id>` after an account swap.
signals
Package signals is the file-based message bus between Claude Code's hooks (writers) and the cux wrapper (reader).
Package signals is the file-based message bus between Claude Code's hooks (writers) and the cux wrapper (reader).
store
Package store owns cux's state file (state.json) and the per-account oauthAccount-block backups.
Package store owns cux's state file (state.json) and the per-account oauthAccount-block backups.
strategy
Package strategy decides which managed account to swap to when the wrapper has determined a swap is needed.
Package strategy decides which managed account to swap to when the wrapper has determined a swap is needed.
supportnotice
Package supportnotice throttles cux's support line: nothing for the first week after install, then at most once every 30 days.
Package supportnotice throttles cux's support line: nothing for the first week after install, then at most once every 30 days.
switcher
Package switcher orchestrates the high-level operations on top of store, creds, and claudecfg: adding the currently-logged-in account, switching the active account, removing one.
Package switcher orchestrates the high-level operations on top of store, creds, and claudecfg: adding the currently-logged-in account, switching the active account, removing one.
transcripts
Package transcripts derives per-project usage statistics from the session transcripts Claude Code already writes under ~/.claude/projects.
Package transcripts derives per-project usage statistics from the session transcripts Claude Code already writes under ~/.claude/projects.
updater
Package updater is the opt-in update-check path.
Package updater is the opt-in update-check path.
usage
Package usage talks to the Anthropic usage API (https://api.anthropic.com/api/oauth/usage) so cux can show users how much of their 5-hour and 7-day budgets each managed account has consumed, and so the wrapper can swap accounts before a hard cap is hit.
Package usage talks to the Anthropic usage API (https://api.anthropic.com/api/oauth/usage) so cux can show users how much of their 5-hour and 7-day budgets each managed account has consumed, and so the wrapper can swap accounts before a hard cap is hit.
wrapper
Package wrapper runs `claude` as a child process and orchestrates the v0.2 hook-driven swap flow:
Package wrapper runs `claude` as a child process and orchestrates the v0.2 hook-driven swap flow:

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL