Multi-repo Git dashboard in your macOS menubar
Monitor PRs, issues, CI failures, and local repo health — all at a glance.
┌──────────────────────────────────────────┐
│ GP 2❗ • 1✖ • 5 │ ← menubar with live counts
└──────────────────────────────────────────┘
┌──────────────────────────────────────────┐
│ GitPulse │
│──────────────────────────────────────────│
│ 👀 NEEDS YOUR REVIEW (2) │
│ ✅ #142 Fix auth timeout — api │
│ ⏳ #138 Add rate limiting — core │
│──────────────────────────────────────────│
│ 🔁 OPEN PULL REQUESTS (5) │
│ ✅ #201 Upgrade deps — frontend │
│ ❌ #199 New dashboard — backend │
│ • #195 Update README — docs │
│──────────────────────────────────────────│
│ 📋 MY ISSUES (3) │
│ #89 Fix login redirect • bug — app │
│ #72 Add dark mode • feature — ui │
│──────────────────────────────────────────│
│ ❌ CI FAILURES (1) │
│ ● deploy → main — backend │
│──────────────────────────────────────────│
│ 💻 LOCAL REPOS │
│ myapp [main] ● modified • ↑2 │
│ backend [dev] ✓ clean │
│──────────────────────────────────────────│
│ 🔓 ACCOUNTS │
│ ✅ GitHub connected │
│ Connect GitLab... │
│ Connect Bitbucket... │
│──────────────────────────────────────────│
│ Updated 45s ago │
│ ↻ Refresh Now ⌘R │
│ ⚙ Open Config ⌘, │
│ Quit GitPulse ⌘Q │
└──────────────────────────────────────────┘
# 1. Install
pip install gitbar
# 2. Launch
gitbar
# 3. Connect (click GP in menubar → Connect GitHub...)That's it. GitBar auto-detects your gh CLI login or walks you through a token setup. Your repos are discovered automatically.
| Feature | Description |
|---|---|
| Pull Requests | See all open PRs across every repo, with CI status indicators |
| Review Requests | PRs waiting for your review, highlighted at the top |
| Issues | Issues assigned to you across all repos |
| CI Failures | Failed GitHub Actions / GitLab pipelines at a glance |
| Local Repos | Dirty files, ahead/behind counts, current branch |
| Click to Open | Click any PR, issue, or CI run to open it in your browser |
| Notifications | Native macOS alerts for new PRs, review requests, CI failures |
| Multi-Provider | GitHub + GitLab + Bitbucket in one view |
| Secure | Tokens stored in macOS Keychain, never in plain text |
| Keyboard Shortcuts | R refresh, , config, Q quit |
graph TB
subgraph Menubar["macOS Menubar (PyObjC)"]
SI["Status Item: GP"]
Menu["NSMenu + NSMenuDelegate"]
end
subgraph Polling["Background Polling"]
Timer["NSTimer (configurable interval)"]
Pool["ThreadPoolExecutor (3 workers)"]
end
subgraph Providers["API Providers"]
GH["GitHub REST API v3"]
GL["GitLab REST API v4"]
BB["Bitbucket REST API v2"]
end
subgraph Data["Data Collected Per Poll"]
PRs["Open PRs + CI Status"]
Issues["Assigned Issues"]
CI["Failed CI Runs"]
Activity["Recent Activity"]
end
subgraph Local["Local Git"]
Checker["LocalRepoChecker"]
Git["git subprocess calls"]
end
subgraph Storage["Persistence"]
Config["~/.gitpulse/config.yaml"]
State["~/.gitpulse/state.json"]
Keychain["macOS Keychain"]
end
subgraph Notify["Notifications"]
Notifier["Notifier (dedup via StateStore)"]
macOS["macOS Notification Center"]
end
SI --> Menu
Timer --> Pool
Pool --> GH & GL & BB
GH & GL & BB --> PRs & Issues & CI & Activity
Timer --> Checker
Checker --> Git
PRs & Issues & CI & Activity --> Menu
Activity --> Notifier --> macOS
Config --> Providers
Keychain --> Providers
Notifier --> State
flowchart TD
A["Click 'Connect GitHub...'"] --> B{gh CLI token cached?}
B -->|Yes| C["Show: 'Found gh CLI as username.\nUse this account?'"]
C -->|Yes, Connect| D["Background thread"]
C -->|Enter Token Manually| E
B -->|No| E["Show PAT dialog"]
E --> F["Open github.com/settings/tokens"]
F --> G["User pastes token"]
G --> D
D --> H["Validate token via API"]
H -->|Invalid| I["Notification: Invalid token"]
H -->|Valid| J["Discover all user repos"]
J --> K["Store token in macOS Keychain"]
K --> L["Save repos to config.yaml"]
L --> M["Start polling"]
M --> N["Menu shows: GitHub ✅"]
style A fill:#4a9eff,color:#fff
style N fill:#22c55e,color:#fff
style I fill:#ef4444,color:#fff
sequenceDiagram
participant T as NSTimer
participant P as ThreadPool
participant GH as GitHub API
participant LC as LocalRepoChecker
participant S as StateStore
participant M as Menu
participant N as Notifier
T->>P: poll_once()
par For each provider
P->>GH: fetch_open_prs()
P->>GH: fetch_assigned_issues()
P->>GH: fetch_ci_runs()
P->>GH: fetch_recent_activity()
end
T->>LC: check_all()
LC-->>T: LocalRepoStatus[]
GH-->>P: PollResult (PRs, Issues, CI, Activity)
P->>S: mark_seen(activity)
P->>N: maybe_notify(new_pr)
N-->>N: macOS notification (if new)
Note over M: User clicks GP icon
M->>M: menuNeedsUpdate_()
M->>M: Build sections from cached data
| Feature | GitHub | GitLab | Bitbucket |
|---|---|---|---|
| Open Pull Requests | ✅ | ✅ | ✅ |
| Review Requests | ✅ | ✅ | ✅ |
| CI/CD Status | ✅ Actions | ✅ Pipelines | — |
| Assigned Issues | ✅ | — | — |
| Failed CI Runs | ✅ | — | — |
| Repo Discovery | ✅ | ✅ | ✅ |
Wildcard Repos (org/*) |
✅ | ✅ | ✅ |
| Keychain Storage | ✅ | ✅ | ✅ |
gh CLI Auto-detect |
✅ | — | — |
| Enterprise / Self-hosted | ✅ | ✅ | — |
Config is auto-created at ~/.gitpulse/config.yaml when you connect an account. You can also edit it manually:
# How often to poll APIs (seconds, minimum 60)
poll_interval: 300
# Play sound with notifications
notification_sound: true
# Git providers (auto-managed via Connect flow)
providers:
github:
token: keychain # "keychain" = stored in macOS Keychain
# api_url: https://github.example.com/api/v3 # GitHub Enterprise
repos:
- owner/repo1
- owner/repo2
- org-name/* # wildcard: all repos in an org
gitlab:
token: keychain
# api_url: https://gitlab.example.com # self-hosted
repos:
- group/project1
- group/*
bitbucket:
token: keychain
username: your_username # required for Bitbucket
repos:
- workspace/repo1
- workspace/*
# Local git repos to monitor
local_repos:
- ~/code/project-a
- ~/code/project-b
# Toggle individual notification types
notify:
new_pr: true
review_requested: true
ci_failure: true
pr_merged: true┌─────────────────────────────────────────────────────────┐
│ macOS Menubar │
│ │
│ PyObjC NSStatusBar → NSMenu with NSMenuDelegate │
│ Menu rebuilds dynamically every time you click GP │
│ No timer-based menu updates (avoids macOS UI bugs) │
└──────────────┬──────────────────────────┬───────────────┘
│ │
┌─────────▼─────────┐ ┌──────────▼──────────┐
│ Background Poll │ │ Local Git Check │
│ │ │ │
│ NSTimer fires │ │ subprocess.run() │
│ every N seconds │ │ git status/log │
│ ThreadPoolExec │ │ per configured │
│ (3 workers max) │ │ repo path │
└─────────┬──────────┘ └──────────┬──────────┘
│ │
┌─────────▼──────────────────────────▼───────────┐
│ Shared State (thread-safe) │
│ │
│ _current_prs[] _current_issues[] │
│ _current_ci_runs[] _local_statuses[] │
│ Protected by threading.Lock() │
└─────────┬──────────────────────────┬───────────┘
│ │
┌─────────▼─────────┐ ┌──────────▼──────────┐
│ Notifier │ │ StateStore │
│ Dedup via seen IDs│ │ JSON persistence │
│ macOS native │ │ LRU (5000 max) │
│ notifications │ │ Debounced flush │
└───────────────────┘ └─────────────────────┘
Key design decisions:
- PyObjC instead of rumps — rumps has compatibility issues on macOS 26+. Native PyObjC gives full control.
- NSMenuDelegate pattern — Menu content rebuilds on every open (
menuNeedsUpdate_), so it's always fresh without timer-based UI updates. - All dialogs in main thread — NSAlert/NSTextField only from menu action callbacks, never from timers or background threads.
- Background work via threading — API calls and git checks run in daemon threads; only
notify()is called from background (via osascript, which is thread-safe). - macOS Keychain for tokens — Uses the
securityCLI tool. Config storestoken: keychainas a sentinel.
# Clone
git clone https://github.com/vamsi876/gitbar.git
cd gitbar
# Install in dev mode
pip install -e .
# Run
gitbar
# or
python run.pygitbar/
├── gitpulse/ # main package
│ ├── app.py # PyObjC menubar app + menu building
│ ├── auth.py # GitHub/GitLab/Bitbucket auth + keychain
│ ├── config.py # YAML config loader/saver
│ ├── models.py # Dataclasses: PullRequest, Issue, CIRun, etc.
│ ├── ui.py # Native macOS dialogs (NSAlert, notifications)
│ ├── poller.py # Concurrent polling scheduler
│ ├── notifier.py # Notification dedup + delivery
│ ├── state.py # Persistent state (seen events, last poll)
│ ├── localrepo.py # Local git repo status checker
│ └── providers/
│ ├── base.py # Abstract provider with rate-limit handling
│ ├── github.py # GitHub REST API v3
│ ├── gitlab.py # GitLab REST API v4
│ └── bitbucket.py # Bitbucket REST API v2
├── run.py # Dev entry point
├── config.example.yaml # Config template
├── pyproject.toml # Package config (PyPI name: gitbar)
├── requirements.txt
├── LICENSE
└── README.md
- macOS 13+ (Ventura or later)
- Python 3.10+
MIT — see LICENSE