Personal configuration, tracked in one place and symlinked into the locations each tool expects.
Each tool has its own README with what it is, why it's used, and how the config is set up.
| Tool | What it's for | Config |
|---|---|---|
| Zsh | Shell + prompt, modular fast-startup setup | config/zsh/, zshenv, zshrc, zprofile |
| Ghostty | GPU terminal emulator (daily driver) | config/ghostty/ |
| kanata | Keyboard remapper: Caps -> Esc / Hyper, app launchers | config/kanata/ |
| Neovim | Main text editor plus a locally built macOS Finder launcher | config/nvim/ |
| Vim / IdeaVim | Shared Vim config for real Vim + JetBrains IDEs | config/vim/ |
| Git | Version control identity + line-ending / ignore policy | config/git/ |
| OpenCode | Terminal AI coding agent | config/opencode/ |
| Gradle | Global JVM / Android build tuning | gradle/ |
| SSH | SSH client config (private keys stay untracked) | ssh/ |
| GnuPG | GPG signing / encryption defaults (keys untracked) | gnupg/ |
| Agent instructions | Shared AI-agent rules (Claude / Gemini / OpenCode / Codex) | AGENTS.md |
The real files live in this repo with plain, visible names (zshrc, not .zshrc).
dotfiles.conf maps each source to its target under $HOME, and
install.sh creates the symlinks.
The installer also links every regular file under scripts/bin/ into $HOME/.local/bin.
Only individual files are ever symlinked. When a manifest entry points at a directory, every file beneath it is linked one by one and the directory tree at the target is created as real directories. That keeps app-generated junk (lock files, caches, state) out of this repo while still letting the app write next to the linked files.
git clone <repo> ~/.dotfiles
~/.dotfiles/install.shinstall.sh is idempotent:
- links already pointing at the right place are left alone
- manifest target collisions are moved into
.backups/<timestamp>/first - wrong executable symlinks are refreshed, while unrelated executable destination files are left untouched and reported as errors
- Move the file into this repo, mirroring its
$HOMEpath but dropping the leading dot (e.g.~/.config/foo/bar→~/.dotfiles/config/foo/bar). - Add a
SOURCE TARGETline todotfiles.conf. - Run
./install.sh.
zshenv, zshrc, zprofile → ~/.zshenv, ~/.zshrc, ~/.zprofile
config/zsh/ → ~/.config/zsh/*.zsh (sourced by ~/.zshrc)
config/git/ → ~/.config/git/config, ignore, attributes
config/ghostty/config → ~/.config/ghostty/config
config/nvim/ → ~/.config/nvim/ (lua config; lazy-lock.json stays app-managed)
config/vim/ → ~/.config/vim/ (vimrc + colors) and ~/.config/ideavim/ideavimrc (mirrors the nvim setup)
config/opencode/ → ~/.config/opencode/opencode.jsonc, tui.json
gradle/gradle.properties → ~/.gradle/gradle.properties
ssh/config → ~/.ssh/config
gnupg/gpg.conf, gpg-agent.conf → ~/.gnupg/gpg.conf, ~/.gnupg/gpg-agent.conf
AGENTS.md → ~/.claude/CLAUDE.md, ~/.gemini/GEMINI.md, ~/.config/opencode/AGENTS.md, ~/.codex/AGENTS.md, ~/AGENTS.md
scripts/bin/* → ~/.local/bin/*
config/nvim/launcher/* → source for locally built ~/Applications/Nvim.app
Only config is tracked, never key material. install.sh forces ~/.ssh and ~/.gnupg
to 0700, and .gitignore refuses to stage private keys, keyrings, and GnuPG
runtime state even if one is ever copied into the repo by accident.