hausfold

haus

Development

The terminal, the shell, the multiplexer and the editor, plus Git tooling, the CLI toolbelt and language runtimes. The command line haus sets up, and the keys that drive it.

hearth is the terminal room. It sets up the whole command line (emulator, multiplexer, shell, prompt, file manager, editor, and a toolbelt of modern replacements for the usual commands), painted from the same palette, down to the default editor.

TerminalGhosttytitle bar hidden, opens straight into the multiplexer
Multiplexerzellijcustom tab bar, three swap layouts
Shellzshno framework, fzf-tab, sensible history, git aliases
Promptstarship
Filesyazimarkdown, code and image previews
Editorhelixthe default $EDITOR, or neovim, vim, nano (below)
Jumperzoxidecd learns your habits; cdi opens a picker

Plus bat over cat, lsd over ls, lazygit (lg), delta for git diffs, and fzf, fd, jq, gh, glow, fastfetch, with nix-index and comma so you can run anything else in nixpkgs without installing it.

Enable it

haus.developer.enable = true;
haus.developer.languages = [ "node" ];

The terminal stack itself (Ghostty, zellij, zsh, the prompt, the editor) is part of the foundation and always there. haus.developer.* is what brings the tools: the CLI toolbelt, the Git tooling and the language runtimes, each with a sub-switch of its own (.toolbelt, .git, .languages).

Ghostty opens into zellij

Launching the terminal drops you into a persistent session called main; new panes and tabs inherit your working directory. Ghostty's own ⌘T, ⌘P, ⌘Y, ⌘W and friends are unbound on purpose, so those keys mean the same thing whether or not you're multiplexed. (⌘C is not one of them; it stayed with Ghostty, as copy. Day to day you won't press it, since zellij copies a mouse selection the moment you release it, but a -drag makes a Ghostty-level selection and ⌘C takes it.)

Two of the unbindings are fixes rather than hand-offs:

  • ⌘W. Ghostty's default there closes the whole surface, and with close confirmation off a stray ⌘W silently took the window and the session in it. Now it closes one pane; ⌘⇧W still closes the window.
  • ⌘D / ⌘⇧D do nothing, on purpose. Their Ghostty defaults nest a second terminal inside the one zellij is driving: invisible to the multiplexer, so its layout and pane keys would apply to half the window. Use ⌘P.
KeysWhat happens
⌘P / ⌘⇧PNew pane. The plain one hops to the repo's main checkout when you're inside an agent worktree; stays put.
⌘T / ⌘⇧TNew tab at $HOME / at the focused pane's directory. Fresh tabs are born named ~, never Tab #N.
⌘WClose the focused pane (and the tab with it, when it was the last one)
⌘F / ⌘⇧FFind in this pane / across every pane
⌘GYour GitHub review queue, if you turned it on
⌘⏎Fullscreen the focused pane, or Ctrl-click the pane body
⌘Y / ⌘⇧Yyazi peek, with and without the worktree hop
⌘LLinks picker: every URL in the pane's transcript or scrollback
⌘A / ⌘⇧ASpawn a coding agent in its own worktree, beside the focused pane or in place of it
⌃⌥⇧ASpawn one in this checkout instead of its own worktree
Ctrl TabWalk tabs in most-recently-used order, browser-style
Alt [ / Alt ]Cycle layouts: grid → spiral → columns

An agent worktree is the throwaway checkout ⌘A puts a coding agent in: its own copy of the repo on its own branch, so several agents can work at once without fighting over one set of files. Half the rows above have a variant because of it: the plain key assumes you want the real repo, and says stay in the copy. The AI room has the rest of the lifecycle: resuming one, setting work aside, cleaning up.

Where that agent's terminal lives is this room's call, though, and haus.hearth.lanes.backend is the switch. "zellij", the default, is what the rows above describe: the lane is a pane in the main session. "zmx" makes it its own zmx session in its own Ghostty window, tiled by prowl. The session outlives every client attached to it, so ⌘W detaches and the agent keeps thinking, and reopening the lane rejoins the live conversation instead of replaying a transcript. The trade is splits: zmx has none by design, so prowl tiles real windows rather than a second window manager nested inside a terminal.

zellij boots Locked. Its single-key submode leaders stay inert until you press Ctrl g, so a stray keystroke can't drop you into resize mode. Everything in the table above works regardless. haus.hearth.zellijStartLocked flips it.

There is no reload chord, because there is nothing to reload: zellij watches its own config and applies a change to the running server within a second. A haus rebuild lands on the session you already have: same tabs, same panes, same directories, live agent conversations uninterrupted.

Click a path

A plain click on a file path opens it in your editor, at its line number (src/main.rs:42 lands on line 42), and a click on a directory opens a new tab already cd'd there. Handy for whatever a build tool just printed. Links work in every pane, including ones running a TUI that has taken the mouse; paths work in any pane no program is tracking the mouse in.

Finding things

⌘F opens a search overlay for the focused pane, ⌘⇧F the same overlay across every pane. It covers the window but is still a floating pane, so Esc puts the tiled layout straight back. Results appear as you type, with the lines around each hit beside them. jumps to the pane a hit came from, ^y copies the line, ^s flips between this-pane and every-pane without losing your query.

In an agent pane it searches the conversation, not the terminal. Claude Code and Opencode panes render in the alt-screen, which has no scrollback at all, so searching the grid would only ever find what's on screen this second. Searching the stored conversation finds all of it, including text inside collapsed tool output. Codex panes fall back to scrollback; they report their pane state like the others but carry no conversation id to join to. Every other pane gets its full scrollback.

zellij's own in-place search is still there, for when you want matches highlighted in the real pane and n/p to walk them: Ctrl g to unlock, then s. Its exits return you to Locked rather than Normal.

Your review queue

⌘G opens gh-dash as a borderless overlay, themed like everything else and gone again on q. It's off unless you ask:

haus.hearth.ghDash.enable = true;
haus.git.org = "your-org";     # or your own account

The first line gets you the tabs that are about you: issues you opened, ones assigned to you, unread notifications, threads you're in. The second is what a PR tab needs, because a PR tab is a GitHub search and a search needs a scope. Set it and four more appear: open, green and red (the same queue split by whether CI passed; together they are the merge decision, and a branch still building shows in neither), and shipped.

Leave haus.git.org empty (right if you read several owners at once) and those four simply aren't written.

yazi peek

⌘Y opens a floating file browser with live previews, sized to cover the terminal window that summoned it exactly, so it reads as that window turning into a file browser rather than a popup landing on it. Enter on a file pages it fullscreen; Enter on a directory opens a new zellij tab there, which makes peek double as a browse-and-pick tab chooser. q or Esc closes it and leaves your layout untouched.

Markdown renders through glow, code through bat, images through chafa. Y copies a file's contents, not its path.

Every floating terminal haus summons wears a thin outline that follows the window's corner curve, so a dark terminal on a dark window still has an edge:

haus.hearth.floatBorder = "accent";  # the default; also "grey", any accent name, or "off"

Git aliases

Plain zsh, no framework, and a finite set of shortcuts. The names follow the widely-used Oh-My-Zsh vocabulary where conventions agree, and deliberately skip the ambiguous ones (gl, gr, gs) whose meanings differ between alias sets. g itself is git.

Addga gaa gapa
Branch / checkoutgb gba gbd gbD gbm gco gcb gcl gsw gswc
Commitgc gca gcam gcmsg gcn gcp gcpa gcpc
Diff / loggd gds gdw glo glog gloga
Fetch / mergegf gfa gfo gm gma gmc gmff
Pull / pushgpl gpr gp gpf gpsup
Rebasegrb grba grbc grbi grbs grt grv
Status / stashgst gss gsb gsta gstl gstp gsts
Tag / worktreegt gwt gwta gwtl gwtr

Add, replace or remove one per host:

haus.git.shellAliases = {
  gsync = "git pull --rebase --autostash";   # add
  gst = "git status --short --branch";       # replace
  gco = null;                                # remove
};

Choosing your editor

Name one and the room installs it. haus.hearth.editorName takes helix (the default), neovim, vim or nano: one choice that installs the editor, sets your $EDITOR, and points every "open in an editor" action at it. Those open in a new zellij tab, which is why the list is terminal editors.

haus.hearth.editorName = "neovim";              # installed, and $EDITOR
haus.hearth.hijackFileAssociations = true;      # off by default: make it the
                                                # opener for .json, .md, .nix, …

helix is the one haus paints from the palette: Nebelung has a theme for it and none for the others, so the alternatives arrive in their own default colours.

To point haus at an editor it does not install (a GUI one, or something your host installs itself), set haus.hearth.editor instead. That one is a shell command haus runs, so it belongs in your host file rather than in a shared desktop, and it wins over whatever editorName chose:

haus.hearth.editor = "code -w";                 # points at it; installs nothing

Smaller things you'll notice

  • Tabs rename themselves. cd into a repo and the tab takes its name; cd ~ puts it back to ~.
  • Agent-worktree aware. A new shell opened inside an agent worktree hops to the parent repo, and the tab is named after the real repo rather than the throwaway checkout. ⌘⇧P is the no-hop escape hatch.
  • Ctrl Tab really works, which it usually doesn't on macOS: Ghostty forwards the kitty-protocol encoding so zellij sees it.

Works with

  • AI: with both rooms on, agent panes get their zellij bindings and the worktree lifecycle around them.
  • Appearance: the whole stack is painted from the same palette.
  • Launcher: commands that open a terminal land in the same multiplexer you already live in.

Remove it

haus.developer.enable = false;

The toolchains, the Git tooling and the toolbelt go; the terminal, shell and editor stay, because they are the floor rather than the room. Take out one piece at a time with haus.developer.git.enable, .toolbelt.enable or an empty .languages.

Options

Every setting, with types and defaults: Development.

On this page