The faithful watchdog for your pull requests. Live PR dashboard with sound alerts, SLA tracking, and light/dark mode — built with Vue 3, Tailwind CSS, and shadcn/ui.
- Multi-repo dashboard — monitor open PRs across any number of GitHub repositories in one view
- Live status — review state (open, approved, changes requested, draft), CI check results, assignees, and reviewers
- SLA tracking — configurable warning and breach thresholds; rows highlight amber/red as PRs age past them
- Comment heat — shows comment count per PR with a 🔥 when it crosses a configurable threshold
- Granular notifications — independently toggle new PR sound, merge sound, merge confetti, and voice announcements
- Custom per-author sounds — optionally load
pr_open.mp3/pr_merged.mp3from each author'spulldog-soundsGitHub repo - Voice announcements (optional) — OpenAI-powered TTS for new PR and merge events
- 7-day summary bar — total open PRs, PRs opened, PRs merged, average lead time, and merge rate
- Light / dark / system theme — persisted to
localStorage, respects OS preference in system mode - Auto-refresh — configurable poll interval (15s to 30m); detects new and merged PRs and triggers notifications
- Flexible filtering — by status, SLA state, repo, author, staleness (7d+), free-text search, and title-regex exclusions
- Cleaner default views — optional "Hide Drafts" and "Hide Merged" behavior when "All" is selected
- GitHub App OAuth — one-click "Connect with GitHub" via a Cloudflare Worker; no token copy-pasting required
- Manual PAT fallback — direct token entry remains available when OAuth is not configured
| Dark mode | Light mode |
|---|---|
| (add your own) | (add your own) |
- Node.js v18 or later
- Wrangler CLI for the auth worker (
npm i -g wrangler) - A Cloudflare account (free tier is sufficient)
- A GitHub App (see setup below)
A single GitHub App works for both development and production — you can register multiple callback URLs.
- Go to github.com → Settings → Developer settings → GitHub Apps → New GitHub App
- Set the following:
- Homepage URL:
https://pulldog.dev(or your domain) - Callback URLs:
https://pulldog.devandhttp://localhost:5173 - Check "Request user authorization (OAuth) during installation"
- Homepage URL:
- Under Permissions → Repository permissions, set:
- Metadata: Read-only (required, auto-selected)
- Pull requests: Read-only
- Note the App ID and Client ID, and generate a Client secret
The worker handles the GitHub token exchange server-side so your client secret never touches the browser.
# Authenticate with Cloudflare
wrangler login
# Set production secrets (uses the GitHub App credentials)
cd worker
wrangler secret put GITHUB_CLIENT_ID
wrangler secret put GITHUB_CLIENT_SECRET
# Deploy
wrangler deployThis gives you a worker URL like https://pulldog-auth.<your-subdomain>.workers.dev.
# Copy the example and fill in your GitHub App credentials
cp worker/.dev.vars.example worker/.dev.vars
# Start the local worker (runs at http://localhost:8787)
cd worker && wrangler dev# 1. Clone the repository
git clone https://github.com/YOUR_USERNAME/pulldog.git
cd pulldog
# 2. Install dependencies
npm install
# 3. Configure environment variables
cp .env.example .env
# Edit .env and fill in VITE_GITHUB_CLIENT_ID (GitHub App client ID) and VITE_GITHUB_WORKER_URL
# 4. Start the local worker in a separate terminal
cp worker/.dev.vars.example worker/.dev.vars
# Edit worker/.dev.vars with your GitHub App credentials
cd worker && wrangler dev
# 5. Start the development server
npm run devOpen http://localhost:5173 and click Connect with GitHub.
Copy .env.example to get started:
# GitHub App Client ID
VITE_GITHUB_CLIENT_ID=your_client_id
# URL of your deployed Cloudflare Worker
VITE_GITHUB_WORKER_URL=https://pulldog-auth.<your-subdomain>.workers.dev
# Override the OAuth redirect URI (defaults to window.location.origin)
VITE_GITHUB_REDIRECT_URI=
# Optional: bake a token directly into the bundle (skips the OAuth flow)
VITE_GITHUB_TOKEN=
# Hours until a PR row turns amber (SLA warning)
VITE_SLA_WARNING_HOURS=24
# Hours until a PR row turns red (SLA breach)
VITE_SLA_BREACH_HOURS=72
# Show 🔥 next to comment count when it reaches this number
VITE_COMMENT_FIRE_THRESHOLD=10
# Poll interval in seconds (how often to refresh PRs, default: 60)
VITE_POLL_INTERVAL_S=60
# Show sound test buttons in the top bar (true | false)
VITE_TEST_MODE=false
# Optional: OpenAI API key for voice announcements
VITE_OPENAI_API_KEY=For local development, override VITE_GITHUB_WORKER_URL to point at the local worker:
# .env.local (git-ignored, overrides .env in dev)
VITE_GITHUB_CLIENT_ID=your_client_id
VITE_GITHUB_WORKER_URL=http://localhost:8787Note: Vite bakes
VITE_*variables into the bundle at build time. Restartnpm run devafter any change.
Copy worker/.dev.vars.example and fill in your GitHub App credentials:
GITHUB_CLIENT_ID=your_dev_client_id
GITHUB_CLIENT_SECRET=your_dev_client_secretProduction secrets are stored in Cloudflare (via wrangler secret put) and never committed.
Pulldog can play per-author custom sounds by loading audio files from GitHub.
When enabled, it looks for files in:
https://raw.githubusercontent.com/<github-username>/pulldog-sounds/main/pr_open.mp3https://raw.githubusercontent.com/<github-username>/pulldog-sounds/main/pr_merged.mp3
For each author you want custom sounds for, create a public repo named pulldog-sounds under that same GitHub account, then add:
pr_open.mp3(played on new PR)pr_merged.mp3(played on merge)
Open Settings → Notifications and turn on:
- New PR → Custom sound
- Merge → Custom sound
Set VITE_TEST_MODE=true in .env, restart npm run dev, and use the top test bar to trigger sample events.
If a custom file is missing or cannot be loaded, Pulldog automatically falls back to the default built-in sound.
pulldog/
├── worker/
│ ├── index.js # Cloudflare Worker — GitHub token exchange
│ ├── wrangler.toml # Worker-only Cloudflare config
│ └── .dev.vars.example # Worker local secrets template
├── src/
│ ├── components/
│ │ ├── ui/ # shadcn-style primitive components
│ │ ├── SetupScreen.vue # OAuth + manual token onboarding flow
│ │ ├── SettingsDialog.vue
│ │ ├── SlaLegend.vue
│ │ ├── SummaryBar.vue
│ │ └── Topbar.vue
│ ├── composables/
│ │ ├── useAudio.ts # Web Audio API sounds
│ │ ├── useGithub.ts # GitHub REST API calls
│ │ ├── useGithubOAuth.ts # GitHub OAuth flow + callback handling
│ │ ├── usePersistedRepos.ts
│ │ ├── usePersistedToken.ts
│ │ ├── useSla.ts # SLA threshold logic
│ │ └── useTheme.ts # Light/dark/system theme
│ ├── types.ts
│ ├── App.vue
│ ├── base.css
│ ├── main.ts
│ └── vite-env.d.ts
├── .env.example # Frontend env template
├── vite.config.ts
└── package.json
| Command | Description |
|---|---|
npm run dev |
Start development server with hot-reload |
npm run build |
Build for production (outputs to dist/) |
npm run preview |
Preview the production build locally |
npm run type-check |
Run TypeScript type checking via vue-tsc |
cd worker && wrangler dev |
Run the auth worker locally on port 8787 |
cd worker && wrangler deploy |
Deploy the worker to Cloudflare |
npm run buildThe dist/ directory is a fully static build. Deploy it to Cloudflare Pages, Vercel, Netlify, or any static host.
Contributions are welcome! Here's how to get involved:
- Fork the repository and create a branch from
maingit checkout -b feat/your-feature-name
- Make your changes and ensure the project builds and type-checks cleanly
npm run type-check && npm run build - Commit using a descriptive message
git commit -m "feat: add X" - Push your branch and open a Pull Request against
main
Open an issue and include:
- What you expected to happen
- What actually happened
- Steps to reproduce
- Browser and Node.js version
Distributed under the MIT License. See LICENSE for full text.
Pulldog has a built-in fullscreen toggle (⛶ button in the top-right corner) that uses the browser Fullscreen API to hide all browser chrome — address bar, tabs, bookmarks — leaving only the dashboard.
Press Esc or click the button again to exit.
For a permanent TV kiosk setup, launch Chrome or Chromium directly into kiosk mode:
# Linux / Raspberry Pi
chromium-browser --kiosk --noerrdialogs --disable-infobars https://pulldog.dev
# macOS
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \
--kiosk --noerrdialogs https://pulldog.dev
# Windows
"C:\Program Files\Google\Chrome\Application\chrome.exe" ^
--kiosk --noerrdialogs https://pulldog.devSet VITE_GITHUB_TOKEN in .env before building to pre-seed a token so the dashboard connects automatically on boot without any interaction.