Skip to main content
MonoSwitch is the provider-namespaced account switcher for AI CLIs. Version 0 ships the claude namespace: it can save the currently logged-in Claude Code account as an encrypted profile, switch the default live login to a saved profile, or start Claude under an isolated CLAUDE_CONFIG_DIR so several terminals can use different accounts at the same time. Published contract: monoswitch 0.1.5, OpenCLIs build 2026-07-18-110041, source commit f07e3578850d792b764b4cd875d9fcc7b2001505. The parser exposes seven claude verbs, a root doctor alias, and root help/version identity aliases.

Quick Start

Use run when parallel terminals should remain on different accounts. Use use only when the machine’s default Claude Code login should change for running and future default-config sessions.

Complete Command Reference

Profile names accepted by add and run are ASCII [A-Za-z0-9_-]+. Auto-generated names use the live email’s local part, replace other characters with hyphens, and add -2, -3, and so on for collisions. Without an email, the fallback is auto-<first-eight-uuid-chars>.

Choose use or run

run refuses to nest when CLAUDE_CONFIG_DIR is already non-empty. It creates config-dirs/<name> even when no Vault profile has that name, strips leading -- separators after the name, and forwards the remaining arguments to claude. Claude Code hashes the raw config path; do not rename a populated profile directory.

The use Transaction Boundary

use is a lock-guarded two-store operation. It is not a crash-atomic transaction across macOS Keychain and JSON files.
  1. Best-effort auto-preserve checks the current live account.
  2. The current token + oauthAccount pair is read before any write.
  3. The target must exist in the encrypted Vault document. If its account UUID already matches the live UUID, the command returns a no-op.
  4. MonoSwitch acquires Claude Code’s ~/.claude.lock and ~/.claude.json.lock. A live lock is retried for up to nine seconds; a lock older than ten seconds is treated as stale and taken over.
  5. Before touching the live stores, the freshest outgoing pair is written to @last-<uuid8> and every non-internal named profile with the same account UUID. This prevents a token that rotated while live from leaving its saved profile frozen at add time.
  6. The target token is written first and the target oauthAccount second. Both are read back; success is reported only when both match.
  7. A write/read-back failure attempts to restore both original values and prints a separate RESTORE WARNING for either failed restoration. The @last-… Vault copy remains the recovery point.
  8. After the live pair verifies, state.json is loaded and updated, then a v2 quota switch marker is appended best-effort when the spool directory exists.
A corrupt or unreadable state.json can make use exit non-zero at step 8 after the live token and identity already changed successfully. Do not blindly repeat a failed switch. Run monoswitch claude current and monoswitch doctor to establish the live account first.
Running Claude sessions adopt a default-login switch on their next API call. Status output between the swap and that call can briefly carry the previous account’s rate-limit view. The v2 switch marker records the intended boundary, but marker absence never fails or rolls back the switch.

Auto-Preservation and Profile State

Only list, current, and use call the automatic preservation helper. add is the explicit snapshot command. run, doctor, and remove do not auto-preserve the default live login. The auto-preserve path first reads the non-secret UUID from oauthAccount. It touches the live token store and Vault only when no metadata profile covers that UUID. Platforms without a verified live token store skip the snapshot; callers warn or continue according to their command contract. state.active is a convenience mirror, not proof of the live account. list and current compare account UUIDs from the live oauthAccount block.

Quota window settling after a switch

A session that still holds the outgoing token can briefly report the old account’s quota under the newly live account’s spool stamp. list has only the current dedup-state snapshot, not Monometer’s observation history, so it uses a narrow deterministic guard: a non-live account’s window remains displayable, while the live account’s window is trusted only when its five-hour reset epoch does not equal another saved account’s epoch. On collision it prints [quota settling…]; once the live account reports its own distinct window, normal 5h … · wk … output resumes. This suppresses a known wrong value; it does not invent or interpolate quota data.

Storage and Credential Custody

Vault secret writes happen before metadata writes. A later state.json failure can therefore leave store drift; doctor reports missing secret/metadata names and recommends re-adding the profile. The Vault executable is resolved from MONOSWITCH_VAULT_BIN, then ~/.openclis/bin/openclis-vault, then PATH. On macOS, MonoSwitch calls Apple-signed /usr/bin/security by absolute path so one Keychain approval is not tied to each MonoSwitch binary cdhash. Writes send normal-size tokens as hex through security -i stdin. An oversized command falls back to argv, where the hex can be briefly visible to a local process inspector. Keychain subprocesses have a ten-second deadline rather than hanging forever on an unseen prompt.

Doctor Contract

doctor is observational, but its result is deliberately not “all warnings are failures.” A successful doctor therefore proves the two hard read boundaries, not that every warning is absent or that a future switch will succeed.

Parser and Exit Contract

  • There is no structured JSON output or MCP schema in 0.1.5.
  • Help is implemented at the root and at bare claude only. Do not probe monoswitch claude add --help, use --help, run --help, or remove --help: --help becomes that verb’s first argument, and mutating paths can act on a profile or directory literally named --help.
  • add, use, and remove consume the first name and ignore later tokens. run alone forwards later tokens to Claude.
  • Unknown root commands and unknown Claude verbs exit non-zero. Missing names for add, use, run, and remove also exit non-zero.
  • list, current, and both doctor routes ignore extra trailing arguments.
  • add and auto-preserve write the Vault before state.json; remove uses the same order. These are not cross-store transactions.
  • A non-zero OpenCLIs Vault read is treated as a fresh empty document by the current client boundary. Run doctor before profile mutation when Vault health or Keychain permission is uncertain.

Platform and Release Proof

The release has no macOS Intel artifact. On macOS, add and use have the verified live-token implementation. Linux, WSL, and Windows publish runnable binaries, but add and use fail closed until their file-based live token store is verified in this estate. run, list, current, remove, and doctor remain available there; doctor reports the live-token gap as a warning rather than a hard failure.
0.1.1 and 0.1.2 were short-lived macOS-only transition builds. 0.1.4 introduced the mixed-era quota-window guard, but its macOS artifact recorded dirty source provenance. 0.1.5 supersedes it from one clean source commit and retains the exact auto-preserve, two-store rollback, metadata, parser, and platform boundaries documented here.

Relationships