Thanks for your interest in contributing! This guide will help you get set up and make sure your changes land smoothly.
- macOS 14 (Sonoma) or later
- Xcode 15+ / Swift 5.9+
- Python 3 (for the mock server, included with macOS)
make appThis builds the release binary via Swift Package Manager, bundles it as a .app, and codesigns it. The app also embeds Sparkle for update checks.
Sources/UsageKit/
├── UsageKitApp.swift # App entry point, menu bar setup
├── UsageService.swift # OAuth, polling, API calls
├── UsageModel.swift # API response types
├── UsageHistoryModel.swift # History data types, time ranges
├── UsageHistoryService.swift # Persistence, downsampling
├── UsageChartView.swift # Swift Charts trajectory view
├── PopoverView.swift # Main popover UI
└── MenuBarIconRenderer.swift # Menu bar icon drawing
| Command | What it does |
|---|---|
make build |
Release build via swift build |
make app |
Build + create .app bundle |
make zip |
Build + bundle + zip, then verify the release artifact |
make dmg |
Build + bundle + drag-to-Applications disk image, then verify it |
make release-artifacts |
Build once, then create and verify both ZIP and DMG artifacts |
make verify-release |
Inspect the packaged ZIP and DMG artifacts for required resources/frameworks |
make install |
Build + install to /Applications |
make clean |
Remove build artifacts |
Releases are tag-driven. Pushing a v* tag triggers the GitHub Actions workflow that:
- builds the release app bundle once
- produces both a ZIP (for Sparkle) and a DMG (for manual drag-to-Applications installs)
- verifies the packaged artifacts before publishing
- uploads those exact artifacts to the GitHub Release
- reuses GitHub-generated release notes for both the release body and the Sparkle update entry
- generates a signed Sparkle appcast from that zip
- deploys the appcast to GitHub Pages
One-time repository setup:
- Enable GitHub Pages with source
GitHub Actions - Add the
SPARKLE_PRIVATE_KEYrepository secret
Local source builds intentionally leave SUFeedURL unset, so Sparkle stays disabled unless your packaging flow injects a feed URL. This prevents forks and dev builds from auto-updating to upstream releases.
To export the current private key from your local Keychain:
.build/artifacts/sparkle/Sparkle/bin/generate_keys --account usagekit -x /tmp/usagekit.sparkle.key
gh secret set SPARKLE_PRIVATE_KEY < /tmp/usagekit.sparkle.keyA mock API server lets you test usage fetching and error handling against different scenarios without needing a real Anthropic account:
python3 scripts/mock-server.py --scenario extraTo connect the app to the mock server:
- In
UsageService.swift, change the endpoint:private let usageEndpoint = URL(string: "http://127.0.0.1:8080/api/oauth/usage")!
- Add local networking to
Resources/Info.plist:<key>NSAppTransportSecurity</key> <dict> <key>NSAllowsLocalNetworking</key> <true/> </dict>
- Rebuild and run the app, then click Refresh.
This only mocks GET /api/oauth/usage. The current app still uses Anthropic’s real OAuth/browser flow unless you separately rewire the auth endpoints.
Available scenarios:
| Scenario | Description |
|---|---|
normal |
Moderate usage (5h: 25%, 7d: 45%) |
high |
Near rate limit (5h: 85%, 7d: 92%) |
maxed |
Fully rate limited (100% / 100%) |
low |
Barely used (5h: 2%, 7d: 5%) |
extra |
Extra usage enabled ($52.30 / $280.00) |
extra_high |
Extra usage near limit ($94.50 / $100.00) |
per_model |
Per-model breakdown (Opus + Sonnet) |
all_features |
Everything: per-model + extra usage |
unauthenticated |
Returns 401 |
rate_limited |
Returns 429 with Retry-After |
error |
Returns 500 |
Remember to revert the endpoint and Info.plist changes before committing.
- Fork the repo and create a branch from
main - Keep PRs focused — one feature or fix per PR
- Test your changes with the mock server when relevant
- Make sure
make appbuilds without errors - Open a pull request against
main
- Follow the existing conventions in the codebase
- SwiftUI views in separate files, one primary view per file
- Keep
UsageServiceas the single source of truth for API state - Keep dependencies minimal — Sparkle is the only third-party runtime dependency
By contributing, you agree that your contributions will be licensed under the BSD 2-Clause License.