This repo is the portable source of truth for my shell and terminal config. It is organized as Stow packages and managed through a global dotfiles CLI so I can recreate the same setup on any macOS machine without having to cd ~/dotfiles for every change.
~/dotfilesis the source of truth.dotfilesis the global command you run from anywhere.Brewfiledefines software to install with Homebrew.stow-packages.txtdefines which config packages are symlinked into$HOME.- Each top-level package directory mirrors the destination path inside
$HOME.
In practice:
- software installation lives in
Brewfile - tracked config lives in a Stow package like
zsh/,tmux/, ornvim/ dotfiles rebuildrestores both sides: Brew packages and symlinked config
Quick start:
curl -fsSL https://raw.githubusercontent.com/parabolabam/dotfiles/main/scripts/install-cli.sh | bash
dotfiles doctor
dotfiles rebuildIf you already cloned the repo and just want the command:
~/dotfiles/scripts/install-cli.shIf you want the full bootstrap path, ./setup.sh still exists and now installs the CLI before applying packages.
Check that the CLI can see the repo and required tools:
dotfiles doctorSee which config packages are managed:
dotfiles listRestow everything:
dotfiles stowRestow only specific packages:
dotfiles stow zsh tmux nvimRemove one package's symlinks:
dotfiles unstow kittyRebuild a machine from the repo:
dotfiles rebuildSync submodules, including the Neovim config:
dotfiles syncRemove a tracked package cleanly:
dotfiles remove atuin --brewEach top-level directory is a Stow package. Files underneath mirror their destination paths in $HOME.
dotfiles/
├── stow-packages.txt
├── bin/dotfiles
├── scripts/add-package.sh
├── scripts/install-cli.sh
├── scripts/stow-packages.sh
├── Brewfile
├── setup.sh
├── zsh/
├── git/
├── ssh/
├── tmux/
├── kitty/
├── alacritty/
├── starship/
├── lazygit/
├── yazi/
├── btop/
├── k9s/
├── claude/
├── gemini/
└── nvim/
The nvim/.config/nvim directory is a Git submodule that points at https://github.com/parabolabam/LazyVimStarter.
The dotfiles add command does two jobs:
- it creates or adopts a Stow package in the repo
- it can also register a Homebrew formula or cask in
Brewfile
The important part is this:
--brewor--caskupdatesBrewfile--installactually runsbrew installorbrew install --cask- if a local config already exists, it is automatically adopted into the repo
In your shell, brew install ... is intentionally intercepted and routed through dotfiles.
That means these pasted commands:
brew install atuin
brew install --cask zed
brew install go-task/tap/go-taskbehave like tracked dotfiles operations:
- the package is added to
Brewfile - a matching Stow package is created in
~/dotfiles - the package is installed immediately
- the config package is restowed
So the normal habit should be:
- paste
brew install ...exactly as tools/docs provide it - let the shell convert it into
dotfiles add ... --install - use
dotfilesdirectly only when you want finer control
If you want to bypass tracking and call real Homebrew directly, use:
command brew install <package>There is matching removal behavior too:
brew uninstall atuin
brew uninstall --cask zedThese are also routed through dotfiles, so they:
- remove the package from
Brewfile - remove the package from
stow-packages.txt - unstow and delete the matching package directory in
~/dotfiles - uninstall it from Homebrew
If you want raw Homebrew uninstall behavior without touching the repo, use:
command brew uninstall <package>Only brew install ... and brew uninstall ... interception are special. Other commands like brew info, brew update, brew bundle, and brew upgrade still go to Homebrew normally.
Register a formula and create its tracked config package, but do not install it yet:
dotfiles add atuin --brewRegister and install a formula right now:
dotfiles add atuin --brew --installRegister and install a cask:
dotfiles add zed --cask --installCreate a package for something that stores config outside .config:
dotfiles add claude-local --path .claudeAdd a formula with a custom Brewfile entry:
dotfiles add go-task --brew-name go-task/tap/go-task --tap go-task/tap --installWhat dotfiles add changes:
- appends the package name to
stow-packages.txt - adds a Brewfile entry if
--brew,--brew-name,--cask, or--cask-nameis used - creates a package directory under
~/dotfiles/<name>/... - moves existing local config into that package if the target already exists
- restows that package immediately
One caveat: the Homebrew formula name is not always the shell command you run later. For example, forgit installs git-forgit, not a forgit binary. When that matters, check brew info <formula>.
The tracked Zsh config includes:
- a
brew()wrapper that interceptsbrew install ...andbrew uninstall ... - two explicit shortcuts if you want to call the tracked flow directly
bi <formula>
bci <cask>They expand to:
bi foo->dotfiles add foo --brew --installbci foo->dotfiles add foo --cask --install
Examples:
brew install atuin
brew install --cask zed
bi atuin
bci zedReload your shell after pulling changes to pick these up:
source ~/.zshrcAudit package drift against the Brewfile:
dotfiles brew auditInstall or restore everything declared in the Brewfile:
dotfiles brew installPreview packages that are installed locally but not declared in the Brewfile:
dotfiles brew cleanup-previewActually remove packages not declared in the Brewfile:
dotfiles brew cleanup-forceKeep in this repo:
- shell/editor/tool config that should follow you to another machine
- theme files, aliases, tmux config, Neovim config, and portable app settings
Keep out of this repo:
- credentials, tokens, OAuth state, cloud configs, logs, caches, history files
- auto-generated backups and app runtime state
- host-specific overrides that only make sense on one machine
~/dotfilesis the source of truth.dotfilesis the global command entrypoint.~/.configis the live target directory after symlinks are applied.- The supported path for now is Homebrew + Stow.
dotfiles add ... --brewonly registers the formula; add--installif you want it installed immediately.