# duoterm Keep more than one Claude account signed in at once. No sign-out, no browser round trip, no re-auth loop. ``` curl -fsSL https://auto.relay70.dev/duoterm/install.sh | sh ``` Or run `./install.sh` inside a copy of the source; it installs the `duoterm.sh` beside it instead of downloading one. One shell script. No daemon, no package manager, no network access after install, nothing reported anywhere. ## Why this works Claude Code reads its identity from the directory named by `CLAUDE_CONFIG_DIR`. - **macOS** stores the OAuth token in the login keychain under a service name derived from that directory's path - `Claude Code-credentials` for the default, `Claude Code-credentials-` for any other. - **Linux** stores it in a credentials file inside the directory itself. Either way, a second config directory holds a second account permanently, and signing into one never evicts the other. `duoterm` creates those directories, symlinks the shared parts of your configuration back to the primary so only the identity differs, and switches which one new sessions use. There is no setting that turns off the single-session behavior. This is the way around it. ### The trap The account called `primary` is your existing `~/.claude`, used with `CLAUDE_CONFIG_DIR` **unset**. Setting the variable explicitly to `~/.claude` is not the same thing - the credential lookup misses and the session reports `Not logged in`. Every code path in `duoterm` preserves that distinction, including the shell wrapper, which actively clears a stale value when you switch back to primary. ## Use ``` duoterm add work create a second account slot duoterm login work sign it in, once, in the browser duoterm use work switch. Instant, local, no network duoterm list every account and its email, current one starred duoterm which account new sessions will use ``` `duoterm use` changes what **new** sessions pick up. Sessions already running keep the account they started with, so you can have both live in different terminals at the same time. Run one session as another account without switching: ``` duoterm run work --print "who am I billed to" ``` Run any command under an account's environment: ``` duoterm with work claude mcp list duoterm with primary my-script.sh ``` Everything else: ``` duoterm sync [name] relink shared config after adding files to ~/.claude duoterm remove delete a slot (never the primary) duoterm path [name] config dir of an account (empty for primary) duoterm env [name] the export/unset line a script should eval duoterm shell [zsh|bash|fish] print the shell wrapper duoterm doctor check the wiring duoterm help ``` ## Shared configuration Each slot symlinks these back to `~/.claude`, so one edit reaches every account: ``` CLAUDE.md settings.json settings.local.json commands hooks skills agents plugins projects history.jsonl ``` Identity is deliberately not shared: `.claude.json` is per-slot, and the credential never leaves its own keychain item. Override the list by writing `~/.claude-accounts/shared.conf`, one entry per line, `#` for comments: ``` CLAUDE.md settings.json commands hooks skills agents my-own-directory ``` Then `duoterm sync`. ## The shell wrapper The installer adds one block to your shell startup file: ```sh # >>> duoterm (claude account switch) >>> eval "$($HOME/.local/bin/duoterm shell zsh)" # <<< duoterm (claude account switch) <<< ``` That defines a `claude` function that reads the current slot from a one-word state file and execs the real binary with the right environment. It costs no subprocess per invocation beyond resolving the binary, and it falls back to the primary account if anything is missing. Skip it with `DUOTERM_NO_WIRE=1` and wire it yourself, or use `duoterm run` / `duoterm with` and never touch your rc file. ## Environment | Variable | Default | Meaning | |---|---|---| | `DUOTERM_HOME` | `~/.claude-accounts` | where account slots live | | `DUOTERM_PRIMARY_DIR` | `~/.claude` | the primary config dir | | `DUOTERM_PREFIX` | `~/.local` | install prefix (installer only) | | `DUOTERM_NO_WIRE` | unset | skip the shell startup edit (installer only) | ## Requirements Bash 3.2 or newer (macOS ships it), plus `curl` or `wget` to install. `jq` is optional: with it, a new slot inherits project trust and MCP servers from your primary; without it, the slot starts clean. Tested on macOS. The mechanism applies to Linux; the credential lives in a file inside the config dir there rather than the keychain. ## Uninstall ``` rm ~/.local/bin/duoterm rm -rf ~/.claude-accounts ``` Then delete the `duoterm` block from your shell startup file. Orphaned keychain items are removed in Keychain Access by searching `Claude Code-credentials`. ## Scope `duoterm` covers Claude Code. It does nothing for the desktop app or the browser - those keep their own sessions, and separate browser profiles are the equivalent trick there. --- Part of the [auto.relay70](https://auto.relay70.dev/) automation registry.