Skip to content

Latest commit

 

History

7 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

claude-mode

A per-account CLI that toggles Claude Code between backends by editing exactly one file: ~/.claude/settings.json.

It never touches settings.local.json, .credentials.json, or .claude.json

  • those are regenerated by other tooling (team config sync, the claude CLI itself). Because claude-mode only owns settings.json, a switch survives a team-wide config re-sync.

Every site-specific value - proxy URLs, ports, model IDs, tunnel host, token env var names, the toggle order - lives in a profile.json you install locally. Nothing in bin/ or lib/ hardcodes a particular site.

What it is

Four backends, one command:

Mode What it routes to
oauth Your Claude subscription (OAuth login)
nexus-claude Claude models via your site's Bedrock-compatible proxy
nexus-gpt GPT-tier Claude Code model IDs via the same proxy
local An on-prem model, via the same proxy or direct (local.transport)

See docs/modes.md for exactly what each mode writes to settings.json and why.

Install

git clone <this-repo> claude-mode
cd claude-mode
./install.sh

install.sh is idempotent:

  • puts everything in bin/ into ~/.local/bin, pointing back at this checkout, so git pull is the only update step you ever need. On macOS, Linux and WSL that's a symlink; on Windows see below. Anything already at the destination that install.sh didn't create is left alone with a warning.
  • writes an initial profile.json from profiles/example.json - to ~/.claude/claude-mode/profile.json when ~/.claude exists, else to ~/.config/claude-mode/profile.json - only if no profile exists in either location - it never clobbers a profile you've already configured
  • warns (does not fail) if ~/.local/bin isn't on your PATH

The starter profile is a template, not a working config - its model IDs and URLs are placeholders. Edit it before your first claude-mode run; see Profile setup.

Use --dry-run to preview every action without writing anything:

./install.sh --dry-run

uninstall.sh removes only what install.sh created (it verifies each entry still points into this repo before removing it) and never touches profile.json or ~/.claude/settings.json.

Windows

Run install.sh from Git Bash or MSYS2 - it needs a POSIX shell and cygpath, both of which Git Bash ships. It detects native Windows and adapts:

  • instead of symlinks (Git Bash's ln -s silently copies, which would freeze your install at the current commit) it writes a launcher pair per script - claude-mode for Git Bash and claude-mode.cmd for PowerShell/cmd - each exec'ing the script out of this checkout
  • it resolves ~ the way the Python CLI does (%USERPROFILE%), not from $HOME, which Git Bash often points at a network share - so profile.json lands where claude-mode will actually read it

WSL is not native Windows: there everything installs as a normal POSIX environment, symlinks and all.

Profile setup

Edit your profile.json after install. It is looked for in this order:

  1. $CLAUDE_MODE_PROFILE (explicit file path override, always wins)
  2. ~/.claude/claude-mode/profile.json, if it exists (preferred - beside the settings.json this tool manages)
  3. $XDG_CONFIG_HOME/claude-mode/profile.json or ~/.config/claude-mode/profile.json (legacy fallback)

Start from profiles/example.json's shape:

{
  "bedrock": { "base_url": "http://localhost:8104", "port": 8104 },
  "local":   { "base_url": "http://localhost:8901", "port": 8901,
               "transport": "bedrock" },
  "models": {
    "oauth": { "opus": "...", "sonnet": "...", "haiku": "...", "small_fast": "..." },
    "nexus": { "opus": "...", "sonnet": "...", "haiku": "...", "small_fast": "..." },
    "gpt":   { "opus": "...", "sonnet": "...", "haiku": "...", "small_fast": "..." },
    "local": { "opus": "...", "sonnet": "...", "haiku": "...", "small_fast": "..." }
  },
  "claude_model": { "oauth": null, "nexus": null, "gpt": null, "local": null },
  "tokens": {
    "bedrock_env": "AWS_BEARER_TOKEN_BEDROCK",
    "env_file": null
  },
  "test": { "timeout_seconds": 20 },
  "tunnel": { "host": "your-server.example.com", "user": null, "jump": null },
  "toggle_cycle": ["oauth", "nexus", "gpt", "local"]
}

claude_model.<mode> optionally pins the top-level model key in settings.json per mode - useful when a model alias set there is only valid on one backend. null or omitted (the default) means "leave the user's model key alone". Leaving a pinned mode for an unpinned one undoes the pin (restoring your own pre-pin model value, if you had one)

  • see docs/modes.md for the exact semantics.

local.transport picks how local mode reaches the on-prem model:

  • "bedrock" (the default, and the only behavior before this key existed) routes it through the Bedrock-compatible proxy, exactly like the nexus modes. Profiles written before this key keep working with no edit.
  • "direct" points ANTHROPIC_BASE_URL straight at local.base_url, for a server that already speaks the Anthropic Messages API natively - no proxy and no Bedrock emulation in between.

Ollama serves that API at /v1/messages, so pointing local at a machine's own Ollama is just:

"local":  { "base_url": "http://localhost:11434", "transport": "direct" },
"models": { "local": { "opus": "llama3.2:1b", "sonnet": "llama3.2:1b",
                       "haiku": "llama3.2:1b", "small_fast": "llama3.2:1b" } }

No token is needed on that path, and no tunnel - claude-mode local gates on the local endpoint itself rather than on the proxy. Switching to any other mode tears the direct keys back down, so a stale ANTHROPIC_BASE_URL can never leave a later backend pointed at the local server.

Any key you omit falls back to a generic built-in default (see lib/profile.py's DEFAULTS); a key with no sane generic default (a real model ID, a real tunnel host) raises a clear ABORT: missing required profile key: '...' naming exactly what's missing, instead of silently using a placeholder.

toggle_cycle entries may use the internal short names (nexus, gpt) or the canonical command names (nexus-claude, nexus-gpt) - an entry that matches no known mode makes claude-mode toggle abort instead of guessing.

$CLAUDE_MODE_PROFILE overrides the profile file path entirely - useful for tests, or running two profiles side by side.

Tokens (Nexus gateway token, etc.) are never stored in profile.json. They are read fresh on every switch from the first env file that contains the key: ~/.claude/.env, then ~/.env - or exactly the file named by tokens.env_file when that profile key is set (see docs/modes.md).

Usage

claude-mode status        # show active backend, proxy health, and account
claude-mode nexus-claude  # Claude models through the configured proxy
claude-mode nexus-gpt     # GPT-tier models through the same proxy
claude-mode local         # route to the on-prem model (proxied or direct)
claude-mode oauth         # route back to the Claude subscription
claude-mode toggle        # cycle through profile.json's toggle_cycle
claude-mode test          # live round-trip through the active backend

Aliases: st for status; nexus/bedrock for nexus-claude; gpt/sol/luna/terra for nexus-gpt; claude/sub/subscription for oauth.

nexus-claude and nexus-gpt are both Claude Code modes using the same Bedrock-compatible Nexus proxy. They read the same token variable, tokens.bedrock_env (normally AWS_BEARER_TOKEN_BEDROCK), from ~/.claude/.env first, then ~/.env. The GPT mode changes only the model IDs; it does not launch Codex or another CLI.

Restart your claude session (exit and relaunch) after switching - the CLI reads its environment once at startup.

Two extra launcher wrappers ship in bin/:

  • cc - launches claude pinned to the oauth model tier regardless of whatever else is currently configured in your shell environment.
  • cc-gpt - launches claude pinned to the nexus-gpt tier using the same shared Nexus token as nexus-claude.

Remote / off-network use

If your machine can't reach the proxy directly (a laptop off the plant network, a personal machine), claude-mode-tunnel opens an SSH tunnel first. See docs/remote-setup.md for the full walkthrough.

claude-mode-tunnel up       # start the tunnel (no-op if already running)
claude-mode-tunnel status   # pid + per-port reachability
claude-mode-tunnel down     # stop it

Troubleshooting

  • ABORT: no <token> found in ... - add the token env var named in the error message to one of the files the message lists (KEY=value form, one per line; ~/.claude/.env is searched first, then ~/.env). That file should be chmod 600 and is git-ignored by design; claude-mode only ever reads it, never writes it. The file must be UTF-8 (a UTF-8 BOM is tolerated) - Windows PowerShell 5.1's echo 'KEY=...' >> file writes UTF-16, which claude-mode warns about and skips; use Add-Content -Encoding utf8 or a UTF-8 editor instead.
  • ABORT: <proxy> is not answering - nothing answered HTTP at the URL your profile points at (an error status like 401/404 counts as answering, so this really means down or unreachable). If tunnel.host is set in your profile, the error tells you to run claude-mode-tunnel up first.
  • ABORT: missing required profile key: '...' - that dotted key has no generic default and isn't set in your profile.json. Add it.
  • claude-mode test times out on a slow machine - raise test.timeout_seconds in your profile.json (default 20).
  • Switch reports success but Claude Code still behaves like the old mode - you didn't restart the session. Exit and relaunch claude.
  • claude-mode not found - ~/.local/bin isn't on your PATH. Add export PATH="$HOME/.local/bin:$PATH" to your shell rc file.
  • Windows: claude-mode not found in PowerShell/cmd - run install.sh from Git Bash (see Windows under Install); it generates the .cmd launchers PowerShell needs. ~/.local/bin still has to be on your Windows PATH.
  • Windows: python3 "not found" / Microsoft Store nag - that's the Store app-execution alias shadowing a real interpreter. Every entry point probes python3 --version and falls back to python, so a working python is enough; if neither runs, install Python 3 or turn the alias off under Settings > Apps > Advanced app settings > App execution aliases.
  • Windows - the Python CLI itself is OS-agnostic; under WSL everything runs unmodified as a normal POSIX environment. On native Windows, claude-mode resolves the same home directory the claude CLI does (%USERPROFILE%) and deliberately ignores a stray HOME env var (Cygwin/MobaXterm/Emacs setups often set one) - otherwise it would edit a settings.json the CLI never reads. Set CLAUDE_MODE_HOME to force a specific home directory (the test suite uses this).

Repo layout

bin/        the installed commands (claude-mode, claude-mode-tunnel, cc, cc-gpt)
lib/        shared Python modules: profile loading, settings.json I/O, platform detection
            plus python.sh, the interpreter probe every shell entry point sources
profiles/   profiles/example.json - the template install.sh copies from
tests/      unittest suite (`make test`)
install.sh / uninstall.sh
Makefile    `make check` = syntax check + compile + unittest + secret/site-value scrub

License

MIT - see LICENSE.

About

A per-account CLI that toggles Claude Code between backends by editing exactly one file: ~/.claude/settings.json

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages