Provider switcher for Claude Code and Codex.
Keep every endpoint, API key, and model in one place. Switch the active provider with a click. Probe reachability before you start working.
Python backend · Next.js UI · native OS webview — no Electron, no Node at runtime.
Switching between Anthropic, OpenRouter, DeepSeek, Azure, xAI, and custom relays usually means editing config files by hand. U-Pool turns that into a desktop app:
- One list of providers for Claude Code and Codex
- One click to make a provider live — in the config file and in the Windows environment the CLIs read
- Health checks that measure latency without spending tokens
- Atomic writes, and a backup beside every file U-Pool changes
| Surgical writes | A switch changes only the keys U-Pool owns — plugins, theme, hooks, MCP servers and project trust stay where they are |
| Windows environment | The variables the CLIs actually read are set in HKCU\Environment too, and only names U-Pool set are ever deleted |
| Atomic writes | Temp file + os.replace; crash mid-write cannot leave half-written config |
| Backups | One copy beside the original — settings.json.backup — refreshed on every change, switchable off in Settings |
| First-run import | Existing Claude / Codex config becomes a provider instead of being overwritten |
| Reachability probe | Plain GET /models — latency only, no completions, no token cost |
| Presets | CodeFast, Yunwu, DeepSeek, Kimi, OpenRouter, MiniMax, Z.ai, Azure, xAI, Custom, and more |
| Official mode | Hand control back to vendor login by clearing U-Pool-managed keys |
| Permission switches | Per-provider checkboxes for bypass mode, auto-accept edits, project MCP trust, Codex approvals/sandbox and live web search |
| Claude Desktop | A third tab, preview only — providers you add there are saved, nothing is written yet |
| In-app updates | Settings shows a pulsing Update button when a newer release is out, downloads it and swaps itself |
| Launch at sign-in | Windows on/off switch — one HKCU\...\Run entry, removed again when you turn it off |
~/.u-pool/config.json is the source of truth. On every switch U-Pool changes only the keys it owns in each target and leaves the rest of the file as it found it. The files are not the whole story: the CLIs also read variables straight out of the Windows environment, so a switch writes there too.
| App | Target | What a switch writes |
|---|---|---|
| Claude Code | ~/.claude/settings.json |
env block: base URL, auth token / API key, models, extras |
| Claude Code | ~/.claude/settings.json |
Only while a checkbox is ticked: permissions.defaultMode, permissions.skipDangerousModePermissionPrompt, enableAllProjectMcpServers |
| Claude Code | HKCU\Environment |
The same names again: ANTHROPIC_BASE_URL, ANTHROPIC_AUTH_TOKEN or ANTHROPIC_API_KEY, ANTHROPIC_MODEL, ANTHROPIC_SMALL_FAST_MODEL, extras |
| Claude Desktop | — | Nothing yet. The provider is saved and marked current, and you get a notice saying so |
| Codex | ~/.codex/config.toml |
model_provider, model, one [model_providers.<slug>] table |
| Codex | ~/.codex/config.toml |
Only while a checkbox is ticked: approval_policy, sandbox_mode, web_search |
| Codex | ~/.codex/auth.json |
Written from scratch: {"OPENAI_API_KEY": "…"} when that is the provider's env key, otherwise {} |
| Codex | HKCU\Environment |
Whatever the provider's env_key is named, plus OPENAI_BASE_URL |
0.5.0 rebuilt ~/.claude/settings.json and ~/.codex/config.toml from the provider record alone. That did stop the pile-up it was aimed at — an orphan [model_providers.*] table, a stale ANTHROPIC_AUTH_TOKEN sitting beside the new one — but it also took everything else in those files with it.
Since 0.6.0 the write is surgical: U-Pool reads the file, replaces the handful of keys it owns, and puts the rest back untouched. enabledPlugins, extraKnownMarketplaces, theme, effortLevel, model, hooks, statusLine and permissions.allow / deny survive a Claude Code switch. [mcp_servers.*], [plugins.*], [projects.*] trust levels, notify, [windows], [features], [shell_environment_policy] and your comments survive a Codex switch — a key whose value has not changed is not even re-rendered, so its trailing comment stays put.
Two things are still owned whole, because that is exactly where the pile-up happened:
- the
envblock insettings.json— rebuilt from the provider every switch, removed entirely for an official one [model_providers]inconfig.toml— only the active provider's table survives; the others are dropped and named in the toast
~/.codex/auth.json is written from scratch, because merging is what let a key an older build wrote sit there for months. If the file holds a ChatGPT login instead of an API key, the whole original is copied to ~/.u-pool/codex-login.json first and restored verbatim when you switch back to the official provider.
A file that will not parse stops the switch instead of being overwritten: preserving what U-Pool did not write means reading it first.
Codex resolves its key by name — config.toml says env_key = "codefast" and Codex then looks for codefast in its process environment, which no file U-Pool writes can supply. Claude Desktop and any shell you open are in the same position. So a switch also writes HKCU\Environment and broadcasts WM_SETTINGCHANGE, so a program started afterwards sees the change without a sign-out.
U-Pool records the names it sets in ~/.u-pool/env-owned.json and deletes only those. A variable you set by hand is never removed. If a name U-Pool needs already exists it takes that name over, and the value it replaced goes to the backup first. Switching to the official provider clears the whole namespace U-Pool claimed and nothing else.
Settings → Windows environment lists what is managed for the current app with masked values, marks anything set outside U-Pool, and opens the Windows editor. Off Windows the whole feature is inert.
Every file U-Pool is about to change is copied once, beside the original, as <filename>.backup — settings.json.backup, config.toml.backup, auth.json.backup. One current copy, overwritten each time, not a history. Registry values cannot sit beside anything, so their pre-change values go to ~/.u-pool/backups/environment.backup.json, where a null means the name did not exist.
The switch in Settings → Backups turns it off, and with it off nothing is written at all. There is no restore screen: the copies are plain files, and putting one back is a rename.
Claude Desktop is a third tab in 0.6.0 and writes nothing. Providers you add there are saved and can be made current, and a notice above the list says the configuration was not written.
The reason it is preview rather than finished: Claude Desktop has no base-URL setting, so the only lever is the operating system environment — and that is the same ANTHROPIC_* namespace Claude Code already owns. Writing it here would move Claude Code's endpoint too. %APPDATA%\Claude\claude_desktop_config.json is never opened; it is your MCP servers and preferences, not provider config.
Each checkbox in Add provider → Advanced options writes exactly one key while it is ticked. These keys are U-Pool's, so unticking a box deletes its key on the next switch rather than leaving it behind.
| Checkbox | App | Written |
|---|---|---|
| Bypass permission prompts | Claude Code | permissions.defaultMode = "bypassPermissions" — the settings form of --dangerously-skip-permissions |
| Skip the bypass warning screen | Claude Code | permissions.skipDangerousModePermissionPrompt = true |
| Auto-accept file edits | Claude Code | permissions.defaultMode = "acceptEdits" |
| Trust MCP servers from the project | Claude Code | enableAllProjectMcpServers = true |
| Bypass approvals & sandbox | Codex | approval_policy = "never" + sandbox_mode = "danger-full-access" — the pair --dangerously-bypass-approvals-and-sandbox sets |
| Live web search | Codex | web_search = "live" at the root (the [tools] boolean form is a no-op in Codex) |
A provider with bypass on is badged Bypass in the list, so you can see it without opening the form. Switching to the official entry clears all of it.
Settings → Updates asks api.github.com for the latest release at most once every six hours, and remembers the answer so an offline launch still knows about it. When there is something newer the Update button pulses; pressing it downloads the release zip, checks it against the length and — when GitHub published one — the SHA-256, unpacks it beside the install folder, and hands over to a small detached script. That script waits for U-Pool to exit, renames the old folder aside, renames the new one into place and starts it. If the second rename fails the first is undone, so a failed update leaves the version you had.
Two situations are refused rather than half-applied, with the reason shown next to an Open release page button instead: running from a source checkout (git pull + pip install -e . + npm run build is the update path there), and an install under Program Files, where Windows will not let U-Pool replace itself without admin rights. Move the folder somewhere you own and the in-place install works.
You can turn the check off entirely with the switch in the same section.
Settings → Startup writes one REG_SZ value named U-Pool under HKCU\Software\Microsoft\Windows\CurrentVersion\Run — no admin rights, no scheduled task, no shortcut. The remembered choice lives in ~/.u-pool/settings.json, and the entry is re-pointed at the current build on every launch so a moved bundle cannot leave a dead command behind.
If you switch U-Pool off under Task Manager → Startup apps, Windows keeps that decision: U-Pool reports "switched off by Windows" and offers a shortcut to the Windows page instead of quietly overriding you.
┌───────────────────────────────────────────────┐
│ Next.js (static export) ── window.pywebview │ UI
├───────────────────────────────────────────────┤
│ Api → Store → Adapters → live files │ Python
└───────────────────────────────────────────────┘
Requirements: Python 3.11+, Node 20+
pip install -e ".[dev]"
cd ui && npm install && npm run build && cd ..
python -m upool# terminal 1
cd ui && npm run dev
# terminal 2
UPOOL_DEV_URL=http://localhost:3000 python -m upoolOpen http://localhost:3000 in a browser without the Python bridge for an in-memory mock preview.
python -m pytest
cd ui && npm run buildTests redirect HOME into a temp folder — your real Claude / Codex configs are never touched.
python scripts/build.py # → dist/U-Pool/U-Pool.exe + dist/U-Pool-<ver>-win64.zip
python scripts/build.py --skip-ui # reuse an existing ui/outOnedir (not onefile) for faster startup. The zip and the SHA256SUMS.txt printed alongside it are what the in-app updater downloads, so both have to be attached to the GitHub release as assets.
| Variable | Purpose |
|---|---|
UPOOL_DEV_URL |
Load the UI from a dev server instead of the bundled export |
UPOOL_DEBUG=1 |
Open the webview DevTools |
UPOOL_HOME |
Move U-Pool’s own state directory |
UPOOL_FAKE_HOME |
Redirect ~/.claude / ~/.codex lookups (used by tests) |
- Backend — Python 3.11+, pywebview, tomlkit
- UI — Next.js 16 (static export), React 19, Tailwind 4, TypeScript
- Ship — setuptools, pytest, PyInstaller
Made with care by @UEdizaslan
