# LibTmux

- **Package:** LibTmux
- **Language:** Raku
- **Kind:** module
- **Source:** https://github.com/libtmux/libtmux-raku/blob/e76160bcce6e03976d49424da15b7c31afb2c68e/lib/LibTmux.rakumod#L3
- **Page:** https://libtmux.org/en/raku/latest/reference/libtmux/

# LibTmux

Typed tmux snapshots, local selections and explicit commands for Raku programs.



# Create a session and split a pane

:obj:`Server <LibTmux::Server>` starts and owns a private daemon. This program splits
the first pane and titles both; each runs ` cat `. ` LEAVE ` closes the owner on
any exit.



```raku
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";
```

It prints ` editor: 2 panes `.



- The earlier snapshot still has one pane: mutation does not change an
observation.



- ` .ref ` builds a command target. ` snapshot ` performs I/O; reading a
captured field or relationship stays local.



# Follow the captured graph

A :obj:`Snapshot <LibTmux::Model::Snapshot>` holds sessions, session groups, physical
windows, window links, panes, clients and buffer metadata.



- A physical window can appear at several indexes, even twice in one
session. Its ` WindowLink ` owns the session-relative index and active state.



- ` windows ` and ` panes ` list each physical identity once; ` window-links `
keeps every placement.



- A pane's ` window ` and a window's ` panes ` resolve inside the snapshot.
Field reads, traversal and selection make no tmux calls, even after the
acquisition owner closes. Use ` .ref.inspect ` or a new snapshot for fresh data.



- References carry daemon incarnation evidence, so a replacement daemon that
reuses an ID does not inherit an old pane reference.



# Query captured values

Combine validated query data with native Raku predicates and transformations:



```raku
use v6.d;
use LibTmux;

sub MAIN(Str:D :$tmux = 'tmux') {
    my $server = Server.start(:binary($tmux), :name<work>,
        :width(160), :height(40), :shell-command('exec cat'));
    LEAVE $server.close if $server.defined;

    my $editor = $server.snapshot.panes.single.ref;
    my $tests = $editor.split(:horizontal, :shell-command('exec cat'));
    my $logs = $tests.split(:shell-command('exec cat'));
    $editor.set-title('editor');
    $tests.set-title('tests');
    $logs.set-title('logs');

    my $snapshot = $server.snapshot;
    my @wide := $snapshot.panes.where(%(width => %(gte => 60)));
    my $selected = @wide.filter(*.title eq 'editor').single;
    say "Wide pane: {$selected.title}";

    my @others = $snapshot.panes.grep(*.id ne $selected.id).map(*.title).sort;
    say 'Other panes: ' ~ @others.join(', ');
}
```

It prints ` Wide pane: editor ` and
` Other panes: logs, tests `.



- Bind with ` := ` to keep a typed selection. Array assignment copies
ordinary Raku values; scalar assignment itemizes the collection.



- ` where ` validates fields and operators even on an empty selection.



- ` filter ` runs its callback eagerly and returns the same selection type.
A ` Failure ` returned directly from the callback throws its wrapped exception
before Boolean coercion.



- ` grep ` (including ` :k `, ` :kv ` and ` :p `) returns a native ` Seq `.
` map `, ` sort `, ` first ` and junctions keep plain Raku behavior; mapping panes
to titles gives strings, not a selection.



- ` single ` wants exactly one member and ` single-or-nil ` allows none.
Several matches throw :obj:`Cardinality <X::LibTmux::Cardinality>`. ` one ` stays a
junction.

Compile reusable criteria with :obj:`Filter <LibTmux::Query::Filter>`:



- ` ACCEPTS ` permits native matching; ` to-json ` and ` from-json ` keep the
bounded portable data grammar.



- Closures, regexes and junctions stay local Raku matchers. Data equality
follows field types, not generic smartmatch.



- Relations use ` some `, ` every `, ` none `, ` is ` and ` is-not `. An unloaded
relation raises :obj:`MissingData <X::LibTmux::MissingData>` instead of acting like an
empty one.



# Choose where a query runs

` query-panes ` takes ` where ` and a ` pushdown ` policy:



- ` auto ` has tmux apply the restrictions it can match exactly and checks
the rest locally.



- ` never ` evaluates locally after acquisition.



- ` require ` refuses a query that cannot run exactly in tmux.

` explain-panes ` describes projections, scope, pushed conditions and residual
work. Arbitrary callbacks are never translated to tmux.



```raku
use v6.d;
use LibTmux;

sub MAIN(Str:D :$tmux = 'tmux') {
    my $server = Server.start(:binary($tmux), :name<work>, :shell-command('exec cat'));
    LEAVE $server.close if $server.defined;
    $server.snapshot.panes.single.ref.set-title('editor');

    my %wide = width => %(gte => 60);
    my %titled = AND => [%wide, %(title => %(contains => 'edit'))];

    say 'width: fully pushed = ' ~ $server.explain-panes(:where(%wide)).fully-pushed;
    my $plan = $server.explain-panes(:where(%titled));
    say 'width and title: fully pushed = ' ~ $plan.fully-pushed;
    say 'tmux filter: ' ~ $plan.source-filter;

    say 'Matches: ' ~ $server.query-panes(:where(%titled)).map(*.title).join(', ');
    try {
        $server.query-panes(:where(%titled), :pushdown<require>);
        CATCH { when X::LibTmux::Unsupported { say 'require: refused, title stays local' } }
    }
}
```

It prints whether each query is fully pushed to tmux, the filter tmux gets, the
match, and the refusal.



# Choose an operation from capabilities

Capabilities describe the daemon, not the client executable. Ask ` supports `
before offering a newer operation, or call ` require ` for a typed refusal with
its reason. This program creates an empty ` status ` pane, floating when
supported and tiled otherwise:



```raku
use v6.d;
use LibTmux;

sub MAIN(Str:D :$tmux = 'tmux') {
    my $server = Server.start(:binary($tmux), :name<work>,
        :width(120), :height(36), :shell-command('exec cat'));
    LEAVE $server.close if $server.defined;

    my $snapshot = $server.snapshot;
    my $main = $snapshot.panes.single.ref;
    my $floating = $snapshot.capabilities.supports('pane-floating');
    my $auxiliary = $floating
        ?? $main.new-floating(:width(40), :height(12), :x(2), :y(2), :empty)
        !! $main.split(:horizontal, :empty);
    $auxiliary.set-title('status');

    my $after = $server.snapshot;
    die 'The auxiliary pane was not captured' unless $after.panes.elems == 2;
    say 'Status pane: ' ~ ($floating ?? 'floating' !! 'tiled');
    say $after.panes.where(%(id => $auxiliary.id)).single.title;
}
```

It prints the placement, then ` status `.



- An empty pane can have no child PID. Its ` pid ` is then null, not a made-up
integer; ` state('pid') ` tells known null from a field that was not acquired.



- Unknown tmux releases are refused. Field, codec, layout and command
decisions come from one packaged catalogue.



# React to terminal output

An ` -async ` method returns a ` Promise ` of the same result. A
:obj:`ControlConnection <LibTmux::Control::Connection>` owns one persistent client;
its event ` Supply ` is shared by subscribers. Native ` react ` and ` whenever `
consume output without polling:



```raku
use v6.d;
use LibTmux;
use LibTmux::Control;

sub MAIN(Str:D :$tmux = 'tmux') {
    my $server = Server.start(:binary($tmux), :name<output>, :shell-command('exec cat'));
    LEAVE $server.close if $server.defined;
    my $snapshot = await $server.snapshot-async;
    my $pane = $snapshot.panes.single.ref;
    my $connection = await $server.open-control-async(:session-id($snapshot.sessions.single.id));
    LEAVE $connection.close if $connection.defined;

    my $message = 'hello from Raku';
    my $received = '';
    react {
        whenever $connection.events.grep(PaneOutput) -> $event {
            if $event.pane-id eq $pane.id {
                $received ~= $event.data.decode('utf8-c8');
                die 'Pane output exceeded its bound' if $received.chars > 65536;
                done if $received.contains($message);
            }
        }
        whenever start { $pane.send-text($message); $pane.send-keys('Enter') } { }
        whenever Promise.in(2) { die 'Pane output timed out' }
    }

    die 'The captured screen did not contain the message'
        unless $pane.capture.decode('utf8').contains($message);
    say $message;
    say 'Screen capture confirmed';
}
```

It prints ` hello from Raku ` and ` Screen capture confirmed `.



- Subscribe before sending input; the deadline bounds the wait.



- ` send-text ` never appends Enter.



- Output stays bytes until you pick a decoder, so fragmented or invalid
UTF-8 survives. Unknown notification types keep their raw form.

Close each connection:



- Cancellation stops and joins owned client work. It does not undo commands
the daemon already ran.



- After reconnecting, take a fresh observation; events do not add up to a
complete current graph.



- :obj:`Attachment <LibTmux::Attachment>` is a separate terminal client owner.



# Recover from partial completion

:obj:`Batch <LibTmux::Batch::Batch>` reports each operation's completion scope and
outcome. The
[batch recovery program](https://github.com/libtmux/libtmux-raku/blob/master/examples/batch-recovery.raku)
shows ` Complete `, ` Failed ` and ` Skipped `, then removes the first effect
itself. Acknowledgement alone does not prove an application effect, and a failed
batch is not rolled back.

The
[session workflow program](https://github.com/libtmux/libtmux-raku/blob/master/examples/session-workflow.raku)
combines creation, control output, capture, local filtering and an explained
source query.



# Ownership and operational errors



- ` Server.start ` owns its private daemon. ` Server.new ` configures a
borrowed endpoint; closing that facade leaves the daemon running. Close both.



- A connection or terminal attachment has its own lifetime; close it too.



- An observation keeps values and the captured graph, not a live
acquisition process.



- Commands pass an argument vector. Literal pane text, key presses, FORMAT
expressions, tmux command templates and deliberate shell commands have separate
entry points.



- Errors keep typed context and captured bytes. A missing optional lookup
never disguises a failed command.



- Fields keep known null, false, zero, empty text, unsupported, unavailable
and unloaded data as distinct states.

## Members

### Common operations

- [`Server`](/en/raku/latest/reference/libtmux-server/) (class)

### Other members

- [`Attachment`](/en/raku/latest/reference/libtmux-attachment/) (class)
- [`Batch`](/en/raku/latest/reference/libtmux-batch/) (module)
- [`Capabilities`](/en/raku/latest/reference/libtmux-capabilities/) (class)
- [`Catalogue`](/en/raku/latest/reference/libtmux-catalogue/) (module): Packaged, source-qualified fields, relationships, options and release profiles.
- [`Client`](/en/raku/latest/reference/libtmux-client/) (module)
- [`Command`](/en/raku/latest/reference/libtmux-command/) (module)
- [`Configuration`](/en/raku/latest/reference/libtmux-configuration/) (module)
- [`Control`](/en/raku/latest/reference/libtmux-control/) (module)
- [`Engine`](/en/raku/latest/reference/libtmux-engine/) (module)
- [`Exceptions`](/en/raku/latest/reference/libtmux-exceptions/) (module): Typed acquisition, decoding, query, ownership and command failures.
- [`Format`](/en/raku/latest/reference/libtmux-format/) (module)
- [`Layout`](/en/raku/latest/reference/libtmux-layout/) (module)
- [`Model`](/en/raku/latest/reference/libtmux-model/) (module)
- [`Query`](/en/raku/latest/reference/libtmux-query/) (module)
- [`Selection`](/en/raku/latest/reference/libtmux-selection/) (module)
- [`Transport`](/en/raku/latest/reference/libtmux-transport/) (module)
- [`Types`](/en/raku/latest/reference/libtmux-types/) (module)
