A native macOS menu bar application that monitors your GitHub pull requests and notifies you when builds complete.
Note: this is not production quality code. It's a developer tool, created to meet a very particular need. It's unrepentently vibe coded, and while I have used it for a week or so and found it pretty handy, I can't warrant whether it will work for your needs or, indeed, work at all.
Thanks to my team at Doximity for encouragement, support, and some tokens.
-
Live PR Monitoring: Displays all your open pull requests with real-time build status
-
Review Requests: Shows both PRs you authored and PRs awaiting your review (with a special indicator)
-
Auto-Refresh: Polls GitHub every 30 seconds (configurable 10-300s)
-
Watch PRs: Mark specific PRs to get notified when their builds finish
-
Inactive Branch Detection: Highlights PRs that haven't been updated in N days (configurable)
-
Smart Sorting: Optionally sort non-success PRs to the top of the list
-
Age Indicators: Shows how long ago each PR was last updated
-
Draft PR Support: Clearly identifies draft pull requests with a DRAFT badge
-
Native Notifications: macOS notifications with sound and voice announcements
-
Multi-Repository: Monitors PRs across all repositories you have access to
-
Build Status Icons (SF Symbols):
- ⚙️✓ Success — gear with checkmark (green)
- ⚙️✗ Failure/Error — gear with xmark (red)
- ⚙️ Pending — animated spinning gear (gray)
- ❗ Merge Conflict (purple)
- ⏳ Inactive (orange)
- ❓ Unknown (gray)
-
Review Decision Icons:
- 👤✓ Approved (green)
- 👤✗ Changes Requested (red)
- 👤? Review Required (gray)
Here's what it looks like in action:
- macOS 14.0 (Sonoma) or later
- GitHub CLI (gh) installed and authenticated
MonitorLizard uses gh under the covers to fetch PR data.
brew install ghgh auth loginFollow the prompts to authenticate with your GitHub account.
- Download the latest
.zipfrom the Releases page - Unzip and drag
MonitorLizard.appto your Applications folder - Launch the app — it's notarized, so no Gatekeeper warnings
- The app will appear in your menu bar with a lizard icon
- Grant notification permissions when prompted
The app checks for updates automatically and will notify you when a new version is available. You can also check manually via Check for Updates... in the menu bar.
- Open
MonitorLizard/MonitorLizard.xcodeprojin Xcode - Select your development team under Signing & Capabilities if needed
- Press ⌘R to build and run
- The app will appear in your menu bar with a lizard icon
- Grant notification permissions when prompted
Note: The app runs as a menu bar-only application (no Dock icon). Look for the lizard icon in your menu bar.
- Launch: Click the lizard icon in your menu bar
- View PRs: See all your open pull requests with their build statuses
- Refresh: Click the refresh button or wait for auto-refresh
- Open PR: Click any PR to open it in your browser
- Start Watching: Click the eye icon on any PR
- Get Notified: When the build completes, you'll receive:
- A macOS notification
- A sound effect (Glass.aiff for success, Basso for failure)
- Voice announcement: "Build ready for Q A" (for successful builds)
- Stop Watching: Click the eye icon again to unwatch
Click Settings to configure:
General:
- Refresh Interval: 10-300 seconds (default: 30s)
- Sort non-success PRs first: Show failing/pending/inactive PRs at the top
- Inactive Branch Detection: Enable detection and set threshold (1-90 days)
Notifications:
- Show Notifications: Enable/disable macOS notifications
- Play Sounds: Enable/disable sound effects
- Voice Announcements: Enable/disable and customize text-to-speech message
The app follows MVVM architecture with SwiftUI:
MonitorLizard/
├── Constants.swift # Centralized constants
├── Models/ # Data models
│ ├── BuildStatus.swift
│ └── PullRequest.swift
├── Services/ # Business logic
│ ├── GitHubService.swift # gh CLI wrapper
│ ├── ShellExecutor.swift # Process execution
│ ├── NotificationService.swift # Notifications
│ ├── UpdateService.swift # Sparkle auto-updates
│ ├── WatchlistService.swift # Persistent storage
│ └── WindowManager.swift # Settings window
├── ViewModels/ # State management
│ └── PRMonitorViewModel.swift
└── Views/ # UI components
├── MonitorLizardApp.swift
├── MenuBarView.swift
├── PRRowView.swift
└── SettingsView.swift
- Polling: Timer fires every N seconds (configurable)
- Fetch PRs: Executes two queries:
- PRs you authored:
gh search prs --author=@me --state=open - PRs awaiting your review:
gh search prs --review-requested=@me --state=open - Both queries fetch:
number,title,repository,url,author,updatedAt,labels,isDraft
- PRs you authored:
- Fetch Status: For each PR, executes
gh pr view N --json headRefName,statusCheckRollup,mergeable,mergeStateStatus,reviewDecision - Parse Status: Determines overall status from individual checks
- Priority: conflict > failure > error > changes requested > pending > inactive > success > unknown
- Inactive Detection: If enabled, marks PRs as inactive when
updatedAtexceeds threshold
- Display: Shows PRs with status icons, age indicators, labels, and review indicator
- Check Completions: Compares with previous status for watched PRs
- Notify: Sends notifications for completed builds
# Fetch PRs you authored
gh search prs --author=@me --state=open --json number,title,repository,url,author,updatedAt,labels,isDraft --limit 100
# Fetch PRs awaiting your review
gh search prs --review-requested=@me --state=open --json number,title,repository,url,author,updatedAt,labels,isDraft --limit 100
# Fetch PR details with status and merge state
gh pr view 123 --repo owner/repo --json headRefName,statusCheckRollup,mergeable,mergeStateStatus,reviewDecision
# Check gh CLI authentication
gh auth statusInstall gh CLI:
brew install ghAuthenticate:
gh auth login- Ensure you have open PRs:
gh pr list --author=@me --state=open - Check gh CLI version:
gh --version(should be 2.0+) - Try manual refresh
- Check System Settings > Notifications > MonitorLizard
- Ensure notifications are enabled in Settings
- Grant notification permissions when prompted
- Ensure macOS deployment target is 13.0+
- Check that all Swift files are added to the target
- Verify Info.plist is configured correctly
- Clean build folder: Product > Clean Build Folder (⇧⌘K)
swift testThe codebase is structured for easy extension:
- New PR filters: Modify
GitHubService.fetchAllOpenPRs() - Custom notifications: Extend
NotificationService - Additional UI: Add views to
Views/directory - New status types: Extend
BuildStatusenum and update priority logic inGitHubService.parseOverallStatus() - New settings: Add to
Constants.swift, create@AppStorageproperties inSettingsView, and wire through to services - Time-based features: Use
Constants.secondsPerDayfor date calculations
The scripts/release.sh script automates the full release process:
./scripts/release.shIt handles:
- Updating version numbers in Info.plist
- Building a Release archive
- Signing with Developer ID Application certificate
- Submitting to Apple for notarization
- Stapling the notarization ticket
- Signing the zip with Sparkle's EdDSA key
- Creating a GitHub release with the zip attached
- Updating
docs/appcast.xmlfor Sparkle auto-updates
After the script completes, commit and push so GitHub Pages serves the updated appcast:
git add MonitorLizard/Info.plist docs/appcast.xml
git commit -m "Release <version>"
git push- Apple Developer account with Developer ID Application certificate
- Notarization credentials stored via
xcrun notarytool store-credentials - Sparkle EdDSA signing key in Keychain (generated by
generate_keys) - GitHub CLI (
gh) installed and authenticated - GitHub Pages enabled on the repo, serving from
docs/onmain
Contributions are welcome! A few guidelines:
Please keep pull requests focused on a single feature, bug fix, or improvement. PRs that bundle multiple unrelated changes are harder to review and more likely to be rejected or asked to be split. If you're unsure whether your changes belong together, err on the side of separating them.
- Fork the repository and create a branch from
main - Build and run the app to confirm your starting point works
- Make your changes and run the test suite:
swift test - Open a pull request with a clear description of what changed and why
AI-assisted and AI-authored contributions are welcome, but the human submitting the PR is responsible for reviewing the changes, understanding what they do, and verifying that the app builds and works correctly before opening the PR. Unvetted AI output will not be merged.
- Bug fixes and reliability improvements
- New build status types or notification options
- Additional GitHub CLI query fields surfaced in the UI
- Improved test coverage
- Documentation fixes
- Changes that require a GitHub token or server-side component (the app intentionally uses only the
ghCLI) - Dependencies beyond Swift Package Manager packages
Follow the patterns already in the codebase: MVVM with @Published/Combine, actors for thread-safe services, @AppStorage for settings. Swift 6 strict concurrency is enabled — all new code must compile without warnings.
Inspired by the original watch-ci-build bash script that watched CircleCI builds via GitHub API.
MIT License - feel free to modify and distribute.

