# libtmux for Ruby

Source: https://libtmux.org/en/ruby/v0.1.2/

> libtmux for Ruby, version v0.1.2.

![](https://libtmux.org/en/brand/ruby/library/logo.svg)

Ruby-based tmux client

tmux client library for Ruby. Control tmux, navigate sessions, windows, and panes, send keys, watch for changes via control mode, capture pane contents

Product

Core API MCP Workspace Manager

Example details

\+ Error handling + Cleanup

```ruby
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|
  pane_ids = window.panes.map(&:id).join(", ")
  puts "#{window.name}: #{pane_ids}"
end
```

```ruby
require "libtmux"


begin
  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|
    pane_ids = window.panes.map(&:id).join(", ")
    puts "#{window.name}: #{pane_ids}"
  end
rescue StandardError => error
  warn "tmux quickstart failed: #{error.message}"
  raise
end
```

```ruby
require "libtmux"


server = LibTmux::Server.start


begin
  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"]
  )


  snapshot = server.snapshot
  snapshot.windows.each do |window|
    pane_ids = window.panes.map(&:id).join(", ")
    puts "#{window.name}: #{pane_ids}"
  end
ensure
  server.close
end
```

```ruby
require "libtmux"


server = LibTmux::Server.start


begin
  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"]
  )


  snapshot = server.snapshot
  snapshot.windows.each do |window|
    pane_ids = window.panes.map(&:id).join(", ")
    puts "#{window.name}: #{pane_ids}"
  end
rescue StandardError => error
  warn "tmux quickstart failed: #{error.message}"
  raise
ensure
  server.close
end
```

Run this example

POSIX shell, tmux 3.3a or newer, Ruby 4.0.7, Bundler 4.0.20, a C build toolchain for gems and access to rubygems.org.

Save the program above as `quickstart.rb` in a new directory, along with the files below.

The Ruby block closes its server automatically. Cleanup shows the explicit ensure form. The program stops its private tmux server and removes any temporary files it created when it finishes. Error handling adds diagnostics; every version checks required results and deadlines.

Gemfile

```ruby
source "https://rubygems.org"
gem "libtmux", "~> 0.1"
```

Gemfile.lock

```text
GEM
  remote: https://rubygems.org/
  specs:
    fiddle (1.1.8)
    fileutils (1.8.1)
    io-console (0.9.4)
    json (3.0.2)
    libtmux (0.1.0)
      fiddle (~> 1.1)
      io-console (~> 0.8)
      json (~> 3.0.2)
      securerandom (~> 0.4)
      tmpdir (~> 0.3)
    securerandom (0.4.1)
    tmpdir (0.3.2)
      fileutils


PLATFORMS
  ruby
  x86_64-linux


DEPENDENCIES
  libtmux (~> 0.1)


CHECKSUMS
  fiddle (1.1.8) sha256=7fa8ee3627271497f3add5503acdbc3f40b32f610fc1cf49634f083ef3f32eee
  fileutils (1.8.1) sha256=2d541a1ee34a9ed5c4f054be8fb819a26b5d654488a31dc8806917cf448b9cc3
  io-console (0.9.4) sha256=a403304f32928ee85e56d457b4612ca43f2703b561a3258844992bd24085fbb1
  json (3.0.2) sha256=8e6d7e7b11384c21230430cef90b71f14849a34a1f4452796670f7c981bd19df
  libtmux (0.1.0) sha256=e1cc928de46d517fc9956ba6161a5b9d205ae8ed337bfd31689b1adb75f3ef00
  securerandom (0.4.1) sha256=cc5193d414a4341b6e225f0cb4446aceca8e50d5e1888743fac16987638ea0b1
  tmpdir (0.3.2) sha256=e276ef386182169f8d417d9e90f12dba82a54e81646df0caf21de4a7987a0c27


BUNDLED WITH
  4.0.20
```

### Setup and run

```bash
bundle config set --local path vendor/bundle
bundle install --jobs 2
```

```bash
unset TMUX TMUX_PANE
export ENV=/dev/null BASH_ENV=/dev/null SHELL=/bin/sh
bundle exec ruby quickstart.rb
```

### Expected output

```text
main: %0
logs: %1, %2
```

Library revision: [c8b839595d5a](https://github.com/libtmux/libtmux-ruby/tree/c8b839595d5a30e271e3f9be0d0fe811ddb11b7e).

examples/api/capture.rb

```ruby
# frozen_string_literal: true


begin
  # docs:begin main
  require "libtmux"


  LibTmux::Server.start do |server|
    created = server.new_session(
      name: "capture", command: ["/bin/sh"],
      environment: {"ENV" => "/dev/null"}, receipt: true
    )
    pane = created.pane
    pane.send_text("printf '\\nlibtmux capture ready\\n'")
    pane.send_keys("Enter")
    deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + 5
    captured = pane.capture.stdout.lines(chomp: true)
    until captured.include?("libtmux capture ready")
      raise "Output did not arrive within five seconds" if Process.clock_gettime(Process::CLOCK_MONOTONIC) > deadline
      sleep 0.05
      captured = pane.capture.stdout.lines(chomp: true)
    end
    raise "Expected output is absent from the pane" unless captured.include?("libtmux capture ready")
    puts "libtmux capture ready"
  end
  # docs:end main
rescue StandardError => error
  warn "#{error.class}: #{error.message}"
  exit 1
end
```

The whole program, [`examples/api/capture.rb`](https://github.com/libtmux/libtmux-ruby/blob/46474e8a2f03e39fc2b0ff73b6a841777b79c4d2/examples/api/capture.rb). The port's CI runs it in `.github/workflows/compatibility.yml`: `bundle exec scripts/matrix`.

examples/api/new_window\.rb

```ruby
# frozen_string_literal: true


begin
  # docs:begin main
  require "libtmux"


  LibTmux::Server.start do |server|
    session = server.new_session(name: "work", command: ["/bin/cat"])
    window = session.new_window(name: "logs", command: ["/bin/cat"])
    puts "window: #{window.snapshot.name}"
    puts "session windows: #{session.list_windows.length}"
  end
  # docs:end main
rescue StandardError => error
  warn "#{error.class}: #{error.message}"
  exit 1
end
```

The whole program, [`examples/api/new_window.rb`](https://github.com/libtmux/libtmux-ruby/blob/46474e8a2f03e39fc2b0ff73b6a841777b79c4d2/examples/api/new_window.rb). The port's CI runs it in `.github/workflows/compatibility.yml`: `bundle exec scripts/matrix`.

APIs used

* [`Server`](https://libtmux.org/en/ruby/v0.1.2/reference/libtmux-server/)
* [`Server.start`](https://libtmux.org/en/ruby/v0.1.2/reference/libtmux-server-start/)
* [`Server#new_session`](https://libtmux.org/en/ruby/v0.1.2/reference/libtmux-server-new_session/)
* [`Session#new_window`](https://libtmux.org/en/ruby/v0.1.2/reference/libtmux-session-new_window/)
* [`Window#split`](https://libtmux.org/en/ruby/v0.1.2/reference/libtmux-window-split/)
* [`Server#snapshot`](https://libtmux.org/en/ruby/v0.1.2/reference/libtmux-server-snapshot/)
* [`Snapshot#windows`](https://libtmux.org/en/ruby/v0.1.2/reference/libtmux-snapshot-windows/)
* [`WindowSnapshot#panes`](https://libtmux.org/en/ruby/v0.1.2/reference/libtmux-windowsnapshot-panes/)
* [`PaneSnapshot#id`](https://libtmux.org/en/ruby/v0.1.2/reference/libtmux-panesnapshot-id/)
* [`WindowSnapshot#name`](https://libtmux.org/en/ruby/v0.1.2/reference/libtmux-windowsnapshot-name/)
* [+1 more](https://libtmux.org/en/ruby/v0.1.2/reference/)

- [`Server`](https://libtmux.org/en/ruby/v0.1.2/reference/libtmux-server/)
- [`Server.start`](https://libtmux.org/en/ruby/v0.1.2/reference/libtmux-server-start/)
- [`Server#new_session`](https://libtmux.org/en/ruby/v0.1.2/reference/libtmux-server-new_session/)
- [`CreationReceipt#pane`](https://libtmux.org/en/ruby/v0.1.2/reference/libtmux-creationreceipt-pane/)
- [`Pane#send_text`](https://libtmux.org/en/ruby/v0.1.2/reference/libtmux-pane-send_text/)
- [`Pane#send_keys`](https://libtmux.org/en/ruby/v0.1.2/reference/libtmux-pane-send_keys/)
- [`Pane#capture`](https://libtmux.org/en/ruby/v0.1.2/reference/libtmux-pane-capture/)
- [`CommandResult#stdout`](https://libtmux.org/en/ruby/v0.1.2/reference/libtmux-commandresult-stdout/)

* [`Server`](https://libtmux.org/en/ruby/v0.1.2/reference/libtmux-server/)
* [`Server.start`](https://libtmux.org/en/ruby/v0.1.2/reference/libtmux-server-start/)
* [`Server#new_session`](https://libtmux.org/en/ruby/v0.1.2/reference/libtmux-server-new_session/)
* [`Session#new_window`](https://libtmux.org/en/ruby/v0.1.2/reference/libtmux-session-new_window/)
* [`Window#snapshot`](https://libtmux.org/en/ruby/v0.1.2/reference/libtmux-window-snapshot/)
* [`WindowSnapshot#name`](https://libtmux.org/en/ruby/v0.1.2/reference/libtmux-windowsnapshot-name/)
* [`Session#list_windows`](https://libtmux.org/en/ruby/v0.1.2/reference/libtmux-session-list_windows/)

[Core Library documentation →](https://libtmux.org/en/ruby/v0.1.2/core/)[More Ruby examples →](https://libtmux.org/en/ruby/v0.1.2/examples/)

### Install `libtmux`

```console
$ gem install libtmux
```

## MCP

The Ruby server lets an agent work with tmux through 8 tools.

### Worked example

Given this prompt, an agent connected to the Ruby server makes these calls and gets these results.

%0 cat

@0

tmux_snapshot read the panes; tmux itself is unchanged.

you

List the panes on my tmux server.

agentAsk the server which tools and limits are enabled.

[`tmux_capabilities`](https://libtmux.org/en/ruby/v0.1.2/mcp/tools/tmux_capabilities/) `{}`

Result

```json
{
  "ok": true,
  "data": {
    "endpoint": "docs",
    "server_identity": {
      "generation": "2b7aa398dd617620e4e735be8ff694bc",
      "pid": 2482776,
      "start_time": 1793692756,
      "tmux_version": "3.8a"
    },
    "enabled_tools": [
      "tmux_capabilities",
      "tmux_snapshot",
      "tmux_send",
      "tmux_create",
      "… 4 more"
    ],
    "criteria_schema": {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "$id": "urn:libtmux-ruby:where:1",
      "title": "Ruby libtmux criteria version 1",
      "description": "The Ruby decoder additionally enforces byte, depth and node limits, rejects duplicate keys and floating-point tokens for integer fields.",
      "oneOf": [
        {
          "type": "object",
          "required": "…",
          "additionalProperties": false,
          "properties": "…"
        },
        {
          "type": "object",
          "required": "…",
          "additionalProperties": false,
          "properties": "…"
        },
        {
          "type": "object",
          "required": "…",
          "additionalProperties": false,
          "properties": "…"
        },
        {
          "type": "object",
          "required": "…",
          "additionalProperties": false,
          "properties": "…"
        },
        "… 1 more"
      ],
      "$defs": {
        "session.id": {
          "anyOf": "…"
        },
        "session.name": {
          "anyOf": "…"
        },
        "session.created": {
          "anyOf": "…"
        },
        "session.attached": {
          "anyOf": "…"
        },
        "session.window_count": {
          "anyOf": "…"
        },
        "session": {
          "type": "object",
          "additionalProperties": false,
          "properties": "…"
        },
        "window.id": {
          "anyOf": "…"
        },
        "window.name": {
          "anyOf": "…"
        },
        "window.width": {
          "anyOf": "…"
        },
        "window.height": {
          "anyOf": "…"
        },
        "window.pane_count": {
          "anyOf": "…"
        },
        "window.layout": {
          "anyOf": "…"
        },
        "…": "30 more"
      }
    },
    "limits": {
      "max_captures": 16,
      "max_capture_bytes": 8388608,
      "capture_ttl_seconds": 30,
      "request_timeout_seconds": 5,
      "max_response_bytes": 1048576,
      "max_mutation_bytes": 65536,
      "max_observers": 16,
      "max_enrollments": 8,
      "default_enrollment_seconds": 60,
      "max_enrollment_seconds": 300,
      "max_script_bytes": 65536,
      "default_run_output_bytes": 65536,
      "…": "2 more"
    },
    "owns_daemon": false,
    "resource_subscriptions": false,
    "observation": {
      "screen": "bounded_rows",
      "history_continuity": "unknown",
      "process_cursor": "conditional",
      "requirements": [
        "tmux >= 3.3a",
        "64-bit Linux with peer pidfds (kernel >= 6.6)",
        "same PID namespace and matching procfs",
        "live pane process",
        "… 3 more"
      ],
      "wait_conditions": [
        "screen_contains",
        "process_exit"
      ]
    },
    "authored_run": {
      "availability": "conditional",
      "shell_profile": "zsh-5.9-zle",
      "enrollment": "explicit_source",
      "authorization": "exact_generation_at_queue_grant",
      "stdin": "closed",
      "persistent_shell_changes": false,
      "descendant_termination": "unobserved",
      "requirements": [
        "tmux >= 3.3a",
        "64-bit Linux with peer pidfds (kernel >= 6.6)",
        "same PID namespace and matching procfs",
        "live pane process",
        "… 5 more"
      ]
    }
  }
}
```

agentList the panes.

[`tmux_snapshot`](https://libtmux.org/en/ruby/v0.1.2/mcp/tools/tmux_snapshot/) `{"entity":"pane","limit":20}`

Result

```json
{
  "ok": true,
  "data": {
    "capture_id": "aec1a3d6e62a4ceb2c8326b8a54dcbc7",
    "server_identity": {
      "generation": "2b7aa398dd617620e4e735be8ff694bc",
      "pid": 2482776,
      "start_time": 1793692756,
      "tmux_version": "3.8a"
    },
    "entity": "pane",
    "coverage": {
      "session": "complete",
      "window": "complete",
      "pane": "complete",
      "window_link": "complete",
      "client": "complete"
    },
    "interval": {
      "clock": "monotonic_seconds",
      "started": 216410.154280461,
      "finished": 216410.167168026,
      "reads": 5
    },
    "items": [
      {
        "kind": "pane",
        "fields": {
          "id": "%0",
          "windowId": "@0",
          "index": 0,
          "pid": 2482777,
          "currentCommand": "cat",
          "currentPath": "/home/user/project",
          "title": "host",
          "active": true,
          "dead": false,
          "deadStatus": null,
          "width": 80,
          "height": 24
        },
        "ref": {
          "generation": "2b7aa398dd617620e4e735be8ff694bc",
          "kind": "pane",
          "id": "%0"
        }
      }
    ],
    "truncated": false
  }
}
```

Calls and results are recorded against [`libtmux/libtmux-ruby`](https://github.com/libtmux/libtmux-ruby/tree/20744401ee9f78a131592481e251c6a09ac8f5fa) at `20744401e`, on a private tmux 3.8a server. The preview draws only what those results state. The agent's narration is illustrative. This page documents [`46474e8a2`](https://github.com/libtmux/libtmux-ruby/tree/46474e8a2f03e39fc2b0ff73b6a841777b79c4d2), a different revision. The calls have the same input and output schemas there, but the results at that revision may differ from the recorded ones. Long values in a result are shortened here; the recording keeps them. The sandbox's project directory is shown as `/home/user/project`.

### Install `libtmux-mcp`

Choose your client and configuration scope, then run the command or paste the configuration.

With [RubyGems ](https://rubygems.org/gems/libtmux-mcp)installed:

```console
$ claude mcp add tmux -- libtmux-mcp --socket-name libtmux-docs --endpoint local
```

The server borrows an existing named socket. Start it first with \`tmux -L libtmux-docs new-session -d\`; \`--endpoint\` is only its public alias.

Config file: `~/.claude.json (this project)`

* [All tools →](https://libtmux.org/en/ruby/v0.1.2/mcp/#tools)
* [Every tool, with its arguments and result →](https://libtmux.org/en/ruby/v0.1.2/mcp/tools/)
* [MCP documentation →](https://libtmux.org/en/ruby/v0.1.2/mcp/)

Embed the server in a program

examples/mcp_protocol.rb

```ruby
application = LibTmux::MCP::Application.new(server: scope.server, endpoint_name: "example")
sdk = application.sdk_server
input, client_input = IO.pipe
client_output, output = IO.pipe
transport = LibTmux::MCP::StdioTransport.new(server: sdk, parent: parent, input: input, output: output)
runner = parent.async { transport.run }
```

Excerpt of [`examples/mcp_protocol.rb`](https://github.com/libtmux/libtmux-ruby/blob/46474e8a2f03e39fc2b0ff73b6a841777b79c4d2/examples/mcp_protocol.rb), which `bundle exec scripts/matrix` runs in the repository's CI (`.github/workflows/compatibility.yml`).

## Workspace Manager

### Tiled layout

The tiled layout arranges the panes in a grid.

```yaml
session_name: work
environment:
  PROJECT_MODE: development
windows:
  - window_name: editor
    window_index: 1
    layout: tiled
    panes:
      - shell_command: printf 'editor ready\n'
      - {}
```

```json
{
  "session_name": "work",
  "environment": {
    "PROJECT_MODE": "development"
  },
  "windows": [
    {
      "window_name": "editor",
      "window_index": 1,
      "layout": "tiled",
      "panes": [
        {
          "shell_command": "printf 'editor ready\\n'"
        },
        {}
      ]
    }
  ]
}
```

Converted from the YAML; the tests load the YAML.

1:editor

layout tiled

Save the file as `workspace.yaml` and load it with:

```sh
libtmux-workspace load workspace.yaml
```

Workspace file: [`examples/workspace.yaml`](https://github.com/libtmux/libtmux-ruby/blob/20744401ee9f78a131592481e251c6a09ac8f5fa/examples/workspace.yaml). Tested by [`test_installed_imports_use_declared_dependencies_without_starting_resources`](https://github.com/libtmux/libtmux-ruby/blob/20744401ee9f78a131592481e251c6a09ac8f5fa/test/packaging/package_test.rb#L21) (test/packaging/package_test.rb; a test runs the command).

### Install `libtmux-workspace`

The workspace package is separate from the core library.

```console
$ gem install libtmux-workspace
```

* [Example workspaces →](https://libtmux.org/en/ruby/v0.1.2/workspace/#example-workspaces)
* [The `libtmux-workspace load` command →](https://libtmux.org/en/ruby/v0.1.2/workspace/cli/load/)
* [Workspace Manager documentation →](https://libtmux.org/en/ruby/v0.1.2/workspace/)

Build the workspace from a program

examples/workspace_apply.rb

```ruby
workspace = LibTmux::Workspace.load(File.join(__dir__, "workspace.yaml"))
plan = workspace.plan(snapshot: server.snapshot)
Example.check(server.list_sessions.size == 1, "planning changed tmux")
result = plan.apply(server: server)
Example.check(result.success?, "workspace apply failed")
```

Excerpt of [`examples/workspace_apply.rb`](https://github.com/libtmux/libtmux-ruby/blob/46474e8a2f03e39fc2b0ff73b6a841777b79c4d2/examples/workspace_apply.rb), which `bundle exec scripts/matrix` runs in the repository's CI (`.github/workflows/compatibility.yml`).

### [Topics](https://libtmux.org/en/ruby/v0.1.2/topics/)

[Subject-by-subject, with Ruby in every code block.](https://libtmux.org/en/ruby/v0.1.2/topics/)

### [Guides](https://libtmux.org/en/ruby/v0.1.2/guides/)

[Task guides for Ruby.](https://libtmux.org/en/ruby/v0.1.2/guides/)

### [Examples](https://libtmux.org/en/ruby/v0.1.2/examples/)

[Complete programs, sourced from the code each port tests.](https://libtmux.org/en/ruby/v0.1.2/examples/)

### [Concepts](https://libtmux.org/en/ruby/v0.1.2/concepts/)

[The domain model every port shares.](https://libtmux.org/en/ruby/v0.1.2/concepts/)

## Packages and integrations

### [Async](https://libtmux.org/en/ruby/v0.1.2/guides/async/)

[Use the libtmux-async companion package for asynchronous tmux work.](https://libtmux.org/en/ruby/v0.1.2/guides/async/)

### [MCP](https://libtmux.org/en/ruby/v0.1.2/mcp/)

[Configure libtmux-mcp and inspect its tmux tool protocol.](https://libtmux.org/en/ruby/v0.1.2/mcp/)

### [Workspace Manager](https://libtmux.org/en/ruby/v0.1.2/workspace/)

[Validate, plan, and load workspace configuration with libtmux-workspace.](https://libtmux.org/en/ruby/v0.1.2/workspace/)

## API references

### [Official reference](https://libtmux.org/en/ruby/v0.1.2/reference/)

[Ruby API signatures and documentation, with links to related guides and APIs in other languages.](https://libtmux.org/en/ruby/v0.1.2/reference/)

[All Ruby versions and install instructions](https://libtmux.org/en/ruby/)
