Skip to main content

optmux

optmux logo

Optimal, opinionated, batteries-included TMUX that's neat and easy for any project.

A tmuxp wrapper that creates per-project tmux config directories with TPM and plugins pre-configured.

Quick Start

# run optmux anywhere (installs on first use via uv)
uvx optmux

# strongly recommended: install wtcode + lazygit for the full experience
brew install netj/tap/optmux

Try the included example:

git clone https://github.com/netj/optmux.git && cd optmux
./example.optmux.yaml

On first run, optmux will:

  1. Create .example.optmux.d/tmux/ next to the YAML file
  2. Seed a default tmux.conf with TPM and plugins
  3. Install TPM and all plugins (visible in window 0)
  4. Launch tmuxp with an isolated tmux server

Usage

With a tmuxp YAML file

Supports .optmux.yaml, .tmuxp.yaml, and .optmuxp.yaml extensions:

optmux myproject.optmux.yaml
optmux myproject.tmuxp.yaml

Without arguments

optmux

Opens plain tmux using .optmux.d/ in the current directory — useful for a quick, isolated tmux session with the bundled config.

As a shebang

Write a tmuxp YAML config with the optmux shebang line and make it executable:

#!/usr/bin/env -S uvx optmux
session_name: myproject
windows:
  - window_name: editor
    panes:
      - vim .
  - window_name: shell
    panes:
      - ""
chmod +x myproject.optmux.yaml
./myproject.optmux.yaml

Example tmuxp YAML

Here's the included example.optmux.yaml showing shortcuts, tmux config, and window layout:

#!/usr/bin/env -S uvx optmux
session_name: example
start_directory: .

optmux:
  shortcuts:
    C-M-b: gh browse .
    C-M-e:
      command: ${VISUAL:-${EDITOR:-vim}} README.md  # exec directly (default for str, no latency)
      window: true                                  # in a new-window
    E:
      send-keys: ${VISUAL:-${EDITOR:-vim}} .        # send-keys (given command is run in a new shell)
      zoom: false                                   # do not zoom (defaults to zoom when split-window)
  tmux_config:
    project-settings: |
      set -g status-style bg=blue

windows:
  - window_name: editor
    panes:
      - vim .
  - window_name: shell
    panes:
      - ""
  - window_name: logs
    panes:
      - tail -f /var/log/system.log

Config directory

Each project gets its own .$NAME.optmux.d/ directory:

Path Purpose
tmux/tmux.conf Main tmux config (editable after creation)
tmux/tmux.*.conf Additional config files you can add
tmux/tmux.sock Tmux server socket (isolates this project)
tmux/plugins/ TPM plugin directory
tmux/plugins-update.sh Run manually to update all plugins

optmux YAML config

Add an optmux: section to your tmuxp YAML to configure shortcuts and tmux settings:

optmux:
  shortcuts:
    C-M-b: gh browse .                              # Ctrl-Alt-b: run command directly
    C-M-e:
      command: ${VISUAL:-${EDITOR:-vim}} README.md  # exec directly (no shell)
      window: true                                  # open in a new-window
    E:
      send-keys: ${VISUAL:-${EDITOR:-vim}} .        # send-keys (runs in a new shell)
      zoom: false                                   # do not zoom (default: true for splits)
  tmux_config:
    project-settings: |
      set -g status-style bg=blue

Shortcuts

Shortcuts bind tmux keys to commands:

  • C-M-* keys are bound globally (no prefix needed)
  • Other keys require the tmux prefix (C-t)
  • command: executes directly (default for string values)
  • send-keys: sends the command to a new shell (supports shell expansion)
  • new_window: true opens in a new window instead of a split
  • float_window: true opens in a floating pane (tmux 3.7+) — see detached shortcuts below
  • zoom: false disables auto-zoom on splits (default: true)
  • detached: true runs it without stealing focus, and holds it open on failure (see remain)

Detached shortcuts

detached: true runs a command without moving your cursor — handy for things like open -R .git or gh browse . that do their real work elsewhere. It comes in three flavors:

where it runs when it finishes
detached: true a split pane in the current window drops the window's zoom (see below)
detached + float_window: true a floating pane over the current window disappears, layout and zoom untouched
detached + new_window: true a background window nothing on screen changes

remain controls what happens on exit: on-error (the default) closes silently on success and holds the pane open with a dismiss prompt on failure, false always closes, true always holds. Whenever a detached shortcut holds itself open it rings the bell, so tmux flags the window in the status line rather than leaving you to find it.

Known issue: a quick detached split loses your zoom. tmux unzooms a window whenever any pane in it dies, so a detached split pane drops the origin pane's zoom the moment the command finishes. The binding does re-zoom when it opens the split, and that holds for as long as the command runs — but nothing survives to re-apply it afterwards (a pane-exited hook fires before tmux fixes the layout, so it cannot help). Long-running commands are unaffected in practice; short ones like open -R .git unzoom you.

Workarounds, in order of preference:

  1. float_window: true — a floating pane sits outside the layout, so opening and closing it never touches the zoom. Best fit for quick commands, and it works whatever state the window is in: because tmux 3.7b crashes if a float is created while the window is zoomed, the binding checks at press time and falls back to a background window in exactly that case, which leaves the zoom alone too. Requires tmux 3.7+; see TROUBLESHOOTING-tmux-floating-panes.md for the details.
  2. new_window: true — always safe, at the cost of the command living in a separate window you may be surprised to find later.
  3. remain: true — the pane never dies, so the zoom never drops; you dismiss it by hand. Note that each invocation leaves a pane behind, and once the window is full the split fails and the zoom is lost anyway.

tmux_config

Entries under tmux_config: are written as tmux.optmux-extras.{name}.conf files and auto-sourced by tmux.

Personal config (~/.optmux.yaml)

Create ~/.optmux.yaml to define personal defaults that apply to all optmux sessions:

optmux:
  shortcuts:
    C-M-g: lazygit
  tmux_config:
    my-defaults: |
      set -g status-style bg=green

Personal config is merged with per-project config. When both define the same key, personal settings take precedence.

Customization

  • Edit tmux/tmux.conf to change tmux settings
  • Drop tmux/tmux.mysetup.conf files for additional config (auto-sourced)
  • Run tmux/plugins-update.sh from inside tmux to update plugins
  • Press prefix + R to reload the config

Environment variables

optmux sets these before launching tmux/tmuxp:

Variable Value
OPTMUX_DIR Absolute path to the .$NAME.optmux.d/ directory
OPTMUX_NAME Name derived from YAML filename or cwd (e.g., myproject)
TMUX_PLUGIN_MANAGER_PATH $OPTMUX_DIR/tmux/plugins

Clipboard integration

optmux ships tmux-yank and sets set-clipboard on + allow-passthrough on, so yanking in copy-mode lands on your system clipboard. Locally that goes through pbcopy/xclip. Over SSH, OSC 52 is the only mechanism — the remote tmux emits an escape sequence and your local terminal writes it to the clipboard, with no relay, agent, or daemon in between.

macOS Terminal.app has no OSC 52 support

Local copying still works, but copying from a remote optmux session over SSH will not reach your pasteboard — Terminal.app discards the escape sequence, and no tmux configuration can change that. optmux deliberately doesn't ship a clipboard relay to work around it; that means a socket on your laptop accepting arbitrary bytes into your pasteboard, to replace what modern terminals already do natively.

Workaround: turn off View → Allow Mouse Reporting (⌘R), or hold Fn, to hand the mouse back to Terminal and drag-select with its own selection, then ⌘C. ⌘R again returns the mouse to tmux. You get only what's on screen, and the selection cuts across split panes — zoom first with prefix + z.

Better: use a terminal that supports OSC 52 — Ghostty, iTerm2, Warp, kitty, WezTerm, or Alacritty. The tips screen (window 0, or C-M-h) nags about this unless it can confirm your terminal, using the attached client's TERM_PROGRAM and terminal type — over SSH only the latter survives, so xterm-ghostty and friends are still recognized on remote hosts.

Development

# install the latest main branch
uvx git+https://github.com/netj/optmux.git

# local editable install for development
uv tool install -e .

# test any local changes directly (best for testing branches)
uv run optmux ./example.optmux.yaml

# run tests
uv run pytest                  # all tests
uv run pytest -m "not e2e"     # skip E2E tests (no tmux needed)
uv run pytest -m e2e           # E2E only (requires tmux)

License

MIT

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

optmux-0.8.0.tar.gz (18.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

optmux-0.8.0-py3-none-any.whl (20.7 kB view details)

Uploaded Python 3

File details

Details for the file optmux-0.8.0.tar.gz.

File metadata

  • Download URL: optmux-0.8.0.tar.gz
  • Upload date:
  • Size: 18.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.32 {"installer":{"name":"uv","version":"0.11.32","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for optmux-0.8.0.tar.gz
Algorithm Hash digest
SHA256 592132634c42d61ec50b0ee0ed48b7c1f189c54b0e9e4500b61cf8557d0b9f1a
MD5 7d00519eb769ed254d34c2f643b5793c
BLAKE2b-256 ff5cdfdcc4b582cb7465527437ca8a24d0d36a24fa7261a34a2b509eea8c1003

See more details on using hashes here.

File details

Details for the file optmux-0.8.0-py3-none-any.whl.

File metadata

  • Download URL: optmux-0.8.0-py3-none-any.whl
  • Upload date:
  • Size: 20.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.32 {"installer":{"name":"uv","version":"0.11.32","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for optmux-0.8.0-py3-none-any.whl
Algorithm Hash digest
SHA256 f78a45f811727771d8ee2b5d928d6d17be57bab5bfd7ad82f7dd5bb9ea4a5e9f
MD5 f05bfd8803fcd042f07eff0139a3c99f
BLAKE2b-256 27c52013b8054fc6a79ceaf14e93b3ef85a3df3d91df4359bb50abefb8a6a21a

See more details on using hashes here.

Release history Release notifications | RSS feed

0.9.0

2 files

This release

0.8.0 This release

2 files

0.7.0

2 files

0.6.1

2 files

0.6.0

2 files

0.5.0

2 files

0.4.0

2 files

0.3.0

2 files

0.2.1

2 files

0.2.0

2 files

0.1.2

2 files

0.1.1

2 files

0.1.0

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page