chore(deps[libtmux]): adopt libtmux 0.56.0 wrappers, drop raw cmd() escape hatches#48
Open
tony wants to merge 6 commits into
Open
chore(deps[libtmux]): adopt libtmux 0.56.0 wrappers, drop raw cmd() escape hatches#48tony wants to merge 6 commits into
tony wants to merge 6 commits into
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #48 +/- ##
==========================================
- Coverage 84.91% 84.87% -0.05%
==========================================
Files 40 40
Lines 2294 2268 -26
Branches 294 286 -8
==========================================
- Hits 1948 1925 -23
+ Misses 261 260 -1
+ Partials 85 83 -2 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Member
Author
Code reviewNo issues found. Checked for bugs and AGENTS.md compliance. 🤖 Generated with Claude Code - If this code review was useful, please react with 👍. Otherwise, react with 👎. |
1e210dc to
53959f3
Compare
why: libtmux 0.56.0 ships over fifty new public methods that close
the long-tail gap between the tmux(1) surface and the Python
wrapper. The MCP currently drops down to raw Server.cmd(...) for
over a dozen commands that the new release now covers; bumping
the floor unlocks the wrapper adoption in the follow-up commits
on this branch.
what:
- pyproject.toml: libtmux>=0.55.1,<1.0 -> libtmux>=0.56.0,<1.0
- pyproject.toml: add libtmux to [tool.uv.exclude-newer-package] so
the 3-day cooldown does not block fresh releases for every
contributor's uv sync. Mirrors the gp-libs / gp-sphinx entries.
- uv.lock: libtmux 0.55.1 -> 0.56.0
- CHANGES: ### Dependencies entry naming the new wrappers this
floor change unlocks (Pane.respawn, Pane.copy_mode, Pane.pipe,
Pane.swap, Pane.paste_buffer, Pane.clear_history,
Pane.display_message, Server.delete_buffer, Session navigation).
why: libtmux 0.56.0 ships Pane.respawn with the exact argv shape the
stopgap predicted; the manual cmd("respawn-pane", *argv) call
was a placeholder waiting on the release.
what:
- src/libtmux_mcp/tools/pane_tools/lifecycle.py: replace the manual
argv builder + cmd("respawn-pane", *argv) + if result.stderr block
with pane.respawn(kill=, start_directory=, environment=, shell=).
- Delete the multi-line stopgap comment that prescribed exactly this
swap. Pane.respawn raises LibTmuxException directly, which the
handle_tool_errors decorator already translates to ToolError.
why: libtmux 0.56.0 exposes typed wrappers for copy-mode entry,
pipe-pane configuration, and clear-history. The MCP was hand-
rolling argv tuples for all three; the wrappers replace that
boilerplate while keeping behaviour identical.
what:
- tools/pane_tools/copy_mode.py: pane.cmd("copy-mode") -> pane.copy_mode().
scroll-up + cancel send-keys -X invocations go through
Pane.send_keys(copy_mode_cmd=..., repeat=..., enter=False) so the
-X flag and -N repeat count are emitted by libtmux's argv builder.
- tools/pane_tools/pipe.py: pane.cmd("pipe-pane", ...) -> pane.pipe(...)
for both the stop (no command) and start (cat redirect command) paths.
- tools/pane_tools/io.py:241: pane.cmd("clear-history") ->
pane.clear_history(). The send-keys -R line above stays as a raw
cmd() because Pane.send_keys would emit an extra empty key
alongside the reset flag, and Pane.reset() is still broken upstream
(libtmux #650).
why: libtmux 0.56.0 ships typed wrappers for paste-buffer and
delete-buffer. The MCP was assembling argv lists by hand and
redundantly injecting -t pane_id (libtmux's Pane.cmd already
does that, leaving the duplicate behind as latent boilerplate).
what:
- tools/buffer_tools.py: paste_buffer tool now calls
pane.paste_buffer(buffer_name=, bracket=) instead of building
-b/-p/-t argv. The duplicate -t injection is gone.
- tools/pane_tools/io.py: paste_text's mid-flight paste uses
pane.paste_buffer(buffer_name=, bracket=, delete_after=True);
the cleanup leg uses server.delete_buffer(buffer_name=).
- server.py: _gc_mcp_buffers cleanup loop now calls
server.delete_buffer(buffer_name=) directly. The list-buffers
read keeps -F because Server.list_buffers() returns tmux's default
formatted lines, not raw names.
why: libtmux 0.56.0 ships Session.next_window / previous_window /
last_window wrappers that inject `-t $session_id`, return the
new active Window, and raise LibTmuxException on stderr. The
MCP's directional-navigation block was hand-rolling all three
concerns.
what:
- tools/session_tools.py: collapse the next/previous/last-window
cmd() dispatch into a method-name lookup. Each wrapper returns
the new active Window, so the manual `if proc.stderr:` branch
and the post-call `session.active_window` re-fetch are gone.
- tests/test_session_tools.py: the "no prior window" regression
test now matches "no last window" (tmux's actual stderr surfaced
via LibTmuxException -> ToolError) instead of the old "last-window"
subcommand-name substring.
…46) why: libtmux 0.56.0 ships typed wrappers for swap-pane and display- message that subsume the MCP's hand-built argv shapes. Pane.select was already available pre-0.56; this commit folds in its adoption alongside the new wrappers as part of the same layout/meta cleanup pass. what: - tools/pane_tools/layout.py:174: server.cmd("select-pane", target=target_pane.pane_id) -> target_pane.select(). - tools/pane_tools/layout.py:221: server.cmd("swap-pane", ...) -> source.swap(target=target) after resolving the target_pane_id to a Pane object. - tools/pane_tools/meta.py:66: pane.cmd("display-message", "-p", fmt) -> pane.display_message(fmt, get_text=True). Same join logic over the returned list[str]. - tools/pane_tools/meta.py:162: same display_message wrapper applied to the snapshot_pane multi-format read. - tools/pane_tools/layout.py:74: the window-zoomed-flag read stays on window.cmd("display-message", ...) — window_zoomed_flag isn't a declared attribute on libtmux's Window in 0.56.0 and Window has no display_message wrapper yet. Comment explains the gap so the next pass can pick it up when upstream lands either surface.
53959f3 to
c0a7233
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #46.
Summary
libtmux 0.56.0 shipped ~50 new public methods that close the long-tail gap between the tmux(1) surface and the Python wrapper. The release notes call this out explicitly: "callers no longer need to drop down to
Server.cmd(...)for the commands tmux ships out of the box."This PR bumps the floor and adopts the wrappers across the eligible MCP call sites in five focused commits.
Commits
py(deps[libtmux]): bump floor to 0.56.0 (was 0.55.1). Adds libtmux to[tool.uv.exclude-newer-package]so the 3-day cooldown does not block fresh releases for every contributor'suv sync— mirrors the existinggp-libs/gp-sphinxentries. CHANGES### Dependenciesentry.Pane(refactor[respawn]): adoptPane.respawnfrom libtmux 0.56. Deletes the long stopgap comment block and the manualif result.stderr:branch.Pane(refactor): adopt copy_mode / pipe / clear_history wrappers.pane.cmd("copy-mode") → pane.copy_mode(),send-keys -X cmdcluster →pane.send_keys(copy_mode_cmd=…, repeat=…, enter=False),pipe-pane → pane.pipe,clear-history → pane.clear_history().Pane,Server(refactor[buffer]): adoptPane.paste_buffer+Server.delete_bufferacrossbuffer_tools.py,pane_tools/io.py, andserver.py. Eliminates a duplicate-t pane_id(libtmux'sPane.cmdalready injects it).Session(refactor): adoptSession.next_window/previous_window/last_window. Each returns the new active Window, raising on stderr — so the directional-navigation block collapses to a method-name lookup with no manual stderr handling oractive_windowre-fetch.Pane(refactor): adoptPane.swap+Pane.display_message+Pane.select. One zoom-flag read inlayout.py:74stays on rawcmd()becausewindow_zoomed_flagisn't a typed attribute on libtmux'sWindowin 0.56.0 andWindowhas nodisplay_messagewrapper yet — comment explains the gap.Out of scope (no wrapper in 0.56.0)
These sites stay on raw
cmd()per the umbrella issue:_utils.py:159,server_tools.py:181/:251— Server-scopeddisplay-messagereads (#{socket_path},#{version}).Serverhas nodisplay_messagewrapper.pane_tools/search.py:206—list-panes -f #{C/i:…}C-filter expression;server.panesdoesn't expose the-fshortcut.server.py:256—list-buffers -F #{buffer_name}.Server.list_buffers()returns tmux's default formatted lines, not raw names.pane_tools/layout.py:74—window.cmd("display-message", "-p", "#{window_zoomed_flag}"), see commit 6 note.Test plan
Each commit was gated by the full verification chain:
rm -rf docs/_builduv run ruff check . --fix --show-fixes— cleanuv run ruff format .— no changesuv run mypy— Success: no issues found in 51 source filesuv run py.test --reruns 0 -vvv— 446 passedjust build-docs— build succeededOne test was updated in commit 5:
test_select_window_last_on_single_window_session_raisesnow matches"no last window"(tmux's actual stderr, surfaced viaLibTmuxException → ToolError) instead of the old"last-window"subcommand-name substring that the previous manual error message contained.