My dotfiles, managed as a bare git repository at ~/.cfg with $HOME as the work tree.
This uses the bare repo pattern: a git repo lives at ~/.cfg/ and tracks files
directly in $HOME without a .git directory polluting the home folder. All
git operations go through the config alias defined in .zshrc:
alias config='git --git-dir=$HOME/.cfg/ --work-tree=$HOME'This means config status, config add, config commit, etc. work exactly
like regular git commands but operate on the dotfiles repo.
New files in $HOME are not tracked by default. The bare repo's own ignore
file (~/.cfg/info/exclude) contains a single *, so every untracked file is
ignored and config add -A can only ever stage changes to already-tracked files.
To start tracking a new file, force it:
config add -f ~/.some-new-dotfileNote this is ~/.cfg/info/exclude, not ~/.gitexclude. The latter is the
global core.excludesfile and applies to every repo on the machine; putting *
there would break unrelated projects.
The shell config is split by concern and OS:
| File | Purpose |
|---|---|
.zshenv |
Shared environment variables (loaded for all shells) |
.zshenv.macos |
macOS-specific env: Homebrew, PATH, Rust, Docker |
.zshenv.linux |
Linux-specific env: keychain SSH agent |
.zshrc |
Shared interactive config: oh-my-zsh, aliases, NVM lazy-loading |
.zshrc.macos |
macOS interactive: pyenv, Nix, Homebrew completions, Alacritty/Zellij integration |
.zshrc.linux |
Linux interactive: tmux TERM fix |
.config/starship.toml |
Starship prompt config (if customized) |
OS-specific files are sourced conditionally via case "$OSTYPE" at the end of
.zshenv and .zshrc.
Vim (.vimrc, .vim/): Uses Vim's native pack/ plugin system. Plugins
live in .vim/pack/latex/start/ as git submodules (note: the latex directory
name is historical — it contains general plugins too). Key plugins: vimtex,
vim-airline, vim-fugitive, UltiSnips.
Doom Emacs (.doom.d/): Primary editor. init.el declares modules,
config.el has package configuration, packages.el declares extra packages.
org-config.el (tangled from org-config.org) contains extensive org-mode,
org-roam, org-agenda, and bibliography setup.
.config/alacritty/alacritty.toml— Alacritty terminal config (TOML format).config/zellij/config.kdl— Zellij terminal multiplexer.tmux.conf/.tmux.conf.local— tmux config (uses gpakosz/.tmux framework via submodule at.tmux/)
.gitconfig— User identity, aliases (co,ci,st,br,hist,la,sync), SSH credential helpers, per-org URL rewrites.gitexclude— Global gitignore patterns (LaTeX build artifacts, Python bytecode, macOS files).gitmodules— Submodule declarations (.tmux)
Packages are declared in tracked files rather than managed imperatively:
| File | OS | Format |
|---|---|---|
Brewfile |
macOS | Homebrew bundle format (brew, cask, tap) |
.packages.arch |
Arch Linux | Plain text, one package per line (comments with #) |
Two shell functions (defined in .zshrc) keep things in sync:
Pulls the latest dotfiles and installs any missing packages in one step:
config pull --rebase— fast-forward the bare repo- Detects the OS and runs the appropriate package installer:
- macOS:
brew bundle install --file=~/Brewfile - Arch Linux:
sudo pacman -S --neededfrom~/.packages.arch
- macOS:
exec zsh— reloads the shell to pick up any config changes
Also available as config sync (git alias).
Reports missing tools without changing anything:
- Checks for common CLI tools:
git,zsh,fzf,rg,bat - macOS: checks for Homebrew and key formulae (
fd,lsd,cmake,tmux,zellij,uv) - Arch Linux: walks
.packages.archand reports any uninstalled packages
Prints "All good!" if everything is present, or lists each missing tool.
curl -L https://gist.github.com/hugcis/73191d55b6bc77815fc4df3b9a62a9a3/raw/ | /bin/bash~/.config/agent-stack/ renders one shared roster of coding agents into four
harnesses (pi, opencode, vibe, Claude Code) and installs the shared skills. Three
pieces of it are deliberately not in git — this repo is public — so a fresh
clone needs them recreated by hand, in this order.
# 1. Bootstrap the ignore rule, in dry form. install.sh creates ~/.cfg/info/exclude
# (git never clones it) and then stops, because there is no profile yet.
~/.config/agent-stack/install.sh
# 2. Machine profile: endpoints, per-harness provider spellings, and which model
# fills each abstract slot. Never committed.
cp ~/.config/agent-stack/profile.example.yaml ~/.config/agent-stack/profile.yaml
$EDITOR ~/.config/agent-stack/profile.yaml # replace every REPLACE-ME
# 3. Pre-commit denylist: the strings this machine must never publish. Never
# committed — a denylist in a public repo publishes what it protects.
cp ~/.config/agent-stack/denylist.example.txt ~/.config/agent-stack/denylist.txt
$EDITOR ~/.config/agent-stack/denylist.txt # one extended-regex pattern per line
# 4. Pre-commit guard. Git does not clone hooks either, and the hook refuses to
# run without step 3, so every commit is blocked until both are in place.
mkdir -p ~/.cfg/hooks
cp ~/.config/agent-stack/pre-commit.sample ~/.cfg/hooks/pre-commit
chmod +x ~/.cfg/hooks/pre-commit
# 5. Now the real run: skills symlinked into each harness, agents rendered,
# profile.env written, and profile.env sourced from ~/.zshenv.<os>.
~/.config/agent-stack/install.shStep 1 is safe to repeat: it only appends the * rule when info/exclude does
not already contain one. Run install.sh again after editing profile.yaml,
roster.yaml, or the prompts.
~/.config/agent-stack/render.py does the rendering and can be run on its own
(python3 ~/.config/agent-stack/render.py) once profile.yaml exists. It
overwrites the per-harness agent directories and the AGENTS.md/CLAUDE.md
files wholesale, keeping timestamped copies of anything it displaces under
~/.local/state/agent-stack/.