Skip to content

feat(cursor): show remaining included usage from dashboard limits API - #273

Merged
chattymin merged 4 commits into
chattymin:mainfrom
florianbehrend:feat/cursor-limits
Sep 24, 2026
Merged

chattymin merged 4 commits into
chattymin:mainfrom
florianbehrend:feat/cursor-limits

Conversation

@florianbehrend

@florianbehrend florianbehrend commented Sep 6, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Fixes #272. Adds Cursor official limits to the popover — monthly included allowance, remaining dollars, and auto/API usage split — fetched from Cursor's undocumented Connect RPC endpoint on api2.cursor.sh.

  • Endpoint: POST /aiserver.v1.DashboardService/GetCurrentPeriodUsage (Bearer JWT + Connect-Protocol-Version: 1)
  • Auth: same local Cursor login as usage events (cursorAuth/accessToken in state.vscdb, or CURSOR_SESSION_TOKEN); on 401 retries once via cursorAuth/refreshToken
  • UI: Cursor tab shows monthly % bar, remaining spend, optional auto/API rows, billing-cycle reset countdown; menu bar limit line when Cursor was used today
  • Disable: respects CURSOR_USAGE_API=0 (shared with usage-events fetch)
  • Docs: README data sources + Privacy updated for api2.cursor.sh in en/ko/ja (13 outbound hosts)
image

Type of change

  • New feature
  • Bug fix
  • Refactor / cleanup
  • Documentation
  • Other:

Checklist

  • swift build passes locally
  • PR title and description are written in English
  • Tests added (CursorLimitsTests: JSON parsing, Bearer/Connect headers, used-percent fallback)
  • README Privacy + data sources updated

Test plan

  • swift build succeeds on macOS 14+
  • Signed-in Cursor session: popover Cursor tab shows Limits (official) with monthly % and remaining dollars matching dashboard
  • Expired JWT: session-expired notice appears; signing into Cursor IDE restores limits on next refresh
  • CURSOR_USAGE_API=0 skips both usage events and limits fetch
  • Menu bar limit line shows Cursor NN% when Cursor has usage today and limit display is enabled

Out of scope

  • Enterprise /auth/usage request-bucket plans
  • On-demand / team pooled spend (spendLimitUsage) UI

florianbehrend and others added 4 commits September 6, 2026 22:57
Fixes chattymin#272. Fetches Cursor's monthly included allowance from the
undocumented Connect RPC endpoint GetCurrentPeriodUsage on
api2.cursor.sh, using the same local Cursor login as usage events.

- CursorRateLimitsProvider with Bearer JWT auth and refresh retry
- Popover limits section: monthly %, remaining dollars, auto/API split
- Menu bar limit line when Cursor was used today
- README privacy/docs updated for api2.cursor.sh (en/ko/ja)
Swift requires memberwise initializer arguments to follow declaration
order; CI failed on totalPercentUsed before includedSpend.
Resolve README conflicts in all three locales: keep main's Aside provider
in the on-device list and re-add the api2.cursor.sh outbound host from this
branch (twelve -> thirteen hosts).
# Conflicts:
#	README.ja.md
#	README.ko.md
#	README.md
#	Sources/PokeTokenBar/Core/UsageStore.swift
#	Sources/PokeTokenBar/UI/PopoverView.swift
@chattymin
chattymin merged commit 777ee27 into chattymin:main Sep 24, 2026
2 checks passed
@chattymin

Copy link
Copy Markdown
Owner

Thank you for your contribution!

LuceteYang added a commit to LuceteYang/PikaTokenBar that referenced this pull request Sep 24, 2026
…1.8.0

- Settings → Advanced shot gains the Additional Claude accounts row (chattymin#327);
  generator footer bumped to 1.8.0.
- New pace-marker shots (en/ko/ja) from ScreenshotGenTests (chattymin#298), plus a
  feature row in each README.
- "Also in the box" lists Cursor included usage (chattymin#273) and bulk Rare Candy (chattymin#328).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
cte13 added a commit to cte13/PokeTokenBar-iOS that referenced this pull request Sep 24, 2026
…Cursor limits) (#66)

* fix(claude): prefer reported cost-state over price-table estimates (chattymin#318)

Claude Code writes its own cost ledger to each session file as cumulative
`type:"cost-state"` records, carrying per-model `costUSD` in `modelUsage`.
The reader ignored it and always priced Claude usage from `ModelPricing`,
so any model the table has no rate for aggregated as unavailable — even
though an exact amount was already sitting in the same file.

This is visible today: the table has no `claude-opus-5` or
`claude-sonnet-5` rate, and `modelUsage` keys carry a context-window
suffix (`claude-opus-5[1m]`) that `message.model` never has. On a heavy
Opus 5 profile that left the cost readout at "$—" while the source had
priced every request, `hasUnknownModelCost` false throughout.

Parse the last cost-state record per file (the ledger is cumulative) and
spread each model's session total across that model's entries in
proportion to tokens, feeding the existing `Entry.explicitCost` that
`Bucket.add` already prefers over an estimate. The session sum stays
exact, so only the split within one session is inferred and the amount
keeps `.source` coverage. Cost for a model that produced no parsed entry
is dropped rather than reassigned, so an unattributable amount can never
inflate another model's day.

Claude blobs previously invalidated on mtime/size alone, so add
`claudeParserVersion` — without it, entries cached by the old rule would
keep their cost-free values until each file happened to change.

Behaviour is unchanged where no ledger exists: table estimate, else
unavailable.

Claude-Session: https://claude.ai/code/session_01KwKz8zsXemuCwftTX2gN3k

* feat: tell shoppers what a released Pokémon keeps (chattymin#291)

The egg card only said the current Pokémon is sent off, so the natural
reading was that its Pokédex entry goes with it. It does not: buyEgg()
appends releasedDexEntry() before clearing the active Pokémon, so the
species stays in the Pokédex.

The odds are worth stating too, and in the other direction from what
you would guess. chooseBase() halves the weight of any base whose line
you have already completed, and that check reads collectedFinals, which
only graduation fills. Releasing leaves it untouched, so a released
Pokémon keeps its full hatch weight and can come back at the same odds.

Add eggReleaseNote directly under eggDescription, where the reader has
just been told what they give up. The price row stays for the price,
the button, and eggShopLockedHint. Shown only while there is a Pokémon
to release.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat(limits): track several Claude accounts (chattymin#327)

* feat(limits): show official limits for every Claude account

Two Claude Code logins side by side (CLAUDE_CONFIG_DIR) only showed the
default account's limits. Additional config folders are now detected
(~/.claude-* and ~/.claude_* folders Claude Code is logged in to, plus an
exported CLAUDE_CONFIG_DIR) or listed in Settings > Advanced, and each
account gets its own tab in the official limits.

- One token cache per folder: its .credentials.json, or the
  "Claude Code-credentials-<sha256 prefix>" Keychain item. Automatic polls
  never read the Keychain; a manual refresh may prompt once per folder and
  stops after a declined prompt (chattymin#280).
- Candy and threshold alerts cover every account's gauges. The default
  account keeps its historical keys, so existing saves carry over.
- A rejected token marks that account expired (dimmed values, retry
  banner) instead of pinning the refresh row. A 429 pauses that account
  only. The default tab stays while its limits load.
- The refresh row shows why a manual refresh failed.
- The profile cache keeps one identity per token.
- Menu bar percentage, warning state, companion mood, floating pet hover
  and the 5h forecast still follow the default account.

* feat(limits): pick which Claude account the menu bar follows

With several Claude accounts, the menu bar percentage, warning state,
companion mood, floating pet hover and 5h forecast followed the default
account only, even while working on another one. A "Tracked Claude
account" setting, shown when several accounts are listed, now drives them:

- Automatic (default): the account that received the latest prompt, read
  from the modification date of each config folder's history.jsonl, which
  Claude Code never shares between logins.
- The default account, a pinned account (back to automatic when it
  disappears), or the highest usage across accounts.

The tracked tab gets a dot, the 5h forecast shows in its tab, and each
additional account shows its own stale label after 15 minutes.

* feat(usage): split Claude tokens and cost between accounts

Transcripts carry no account and projects/ may be shared between logins,
but each login's history.jsonl lists its prompts with their session and
time. Usage entries now keep their session (from the transcript path,
also for blobs cached before this change, without a re-parse), and each
turn goes to the login that sent the latest prompt of its session before
it, so a session resumed on another login changes owner at that point.

Each account tab shows its today and month tokens and cost; turns no
history explains are counted as unattributed and shown under the tabs.
The header totals stay machine-wide. The default account's placeholder
tab now only appears when that login exists. README (EN/KO/JA) documents
the new data sources and the Keychain reads per config folder.

* fix(usage): scan every account folder and harden account refreshes

Account folders that keep their own projects/ were never scanned, so
their turns were missing from every total. Each config folder now adds
its projects/ to the Claude scan roots; a symlinked shared folder folds
into the default one.

Review fixes on the multi-account work:

- history.jsonl is read again only for its new complete lines, and a
  saved login is read again only when its file changes.
- The 5h forecast uses the tracked account's own block with several
  accounts, and the scan covers a block that started last month.
- Additional account refreshes run one after the other. Turning Keychain
  access off clears them. With a session key, only folders listed in
  Settings may prompt.
- The same email in another organization is another account. A folder
  of an account already shown adds its activity and usage to that tab;
  usage of an account without a tab is unattributed.
- An account first seen at its limit earns no candy until seen below it.
- Changing the tracked account updates the companion at once, an
  automatic success clears the manual refresh error, and the refresh row
  waits for default limits only when the default login exists.
- History, usage and last prompt dates are read from the user's folders
  only inside the app, like the folder discovery.

README (EN/KO/JA) lists the per-folder projects/ and the incremental
history read.

* fix(limits): name the account behind a manual refresh error

With several accounts the refresh error sits above the account tabs and
read as everyone's failure. It now names the default account, the only
one it is about. The 5h block row says it sums all accounts, the session
key hint says it only covers ~/.claude, and additional account refreshes
are logged like the default one.

* fix(limits): find folder logins exported with a trailing slash

Claude Code hashes CLAUDE_CONFIG_DIR as given, so a folder exported with a
trailing slash (as shell completion writes it) lives under another Keychain
item. Both names are tried; a missing item moves on, a cancelled prompt
stops the lookup.

The menu bar percentage names its account in a tooltip when there are
several, and the Settings refresh error names the account too and wraps
instead of being cut at two lines.

* fix(limits): say whose limit an alert is about without hiding the percentage

With several accounts, only the additional ones were named in alerts and
candy notifications, and the name sat before the percentage: the floating
pet bubble, cut at two lines, could lose the percentage. Every account is
now named next to others, alerts put the account after the percentage, and
the bubble drops an account name that does not fit.

* fix(settings): show automatic when the pinned Claude account is gone

The store already falls back to the last used account; the picker showed an empty selection instead.

* fix(settings): stop promising one Keychain prompt per account folder

macOS can ask twice per Keychain item (access, then partition), and again
after every "Allow" that was not "Always Allow", so the hint now says a
manual refresh may ask for each folder.

* fix(limits): give each account tab its own 5h block and explain an idle session

With several accounts the current 5h block summed every account and ended at
neither account's reset. Each tab now shows its own block, like the single
account view. A 5-hour window at 0% without a reset date has not started:
the row now says it starts with the next message instead of showing nothing.

* fix(limits): align each account's 5h block with its official window

The local block starts at the first turn of the last five hours, so its reset
rarely matched the account's real one. A tab now counts the turns of the
account's running official window and ends at its reset. With no session
running there is no block; expired or missing limits keep the rolling block.

* feat(cursor): show remaining included usage from dashboard limits API (chattymin#273)

* feat(cursor): show remaining included usage from dashboard limits API

Fixes chattymin#272. Fetches Cursor's monthly included allowance from the
undocumented Connect RPC endpoint GetCurrentPeriodUsage on
api2.cursor.sh, using the same local Cursor login as usage events.

- CursorRateLimitsProvider with Bearer JWT auth and refresh retry
- Popover limits section: monthly %, remaining dollars, auto/API split
- Menu bar limit line when Cursor was used today
- README privacy/docs updated for api2.cursor.sh (en/ko/ja)

* test(cursor): fix CursorPlanUsage initializer argument order

Swift requires memberwise initializer arguments to follow declaration
order; CI failed on totalPercentUsed before includedSpend.

---------

Co-authored-by: Patrick.Park <pdm001125@naver.com>

---------

Co-authored-by: DenisSeller <144274564+DenisSeller@users.noreply.github.com>
Co-authored-by: 오티스(otis.sh) <jae6120@naver.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Dylan LE FLOUR <dylan.leflour25@gmail.com>
Co-authored-by: Florian Behrend <56365738+florianbehrend@users.noreply.github.com>
Co-authored-by: Patrick.Park <pdm001125@naver.com>
Co-authored-by: turtle hermit <4173875+cte13@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(cursor): show remaining included usage from dashboard limits API

2 participants