My config files for maintaining a consistent dev environment across machines.
Note
The screenshot above is outdated and does not reflect the current setup.
- Editor: NeoVim, with a lightweight Vim fallback config (no dependencies) for maximum portability.
- Multiplexer: Herdr, my daily driver since it is agent-first and a lot of my work these days runs through coding agents. Tmux stays as a fallback, it has served me well for years. Both are configured to share the same muscle memory, so switching between them is seamless.
- Main Terminal: Ghostty (Previously: WezTerm)
- Shell Prompt: Starship
- Color Theme: Nord across all tools.
- Window Management: Rectangle + Karabiner-Elements for keyboard-driven window resizing and app switching.
- File Manager: Yazi (Previously: Ranger)
- Coding Agent: Claude Code, see Claude Code below.
Note
This repo also includes the config for VSCode, which I no longer actively use. I keep it around as reference and for easy reactivation, its symlink and Brewfile entries are simply commented out.
I find macOS window management extremely frustrating: Repeatedly pressing Cmd+Tab to switch apps or having to reach for the mouse to click and drag. It's painfully slow and breaks my flow. To streamline my workflow, I built a custom setup using Karabiner-Elements and Rectangle. Together, they let me manage windows and switch apps with minimal mental overhead, at maxium speed, entirely from the keyboard. Here's how it works:
The Tab key acts as a regular Tab when tapped, but when held it becomes a modifier (hyperkey) that unlocks two layers:
- Window layer (
Tab + W + ...): Resize and position windows via Rectangle. E.g.Tab + W + Hfor left half,Tab + W + Lfor right half. - Expose layer (
Tab + E + ...): Jump directly to a specific app. E.g.Tab + E + Jfor browser,Tab + E + Kfor terminal.
The claude/ directory holds global instructions (CLAUDE.md), settings, keybindings, and a status line script. Each file is symlinked individually into ~/.claude/, because that directory also stores session state and credentials that must never be versioned.
CLAUDE.md ends with two imports that are not part of this repo, @~/.claude/work.md and @~/.claude/private.md, and an absent import silently resolves to nothing. private.md comes from the private counterpart below. work.md stays machine-local, so a fresh work machine needs it copied over by hand.
Plugins are restored from the enabledPlugins entry in settings.json. Skills, subagents, commands, and hooks come from the private counterpart.
Anything personal or otherwise not publishable lives in dotfiles-private, cloned next to this repo at ../dotfiles-private. Same structure as here, one directory per tool, driven by its own symlinks.conf. Today that is only Claude Code, but nothing about the setup is specific to it.
Its symlinks.sh reads that config the way scripts/symlinks.sh reads the one here, with one addition: a source ending in /* links every entry inside a directory rather than the directory itself. That form is needed for targets like ~/.claude/skills, which also hold Homebrew-managed and separately installed content that a directory-level symlink would hide.
install.sh calls the script at the end when the repo is present and skips the step otherwise, so a machine without it still installs cleanly.
Clone this repo, and the private counterpart next to it if you have access to it:
git clone https://github.com/hendrikmi/dotfiles.git
git clone https://github.com/hendrikmi/dotfiles-private.gitThen run the installer from the repo root and follow the on-screen prompts:
./install.shThe private repo has to be in place before this runs, otherwise the installer skips it and you need a separate ../dotfiles-private/symlinks.sh afterwards.
To remove all symlinks created by the installation script:
./scripts/symlinks.sh --deleteThis only removes the symlinks, not the actual config files, so you can easily revert if needed. The private counterpart is removed separately with ../dotfiles-private/symlinks.sh --delete.
- Place the config file in the appropriate directory within this repo.
- Add a symlink entry in
symlinks.conf. - If needed, update
install.shto handle any additional setup.
Software is managed via Homebrew. To add a formula or cask, update homebrew/Brewfile and run ./scripts/brew-install.sh.
To pin a specific version, create a local tap with brew tap-new <user>/local and place the formula there. Homebrew rejects formulae and casks that live outside a tap, so a plain .rb file inside this repo cannot be installed.
