DEV Community

Cover image for BrewUI: A First Look at Homebrew's Official macOS GUI
ArshTechPro
ArshTechPro

Posted on

BrewUI: A First Look at Homebrew's Official macOS GUI

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:

  1. The brew command line tool, run as a subprocess (the same way you would run it yourself).
  2. 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)