Create tmux sessions, split windows, send input, and capture pane output from
Ruby. Read server state into a snapshot, then query it with
where, select, and the rest of
Enumerable.
Quick start · Find things · Packages · Guide · API reference · Recipes · API on libtmux.org
Alpha. APIs may change between releases. See the initial alpha notes and release guide.
It suits scripts, tests that need a real tmux, and tools that drive a terminal program. An MCP server, an Async adapter and a workspace loader ship beside the core, under Packages.
Contents — Quick start · Find things · Your own server · Packages · Build from source · Leaves out · Support · Documentation
Install the core prerelease from RubyGems. It needs Ruby 3.3 or newer and tmux
3.3a or newer on PATH:
$ gem install libtmux --preThis program creates a session, adds a window, splits it, and prints each
window with its panes. Server.start closes its private tmux
server when the block exits, and the snapshot stays readable afterward:
require "libtmux"
snapshot = LibTmux::Server.start do |server|
session = server.new_session(name: "work", window_name: "main", command: ["/bin/cat"])
window = session.new_window(name: "logs", command: ["/bin/cat"])
window.split(direction: :horizontal, size: "40%", command: ["/bin/cat"])
server.snapshot
end
snapshot.windows.each do |window|
puts "#{window.name}: #{window.panes.map(&:id).join(', ')}"
endFrom a checkout, run the complete example:
$ mise exec -- bundle exec ruby examples/quickstart.rbIt prints:
main: %0
logs: %1, %2
Pane commands take argument arrays. Server.start gives its
tmux server a socket and an empty configuration of its own, so it never
touches a tmux you already use.
Server#snapshot: one read, many queries
Continue with the snapshot above. where accepts criteria
as data, and select accepts a Ruby block. These queries
make no tmux calls:
panes = snapshot.panes
active_ids = panes.where(active: true).map(&:id)
wide_panes = panes.select { |pane| pane.width >= 40 }
panes_by_window = panes.group_by { |pane| pane.window.name }
logs = snapshot.windows.one(name: "logs")
missing = snapshot.windows.one_or_nil(name: "missing")one raises NoMatchError or
MultipleMatchesError unless exactly one record
matches. one_or_nil returns nil for no match and
still rejects duplicates. Both errors live under LibTmux.
A selection keeps the membership it captured. Call
server.snapshot again while the server is open to read
later changes.
The field catalog lists query fields and wire names, and the list/filter recipe covers exact matches and queries after close.
Server.new: borrow a daemon
Server.open and Server.new bind an endpoint
that already exists, such as Server.open(socket_path: ...). Closing the
binding leaves the daemon running. The outer Server.start
block here only supplies a daemon to borrow:
require "libtmux"
LibTmux::Server.start do |owner|
owner.new_session(name: "work", command: ["/bin/cat"])
server = LibTmux::Server.new(endpoint: owner.endpoint)
begin
puts "connected sessions: #{server.list_sessions.length}"
ensure
server.close
end
puts "sessions after client close: #{owner.list_sessions.length}"
endIt prints:
connected sessions: 1
sessions after client close: 1
See ownership and errors for what close and failure mean.
Start with libtmux. Add the companion for your caller:
| Gem | Require | Use it for |
|---|---|---|
| libtmux | libtmux |
Blocking scripts, snapshots, and control connections |
| libtmux-async | libtmux/async |
Concurrent commands and bounded streams in Async tasks |
| libtmux-mcp | libtmux/mcp |
An MCP server with explicit endpoints and tool policy |
| libtmux-workspace | libtmux/workspace |
YAML/JSON workspace plans and a CLI to apply them |
Requiring a gem starts no tmux process, scheduler, or protocol server. The
companion gems install separately, and need --pre for their alpha versions
too. Execution modes explains blocking calls, Async tasks, and
control subscriptions.
libtmux: the core
Blocking server, session, window and
pane handles, immutable snapshots, and
control connections. Require libtmux.
API reference · Guides · RubyGems
$ gem install libtmux --preThe quick start and snapshot queries above run against this gem.
libtmux-async: concurrent commands
Concurrent commands and bounded control streams inside an Async task that your
application owns. Require libtmux/async, then open a scope with
LibTmux::Async.open.
API reference · Guide · RubyGems
$ gem install libtmux-async --preCapture every pane concurrently with Scope#map. server
is an open LibTmux::Server:
Async do |parent|
LibTmux::Async.open(parent: parent, server: server) do |scope|
refs = scope.server.list_panes.map(&:ref)
screens = scope.map(refs, concurrency: 2) { |ref| scope.server.pane(ref).capture }
Example.check(screens.length == refs.length && screens.all?(&:success?), "concurrent captures failed")
end
end.waitlibtmux-mcp: a tmux server for MCP clients
An MCP server over stdio for one tmux server, with an explicit tool policy.
Installs libtmux-mcp; require libtmux/mcp to embed it through
LibTmux::MCP::Application.
API reference · Tools · Guides · RubyGems
$ gem install libtmux-mcp --preThe server borrows a tmux server that is already running. Start one on a socket of its own:
$ tmux -L agent new-session -d -s workAdd the server to Claude Code. Until you enable more tools, it serves
tmux_capabilities and
tmux_snapshot:
$ claude mcp add tmux -- libtmux-mcp --socket-name agent --endpoint devThe MCP README covers other clients, every tool and authored shell commands. Tracked captures, waits and authored runs need tmux 3.3a or newer and native process identity.
libtmux-workspace: YAML workspaces
YAML or JSON workspaces, validated, planned offline and applied to an open
server. Installs libtmux-workspace; require libtmux/workspace to use it
from Ruby through LibTmux::Workspace.
API reference · Command reference · Guides · RubyGems
$ gem install libtmux-workspace --preSave this workspace as workspace.yaml:
session_name: work
environment:
PROJECT_MODE: development
windows:
- window_name: editor
window_index: 1
layout: tiled
panes:
- shell_command: printf 'editor ready\n'
- {}Check it without contacting tmux:
$ libtmux-workspace validate workspace.yamlWorkspace configuration is valid.
The workspace README loads it into a
running server and lists every option, including how --switch moves a
client.
Use the Ruby pinned in .tool-versions and have tmux on your
PATH. From this checkout, install the development bundle:
$ mise install$ mise exec -- bundle config set --local path vendor/bundle$ mise exec -- bundle installTo use the core outside this checkout, build the artifacts:
$ mise exec -- bundle exec rake buildInstall into the Ruby environment that will run your application. The core's runtime dependencies must already be installed for this local-only command:
$ gem install \
--local \
--no-document \
pkg/libtmux-0.1.0.alpha.2.gemCompanion gems need their declared runtime dependencies too. The packaging check verifies each gem in an isolated installation and runs the recipes outside the checkout.
- Live objects. A snapshot record is what tmux said when you read it, and a selection keeps the membership it captured. Read again to see a change.
- A terminal emulator.
Pane#capturereturns what tmux'scapture-paneprints for the pane. - An implied server.
Endpoint.newneeds an explicit socket, and no API in the recipes uses the default server. - Whole-program typing. RBS declarations ship with each gem, and selected installed calls are checked. See the public methods.
- Throughput claims. The benchmarks list their workloads, measurements and limits.
Alpha. APIs may change between releases.
- Ruby 3.3 or newer and tmux 3.3a or newer. Unpatched 3.3 is below the minimum. A server handle checks the connected daemon's release, not the client's, and refuses an older daemon before any requested command runs.
- Linux, including WSL, and macOS. The compatibility workflow runs Ruby 3.3, 3.4 and 4.0 with tmux 3.3a through 3.8 on Ubuntu 24.04. macOS is supported on a best-effort basis, and CI coverage for it is highly limited.
- tmux master and old daemons. On Ubuntu the workflow also runs the suite against a pinned tmux master, and checks that tmux 3.0a, 3.1c, 3.2a and 3.3 daemons are refused.
- Per-revision results. Check the workflow's results for your revision before relying on one combination.
- Send text and capture output: split panes, wait for output events, and round-trip binary buffers.
- Work with linked windows: address one window at several session indexes.
- Capture concurrently: read panes while another request waits, then cancel it.
- Load a workspace: parse a configuration, plan, and apply it.
- All recipes: cancellation, control streams, command groups, and MCP.
- API reference: public methods, source links, and behavioral contracts. RBS declarations ship with each gem; selected installed calls are checked, without a whole-program typing guarantee.
- Benchmarks: workloads, measurements, and their limits.
See Contributing for setup and checks.
Please use the following BibTeX template to cite libtmux-ruby in scientific discourse:
@misc{libtmux-ruby,
author = {Tony Narlock},
year = {2026},
note = {https://libtmux.org/en/ruby/},
title = {libtmux-ruby: Ruby wrapper for tmux}
}MIT. See LICENSE.