Personal configuration for Fish, Neovim, Zellij, Ghostty, mise, Git, and SSH, managed via symlinks into ~/.config/ and ~/. Two Python scripts (install.py, uninstall.py) wire it up. Stdlib only — Python 3.9+.
git clone <your-repo-url> ~/dotfiles
cd ~/dotfiles
./install.py # see `./install.py --help` for flagsOn the first install, set up machine-local files (all gitignored):
# Required — email + SSH signing key for commits
cp gitconfig.local.template ~/.gitconfig.local
nvim ~/.gitconfig.local
# Optional — work-specific env vars / abbreviations
cp fish/config.local.fish.template ~/.config/fish/config.local.fish
# Optional — zellij overrides (cert paths, etc.). Appended to the generated
# ~/.config/zellij/config.kdl on every install.py run.
nvim ~/.config/zellij/config.local.kdl # then re-run ./install.pyThen restart your shell (exec $SHELL) and open nvim once to initialize plugins.
./uninstall.pyRemoves only symlinks that point back into this repo, restores the exact .bak path recorded by install.py, and copies preserved machine-local files (fish/config.local.fish, zellij/config.kdl) back into the restored directories. Without a state record, backups are left untouched for manual recovery rather than guessed by filename.
Global tooling splits between mise and brew:
mise— language runtimes and developer CLIs (anything installable viacargo:,go:,pipx:,npm:, or a native/aqua plugin). Config inmise/config.toml.brew— only what can't live in mise cleanly:miseitself (bootstrap),fish(login shell), GPG/macOS integration (gnupg,pinentry-mac), third-party taps, R (ther-appcask, so CRAN's prebuilt packages work), a handful of trivial unix utilities. Tracked inBrewfile.
install.py runs brew bundle against the committed Brewfile — additive only, never cleanup, so ad-hoc brew installs are left alone. Skip with --skip-brew or DOTFILES_SKIP_BREW=1; no-ops when brew isn't on PATH and exits nonzero when an attempted bundle install fails. The r-app cask installs from CRAN's .pkg, so a fresh run stops once for your sudo password, even with --force.
For code-level details (how the symlink/backup/state machinery works, how to add a new dotfile), see CLAUDE.md.
python3 -m unittest discover tests -v