Core LibraryunreleasedGuides

Choose documentation 2

latest

Current version

latest
English

Unreleased libtmux has not been released. Its API may change during development. This site's structure and URLs may change too.

Getting started

libtmux-lisp is a native Common Lisp tmux client library. Acquire a CLOS object graph once, process its panes with ordinary sequences, and make mutations through an explicit client.

Implemented: captured sessions, physical windows, window links and panes; bounded subprocess transport; cancellation; named operations, byte buffers, validated native queries, eager QuerySets, optional regex and bounded JSON; bounded declarative workspaces with pure plans, explicit apply and an explicit-endpoint CLI; decoded configuration reads, printer inspection and typed option, hook and environment mutations for the exact reviewed tmux catalogs through 3.8; explicit entity and server termination with retained outcome evidence; owned daemon scopes; optional queued control commands, pane-output streams and explicit reconnection on tmux 3.8+. In development: broader event qualification and source filtering. MCP is intentionally out of scope. The 0.1.0 systems are unreleased.

Install from source

Use Linux x86-64 (including WSL), kernel 6.9+, Bash and a native tmux binary. The supported tmux range is 3.3a through 3.8, including intermediate releases. Plain tmux 3.3 is rejected. The pinned toolchain is SBCL 2.6.10, ASDF/UIOP 3.3.7 and Qlot 1.8.4. The process requirements also apply to WSL; its distribution name alone does not establish kernel capabilities.

Core and query loading require ASDF/UIOP only. Mise reproduces the contributor toolchain; Qlot installs development and optional dependencies. ASDF is the Common Lisp build system.

Terminal window
$ git clone https://github.com/libtmux/libtmux-lisp.git && cd libtmux-lisp

In an existing SBCL application with ASDF loaded, register this checkout from its root:

(asdf:load-asd (truename "libtmux.asd"))

Load core and its local query API:

(asdf:load-system "libtmux/query")

To reproduce the pinned contributor environment and run all optional examples, use mise 2026.11.3+. Review mise.toml, then trust the checkout:

Terminal window
$ mise trust
Terminal window
$ mise install

Install the locked dependencies. This is the network step:

Terminal window
$ mise run setup

Run the complete workspace example. It creates a private tmux socket, checks the results and removes its daemon; it never addresses your default server.

Terminal window
$ mise run examples

Success includes EXAMPLES PASS: graph, links, native, compiled, relation, regex and JSON selection, declarative workspace, false/presence, ownership, refresh, bytes, capture, close, terminate. Core and query loading need ASDF/UIOP only; optional regex/JSON use the locked CL-PPCRE/Jzon systems. The example runner also needs Python 3. No Java runtime is involved.

Choose an ASDF system

All systems share this source distribution, version and changelog. Load only the systems your application uses. Each system has its own README:

SystemUse it forAdditional dependencies
libtmuxCaptures, CLOS objects and explicit tmux operationsASDF/UIOP
libtmux/queryNative predicates, relations and eager QuerySetsCore
libtmux/regexCL-PPCRE search and full-match filtersQuery, CL-PPCRE
libtmux/jsonBounded query document interchangeQuery, Jzon
libtmux/controlQueued requests, output streams and reconnection on tmux 3.8+Core
libtmux/workspaceNative declarations, topology previews and explicit applyCore, Jzon
libtmux/workspace/cliWorkspace validation, previews and explicit-endpoint applyWorkspace

These directories provide system documentation. ASDF definitions remain in libtmux.asd; source stays in src/. Secondary systems are optional parts of one library release.

Start an isolated daemon

This complete function starts an empty-config daemon, creates one session and returns pane data after closing its client and daemon. The runnable program includes loading and assertions.

(defun inspect-owned-workspace (&key (program "tmux"))
"Start an isolated daemon; return owned pane data after closing both resources."
(libtmux:with-daemon (daemon :program program)
(libtmux:with-client (client :program (libtmux:daemon-program daemon)
:socket-path (libtmux:daemon-socket-path daemon))
(let ((outcome (libtmux:new-session client :name "demo" :command '("/bin/cat"))))
(when (libtmux:outcome-observation-error outcome)
(error (libtmux:outcome-observation-error outcome))))
(map 'list #'libtmux:entity-data
(libtmux:panes (libtmux:fetch-snapshot client))))))

Run it from the checkout with the pinned toolchain:

Terminal window
$ mise exec -- tools/lisp.sh examples/managed-daemon.lisp

It prints MANAGED DAEMON PASS and a pane record. Read daemon scopes for environment, readiness and cleanup contracts. Use an ordinary client alone when connecting to an existing server.

Inspect a workspace

The REPL tutorial loads the example package, whose local nickname tm refers to libtmux. This complete function returns ordinary owned data after closing its client:

(defun inspect-workspace (socket-path &key (program "tmux"))
"Acquire one graph from SOCKET-PATH; return owned session/window/pane data."
(tm:with-client (client :program program :socket-path socket-path)
(let ((snapshot (tm:fetch-snapshot client)))
(loop for session across (tm:sessions snapshot)
collect
(list :session (tm:name session)
:windows
(loop for link across (tm:window-links session)
for window = (tm:window link)
collect (list :index (tm:index link)
:name (tm:name window)
:panes (map 'list #'tm:entity-data
(tm:panes window)))))))))

libtmux:fetch-snapshot contacts tmux. Traversal and projection inside the loops are local. Returned strings and sequence containers belong to the caller. Acquisition spans an interval; it is not a transaction over a changing daemon.

The walkthrough also creates two sessions, splits a pane and links one physical window into both sessions:

graph LR
work[Session: work] --> a[Window link: index 0]
work --> b[Window link: logs]
review[Session: review] --> c[Window link: index 7]
review --> d[Window link: notes]
a --> editor[Physical window: editor]
c --> editor
editor --> p1[Pane 1]
editor --> p2[Pane 2]
b --> logs[Physical window: logs]
d --> notes[Physical window: notes]

Select a window link when session/index context matters. Rename the physical window to affect every link. Refresh returns a new observation; earlier captures retain their values. See identity and ownership.

Process the retained capture with ordinary Lisp. This function uses only local accessors and returns a list of owned pane data:

(defun wide-active-panes (snapshot)
"Select captured active panes at least 40 cells wide; no tmux commands."
(loop for pane across (tm:panes snapshot)
when (and (tm:active-p pane) (>= (tm:width pane) 40))
collect (tm:entity-data pane)))

Compile a local query

The tutorial’s example package gives libtmux and libtmux.query the local nicknames tm and q. This checked function compiles once, filters a captured vector locally, and returns ordinary owned data:

(defparameter *wide-active-filter*
(q:compile-filter
:pane
'(:and (:= :pane/active t)
(:>= :pane/width 40))))
(defun wide-active-pane-data (snapshot)
"Return owned data for captured active panes at least 40 cells wide; no I/O."
(map 'vector #'tm:entity-data
(q:matching *wide-active-filter* (tm:panes snapshot))))

matching returns a fresh simple vector and preserves order and duplicates. The compiler owns mutable operands, rejects cycles and mistyped fields, and never evaluates query data. Scalar filtering performs no tmux calls. Read the query guide for presence, limits, inspection and strict cardinality.

Relations traverse the same captured graph. This checked query follows session links to physical windows and panes, then uses the eager local QuerySet:

(defparameter *active-session-filter*
(q:compile-filter
:session
'(:any :session/window-links
(:is :link/window
(:any :window/panes
(:= :pane/active t))))))
(defun active-session-data (snapshot)
"Return owned data for sessions with an active pane; no I/O."
(map 'vector #'tm:entity-data
(q:items
(q:where (q:from (tm:sessions snapshot) :model :session)
*active-session-filter*))))

Declare and apply a workspace

The optional libtmux/workspace ASDF system reads a closed, bounded JSON document, plans locally, and applies only through an explicit client. The shipped workspace.json creates two windows and three panes from argv arrays. This tested function returns both the inspectable plan and retained application evidence:

(defun apply-workspace-document (client pathname)
"Plan and apply one bounded workspace document through explicit CLIENT.
Return the preview steps and application result as two values. Application
shares a 30-second budget; file loading and the preview precede that budget."
(let* ((workspace (ws:read-workspace pathname))
(snapshot (tm:fetch-snapshot client))
(plan (ws:plan-workspace workspace snapshot)))
(values (ws:plan-steps plan)
(ws:apply-workspace client workspace :total-timeout 30))))

An exact second apply returns an empty plan and no command outcomes. A conflict fails before dispatch. A partial failure retains the failed step and every core outcome; no mutation is retried or rolled back. The CLI’s validate and plan modes need no daemon, while apply requires --socket-path. Read the workspace guide and generated workspace API.

Add regex and a versioned filter document

Optional systems keep core/query dependency-free while providing a compiled CL-PPCRE scanner and a bounded native JSON envelope. These checked definitions match the demo’s cat panes, serialize the filter, read it with explicit regex permission, and evaluate the restored predicate against the same capture:

(defparameter *cat-command-filter*
(q:compile-filter
:pane
'(:regex-full-match :pane/current-command
(:cl-ppcre "CAT" :case-insensitive-mode t))))
(defun cat-pane-data (snapshot)
"Return owned data for captured panes whose command is exactly cat; no I/O."
(map 'vector #'tm:entity-data
(q:matching *cat-command-filter* (tm:panes snapshot))))
(defun round-trip-filter-document (snapshot)
"Round-trip the checked regex filter and apply it to one retained snapshot."
(let* ((document (j:write-filter *cat-command-filter*))
(restored (j:read-filter document :allow-regex t)))
(values document
(map 'vector #'tm:entity-data
(q:matching restored (tm:panes snapshot))))))

The JSON decoder rejects duplicates, unknown names, null operands and exhausted input/depth/node/string/membership/numeric bounds. It never interns external names. See optional query interchange.

Find the right guide

TaskStart here
Load the library; inspect and build a workspaceREPL tutorial
Start and stop a private empty-config daemonDaemon scopes
Filter with loop, handle false/absence, export plistsLocal data recipes
Compile typed filters, traverse relations and compose QuerySetsNative queries · Query API
Add CL-PPCRE or exchange bounded filter documentsQuery interchange · JSON API
Validate, plan or explicitly apply a declarative workspaceWorkspaces · Workspace API
Rename, resize, move, send input, capture, use buffers or terminate explicit targetsOperation contracts
Read typed options, hooks and environmentDecoded configuration
Inspect native option, hook and environment outputConfiguration inspection
Look up signatures, multiple values and conditionsPublic API · Captured fields
Understand deadlines, cancellation and cleanupTransport
Reconnect without replaying commandsFresh control observations
Evaluate async, streaming and performance supportCapabilities and limits
Rerun FiveAM tests at the REPL or inspect CI coverageTesting · Verification
Navigate all documentationGuide index

Core subprocess calls block. Each client accepts one external operation at a time; concurrent reuse signals client-busy. Optional control submit-command queues a request without awaiting its reply; use await-request or next-event for bounded waits. Closing a client releases its resources and leaves tmux running. kill-server is a separate explicit operation that proves the daemon accepted during its preflight exited. Daemon scopes separately own a new private daemon; the tutorial also documents explicit tmux setup and cleanup.

Named mutations preserve dispatch evidence and separate successful changes from follow-up observation failures. Inspect command-outcome or the condition’s error-outcome; never retry an uncertain mutation blindly. See outcome handling.

The examples are shipped source, executed on isolated servers and checked for excerpt drift. All tmux releases from 3.3a through 3.8 are supported. CI uses the latest stable SBCL pin and tests only the range’s endpoints, 3.3a and 3.8, to reduce build time. No benchmark result is claimed.

For changes, read Contributing and Writing. Licensed under MIT.

Branding assets include the logo, icons, and social cards.

Esc

Type to search.

Reference previewOpen reference