If you use a Mac for development, you almost certainly have Homebrew installed. You type brew install something, wait a few seconds, and move on with your day.
But not everyone who needs Homebrew is comfortable in a terminal. Designers, data folks, students, and plenty of developers early in their careers often get told "just install it with brew" and then get stuck.
The Homebrew team is working on an answer to that: BrewUI, an official, native macOS app for managing Homebrew packages. The project lives at github.com/Homebrew/BrewUI.
This article walks through what BrewUI is, the ideas behind it, and how its architecture works, in plain terms.
Heads up: BrewUI is in early development. The team describes the current phase as building the app foundation. Treat everything here as a look at a project in progress, not a finished product you can download today.
What is BrewUI?
BrewUI is a graphical front end for Homebrew. Instead of typing commands, you can search for packages, install them, update them, and remove them through a regular Mac app window.
The key word is official. There have been third-party Homebrew GUIs over the years, but this one is maintained under the Homebrew organization itself.
The stated goal is to let people who avoid the command line safely discover and manage packages, without the app ever hiding what Homebrew is actually doing.
The core idea: transparency
Most GUIs wrap a tool and try to make you forget the tool exists. BrewUI takes the opposite approach.
The design rules say the app should always show the exact command it is running. If you click "Install" on a package, you should be able to see the real brew command behind that button, along with its output.
Homebrew stays the source of truth
BrewUI does not reimplement Homebrew or reach into its internals. It gets data in two ways:
-
The
brewcommand line tool, run as a subprocess (the same way you would run it yourself). -
The Homebrew JSON API at
formulae.brew.sh, which provides package metadata.
It also does not bundle Homebrew. If Homebrew is not installed, the app is meant to detect that and degrade gracefully rather than crash. To find brew, it checks the two standard locations:
/opt/homebrew/bin/brew # Apple Silicon
/usr/local/bin/brew # Intel
This keeps the app simple: Homebrew does the real work, and BrewUI presents it.
Tech stack at a glance
| Area | Choice |
|---|---|
| Language | Swift 6.0 with strict concurrency |
| UI | SwiftUI (AppKit only when SwiftUI cannot do the job) |
| State | @Observable |
| Concurrency |
async/await, actors for shared mutable state |
| Dependencies | Swift Package Manager |
| Platform | macOS only, with macOS Tahoe 26 as the main target |
| License | AGPL-3.0 |
If you have been meaning to see what a modern, "all in" Swift 6 macOS project looks like, this repository is a good real-world example.
How the architecture works
Here is the part that is most interesting for developers. BrewUI follows a layered design. A user action flows down through the layers like this:
View -> ViewModel -> Repository or Interactor -> Service -> brew CLI / JSON API
Let's break that down with a simple example: showing a list of installed packages and letting you upgrade one.
Views
SwiftUI views are kept thin. They display state and forward user actions. A view knows that a button was tapped; it does not know how to upgrade a package.
ViewModels
The ViewModel holds presentation state, such as "is this list loading?" or "what text should this label show?". It turns domain data into something the UI can display, and passes real work further down.
Repositories
A Repository is about reading data from a source. For example, getting the list of installed packages from brew list output, or package details from the JSON API.
The benefit: the rest of the app does not care where the data came from. You could swap in a mock repository for tests and nothing above it would change.
Interactors
An Interactor represents one specific use case, such as running brew doctor. It is not a generic "fetch everything" layer; it is a single, focused action.
Services
Services are the infrastructure at the edge of the app: actually launching the brew process, making HTTP requests, and so on.
Models
Plain domain types (like a package) shared across layers. They intentionally stay free of UI details, so the same model works for a list row, a detail page, or a test.
If you know MVVM and Clean Architecture, this will feel familiar. The project describes it as a lightweight MVVM with a coordinator style, guided by Clean Architecture ideas, and it explicitly prefers the smallest pattern that solves the current problem rather than building everything up front.
The command center: why brew commands run one at a time
One detail worth calling out is the command center, an actor named BrewCommandCenter.
Homebrew does not like it when you run multiple commands that change your system at the same time. Picture a user clicking "Upgrade" on three packages in quick succession. In a terminal you would naturally run them one after another. A GUI needs to enforce that on your behalf.
The command center:
- Serializes any command that changes the system, so they run in order.
- Tracks the state of each operation (in progress, failed, and so on), so every part of the UI can show accurate status.
- Runs small, dedicated command types for each mutating action.
Reading data, like parsing brew list or brew info, is not its job. That stays in the repositories. This separation keeps "things that change your machine" in one controlled place.
Using a Swift actor here is a nice fit: actors guarantee only one task touches their state at a time, which is exactly the property you want.
Designing for tools that change
BrewUI depends on two things it does not control: the text output of brew and the shape of the JSON API. Both can change between Homebrew releases.
The project handles that with a defensive mindset:
- CLI output is treated as unstable. Parsers are expected to be tolerant and have fallbacks.
- JSON decoding is resilient. Unknown or missing fields should never crash the app.
- Commands are async and cancellable, with output streamed or preserved for logs.
This is good advice for any app that wraps a command line tool, not just this one.
Check it out here: github.com/Homebrew/BrewUI
Top comments (0)