Personal macOS dotfiles. Module-based — pick what you need, skip what you don't.
Catppuccin Mocha everywhere. Vim keybindings everywhere.
git clone https://github.com/philngo/dotfiles.git ~/dev/dotfiles
cd ~/dev/dotfiles
# Optional: choose your modules (defaults to all if skipped)
cp modules.conf.example modules.conf
vim modules.conf
# Install
./install.shinstall.sh is idempotent — safe to re-run anytime. It installs Homebrew (if needed), installs packages for enabled modules, and symlinks config files to the right places.
After install, follow the post-install reminders printed at the end (local config files, macOS defaults, etc.).
| Module | What you get |
|---|---|
| core | Zsh (history, completions, aliases), Starship prompt, bat, eza, fzf, fd, ripgrep, Nerd Font |
| git | Git config, delta (side-by-side diffs), lazygit, shell functions (gs, gbd, grm, gcm, gf) |
| jj | Jujutsu VCS config, jj-starship prompt, shell functions (js, jjs, jr, vb, jw-add, jw-rm, jw-list) |
| nvim | Neovim with lazy.nvim, 40+ plugins, LSP, Treesitter, Telescope |
| wezterm | WezTerm terminal — project workspaces, pane/tab keybindings, dev layouts |
| wm | AeroSpace tiling window manager + JankyBorders |
| ai | Claude Code + Codex — user instructions, agents, hooks, skills |
| tools | yazi, atuin, direnv, mise, zoxide, gh, glow, and more |
| apps | GUI apps — Slack, Zoom, VS Code, Spotify, Figma, Alfred, etc. |
Use all in modules.conf to enable everything (automatically picks up new modules):
all
Or list only what you need:
core
git
nvim
wezterm
ai
Run cs (or cheatsheet) to view keybindings for all tools in the terminal.
Some settings vary per machine (git email, paths, etc.). These use .local files that aren't tracked:
cp ~/dev/dotfiles/home/.gitconfig.local.example ~/.gitconfig.local
cp ~/dev/dotfiles/home/.zshrc.local.example ~/.zshrc.local
cp ~/dev/dotfiles/config/jj/conf.d/local.toml.example ~/.config/jj/conf.d/local.tomlMachine-specific Homebrew packages go in brew/local.Brewfile (also git-ignored).
# Re-run to install any new packages for enabled modules
./install.sh
# Or manually for a specific Brewfile
brew bundle --file=brew/tools.BrewfileSymlink-based, no stow. install.sh reads modules.conf, then:
- Concatenates
brew/<module>.Brewfilefor enabled modules → singlebrew bundle - Symlinks
home/*→~/, filtered by module ownership - Recursively symlinks individual files in
config/*→~/.config/(not whole directories, to avoid clobbering unmanaged files) - Symlinks
zsh/<module>.zsh→~/.config/zsh/for enabled modules - Picks
starship-jj.tomlorstarship-git.tomlbased on whether jj module is enabled - Symlinks Claude rules from
claude/rules/for enabled modules (uses~/.claude/rules/directory) - Symlinks Claude hooks from
claude/hooks/<module>/for enabled modules - Runs post-install steps (mise, yazi plugins, bat theme cache, etc.)
Existing non-symlink files are backed up to *.backup before overwriting.
modules.conf.example # All modules listed — copy to modules.conf
brew/ # Per-module Brewfiles + local.Brewfile (git-ignored)
zsh/ # Per-module shell configs → ~/.config/zsh/
core.zsh # history, completions, aliases, prompt, plugins
git.zsh # git functions (gs, gbd, grm, gcm)
jj.zsh # jj functions (js, jjs, jr, vb, jw-*)
tools.zsh # mise, direnv, zoxide, atuin, yazi
home/ # Dotfiles → ~/
config/ # Configs → ~/.config/
starship-git.toml # prompt variant (git-only)
starship-jj.toml # prompt variant (jj-starship)
nvim/ # Neovim (lazy.nvim, plugins in lua/plugins/init.lua)
wezterm/ # WezTerm (projects.lua is git-ignored)
jj/ # Jujutsu VCS
atuin/ # Shell history
yazi/ # File manager
bat/ # Syntax highlighting themes
delta/ # Git diff pager themes
claude/
CLAUDE.md # Base user instructions → ~/.claude/CLAUDE.md
rules/jj.md # jj workflow rules → ~/.claude/rules/ (if jj enabled)
hooks/wezterm/ # Notification hooks (if wezterm enabled)
agents/ # Custom agents → ~/.claude/agents/
codex/
AGENTS.md # User instructions → ~/.codex/AGENTS.md
skills/ # Custom skills → ~/.codex/skills/
iterm/ # iTerm2 dynamic profiles
macos/defaults.sh # macOS system preferences
docs/ # Manual setup guides
When Claude Code is waiting for permission in one WezTerm workspace and you're in another, a macOS notification is sent. Clicking it switches to the right workspace.
Requires the wezterm module. The hooks (claude-notify, wezterm-focus) are in claude/hooks/wezterm/ and only installed when that module is enabled.
First-time setup: grant notification permission to terminal-notifier in System Settings → Notifications. Without this, the hook runs successfully but no banner appears. Also make sure Do Not Disturb / Focus isn't on — DND silently suppresses banners.
./macos/defaults.shSee docs/manual-setup.md for settings that can't be automated (trackpad, Touch ID, display arrangement, etc.).
- Place it in
home/(for~/) orconfig/(for~/.config/) - If it belongs to a specific module, add the mapping in
install.sh(home_moduleorconfig_module) - Run
./install.sh
- Create
brew/<name>.Brewfilewith its packages - Optionally create
zsh/<name>.zshfor shell integrations - Add config dirs/home files and update the mappings in
install.sh - Add the module to
modules.conf.example
Users with all in their modules.conf pick it up automatically.
# Dump everything currently installed (useful for auditing drift)
brew bundle dump --force