A lightweight macOS menu-bar app that shows your Upwork freelance earnings at a glance — total hours, total earnings, and per-project breakdowns rolled up by today / week / month / year.
- Lives in the menu bar, no Dock icon
- No third-party dependencies — pure Swift + SwiftPM
- Tokens stored securely in the macOS Keychain
- Requires macOS 14 (Sonoma) or newer
Watch the app feature walkthrough:
▶ Watch UpworkBuddy quick introduction
The video was built with HyperFrames and highlights the menu-bar dashboard, working-hour and earnings rollups, per-project breakdowns, goals and celebrations, focus music, exercise reminders, themes, and the open-source privacy promise.
Regenerate it from the repo root:
npx hyperframes preview docs/hyperframes/upworkbuddy-quick-intro
npx hyperframes render docs/hyperframes/upworkbuddy-quick-intro \
--output docs/hyperframes/upworkbuddy-quick-intro/upworkbuddy-quick-intro.mp4 \
--fps 30 \
--quality standardAfter rendering, the video is available at docs/hyperframes/upworkbuddy-quick-intro/upworkbuddy-quick-intro.mp4 and can be attached to releases, the GitHub repository page, or product announcements.
- macOS 14 (Sonoma) or newer
- Swift 6 toolchain (
swift --version≥ 6.0) - An Upwork developer app — register at https://www.upwork.com/developer/keys/apply
- Set the Redirect URI to
upworkbuddy://callback - Copy your
client_idandclient_secret
- Set the Redirect URI to
cp Sources/UpworkBuddy/Resources/Config.example.plist \
Sources/UpworkBuddy/Resources/Config.plistOpen Config.plist and fill in your Upwork credentials:
| Key | Value |
|---|---|
UpworkClientId |
OAuth client_id from developers.upwork.com |
UpworkClientSecret |
OAuth client_secret |
UpworkAPIBaseURL |
https://api.upwork.com/graphql |
UpworkAuthorizeURL |
https://www.upwork.com/ab/account-security/oauth2/authorize |
UpworkTokenURL |
https://www.upwork.com/api/v3/oauth2/token |
UpworkRedirectURI |
upworkbuddy://callback |
Config.plist is gitignored — your credentials will never be committed.
chmod +x Scripts/package-app.sh
Scripts/package-app.sh # produces .build/dist/UpworkBuddy.appcp -r .build/dist/UpworkBuddy.app /Applications/
open /Applications/UpworkBuddy.appNote: OAuth login requires the packaged
.app(notswift run) because URL-scheme handling only works through a signed bundle.
- Click the briefcase icon in the menu bar → Connect Upwork
- A browser tab opens — sign in and approve
- macOS redirects back to the app via the
upworkbuddy://callbackURL scheme - Tokens are stored in the macOS Keychain under
com.upworkbuddy.tokens
To verify stored tokens:
security find-generic-password -s com.upworkbuddy.tokens -a access -wswift build # type-check and build
swift test # run unit tests
swift run UpworkBuddy # run unbundled (OAuth callback not supported here)To rebuild and reinstall after changes:
Scripts/package-app.sh && rm -rf /Applications/UpworkBuddy.app && cp -R .build/dist/UpworkBuddy.app /Applications/Runtime preferences (refresh interval, currency, workspace) are stored in UserDefaults. Tokens live in the Keychain.
Two GitHub Actions workflows are included:
ci.yml— runs a debug build and tests on every push/PR tomainrelease.yml— fires onv*.*.*tag pushes; builds a universal.app, signs it with Developer ID, notarizes, packages a DMG, and publishes a GitHub Release
| Secret | Purpose |
|---|---|
BUILD_CERTIFICATE_BASE64 |
base64-encoded Developer ID .p12 |
P12_PASSWORD |
export password for the .p12 |
KEYCHAIN_PASSWORD |
any string — used for the ephemeral CI keychain |
NOTARY_APPLE_ID |
Apple ID email for notarytool |
NOTARY_TEAM_ID |
Your Apple team ID |
NOTARY_PASSWORD |
App-specific password from appleid.apple.com |
CONFIG_PLIST_BASE64 |
base64-encoded production Config.plist |
The packaging script signs with the Developer ID identity found in your keychain. Override with the SIGN_IDENTITY env var.
To notarize + staple, store credentials once:
xcrun notarytool store-credentials upworkbuddy-notary \
--apple-id <apple-id> --team-id <TEAMID> --password <app-specific-password>Then build with notarization:
NOTARY_PROFILE=upworkbuddy-notary Scripts/package-app.sh 1.0.0Without NOTARY_PROFILE, the app is signed but not notarized — Gatekeeper will check online on first launch.
- Hourly contracts only (
contractTimeReport.totalCharges) - USD display only
- Auto-selects your first Upwork organization
Fixed-price earnings, multi-currency support, and a workspace picker are planned for a future release.
MIT