Skip to content

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

Open
maxmilian wants to merge 1 commit 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 1 commit 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

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
@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.

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