A tiny macOS menu-bar app that shows every background service on your machine — and can actually make them stay stopped.
You kill a stray dev server, and two seconds later it's back. That's launchd doing its job: if a launch agent has KeepAlive, killing the process just tells macOS to respawn it. Months later you've got a graveyard of forgotten Python servers, updaters, and "temporary" scripts quietly running behind your Mac.
bgviewer sits in your menu bar, finds all of it, shows you where each thing comes from, and stops it at the source.
| Section | Source | Typical residents |
|---|---|---|
| Auto-start Agents | ~/Library/LaunchAgents/*.plist |
that script you wired up months ago, app updaters |
| Machine-wide Agents | /Library/LaunchAgents (all users) |
vendor agents — Zoom updaters, Logitech, AV tools |
| Homebrew Services | brew services |
postgres, redis, kafka, nginx |
| Listening Processes | anything holding a TCP port (lsof) |
forgotten python -m http.server, dev servers |
| Resource Hogs | background processes burning CPU or RAM | that helper app quietly eating 25% CPU for days |
| Scheduled (cron) | crontab -l (read-only) |
nightly scripts you set up and forgot |
| Disabled (parked) | agents you disabled via bgviewer | one click to bring them back — or 🗑 to Trash |
Agents that can resurrect themselves are marked ↻ auto-restart, so you know why something keeps coming back. Apple system processes get a 🔒 and no destructive buttons — you can't accidentally break your Mac.
| Action | What actually happens | |
|---|---|---|
| ⏹ | Stop | Agents: launchctl bootout — beats KeepAlive, it will not respawn. Brew: brew services stop. Processes: SIGTERM, escalating to SIGKILL if ignored. |
| ⏸ | Pause / Resume | Freezes the process (SIGSTOP/SIGCONT) without quitting it — RAM kept, CPU freed. |
| ↻ | Restart | Bootout + bootstrap, brew services restart, or relaunch of the same command line in its original working directory. |
| 🚫 | Disable | Stop and block every future auto-start, including at login. |
| ↩ | Re-enable | Undo a disable, verbatim. |
Every action is verified after it runs; if something fails, the reason shows up in the footer. Nothing fails silently.
There's also a 💾 Storage pane (drive button in the header): the biggest files quietly sitting in Downloads, Desktop, Documents and Movies — size, location, how long you've ignored them — with a free-disk-space bar in the corner. From there, open the Disk Map: a full window with a clickable pie of what's eating your disk — drill into folders wedge by wedge, scan any folder you pick. For your files, there is deliberately no delete button: rows only reveal in Finder, because deleting your data is a decision bgviewer refuses to make for you.
The one exception is the Dev Junk view (second tab of the Disk Map): regenerable build artifacts — node_modules, Python venvs, Rust target, CocoaPods, .next/.turbo, Xcode DerivedData — plus app caches (Adobe media cache, Xcode device support, npm/pip/Homebrew caches, and any non-Apple ~/Library/Caches folder over 100 MB), all of which rebuild on the next install or launch. Those it can delete, behind strict guards (a folder only qualifies with its manifest beside it — package.json, Cargo.toml, pyvenv.cfg…), a guard re-check at delete time, and a two-step confirm. Ten projects × 1 GB of duplicate dependencies adds up; this is where you get it back.
Beyond the buttons: click a row for details — the full command line, Copy command, Reveal plist, View log, and for dev servers a one-click open localhost:port. There's a search box (name, port, or command) and the list auto-refreshes every few seconds while open. The ⚙ Settings panel holds Alerts (a notification the moment a new dev server starts listening, even while the dropdown is closed), start at login, and updates: when a newer release exists a green button appears in the header, and one click updates the app in place — checksum-verified against the release's checksums.txt, signature-checked, with automatic rollback. Flip auto-update on and new releases install themselves. Release checks hit GitHub at most every 6 hours and updates come only from this repo's Releases — no telemetry, ever. All of this is also explained in-app — the ⓘ button top-left.
Disable does three things:
launchctl bootout— stop it nowlaunchctl disable— mark it disabled in launchd's overrides- Moves the plist into
~/Library/LaunchAgents/Disabled by bgviewer/— launchd never scans subdirectories, so it can't load it at login either
Nothing is ever deleted. Re-enable moves the plist back and bootstraps it again. If you ever stop trusting bgviewer, your plists are sitting in that folder, intact.
More guardrails:
com.apple.*agents and system processes (paths under/System,/usr/libexec, …) are locked 🔒 — no action buttons at all.- User-scope only: no admin prompt, and root daemons in
/Library/LaunchDaemonsare untouchable by design.
Quick install (recommended — downloads the latest release, installs to /Applications, launches):
curl -fsSL https://raw.githubusercontent.com/AroraShreshth/bgviewer/main/install.sh | bashHomebrew:
brew tap arorashreshth/bgviewer https://github.com/AroraShreshth/bgviewer
brew install --cask arorashreshth/bgviewer/bgviewer --no-quarantineManually: download the zip from Releases, unzip, drag bgviewer.app to /Applications. The app isn't notarized yet, so macOS will block the first open — clear the flag with
xattr -d com.apple.quarantine /Applications/bgviewer.appor approve it under System Settings → Privacy & Security → "Open Anyway" (on macOS 13–14, right-click → Open also works).
From source (macOS 13+, Xcode command-line tools):
git clone https://github.com/AroraShreshth/bgviewer && cd bgviewer
./build.sh
open bgviewer.app # the gauge icon appears in your menu barRelease builds are universal (Apple Silicon + Intel) and need macOS 13 Ventura or newer.
Start at login: System Settings → General → Login Items → add bgviewer. (Yes, an app for killing auto-started things can itself auto-start. We appreciate the irony.)
Two GitHub Actions workflows keep this honest:
- CI — every push and PR builds the app from scratch, runs the unit-test suite, and then runs the install smoke test on a macOS runner: the freshly built app is zipped, installed, and launched, and must stay alive — so "it builds" always also means "it runs".
- Release — pushing a tag like
v1.0.0automatically builds, smoke-tests, zips, checksums, and attaches the app to a GitHub Release with generated notes. A release cannot ship unless the app demonstrably launches.
Sources/
BgviewerApp.swift @main MenuBarExtra entry point
Models.swift service model + button-eligibility rules
ServiceScanner.swift discovery: launchctl / brew / lsof / ps parsing
ServiceControl.swift actions: stop, pause, restart, disable, enable
ServiceStore.swift observable state for the UI
Views.swift SwiftUI dropdown
Shell.swift subprocess runner (concurrent pipes + timeout watchdog)
Tests/main.swift 117-check test suite
build.sh builds bgviewer.app with plain swiftc — no Xcode project
test.sh compiles and runs the tests
smoke-test.sh full install loop: package → install → launch → stays up
release.sh stamps a version, builds, smoke-tests, zips into dist/
No dependencies. No Xcode project. The whole build is one swiftc invocation.
./test.sh # full suite, ~10s — includes integration tests that create a
# throwaway launch agent (com.bgviewer.selftest) and real
# processes against live launchd, then clean up completely
./test.sh --unit # pure logic only; this is what CI runsThe integration tests are the interesting part: they spin up a real KeepAlive agent, stop it through the app's own code, and assert it stays dead — the exact failure mode this app exists to fix.
- User launch agents only — root daemons would need elevated rights (planned, behind an explicit prompt).
- Listening-process discovery is TCP-only.
- Restarting a loose process re-runs its command line via the shell in its original cwd — great for dev servers, not guaranteed for anything exotic.
- Not notarized yet — hence the one-time quarantine/"Open Anyway" step (Developer ID signing is planned).
MIT © 2026 Shreshth Arora
