vibe-check

Vibe Check

A native macOS menu bar app that shows your AI-tool quota remaining for the current billing cycle and its subcycles — with live countdown timers to each reset.

Supported providers:

The menu bar always shows the most constrained quota across all connected providers — the one you’d run out of first — with its reset countdown.


Requirements


Build

cd ai-usage
./scripts/build-app.sh

This produces dist/Vibe Check.app.

To install permanently:

cp -r "dist/Vibe Check.app" /Applications/

Run (development)

swift run

Note: On macOS 26 beta, swift run has a known CA rendering crash in MenuBarExtra. Use the built .app bundle instead.


Usage

  1. Launch the app — a chart icon appears in your menu bar showing until connected.
  2. Click the icon to open the usage panel.
  3. Click Connect — an embedded browser opens to claude.ai/settings/usage.
  4. Sign in to Claude if prompted, then wait for the usage page to fully load.
  5. Close the browser window — the app syncs your session and refreshes automatically.
  6. Stats appear — current session %, weekly session %, remaining capacity, and live countdown timers to reset.

The app auto-refreshes every 60 seconds. Click the ↺ button to refresh manually.

To quit: open the panel and click Quit at the bottom right (the app has no dock icon by design).


How It Works

Every provider maps its API into one canonical model: a ProviderSnapshot holding an ordered list of QuotaWindows (subcycles first, then billing-cycle windows), each with a used fraction, native quantities (requests / tokens / USD), and a reset timestamp. The UI, menu bar summary, refresh scheduling, and disk cache are written once against that model.

Claude and Cursor authenticate by embedding a WKWebView pointed at the vendor’s settings page. When you sign in, WebKit stores your session cookies; a WKHTTPCookieStoreObserver copies them into HTTPCookieStorage.shared in real time, so by the time you close the browser your session is available to URLSession. The app then calls the same internal APIs the vendor dashboards use:

GET  https://claude.ai/api/organizations/{org_id}/usage
GET  https://cursor.com/api/auth/me
GET  https://cursor.com/api/usage?user={sub}
POST https://cursor.com/api/dashboard/get-monthly-invoice
POST https://cursor.com/api/dashboard/get-hard-limit

Copilot uses a GitHub PAT (classic, copilot scope) against the endpoint the editor plugins call:

GET https://api.github.com/copilot_internal/user

which returns quota_snapshots (premium_interactions, chat, completions) with entitlement, remaining, and the monthly quota_reset_date.

When a fetch fails, the app keeps showing the last good snapshot with a “stale” badge, backs off its refresh interval, and — on parse failures — writes the raw response body to ~/Library/Application Support/VibeCheck/captures/ so the parser can be fixed against real data.


Architecture

Sources/
├── AIUsageApp.swift              # NSStatusItem + NSPopover entry point (no MenuBarExtra)
├── AppModel.swift                # @MainActor state; per-provider refresh loops with backoff; snapshot cache
├── Models.swift                  # Canonical model: ProviderSnapshot, QuotaWindow, ProviderStatus
├── UsageService.swift            # ClaudeProvider — claude.ai internal usage API
├── GitHubCopilotService.swift    # CopilotProvider — copilot_internal/user quota endpoint
├── CursorService.swift           # CursorProvider — cursor.com dashboard API
├── CookieStore.swift             # WKWebsiteDataStore → HTTPCookieStorage bridge
├── AuthWindowManager.swift       # Claude auth window + real-time cookie observer
├── CursorAuthWindowManager.swift # Cursor auth window
├── KeychainStore.swift           # PAT storage
├── Config.swift                  # Feature flags + RawCapture debug dumps
└── Views.swift                   # Generic provider card + quota gauges in NSPopover

Why NSStatusItem instead of MenuBarExtra? SwiftUI’s MenuBarExtra has a rendering regression on macOS 26 beta (NSConcretePointerArray crash in CA::Context::commit_transaction). NSStatusItem + NSPopover is the pre-SwiftUI, stable alternative that bypasses this entirely.


Re-authentication

Session cookies expire eventually. When stats stop updating or you see “Sign in required”, click Connect again to re-authenticate.


Privacy

No data leaves your machine except to claude.ai (the same requests your browser makes). No telemetry, no external services, no API keys stored anywhere.