Following up on the intro post, here’s the setup I actually work in day to day. None of this is exotic, but it’s stable, it’s synced across machines, and it’s the kind of thing I wish someone had written down for me when I was assembling it.
The hardware
I run a Windows Dell XPS 17. Windows is the host OS, but almost none of my actual work happens there. It’s a shell for WSL.
WSL as the real environment
WSL2 runs Ubuntu, and that’s where the research computing happens: analysis code, grid job submission, git, everything. The split works well in practice. Windows handles hardware, drivers, and anything that wants a native GUI; Ubuntu handles the actual software environment, which matters when your collaborations assume a Linux-like target (novagpvm, emphaticgpvm, dunegpvm, and the like all expect that world).
WezTerm as the terminal
WezTerm is the terminal emulator on the Windows side, configured to launch straight into WSL Ubuntu on startup rather than opening a Windows shell first. A couple of details make the WSL boundary less annoying:
- A small startup script attaches to my existing
maintmux session, or creates it if it does not exist, so opening a terminal takes me straight back to the same workspace. - A
gui-startuphook maximizes the window automatically, so every new WezTerm launch starts full screen without me touching it.
The config lives at ~/.wezterm.lua inside WSL and gets copied out to the
Windows side after edits, since WezTerm on Windows reads its config from the
Windows home directory, not the Linux one. The color scheme is
Dracula (Tokyo Night is sitting commented out
right next to it as the backup option), and that same palette carries through
to the prompt below, so more on it there.
tmux for persistence
tmux keeps sessions alive independent of the terminal window, which matters
most for long-running or remote work: a grid job monitor, an SSH session to a
gpvm, or a long build that I don’t want to lose if WezTerm closes. Prefix is
remapped from the default Ctrl-b to Ctrl-a, panes split with | and -
instead of the default bindings, and Alt+arrow switches panes without needing
the prefix at all, which turns out to be the single biggest quality-of-life
change once it’s muscle memory. tmux-resurrect and tmux-continuum (via
TPM) handle the actual persistence:
sessions get saved automatically and reconstructed on the next tmux start.
After a reboot I get the pane layout and working directories back, although
live processes do not survive a WSL shutdown.
zsh, plugins, and fzf
The shell itself is zsh, with a couple of plugins sourced directly rather
than through a full framework: zsh-syntax-highlighting for as-you-type
command coloring, and zsh-autosuggestions for fish-style history
completions as I type. fzf adds fuzzy
file and history search on top of that. A small ls wrapper function
translates familiar flags (-l, -t, -r) onto
eza so directory listings get icons
and git status without having to relearn a new command.
The useful lesson here was to measure before replacing the most visible piece. My interactive shell was taking roughly 1.6 seconds to start; removing oh-my-zsh and lazy-loading pyenv cut that to roughly 0.8 seconds. nvm is lazy-loaded for the same reason. Changing the prompt alone was not the fix.
Atuin for shell history
Atuin layers a searchable local SQLite database over plain shell history, tagging commands with their directory, exit code, duration, hostname, and session. I keep it local-only for now rather than syncing history between machines. Searching by content, not just recency, turns “what was that command I ran last month to fix the CVMFS mount” from a scrollback hunt into a two-second lookup. Hooks also record shell commands run by Claude Code and Codex, so I can find what an agent ran later in the same history.
yadm for dotfiles
yadm manages the dotfiles themselves: .zshrc,
.tmux.conf, .wezterm.lua, .config/starship.toml, and the various
CLAUDE.md/AGENTS.md agent instruction files that now live alongside them.
It’s a thin wrapper around git, which means the whole home-directory config is
just a repo I can clone onto a new machine and be back to a working
environment in minutes, encrypted secrets included via yadm encrypt.
The rest of the prompt
Starship renders the actual prompt: current
directory, git branch and status, command duration, and the active Python
environment, all on a Dracula-themed palette that matches the WezTerm color
scheme, so the whole stack looks like one thing rather than four tools duct
taped together. The actual palette, straight out of starship.toml:
#282a36
#44475a
#f8f8f2
#6272a4
#8be9fd
#50fa7b
#ffb86c
#ff79c6
#bd93f9
#ff5555
#f1fa8c
palette = "dracula"
[palettes.dracula]
background = "#282a36"
current_line = "#44475a"
foreground = "#f8f8f2"
comment = "#6272a4"
cyan = "#8be9fd"
green = "#50fa7b"
orange = "#ffb86c"
pink = "#ff79c6"
purple = "#bd93f9"
red = "#ff5555"
yellow = "#f1fa8c"
WezTerm runs the same Dracula scheme, so moving from a prompt to a diff to a man page never involves a jarring color shift.
VS Code, for the GUI moments
Most day-to-day work happens in the terminal, but VS Code covers everything that benefits from a real editor: the Remote - WSL extension connects straight into the Ubuntu filesystem, so it’s editing the same files the terminal tools see, not a separate Windows-side copy. The config is intentionally thin. A couple of keybinding tweaks so the terminal’s copy/paste behaves the way every other terminal on the machine does:
[
{
"key": "ctrl+shift+c",
"command": "workbench.action.terminal.copySelection",
"when": "terminalFocus && terminalTextSelected"
},
{
"key": "ctrl+shift+v",
"command": "workbench.action.terminal.paste",
"when": "terminalFocus"
}
]
and an Emacs keybinding extension for anyone (like me) whose fingers learned
C-x C-s before they learned Ctrl+S.
AI coding assistants
I run three different AI coding CLIs side by side, mostly because they’re each strongest in different places: Anthropic’s Claude Code for the bulk of day-to-day coding and agentic work (including most of the work behind this site and the legwork to gather configs and setup details for this post), OpenAI’s Codex CLI as a second opinion, and Google’s Antigravity for an occasional alternative pass.
The other half: notes in Obsidian
Everything above is the software side. There’s an equally load-bearing second brain sitting next to it: a git-tracked Obsidian vault holding research notes, project logs, and daily journal entries, which is where most of this post actually started as a scratch note before it became a blog post.
Why bother writing this down
Partly so I have somewhere to point collaborators and students who ask “wait,
how do you have your terminal set up,” and partly because dotfiles rot if
nobody explains the reasoning behind them, only the syntax. A future me
re-reading .tmux.conf in two years will at least know why the prefix key
is remapped.