Repository navigation
Adopt libtmux 0.56.0 wrappers, drop raw cmd() escape hatches #46
Description
Background
libtmux v0.56.0 (PR #653) lands ~50 new public methods covering tmux commands that previously required dropping down to Server.cmd(...). The release notes call this out explicitly:
Net result: callers no longer need to drop down to
Server.cmd(...)for the commands tmux ships out of the box.
libtmux-mcp is one of those callers. This issue tracks adopting the new wrappers across the 15 call sites that have direct replacements, plus the dependency floor bump that unlocks them.
Prerequisite
- Bump
libtmux>=0.55.1,<1.0→libtmux>=0.56.0,<1.0inpyproject.toml
Migration checklist
Each row pairs an MCP call site (left) with the libtmux 0.56.0 wrapper that replaces it (right, permalinked to the v0.56.0 tag).
Cluster 1 — Pane lifecycle / respawn
-
src/libtmux_mcp/tools/pane_tools/lifecycle.py:172—pane.cmd(\"respawn-pane\", *argv)→Pane.respawn. Deletes the stopgap comment block atlifecycle.py:153-162and the manualif result.stderr:branch (Pane.respawnraisesLibTmuxExceptiondirectly).
Cluster 2 — Copy mode / pipe / clear_history
-
src/libtmux_mcp/tools/pane_tools/copy_mode.py:60—pane.cmd(\"copy-mode\")→Pane.copy_mode -
src/libtmux_mcp/tools/pane_tools/copy_mode.py:62-68—pane.cmd(\"send-keys\", \"-X\", \"-N\", str(n), \"scroll-up\")→Pane.send_keys(copy_mode_cmd=\"scroll-up\", repeat=scroll_up, enter=False) -
src/libtmux_mcp/tools/pane_tools/copy_mode.py:113—pane.cmd(\"send-keys\", \"-X\", \"cancel\")→Pane.send_keys(copy_mode_cmd=\"cancel\", enter=False) -
src/libtmux_mcp/tools/pane_tools/pipe.py:78—pane.cmd(\"pipe-pane\")→Pane.pipe() -
src/libtmux_mcp/tools/pane_tools/pipe.py:86—pane.cmd(\"pipe-pane\", \"cat ...\")→Pane.pipe(\"cat ...\") -
src/libtmux_mcp/tools/pane_tools/io.py:241—pane.cmd(\"clear-history\")→Pane.clear_history(). Keep the explicit two-call structure (do NOT usePane.reset()— it's still broken upstream per libtmux #650); the line above (pane.cmd(\"send-keys\", \"-R\")) stays as-is.
Cluster 3 — Buffer I/O
-
src/libtmux_mcp/tools/buffer_tools.py:275—pane.cmd(\"paste-buffer\", \"-b\", n, ...)→Pane.paste_buffer(buffer_name=cname, bracket=bracket). Eliminates a duplicate-t pane_id(libtmux'sPane.cmdalready injects it). -
src/libtmux_mcp/tools/pane_tools/io.py:334—pane.cmd(\"paste-buffer\", ...)→Pane.paste_buffer(buffer_name=..., bracket=bracket, delete_after=True) -
src/libtmux_mcp/tools/pane_tools/io.py:342—server.cmd(\"delete-buffer\", \"-b\", buffer_name)→Server.delete_buffer(buffer_name=buffer_name) -
src/libtmux_mcp/server.py:264—server.cmd(\"delete-buffer\", \"-b\", name)→Server.delete_buffer(buffer_name=name)
Cluster 4 — Session window navigation
-
src/libtmux_mcp/tools/session_tools.py:313-329— thenext/previous/last-windowdispatch block →Session.next_window/Session.previous_window/Session.last_window. Each wrapper returns the new activeWindowdirectly and raises on stderr, removing theif proc.stderr:branch and the post-callsession.active_windowre-fetch.
Cluster 5 — Layout / select / swap / display-message
-
src/libtmux_mcp/tools/pane_tools/layout.py:74—window.cmd(\"display-message\", \"-p\", \"#{window_zoomed_flag}\")→window.refresh(); is_zoomed = window.window_zoomed_flag == \"1\"(no round trip; just read the ORM attribute). -
src/libtmux_mcp/tools/pane_tools/layout.py:174—server.cmd(\"select-pane\", target=target_pane.pane_id)→target_pane.select()(already a method onPane; no new wrapper needed, but the migration belongs in this cleanup). -
src/libtmux_mcp/tools/pane_tools/layout.py:221—server.cmd(\"swap-pane\", \"-s\", src, \"-t\", tgt)→Pane.swap(called assource.swap(target=target)after resolvingtargetto aPane). -
src/libtmux_mcp/tools/pane_tools/meta.py:66—pane.cmd(\"display-message\", \"-p\", format_string)→Pane.display_message(format_string, get_text=True) -
src/libtmux_mcp/tools/pane_tools/meta.py:162—pane.cmd(\"display-message\", \"-p\", fmt)→Pane.display_message(fmt, get_text=True)
Out of scope (no wrapper yet)
These sites have no clean wrapper in 0.56.0 and should remain .cmd(...):
src/libtmux_mcp/_utils.py:159—server.cmd(\"display-message\", \"-p\", \"#{socket_path}\").Serverhas nodisplay_messagewrapper (onlyPanedoes).src/libtmux_mcp/tools/server_tools.py:181, 251— same: server-scoped#{version}read.src/libtmux_mcp/tools/pane_tools/search.py:206—list-panes -f #{C/...}C-filter expression;server.panesdoesn't expose the-fshortcut.src/libtmux_mcp/server.py:256—list-buffers -F #{buffer_name}.Server.list_buffers()returns tmux's default formatted lines (name: 5 bytes: ...), not raw names; keep the-Fform until upstream adds aformat=kwarg.
Suggested execution
Per the project's per-phase commit workflow, each cluster above maps to one phase: implement, run the full verification chain (just ruff check, just ruff format, just mypy, just test, just build-docs), commit, then proceed to the next cluster. All five clusters land in one PR.
References
- libtmux v0.56.0 release notes: https://github.com/tmux-python/libtmux/releases/tag/v0.56.0
- libtmux PR #653: tmux parity: add interactive command coverage and align flag semantics with tmux libtmux#653
- libtmux issue #650 (why
Pane.reset()stays unused): mcp(pane_tools): clear_pane does not reliably clear visible content libtmux#650
Activity
py(deps[libtmux]): bump floor to 0.56.0 (#46)
Pane(refactor[respawn]): adopt Pane.respawn from libtmux 0.56 (#46)
Pane(refactor): adopt copy_mode / pipe / clear_history wrappers (#46)
Pane,Server(refactor[buffer]): adopt paste_buffer + delete_buffer (#46)
Session(refactor): adopt Session.{next,previous,last}_window (#46)
Pane(refactor): adopt Pane.swap + Pane.display_message + Pane.select (#…
py(deps[libtmux]): bump floor to 0.56.0 (#46)
Pane(refactor[respawn]): adopt Pane.respawn from libtmux 0.56 (#46)
Pane(refactor): adopt copy_mode / pipe / clear_history wrappers (#46)
Pane,Server(refactor[buffer]): adopt paste_buffer + delete_buffer (#46)
Session(refactor): adopt Session.{next,previous,last}_window (#46)
Pane(refactor): adopt Pane.swap + Pane.display_message + Pane.select (#…
py(deps[libtmux]): bump floor to 0.56.0 (#46)
Pane(refactor[respawn]): adopt Pane.respawn from libtmux 0.56 (#46)
Pane(refactor): adopt copy_mode / pipe / clear_history wrappers (#46)
Pane,Server(refactor[buffer]): adopt paste_buffer + delete_buffer (#46)
Session(refactor): adopt Session.{next,previous,last}_window (#46)
Pane(refactor): adopt Pane.swap + Pane.display_message + Pane.select (#…
py(deps[libtmux]): bump floor to 0.56.0 (#46)
Pane(refactor[respawn]): adopt Pane.respawn from libtmux 0.56 (#46)
Pane(refactor): adopt copy_mode / pipe / clear_history wrappers (#46)
Pane,Server(refactor[buffer]): adopt paste_buffer + delete_buffer (#46)
Session(refactor): adopt Session.{next,previous,last}_window (#46)
Pane(refactor): adopt Pane.swap + Pane.display_message + Pane.select (#…