Skip to content

feat: quota tracking via /api/oauth/usage#37

Merged
hiskudin merged 2 commits into
mainfrom
feat/quota-tracking
May 14, 2026
Merged

feat: quota tracking via /api/oauth/usage#37
hiskudin merged 2 commits into
mainfrom
feat/quota-tracking

Conversation

@hiskudin

Copy link
Copy Markdown
Collaborator

Summary

Adds an in-panel Usage tab + threshold-crossing notifications for Claude Code subscription quotas. Mirrors what claude /usage shows in the TUI, but always-visible and proactive — alerts you when you're about to burn through your weekly cap.

How it gets the data

Claude Code's /usage slash command queries https://api.anthropic.com/api/oauth/usage and renders the bars itself. We do the same call directly:

  1. Read the OAuth token from the macOS Keychain (Claude Code-credentials). One-time system prompt grants access.
  2. Extract claudeAiOauth.accessToken from the JSON blob.
  3. GET /api/oauth/usage with the documented headers (Authorization: Bearer …, anthropic-beta: oauth-2025-04-20).
  4. Decode the response into a typed QuotaSnapshot { fiveHour, sevenDay, sevenDayOpus, sevenDaySonnet }.

No SwiftTerm, no TUI parsing, no claude subprocess. The endpoint is what Claude Code's own statusline depends on, so the data is the same Anthropic shows you.

What lands

Surface Behaviour
Usage tab (Cmd+3, between Sessions and Settings) Quota bars for each tier the user's plan has. Color-coded: green < 50%, yellow 50–80%, red ≥ 80%. Reset times per tier.
Poller 60s while the panel is visible; 5 min when hidden (configurable via STACKNUDGE_USAGE_POLL_MIN).
Threshold notifications One banner per (tier, period) when utilization crosses the user-configured threshold. State resets when the tier's resets_at advances (new period, fresh budget).
Settings → Usage New section with two rows: Quota alerts (toggle, default On) + Alert threshold (cycle: 50% / 70% / 80% / 90% / 95%, default 80%).
Failure modes No token / denied keychain / 401 / 429 → empty state in the Usage tab + silent stderr log. No retries on the first failure; recovered automatically on the next poll.

Files

  • panel/SessionUsage.swift (new) — QuotaSnapshot types, QuotaProbe (Keychain + HTTP), UsageView (SwiftUI tab content).
  • panel/Panel.swift — Adds .usage case in PanelContentView, Cmd+3 hotkey, the poller + threshold-evaluation logic.
  • panel/PanelNav.swift.usage PanelMode, snapshot + lastUpdated fields, new Settings rows for the alert toggle / threshold cycle, row-layout shifts.
  • panel/Settings.swift — New "Usage" section between Voice and Actions.
  • build.sh — Registers the new source.

Trade-offs / known gotchas

  • The endpoint is unofficial (Anthropic could change or remove it). The same risk applies to claude /usage, ClaudeBar, and every statusline tool. Multiple maintained projects rely on it today.
  • macOS-only. No Linux fallback; the Keychain access path is macOS-specific. Acceptable since stack-nudge is mac-native.
  • Ad-hoc-signed builds re-prompt for keychain on every rebuild. Resolved when we eventually sign with Developer ID — same path that helps the auto-updater.
  • Single threshold across all tiers (not per-tier). Intentional simplification for v1; multi-tier thresholds can come later if users ask.

Test plan

  • Merge → release-please cuts the next version
  • Install the new release; first launch shows a macOS Keychain prompt for Claude Code-credentials — click Always Allow
  • Cmd+3 → Usage tab renders bars matching what claude /usage shows in your terminal
  • Cmd+4 → Settings → Usage section shows two rows that toggle/cycle correctly and persist to ~/.stack-nudge/config
  • Lower the threshold to 50% (or whatever's just above your current util) → wait one poll → notification banner fires once
  • Toggle "Quota alerts" Off → no more banners even when threshold is exceeded

🤖 Generated with Claude Code

Adds a Usage tab and threshold-crossing notifications backed by the
unofficial /api/oauth/usage endpoint that Claude Code's own statusline
already consumes. No SwiftTerm or TUI parsing — the OAuth access token
is read from the macOS Keychain (one-time system prompt to grant
access), and the JSON response is decoded into a typed QuotaSnapshot.

Surfaces:

- QuotaProbe: reads Keychain entry "Claude Code-credentials", extracts
  claudeAiOauth.accessToken, calls api.anthropic.com/api/oauth/usage
  with the documented headers (Authorization Bearer + anthropic-beta
  oauth-2025-04-20). Failures (missing token, denied access, 401, 429)
  return nil and log to stderr — soft signal, never fatal.

- Poller in PanelController: 60s cadence while panel is visible, 5 min
  by default when hidden. Configurable via STACKNUDGE_USAGE_POLL_MIN.

- Usage tab (Cmd+3): renders bars for "Current session" (5h), "Current
  week (all)", "Current week (Opus)" / "(Sonnet)" if the user's plan
  has those tiers. Color-coded green / yellow / red. Reset times shown
  per tier.

- Threshold notifications: when any tier crosses the configured
  threshold, a UNNotification banner fires — once per period per tier
  (state resets when resets_at advances). Master switch + threshold
  selection (50/70/80/90/95) live in the new Settings → Usage section.

Cmd+1/2/3/4 now jumps Events / Sessions / Usage / Settings.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@StuBehan StuBehan left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

- Drop ExtraUsage type + parser — it was rendered nowhere after the
  earlier UI removal, just dangling code.
- Cache RelativeDateTimeFormatter statics in UsageView + Panel so
  SwiftUI re-renders don't allocate one per call.
- Render fiveHour/sevenDay only when present (matching the Opus/Sonnet
  pattern); fall back to the empty state when the whole snapshot is
  blank. The previous "Not available on this plan" was misleading
  since those tiers ARE always available — a nil indicates a probe
  issue, not a plan tier.
- Invalidate quotaTimer in applicationWillTerminate.
- Fix copy-paste comment in QuotaProbe header.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@hiskudin hiskudin merged commit 6bad9a7 into main May 14, 2026
4 checks passed
@hiskudin hiskudin deleted the feat/quota-tracking branch May 14, 2026 16:03
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