Terminal-only Codex provider switcher.
It switches Codex live config files without depending on cc-switch, while preserving machine-local settings such as trusted projects and sandbox sections.
Profile names are user-defined aliases. cs does not invent meaning from provider config; whatever you write after [profiles.<alias>] is what you use in commands.
The script works in three layers:
- It snapshots your machine-local base config into the local Codex Switch state directory.
- It keeps provider definitions in a local
./profiles.toml. - On each switch, it merges
base-config.toml + selected profileand writes the live Codex config.
That means provider-specific keys are replaced, while unrelated local settings are preserved.
Files committed to git:
codex_switch.pyinstall.shREADME.mdprofiles.example.tomltests/
Local-only files not committed:
./profiles.toml- local Codex Switch state files
- local Codex live config files
profiles.toml is ignored by git on purpose because it may contain real API keys.
- Clone the repository and enter it:
git clone git@github.com:Sawyer-Wu/CodexSwitch.git
cd CodexSwitch- Install the command:
./install.sh- Initialize local files:
cs init- Edit your local profile definitions:
vim ./profiles.toml- Test one profile:
cs test <alias>- Switch to it:
cs switch <alias>Notes for a fresh machine:
- If Codex live config already exists on that machine,
cs initsnapshots the non-provider part into the local base config. - If Codex live config does not exist yet,
cs initstill creates./profiles.tomland an empty base snapshot. Your firstcs switch <profile>will write the live config.
Install the command:
./install.shInitialize this machine:
cs initcs init does two things:
- creates or refreshes the local base snapshot from your current live Codex config if it exists
- creates local
./profiles.tomlfrom./profiles.example.tomlif./profiles.tomldoes not exist yet
Recommended first-time setup:
- Run:
cs init- Edit local
./profiles.toml:
vim ./profiles.toml- Choose your own aliases and replace example endpoints and keys:
[profiles.work_primary]means the alias iswork_primarycs switch work_primaryuses that alias- aliases are for humans; use names that make sense to you
- Choose one of these key styles:
api_key = "sk-..."for a direct local keyapi_key_env = "ENV_NAME"if you prefer reading keys from environment variables
- Test before switching:
cs test <alias>- Apply the switch:
cs switch <alias>If you later change the machine-local shared part of your live Codex config, refresh the base snapshot:
cs init --refresh-base./profiles.toml is a normal TOML file with these parts:
[defaults]is optional.default_profileis the alias used bycs switchwith no argument.[profiles.<alias>]defines one switch target.<alias>is chosen by you. It is whatcs list,cs show,cs test, andcs switchuse.- Each profile must contain exactly one key source:
api_key = "sk-..."- or
api_key_env = "ENV_NAME"
- Each profile must contain
config_fragment = '''...'''. config_fragmentmust be valid TOML.model_providerinsideconfig_fragmentis Codex's provider key, not your profile alias.
Example:
[defaults]
default_profile = "work_primary"
[profiles.work_primary]
api_key_env = "CODEX_WORK_PRIMARY_KEY"
config_fragment = '''
model_provider = "relay"
model = "gpt-5.4"
[model_providers.relay]
name = "Work Relay"
base_url = "https://relay-primary.example.com/v1"
wire_api = "responses"
requires_openai_auth = true
'''In that example:
work_primaryis your aliasrelayis the provider key written into Codex configcs switch work_primaryswitches to that profile
If you have two accounts or two endpoints for the same provider format, create two aliases:
[profiles.work_primary]
api_key_env = "CODEX_WORK_PRIMARY_KEY"
config_fragment = '''
model_provider = "relay"
[model_providers.relay]
name = "Work Relay"
base_url = "https://relay-primary.example.com/v1"
wire_api = "responses"
requires_openai_auth = true
'''
[profiles.work_backup]
api_key_env = "CODEX_WORK_BACKUP_KEY"
config_fragment = '''
model_provider = "relay"
[model_providers.relay]
name = "Work Relay"
base_url = "https://relay-backup.example.com/v1"
wire_api = "responses"
requires_openai_auth = true
'''This repository includes a sanitized template in ./profiles.example.toml.
List profiles:
cs listShow the current live config:
cs currentShow one profile:
cs show <alias>Test the current live config:
cs testTest one profile without switching:
cs test <alias>Test all profiles:
cs test --allPreview the rendered config:
cs switch <alias> --dry-runSwitch to the default profile:
cs switchSwitch to a specific profile:
cs switch <alias>cs listshows a compact tablecs currentshows the active profile, provider, model, and endpointcs testshows only result and latency by defaultcs test --verboseadds endpoint, provider, response id, preview, and retry attempts
- Each switch backs up the previous live
config.tomlandauth.json. cs currentmatches the live config back to a known profile when possible.cs testsends a minimal real API request.cs testpreferscurlwhen available because some providers block Python default HTTP signatures behind Cloudflare.- Non-conflicting local config such as
[sandbox_workspace_write]and[projects."..."]is preserved from the base snapshot. - Conflicting keys from the selected profile overwrite the base snapshot.
cs testsupports retries for transient failures; recovered checks are shown asFLAKY.
python3 -m unittest discover -s tests