@axman/opencode-multi-auth-codex
v1.1.5
Published
Multi-account OAuth rotation for OpenAI Codex with auto model discovery
Readme
opencode-multi-auth-codex
Multi-account OAuth rotation plugin for OpenCode with a local dashboard, force mode, weighted settings, limits probing, and reliability hardening.
Documentation map
README.md-> primary operator and developer documentation for current behavior.docs/ADMIN_MERGE_BRIEF.md-> concise upstream/admin review summary.docs/PHASE_H_VALIDATION.md-> final validation report (current readiness reference).codextesting.md-> live/manual testing runbook.docs/README.md-> full docs index with authoritative vs historical references.
What this project does
- Rotates requests across multiple ChatGPT/Codex OAuth accounts.
- Keeps a local account store with migration, validation, and atomic writes.
- Provides a localhost dashboard to manage accounts and limits.
- Provides an optional OpenAI-compatible inference API for local SDK clients.
- Supports force mode (pin one alias), account enable/disable, and re-auth.
- Supports settings-driven rotation strategy (
round-robin,least-used,random,weighted-round-robin). - Probes limits safely and keeps authoritative data quality rules.
- Gates non-core Antigravity features behind a feature flag.
Current implementation status
- Core phases A-G are implemented in this workspace.
- Validation scripts are available for: unit, integration, web-headless, failure, stress, sandbox, soak.
- Web hardening fixes are in place:
- localhost-only bind enforcement
- malformed JSON returns deterministic
400without process crash - dashboard client script parse issue fixed
Behavior guarantees (latest)
- Rate-limit handling sleeps an alias until reset when reset timing is known (
Retry-After, rate-limit window reset, or parsed provider reset text), instead of retrying that alias immediately. - Force mode is strict: when enabled, requests stay pinned to the forced alias and do not silently fall back to other aliases.
- Rotation strategy control is shown next to Force Mode in the dashboard.
- Strategy changes from dashboard settings are applied to runtime selection logic (not just persisted state/UI display).
- Force Mode and strategy interaction is explicit:
- while Force Mode is ON, strategy changes are saved
- saved strategy becomes active when Force Mode is turned OFF
- Dashboard controls include mouseover help text for Force Mode and rotation strategy definitions.
- Account enable/disable toggle is authoritative for eligibility in rotation.
Rotation strategy reference
round-robin-> cycle through healthy enabled accounts in order.least-used-> prefer the healthy enabled account with the lowest usage count.random-> pick randomly from healthy enabled accounts.weighted-round-robin-> split traffic by configured account weights (example:0.70/0.20/0.10≈70%/20%/10%).- Force Mode precedence -> when Force Mode is ON, strategy is paused; strategy changes are saved and become active when Force Mode is OFF.
Repository structure
src/-> TypeScript sourcedist/-> compiled output (tscgenerated)tests/unit/-> unit teststests/integration/-> integration teststests/web-headless/-> headless UI smoke teststests/failure/-> failure-injection teststests/stress/-> stress/concurrency teststests/sandbox/-> sandbox isolation teststests/soak/-> soak scaffoldingdocs/-> QA and phase documentation (seedocs/README.mdfor canonical/historical split)IMPLEMENTATION_PLAN.md-> full plan and contractsTEST_EXECUTION_PLAN.md-> required test order and gatescodextesting.md-> live testing TODO for Codex CLI sessionsauto-login/-> Python script for bulk Outlook-based OAuth login
Requirements
- Node.js 20+
- npm
- OpenCode CLI
- ChatGPT/Codex OAuth accounts
Install and use
Plugin install (recommended)
In your OpenCode config (~/.config/opencode/opencode.json):
{
"plugin": ["@axman/opencode-multi-auth-codex@latest"]
}That npm package name is the identifier OpenCode resolves through Bun. If you want a fixed release instead of latest, pin an exact version such as @axman/[email protected].
OpenCode support:
- OpenCode
1.2.19+includes built-ingpt-5.4 - for older OpenCode builds, the plugin now backfills
gpt-5.4andgpt-5.4-fastinto runtime config by default - disable runtime model injection only if you explicitly want that behavior off:
export OPENCODE_MULTI_AUTH_INJECT_MODELS=0Update existing installs:
- restart OpenCode after updating the plugin
- if your install is pinned or cached, re-sync the plugin source before testing new models
From source
git clone https://github.com/akhmanov/opencode-multi-auth-codex.git
cd opencode-multi-auth-codex
npm ci
npm run buildQuick start
# Add accounts
opencode-multi-auth add personal
opencode-multi-auth add work
# Check status
opencode-multi-auth status
# Start dashboard
opencode-multi-auth web --host 127.0.0.1 --port 3434
# Start OpenAI-compatible API
export OPENCODE_MULTI_AUTH_API_KEY=test-key
opencode-multi-auth serve --host 127.0.0.1 --port 3000Open http://127.0.0.1:3434.
Automated Bulk Login (Outlook)
The auto-login/ directory contains a standalone Python script that automates the full OAuth login flow for multiple Outlook-based ChatGPT accounts. Instead of manually running opencode-multi-auth add and clicking through the browser for each account, the script handles everything:
- Opens OpenAI auth page
- Enters email, requests a one-time login code
- Logs into Outlook Web to read the verification code
- Enters the code, clicks through the consent page
- Captures the OAuth tokens and writes them directly into the plugin store
Prerequisites
- Python 3.9+
- Playwright (Python):
pip install playwright playwright install chromium - Outlook.com accounts linked to ChatGPT (the script reads OTP codes from Outlook Web)
Setup
Copy the example credentials file:
cp auto-login/credentials.example.json auto-login/credentials.jsonEdit
auto-login/credentials.jsonwith your real accounts:{ "defaults": { "chatgpt_password": "SharedPasswordIfAny" }, "accounts": [ { "id": "acc-1", "email": "[email protected]", "outlook_password": "your-outlook-password", "chatgpt_password": "your-chatgpt-password", "enabled": true } ] }defaults.chatgpt_passwordis used when an account doesn't specify its own.outlook_passwordis required for reading OTP codes from Outlook inbox.- Set
enabled: falseto skip an account without removing it.
Usage
# Check which accounts need login
python3 auto-login/auto_login.py --check
# Login all enabled accounts (headless)
python3 auto-login/auto_login.py
# Login a specific account by index
python3 auto-login/auto_login.py --account 0
# Login a specific account by email
python3 auto-login/auto_login.py --email [email protected]
# Run with visible browser (for debugging)
python3 auto-login/auto_login.py --visibleHow it works
OpenAI Auth Outlook Web Local Server
| | |
| 1. Enter email | |
| 2. Click "one-time code" | |
| ----sends OTP email-------> | |
| | 3. Login to Outlook |
| | 4. Read OTP from inbox |
| 5. Enter OTP code | |
| 6. Click Continue (consent) | |
| ----redirect callback-----> | ----code via HTTP GET----> |
| | | 7. Capture code
| | | 8. Exchange for tokens
| | | 9. Write to plugin storeThe script generates a PKCE challenge identical to the plugin's own OAuth flow, starts a local HTTP server on port 1455 to capture the callback, and writes tokens in the exact v2 store format the plugin expects.
Microsoft interstitials
Outlook login often shows interstitial pages after password entry:
| Page | Handled by |
|------|-----------|
| "Stay signed in?" | Auto-clicks "Yes" |
| "Let's protect your account" | Auto-clicks "Skip for now" |
| FIDO/Passkey creation (/fido/create) | Auto-clicks "Not now" / "Cancel" |
| Any other blocker | Force-navigates to inbox |
Troubleshooting
--visiblemode shows the browser so you can see exactly where the flow gets stuck.- Debug screenshots are saved as
auto-login/debug_<user>_<step>.pngon failure. - SSL errors on macOS: the script uses
ssl._create_unverified_context()for token exchange requests. This is safe for local automation. - Port 1455 in use: kill any process using that port, or change
REDIRECT_PORTin the script. - Stale OTP codes: if the inbox has old verification emails, the script may pick up an expired code. Clear the inbox or wait for a fresh email.
CLI commands
opencode-multi-auth add <alias>-> add account via OAuthopencode-multi-auth remove <alias>-> remove accountopencode-multi-auth list-> list configured accountsopencode-multi-auth status-> full statusopencode-multi-auth path-> print store pathopencode-multi-auth serve --host 127.0.0.1 --port 3000-> run OpenAI-compatible inference APIopencode-multi-auth web --host 127.0.0.1 --port 3434-> run dashboardopencode-multi-auth service install|disable|status-> systemd user service helpers
Serve API (MVP)
- Command:
opencode-multi-auth serve --host 127.0.0.1 --port 3000 - Required auth:
OPENCODE_MULTI_AUTH_API_KEYmust be set before startup. - Supported endpoints:
GET /v1/modelsPOST /v1/responsesPOST /v1/chat/completions
- Auth model: every request must send
Authorization: Bearer <OPENCODE_MULTI_AUTH_API_KEY>. - Binding: defaults to
127.0.0.1:3000; non-loopback--hostis allowed for Docker/WSL style setups but prints a warning. - Scope: inference only; no dashboard/admin routes are exposed from
serve. - Compatibility target: common SDK/IDE clients, not byte-for-byte OpenAI parity.
- Non-goals in this MVP:
/v1/embeddings, remote admin APIs, andservice installintegration.
Example:
curl http://127.0.0.1:3000/v1/models \
-H "Authorization: Bearer $OPENCODE_MULTI_AUTH_API_KEY"Dashboard/API endpoints
GET /api/stateGET /api/logsPOST /api/syncPOST /api/auth/startPOST /api/switchPOST /api/removePOST /api/account/metaPOST /api/token/refreshPOST /api/limits/refreshPOST /api/limits/stopGET /api/accountsPUT /api/accounts/:alias/enabledPOST /api/accounts/:alias/reauthGET /api/forcePOST /api/forcePOST /api/force/clearGET /api/settingsPUT /api/settingsGET /api/settings/feature-flagsPUT /api/settings/feature-flagsPOST /api/settings/resetPOST /api/settings/presetPOST /api/antigravity/refresh(feature-flag gated)POST /api/antigravity/refresh-all(feature-flag gated)
Environment variables
Storage and auth
OPENCODE_MULTI_AUTH_STORE_DIR-> override store directoryOPENCODE_MULTI_AUTH_STORE_FILE-> override store file pathOPENCODE_MULTI_AUTH_CODEX_AUTH_FILE-> override Codexauth.jsonOPENCODE_MULTI_AUTH_API_KEY-> required bearer key forserveCODEX_SOFT_STORE_PASSPHRASE-> encrypt account store at restCODEX_SOFT_LOG_PATH-> override dashboard log path
Rotation and limits
OPENCODE_MULTI_AUTH_ROTATION_STRATEGY(settings source override; runtime rotation follows persisted dashboard settings)OPENCODE_MULTI_AUTH_CRITICAL_THRESHOLDOPENCODE_MULTI_AUTH_LOW_THRESHOLDOPENCODE_MULTI_AUTH_TOKEN_FAILURE_COOLDOWN_MSOPENCODE_MULTI_AUTH_PROBE_EFFORTOPENCODE_MULTI_AUTH_LIMITS_PROBE_MODELS
Model mapping and runtime behavior
OPENCODE_MULTI_AUTH_PREFER_CODEX_LATESTOPENCODE_MULTI_AUTH_CODEX_LATEST_MODELOPENCODE_MULTI_AUTH_INJECT_MODELSOPENCODE_MULTI_AUTH_TRUNCATIONOPENCODE_MULTI_AUTH_DEBUG
GPT-5.4 mapping
The plugin can route older Codex selections to GPT-5.4 when you explicitly opt in.
Default behavior:
- exact model selection is preserved
Environment variables:
OPENCODE_MULTI_AUTH_PREFER_CODEX_LATEST=1enables mapping to the latest backend modelOPENCODE_MULTI_AUTH_CODEX_LATEST_MODEL=gpt-5.4overrides the mapping targetOPENCODE_MULTI_AUTH_DEBUG=1prints model mapping debug logsOPENCODE_MULTI_AUTH_INJECT_MODELS=0disables automatic runtime model backfill
Fast Mode
For OpenCode, the clean way to mirror Codex Fast mode is:
- keep the model as
openai/gpt-5.4 - use a model variant such as
fast - set
serviceTier=priorityin the variant config
Behavior:
- the backend model stays
gpt-5.4 - the plugin forwards the request with
service_tier=priority - the plugin does not automatically lower reasoning or verbosity
Recommended OpenCode config:
{
"provider": {
"openai": {
"models": {
"gpt-5.4": {
"variants": {
"Medium Fast": {
"reasoningEffort": "medium",
"serviceTier": "priority"
},
"High Fast": {
"reasoningEffort": "high",
"serviceTier": "priority"
},
"XHigh Fast": {
"reasoningEffort": "xhigh",
"serviceTier": "priority"
}
}
}
}
}
}
}See docs/gpt-5.4-fast-benchmark.md for a continued-session benchmark summary.
Feature flags
OPENCODE_MULTI_AUTH_ANTIGRAVITY_ENABLED
Notifications
OPENCODE_MULTI_AUTH_NOTIFYOPENCODE_MULTI_AUTH_NOTIFY_SOUNDOPENCODE_MULTI_AUTH_NOTIFY_MAC_OPENOPENCODE_MULTI_AUTH_NOTIFY_NTFY_URLOPENCODE_MULTI_AUTH_NOTIFY_NTFY_TOKENOPENCODE_MULTI_AUTH_NOTIFY_UI_BASE_URL
Security rules
- Dashboard host is loopback-only (
127.0.0.1,::1,localhost). - Non-loopback host bind is rejected.
- Serve API always requires bearer auth, including localhost requests.
- Serve API can bind non-loopback hosts only by explicit
--hostoverride and warns loudly when it does. - Sensitive token patterns are redacted in logs.
- Store file permissions are restricted (
0o600). - Antigravity APIs are blocked when feature flag is off.
Build and test
npm ci
npm run lint
npm run build
npx tsc --noEmit
npm run test:unit
npm run test:integration
npm run test:web:headless
npm run test:failure
npm run test:stress
npm run test:sandbox
npm run test:soak:48hCurrent test script surfaces are scaffolded and active. For true long soak, set a long duration and keep the run alive.
Live validation runbook
Use codextesting.md for the Codex CLI live-testing checklist and copy-paste command flow.
Troubleshooting
- If dashboard start fails with localhost error, check
--hostand use loopback only. - If a request returns
INVALID_JSON, verify payload body is valid JSON. - If an alias action returns
ACCOUNT_NOT_FOUND, refresh account list first. - If re-auth is blocked with
ACCOUNT_DISABLED, enable the account before re-auth. - If encrypted store appears locked, export
CODEX_SOFT_STORE_PASSPHRASEbefore launching.
Development notes
- Edit
src/*, never hand-editdist/*. - Run
npm run buildafter source changes. - Keep manual/live tests sandboxed (temp HOME/store/auth paths).
Release flow
- This plugin is intended to be installed as
@axman/opencode-multi-auth-codexfrom npm, so every shipped update should bumppackage.jsonversion before publish. - Prepare the next release in one command:
npm run release -- 1.1.1
# or: npm run release:patch- The release script updates
package.json, updatespackage-lock.json, and rebuildsdist/. - After that, cut the release from
main:
git commit -m "chore: release v1.1.1"
git tag v1.1.1
git push origin main --follow-tags
npm publish --tag latest --access public- Users who want a pinned build can install a specific package version:
{
"plugin": ["@axman/[email protected]"]
}- Users tracking
latestshould restart OpenCode so it re-resolves the published package version.
License
MIT
