# Homebrew Dev

> Package and distribute macOS apps, fonts, CLI tools, and arbitrary files using Homebrew formulas and casks. Use this skill whenever the user wants to create a Homebrew formula or cask, set up a personal tap, package a macOS .app bundle, distribute fonts or pre-built binaries via brew, use `brew create`, bump a formula or cask to a new version, submit a package to homebrew-core or homebrew-cask, or publish anything with Homebrew — even if they just ask how to "make something installable with brew", "share my app through Homebrew", "update my formula", or "get my package into Homebrew".

- Skill: `trtmn/homebrew-dev` (Agent Skill, multi-file: 6 files)
- Install (CLI): `npx skillmds@latest add trtmn/homebrew-dev`
- Raw SKILL.md: https://api.skillmd.com/api/skills/trtmn/homebrew-dev/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: trtmn (https://skillmd.com/u/trtmn)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/trtmn/homebrew-dev

---


# Homebrew Packaging Skill

You are an expert at packaging software with Homebrew — both formulas (source builds and pre-built binaries) and casks (macOS apps, fonts, arbitrary files). You help users create, test, and distribute packages through personal taps on GitHub.

## Deciding: Formula vs. Cask

| Scenario | Use |
|----------|-----|
| CLI tool with source code (C, Go, Rust, etc.) | Formula |
| Pre-built binary tarball for a CLI tool | Formula (no build step) |
| macOS `.app` bundle | Cask |
| Font files (`.ttf`, `.otf`) | Cask |
| PKG installer | Cask |
| Arbitrary file placement (e.g. completions, configs) | Cask with `artifact` |
| App with auto-updater | Cask with `auto_updates true` |
| Both a CLI and a GUI component | Both (linked from cask with `binary`) |

---

## Creating a Formula

### Scaffold
```bash
brew create https://example.com/mytool-1.0.tar.gz
```
This auto-populates `url`, `sha256`, `version`, and opens the editor.

### Key fields
```ruby
class Mytool < Formula
  desc "One-line description (imperative, no trailing period, no 'A' prefix)"
  homepage "https://example.com/mytool"
  url "https://example.com/mytool-1.0.tar.gz"
  sha256 "abc123..."      # from: shasum -a 256 <file>
  license "MIT"
  version "1.0"           # omit if parseable from url

  depends_on "cmake" => :build           # build-time only
  depends_on "openssl@3"                 # runtime
  depends_on "python@3.12" => :optional  # optional runtime
  depends_on :macos => :monterey         # minimum macOS

  def install
    system "./configure", *std_configure_args
    system "make", "install"
  end

  test do
    system "#{bin}/mytool", "--version"
    assert_match "1.0", shell_output("#{bin}/mytool --version")
  end
end
```

### Install directory variables
| Variable | Expands to |
|----------|-----------|
| `bin` | `$(brew --prefix)/bin` |
| `lib` | `$(brew --prefix)/lib` |
| `include` | `$(brew --prefix)/include` |
| `share` | `$(brew --prefix)/share` |
| `etc` | `$(brew --prefix)/etc` |
| `var` | `$(brew --prefix)/var` |
| `libexec` | `$(brew --prefix)/opt/<name>/libexec` |

### Build systems
```ruby
# Autoconf/Make
system "./configure", *std_configure_args
system "make", "install"

# CMake
system "cmake", "-S", ".", "-B", "build", *std_cmake_args
system "cmake", "--build", "build"
system "cmake", "--install", "build"

# Cargo (Rust)
system "cargo", "install", *std_cargo_args

# Go
system "go", "build", *std_go_args, "-o", bin/"mytool", "./cmd/mytool"

# Pre-built binary (no build)
def install
  bin.install "mytool"
  man1.install "mytool.1"
end
```

### Architecture conditionals
```ruby
on_arm do
  url "https://example.com/mytool-1.0-arm64.tar.gz"
  sha256 "arm_sha..."
end

on_intel do
  url "https://example.com/mytool-1.0-x86_64.tar.gz"
  sha256 "intel_sha..."
end
```

---

## Creating a Cask

### Scaffold
```bash
brew create --cask https://example.com/MyApp-1.0.dmg
```

### Artifact types and their uses

| Artifact | Use case | Installs to |
|----------|----------|-------------|
| `app "MyApp.app"` | macOS `.app` bundles | `/Applications` |
| `font "MyFont.ttf"` | Font files | `~/Library/Fonts/` |
| `binary "mytool"` | Pre-built CLI tool from archive | `$(brew --prefix)/bin/` |
| `artifact "myfile", target: "~/.config/myfile"` | Arbitrary file placement | Custom path |
| `pkg "MyApp.pkg"` | PKG installer | System (requires `uninstall`) |
| `suite "MyApp.app"` | Multi-app suites | `/Applications` |

### Minimal cask (DMG with app)
```ruby
cask "myapp" do
  version "1.0"
  sha256 "abc123..."

  url "https://example.com/MyApp-#{version}.dmg"
  name "MyApp"
  desc "Does something useful"
  homepage "https://example.com/myapp"

  app "MyApp.app"

  zap trash: [
    "~/Library/Application Support/MyApp",
    "~/Library/Preferences/com.example.myapp.plist",
  ]
end
```

### Font cask
```ruby
cask "font-my-font" do
  version "2.0"
  sha256 "abc123..."

  url "https://example.com/MyFont-#{version}.zip"
  name "My Font"
  desc "A beautiful typeface"
  homepage "https://example.com/myfont"

  font "MyFont-Regular.ttf"
  font "MyFont-Bold.ttf"
  font "MyFont-Italic.ttf"
end
```

### Arbitrary file placement
```ruby
cask "mycli" do
  version "1.0"
  sha256 "abc123..."

  url "https://example.com/mycli-#{version}.zip"
  name "MyCLI"
  desc "A command-line tool"
  homepage "https://example.com/mycli"

  binary "mycli"
  artifact "mycli.zsh-completion", target: "#{HOMEBREW_PREFIX}/share/zsh/site-functions/_mycli"
  artifact "mycli.fish", target: "#{HOMEBREW_PREFIX}/share/fish/vendor_completions.d/mycli.fish"
end
```

### PKG installer
```ruby
cask "myapp" do
  version "1.0"
  sha256 "abc123..."

  url "https://example.com/MyApp-#{version}.pkg"
  name "MyApp"
  desc "Does something useful"
  homepage "https://example.com/myapp"

  pkg "MyApp.pkg"

  uninstall pkgutil: "com.example.myapp"

  zap trash: [
    "~/Library/Application Support/MyApp",
    "~/Library/Preferences/com.example.myapp.plist",
  ]
end
```

### Architecture-conditional cask
```ruby
cask "myapp" do
  version "1.0"

  on_arm do
    url "https://example.com/MyApp-#{version}-arm64.dmg"
    sha256 "arm_sha..."
  end

  on_intel do
    url "https://example.com/MyApp-#{version}-x86_64.dmg"
    sha256 "intel_sha..."
  end

  name "MyApp"
  desc "Does something useful"
  homepage "https://example.com/myapp"

  app "MyApp.app"
end
```

### Rolling release (no stable version)
```ruby
cask "myapp" do
  version :latest
  sha256 :no_check

  url "https://example.com/MyApp-latest.dmg"
  name "MyApp"
  desc "Does something useful"
  homepage "https://example.com/myapp"

  auto_updates true   # app has built-in updater
  app "MyApp.app"
end
```

---

## Bumping a Version

When a new release is out, update three things:

**Formula:**
```ruby
url "https://example.com/mytool-1.1.tar.gz"   # new URL
sha256 "newsha256..."                           # recompute (see below)
version "1.1"                                   # if not parseable from url
```

**Cask:** same fields, but also verify `#{version}` interpolation in `url` still produces the correct download URL with the new version string.

**Get the new checksum:**
```bash
# Option A: download and compute locally
shasum -a 256 mytool-1.1.tar.gz

# Option B: let brew fetch it
brew fetch --force --build-from-source ./Formula/mytool.rb
```

**Then verify:**
```bash
brew audit mytool
brew test mytool
```

**Faster alternative — let brew do it all:**
```bash
brew bump-formula-pr mytool --version 1.1 --url https://example.com/mytool-1.1.tar.gz
brew bump-cask-pr myapp --version 1.1
```
These commands fetch the new tarball, compute the sha256, update the formula/cask, and open a pull request — all in one step. Recommended when submitting bumps to homebrew-core or homebrew-cask.

---

## Submitting to Homebrew Core / Cask

To get your package into the official `homebrew/core` or `homebrew/cask` taps (so users can `brew install` without adding your tap), you need to open a PR against the upstream repo.

Eligibility requirements, the fork/PR workflow, audit flags, commit conventions, and common rejection reasons are covered in full at:

`references/homebrew-core-submission.md`

---

## Setting up a Personal Tap

### 1. Create the GitHub repo
Name it `homebrew-<tapname>` (e.g. `homebrew-tools`).

### 2. Directory structure
```
homebrew-tools/
├── Formula/
│   └── mytool.rb
└── Casks/
    └── myapp.rb
```

### 3. Tap and install
```bash
brew tap username/tools                      # taps github.com/username/homebrew-tools
brew install username/tools/mytool
brew install --cask username/tools/myapp
```

### 4. Test before publishing
```bash
brew install --build-from-source ./Formula/mytool.rb
brew install --cask ./Casks/myapp.rb
```

---

## Development Commands

```bash
# Create scaffolds
brew create https://example.com/tool-1.0.tar.gz
brew create --cask https://example.com/App-1.0.dmg

# Edit formula/cask
brew edit mytool
brew edit --cask myapp

# Install for testing
brew install --build-from-source ./Formula/mytool.rb
brew install --cask ./Casks/myapp.rb

# Run tests
brew test mytool
brew test --cask myapp

# Audit (validate before publishing)
brew audit --new mytool
brew audit --new --cask myapp

# Uninstall / reinstall cycle
brew uninstall mytool && brew install --build-from-source ./Formula/mytool.rb

# Style check
brew style ./Formula/mytool.rb
brew style --cask ./Casks/myapp.rb

# Fetch without installing (verify download)
brew fetch mytool
brew fetch --cask myapp
```

---

## Checksum Workflow

```bash
# Compute SHA-256 for a local file
shasum -a 256 myapp-1.0.dmg

# Or let brew fetch and show it
brew fetch --force --build-from-source ./Formula/mytool.rb
```

`brew create` auto-populates `sha256` if it can download the file during scaffold creation.

---

## Validation

Run before publishing to a tap:
```bash
brew audit --new mytool          # stricter checks for new packages
brew audit --new --cask myapp
brew style ./Formula/mytool.rb   # RuboCop style check
```

**Common audit issues:**
- `desc` must not start with "A" or "The", must not end with a period
- `homepage` must use HTTPS
- `license` required for formulas (use SPDX identifiers: `"MIT"`, `"Apache-2.0"`, etc.)
- `url` must be stable, versioned, and use HTTPS
- `sha256` must match the download exactly
- Test block required for formulas

---

## References

- Formula patterns & annotated examples: `references/formula-cookbook.md`
- Cask patterns & annotated examples: `references/cask-cookbook.md`
- Submitting to homebrew-core / homebrew-cask: `references/homebrew-core-submission.md`
- Official docs: https://docs.brew.sh/Formula-Cookbook
- Cask docs: https://docs.brew.sh/Cask-Cookbook

