Getting started
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
Releases are numbered 0.1.N, and each release increments N. The API is not
settled: any release may change or remove exported identifiers, and the
changelog marks each break. Pin an exact version to avoid one.
See the changelog 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
Quick start¶
Install the core gem from RubyGems. It needs Ruby 3.3 or newer and tmux
3.3a or newer on PATH:
$ gem install libtmuxThis 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.snapshotend
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: %0logs: %1, %2Pane 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.
Find things in a snapshot¶
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.panesactive_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.
Use the tmux server you already run¶
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: 1sessions after client close: 1See ownership and errors for what close and failure mean.
Packages¶
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. 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 libtmuxThe 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-asyncCapture 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") endend.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-mcpThe 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-workspaceSave this workspace as workspace.yaml:
session_name: workenvironment: PROJECT_MODE: developmentwindows: - 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.
Build from source¶
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.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.
What it leaves out¶
- 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.
Support and status¶
Releases are numbered 0.1.N, and each release increments N. The API is not
settled: any release may change or remove exported identifiers, and the
changelog marks each break. Pin an exact version to avoid one.
- 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.
- Control connections need tmux 3.7 or newer. On an older daemon
open_controlraisesUnsupportedFeatureErrorbefore a client attaches, andServer#supports_control?returns false. Other optional features carry their own releases in the optional-feature table;capabilities.jsonholds the same records for tools. - 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.
Documentation¶
- 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.
Attribution¶
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}}License¶
MIT. See LICENSE.