Skip to content

feat(usage): track model-scoped limits from the API's limits array - #32

Open
mpecan wants to merge 2 commits into
eddmann:mainfrom
mpecan:feat/model-scoped-usage-limits
Open

feat(usage): track model-scoped limits from the API's limits array#32
mpecan wants to merge 2 commits into
eddmann:mainfrom
mpecan:feat/model-scoped-usage-limits

Conversation

@mpecan

@mpecan mpecan commented Jul 13, 2026

Copy link
Copy Markdown

Problem

The usage endpoint no longer populates the flat per-model fields — seven_day_sonnet, seven_day_opus and the rest return null, and no new ones are added. Model-specific caps now appear only as entries in the limits array, which name their own scope:

{ "kind": "weekly_scoped", "percent": 50, "resets_at": "...", "is_active": true,
  "scope": { "model": { "id": null, "display_name": "Fable" }, "surface": null } }

ClaudeMeter reads seven_day_sonnet, so it shows nothing for a model-specific cap.

Change

Read limits and render a card per scoped entry. The API supplies the display name, so a model released after this build needs no code change. The flat five_hour / seven_day fields remain as a fallback.

  • UsageData.sonnetUsage becomes a ScopedUsageLimit list
  • Settings renders one toggle per reported model, generated from the response
  • Visibility is opt-in (shown_scoped_models, empty by default): a new model is listed in Settings but stays out of the popover until switched on
  • Headline kinds are excluded from the scoped pass, so an entry the API later scopes cannot render twice

Compatibility

  • show_sonnet_usage: true migrates to shown_scoped_models: ["Sonnet"]; false or absent opts into nothing
  • usage.json moves to a UsageExportPayload owned by the repository layer. sonnet_usage is still emitted when a Sonnet limit exists; scoped_usage is the general form
  • Old on-disk caches still decode

Verification

Run against the live API: fetches, decodes limits, renders the card, writes scoped_usage to ~/.claudemeter/usage.json.

70 tests pass. The 8 MenuBarIconSnapshotTests failures are pre-existing, unrelated to this change, and fixed separately in #31.

Not covered

  • The spend object (extra-usage credits) is unsurfaced
  • Toggles key on display name; scope.model.id is currently null

🤖 Generated with Claude Code

mpecan and others added 2 commits July 13, 2026 19:12
The claude.ai usage endpoint no longer populates the flat per-model fields.
seven_day_sonnet, seven_day_opus and friends return null, and no new ones are
added — a model-specific cap now appears only as an entry in the `limits` array,
which names its own scope (scope.model.display_name, e.g. "Fable").

ClaudeMeter read seven_day_sonnet, so a model-specific cap showed nothing at all.

Read `limits` instead and render a card per scoped entry. Because the API names
its own scopes, a model released after this build surfaces with no code change.
The flat five_hour/seven_day fields are kept as a fallback.

- Replace UsageData.sonnetUsage with a ScopedUsageLimit list
- Settings renders one toggle per reported model, stored as an opt-out set so a
  new model appears rather than hiding behind a switch nobody knows to flip
- Migrate saved settings: show_sonnet_usage=false becomes hidden=["Sonnet"],
  which does not suppress other models
- Move the ~/.claudemeter/usage.json contract into a UsageExportPayload owned by
  the repository layer; sonnet_usage stays as a deprecated alias so existing
  statusline scripts keep working

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Store shown_scoped_models rather than hidden_scoped_models, defaulting to empty.

Opt-out meant a model the API started reporting would appear in the popover on
its own, which is the app deciding for the user. Opt-in keeps the popover to what
the user actually asked for; Settings still lists every model the API reports, so
a new one stays discoverable without an app update.

Migration follows the same rule: show_sonnet_usage=true becomes ["Sonnet"], and
false (or absent) opts into nothing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
mpecan added a commit to mpecan/rusted-claude-meter that referenced this pull request Jul 18, 2026
Write ~/.claudemeter/usage.json after every successful fetch, atomically
(temp file + rename), so existing statusline integrations built against
the Swift ClaudeMeter keep working when a user switches to this app. The
typed UsageExportPayload mirrors ClaudeMeter's schema (eddmann/ClaudeMeter#32):
scoped_usage is the general per-model form, and sonnet_usage stays as a
deprecated alias for the scoped "Sonnet" entry when one exists. Export
write failures are logged (eprintln!) but never fail the refresh itself.

Bundles the disk-cache and export paths into a new PersistPaths struct so
run_loop stays within the workspace's too-many-arguments lint threshold.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@mohitakahalo

Copy link
Copy Markdown

guys can we pls merge this and release a new build? Would be very helpful for newer subscription plans.

@mpecan

mpecan commented Jul 24, 2026

Copy link
Copy Markdown
Author

@mohitakahalo FYI: I gave up and built a new one: https://github.com/mpecan/rusted-claude-meter/

@mohitakahalo

Copy link
Copy Markdown

Thanks @mpecan you are a life saver!

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.

2 participants