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
Inspired by the original watch-ci-build bash script that watched CircleCI builds via GitHub API.
MIT License - feel free to modify and distribute.

