Machine-centric · Git-based · Interactive · Non-destructive
BrewSync keeps your macOS machines in sync. Install something on one machine, easily replicate it on your others — no manual tracking required.
|
📦 Multi-Package Support
🎨 Interactive TUI
|
⚙️ Smart Management
🔒 Safe by Default
|
- Machine-centric: Each machine maintains its own Brewfile as the source of truth
- Git-based: Designed for dotfiles workflows, not cloud storage
- Non-destructive by default: Import mode only adds packages, never removes
- Interactive: User picks what to install, with batch options available
# Using Make (recommended)
make build # Build to ./bin/brewsync
make install # Install to $GOPATH/bin
# Or build directly with Go
go build -o brewsync ./cmd/brewsync
go install ./cmd/brewsync💡 Tip: Run
make helpto see all available commands. See MAKEFILE_GUIDE.md for details.
brewsync config initCreates |
brewsync dumpUpdates your Brewfile with all installed packages, including descriptions. |
brewsync statusShows machine info, package counts, and pending changes. |
|
Machine A (main workstation) 1. brewsync config init
2. brewsync dump
3. git commit & push |
Machine B (laptop) 1. brewsync config init
2. brewsync dump
3. git pull
4. brewsync diff --from machineA
5. brewsync import --from machineA |
# On your main machine - install new tools
brew install newtool
brew install --cask newapp
# Save state at end of day
brewsync dump
cd ~/dotfiles && git add -A && git commit -m "Update brewfile" && git push
# On another machine - sync up
cd ~/dotfiles && git pull
brewsync status # See what's new
brewsync import # Install missing packages| Command | Description |
|---|---|
dump |
Update Brewfile from installed packages |
list |
List packages in a Brewfile |
diff |
Show differences between machines |
import |
Install missing packages from another machine (interactive TUI) |
sync |
Make current machine match source exactly (preview + apply) |
| Command | Description |
|---|---|
status |
Show current machine state overview |
doctor |
Validate setup and diagnose issues |
history |
View operation history |
| Command | Description |
|---|---|
config show |
Display current configuration |
config edit |
Open config in $EDITOR |
config path |
Show config file path |
config init |
Initialize configuration |
config add-machine |
Add a new machine |
| Command | Description |
|---|---|
ignore list |
Show all ignored packages |
ignore add |
Add package to ignore list |
ignore remove |
Remove from ignore list |
ignore clear |
Clear all ignored packages |
| Command | Description |
|---|---|
profile list |
List available profiles |
profile show |
Display profile contents |
profile install |
Install packages from profile(s) |
profile create |
Create a new profile |
profile edit |
Edit profile in $EDITOR |
profile delete |
Delete a profile |
Using Profiles:
# Create a profile for core tools
brewsync profile create core --description "Essential tools"
brewsync profile edit core
# Install from profile on any machine
brewsync profile install core
# Install multiple profiles at once
brewsync profile install core,dev-go,k8s--config string Config file (default ~/.config/brewsync/config.yaml)
--dry-run Preview without executing
--verbose, -v Detailed output
--quiet, -q Minimal output
--no-color Disable colored output
--yes, -y Skip confirmationsbrewsync dump # Update Brewfile with descriptions
brewsync dump --commit # Commit changes to git
brewsync dump --push # Commit and push
brewsync dump --dry-run # Preview changesDescription Support: By default, brewsync dump uses brew bundle dump --describe to capture package descriptions from Homebrew's database. Descriptions appear as comments above each package in your Brewfile, making it self-documenting.
To disable automatic descriptions (manual collection), edit your config:
dump:
use_brew_bundle: falseSee DUMP_DESCRIPTIONS.md for more details.
brewsync import # Interactive TUI selection
brewsync import --from air # From specific machine
brewsync import --from mini,air # Union of multiple machines
brewsync import --only brew,cask # Filter categories
brewsync import --skip vscode # Exclude categories
brewsync import --yes # Install all without prompts
brewsync import --dry-run # Preview only
brewsync import --include-machine-specific # Include machine-specific packagesThe interactive TUI lets you:
- Toggle packages with
space - Select all/none with
a/n - Filter by category with number keys
1-8 - Search with
/ - Mark as ignored with
i - Confirm with
enter
brewsync sync # Preview mode (shows changes)
brewsync sync --apply # Execute changes
brewsync sync --from air # Sync from specific machine
brewsync sync --only brew # Only sync specific types
brewsync sync --apply --yes # Apply without confirmationSync differs from import:
- Import only adds missing packages
- Sync adds AND removes to match source exactly
- Protected packages (machine-specific, ignored) are never removed
brewsync list # Current machine
brewsync list --from mini # Another machine
brewsync list --only brew,cask # Filter by type
brewsync list --format json # JSON outputbrewsync diff # Compare with default source
brewsync diff --from air # Compare with specific machine
brewsync diff --only brew,cask # Filter to specific types
brewsync diff --format json # Output as JSONNote: Packages marked with (ignored) are in your ignore list and won't be installed during import or sync operations.
The ignore system has two layers stored in a separate ignore.yaml file:
Category-level ignores (ignore entire package types):
brewsync ignore category add mas # Ignore ALL Mac App Store apps
brewsync ignore category add go --machine mini # Ignore ALL Go tools on mini
brewsync ignore category remove mas # Remove category ignore
brewsync ignore category list # List ignored categoriesPackage-level ignores (ignore specific packages):
brewsync ignore add cask:bluestacks # Ignore specific package
brewsync ignore add brew:postgresql --global # Ignore globally
brewsync ignore add cask:steam --machine mini # Ignore on specific machine
brewsync ignore remove cask:bluestacks # Remove from ignore
brewsync ignore list # Show all ignores (categories + packages)Utility commands:
brewsync ignore path # Show ignore file location
brewsync ignore init # Create default ignore.yamlTwo-Layer System:
- Categories: Ignore entire package types (e.g., all
mas, allgo) - Packages: Ignore specific packages within non-ignored categories
Scope: Global (all machines) or per-machine
Note: Ignore lists apply to import, sync, and diff commands but not to dump. The dump command captures everything installed (source of truth).
brewsync profile list # List profiles
brewsync profile show core # Show profile contents
brewsync profile install core # Install from profile
brewsync profile install core,dev-go # Install multiple
brewsync profile create web-dev # Create new profile
brewsync profile edit core # Edit in $EDITOR
brewsync profile delete old-profile # Delete profileConfiguration is split into two files:
config.yaml- Main settings (machines, defaults, output)ignore.yaml- Ignore rules (separate file for better organization)
Both are stored in ~/.config/brewsync/.
machines:
mini:
hostname: "Andrews-Mac-mini"
brewfile: "/Users/andrew/dotfiles/_brew_mini/Brewfile"
description: "Mac Mini - primary workstation"
air:
hostname: "Andrews-MacBook-Air"
brewfile: "/Users/andrew/dotfiles/_brew_air/Brewfile"
description: "MacBook Air - portable"
current_machine: auto # Auto-detect from hostname
default_source: mini # Default machine for import/diff
default_categories:
- tap
- brew
- cask
- vscode
- cursor
- antigravity
- go
- mas
dump:
use_brew_bundle: true # Use 'brew bundle dump --describe' for descriptions
output:
color: true
verbose: false# Global ignores (apply to all machines)
global:
categories:
- mas # Ignore ALL Mac App Store apps
- go # Ignore ALL Go tools
packages:
cask:
- "company-vpn" # Specific cask to ignore
brew:
- "postgresql" # Specific brew formula to ignore
# Machine-specific ignores
machines:
mini:
categories:
- antigravity # Don't use Antigravity on mini
packages:
cask:
- "bluestacks" # Don't need on workstation
air:
categories: []
packages:
brew:
- "scrcpy" # Laptop-specific exclusionProfiles are YAML files stored in ~/.config/brewsync/profiles/.
name: core
description: "Essential tools for any machine"
packages:
tap:
- homebrew/bundle
brew:
- git
- fzf
- bat
- eza
- fd
- ripgrep
- lazygit
- starship
cask:
- raycast
- iterm2
vscode:
- vscodevim.vim
- eamodio.gitlens~/.config/brewsync/
├── config.yaml # Main configuration
├── ignore.yaml # Ignore rules (categories + packages)
├── history.log # Operation history
└── profiles/ # Profile definitions
├── core.yaml
├── dev-go.yaml
└── dev-python.yaml
~/dotfiles/ # Your dotfiles repo
├── _brew_mini/
│ └── Brewfile # Mini's package list
├── _brew_air/
│ └── Brewfile # Air's package list
└── ...
| Type | Source | Example |
|---|---|---|
tap |
Homebrew taps | charmbracelet/tap |
brew |
Homebrew formulae | git, fzf, bat |
cask |
Homebrew casks | raycast, slack |
vscode |
VSCode extensions | golang.go |
cursor |
Cursor extensions | ms-python.python |
antigravity |
Antigravity extensions | python.lsp |
go |
Go tools | golang.org/x/tools/gopls |
mas |
Mac App Store | 497799835 (Xcode) |
BrewSync uses the standard Brewfile format with extensions:
# Standard Homebrew entries
tap "homebrew/bundle"
# Distributed revision control system
brew "git"
brew "libpq", link: true
# Launcher and productivity tool
cask "raycast"
mas "Xcode", id: 497799835
vscode "golang.go"
# BrewSync extensions
cursor "golang.go"
antigravity "python.lsp"
go "golang.org/x/tools/gopls"Package Descriptions: Comments above packages (e.g., # Distributed revision control system) are automatically captured by brew bundle dump --describe. This makes your Brewfile self-documenting and helps when reviewing packages across machines.
brewsync doctorThis checks:
- Config file exists and is valid
- Current machine is detected
- Brewfile paths exist
- Required CLI tools are available
| Issue | Solution |
|---|---|
| "Machine not recognized" | Run brewsync config init or add machine manually |
| "Brewfile not found" | Run brewsync dump to create it |
| "brew command failed" | Check package name, verify network |
| CLI not available | Install missing tool (code, cursor, mas, go) |
- macOS
- Go 1.21+ (for building from source)
- Homebrew
- Optional: VSCode (
codeCLI), Cursor (cursorCLI), Antigravity (agyCLI), mas-cli, Go
# Quick development cycle
make quick # Build + test (fastest)
make dev # Build + show version
# Testing
make test # Run all tests
make test-coverage # With coverage
make test-verbose # Verbose output
# Code quality
make pre-commit # Format + vet + test (run before committing)
make ci # CI checks
# Build
make build # Build to ./bin/brewsync
make install # Install to $GOPATH/bin
make release # Optimized production build
# Demo generation
make demo # Generate all demo GIFs
make demo-quick # Generate quick demo
make demo-tui # Generate TUI demo
# Manual testing
make test-setup # Setup test environment
# ... follow MANUAL_TEST_GUIDE.md
make test-cleanup # Cleanup test environment
# More commands
make help # See all available commandsSee MAKEFILE_GUIDE.md for complete documentation.
The following TUI features are planned or need fixes:
| # | Item | Description |
|---|---|---|
| 1 | Fix TUI Sync page | Sync preview and apply functionality not fully working |
| 2 | Add global i/X hotkeys |
Install (i) or uninstall (X) package under cursor in List/Diff screens |
| 3 | Fix Config TUI page | Config screen not displaying/editing properly |
| 4 | Fix Profiles TUI page | Profiles screen not fully functional |
| 5 | Support searching with / |
Add fuzzy search in List, Diff, Import screens |
| 6 | Fix diff lists height bug | Column heights not calculated correctly in some cases |
| 7 | Enhance Doctor screen | Add recommended fixes for warnings + descriptions for each check |
MIT
