Dev setup
The machines, editor, terminal and tooling I write code with.
Dotfiles
Everything starts from my dotfiles repo. It’s managed with
Dotbot, which symlinks each config
into place, plus the dotbot-brew
plugin, which installs my Homebrew packages in the same run. On a fresh
machine, one ./install gets me my shell, prompt, git config and base tools.
Two machines, one setup
I work on a Mac and on a Debian VM running code-server. The Mac uses Homebrew and the VM uses Linuxbrew, which is Homebrew on Linux. Both install from the same package list in the dotfiles, so the tooling is the same wherever I sit down.
Editor
VS Code for everything. On the VM that’s code-server, which is still VS Code, just served to a browser.
- Theme: Monokai Pro. I’ve used some flavour of Monokai since the early Sublime Text days, and I got so used to scanning the way it hightlights syntax that I just stick with it.
- Icons: vscode-icons.
- Start page: VSHome, a welcome page I built. I can pin projects, see recent ones, and give each project an icon and a proper name instead of its folder name. New projects are created in my default projects folder automatically.
Layout: the side bar (files, git, extensions) lives on the right. When I widen it to read a long file name, or because an extension looks better wider, my code doesn’t move. It stays in exactly the same spot on screen. The terminal panel is almost always open at the bottom.
Terminal
- App: Ghostty when I want a dedicated window, otherwise the terminal built into VS Code.
- Font: MesloLGS Nerd Font Mono.
- Shell: zsh, with a mix of plugins (syntax highlighting, autosuggestions) and small scripts of my own.
- Prompt: oh-my-posh with a custom theme.
- direnv: loads a project’s environment variables
when I
cdinto it, and unloads them when I leave. - thefuck: fixes the command I just mistyped, so I don’t have to retype it.
Project tooling
Every repo gets the same shape, so I never have to remember how a project runs:
- just is the entrypoint:
just setup,just dev,just test,just lint. - uv manages Python versions and virtual environments.
- nvm manages Node versions. It only
loads the first time I run
nodeornpm, so new shells stay fast. - overmind runs multi-process dev
stacks, started from
just dev. - Docker runs local services like Postgres and Redis.
- pre-commit runs linting and formatting. Hook versions are pinned, so my machine and CI always agree.
Linting
Ruff for Python and oxlint for JavaScript and TypeScript (I switched from ESLint).
On top of that, I use an AI skill that sets up “anti-slop” lint rules in a project. The skill doesn’t check any code itself. It writes rules into the repo that Ruff and oxlint then enforce directly. So the checks run at linter speed, cost nothing per run, and give the same answer every time, catching problems before they’re committed.
AI
Claude Code is my go-to AI harness. Part of that is simply that I already pay for a Claude subscription, which makes it the obvious choice. But I also genuinely like using it. I’d like to try other setups, just not enough to pay API pricing for them.
My Claude Code setup lives in the dotfiles too: my global instructions
and custom skills, such as the anti-slop one above. The same
./install links them into place, so every machine gets the same AI setup
along with everything else.