Project-based tmux workspaces with a two-line session bar and hotkey switching.
Drop a .dev.yml in your project root, run dev, and get a tmux session with a shell window and a services window running your background processes. Switch between project sessions with Ctrl+Cmd+1-9.
- tmux 3.1+ (uses
user-keysandstatus-format) - wezterm (for
Ctrl+Cmdhotkey bindings) - yq (for parsing
.dev.yml) - Catppuccin tmux (session bar uses Mocha palette)
- A Nerd Font (for powerline separators in the session bar)
git clone https://github.com/jimrubenstein/mux.git
cd mux
sh install.shThis copies files to ~/.local/bin/dev and ~/.local/share/mux/. The installer prints the tmux and wezterm config snippets you need to add.
Make sure ~/.local/bin is in your PATH.
Add a .dev.yml to your project root:
name: my-project
services:
- name: server
cmd: npm run dev
- name: worker
cmd: npm run workerSee template.dev.yml for a full example.
cd ~/projects/my-project
devThis creates a tmux session named my-project with:
- Window 1 (
shell): a plain shell at the project root - Window 10 (
services): one pane per service, stacked vertically
If the session already exists, dev switches to it.
You can also point it at a directory:
dev ~/projects/my-projectOr launch without a .dev.yml — it creates a plain session named after the directory:
cd ~/scratch
dev
# creates session "scratch" with a shell windowdev stop # stops the session for the current directory
dev stop my-project # stops a session by namePress Ctrl+Cmd+1 through Ctrl+Cmd+9 to jump to sessions by their position in the session bar (sorted by creation time, oldest first).
Cmd+1 through Cmd+9 still switch tmux windows within the current session (default behavior).
The top status line shows clickable session pills:
- Active session: solid peach pill
- Inactive sessions: blue icon with dark name
Sessions are ordered by creation time (oldest left, newest right). The number on each pill matches the Ctrl+Cmd+N hotkey.
Wezterm sends a custom escape sequence (\e[30001~ through \e[30009~) via SendString. tmux maps these to user-keys bound in the root key table (no prefix needed), which run switch-session.sh to switch by index.
This avoids all the usual problems with Cmd+Shift+digit (macOS intercepts it) and modifier encoding across terminal/tmux boundaries.
Services run in window 10 (not window 2) so they stay pinned at the end of the window list. renumber-windows off keeps it there. Access it with Cmd+0 (which maps to prefix+0 → select-window -t :10).
Add these to your ~/.tmux.conf — the user-keys and bindings go before TPM, the setup-status-bar.sh call goes after:
set -g renumber-windows off
set -g status 2
# Session switching via custom escape sequences
set -s user-keys[0] "\e[30001~"
set -s user-keys[1] "\e[30002~"
set -s user-keys[2] "\e[30003~"
set -s user-keys[3] "\e[30004~"
set -s user-keys[4] "\e[30005~"
set -s user-keys[5] "\e[30006~"
set -s user-keys[6] "\e[30007~"
set -s user-keys[7] "\e[30008~"
set -s user-keys[8] "\e[30009~"
bind-key -n User0 run-shell '~/.local/share/mux/tmux/switch-session.sh 1'
bind-key -n User1 run-shell '~/.local/share/mux/tmux/switch-session.sh 2'
bind-key -n User2 run-shell '~/.local/share/mux/tmux/switch-session.sh 3'
bind-key -n User3 run-shell '~/.local/share/mux/tmux/switch-session.sh 4'
bind-key -n User4 run-shell '~/.local/share/mux/tmux/switch-session.sh 5'
bind-key -n User5 run-shell '~/.local/share/mux/tmux/switch-session.sh 6'
bind-key -n User6 run-shell '~/.local/share/mux/tmux/switch-session.sh 7'
bind-key -n User7 run-shell '~/.local/share/mux/tmux/switch-session.sh 8'
bind-key -n User8 run-shell '~/.local/share/mux/tmux/switch-session.sh 9'
# ... TPM plugins and initialization ...
run '~/.tmux/plugins/tpm/tpm'
# Two-line status bar (must come after TPM)
run-shell '~/.local/share/mux/tmux/setup-status-bar.sh'Add these to your keys table in wezterm.lua:
-- Ctrl+Cmd+1..9 → tmux session switching
{ key = "1", mods = "CTRL|CMD", action = wezterm.action.SendString("\x1b[30001~") },
{ key = "2", mods = "CTRL|CMD", action = wezterm.action.SendString("\x1b[30002~") },
{ key = "3", mods = "CTRL|CMD", action = wezterm.action.SendString("\x1b[30003~") },
{ key = "4", mods = "CTRL|CMD", action = wezterm.action.SendString("\x1b[30004~") },
{ key = "5", mods = "CTRL|CMD", action = wezterm.action.SendString("\x1b[30005~") },
{ key = "6", mods = "CTRL|CMD", action = wezterm.action.SendString("\x1b[30006~") },
{ key = "7", mods = "CTRL|CMD", action = wezterm.action.SendString("\x1b[30007~") },
{ key = "8", mods = "CTRL|CMD", action = wezterm.action.SendString("\x1b[30008~") },
{ key = "9", mods = "CTRL|CMD", action = wezterm.action.SendString("\x1b[30009~") },MIT