tmux client library for Raku
Control tmux, connect to and create servers, navigate sessions, windows, and panes, send keys, watch for changes via control mode, capture pane contents.
Getting started
Install LibTmux, create and split panes, and find them through typed snapshots.
Examples
Run six complete programs against private tmux servers and compare their output.
tmux compatibility
Check the supported tmux releases and how the library handles newer ones.
API reference
Native signatures, RakuDoc and links for every public module, class and method.
Install
zef install 'LibTmux:auth<zef:libtmux>'At a glance
Server → Session → LibTmux::Model::WindowLink → Window → Pane. Each level of the tmux hierarchy has its own entry in the Raku API, with the tmux object it wraps.
| Type | Wraps |
|---|---|
LibTmux::Server | A tmux server |
LibTmux::Model::Session | A session, ID $n |
LibTmux::Model::Window | A physical window linked into sessions, ID @n |
LibTmux::Model::Pane | A pane in a window, ID %n |
LibTmux::Model::WindowLink | A session-window association, with its index and active state |
What it does
use LibTmux;
my $server = Server.start(:name<work>, :shell-command('exec cat'));LEAVE $server.close if $server.defined;
my $before = $server.snapshot;my $left = $before.panes.single.ref;my $right = $left.split(:horizontal, :shell-command('exec cat'));$left.set-title('editor');$right.set-title('logs');
my $after = $server.snapshot;my $editor = $after.panes.filter(*.title eq 'editor').single;say "{$editor.title}: {$after.panes.elems} panes";tmux before the first call: no server is running.
tmux after Server.start: added session work.
tmux after Server.snapshot: unchanged.
tmux after PaneSelection.single: unchanged, the call sent no command.
tmux after PaneRef.split: added pane %1 to window 0:sh.
tmux after PaneRef.set-title: unchanged.
tmux after PaneRef.set-title: unchanged.
tmux after Server.snapshot: unchanged.
tmux after PaneSelection.single: unchanged, the call sent no command.
tmux after Server.close: the server has exited; the call sent no command.
Before the first call
No tmux server is running yet.
Server.start returned Server
{ "socket": "libtmux-owned-AbC123"}1 tmux command, 1 that changes tmux
-
new-session -d -P -F LTM1-session-#{session_id} -s work -x 80 -y 24 -- 'exec cat'LTM1-session-$0
Before these, the library asked tmux about itself 4 times (its version, its daemon, how a control client reads escapes).
Server.snapshot returned Snapshot
{ "panes": 1, "sessions": 1, "windows": 1}7 tmux commands
-
list-sessions -F 'LTM1\011libtmux.format/v1:session:session:qa:none:id,name,windows-count,attached-count,created,activity,path,grouped,group-name\011qa\011#{session_id}\011#{q/a:session_name}\011#{session_windows}\011#{session_attached}\011#{session_created}\011#{session_activity}\011#{q/a:session_path}\011#{session_grouped}\011#{q/a:session_group}\011END'LTM1 libtmux.format/v1:session:session:qa:none:id,name,windows-count,attached-count,created,activity,path,grouped,group-name qa $0 work 1 0 1794634589 1794634589 /tmp/marked 0 END -
list-windows -a -F 'LTM1\011libtmux.format/v1:window-link:window-link:qa:none:session-id,window-id,index,active,last,activity,bell,silence\011qa\011#{session_id}\011#{window_id}\011#{window_index}\011#{window_active}\011#{window_last_flag}\011#{window_activity_flag}\011#{window_bell_flag}\011#{window_silence_flag}\011END'LTM1 libtmux.format/v1:window-link:window-link:qa:none:session-id,window-id,index,active,last,activity,bell,silence qa $0 @0 0 1 0 0 0 0 END -
list-windows -a -F 'LTM1\011libtmux.format/v1:window:window:qa:none:id,name,width,height,panes-count,layout,visible-layout,zoomed,modal-pane-id\011qa\011#{window_id}\011#{q/a:window_name}\011#{window_width}\011#{window_height}\011#{window_panes}\011#{q/a:window_layout}\011#{q/a:window_visible_layout}\011#{window_zoomed_flag}\011#{window_modal_pane}\011END'LTM1 libtmux.format/v1:window:window:qa:none:id,name,width,height,panes-count,layout,visible-layout,zoomed,modal-pane-id qa @0 sh 80 24 1 "{\"V\":2,\"L\":{\"t\":\"p\",\"w\":80,\"h\":24,\"x\":0,\"y\":0,\"a\":true,\"i\":0,\"I\":\"%0\"}}" "{\"V\":2,\"L\":{\"t\":\"p\",\"w\":80,\"h\":24,\"x\":0,\"y\":0,\"a\":true,\"i\":0,\"I\":\"%0\"}}" 0 END -
list-panes -a -F 'LTM1\011libtmux.format/v1:pane:pane:qa:none:id,window-id,index,active,width,height,left,top,pid,tty,title,current-command,current-path,start-command,start-path,dead,dead-status,dead-signal,dead-time,in-mode,mode,pipe,synchronized,floating,modal,x,y,z,command-running,command-status,at-top,at-bottom,at-left,at-right\011qa\011#{pane_id}\011#{window_id}\011#{pane_index}\011#{pane_act…LTM1 libtmux.format/v1:pane:pane:qa:none:id,window-id,index,active,width,height,left,top,pid,tty,title,current-command,current-path,start-command,start-path,dead,dead-status,dead-signal,dead-time,in-mode,mode,pipe,synchronized,floating,modal,x,y,z,command-running,command-status,at-top,at-bottom,at-left,at-right qa %0 @0 0 1 80 24 0 0 2005202 /dev/pts/19 host cat /tmp/marked "\"exe… -
list-clients -F 'LTM1\011libtmux.format/v1:client:client:qa:none:name,session-id,session-name,tty,pid,width,height,created,activity,control-mode,read-only,terminal-name,key-table\011qa\011#{q/a:client_name}\011#{session_id}\011#{q/a:client_session}\011#{q/a:client_tty}\011#{client_pid}\011#{client_width}\011#{client_height}\011#{client_created}\011#{client_activity}\011#{client_control_mode}\011#{…no output -
list-sessions -f #{session_grouped} -F 'LTM1\011libtmux.format/v1:session-group:grouped-session:qa:none:name,size,attached-count\011qa\011#{q/a:session_group}\011#{session_group_size}\011#{session_group_attached}\011END'no output -
list-buffers -F 'LTM1\011libtmux.format/v1:buffer:buffer:qa:none:name,size,created,sample\011qa\011#{q/a:buffer_name}\011#{buffer_size}\011#{buffer_created}\011#{q/a:buffer_sample}\011END'no output
Before these, the library asked tmux about itself 6 times (its version, its daemon, how a control client reads escapes).
PaneSelection.single returned PaneRef
{ "id": "%0"}The call sent no tmux command.
PaneRef.split returned PaneRef
{ "id": "%1"}1 tmux command, 1 that changes tmux
-
split-window -P -F LTM1-pane-#{pane_id} -t %0 -h -d -- 'exec cat'LTM1-pane-%1
PaneRef.set-title returned void
null1 tmux command, 1 that changes tmux
-
select-pane -t %0 -T editorno output
PaneRef.set-title returned void
null1 tmux command, 1 that changes tmux
-
select-pane -t %1 -T logsno output
Server.snapshot returned Snapshot
{ "panes": 2, "sessions": 1, "windows": 1}7 tmux commands
-
list-sessions -F 'LTM1\011libtmux.format/v1:session:session:qa:none:id,name,windows-count,attached-count,created,activity,path,grouped,group-name\011qa\011#{session_id}\011#{q/a:session_name}\011#{session_windows}\011#{session_attached}\011#{session_created}\011#{session_activity}\011#{q/a:session_path}\011#{session_grouped}\011#{q/a:session_group}\011END'LTM1 libtmux.format/v1:session:session:qa:none:id,name,windows-count,attached-count,created,activity,path,grouped,group-name qa $0 work 1 0 1794634589 1794634589 /tmp/marked 0 END -
list-windows -a -F 'LTM1\011libtmux.format/v1:window-link:window-link:qa:none:session-id,window-id,index,active,last,activity,bell,silence\011qa\011#{session_id}\011#{window_id}\011#{window_index}\011#{window_active}\011#{window_last_flag}\011#{window_activity_flag}\011#{window_bell_flag}\011#{window_silence_flag}\011END'LTM1 libtmux.format/v1:window-link:window-link:qa:none:session-id,window-id,index,active,last,activity,bell,silence qa $0 @0 0 1 0 0 0 0 END -
list-windows -a -F 'LTM1\011libtmux.format/v1:window:window:qa:none:id,name,width,height,panes-count,layout,visible-layout,zoomed,modal-pane-id\011qa\011#{window_id}\011#{q/a:window_name}\011#{window_width}\011#{window_height}\011#{window_panes}\011#{q/a:window_layout}\011#{q/a:window_visible_layout}\011#{window_zoomed_flag}\011#{window_modal_pane}\011END'LTM1 libtmux.format/v1:window:window:qa:none:id,name,width,height,panes-count,layout,visible-layout,zoomed,modal-pane-id qa @0 sh 80 24 2 "{\"V\":2,\"L\":{\"t\":\"h\",\"w\":80,\"h\":24,\"x\":0,\"y\":0,\"c\":[{\"t\":\"p\",\"w\":40,\"h\":24,\"x\":0,\"y\":0,\"a\":true,\"i\":0,\"I\":\"%0\"},{\"t\":\"p\",\"w\":39,\"h\":24,\"x\":41,\"y\":0,\"i\":1,\"I\":\"%1\"}]}}" "{\"V\":2,\"L\":{\"t\":\"h\",\"w\":80,… -
list-panes -a -F 'LTM1\011libtmux.format/v1:pane:pane:qa:none:id,window-id,index,active,width,height,left,top,pid,tty,title,current-command,current-path,start-command,start-path,dead,dead-status,dead-signal,dead-time,in-mode,mode,pipe,synchronized,floating,modal,x,y,z,command-running,command-status,at-top,at-bottom,at-left,at-right\011qa\011#{pane_id}\011#{window_id}\011#{pane_index}\011#{pane_act…LTM1 libtmux.format/v1:pane:pane:qa:none:id,window-id,index,active,width,height,left,top,pid,tty,title,current-command,current-path,start-command,start-path,dead,dead-status,dead-signal,dead-time,in-mode,mode,pipe,synchronized,floating,modal,x,y,z,command-running,command-status,at-top,at-bottom,at-left,at-right qa %0 @0 0 1 40 24 0 0 2005202 /dev/pts/19 editor cat /tmp/marked "… -
list-clients -F 'LTM1\011libtmux.format/v1:client:client:qa:none:name,session-id,session-name,tty,pid,width,height,created,activity,control-mode,read-only,terminal-name,key-table\011qa\011#{q/a:client_name}\011#{session_id}\011#{q/a:client_session}\011#{q/a:client_tty}\011#{client_pid}\011#{client_width}\011#{client_height}\011#{client_created}\011#{client_activity}\011#{client_control_mode}\011#{…no output -
list-sessions -f #{session_grouped} -F 'LTM1\011libtmux.format/v1:session-group:grouped-session:qa:none:name,size,attached-count\011qa\011#{q/a:session_group}\011#{session_group_size}\011#{session_group_attached}\011END'no output -
list-buffers -F 'LTM1\011libtmux.format/v1:buffer:buffer:qa:none:name,size,created,sample\011qa\011#{q/a:buffer_name}\011#{buffer_size}\011#{buffer_created}\011#{q/a:buffer_sample}\011END'no output
Before these, the library asked tmux about itself 6 times (its version, its daemon, how a control client reads escapes).
PaneSelection.single returned Pane
{ "id": "%0", "title": "editor"}The call sent no tmux command.
Server.close returned void
nullThe call sent no tmux command.
Run this example
Linux, Raku 6.d with zef, and tmux 3.3a through 3.8 on PATH. The first command installs the published LibTmux distribution from Raku Land, so it needs network access to the zef ecosystem. The program starts its own private tmux server.
Save the program above as quickstart.raku in a new directory, along with the files below.
This complete program owns a private tmux server and closes it with LEAVE when the program exits, including on an exception.
Setup and run
zef install 'LibTmux:ver<0.0.1+>:auth<zef:libtmux>'raku quickstart.rakuExpected output
editor: 2 panesTyped API
- Typed objects instead of parsed format strings:
LibTmux::Model::Pane - Addressing by tmux ID:
LibTmux::Model::Pane.id - Filtering and queries over tmux objects:
LibTmux::Model::PaneSelection.where - Typed errors instead of exit codes and stderr:
X::LibTmux::InvalidFilter
What you get over the tmux CLI
- Connect to or start a tmux server:
LibTmux::Server.start - Traversal from a server down to its panes:
LibTmux::Server.snapshot - Send keys to a pane:
LibTmux::Model::PaneRef.send-keys - Watch for changes through tmux control mode:
LibTmux::Server.open-control-async - Capture pane contents:
LibTmux::Model::PaneRef.capture - Arguments passed as a list, with no shell quoting:
LibTmux::Server.command - A typed error when the running tmux lacks a feature:
X::LibTmux::Unsupported - Async operations:
LibTmux::Server.snapshot-async
LibTmux gives Raku programs typed snapshots of a tmux server, ordinary predicates over them, and explicit commands that change tmux. Reading a snapshot makes no further tmux calls; an operation through a reference does.
The LibTmux distribution from the zef:libtmux authority is published on Raku Land. Its API
is experimental and may change between 0.x releases. CI qualifies Rakudo
2026.09 on Linux with tmux 3.3a and 3.8; other platforms and compiler versions
are unverified. The library has no workspace manager or MCP server.