Skip to content

Releases: U-C4N/U-Pool

U-Pool 0.8.0 - the Cursor account pool

Choose a tag to compare

@U-C4N U-C4N released this 06 Aug 07:25

A sixth tab: a pool of Cursor accounts. Paste cookies, see who each account is and what it has spent, and switch between the ones you have signed into Cursor.

U-Pool 0.8.0

The Cursor pool

A Cursor account is not a provider — no base URL, no API key, no model, just a session token — so it gets its own tab rather than a provider form. The five existing apps are untouched.

Add account is one paste box (or a file picker) that takes whatever you have: a Netscape cookies.txt line, a whole Cookie: header, a bare WorkosCursorSessionToken=…, a raw user_…::token, an email,token CSV row, or JSON at any nesting. Paste one line or two hundred; unrecognised lines are counted and skipped, so a whole browser export works as-is. Accounts are keyed on the user id inside the cookie, not the email, so re-pasting a rotated cookie refreshes that account in place instead of duplicating it. A credential with no user_…:: half is skipped — that is the usual reason a line lands in the skipped count.

Every account refreshes in the background on launch, against the endpoints cursor.com's own dashboard calls — /api/auth/me for name, email and avatar, /api/auth/stripe for the plan, /api/usage-summary (or the legacy /api/usage for a request-quota plan) for the meter. Those endpoints are undocumented and will change: a figure that was never learned renders as , and one that stops arriving keeps its last value rather than blanking — a Cursor outage must not wipe a card. A cookie cursor.com rejects marks the row Expired and disables its button; a timeout or a 5xx leaves it alone.

What a cookie can and cannot do

A cookie exported from a browser reads the account — name, plan, usage — but it is a web token, and writing one into Cursor's database makes the desktop app reject it and sign itself out. Only the session token Cursor writes when you sign into an account in the app itself signs the desktop client in. So:

  • U-Pool banks that session automatically the moment you are signed in — the account you are already using is added to the pool on launch and marked in use, so a first switch can never throw away a session you have no other copy of.
  • The accounts you can switch to are the ones you have signed into Cursor at least once (or whose session token you paste directly).
  • A browser-cookie row shows its usage but Use is disabled, with a note saying so, rather than signing you out.

Turning a browser cookie into a session token — Cursor's own deep-login exchange — is proven possible and planned, but not in this release.

What a switch touches

Use closes Cursor (only if it is running), copies state.vscdb beside itself as state.vscdb.backup, writes eight keys, and starts Cursor again (only if U-Pool was the one that closed it).

Owned seven cursorAuth/* rows in ItemTable, plus glass.lastSignedInAuthId
Never opened cursorDiskKV and composerHeaders — your Cursor conversations
Never read every other ItemTable row, including the live mcpOAuth.secret.* credentials
Not touched storage.json and %APPDATA%\Cursor\machineId — the telemetry ids
Measured, deliberately not owned cursorAuth/onboardingDate, which a pasted cookie cannot supply

Ownership is by eight keys named in full, never by the cursorAuth/ prefix — a prefix sweep would take whatever Cursor adds under it next. Every statement in the writer is a SELECT or an INSERT OR REPLACE on one of those keys. There is no DELETE, no DROP, no VACUUM, no rewrite of the file. Resetting the machine ids is not part of switching accounts — other switchers do it to defeat a per-device limit, and those values are Cursor's own state.

There is no force kill. Closing Cursor is the same request the quit menu sends. If it is still there after ten seconds, not one byte is written — not even the backup — and the switch tells you to close it yourself. The failure mode of a half-applied auth record is an editor that can neither sign in nor sign out.

The key list was measured, not guessed

Cursor 3.14.7 was snapshotted signed out (100 ItemTable rows), signed in, and snapshotted again (148). Fifty rows appeared and eighteen changed; almost all of that is the session — window layout, theme, 594 KB of pushed experiment config. Eight rows are the account. Four things that measurement settled and guesswork would have got wrong:

  • Values are stored raw, not JSON-encoded. cachedEmail is you@example.com, not "you@example.com". Only cachedScopedProfile is JSON, being an object — and the avatar lives inside it, which is why a switch does not blank the account-menu picture.
  • accessToken and refreshToken hold the same JWT, so the single credential a cookie carries is exactly what Cursor keeps.
  • The Electron cookie jar is empty while signed in — state.vscdb alone carries the session, so the jar is never opened.
  • cachedScopedProfile and stripeSubscriptionStatus are new in the 3.x builds and in no existing switcher. Writing the ecosystem's familiar five-key list would have left the previous account's name and subscription status under the new account's token.

glass.lastSignedInAuthId is the one owned key outside the cursorAuth/ prefix, and widening the claim to reach it is stated rather than quiet: it holds the JWT's sub verbatim, and leaving it would pair account B's token with account A's id. These constants come from one sign-in on one build, not from documentation.

One fix that matters if you are on 0.7.0

clis.refresh_async(force=False) took a lock and then called snapshot(), which takes the same non-reentrant lock. Cold, the branch is skipped and nothing notices; warm, the process stops dead. bootstrap is the only force=False caller and normally runs once — but a webview reload calls it a second time, and that hung the app with a blank window and nothing in any log. Fixed here, with a regression test that fails by timing out rather than by asserting.

Also

  • Session tokens live in ~/.u-pool/cursor.json in plain text — the same exposure config.json already carries for provider keys — and the token never leaves the backend: the UI is told only whether one is present.
  • The account currently in use cannot be deleted; that row holds U-Pool's only copy of the token now in state.vscdb.
  • The tab is useful with no Cursor installed — accounts still paste and refresh, only Use needs a database.
  • Cross-platform where it can be: Cursor resolves ~/Library/Application Support/Cursor on macOS and ~/.config/Cursor on Linux, though only the Windows path has been exercised.
  • 434 tests, up from 267 at 0.7.0. No new dependency — stdlib urllib and sqlite3 throughout.

Not verified

No switch between two different accounts has been applied to a live install. A round-trip against the real 148-row database — pool the account already signed in, refresh it, switch to it — came back byte-identical on all eight owned keys, with onboardingDate untouched, the row count unchanged and state.vscdb.backup in place. That proves Cursor's own sign-in state can be reproduced exactly. It cannot, on its own, prove Cursor honours a different account's keys on next start. If a switch ever goes wrong, the backup is a rename away in a folder you can already open.

Install

Download U-Pool-0.8.0-win64.zip, unzip anywhere you own (not Program Files — Windows will not let U-Pool replace itself there on update), and run U-Pool.exe.

SHA-256  36017b80aaf972f866a540f4396008967dddd7c79c623964f5f6ba5b5cce3b60

Full changelog: v0.7.0...v0.8.0

U-Pool 0.7.0

Choose a tag to compare

@U-C4N U-C4N released this 01 Aug 19:17

Two new app tabs, a way to clear your transcripts, and the installed CLI versions in the header.

U-Pool 0.7.0

Hermes and OpenCode

Both are full app targets now, with their own config writers — not preset chips on a Claude form.

App Target What a switch writes
Hermes %LOCALAPPDATA%\hermes\config.yaml One entry under providers:, plus model.provider and model.default
OpenCode ~/.config/opencode/opencode.json One entry under provider, plus $schema and the top-level model

A switch removes only the entry U-Pool wrote last time. The Codex adapter clears every [model_providers.*] table but the active one, because Codex was reading two at once. Doing that here would delete the providers you added by hand, so it does not: ownership is recorded per entry in ~/.u-pool/env-owned.json.

config.yaml is spliced rather than re-serialised. U-Pool parses it to read, then swaps new text into the line ranges of the two sections it owns — the other thirty-odd sections of a 754-line document come back byte for byte. Measured against a real config: 13 lines changed out of 754.

Hermes needs a transport (anthropic_messages, chat_completions, codex_responses, bedrock_converse) and OpenCode an npm SDK package, because neither infers the protocol from the URL. The preset chips set both.

Neither writes to HKCU\Environment — both read their key from their own config file.

Delete all sessions

Settings → Sessions has one red button per CLI, with the file count and size measured before you press it.

Claude Code ~/.claude/projects, ~/.claude/sessions, ~/.claude/history.jsonl
Codex ~/.codex/sessions, ~/.codex/archived_sessions, ~/.codex/history.jsonl, ~/.codex/session_index.jsonl

That table is the entire scope. Both CLIs keep transcripts in the same folder as their credentials, so settings.json, .credentials.json, auth.json, config.toml, plugins, skills and file-history are untouched. Nothing is backed up first, and the confirm dialog names every path before it deletes.

CLI versions

The header shows the installed Claude Code and Codex versions; the refresh button re-probes. shutil.which is tried first, then the places a global install actually lands — a window launched from Explorer or the sign-in entry inherits the PATH as it stood at logon, not the one your terminal has.

A tool found on disk that cannot report a version says so, rather than being reported as missing. Settings → Installed CLIs shows the resolved path for each.

Also

  • The header is two rows: wordmark, versions and buttons above, the five app tabs below.
  • Upgrading from 0.6.0 seeds the new tabs with their official entry instead of leaving them empty.
  • Real brand marks for Hermes and OpenCode.
  • New dependency: PyYAML — the first since 0.5.0.

Install

Download U-Pool-0.7.0-win64.zip, unzip anywhere you own (not Program Files — Windows will not let U-Pool replace itself there on update), and run U-Pool.exe.

SHA-256  a3af8fe27e50dc13168f14e135965c6fe0d31dc66c40951813df8fd003b99006

Full changelog: v0.6.0...v0.7.0

U-Pool 0.6.0 — surgical config writes, Windows environment, Claude Desktop

Choose a tag to compare

@U-C4N U-C4N released this 31 Jul 21:56

Switching provider in 0.5.0 rewrote the target CLI's config file from the provider
record alone. That removed the leftovers it was meant to remove and everything else
with them — your plugins, your theme, your MCP servers, your project trust levels.

0.6.0 only touches the keys it owns.

Surgical writes

~/.claude/settings.json keeps enabledPlugins, extraKnownMarketplaces, theme,
model, effortLevel, hooks, statusLine and permissions.allow / deny
through a switch. U-Pool manages the env block and three toggle keys, nothing else.

~/.codex/config.toml keeps [mcp_servers.*] with its env tables, [plugins.*],
[projects.*] trust levels, notify, [windows], [features] and
[shell_environment_policy]. U-Pool manages model_provider, model, the
top-level knobs a provider supplies, the bypass pair, web_search, and the
[model_providers] tables — where only the active provider's table survives, since
stale sibling tables were the original bug.

A file that will not parse now refuses the switch rather than being overwritten.

Windows environment variables

Rewriting files was never enough. Codex resolves env_key against the process
environment, so a config.toml naming env_key = "codefast" reads that variable
and ignores what U-Pool wrote. Claude Code honours ANTHROPIC_* from the
environment too. A switch now updates HKCU\Environment and broadcasts
WM_SETTINGCHANGE, so the next shell is on the provider you picked without a
sign-out.

It removes only the names it set itself. Your own MINIMAX_CN_API_KEY or
YUNWU_API_KEY in the same key are not U-Pool's to delete, and neither is an
ANTHROPIC_AUTH_TOKEN you added to settings.json by hand. Switching provider
still replaces the variables that name the account — that is what a switch means.

auth.json

~/.codex/auth.json is written from scratch. A provider with a custom env_key
leaves it empty and takes its key from the environment instead, so the old "set that
variable yourself" warning is gone. A ChatGPT login found there is stashed first and
restored when you switch back to the official provider.

Backups

One current copy beside the original — settings.json.backup,
config.toml.backup, auth.json.backup — overwritten on every change. A switch in
Settings turns it off, and nothing is written while it is off. This replaces the
ten-deep rotation under ~/.u-pool/backups/.

Claude Desktop

A third tab in the header. It writes nothing in this release. Claude Desktop has
no base-URL setting and its only lever is the OS environment, which it shares with
Claude Code, so the tab stores providers and says so rather than guessing.
%APPDATA%\Claude\claude_desktop_config.json is never opened.

Also

  • The provider form showed Add provider on the save button while editing. It now
    reads Save changes, and one provider's state can no longer survive into the next.
  • "CC Switch" wording removed from the preset picker.
  • paths.home() refuses to resolve the real home during a test run, and
    scripts/sandbox.py runs one-off scripts against a throwaway home and a scratch
    registry key.

212 tests. The decisive ones rebuild a settings.json and a config.toml from the
shapes those files actually have in use and assert every foreign key survives.


Heads up: this release has not been through a code review pass. It is covered by
tests, and the bundle starts, but the switch has not been exercised by hand in the
built app. Your previous config files are copied to <name>.backup before the first
write as long as backup mode is on, which it is by default.

U-Pool 0.5.0 — clean config writes, CodeFast, in-app updates

Choose a tag to compare

@U-C4N U-C4N released this 30 Jul 09:23

The fix

Switching provider used to merge into the target CLI's config: it swapped the keys U-Pool recognised and wrote everything else back. The Codex adapter only retired the [model_providers.<slug>] table named by previous, which came from U-Pool's own config.json — so it was empty whenever that had drifted from what was actually on disk (a fresh install over an existing setup, re-applying the same provider, a hand-edited file).

Leftovers from an earlier provider then sat next to the new one — an orphan provider table, a stale ANTHROPIC_AUTH_TOKEN, a leftover preferred_auth_method — and the CLI read the two as one contradictory configuration.

Both adapters now write from scratch. After a switch, ~/.claude/settings.json and ~/.codex/config.toml hold exactly the provider you picked and nothing else.

What that costs

hooks, statusLine, permissions.allow / deny, [mcp_servers.*] and your comments are removed. Nothing goes silently:

  • the toast after a switch names what went away
  • the previous version is copied into ~/.u-pool/backups/<app>/ as before
  • the first clean write also keeps a permanent *.pre-0.5.0.keep copy that the ten-deep rotation never prunes

~/.codex/auth.json is the one exception and is still merged key by key — it is a credential store, not a provider config, and rewriting it would drop a ChatGPT login U-Pool cannot recreate.

CodeFast

Presets for both apps: https://api.codefast.app/claude-api for Claude Code and https://api.codefast.app/codex-api/v1 for Codex.

The Codex preset uses env_key = "OPENAI_API_KEY" rather than the documented "codefast". Only that name can be written into auth.json, so the documented form would need a manual export and would ship a preset that does not work out of the box. Change it in the form if you prefer their version.

In-app updates

Settings → Updates checks the releases page at most once every six hours and remembers the answer, so an offline launch still knows. When something newer exists the Update button pulses; pressing it downloads the zip, checks it against the published length and SHA-256 and every member's CRC, unpacks it beside the install folder, and hands over to a small detached script that waits for U-Pool to exit, renames the old folder aside, renames the new one in — and undoes the first rename if the second fails.

Two situations are refused with an explanation and an Open release page button rather than half-applied: running from a source checkout, and an install under Program Files, where Windows will not let U-Pool replace itself without admin rights.

Also

  • Fixed a bundle that could never start. src/upool/__main__.py used a relative import, and PyInstaller runs it as a top-level __main__ with no package around it — so dist/U-Pool/U-Pool.exe died on launch with attempted relative import with no known parent package. scripts/build.py now starts the built exe once before packaging, so this cannot ship unnoticed again.
  • scripts/build.py emits U-Pool-<ver>-win64.zip and SHA256SUMS.txt — the assets the updater downloads.

Download

U-Pool-0.5.0-win64.zip — unzip anywhere you own (not Program Files, or in-app updates cannot replace the folder) and run U-Pool.exe.

SHA-256  e55cc22b4256585f11f026ec9172a9cf5c6ce7713cb63ba1bd6da528573b6f46

U-Pool 0.4.0 — permission switches & launch at sign-in

Choose a tag to compare

@U-C4N U-C4N released this 29 Jul 07:52

Per-provider permission switches, and a launch-at-sign-in toggle for Windows.

Advanced options: checkboxes

Add provider → Advanced options now carries checkboxes that write real keys into the CLI's own config. Each box owns exactly one key while it is ticked, and takes that key back out when you untick it.

Checkbox App Written
Bypass approvals & sandbox Codex approval_policy = "never" + sandbox_mode = "danger-full-access" — the pair --dangerously-bypass-approvals-and-sandbox (--yolo) sets
Live web search Codex web_search = "live"
Bypass permission prompts Claude Code permissions.defaultMode = "bypassPermissions" — the settings form of --dangerously-skip-permissions
Skip the bypass warning screen Claude Code permissions.skipDangerousModePermissionPrompt
Auto-accept file edits Claude Code permissions.defaultMode = "acceptEdits"
Trust MCP servers from the project Claude Code enableAllProjectMcpServers

The flag itself is never written anywhere — the documented settings equivalent is, so the CLI starts that way without you passing anything on the command line.

What stays yours:

  • permissions.allow / deny / ask and everything else in that block are untouched.
  • A defaultMode U-Pool does not itself write (plan, auto) is left alone.
  • The Codex bypass pair is only reclaimed when both halves are present, so a lone hand-written sandbox_mode = "danger-full-access" survives.
  • A permissions value that is not an object is refused with a warning instead of being replaced.
  • Codex keys land at the root of config.toml, never inside [model_providers.<slug>] where Codex would ignore them.

First-run import reads every flag back out of an existing config, a provider running wide open is badged Bypass in the list, and switching to the official entry clears all of it.

Note on [tools] web_search = true: Codex parses that boolean and then discards it — it is a no-op. The switch that works is the top-level web_search = "live", which is what U-Pool writes.

Launch at sign-in (Windows)

Settings → Startup has an on/off switch. It writes one REG_SZ value named U-Pool under HKCU\Software\Microsoft\Windows\CurrentVersion\Run — no elevation, no scheduled task, no shortcut — and deletes it again when you switch it off. The command is quoted for CreateProcess (pythonw -m upool from source, the bundled U-Pool.exe when frozen) and repointed at the current build on every launch, so a moved bundle cannot leave a dead command behind. Your choice is remembered in ~/.u-pool/settings.json.

It only ever repoints — an entry you deleted with regedit or an autoruns tool stays deleted. And if you switch U-Pool off under Task Manager → Startup apps, Windows keeps that decision: U-Pool reports it and links you to the Windows page rather than quietly overriding you. The switch still removes the entry from here, veto or not.

Also

  • Version is single-sourced from upool.__version__.
  • 96 tests, including registry isolation so a test run can never touch your real startup entry.
  • New src/upool/settings.py and src/upool/autostart.py; new Checkbox and Switch primitives in the UI.

Heads-up when upgrading

These keys become U-Pool-managed. If you had set enableAllProjectMcpServers or web_search by hand and you switch provider with the box unticked, they are removed — the same deal the ANTHROPIC_* env vars have had since the first release. Every switch backs up the file it overwrites first, ten deep, under ~/.u-pool/backups/.


pip install -e ".[dev]"
cd ui && npm install && npm run build && cd ..
python -m upool

v0.3.0

Choose a tag to compare

@U-C4N U-C4N released this 27 Jul 15:55

U-Pool 0.3.0

Desktop provider switcher for Claude Code and Codex.

Highlights

  • Liquid Glass UI refresh
  • Curated provider presets (DeepSeek, Kimi, OpenRouter, MiniMax, Z.ai, Zhipu, Nvidia NIM, OpenCode Go, Xiaomi MiMo, Azure, xAI, OpenAI API)
  • Kadirr.Dev (Codex) and Yunwu (Codex + Claude) presets
  • Version badge in the app header

README is intentionally empty for now.