Skip to content

Repository files navigation

libtmux for Ruby

libtmux for Ruby

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

Quick start

Install the core prerelease from RubyGems. It needs Ruby 3.3 or newer and tmux 3.3a or newer on PATH:

$ gem install libtmux --pre

This 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(', ')}"
end

From a checkout, run the complete example:

$ mise exec -- bundle exec ruby examples/quickstart.rb

It 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.

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.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.

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}"
end

It prints:

connected sessions: 1
sessions after client close: 1

See 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, 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 --pre

The 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 --pre

Capture 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.wait

libtmux-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 --pre

The server borrows a tmux server that is already running. Start one on a socket of its own:

$ tmux -L agent new-session -d -s work

Add 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 dev

The 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 --pre

Save 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.yaml
Workspace 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 install

To use the core outside this checkout, build the artifacts:

$ mise exec -- bundle exec rake build

Install 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.gem

Companion 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#capture returns what tmux's capture-pane prints for the pane.
  • An implied server. Endpoint.new needs 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

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.

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.

Contributors

Languages