One Ghostty window → one tmux session → a ragged grid where each row is a repo.
Single-Cmd chords jump-and-zoom to any terminal; Cmd+0 shows the whole overview.
The model · Install · Usage · Profiles · Status bar
OVERVIEW (Cmd+0) ──► TERMINAL N (Cmd+1 … Cmd+9, jumps AND zooms)
the full ragged grid one pane filling the screen
[ 1 · web ] | [ 2 · web ] row 1 (repo: web)
[ 3 · app ] | [ 4 · app ] | [ 5 · app ] row 2 (repo: app)
[ 6 · api ] | [ 7 · api ] row 3 (repo: api)
Numbers are positional (reading order, top→bottom then left→right) and reflow whenever you add/remove panes — exactly the dynamic behavior you wanted.
Note on
Cmd+N: the numbers you see are reading-order positions, which are NOT the same as tmux's internalpane_indexafter arbitrary splits. SoCmd+Nis routed throughtmux-gotopane, which always resolves N to the pane whose visible title isN · repo. (This is why the binds arerun-shell 'tmux-gotopane N'rather thanselect-pane -t N.)
- macOS — the
Cmd/Optkeybinds are Mac-specific. - Ghostty — the terminal the keybinds drive.
- tmux —
install.shinstalls it via Homebrew if it's missing. - zsh — pane commands run in your interactive shell, and
install.shadds~/binto~/.zshrc.
The quickest path is the bundled installer:
./install.shIt installs tmux (via Homebrew if missing), copies the scripts to ~/bin and
ensures it's on PATH, installs ~/.tmux.conf, and appends the Ghostty
keybinds to your active Ghostty config (detecting config vs config.ghostty).
-
Scripts — put
mygrid,tmux-renumber, andtmux-gotopaneon yourPATH:mkdir -p ~/bin cp bin/mygrid bin/tmux-renumber bin/tmux-gotopane ~/bin/ chmod +x ~/bin/mygrid ~/bin/tmux-renumber ~/bin/tmux-gotopane # ensure ~/bin is on PATH (add to ~/.zshrc if needed): echo 'export PATH="$HOME/bin:$PATH"' >> ~/.zshrc
-
tmux:
brew install tmux cp tmux.conf ~/.tmux.conf -
Ghostty — append the keybinds to your active config, then fully quit and reopen Ghostty:
cat ghostty-config.txt >> "$HOME/Library/Application Support/com.mitchellh.ghostty/config"
install.sh is idempotent and backs up anything it replaces. It touches only:
~/bin/— copies inmygridand thetmux-*helper scripts.~/.zshrc— appends aPATHline for~/bin(only if missing).~/.tmux.conf— installs this repo's config (your old one is saved to~/.tmux.conf.bak).~/.config/mygrid/profiles/— seedsexample.confon a fresh setup.- Your Ghostty
config— appends the keybinds between# >>> mygrid keybinds >>>markers.
To uninstall: delete ~/bin/mygrid and the ~/bin/tmux-* scripts, remove the marked
keybind block from your Ghostty config, restore ~/.tmux.conf.bak (or remove
~/.tmux.conf), and drop the ~/bin PATH line from ~/.zshrc. Nothing is installed
system-wide.
Build a layout (each token is a row; count[:path]):
mygrid 2,3,2
mygrid 2:~/code/web,3:~/code/app,2:~/code/apiOr use named profiles (see below) — one row per profile:
mygrid web apiThen, with a single Cmd chord:
| Key | Action |
|---|---|
| Cmd+1..9 | jump to terminal N and zoom it fullscreen |
| Cmd+0 | overview — un-zoom to the full ragged grid |
| Opt+1..9 | focus terminal N without zooming (overview) |
| Cmd+Shift+A | add a pane to the current row, then renumber |
| Cmd+Shift+R | add a new row (new repo), then renumber |
| Cmd+W | close current pane (no confirm), then renumber |
| Cmd+Shift+X | close current pane (confirm), then renumber |
| Cmd+Shift+N | force a renumber/relabel pass |
| Cmd+Shift+= | rebalance row heights |
| *Cmd+Shift+* | rebalance column widths within each row |
(The same actions exist under the tmux prefix Ctrl-A if you ever want them
manually: Ctrl-A then a, r, x, n, =, |, or a digit.)
After adding/removing rows you may want Cmd+Shift+= to even the row heights.
A profile is a named row definition: a directory plus the panes (and their
startup commands) for one row. mygrid <name> [name...] builds one row per
profile, in order.
Profiles live in ~/.config/mygrid/profiles/<name>.conf (override the location
with $MYGRID_PROFILES). Format:
# ~/.config/mygrid/profiles/web.conf
dir = ~/dev/web-client # directory every pane in this row opens in
pane = yarn dev:prod # left pane — runs on startup
pane = claude # middle pane
pane = gl # right pane# ~/.config/mygrid/profiles/api.conf
dir = ~/dev/api-server
count = 3 # 3 plain shells, no startup commands- Each
pane = <command>adds one pane (left→right). The command runs in your normal interactive shell (viatmux send-keys), so~/.zshrcaliases and functions likeglwork, and the pane stays open as a shell after the command finishes.pane =(empty) is a plain shell. count = Nis shorthand for N plain shells; it's used only when there are nopane =lines.- The profile name is independent of the directory, so
web→~/dev/web-client. mygridwith no args (or an unknown name) lists the profiles you have.- Mix freely:
mygrid web apiis two rows; add more names for more rows.
profiles/example.conf in this repo is a documented template; install.sh
seeds it into ~/.config/mygrid/profiles/ on a fresh setup.
The bottom line shows, on the left:
web │ 1 [2] 3 │ ⊞ OVERVIEW
web— the profile of the row you're focused on (only that row, never the whole spec). Ad-hoc rows show the row's directory name instead.1 [2] 3— that row's panes by their global reading-order numbers; the focused one is bracketed and highlighted in the accent color.⊞ OVERVIEW/⛶ ZOOMED— whether you're seeing the full grid or a single zoomed pane (ZOOMED is accent/bold as a reminder that other panes exist off-screen).
It updates instantly on focus changes via a pane-focus-in hook running
tmux-statusmap (no polling). Pane border titles use the same one-name rule:
2 · web, not 2 · web-client · web. The default window list is hidden —
mygrid keeps everything in one window, so it never said anything.
The repo ships neutral colors only — pulling it never rethemes your setup.
Personalize in ~/.tmux.conf.local, sourced at the very end of tmux.conf
and never written by install.sh, so it survives every pull/reinstall:
# ~/.tmux.conf.local — example matched to Ghostty's "Django Reborn Again"
set -g @grid_accent_bg "#ffcc00" # accent: focused-pane border,
set -g @grid_accent_fg "#051f14" # map highlight, ZOOMED badge
set -g status-style "bg=#245032,fg=#dadedc" # status bar background/textThe @grid_accent_* pair is one knob for everything accent-colored; any other
tmux option (border styles, the bar, whatever) can be overridden there too.
monitor-activity and monitor-bell are on, so a pane that prints output while
unfocused (a Claude permission prompt, a failed command) gets flagged in the
status line — your cue for which terminal to check, even before you open the
overview.
- Ragged widths: each row divides its own width among its panes, so a 2-pane
row is wider per-pane than a 3-pane row. tmux's automatic layouts can't do this
on their own, which is why
mygridbuilds rows manually. Cmd+Shift+Asplits the active pane, so the new pane takes its width from that pane — the row may look slightly uneven. It stays in the same row.- Clicking links:
mouse on(below) makes tmux capture the mouse, so a plain Cmd+click no longer reaches Ghostty's link handler. Hold Shift while clicking (Shift+click or Cmd+Shift+click) to bypass tmux's mouse grab and open the link — Ghostty'smouse-shift-capture = falsedefault makes this work. (Turn offset -g mouse onin tmux.conf if you'd rather have plain Cmd+click back at the cost of scroll / click-to-focus / drag-resize.) - Mouse:
mouse ongives you trackpad scrollback, click-to-focus a pane, and drag-to-resize borders. - Persistence: the tmux session survives closing Ghostty (quitting Ghostty
does not end the tmux server). Reopen and run
mygrid(no args) ormygrid <same spec>to re-attach, ortmux attach -t dev. To switch to a different layout you must end the old session first:tmux kill-session -t dev && mygrid <new spec>. Runningmygrid <different spec>while a session exists refuses (rather than silently attaching to the old layout) and tells you this — mygrid records the spec that built the session in the@mygrid_specoption to detect the mismatch. - Change the session name with
MYGRID_SESSION=foo mygrid 2,2.
