tmux UI wrapper — typeahead-first workspace manager.
# Build the bundle and install the wrapper script to ~/.local/bin/cmux
bun run install
# Make sure ~/.local/bin is in your PATH
export PATH="$HOME/.local/bin:$PATH"bun run install builds dist/cmux.js first, then runs it with --install to write the wrapper. Do not run bun src/main.ts --install — the wrapper it installs would point back at the source file.
cmux uses a shell replacement trick for zero-overhead startup:
- Wrapper script (
~/.local/bin/cmux):eval "$(bun src/main.ts)" - Outside tmux: cmux outputs
exec tmux new-session ...— the wrapper evals this, replacing the shell process with tmux directly. No intermediate process remains. The tmux session gets an Alt-Space binding for the popup. - Inside tmux (Alt-Space):
display-popup -w 80% -h 80% -E 'bun src/main.ts'runs the UI directly in a popup. When cmux exits, the popup closes.
The exec pattern means environment variables (API keys, etc.) don't leak to child processes — the original shell is replaced entirely.
- Typeahead picker — unified fuzzy finder for repos, screens, directories, and commands. Opens on startup. Items sorted by selection frequency.
- Window carousel — horizontal strip showing all tmux windows with repo/branch names. Navigate with h/l, Tab to switch focus.
- Layout picker — cycle through 2-4 pane layouts with slide animation. Enter from carousel to apply.
- Quick shell — run a single command, output copied to clipboard automatically. Full zsh completion and aliases.
- Worktree creation — select a repo, type a branch name, get a new worktree branched from origin/main.
- Frequency tracking — picker items and layout transitions are ranked by how often you use them.
- Smart deduplication — repos that match open screens are shown once; directories that are repos appear only as repos.
CMUX_DEBUG=1- Enables debug logging to/tmp/cmux.logCMUX_BENCHMARK=1- Headless mode for benchmarking (exits after init)
Single binary (src/main.ts) using raw ANSI for fast startup (~16ms).
src/main.ts- Main UI, key handling, focus management, tmux integrationsrc/db.ts- Shared SQLite database (single file, no WAL)src/typeahead.ts- Generic typeahead component with fuzzy filteringsrc/resumable-filter.ts- Resumable BFS directory scan used by the repo pickersrc/repo-picker.ts- Top-level picker (repos, screens, directories, commands)src/branch-picker.ts- Branch/worktree picker for a specific reposrc/picker-store.ts- Selection frequency trackingsrc/layout-store.ts- Layout transition trackingsrc/layouts.ts- Fixed layout templates (1-4 panes),ALL_LAYOUTSflat listsrc/layout-preview.ts- ASCII box-drawing preview renderersrc/tmux.ts- tmux helpers (getWindows,getWindowInfo,STARTUP_COMMAND)src/tmux-layout.ts- Generates tmux layout strings with checksumsrc/pane-matcher.ts- Position-based pane matching for layout transitionssrc/swap-orchestrator.ts- Computes and executes pane swap sequencessrc/repo-store.ts- Known git repo trackingsrc/git-utils.ts-isGitRepo,resolveRepoPath,getBranch,getLastActivitysrc/worktree-utils.ts-deleteWorktreewith force fallbacksrc/window-naming.ts- Window naming from git/directory contextsrc/logger.ts- Debug log to/tmp/cmux.loggated onCMUX_DEBUGsrc/utils.ts- Shared utilities (name truncation, sanitization)src/box-chars.ts- Box-drawing character constants
┌──────────────────────────────────────────────────┐
│ ┌─────────────────¹┐ ┌─────────────────²┐ │
│ │ repo-name │ │ another-repo │ │ <- Carousel (always visible)
│ │ branch-name ● │ │ feature-xyz │ │
│ └──────────────────┘ └──────────────────┘ │
└──────────────────────────────────────────────────┘
────────────────────────────────────────────────────
┌──────────────────────────────────────┐
│ > search... │ <- Middle panel:
│ ──────────────────────────────────── │ Typeahead (default)
│ ⚡ → shell │ OR Layout picker + form
│ 📦 cmux │
│ 📺 other-screen │
│ 📁 ~/code │
└──────────────────────────────────────┘
────────────────────────────────────────────────────
type to filter jk nav ⏎ select tab carousel
Three focus states, navigable with Tab, up/down, and Escape:
Carousel (default on startup):
h/l- Move selection left/rightjorCtrl+N- Switch to typeaheadEnteron current window → layout picker; on another window → switch to it and exit1-9- Quick select window (same Enter semantics)-orx- Delete window (with confirmation)Alt+h/l- Reorder windowsTab/Escape- Switch to typeaheadq- Quit
Typeahead (Tab or down from carousel):
- Type to filter items
j/korCtrl+N/Ctrl+P- Navigate items (up at top → carousel)Enter- Select item (switch screen, drill into repo, open directory, run command)Tab- Switch to carouselEscape- Quit
Layout + Form (Enter on current window from carousel):
h/l- Cycle through layouts (with slide animation)j/k- Move between layout picker and rename fields (directory, repo, branch)Enter- Apply layout and exitEscape- Back to carousel
Panes are numbered largest-to-smallest by area. Layouts cover 1-4 panes (1 + 1 + 4 + 4 = 10 total).
Single shared SQLite database at ~/.cache/cmux/cmux.sqlite (no WAL, single-writer). Tables:
repos- Known git repositories with activity trackingpicker_frequency- Selection counts per item type, ordered by frequencytransitions- Layout transition counts for smarter ordering
bun run build # bundle to dist/cmux.js
bun run install # build + install wrapper to ~/.local/bin/cmuxAlways run bun run build after making code changes. The installed cmux command runs dist/cmux.js, not the source files, so any change is invisible until the bundle is rebuilt. Rebuild before handing off to the user for testing.
bun testFor UI changes, always verify interactively in a real tmux session:
bun run build
# Then trigger cmux via Alt-Space, or:
tmux new-session -d -s test-cmux
tmux send-keys -t test-cmux 'bun dist/cmux.js' Enter
tmux attach -t test-cmuxUnit tests don't catch all UI bugs. Before declaring a fix complete:
- Actually run the app
- Test the specific behavior that was changed
- Verify related functionality still works