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 main tmux session, or creates it if it does not exist, so opening a terminal takes me straight back to the same workspace.
  • A gui-startup hook 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:

background
#282a36
current line
#44475a
foreground
#f8f8f2
comment
#6272a4
cyan
#8be9fd
green
#50fa7b
orange
#ffb86c
pink
#ff79c6
purple
#bd93f9
red
#ff5555
yellow
#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.

Note Icons via simple-icons, whose SVG shapes are released under CC0. The trademarks and brand identities they represent still belong to Anthropic, OpenAI, and Google respectively.

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.

Note That vault, how it's organized, and how it fits together with the AI tools above is a big enough topic for its own post. More on that soon.

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.