Skip to content

U-Pool 0.8.0 - the Cursor account pool

Latest

Choose a tag to compare

@U-C4N U-C4N released this 06 Aug 07:25
· 11 commits to main since this release

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