BrewPackageManager is a native macOS menu bar companion for Homebrew that turns package maintenance, search, cleanup, diagnostics, and service management into a fast, readable desktop workflow instead of a collection of terminal commands.
The 3.0 line rebuilds the interface as a fully native macOS experience: system materials and vibrancy, automatic light/dark mode, the user's configured accent color, system typography, and standard controls everywhere. The popover keeps the fast four-tab flow, while the window mode becomes a real macOS management app with a grouped sidebar, in the spirit of Finder or System Settings.
Features • Screenshots • Installation • Usage • Documentation • Development
The 3.0 line replaces the custom dark theme with a fully native shell. The system decides the base colors and materials; the app only contributes state semantics (green for up to date, orange for pending, red for errors) on top.
- Native by default: system materials, automatic light/dark mode, the user's accent color, system typography, and standard buttons, pickers, and forms
- Popover: compact header with live status badge, native segmented tabs (Overview, Search, Tools, Settings), and push navigation with standard back buttons
- Window mode:
NavigationSplitViewwith a grouped sidebar — Packages (Overview, Search, Activity), Maintenance (Services, Cleanup, Dependencies), System (Statistics, Settings) — instead of a scaled-up popover - Contextual error banners: failures appear inside the affected screen with a retry action, replacing generic modal alerts
The four primary areas keep their roles: Overview is for action, Search is for discovery, Tools is for deeper management work, and Settings is for changing how the app behaves over time.
Package management is still the center of the app, and 3.0 keeps the strong distinction between what is installed, what is outdated, and what is actually actionable. That matters because Homebrew setups often contain pinned packages, hidden updates, or items you want to keep around without turning them into constant visual noise.
- Installed Package Visibility: installed Homebrew packages with version and update state
- Visible Update Count: pinned and hidden updates are excluded from the main action count
- Bulk Update Action: update all visible packages from the overview flow
- Package Details: inspect metadata, versions, homepage, and release links
- Pinned Guidance: pinned formulae stay visible as state, but are not treated as actionable updates
- Hidden Items: hide noisy packages or updates and restore them later
In practice, this means the UI tries to stay honest. If something cannot be updated because it is pinned, the app should treat that as context, not as a broken action. If something is intentionally hidden, it should disappear from the main flow but still remain recoverable.
Search is designed for quick package discovery without leaving the menu bar flow. Instead of a one-shot input that forces an extra confirmation step, the search experience reacts while you type and keeps install and detail actions close to the result itself.
- Live Search: search runs as you type
- Type Filters: All, Formulae, or Casks
- Direct Install: install packages from search results
- Details Flow: open package details without losing the surrounding navigation context
This makes the screen useful both for fast installs and for cautious inspection. You can scan the result set, narrow by package type, open a package first, and only then install if it looks right.
Homebrew services are one of the places where a GUI can save real time, especially when you are juggling local databases, queues, or background daemons. The app tries to surface the status of each service clearly enough that you can understand what is running before you touch anything.
- Service Control: start, stop, and restart Homebrew services
- Status Summary: running and stopped counts at the top of the screen
- Row-Level Feedback: each service shows only valid actions for its current state
- Metadata Visibility: service status, PID, and summary data stay readable in one card
The important part is not only that the action exists, but that the result is legible. A service screen should tell you whether something is running, who owns it, and what action makes sense next, instead of forcing you to infer that from raw command output.
Cleanup is intentionally split because Homebrew cleanup work is not all the same. Clearing the download cache, removing old package versions, and uninstalling packages are different actions with different consequences, and the UI should not blur them together.
- Separated Actions: cache clearing and old-version cleanup are explicitly different flows
- Immediate Cache Clear: clearing download cache does not ask for confirmation first
- Old Versions Cleanup: destructive package-version cleanup still requires confirmation
- Result Messaging: cleanup feedback explains what was cleared and what remains
That separation is there to reduce anxiety and avoid false expectations. If you clear cache, the app should say exactly that. If old versions remain, the app should say that too instead of making the user guess whether the action failed.
When Homebrew behaves unexpectedly, the problem is rarely just “something failed.” Usually you need context: what command ran, whether it timed out, whether it was cancelled, what happened previously, and whether the app still has a sane internal state. This part of the app is there to make that debugging path much shorter.
- Persistent History: operations are recorded for later inspection
- Statistics View: high-level counts and success-rate summary
- Command Diagnostics: debug mode keeps command output and metadata easier to reason about
- GitHub Release Checks: manual and automatic app update checks against releases
This is especially useful when a workflow breaks after a Homebrew update. Instead of jumping immediately into guesswork, you can inspect what the app saw, what it tried to do, and where the failure surfaced.
Homebrew uses two package families:
Those names come directly from Homebrew itself, so they appear in command output, JSON payloads, and the app UI. If you do not work with Homebrew terminology every day, it helps to map them to more familiar product language.
- Formulae: CLI tools, libraries, and developer packages such as
tree,wget, orpython - Casks: macOS apps and app-like binaries such as
visual-studio-code,google-chrome, orslack
If you prefer user-facing wording, think of them as:
- CLI Tools = Formulae
- Mac Apps = Casks
The app targets a modern macOS setup and expects a standard Homebrew installation. It is intentionally opinionated here because predictable paths and a current toolchain make both runtime behavior and contributor support much more reliable.
- macOS 15.0 or later
- Homebrew installed at one of the standard paths:
/opt/homebrew/bin/brew(Apple Silicon)/usr/local/bin/brew(Intel)
- Xcode 26.5+ if you want to build from source with the same Swift 6.3.2 toolchain used in the current audit
- Docker 29+ if you want to run the external audit lane locally
Download the latest release DMG from the releases page.
This is the simplest path if you only want to install and use the app. The DMG is meant to behave like a normal macOS distribution: drag, drop, open, and start using the menu bar extra.
- Open the DMG file
- Drag BrewPackageManager.app to the Applications folder
- Launch from Applications
git clone https://github.com/686f6c61/BrewPackageManager.git
cd BrewPackageManager
./create-dmg.shThe DMG will be created at dmg/BrewPackageManager-3.0.1.dmg.
For public distribution outside your own machine, the recommended path is:
SIGNING_IDENTITY="Developer ID Application: Your Name (TEAMID)" \
NOTARYTOOL_PROFILE="bpm-notary" \
./create-dmg.shThat release path signs the app bundle, signs the DMG container, submits the DMG with notarytool, and staples the notarization ticket back onto the final artifact when your credentials are configured.
Building from source is the better route if you want to inspect the code, test the new shell, or iterate on the app locally before packaging a release.
App Sandbox must be disabled for local builds and releases because the app needs to execute
brewcommands directly.
- Left-click the menu bar icon to open the main popover
- Right-click the menu bar icon for quick actions such as refresh, app update check, opening the management window, or quit
The app is built around the menu bar first. Left click is the normal working surface, while right click is for quick utility actions when you do not need the full UI.
- Open Overview
- Review the visible update count
- Use Update all visible when the overview shows actionable packages
- Pinned or hidden updates are intentionally excluded from that action
This keeps the bulk update path deliberate. You get a quick answer to “what should I deal with now?” without mixing in pinned packages or intentionally hidden items that would only create friction.
- Open Search
- Type a package name such as
tree,python, ormongodb - Use filters if you want only formulae or only casks
- Install directly from the result card or open package details first
This flow is designed for both speed and caution. If you already know what you want, install is close at hand. If not, the package detail screen gives you enough context to inspect before making a change.
Open Tools for the deeper management surfaces:
- Services
- Cleanup
- Dependencies
- Activity
- Statistics
- Hidden Items
- Help
This section is where the app keeps the heavier operational screens. The intent is to keep the main overview clean while still making advanced workflows available in one obvious place.
Open Settings to manage:
- Launch at login
- Automatic app update checks
- Show only outdated packages
- Debug mode
- Auto-refresh interval
These settings are about runtime behavior, not decoration. They exist to help you decide how noisy, automatic, or verbose the app should be for your own Homebrew workflow.
- Swift 6.3
- SwiftUI
- Observation
- AppKit
- OSLog
The stack is intentionally native. This app fits best as a lightweight macOS utility, so the architecture leans on SwiftUI for screen composition, AppKit for menu bar and window integration, and native concurrency/observation patterns for state flow.
BrewPackageManager/
├── Brew/ # Homebrew interaction & JSON parsing
├── Shell/ # Command execution infrastructure
├── Packages/ # State management & business logic
├── MenuBar/ # Status item and window/popover controller
├── UI/ # Native 3.0 interface
│ ├── Components/ # Reusable rows, badges, tiles, and banners
│ └── Screens/ # One focused file per screen
├── Settings/ # User settings persistence
├── Updates/ # GitHub release checks
├── Services/ # Homebrew services management
├── Cleanup/ # Cache & cleanup operations
├── Dependencies/ # Dependency analysis
├── History/ # Operation history & statistics
└── Utilities/ # Helper functions & AppKit bridges
The 3.0 redesign resolved the main structural debt of the 2.0 line: the monolithic UI shell (1,600+ lines in one file) was replaced by a modular UI/ layer with a navigation model, shared components, and one focused file per screen. The remaining known debt is the decomposition of PackagesStore, which still concentrates package state, search state, and update checks.
./build.shUse the helper script if you want the fastest path to a local debug build with the expected project assumptions already baked in.
Or directly:
xcodebuild \
-project BrewPackageManager/BrewPackageManager.xcodeproj \
-scheme BrewPackageManager \
-configuration Debug \
ENABLE_APP_SANDBOX=NO \
buildxcodebuild \
-project BrewPackageManager/BrewPackageManager.xcodeproj \
-scheme BrewPackageManager \
-destination 'platform=macOS' \
-derivedDataPath .derived-audit-2 \
testThis runs the current unit and UI smoke suite. It is the minimum baseline before talking about packaging or release work.
./scripts/audit-swift-in-docker.shThe Docker lane is there to give us a repeatable external read on formatting, lint, and static analysis. It is especially useful when we want to separate “works locally” from “is actually clean enough to ship.”
This runs:
SwiftLintin DockerSwiftFormat --lintin DockerSemgrepin Docker
and writes logs to .audit-docker/.
The app does not invent its own package-management semantics. It builds on top of Homebrew’s own commands and JSON output, then adds a native UI, state handling, and diagnostics around those flows.
# Package inventory and metadata
brew info --json=v2 --installed
brew outdated --json=v2
brew info --json=v2 <package>
brew search --json=v2 <query>
brew install <package>
brew uninstall <package>
# Services
brew services list --json
brew services start <service>
brew services stop <service>
brew services restart <service>
# Cleanup
brew cleanup --dry-run -s
brew cleanup -s
brew --cache
# Dependency inspection
brew info --json=v2 --installed
brew uses --installed <package>The app also sets:
HOMEBREW_NO_AUTO_UPDATE=1
HOMEBREW_NO_INSTALL_CLEANUP=1This project builds on ideas and infrastructure from BrewServicesManager by validatedev (MIT License), while the current 3.0 native UI is a new implementation.
Icon: Icons8 Box Icon








