Unified multi-account manager for Gemini CLI and Codex CLI on Ubuntu/Linux
Project description
Switch between multiple Google Gemini and OpenAI Codex accounts instantly — no re-login required. AI Account Switcher manages OAuth tokens, API keys, keyring credentials, and shell wrappers so you can jump between work, personal, and test accounts in a single command.
Table of Contents
- Features
- Requirements
- Installation
- Quick Start
- How-To Guides
- Command Reference
- How Switching Works
- Project Structure
- Contributing
- License
Features
- Instant profile switching — atomic symlink swaps, no CLI restart for API key profiles
- Both CLIs, one tool — manages Gemini CLI and Codex CLI from a single interface
- All auth types — OAuth, API keys (Gemini), API keys, ChatGPT OAuth (Codex)
- Keyring integration — writes to GNOME Keyring / KWallet with automatic file fallback for headless systems
- Health checks — validate tokens and API keys before switching
- Live quota display —
switcher gemini quotashows quota used per OAuth profile with visual progress bars - Auto-rotation — Gemini hooks detect quota exhaustion and rotate profiles automatically; hooks safely handle Gemini CLI's
stopHookActiveflag to prevent deadlocks - Codex isolation — per-profile memory, plugin list, and sandbox policy snapshots restore automatically on switch
- OAuth client caching — discovered OAuth credentials are cached for 24 hours, reducing startup latency
- Interactive menu —
switcher gemini menulaunches a numbered TUI for profile management without memorising commands - Shell integration —
env.shsourced per invocation via shell wrappers and aliases - Import/export — move profiles between machines or back them up
- Pool sub-commands —
switcher gemini pool health/export/statusfor managing the rotation pool - Alerts —
switcher alertstails recent error-log entries for quick diagnostics - Update check —
switcher version --checkchecks PyPI for a newer release - Discrete logging — separate
errors.logfor error-only entries andcommands.logfor per-invocation timing - XDG compliant — all state stored under
~/.config/ai-account-switcher/
Requirements
| Requirement | Details |
|---|---|
| Python | 3.10 or newer |
| OS | Linux (Ubuntu 22.04+ recommended) |
| CLI tools | Gemini CLI and/or Codex CLI |
| Optional | GNOME Keyring or KWallet for secure credential storage |
Installation
From Source (recommended)
# 1. Clone the repository
git clone https://github.com/your-user/ai-account-switcher.git
cd ai-account-switcher
# 2. Install from PyPI
pip install ai-account-switcher
# --- OR install from source (development) ---
python3 -m venv .venv
source .venv/bin/activate
pip install -e .
# 3. Install shell integration, hooks, and the `switcher` bin symlink
switcher install
# 4. Reload your shell
source ~/.bashrc # or ~/.zshrc
Verify Installation
switcher version
# ai-account-switcher 0.3.0
switcher
# Shows the status dashboard
Uninstall
switcher uninstall # removes shell hooks, aliases, bin symlink
pip uninstall ai-account-switcher
Quick Start
# Import your existing Gemini login as a named profile
switcher gemini import ~/.gemini/oauth_creds.json work
# Import a second account
switcher gemini import ~/backup/oauth_creds.json personal
# See all profiles
switcher gemini list
# Switch to the personal account
switcher gemini switch personal
# View the dashboard
switcher
How-To Guides
Managing Gemini Profiles
Add a new OAuth profile (interactive login)
switcher gemini add my-account --type oauth
# Follow the OAuth flow in your browser, then import the generated credentials
Add an API key profile
switcher gemini add my-api-key --type apikey
# The command creates a profile directory — add your API key to the file shown in output
Switch between profiles
# By label
switcher gemini switch work
# By index number (shown in `list`)
switcher gemini switch 2
# Rotate to the next profile in order
switcher gemini next
# /change command — no target rotates, label/number switches directly
switcher gemini change
switcher gemini change personal
switcher gemini change 2
Remove a profile
switcher gemini remove personal
switcher gemini remove 3
Managing Codex Profiles
Add an API key profile
switcher codex add work-key --type apikey
# Add your OPENAI_API_KEY to the file shown in output
Import an existing auth.json
switcher codex import ~/.codex/auth.json my-chatgpt
Switch and rotate
switcher codex switch work-key
switcher codex next
Codex profile isolation
When switching Codex profiles, the following are automatically snapshotted and restored:
- Conversation memory — Codex's SQLite/flat-file memory store is saved per profile
- Plugin list — diverging plugins between profiles are reported as a warning
- Sandbox policy —
policy.tomlis saved per profile and restored on switch
Import & Export Profiles
Export a profile to a file
# Export by label — writes to current directory
switcher gemini export work
# Export to a specific path
switcher gemini export work ~/backups/gemini-work.json
# Export by index number
switcher gemini export 1 ~/backups/
Import a profile from a file
# Import with a custom label
switcher gemini import /path/to/oauth_creds.json work-account
# Import with auto-detected label
switcher codex import /path/to/auth.json
The importer auto-detects the auth type (OAuth vs API key) from the file contents.
Cross-Machine Profile Transfer
You can export profiles on one machine and import them on another:
# ── Machine A ──
switcher gemini export work /tmp/work-creds.json
# Transfer the file securely (scp, USB, etc.)
scp /tmp/work-creds.json user@machine-b:/tmp/
# ── Machine B ──
switcher gemini import /tmp/work-creds.json work
What transfers:
- ✅ OAuth credentials (
oauth_creds.jsonwith refresh tokens) - ✅ API keys (
api_key.txtorauth.json)
What does NOT transfer:
- ❌ Profile metadata (label, notes, health status — regenerated on import)
- ❌ Keyring entries (re-created automatically on first switch)
Security notes:
- Exported files contain plaintext credentials — transfer securely and delete intermediate files
- OAuth refresh tokens may expire or be device-bound; re-authenticate if needed after import
Auto-Rotation (Gemini)
Auto-rotation uses Gemini CLI's hook system to detect quota exhaustion and automatically switch to the next available profile.
Enable auto-rotation
switcher config set auto_rotate.enabled true
Configure rotation behavior
# Maximum retry attempts per session (default: 3)
switcher config set auto_rotate.max_retries 3
# Quota threshold percentage for proactive switching (default: 10)
switcher config set auto_rotate.threshold 10
# Rotation strategy: "conservative" (default) or "gemini3-first"
switcher config set auto_rotate.strategy conservative
# Cache quota check results for N minutes (default: 5)
switcher config set auto_rotate.cache_minutes 5
# Enable proactive pre-check before each request (default: true)
switcher config set auto_rotate.pre_check true
# After rotating, instruct Gemini CLI to restart the agent (default: false)
switcher config set auto_rotate.restart_on_switch true
How it works
- AfterAgent hook — runs after each Gemini CLI response. Detects "Resource exhausted" / HTTP 429 errors and triggers
switcher gemini next. After rotating, writes a short-lived handoff flag so the next hook invocation skips its API call. Safely exits with{}whenstopHookActiveis set. - BeforeAgent hook — runs before each request. Checks for the handoff flag first (skipping the API call if set); otherwise proactively checks remaining quota via Google's API and switches profiles before hitting limits. Safely exits with
{}whenstopHookActiveis set.
Hooks are registered in ~/.gemini/settings.json by switcher install. Re-running switcher install is safe — hooks are updated in-place without duplicates.
Live Quota Display
View real-time quota usage for all Gemini OAuth profiles:
switcher gemini quota
Sample output:
ℹ️ Fetching quota for 2 OAuth profile(s)...
──────────────────────────────────────────────────────────────
work ●
(you@work.com) [Tier: Standard]
gemini-2.0-flash ██░░░░░░░░░░░░ 14.7% used
personal
(you@personal.com)
gemini-2.0-flash ████████████░░ 87.9% used ⚠️ resets in 4 h
●marks the active profile⚠️appears when quota used exceeds 80%- Tier name is shown when available from the API
- Only OAuth profiles are checked (API key profiles have no quota API)
Interactive Menu
Launch a numbered TUI for profile management — useful when you don't remember the exact command:
switcher gemini menu
# or
switcher codex menu
Gemini Profile Manager
Active: work
────────────────────────────────────
1) List all profiles
2) Switch to a profile
3) Rotate to next profile
4) Show live quota usage
5) Check profile health
6) Add a new profile
7) Import credentials
8) Toggle auto-rotate on/off
q) Quit
Choice:
When stdin is not a TTY (e.g. piped or in a script), the menu prints a compact command reference instead of prompting.
Slash Commands
switcher install configures the following CLI-native slash commands:
- Gemini CLI
/change→ alias forswitcher gemini change— no argument rotates to next profile, a label or number switches directly- Command file:
~/.gemini/commands/change.toml
- Codex CLI
- No custom slash commands are installed by
ai-account-switchercurrently. - Use regular commands instead (for example:
sw codex next).
- No custom slash commands are installed by
Health Checks
Validate that your profiles' credentials are still working:
# Check all Gemini profiles
switcher gemini health
# Check all Codex profiles
switcher codex health
Health checks verify:
- OAuth tokens — test refresh token validity against the provider's token endpoint
- API keys — make a lightweight API call to confirm the key is active
- Status values —
healthy,expired,invalid,rate_limited,unknown
Pool Management
The pool sub-commands offer a focused view of the rotation pool:
# List all pool profiles
switcher gemini pool list
# Run health checks across all pool profiles
switcher gemini pool health
# Export all pool profiles to a directory
switcher gemini pool export --dest ~/backups/gemini-pool/
# Show rotation state and next-up profile
switcher gemini pool status
Alerts and Diagnostics
# Tail the last 20 error-log entries (default)
switcher alerts
# Show more lines
switcher alerts --lines 50
# Check for a newer release on PyPI
switcher version --check
Discrete log files:
| File | Contents |
|---|---|
~/.config/ai-account-switcher/logs/switcher.log |
Full application log (all levels) |
~/.config/ai-account-switcher/logs/errors.log |
Error-only entries for quick triage |
~/.config/ai-account-switcher/logs/commands.log |
Per-invocation timing and exit status |
Configuration
All configuration is stored in ~/.config/ai-account-switcher/config.toml:
[general]
log_level = "info" # debug, info, warning, error
[auto_rotate]
enabled = false
max_retries = 3
threshold = 10
strategy = "conservative" # or "gemini3-first"
cache_minutes = 5
pre_check = true
restart_on_switch = false # ask Gemini CLI to restart agent after rotation
View and modify config
# Show all configuration
switcher config
# Get a specific value
switcher config get general.log_level
# Set a value
switcher config set general.log_level debug
Command Reference
Global Commands
| Command | Description |
|---|---|
switcher or switcher status |
Show status dashboard for all CLIs |
switcher config |
Show current configuration |
switcher config set <key> <value> |
Set a config value |
switcher install |
Install shell integration, hooks, and bin symlink |
switcher uninstall |
Remove all integration |
switcher alerts [--lines N] |
Tail recent error-log entries |
switcher version |
Print version |
switcher version --check |
Print version and check PyPI for updates |
Gemini Commands
| Command | Description |
|---|---|
switcher gemini list |
List all Gemini profiles with status |
switcher gemini switch <n|label> |
Switch to a profile by index or label |
switcher gemini next |
Rotate to the next profile |
switcher gemini change [n|label] |
Slash-command parity: no arg → next, label/index → switch |
switcher gemini menu |
Launch interactive profile management menu |
switcher gemini quota |
Show live quota usage (% used) for all OAuth profiles |
switcher gemini add [label] [--type oauth|apikey] |
Create a new empty profile |
switcher gemini remove <n|label> |
Delete a profile |
switcher gemini import <path> [label] |
Import credentials from a file |
switcher gemini export <n|label> [dest] |
Export profile credentials to a file |
switcher gemini health |
Run health checks on all profiles |
switcher gemini pool |
Alias for list |
switcher gemini pool list |
List rotation pool profiles |
switcher gemini pool health |
Run health checks across pool profiles |
switcher gemini pool export [--dest DIR] |
Export all pool profiles to a directory |
switcher gemini pool status |
Show rotation state and next-up profile |
switcher gemini pool add [label] |
Alias for add |
switcher gemini pool remove <n|label> |
Alias for remove |
switcher gemini pool import <path> [label] |
Alias for import |
Codex Commands
| Command | Description |
|---|---|
switcher codex list |
List all Codex profiles with status |
switcher codex switch <n|label> |
Switch to a profile by index or label |
switcher codex next |
Rotate to the next profile |
switcher codex change [n|label] |
Slash-command parity: no arg → next, label/index → switch |
switcher codex menu |
Launch interactive profile management menu |
switcher codex add [label] [--type apikey|chatgpt] |
Create a new empty profile |
switcher codex remove <n|label> |
Delete a profile |
switcher codex import <path> [label] |
Import auth.json or API key file |
switcher codex export <n|label> [dest] |
Export profile credentials to a file |
switcher codex pool |
Alias for list |
switcher codex pool list |
List rotation pool profiles |
switcher codex pool health |
Run health checks across pool profiles |
switcher codex pool export [--dest DIR] |
Export all pool profiles to a directory |
switcher codex pool status |
Show rotation state and next-up profile |
switcher codex pool add [label] |
Alias for add |
switcher codex pool remove <n|label> |
Alias for remove |
switcher codex pool import <path> [label] |
Alias for import |
How Switching Works
Gemini — OAuth Profiles
- Creates an atomic symlink:
~/.gemini/oauth_creds.json→ profile directory - Writes credentials to the OS keyring (service:
gemini-cli-oauth, key:main-account) - Deletes
~/.gemini/mcp-oauth-tokens.json(token cache) to avoid stale sessions - Next Gemini CLI invocation picks up the new credentials immediately
Gemini — API Key Profiles
- Writes
GEMINI_API_KEYandGOOGLE_API_KEYto~/.config/ai-account-switcher/env.sh - Shell wrapper sources
env.shbefore launchinggemini - No restart needed — takes effect on next invocation
Codex — API Key Profiles
- Creates an atomic symlink:
~/.codex/auth.json→ profile directory - Snapshots/restores per-profile memory (SQLite DB), plugin list, and sandbox policy
- Writes
OPENAI_API_KEYtoenv.sh - Shell wrapper sources
env.shbefore launchingcodex
Codex — ChatGPT OAuth Profiles
- Creates an atomic symlink:
~/.codex/auth.json→ profile directory - Codex CLI reads tokens from the file directly
- May require a Codex restart for ChatGPT OAuth (account-ID-gated
reload())
Project Structure
ai-account-switcher/
├── main.py # Direct-run entry point
├── pyproject.toml # Project metadata and dependencies
├── docs/
│ └── spec.md # Full technical specification
├── switcher/
│ ├── __init__.py
│ ├── cli.py # Argument parsing, command routing
│ ├── config.py # TOML config management
│ ├── state.py # Active profile state + OAuth client cache + handoff flags
│ ├── ui.py # Terminal colors, tables, dashboard
│ ├── ui_menu.py # Interactive numbered TUI menu
│ ├── utils.py # XDG paths, logging, file locking, atomic symlinks
│ ├── errors.py # Custom exception hierarchy
│ ├── health.py # Token/key validation, quota fetch, OAuth client discovery
│ ├── installer.py # Shell RC injection, hooks, bin symlinks, env.sh generation
│ ├── profiles/
│ │ ├── base.py # Abstract ProfileManager
│ │ ├── gemini.py # Gemini profile operations
│ │ └── codex.py # Codex profile operations + isolation integration
│ ├── auth/
│ │ ├── keyring_backend.py # Keyring CRUD with file fallback
│ │ ├── gemini_auth.py # Gemini credential activation
│ │ ├── codex_auth.py # Codex credential activation
│ │ ├── codex_memory.py # Per-profile Codex memory snapshot/restore
│ │ ├── codex_plugins.py # Per-profile plugin list snapshot + divergence warning
│ │ └── codex_sandbox.py # Per-profile sandbox policy snapshot/restore
│ └── hooks/
│ ├── quota_patterns.py # Centralised quota-error regex patterns
│ ├── gemini_after_agent.py # Post-response quota error detection + rotation
│ └── gemini_before_agent.py # Pre-request proactive quota check
└── tests/ # Test suite (pytest, 471+ tests)
Key File Paths
| Path | Purpose |
|---|---|
~/.config/ai-account-switcher/config.toml |
User preferences |
~/.config/ai-account-switcher/state.json |
Active profiles, rotation state |
~/.config/ai-account-switcher/state/quota_error_gemini.json |
Short-lived handoff flag (AfterAgent → BeforeAgent) |
~/.config/ai-account-switcher/env.sh |
Exported API key environment variables |
~/.config/ai-account-switcher/cache/oauth_client.json |
Cached OAuth client credentials (24-hour TTL) |
~/.config/ai-account-switcher/profiles/ |
Profile credential storage |
~/.config/ai-account-switcher/logs/switcher.log |
Full application log |
~/.config/ai-account-switcher/logs/errors.log |
Error-only log for quick triage |
~/.config/ai-account-switcher/logs/commands.log |
Per-command timing and exit status |
~/.gemini/oauth_creds.json |
Symlinked by switcher |
~/.gemini/settings.json |
Gemini hooks registered here |
~/.codex/auth.json |
Symlinked by switcher |
Contributing
Contributions are welcome! Here's how to get started.
Setting Up the Development Environment
# Clone and enter the repository
git clone https://github.com/your-user/ai-account-switcher.git
cd ai-account-switcher
# Create a virtual environment
python3 -m venv .venv
source .venv/bin/activate
# Install in editable mode with dev dependencies
pip install -e ".[dev]"
Running Tests
# Run the full test suite
pytest
# Run a specific test file
pytest tests/test_config.py -v
# Run with coverage
pytest --cov=switcher --cov-report=term-missing
Linting and Formatting
# Check for lint issues
ruff check switcher/ tests/
# Auto-fix lint issues
ruff check --fix switcher/ tests/
# Check formatting
ruff format --check switcher/ tests/
# Auto-format
ruff format switcher/ tests/
Type Checking
mypy switcher/
Code Style Guidelines
- Python 3.10+ — use
from __future__ import annotationsfor modern union syntax - Ruff for linting and formatting (line-length 88; rules: E, F, W, I, B, UP, RUF, SIM, TCH)
- mypy --strict for type checking
pathlib.Pathfor all filesystem paths — never raw stringsdataclasses.dataclass(slots=True)for structured data- Google-style docstrings (
Args:,Returns:,Raises:) argparsefor CLI — no third-party CLI frameworks
Testing Conventions
- Never touch real
~/.gemini/,~/.codex/, or the OS keyring in tests - Use
tmp_pathfixtures andmock_keyringfor isolation - Patch
switcher.utils.get_config_dir()/get_gemini_dir()/get_codex_dir()to return temp paths - Mock HTTP calls (
requests.post/requests.get) for health checks and quota APIs - Target: ≥80% line coverage
Submitting Changes
- Fork the repository
- Create a feature branch:
git checkout -b feature/my-feature - Make your changes with clear, atomic commits
- Ensure all checks pass:
ruff check && ruff format --check && mypy switcher/ && pytest - Push and open a pull request against
main
Reporting Issues
- Use GitHub Issues to report bugs or request features
- Include your Python version, OS, and the output of
switcher version - For bugs, run
switcher alertsor check~/.config/ai-account-switcher/logs/errors.log
License
This project is licensed under the MIT License — see the LICENSE file for details.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ai_account_switcher-0.3.0.tar.gz.
File metadata
- Download URL: ai_account_switcher-0.3.0.tar.gz
- Upload date:
- Size: 120.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a2bef706bdc4c4527165abfcb19f511fc076793bfe3293d00bf851f0f570e56a
|
|
| MD5 |
1a25a0f4dbb4b8147c2f8adfe48609b2
|
|
| BLAKE2b-256 |
ee22de621cec09dee3972fc235e39c4c6a4c062c6b7a5afdfe75e468b4278f7b
|
File details
Details for the file ai_account_switcher-0.3.0-py3-none-any.whl.
File metadata
- Download URL: ai_account_switcher-0.3.0-py3-none-any.whl
- Upload date:
- Size: 62.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b51486d2866bd384ec0644ca6217e13ed48ff19ecb161d78221d222900d60923
|
|
| MD5 |
8ab1398c49f8aaec6ce07acc67442f82
|
|
| BLAKE2b-256 |
5193249fd4970edbf3f57a377b2269ba3adfed2a969f69267a1caf5dee82f113
|