Skip to content

feat(cursor): spend view toggle for included vs billed usage - #996

Open
maxmilian wants to merge 3 commits into
robinebers:mainfrom
maxmilian:feat/cursor-usage-view-toggle
Open

feat(cursor): spend view toggle for included vs billed usage#996
maxmilian wants to merge 3 commits into
robinebers:mainfrom
maxmilian:feat/cursor-usage-view-toggle

Conversation

@maxmilian

Copy link
Copy Markdown

TL;DR

Adds a Cursor Spend View toggle (All / Included / API Usage) so the spend tiles and Usage Trend can show plan-included usage, billed on-demand usage, or the combined picture — implementing #609.

Closes #609.

What was happening

  • Cursor's export marks each row as plan-Included or billed (a dollar Cost), but OpenUsage dropped the Cost column and summed every row together.
  • The Today / Yesterday / Last 30 Days tiles and the Usage Trend therefore always mixed both kinds, so a user couldn't answer "how much of my included plan did I use?" vs "how much on-demand spend did I rack up?"

What this changes

  • Parse billing kindCursorUsageCSV reads the Cost column into a new CursorBillingKind (included / billed / unknown). The column is optional: exports without it still parse, with rows left .unknown.
  • Spend View setting — new CursorSpendViewSetting (all / included / api), a UserDefaultsBacked enum like TimeFormatSetting, persisted per device, default all (unchanged behavior).
  • FilteringCursorUsageMapper.appendSpendLines takes the view and filters rows before aggregating; a view with no matching rows reads "No data" rather than a fabricated $0. Filtered rows drop out of tiles, trend, and the model breakdown alike.
  • ProviderCursorProvider reads the setting at each refresh (so a flip applies on the next snapshot) and logs when a Cost-less export can't satisfy a non-All view.
  • UI — a Cursor-only Spend View card in the Cursor Customize detail (CursorSpendViewSection), following the APIKeysSection pattern; changing it forces a refresh.
  • Docsdocs/providers/cursor.md gains a Spend View subsection.

Heads-up

  • Per the issue's open question: the bounded meters (Total / Auto / API Usage %) intentionally stay as Cursor reports them — they come from Cursor's live API, not the export, and re-deriving them locally would contradict the dashboard. The toggle scopes to the CSV-derived spend tiles + trend only.
  • unknown rows (Cost-less exports) count only under All Usage; assigning them to a side would fake the split.

Tests

  • CursorSpendViewSetting filtering: All = $7 / 700 tok, Included = $1 / 100 tok, API = $2 / 200 tok on a mixed day; empty-after-filter → "No data".
  • Cost column parsing: Included/included → included, \$1.23/1.23 → billed, empty/absent column → unknown.
  • Full suite green apart from the 4 Codex tests already red on a clean main.

Screenshots

Cursor Customize detail → new Spend View card with the three-way picker:

spend view picker

🤖 Generated with Claude Code

https://claude.ai/code/session_019dpF8491eHK8reiuJ8vMxB

@maxmilian
maxmilian marked this pull request as ready for review July 13, 2026 16:32
@robinebers robinebers added the gate-passed Passed the automated PR gatekeeper checks (gatekeeper skips it on later runs) label Jul 13, 2026 — with Cursor
@robinebers

Copy link
Copy Markdown
Owner

Nice work — architecture and default look right (All Usage / .all preserves today's combined spend tiles; meters stay API-sourced; Cost-less rows only count under All).

Follow-up I'd take:

  1. Fix the label wrapSpend Tiles Show breaks onto two lines even with empty space before the picker. SwiftUI is letting the Text wrap so the Spacer can expand. Pin it with .lineLimit(1) (or .fixedSize(horizontal: true, vertical: false)).
  2. Shorten the row copy — the section is already titled Spend View, so Spend Tiles Show is redundant. Something like Show (or reuse Settings' plain row+picker shape) would read cleaner and reduce wrap pressure.
  3. Optional naming polish — picker option API Usage collides with Cursor's existing API Usage meter. Worth a later pass if that confuses people in practice (On-Demand / Billed?).

Not blocking on (3); (1)–(2) are the ones I'd land before or right after merge.

@maxmilian

Copy link
Copy Markdown
Author

Thanks for the review! Pushed 4ac3e81 addressing (1) and (2) together:

  • Row label is now just Show — the Spend View header already names the card, so the longer copy was redundant.
  • Pinned it with .fixedSize(horizontal: true, vertical: false) so the trailing Spacer can't squeeze the Text into a wrap.

On (3): agreed that API Usage colliding with Cursor's own API Usage meter is worth clearing up — I'd lean Billed (it's the on-demand/over-plan spend). Happy to fold that rename in here if you'd like it now, or leave it for a follow-up per your call since it touches the option labels users will see.

maxmilian and others added 3 commits August 4, 2026 00:10
Cursor's export marks each row as plan-included or billed (on-demand) via
the Cost column. Parse that into CursorBillingKind and add a per-device
Spend View setting (All / Included / API) that filters which rows the
spend tiles and Usage Trend aggregate; All Usage keeps today's combined
behavior. The bounded meters (Total/Auto/API %) stay as Cursor's API
reports them. Rows from exports without a Cost column can't be split, so
they only count under All Usage. Setting lives in the Cursor Customize
detail and forces a refresh on change.

Closes robinebers#609

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019dpF8491eHK8reiuJ8vMxB
The 'Spend View' section already titles the card, so the row label only
needs 'Show'. Pin it with .fixedSize(horizontal: true) so the trailing
Spacer can't squeeze it onto a second line (addresses review follow-up 1-2).
"API Usage" was already taken by the bounded meter Cursor reports, so a
picker option of the same name read as if it filtered that meter rather
than the spend tiles. The case is renamed alongside the label so the code
has one word for the billed side instead of two (addresses review
follow-up 3).
@maxmilian
maxmilian force-pushed the feat/cursor-usage-view-toggle branch from 4ac3e81 to 53176f5 Compare August 3, 2026 16:12
@maxmilian

Copy link
Copy Markdown
Author

Folded (3) in and rebased onto current main — 23 commits behind; the only two that touched these files are the Account-first passes over CustomizeProviderDetailView, which don't overlap the spend view, so the rebase was clean.

On (3) specifically: I asked back on 07-18 whether you wanted the rename now or left as a follow-up. Rather than hold the PR waiting on an answer, I've made the call and kept it trivially reversible — it's its own commit (53176f5), so dropping it leaves (1)–(2) intact if you'd rather ship the rename separately or not at all.

API UsageBilled. I renamed the enum case along with the label (case apicase billed) instead of only the user-facing string, so the code doesn't carry two words for the same side. The raw value moves with it; nothing to migrate, since this hasn't shipped.

Deliberately left alone: the bounded API Usage % meter, and the doc sentence describing Cursor's own export terminology ("plan-included vs billed API usage"). The collision was the picker borrowing the meter's name — Cursor's term for the billing side isn't itself wrong, so only the option that sat next to the meter needed to move.

swift build clean; swift test --filter CursorSpend 14/14 green.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs gate-passed Passed the automated PR gatekeeper checks (gatekeeper skips it on later runs) provider tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cursor: toggle between included usage and raw API usage

2 participants