feat(openrouter): add OpenRouter usage provider#763
Conversation
Adds a native OpenRouter provider to the Swift edition (issue #578). Reads an API key from OPENROUTER_API_KEY or ~/.config/openusage/openrouter.json, then calls GET /credits (balance, required) and GET /key (tier, period spend, and an optional per-key cap — best-effort). Maps to a Credits meter + Balance (primary), with Today / This Week / This Month / Key Limit below the caret. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…t on upgrade Below-the-fold (expanded) membership was only seeded on a genuinely fresh install, so a default-expanded metric added after release was auto-enabled but landed above the fold for every existing layout. seedNewDefaultMetrics now reports the ids it newly auto-enabled, and init unions the default-expanded ones into expandedMetricIDs (guarded to brand-new metrics only, so a metric the user already lived with is never silently hidden). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 180e851. Configure here.
|
Want your agent to iterate on Greptile's feedback? Try greploops. |
…presence
The migration persists an expanded set when it tucks a new default-expanded
metric below the caret. That created a saved expandedMetrics key on the next
launch, which flipped init into the "saved set" branch and zeroed
defaultExpandedOnEnableIDs — so a legacy optional default-expanded metric (e.g.
cursor.requests) the user hadn't enabled yet would land above the fold instead
of below the caret. Compute the on-enable queue from the final expanded set
("default-expanded, not already a member, not placed") so it no longer depends
on whether an expanded set happens to be saved.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rland-68c013 # Conflicts: # Sources/OpenUsage/Providers/ErrorCategory.swift # Sources/OpenUsage/Stores/DefaultLayout.swift
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 35453c0d09
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…g file - Build the snapshot from whatever `/credits` and `/key` return instead of hard- requiring `/credits`: if it ever returns 403 (e.g. an endpoint gated to a management key) while `/key` succeeds, the spend rows still show rather than erroring out. Only fail when neither endpoint yields data, reporting an invalid key when either was rejected. - Check the config file before the environment variable, matching the documented precedence, so editing the config to rotate the key isn't shadowed by a stale OPENROUTER_API_KEY left in the app environment. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…g it Recomputing defaultExpandedOnEnableIDs from defaults every launch resurrected a fallback the user had already consumed by moving a disabled default-expanded metric above the divider — so enabling it later forced it back below the caret against the saved order. Persist the queue (seeded once, consumed durably on enable / divider move / reset / undo) and load it as-is, re-filtering only for metrics since placed or expanded. This also keeps the queue intact when the OpenRouter migration persists an expanded set, so a legacy optional metric still enters below the caret on the next launch. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rland-68c013 # Conflicts: # README.md
There was a problem hiding this comment.
robinebers has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.

TL;DR
Adds a native OpenRouter usage provider to the Swift edition (closes #578): a Credits meter + Balance above the fold, with daily/weekly/monthly spend and an optional per-key cap below the caret. Along the way it fixes a latent layout bug where below-the-fold metrics added after release surfaced above the fold for existing users.
What was happening
DefaultLayout.expandedMetricIDs. OpenRouter is the first provider to add several below-fold metrics post-release, so it's the first to expose this.What this changes
New OpenRouter provider (
Sources/OpenUsage/Providers/OpenRouter/)OpenRouterAuthStore— reads the API key fromOPENROUTER_API_KEY(orOPENROUTER_KEY), else~/.config/openusage/openrouter.json(apiKey/api_key/key, or a plain-text key file). OpenRouter is the first provider needing a user-supplied key — it has no companion CLI/app that leaves a credential behind. The config file is the reliable path since the GUI app doesn't inherit the shell env.OpenRouterUsageClient—GET /api/v1/credits(required) +GET /api/v1/key(best-effort), Bearer auth.OpenRouterUsageMapper— Credits meter (total_usage/total_credits), Balance (remaining), Today/This Week/This Month spend (real$0.00shown, never "No data"), optional Key Limit meter when the key has a cap. Tier (is_free_tier) maps to the snapshotplan("Pay as you go" / "Free tier"), not a separate tile.AppContainer(alphabetical tail), withErrorCategoryconformances, a provider mark (Resources/ProviderIcons/openrouter.svg) + SF Symbol fallback.docs/providers/openrouter.md) + README entry.Layout migration fix (
LayoutStore)seedNewDefaultMetricsnow reports the ids it newly auto-enabled; init unions the default-expanded ones intoexpandedMetricIDsso they enter below the caret on upgrade. Guarded to brand-new metrics only, so a metric the user already lived with is never silently hidden. This is broader than OpenRouter — it corrects placement for any future below-fold metric addition.Heads-up
LayoutStorechange touches the shared seeding path for every provider — the new regression test plus the existing 40LayoutStoretests cover it, but worth a careful look.total_creditsis lifetime credits purchased, so the Credits meter is a lifetime burn-down (subtitle "$X purchased"), not a recurring quota. Balance is the more glanceable number and is also primary.Tests
/key/ auth failure, full refresh).LayoutStoreregression test for the upgrade path./v1/usage/openrouter):plan: Pay as you go, Credits$178.20 / $277.47, Balance$99.27, spend rows real zeros, Key Limit correctly omitted (no cap). Confirmed in the running app that the four spend rows seed below the caret and Credits/Balance stay primary.Screenshots
Note
Medium Risk
OpenRouter is isolated, but LayoutStore touches shared seeding/persistence for all providers; API keys are read from disk/env and sent as Bearer tokens to OpenRouter only.
Overview
Adds OpenRouter as a first-class provider: API keys from
~/.config/openusage/openrouter.json(over env), independentGET /creditsandGET /keycalls with partial-failure tolerance, and widgets for credits, balance, period spend, and optional key limit. Registered in the app with default layout (Credits pinned primary), icon, telemetry error mapping, and provider docs.LayoutStore changes fix upgrade behavior when new default-expanded metrics ship: auto-enabled metrics from
seedNewDefaultMetricsare added toexpandedMetricIDs, and the expand-on-enable queue is persisted (expandOnEnableKey) so legacy optional metrics still land below the caret, explicit drags consume queue entries across relaunch, and undo/reset persist the queue.README now lists OpenRouter and notes it is the exception that requires a user API key.
Reviewed by Cursor Bugbot for commit f5f55a0. Bugbot is set up for automated code reviews on this repo. Configure here.
Confidence Score: 5/5
Safe to merge. The new OpenRouter provider is well-isolated, and the LayoutStore migration fix is carefully guarded to only affect brand-new metrics.
The two-endpoint fetch is correctly resilient to partial failures (each leg is independent). The expandOnEnableKey persistence correctly solves the legacy-layout upgrade path that the previous review raised, and the three new regression tests confirm all three branches. The default layout placement (Credits/Balance primary, period spend and Key Limit secondary) matches the documented intent. No data-loss, auth, or layout-corruption paths were found.
No files require special attention.
Comments Outside Diff (1)
Tests/OpenUsageTests/LayoutStoreTests.swift, line 497-529 (link)defaultExpandedOnEnableIDsbehaviourThe new regression test exercises the branch where
layout.expandedMetricsis already saved (savedExpandedexists). There is a second branch —hasStoredLayout && !savedExpanded— for users who have a stored layout but predate the expanded-metrics feature, whereexpandedMetricIDsstarts empty andinitialDefaultExpandedOnEnableIDsis populated with unplaced default-expanded metrics such ascursor.requests.For users in that branch, the migration fix now unions the new OpenRouter below-fold metrics into
expandedMetricIDsand persists that set. On the next launch they enter thesavedExpandedpath, whereinitialDefaultExpandedOnEnableIDsis always seeded as[]. Any default-expanded metric they hadn't yet enabled (e.g.cursor.requests) will then appear above the fold when first enabled, instead of below the caret as originally intended. The effect is narrow — users must be in that exact old state and then enable one of those metrics after upgrading — but a test covering this path would confirm the intended trade-off is deliberate.Prompt To Fix With AI
Reviews (5): Last reviewed commit: "fix(layout): persist the expand-on-enabl..." | Re-trigger Greptile