# libtmux for Common Lisp > The Common Lisp library (libtmux). These pages include its guides, tested examples and API documentation. - [Common Lisp API reference](https://libtmux.org/en/lisp/latest/reference/): every public symbol, generated from the source. Hosted on libtmux.org. --- # API glossary Source: https://libtmux.org/en/lisp/latest/reference/glossary/ > Kinds, modifiers and package labels in the Common Lisp API reference. ## Symbol kinds Kinds identify declarations in the Common Lisp API reference. ### module A Common Lisp package that names and exports symbols. The package badge describes a namespace. ASDF systems are separate loadable units and may define one or more packages. References: [Common Lisp packages](https://www.lispworks.com/documentation/HyperSpec/Body/11_.htm). ### class A CLOS class whose instances participate in generic-function dispatch. The public readers and generic functions are the supported interface; private slots are not an additional API. References: [Common Lisp classes](https://www.lispworks.com/documentation/HyperSpec/Body/m_defcla.htm). ### function A Common Lisp function binding called with its native lambda list. A function can return multiple values. The displayed lambda list identifies required, optional and keyword arguments. References: [Common Lisp functions](https://www.lispworks.com/documentation/HyperSpec/Body/m_defun.htm). ### exception A Common Lisp condition type that handlers can inspect and, where documented, recover from through restarts. Signalling a condition and transferring control are distinct operations; read the function contract for the condition and restart protocol. References: [Common Lisp conditions](https://www.lispworks.com/documentation/HyperSpec/Body/09_.htm). ## Modifiers Modifiers add information about calling, implementing, or accessing a declaration. Their precise effect follows the language and the signature. ### opaque A public CLOS class whose representation is intentionally outside the supported API. Construct, inspect and compare instances through exported functions rather than depending on private slot names. References: [Common Lisp classes](https://www.lispworks.com/documentation/HyperSpec/Body/m_defcla.htm). ### macro A Common Lisp macro that transforms source forms before their surrounding code runs. The displayed lambda list describes source syntax, not ordinary evaluated function arguments. References: [Common Lisp macros](https://www.lispworks.com/documentation/HyperSpec/Body/m_defmac.htm). ## Package context Package labels identify where an API is supplied. ### package The ASDF system supplying this API, distinct from the Common Lisp package that qualifies its symbols. References: [ASDF systems](https://asdf.common-lisp.dev/asdf.html#Defining-systems-with-defsystem). --- # tmux workspace manager, built in Common Lisp, is source-only Source: https://libtmux.org/en/lisp/latest/workspace/ > Validate, plan, and explicitly apply bounded JSON workspaces with the native Common Lisp engine. **Workspace Manager concept example** A working example of the core library. It can change without notice. Build `mise` from the source revision in the installation guide. Follow the workspace guide for its configuration format. The Common Lisp workspace manager is implemented and tested from the same source checkout as the core library. It is not published as a separate system or executable. Build the pinned toolchain and run it through `mise`: ```console $ mise exec -- tools/lisp.sh tools/workspace-cli.lisp -h ``` The command validates a bounded JSON document before it contacts tmux. Planning is local. Applying a plan uses the native client and returns a JSON summary. An application failure reports the retained step outcomes and dispatch status; it does not claim transactional rollback. Read the [source installation and command guide](https://libtmux.org/en/lisp/latest/workspace/source-guide/) for the file format, validation limits, planning output, apply behavior, and exit status. Use the [core guides](https://libtmux.org/en/lisp/latest/guides/) for snapshot ownership, explicit operations, and private tmux fixtures. --- # Workspace API reference Source: https://libtmux.org/en/lisp/latest/workspace/reference/ > Native Common Lisp functions, conditions, plans, and results for bounded JSON workspaces. **Workspace Manager concept example** A working example of the core library. It can change without notice. Load `libtmux/workspace` to validate and plan workspace data, then apply it through an explicit client. The `libtmux/workspace/cli` system provides the command entry point. Both systems use the native core library. The [workspace guide](https://libtmux.org/en/lisp/latest/workspace/source-guide/) covers configuration, cleanup, conflicts, and partial outcomes. The declarations below come from the same source revision as the guide. ## API declarations - [libtmux.workspace](https://libtmux.org/en/lisp/latest/workspace/reference/libtmux-workspace/) - [libtmux.workspace.cli](https://libtmux.org/en/lisp/latest/workspace/reference/libtmux-workspace-cli/) --- # API glossary Source: https://libtmux.org/en/lisp/latest/workspace/reference/glossary/ > Kinds, modifiers and package labels in the Common Lisp API reference. ## Symbol kinds Kinds identify declarations in the Common Lisp API reference. ### module A Common Lisp package that names and exports symbols. The package badge describes a namespace. ASDF systems are separate loadable units and may define one or more packages. References: [Common Lisp packages](https://www.lispworks.com/documentation/HyperSpec/Body/11_.htm). ### class A CLOS class whose instances participate in generic-function dispatch. The public readers and generic functions are the supported interface; private slots are not an additional API. References: [Common Lisp classes](https://www.lispworks.com/documentation/HyperSpec/Body/m_defcla.htm). ### function A Common Lisp function binding called with its native lambda list. A function can return multiple values. The displayed lambda list identifies required, optional and keyword arguments. References: [Common Lisp functions](https://www.lispworks.com/documentation/HyperSpec/Body/m_defun.htm). ### exception A Common Lisp condition type that handlers can inspect and, where documented, recover from through restarts. Signalling a condition and transferring control are distinct operations; read the function contract for the condition and restart protocol. References: [Common Lisp conditions](https://www.lispworks.com/documentation/HyperSpec/Body/09_.htm). ## Modifiers Modifiers add information about calling, implementing, or accessing a declaration. Their precise effect follows the language and the signature. ### opaque A public CLOS class whose representation is intentionally outside the supported API. Construct, inspect and compare instances through exported functions rather than depending on private slot names. References: [Common Lisp classes](https://www.lispworks.com/documentation/HyperSpec/Body/m_defcla.htm). ### macro A Common Lisp macro that transforms source forms before their surrounding code runs. The displayed lambda list describes source syntax, not ordinary evaluated function arguments. References: [Common Lisp macros](https://www.lispworks.com/documentation/HyperSpec/Body/m_defmac.htm). ## Package context Package labels identify where an API is supplied. ### package The ASDF system supplying this API, distinct from the Common Lisp package that qualifies its symbols. References: [ASDF systems](https://asdf.common-lisp.dev/asdf.html#Defining-systems-with-defsystem). --- # Captures and identity Source: https://libtmux.org/en/lisp/latest/concepts/captures/ > Captures and identity: libtmux documentation. [`fetch-snapshot`]() reads the selected daemon and returns a native CLOS graph. Acquisition checks the [supported command-alias configuration](https://libtmux.org/en/lisp/latest/guides/operations/#command-aliases-and-daemon-configuration) before listing state. [`sessions`](), [`windows`](), [`panes`](), [`window-links`]() and [`clients`]() then return fresh vectors from that observation. These getters, field access, printing, [`same-entity-p`]() and [`entity-data`]() perform no tmux calls. Captured objects remain usable after the client closes. [`fetch-sessions`](), [`fetch-windows`](), [`fetch-panes`]() and [`fetch-clients`]() acquire a complete graph and return the corresponding vector. Repeated fetches read again. Retain one snapshot when several operations should inspect the same observation. Filtering a retained vector will use ordinary sequence functions; the separate [`libtmux/query`](https://libtmux.org/en/lisp/latest/guides/queries/) system also provides validated local filters, relation quantifiers and eager QuerySets. ## Entities, links and order A snapshot contains each physical session, window and pane once. A [`window-link`]() records a session, index and expected physical window. Linking a window twice into one session creates two links; linking it elsewhere creates another. Session-relative active-window state belongs to the link. | Accessor | Captured result | | --- | --- | | `sessions snapshot` | Physical sessions in tmux's observed order | | `windows snapshot`, `panes snapshot` | Physical entities in first-occurrence order | | `window-links snapshot` | Every contextual link in listing order | | `window-links session` | That session's links in observed index order | | `windows session` | One window per link; repeated physical windows remain repeated | | `panes window` | Physical panes in captured pane order | | `window-links window` | Every link to that physical window | | `window pane`, `window link` | The captured physical window | | `session link`, `session attached-client` | The captured session | | `clients snapshot`, `clients session` | Attached-client rows, preserving order and multiplicity | An attached tmux client is an [`attached-client`]() observation. It is distinct from the library's connection [`client`](). Repeated target names are retained; a name does not establish identity. ## Values and ownership [`field-value`]() returns value and presence as two values. Present false is `nil, t`; an absent optional field is `nil, nil`. Empty names and pane titles are present strings. Current command/path may be absent when tmux reports an empty value. An empty or zero pane PID is absent, while the captured dead flag retains the daemon version's reported meaning. Never infer one from the other. The convenience readers [`name`](), [`active-p`](), [`width`](), [`height`](), [`index`](), [`current-command`](), [`current-path`](), [`title`]() and [`dead-p`]() follow the same presence contract. Paths remain text from tmux; they are not normalized as local paths. Unknown fields and unloaded relations signal [`field-unavailable`](). Capture records preserve percent signs, dollar signs and non-NUL ASCII control characters through an encoding applied before tmux prints each field. This avoids the visual escaping used by older tmux releases, including the extra backslash before `$HOME` in tmux 3.4. Literal backslashes and valid Unicode remain unchanged. The decoder rejects malformed escapes and invalid UTF-8. This preserves the field text tmux supplies; it cannot recover filesystem bytes that tmux has already converted to display text. A captured current path is therefore not a filesystem identity or a byte-exact filesystem argument. Every public mutable result is owned by the caller: strings, relation vectors, provenance and projection containers are copied. Entity observations can be shared between result vectors. Changing a returned vector or string does not change the capture. Deliberate private-slot mutation is unsupported. [`entity-data`]() returns a finite plist with captured fields, provenance and relationship identifiers. It omits absent optional fields and retains present false values. It contains no operational handles or recursive object graph. Use [`id`]() to inspect identity: physical IDs are strings; link IDs are `(session-id index expected-window-id)`; attached-client row IDs have meaning only within that observation. ## Identity and refresh [`same-entity-p`]() compares model, connection context, generation and captured ID. Two clients have distinct contexts even when connected to the same daemon. Window-link identity retains its expected window and index; the index is a mutable locator. Attached-client identity is scoped to one snapshot. [`refresh`]() takes the connection client and an existing entity or snapshot. It acquires a replacement without changing the old observation. A different server generation, missing target or changed link mapping signals [`stale-target`](). Refreshing an attached client requires a unique live target name and matching PID, creation time and tty presence; the returned object's observation identity is new. The native Linux witness includes daemon PID, daemon startup, socket inode and device, kernel process-start identity and a retained pidfd. This distinguishes server replacement even if tmux reuses its numeric IDs and startup second. The daemon version and selected executable version are recorded separately. Acquisition first verifies the selected endpoint, then pins subsequent commands to its canonical path. Every listing carries a tagged witness produced on the same server connection as its rows, including an empty listing. A changed peer invalidates the attempt. Final continuity checks detect server replacement and persistent selected-endpoint changes. This does not make graph acquisition transactional: tmux state can change between the listing commands. A successful nonempty capture uses five tmux launches: alias audit, initial witness, session listing, a batch of window-link/pane/client listings, and final witness. Each batched listing has its own connection witness, and both outer witnesses retain the full process/socket checks. A live empty daemon uses four launches. Counts exclude client opening and retries. The batch has one command outcome. A framing or witness failure retains that outcome; it does not invent an outcome for each listing. Row limits remain per listing, while stdout and stderr across all launches share the total byte cap. Hooks can change state inside a batch, so graph validation and bounded retries remain necessary. ## Bounds and failures Provenance records monotonic acquisition bounds and Common Lisp universal-time bounds, as well as tmux's Unix server-start time. Duplicate physical rows must agree. References, contextual pane order, counts and active states are checked. Contradictory captures retry at most twice, then signal [`incomplete-snapshot`](). Retries share the original deadline and byte budget. The default total timeout is 30 seconds and total output cap is 16 MiB, inherited from the client. Defaults also bound each listing to 100,000 rows, the graph to 100,000 entities, and each encoded field to 1 MiB. `:timeout`, `:output-limit`, `:row-limit`, `:entity-limit`, `:field-limit` and `:cancel-token` configure these limits on fetch operations. Decoding and graph construction check cancellation and the same deadline as subprocess work. A confirmed live daemon with zero sessions returns empty vectors. A missing daemon or failed listing signals a condition; neither becomes an empty result. Acquisition uses tmux's no-start option and never starts a daemon implicitly. The current core observes entities reachable from sessions; it does not claim an inventory of detached internal tmux objects outside that graph. --- # Concepts Source: https://libtmux.org/en/lisp/latest/concepts/ > Concepts for libtmux. Understand captured queries, object identities, and operations. - [Captures and identity](https://libtmux.org/en/lisp/latest/concepts/captures/): Read about captured objects. - [Transport and cleanup](https://libtmux.org/en/lisp/latest/concepts/transport/): Read about clients and subprocess commands. --- # Transport and cleanup Source: https://libtmux.org/en/lisp/latest/concepts/transport/ > Transport and cleanup: libtmux documentation. [`open-client`]() checks the selected tmux executable and returns a connection scope. It does not start a daemon. Choose one of `:socket-name` or `:socket-path`; omitting both selects tmux's default endpoint. `:config-file` is passed explicitly when provided. Configuration strings are copied. [`with-client`]() closes the original client on every exit. The body supports declarations and preserves multiple values. Rebinding its visible variable does not change the resource to close. Closing releases the client's processes and streams; it leaves the tmux daemon running. ## Command results [`run-command`]() takes a client and a finite list or vector of argv strings. Supply `:effect :read` or `:effect :mutation` explicitly. Arguments bypass shell parsing, but tmux's command and format grammar still applies. This escape hatch does not validate captured target identities. Named entity operations provide that validation; see the [operation contracts](https://libtmux.org/en/lisp/latest/guides/operations/). The optional `:input` is an octet vector, copied before dispatch. Standard output and diagnostic output are retained as bytes, including invalid UTF-8. [`outcome-stdout`](), [`outcome-stderr`]() and [`outcome-arguments`]() return owned copies. An external command failure signals a condition whose [`error-outcome`]() retains the result and whose [`error-cause`]() retains an underlying failure when present. [`error-client`]() identifies the client, including a partially opened client that still owns cleanup work. Retry [`close-client`]() on that object if cleanup failed. | Outcome status | Meaning | | --- | --- | | `:succeeded` | The child exited successfully and transport cleanup completed | | `:failed` | The child reported a nonzero exit status | | `:unknown` | Dispatch was possible but interruption, limits or cleanup prevented a complete result | | `:not-dispatched` | Dispatch was prevented or the launcher positively reported an exec failure | Raw [`outcome-phase`]() records `:prepared`, `:launching`, `:dispatched` or `:exited`; named operations also use `:preflight` and `:guard-rejected`. Their [receipt and outcome policy](https://libtmux.org/en/lisp/latest/guides/operations/#outcomes-and-target-checks) requires more evidence than the raw process runner. Inspect [`outcome-reason`](), [`outcome-exit-code`]() and retained diagnostics as well. A command reporting failure can already have changed tmux; these statuses do not promise rollback. The library never retries an uncertain mutation automatically. ## Bounds and concurrency The default command timeout is 30 seconds. The default combined stdout/stderr limit is 16 MiB. Override these through client defaults or per-command `:timeout` and `:output-limit`. The low-level deadline starts after validation, argument/environment copies, selected child-directory validation and executable inspection. It charges setup, launch and pipe service, but synchronous setup/launch cannot be interrupted by its later checks. The gate rechecks admission before releasing the target. This is an execution deadline, not a hard limit on caller wall time. A byte limit retains at most the configured count. Input is written while both output pipes are drained, avoiding pipe deadlock. One external operation owns a client at a time; concurrent work signals [`client-busy`]() before dispatch. Use separate clients for independent operations. [`make-cancellation-token`]() creates reusable one-way cancellation state; pass it as `:cancel-token`, then call [`cancel`]() from another thread. [`cancel`]() returns true only for the first request. An idle token owns no OS descriptors. Cancellation signals [`command-cancelled`]() from the operation and retains its dispatch evidence; it does not undo a completed mutation. Closing rejects new operations, requests cancellation and waits for the owner. Normal process stop allows 50 ms before KILL and 500 ms for stop/drain. Final cleanup can separately wait another 500 ms for child exit. Client close has a 750 ms active-owner join allowance after its initial lock/cancellation work; lock acquisition and retained-job cleanup add time. Timed condition-wait reacquisition has no separate lock timeout. A failed join signals [`cleanup-failed`]() and leaves the client closing. If child authority or cleanup cannot be completed, the client retains the resources. Correct the underlying resource problem and retry [`close-client`](). [`client-cleanup-failures`]() returns a fresh vector of retained secondary conditions. Cleanup failure after a normal scope return signals. During an existing error or nonlocal exit it is recorded without replacing that exit. Captured data remains usable locally after client closure. ## Supported process boundary The first backend targets SBCL on Linux x86-64. It requires pidfds, eventfd, pidfd process-group signalling (Linux 6.9 or newer), their libc wrappers, and Bash. [`open-client`](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/docs/api.md#open-client) checks the runtime profile before resolving the executable. The first open also probes pidfd/group signalling, eventfd and the launcher, closing the temporary descriptors before proceeding. [`unsupported-platform`](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/docs/api.md#unsupported-platform) identifies the requirement through [`error-feature`](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/docs/api.md#error-feature): `:runtime-profile`, `:pidfd`, `:pidfd-process-group`, `:eventfd` or `:launcher`. [`error-cause`](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/docs/api.md#error-cause) retains a failed probe when available. The check is cached in the running image; it is not a continuing health monitor or qualification of another implementation or platform. Non-SBCL hosts have no replacement backend. SBCL creates the child group for redirected stdin. A private FIFO exec barrier delays target execution until Lisp retains the pidfd, verifies the group and admits dispatch. It passes argv as separate positional arguments and does not evaluate caller strings as shell source. Only trusted native ELF target executables are accepted. A script or tool-manager shim error names the selected file; pass the underlying tmux binary through [`open-client`]()'s `:program` argument. Ordinary commands force `LC_ALL=C.UTF-8`, clear `TMUX` and `TMUX_PANE`, and normalize Bash startup variables used by its fixed launcher. It is not a byte-for-byte environment transfer API. Each private process job owns its selected environment and any explicit child working directory; preparing or launching it does not change the Lisp process's environment or working directory. A separate daemon owner can use that boundary without imposing the command locale on newly created panes; [managed daemon scopes](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/docs/daemons.md) keep that environment and lifecycle separate. Use explicit tmux environment operations to change a running server's values. The private descriptor poll does not run application REPL handlers. The library does not install a process-wide subreaper. The bounds cover the supervised process group. They cannot force an uninterruptible kernel syscall to return or contain a descendant that deliberately leaves that group. An unfinished launch remains owned and is reported as incomplete cleanup. See the [architecture decision](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/docs/adr/0001-native-common-lisp-engine.md#supervise-subprocesses-with-explicit-scopes-and-bounded-outcomes) for launcher alternatives and saved-image limitations. ## Persistent control implementation The optional `libtmux/control` system exposes scoped native connections, queued requests and byte events through [`libtmux.control`](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/docs/control-api.md). See the [control guide](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/docs/control.md) and its executable example. The engine reuses the existing process supervisor, one reader and serialized request writes. Requests queue without waiting; awaiting a result and receiving an event have separate timeout results. Closing cancels pending work, joins the owned worker and leaves the daemon running. The live daemon must be at least 3.8 so native notifications are deferred until a command reply ends. The 3.7 series can include pause/continue notices inside replies. The version comes from the daemon witness. Core operations still support 3.3a through 3.8. Current live control tests exercise 3.8; the 3.3a test proves rejection before a control launch. Reply bytes, pending requests and event delivery have separate finite limits. An expired queued request is never dispatched. An in-flight deadline closes the connection and retains partial replies and dispatch state. Awaiting with a shorter timeout leaves the request live. Event overflow drops the oldest events and reports a gap with event and byte counts. Completion means the native command queue progressed; application readiness needs its own evidence. The LF assembler accepts split octet chunks, preserves all bytes except the terminating LF, and returns owned line vectors. Separate limits bound each line, each feed's bytes and each returned batch's line count. Truncated EOF signals a decoding error. A resource failure, cancellation or escaping exit retires the decoder; it cannot resume after losing consumed input. The pane-output leaf decoder preserves NUL, invalid UTF-8 and all other bytes. It understands tmux's three-digit octal escapes and extended-output age and reserved fields. Encoded and decoded byte limits are separate. Codecs themselves do not own queues or workers; the persistent owner supplies those resources. The engine follows tmux's ordinary reply guards and notification conventions. It sends a separate completion command after each request so a failed command does not strand the next request. It performs no automatic mutation retry or reconnection. See the [architecture record](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/docs/adr/0001-native-common-lisp-engine.md#qualify-control-mode-and-source-filtering-before-exposing-them) for the remaining reconnect, event coverage and compatibility work. --- # tmux client library for Common Lisp Source: https://libtmux.org/en/lisp/latest/core/ > tmux client library for Common Lisp. Control tmux, navigate sessions, windows, and panes, send keys, capture pane contents Control tmux, navigate sessions, windows, and panes, send keys, capture pane contents. ## At a glance | Type | Wraps | | ---- | ----- | | [`libtmux:client`](https://libtmux.org/en/lisp/latest/reference/libtmux-client/) | A tmux server | | [`libtmux:session`](https://libtmux.org/en/lisp/latest/reference/libtmux-session-type/) | A session, ID `$n` | | [`libtmux:window`](https://libtmux.org/en/lisp/latest/reference/libtmux-window-type/) | A window in a session, ID `@n` | | [`libtmux:pane`](https://libtmux.org/en/lisp/latest/reference/libtmux-pane/) | A pane in a window, ID `%n` | ## Typed API - Typed objects instead of parsed format strings: [`libtmux:snapshot`](https://libtmux.org/en/lisp/latest/reference/libtmux-snapshot/) - Addressing by tmux ID: [`libtmux:id`](https://libtmux.org/en/lisp/latest/reference/libtmux-id/) - Filtering and queries over tmux objects: [`libtmux.query:compile-filter`](https://libtmux.org/en/lisp/latest/reference/libtmux-query-compile-filter/) - Typed errors instead of exit codes and stderr: [`libtmux:libtmux-error`](https://libtmux.org/en/lisp/latest/reference/libtmux-libtmux-error/) ## What you get over the tmux CLI - Traversal from a server down to its panes: [`libtmux:fetch-snapshot`](https://libtmux.org/en/lisp/latest/reference/libtmux-fetch-snapshot/) - Send keys to a pane: [`libtmux:send-keys`](https://libtmux.org/en/lisp/latest/reference/libtmux-send-keys/) - Capture pane contents: [`libtmux:capture-pane`](https://libtmux.org/en/lisp/latest/reference/libtmux-capture-pane/) - Arguments passed as a list, with no shell quoting: [`libtmux:run-command`](https://libtmux.org/en/lisp/latest/reference/libtmux-run-command/) - A typed error when the running tmux lacks a feature: [`libtmux:unsupported-capability`](https://libtmux.org/en/lisp/latest/reference/libtmux-unsupported-capability/) - [Guides](https://libtmux.org/en/lisp/latest/guides/): Install the source checkout, acquire snapshots, and issue explicit operations. - [Concepts](https://libtmux.org/en/lisp/latest/concepts/): Understand captured identity, transport ownership, and local query behavior. - [Examples](https://libtmux.org/en/lisp/latest/examples/): Run tested programs against an owned tmux server. - [API reference](https://libtmux.org/en/lisp/latest/reference/): Browse public packages, CLOS types, conditions, macros, and functions. --- # Examples Source: https://libtmux.org/en/lisp/latest/examples/ > Examples for libtmux. Run complete programs with their documented setup and cleanup. - [Capture pane output](https://libtmux.org/en/lisp/latest/examples/capture-pane-output/): Send a command, wait for its output, and clean up the private server. --- # Capture pane output Source: https://libtmux.org/en/lisp/latest/examples/capture-pane-output/ > Run one Common Lisp program that creates, captures, and cleans up an isolated tmux server. This standalone Common Lisp program owns its temporary directory, private tmux server, bounded client operations, and cleanup. It sends one command, waits for an explicit completion signal, and asserts that the captured screen contains the complete output line. It needs Linux, SBCL, tmux, and mise. Save this program, then follow [Setup and run](https://libtmux.org/en/lisp/latest/examples/capture-pane-output/#setup-and-run). ## Read what's on screen The pane command prints on a fresh row and signals a tmux [`wait-for`](https://libtmux.org/en/tmux/latest/manual/wait-for/) channel. The client waits for that signal with a five-second command deadline, then captures once. Matching a complete line avoids accepting the echoed command as output. ```lisp title="capture.lisp" (require :asdf) (require :sb-posix) (asdf:load-asd (truename "libtmux.asd")) (asdf:load-system "libtmux") (defun line-present-p (needle text) (with-input-from-string (stream text) (loop for line = (read-line stream nil nil) while line thereis (string= needle line)))) (let* ((directory (sb-posix:mkdtemp "/tmp/libtmux-lisp-capture-XXXXXX")) (socket (concatenate 'string directory "/tmux.sock")) (program (or (uiop:getenv "LIBTMUX_PROGRAM") "tmux")) (normal nil) (cleanup-ok t)) (unwind-protect (libtmux:with-client (client :program program :socket-path socket :config-file "/dev/null" :timeout 5) (unwind-protect (multiple-value-prog1 (progn (setf cleanup-ok nil) (libtmux:run-command client (list "new-session" "-d" "-s" "capture" "-e" "ENV=/dev/null" "-e" (concatenate 'string "CAPTURE_TMUX=" program) "-e" (concatenate 'string "CAPTURE_SOCKET=" socket) "/bin/sh") :effect :mutation) (let* ((snapshot (libtmux:fetch-snapshot client)) (pane (aref (libtmux:panes snapshot) 0))) (libtmux:send-text client pane (concatenate 'string "printf '\\nlibtmux capture ready\\n'; " "\"$CAPTURE_TMUX\" -N -S \"$CAPTURE_SOCKET\" " "wait-for -S capture-ready") :enter t) (libtmux:run-command client '("wait-for" "capture-ready") :effect :read) (multiple-value-bind (text outcome) (libtmux:capture-pane client pane :start :all) (assert (eq :succeeded (libtmux:outcome-status outcome))) (assert (line-present-p "libtmux capture ready" text)) (format t "libtmux capture ready~%")))) (setf normal t)) (when (probe-file socket) (handler-case (progn (libtmux:kill-server client) (setf cleanup-ok t)) (error (condition) (if normal (error condition) (warn "Owned server cleanup failed: ~A" condition))))))) (if cleanup-ok (uiop:delete-directory-tree (uiop:ensure-directory-pathname directory) :validate t) (warn "Retained owned fixture directory: ~A" directory)))) ``` ## Completion and ownership [`libtmux:run-command`](https://libtmux.org/en/lisp/latest/reference/libtmux-run-command/) explicitly starts the daemon on the new private socket. The high-level [`libtmux:new-session`](https://libtmux.org/en/lisp/latest/reference/libtmux-new-session/) currently requires an existing daemon, so the example uses the documented argv escape hatch for this bootstrap step. [`libtmux:fetch-snapshot`](https://libtmux.org/en/lisp/latest/reference/libtmux-fetch-snapshot/) acquires the graph. [`libtmux:send-text`](https://libtmux.org/en/lisp/latest/reference/libtmux-send-text/) acknowledges input dispatch; the explicit [`wait-for`](https://libtmux.org/en/tmux/latest/manual/wait-for/) separates shell command completion from [`libtmux:capture-pane`](https://libtmux.org/en/lisp/latest/reference/libtmux-capture-pane/). [`libtmux:with-client`](https://libtmux.org/en/lisp/latest/reference/libtmux-with-client/) closes the library client. The surrounding `unwind-protect` also calls [`libtmux:kill-server`](https://libtmux.org/en/lisp/latest/reference/libtmux-kill-server/) because this program created the private server. Cleanup runs on normal return and Lisp conditions. If server termination fails, the program retains the directory for inspection and preserves an existing error. Use this termination pattern only for a server your program owns. ## Setup and run Use an empty directory and save the displayed program under its shown name. Clone the exact library revision used to verify it: ```console $ git clone https://github.com/libtmux/libtmux-lisp libtmux-source ``` ```console $ git -C libtmux-source checkout 0f9da93add247569a184bbda7d1dd1ca986c2e0f ``` Review [`mise.toml`](), install the pinned toolchain, and run the program from the checkout. The explicit ASDF load selects the pinned build system rather than SBCL's bundled version. Core loading needs no external Lisp dependencies. ```console $ cd libtmux-source && mise trust && mise install && mise exec -- sbcl \ --noinform \ --no-sysinit \ --no-userinit \ --disable-debugger \ --load "$(mise where http:asdf-lisp)/asdf.lisp" \ --script ../capture.lisp ``` Success prints `libtmux capture ready` once. Set `LIBTMUX_PROGRAM` to select another native tmux executable; it defaults to `tmux` on `PATH`. ## Where this comes from The displayed program ran against the pinned library on Linux with SBCL 2.6.10, ASDF 3.3.7, and isolated tmux 3.3a and 3.8 servers. Its output assertion and directory cleanup passed. The shared-docs checks preserve the displayed source bytes and validate API links. --- # Getting started Source: https://libtmux.org/en/lisp/latest/guides/getting-started/ > Getting started: libtmux documentation. 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](https://libtmux.org/en/lisp/latest/workspace/source-guide/) with pure plans, explicit apply and an explicit-endpoint CLI; [decoded configuration reads](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/docs/configuration.md), [printer inspection](https://libtmux.org/en/lisp/latest/guides/operations/#configuration-inspection) and [typed option, hook and environment mutations](https://libtmux.org/en/lisp/latest/guides/operations/#configuration-mutations) for the exact reviewed tmux catalogs through 3.8; explicit entity and server termination with retained outcome evidence; [owned daemon scopes](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/docs/daemons.md); optional [queued control commands, pane-output streams and explicit reconnection](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/docs/control.md) 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](https://libtmux.org/en/lisp/latest/concepts/transport/#supported-process-boundary) 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. ```console $ 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: ```lisp (asdf:load-asd (truename "libtmux.asd")) ``` Load core and its local query API: ```lisp (asdf:load-system "libtmux/query") ``` To reproduce the pinned contributor environment and run all optional examples, use [mise 2026.11.3+](https://mise.jdx.dev/getting-started.html). Review [mise.toml](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/mise.toml), then trust the checkout: ```console $ mise trust ``` ```console $ mise install ``` Install the locked dependencies. This is the network step: ```console $ 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. ```console $ 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](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/CHANGELOG.md). Load only the systems your application uses. Each system has its own README: | System | Use it for | Additional dependencies | | --- | --- | --- | | [`libtmux`]() | Captures, CLOS objects and explicit tmux operations | ASDF/UIOP | | [`libtmux/query`](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/query/README.md) | Native predicates, relations and eager QuerySets | Core | | [`libtmux/regex`](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/regex/README.md) | CL-PPCRE search and full-match filters | Query, CL-PPCRE | | [`libtmux/json`](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/json/README.md) | Bounded query document interchange | Query, Jzon | | [`libtmux/control`](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/control/README.md) | Queued requests, output streams and reconnection on tmux 3.8+ | Core | | [`libtmux/workspace`](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/workspace/README.md) | Native declarations, topology previews and explicit apply | Core, Jzon | | [`libtmux/workspace/cli`](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/workspace/cli/README.md) | Workspace validation, previews and explicit-endpoint apply | Workspace | These directories provide system documentation. ASDF definitions remain in [`libtmux.asd`](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/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](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/examples/managed-daemon.lisp) includes loading and assertions. ```lisp (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: ```console $ mise exec -- tools/lisp.sh examples/managed-daemon.lisp ``` It prints `MANAGED DAEMON PASS` and a pane record. Read [daemon scopes](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/docs/daemons.md) for environment, readiness and cleanup contracts. Use an ordinary client alone when connecting to an existing server. ## Inspect a workspace The [REPL tutorial](https://libtmux.org/en/lisp/latest/guides/tutorial/) loads the example package, whose local nickname `tm` refers to [`libtmux`](). This complete function returns ordinary owned data after closing its client: ```lisp (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](https://libtmux.org/en/lisp/latest/reference/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](https://libtmux.org/en/lisp/latest/guides/tutorial/#build-a-linked-workspace) also creates two sessions, splits a pane and links one physical window into both sessions: ```mermaid 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](https://libtmux.org/en/lisp/latest/concepts/captures/). Process the retained capture with ordinary Lisp. This function uses only local accessors and returns a list of owned pane data: ```lisp (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: ```lisp (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](https://libtmux.org/en/lisp/latest/guides/queries/) 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: ```lisp (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`](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/examples/workspace.json) creates two windows and three panes from argv arrays. This tested function returns both the inspectable plan and retained application evidence: ```lisp (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](https://libtmux.org/en/lisp/latest/workspace/source-guide/) and [generated workspace API](https://libtmux.org/en/lisp/latest/workspace/reference/). ## 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: ```lisp (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)))) ``` ```lisp (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](https://libtmux.org/en/lisp/latest/topics/query-interchange/). ## Find the right guide | Task | Start here | | --- | --- | | Load the library; inspect and build a workspace | [REPL tutorial](https://libtmux.org/en/lisp/latest/guides/tutorial/) | | Start and stop a private empty-config daemon | [Daemon scopes](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/docs/daemons.md) | | Filter with `loop`, handle false/absence, export plists | [Local data recipes](https://libtmux.org/en/lisp/latest/guides/tutorial/#select-and-project-locally) | | Compile typed filters, traverse relations and compose QuerySets | [Native queries](https://libtmux.org/en/lisp/latest/guides/queries/) · [Query API](https://libtmux.org/en/lisp/latest/reference/) | | Add CL-PPCRE or exchange bounded filter documents | [Query interchange](https://libtmux.org/en/lisp/latest/topics/query-interchange/) · [JSON API](https://libtmux.org/en/lisp/latest/reference/) | | Validate, plan or explicitly apply a declarative workspace | [Workspaces](https://libtmux.org/en/lisp/latest/workspace/source-guide/) · [Workspace API](https://libtmux.org/en/lisp/latest/workspace/reference/) | | Rename, resize, move, send input, capture, use buffers or terminate explicit targets | [Operation contracts](https://libtmux.org/en/lisp/latest/guides/operations/) | | Read typed options, hooks and environment | [Decoded configuration](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/docs/configuration.md) | | Inspect native option, hook and environment output | [Configuration inspection](https://libtmux.org/en/lisp/latest/guides/operations/#configuration-inspection) | | Look up signatures, multiple values and conditions | [Public API](https://libtmux.org/en/lisp/latest/reference/) · [Captured fields](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/docs/fields.md) | | Understand deadlines, cancellation and cleanup | [Transport](https://libtmux.org/en/lisp/latest/concepts/transport/) | | Reconnect without replaying commands | [Fresh control observations](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/docs/control.md#reconnect-with-fresh-observations) | | Evaluate async, streaming and performance support | [Capabilities and limits](https://libtmux.org/en/lisp/latest/guides/capabilities/) | | Rerun FiveAM tests at the REPL or inspect CI coverage | [Testing](https://libtmux.org/en/lisp/latest/guides/testing/) · [Verification](https://libtmux.org/en/lisp/latest/guides/verification/) | | Navigate all documentation | [Guide index](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/docs/index.md) | Core subprocess calls block. Each client accepts one external operation at a time; concurrent reuse signals [`client-busy`](). Optional control [`submit-command`](https://libtmux.org/en/lisp/latest/reference/libtmux-control-submit-command/) queues a request without awaiting its reply; use [`await-request`](https://libtmux.org/en/lisp/latest/reference/libtmux-control-await-request/) or [`next-event`](https://libtmux.org/en/lisp/latest/reference/libtmux-control-next-event/) for bounded waits. Closing a client releases its resources and leaves tmux running. [`kill-server`](https://libtmux.org/en/lisp/latest/reference/libtmux-kill-server/) is a separate explicit operation that proves the daemon accepted during its preflight exited. [Daemon scopes](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/docs/daemons.md) 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](https://libtmux.org/en/lisp/latest/reference/libtmux-command-outcome/) or the condition's [error-outcome](https://libtmux.org/en/lisp/latest/reference/libtmux-error-outcome/); never retry an uncertain mutation blindly. See [outcome handling](https://libtmux.org/en/lisp/latest/guides/tutorial/#handle-command-and-observation-outcomes). 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](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/.github/CONTRIBUTING.md) and [Writing](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/.github/WRITING.md). Licensed under [MIT](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/LICENSE). [Branding assets](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/docs/branding.md) include the logo, icons, and social cards. --- # Guides Source: https://libtmux.org/en/lisp/latest/guides/ > Guides for libtmux. Connect to tmux and work with its sessions, windows, and panes. - [Getting started](https://libtmux.org/en/lisp/latest/guides/getting-started/): Read about libtmux-lisp. - [Attaching to tmux](https://libtmux.org/en/lisp/latest/guides/attaching-to-tmux/): Connect to an existing socket and leave its server running. - [REPL tutorial](https://libtmux.org/en/lisp/latest/guides/tutorial/): Read about capture, inspect and build a workspace. - [Native queries](https://libtmux.org/en/lisp/latest/guides/queries/): Read about native queries. - [Explicit operations](https://libtmux.org/en/lisp/latest/guides/operations/): Read about explicit operations. - [Capabilities and limits](https://libtmux.org/en/lisp/latest/guides/capabilities/): Read about capabilities and current limits. - [Testing](https://libtmux.org/en/lisp/latest/guides/testing/): Read about testing. - [Verification evidence](https://libtmux.org/en/lisp/latest/guides/verification/): Read about verification. --- # Attaching to tmux Source: https://libtmux.org/en/lisp/latest/guides/attaching-to-tmux/ > Inspect an existing tmux server from a standalone Common Lisp program, with an owned demo fixture. Connect a [`libtmux:client`](https://libtmux.org/en/lisp/latest/reference/libtmux-client/) to an explicit socket, acquire a snapshot, and find the `work` session. Closing that client leaves the borrowed server running. A missing server or session is an error. This controls tmux from Common Lisp. To open a terminal session, use [`tmux attach-session`](https://libtmux.org/en/tmux/latest/manual/attach-session/); the [shared guide](https://libtmux.org/en/tmux/guides/attaching-to-tmux/) covers interactive attachment and detaching. ## Connect to an existing server Save the program below. With `LIBTMUX_SOCKET_PATH` set, it inspects that server without owning its lifetime. With the variable unset, it creates a private server, proves the inspection left it alive, and then removes its own fixture. Both paths run from the library checkout using its pinned SBCL and ASDF. ```lisp title="connect.lisp" (require :asdf) (require :sb-posix) (asdf:load-asd (truename "libtmux.asd")) (asdf:load-system "libtmux") (defparameter *program* (or (uiop:getenv "LIBTMUX_PROGRAM") "tmux")) (defun inspect-work (socket) "Print the captured work session and leave the borrowed server running." (libtmux:with-client (client :program *program* :socket-path socket :timeout 5) (let* ((snapshot (libtmux:fetch-snapshot client)) (session (find "work" (libtmux:sessions snapshot) :key #'libtmux:name :test #'string=))) (unless session (error "The work session does not exist")) (format t "~A~%" (libtmux:name session))))) (defun run-demo () "Own a private fixture; prove inspection does not terminate its server." (let* ((directory (sb-posix:mkdtemp "/tmp/libtmux-lisp-attach-XXXXXX")) (socket (concatenate 'string directory "/tmux.sock")) (normal nil) (cleanup-ok t)) (unwind-protect (libtmux:with-client (owner :program *program* :socket-path socket :config-file "/dev/null" :timeout 5) (unwind-protect (multiple-value-prog1 (progn (setf cleanup-ok nil) (libtmux:run-command owner '("new-session" "-d" "-s" "work" "/bin/cat") :effect :mutation) (inspect-work socket) (libtmux:run-command owner '("has-session" "-t" "=work") :effect :read)) (setf normal t)) (when (probe-file socket) (handler-case (progn (libtmux:kill-server owner) (setf cleanup-ok t)) (error (condition) (if normal (error condition) (warn "Owned server cleanup failed: ~A" condition))))))) (if cleanup-ok (uiop:delete-directory-tree (uiop:ensure-directory-pathname directory) :validate t) (warn "Retained owned fixture directory: ~A" directory))))) (let ((socket (uiop:getenv "LIBTMUX_SOCKET_PATH"))) (if socket (inspect-work socket) (run-demo))) ``` [`libtmux:fetch-snapshot`](https://libtmux.org/en/lisp/latest/reference/libtmux-fetch-snapshot/) acquires the graph. [`libtmux:sessions`](https://libtmux.org/en/lisp/latest/reference/libtmux-sessions/) and `find` work on captured state locally. The client scope releases process resources; it does not terminate tmux. The demo explicitly bootstraps its owned server through [`libtmux:run-command`](https://libtmux.org/en/lisp/latest/reference/libtmux-run-command/). The high-level [`libtmux:new-session`](https://libtmux.org/en/lisp/latest/reference/libtmux-new-session/) currently requires a live daemon. Only the demo's owner calls [`libtmux:kill-server`](https://libtmux.org/en/lisp/latest/reference/libtmux-kill-server/). Its cleanup runs on normal return and Lisp conditions; failed termination retains the fixture directory and preserves an existing error. ## Setup and run Use Linux, Git, mise, and tmux 3.3a or newer. Save the displayed program under its shown filename, then fetch the tested source revision: ```console $ git clone https://github.com/libtmux/libtmux-lisp libtmux-source ``` ```console $ git -C libtmux-source checkout 0f9da93add247569a184bbda7d1dd1ca986c2e0f ``` Review [`mise.toml`](), install its pinned toolchain, and run the private demo: ```console $ cd libtmux-source && mise trust && mise install && \ env -u LIBTMUX_SOCKET_PATH mise exec -- sbcl \ --noinform \ --no-sysinit \ --no-userinit \ --disable-debugger \ --load "$(mise where http:asdf-lisp)/asdf.lisp" \ --script ../connect.lisp ``` Success prints `work`. The demo verifies that the inspection leaves its server alive before the owning scope terminates it and removes the directory. The demo command clears `LIBTMUX_SOCKET_PATH` to ensure fixture isolation. To inspect your own server, set that variable to its absolute socket path and repeat the SBCL invocation without `env -u`. That path performs no creation or termination. Set `LIBTMUX_PROGRAM` to select another native tmux executable. ## Find or create Reading a session does not reserve its name. Another client can create that name between a lookup and a creation request. Handle the operation's outcome instead of assuming the lookup reserved the name. Continue with the [REPL tutorial](https://libtmux.org/en/lisp/latest/guides/tutorial/) to traverse the complete session, window-link, window, and pane graph. ## Verification The exact displayed program and setup ran against the pinned library on Linux with SBCL 2.6.10 and ASDF 3.3.7. Isolated tmux 3.3a and 3.8 runs printed `work`, proved that the borrowed connection left its server alive, and cleaned up the owned fixture. The shared-docs checks bind the displayed bytes to these runs. --- # REPL tutorial Source: https://libtmux.org/en/lisp/latest/guides/tutorial/ > REPL tutorial: libtmux documentation. This tutorial uses an isolated existing tmux daemon. It creates two sessions, three physical windows, four links and four panes, then inspects them through ordinary Common Lisp data. It modifies only the daemon you start below. Complete the [source installation](https://libtmux.org/en/lisp/latest/guides/getting-started/#install-from-source) first. ## Run the checked walkthrough ```console $ mise run examples ``` The runner creates a mode-0700 temporary directory and explicit socket, clears inherited tmux variables, starts an empty-configuration daemon, runs the shipped Lisp examples, checks their results and terminates its own server. Assertions check object/link counts, false with presence, identity, ownership, literal names, refresh, byte buffers, optional regex/JSON queries and local access after client closure. A failure exits nonzero. It leaves the optional thread library unloaded. Select another native tmux binary explicitly: ```console $ LIBTMUX_TEST_TMUX=/usr/bin/tmux mise run examples ``` ## Work at the REPL For an interactive session, create a private daemon in a shell and keep that shell open. This command prints its socket path; use that path when prompted in Lisp. The automated runner and final cleanup helper need Python 3; the library itself does not. ```console $ export LIBTMUX_DEMO_DIR="$(mktemp -d /tmp/libtmux-lisp-demo-XXXXXXXX)" && \ env -u TMUX \ -u TMUX_PANE \ tmux \ -S "$LIBTMUX_DEMO_DIR/tmux.sock" \ -f /dev/null \ new-session \ -d \ -s work \ -n editor \ -x 120 \ -y 40 \ -- /bin/cat && \ env -u TMUX \ -u TMUX_PANE \ tmux \ -S "$LIBTMUX_DEMO_DIR/tmux.sock" \ display-message \ -p '#{pid}' \ > "$LIBTMUX_DEMO_DIR/server-witness.txt" && \ stat \ -Lc '%d %i' \ "$LIBTMUX_DEMO_DIR/tmux.sock" \ >> "$LIBTMUX_DEMO_DIR/server-witness.txt" && \ printf '%s\n' "$LIBTMUX_DEMO_DIR/tmux.sock" ``` Use the same terminal to start the pinned REPL: ```console $ mise run repl ``` Load the query system, which depends on the core. The development REPL registers the ASDF definition but does not implicitly load either system: ```lisp (asdf:load-system "libtmux/query") ``` Load the three optional systems used later in the checked walkthrough: ```lisp (asdf:load-system "libtmux/regex") ``` ```lisp (asdf:load-system "libtmux/json") ``` ```lisp (asdf:load-system "libtmux/workspace") ``` Load the recipe functions. Their package declares the SBCL package-local nicknames `tm`, `q`, `j` and `ws` for the core, query, JSON and workspace packages; the library claims no global short nicknames. ```lisp (load "examples/workspace.lisp") ``` ```lisp (in-package #:libtmux.examples) ``` Supply the printed socket path as data, without invoking the Lisp reader: ```lisp (defparameter *socket-path* (progn (write-line "Paste the demo socket path:") (read-line))) ``` This acquires the graph and returns one session record named `work`, containing its `editor` window and one pane. Pane projections include identity, captured fields, provenance and relationship IDs: ```lisp (inspect-workspace *socket-path*) ``` ## Build a linked workspace The complete recipe is copied from [workspace.lisp](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/examples/workspace.lisp). It creates a `logs` window and `review` session, splits `editor`, shares that physical window through index 7 of `review`, and renames logs literally. Run it once on the fresh demo; a repeated invocation encounters existing names. ```lisp (defun create-workspace (client) "Extend the demo's WORK session; return a fresh captured graph. Creates a logs window, a second editor pane, a review session and a shared link. The caller owns the isolated daemon; repeating this recipe there is an error." (let* ((before (tm:fetch-snapshot client)) (work (find "work" (tm:sessions before) :key #'tm:name :test #'string=))) (unless work (error "Start the demo's WORK session first")) (let* ((editor-link (aref (tm:window-links work) 0)) (editor (tm:window editor-link)) (pane (aref (tm:panes editor) 0)) (logs (observed-value (tm:new-window client work :name "logs" :command '("/bin/cat")))) (review (observed-value (tm:new-session client :name "review" :window-name "notes" :command '("/bin/cat"))))) (tm:split-pane client pane :direction :horizontal :command '("/bin/cat")) (tm:link-window client editor-link review 7) (tm:rename-window client logs "logs #{literal}") (tm:fetch-snapshot client)))) ``` Keep a captured graph after closing the client. These globals are interactive scratch values, not a global connection convention in the library: ```lisp (defparameter *snapshot* (tm:with-client (client :socket-path *socket-path*) (create-workspace client))) ``` ```lisp (list (length (tm:sessions *snapshot*)) (length (tm:windows *snapshot*)) (length (tm:window-links *snapshot*)) (length (tm:panes *snapshot*))) ``` The result is `(2 3 4 4)`. The two editor links have different session/index locators and the same physical window. [`windows`]() on a snapshot deduplicates physical windows; [`windows`]() on a session retains one entry per link. The [graph contract](https://libtmux.org/en/lisp/latest/concepts/captures/) explains all traversal directions. ## Select and project locally ```lisp (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))) ``` ```lisp (wide-active-panes *snapshot*) ``` This returns a nonempty list of plists from the retained graph. You can instead use `map`, `find-if`, `count-if`, `reduce` or `remove-if` with a native function. These calls do not acquire state. Compile a reusable scalar filter when the selection should be validated, inspected or shared. The example package's local nickname `q` refers to [`libtmux.query`](); the library claims no global nickname. ```lisp (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)))) ``` ```lisp (wide-active-pane-data *snapshot*) ``` [`matching`]() returns a fresh simple vector in captured order. Compilation copies the query form and checks field types, cycles and resource limits. The returned predicate is ordinary Lisp code over retained observations; neither compilation nor matching contacts tmux. Traverse registered relationships without reacquiring state, and keep the eager selection in a QuerySet with explicit root-model metadata: ```lisp (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*)))) ``` See the [native query guide](https://libtmux.org/en/lisp/latest/guides/queries/) for quantifier identities, stable ordering, ownership and strict cardinality. ## Match and serialize an optional query `libtmux/regex` adds CL-PPCRE-backed operators without adding that dependency to core or `libtmux/query`. The scanner is compiled once with the filter; this full-match example also proves that a match beginning at position zero works: ```lisp (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)))) ``` ```lisp (cat-pane-data *snapshot*) ``` `libtmux/json` writes the native schema `libtmux.lisp.filter/1`. Reading regex is an explicit local opt-in because parser limits do not interrupt CL-PPCRE scanner execution: ```lisp (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)))))) ``` ```lisp (round-trip-filter-document *snapshot*) ``` Both operations are local. The [interchange guide](https://libtmux.org/en/lisp/latest/topics/query-interchange/) documents the closed envelope, limits and false/empty/null distinctions. Use both values when false and absence matter. This field is always present; an inactive pane returns `nil` and `t`: ```lisp (let ((pane (find-if-not #'tm:active-p (tm:panes *snapshot*)))) (multiple-value-bind (value present-p) (tm:field-value pane :pane/active) (list value present-p))) ``` Returned strings, plists and relation vectors are caller-owned copies. [`entity-data`]() is finite and contains relationship identifiers rather than a recursive graph. A copied plist is local data; editing it does not mutate tmux. See the [field reference](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/docs/fields.md) for types and optional values. ## Handle command and observation outcomes Creation and named mutations return [command-outcome](https://libtmux.org/en/lisp/latest/reference/libtmux-command-outcome/). A successful command can have no replacement observation because the target disappeared afterward. Check the observation separately; this helper never retries the mutation: ```lisp (defun observed-value (outcome) "Return an operation's observed value, or signal its observation failure." (when (tm:outcome-observation-error outcome) (error (tm:outcome-observation-error outcome))) (multiple-value-bind (value present-p) (tm:outcome-value outcome) (unless present-p (error "This operation did not return an observation")) value)) ``` Rename a captured physical window and inspect old and new observations: ```lisp (tm:with-client (client :socket-path *socket-path*) (let* ((snapshot (tm:fetch-snapshot client)) (window (find "editor" (tm:windows snapshot) :key #'tm:name :test #'string=)) (renamed (observed-value (tm:rename-window client window "api #{literal}")))) (list (tm:name window) (tm:name renamed) (tm:same-entity-p window renamed)))) ``` The result is `("editor" "api #{literal}" t)`. The literal format punctuation survives. Refresh uses the **same client context** and returns another captured observation. Observations from a different connection are not interchangeable operational targets, even when their numeric IDs match. External failures signal typed conditions. A handler can inspect [error-outcome](https://libtmux.org/en/lisp/latest/reference/libtmux-error-outcome/), [outcome-status](https://libtmux.org/en/lisp/latest/reference/libtmux-outcome-status/), [outcome-phase](https://libtmux.org/en/lisp/latest/reference/libtmux-outcome-phase/), [outcome-exit-code](https://libtmux.org/en/lisp/latest/reference/libtmux-outcome-exit-code/) and owned diagnostic bytes. Missing completion evidence can mean the mutation already happened; an automatic retry can duplicate effects. The [operation guide](https://libtmux.org/en/lisp/latest/guides/operations/#outcomes-and-target-checks) defines these cases. ## Preserve binary data ```lisp (defun round-trip-bytes (client) "Store and retrieve a named binary buffer, including NUL and invalid UTF-8." (let ((bytes (make-array 5 :element-type '(unsigned-byte 8) :initial-contents '(0 10 92 195 255)))) (tm:set-buffer client bytes :name "example bytes") (multiple-value-bind (actual outcome) (tm:show-buffer client :name "example bytes") (assert (eq :succeeded (tm:outcome-status outcome))) (assert (equalp bytes actual)) actual))) ``` ```lisp (tm:with-client (client :socket-path *socket-path*) (round-trip-bytes client)) ``` This returns five octets, including NUL and invalid UTF-8, unchanged. For pane contents, [`capture-pane`](https://libtmux.org/en/tmux/latest/manual/capture-pane/) returns UTF-8 text and outcome; use [`capture-pane-bytes`]() for octets. Captures and buffer reads are bounded complete results, not subscriptions or streams. Successful input dispatch does not prove application readiness or consumption. Coordinate with an application signal; do not add a sleep or treat printed text as proof. [`pipe-pane`](https://libtmux.org/en/tmux/latest/manual/pipe-pane/) configures a daemon-owned shell pipe and has a separate [ownership contract](https://libtmux.org/en/lisp/latest/guides/operations/#pane-processes). ## Drive an application without polling The shipped [worker](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/examples/worker.sh) signals initialization, reads one request, writes its receipt file, then signals consumption. The Lisp controller uses a fresh [`wait-for`](https://libtmux.org/en/tmux/latest/manual/wait-for/) channel for each signal. The file receipt proves what the application consumed; a successful send or printed marker would not. These are blocking calls with deadlines, not a control-mode subscription API. ```lisp (defun drive-worker (client session socket-path program worker-path receipt-path) "Launch a worker through argv, wait for readiness and verify input consumption. WORKER-PATH names the shipped worker.sh; RECEIPT-PATH is in the owned demo directory. Signals operational conditions without retrying a mutation. Calls block." (let* ((ready (symbol-name (gensym "example-ready-"))) (consumed (symbol-name (gensym "example-consumed-"))) (window (observed-value (tm:new-window client session :name "worker" :command (list "/bin/sh" worker-path program socket-path ready consumed receipt-path)))) (pane (aref (tm:panes window) 0)) (message "release #{literal}; λ")) (tm:run-command client (list "-N" "wait-for" ready) :effect :read :timeout 1 :output-limit 4096) (tm:send-text client pane message :enter t) (tm:run-command client (list "-N" "wait-for" consumed) :effect :read :timeout 1 :output-limit 4096) (let ((receipt (uiop:read-file-string receipt-path :external-format :utf-8))) (assert (string= message receipt)) (list :consumed receipt :pane (tm:id pane))))) ``` ```lisp (tm:with-client (client :socket-path *socket-path*) (let ((session (find "work" (tm:fetch-sessions client) :key #'tm:name :test #'string=))) (drive-worker client session *socket-path* "tmux" (namestring (truename "examples/worker.sh")) (namestring (merge-pathnames "worker-receipt.txt" (uiop:pathname-directory-pathname *socket-path*)))))) ``` The result contains `:consumed "release #{literal}; λ"` and a pane ID. The request travels as literal text; worker arguments travel through argv. The worker remains in its pane until the owned daemon is stopped. Receipt paths are local to this example's daemon host; this is not a remote-file API. ## Inspect a typed failure This cancels a read before it acquires data, then handles [`command-cancelled`]() and returns the retained dispatch evidence: ```lisp (defun cancelled-read (client) "Demonstrate typed failure evidence using a read cancelled before acquisition." (let ((token (tm:make-cancellation-token))) (tm:cancel token) (handler-case (progn (tm:fetch-snapshot client :cancel-token token) (error "A cancelled read unexpectedly completed")) (tm:command-cancelled (condition) (let ((outcome (tm:error-outcome condition))) (list :condition (type-of condition) :status (and outcome (tm:outcome-status outcome)) :phase (and outcome (tm:outcome-phase outcome)))))))) ``` ```lisp (tm:with-client (client :socket-path *socket-path*) (cancelled-read client)) ``` This handler inspects evidence and returns; it never retries a command. Post-dispatch cancellation of a mutation can have a different status or phase. See the [condition reference](https://libtmux.org/en/lisp/latest/reference/libtmux-command-cancelled/). ## Terminate the owned server Closing a client leaves its daemon alive. This checked helper uses the separate [`kill-server`](https://libtmux.org/en/lisp/latest/reference/libtmux-kill-server/) operation only for the explicit socket passed by the caller. It accepts success only after the operation proves both dispatch and daemon exit: ```lisp (defun terminate-owned-server (socket-path &key (program "tmux")) "Terminate the explicitly selected owned server and return its proven outcome." (tm:with-client (client :program program :socket-path socket-path) (let ((outcome (tm:kill-server client))) (assert (eq :succeeded (tm:outcome-status outcome))) (assert (eq :server-terminated (tm:outcome-phase outcome))) outcome))) ``` Run it only for the private tutorial endpoint: ```lisp (terminate-owned-server *socket-path*) ``` ## Clean up Exit the REPL: ```lisp (uiop:quit) ``` In the original shell, terminate only the daemon created for this tutorial: ```console $ python3 tools/stop-example.py --already-terminated "$LIBTMUX_DEMO_DIR" ``` The helper proves the retained daemon PID exited, rejects a replaced socket, then removes the owned witness, receipt and stale socket file. Tmux 3.3a can leave its socket behind after exit. The automated runner executes the same Lisp termination function before this file cleanup. If an earlier example fails, it uses the retained witness to terminate and reap only this tutorial daemon. --- # Workspace guide Source: https://libtmux.org/en/lisp/latest/workspace/source-guide/ > Workspace guide: libtmux documentation. **Workspace Manager concept example** A working example of the core library. It can change without notice. `libtmux/workspace` validates a closed workspace description, produces an inspectable plan without I/O, and creates a missing session through the core library. Reapplying an exact topology dispatches no mutations. A conflict fails before the first mutation; a later failure retains every attempted [`command-outcome`](https://libtmux.org/en/lisp/latest/reference/libtmux-command-outcome/). [Workspace API](https://libtmux.org/en/lisp/latest/workspace/reference/) · [v1 contract](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/docs/design/workspace-v1.md) · [operation outcomes](https://libtmux.org/en/lisp/latest/guides/operations/#outcomes-and-target-checks) ## Load the optional system The workspace system depends on core [`libtmux`]() and the locked Jzon parser. It does not load with core. ```lisp (asdf:load-system "libtmux/workspace") ``` The command-line adapter is another explicit system: ```lisp (asdf:load-system "libtmux/workspace/cli") ``` ## Describe a topology Start with native data. This function from [`examples/workspace.lisp`](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/examples/workspace.lisp) constructs an owned declaration without I/O. The session environment is inherited by later panes. `:command` is literal argv; `:shell-command` explicitly requests shell execution during apply. ```lisp (defun workspace-description () "Build the demo's declaration without I/O. Commands run only during apply. APP_LABEL seeds the session environment, inherited by subsequent panes." (ws:make-workspace '(:schema "libtmux.lisp.workspace/1" :session (:name "declared" :cwd "/tmp" :environment #(("APP_LABEL" . "declared")) :windows #((:name "code" :index 2 :layout :tiled :panes #((:shell-command "test \"$APP_LABEL\" = declared && exec /bin/cat") (:command #("/bin/cat")))) (:name "shell" :index 5 :panes #((:command #("/bin/cat"))))))))) ``` The shipped [`examples/workspace.json`](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/examples/workspace.json) describes the same two windows and three panes. The tested examples compare the native and JSON declarations before applying them. The loader never invokes the Common Lisp reader, evaluates input, or interns external names. ```json { "schema": "libtmux.lisp.workspace/1", "session": { "name": "declared", "cwd": "/tmp", "environment": {"APP_LABEL": "declared"}, "windows": [ { "name": "code", "index": 2, "layout": "tiled", "panes": [ {"shell_command": "test \"$APP_LABEL\" = declared && exec /bin/cat"}, {"command": ["/bin/cat"]} ] }, { "name": "shell", "index": 5, "panes": [{"command": ["/bin/cat"]}] } ] } } ``` [`read-workspace`](https://libtmux.org/en/lisp/latest/workspace/reference/libtmux-workspace-read-workspace/) requires a regular UTF-8 file. [`parse-workspace`](https://libtmux.org/en/lisp/latest/workspace/reference/libtmux-workspace-parse-workspace/) accepts a complete string. Both reject duplicate and unknown members, trailing data, nulls, floating-point numbers, and exhausted input, nesting, node, string, window, pane, argv, environment-pair, or integer-token bounds. For trusted in-process data, [`make-workspace`](https://libtmux.org/en/lisp/latest/workspace/reference/libtmux-workspace-make-workspace/) accepts the equivalent closed keyword plist with vectors or finite proper lists. It copies retained strings and containers. Session names cannot contain periods or colons; names also reject characters tmux would rewrite. `:layout` uses a Common Lisp keyword such as `:tiled`; JSON uses the corresponding string. Custom layouts use `(:custom text)` or JSON `{"custom": "..."}`. Tmux validates their checksum and geometry during apply; failure can follow completed creation. Session `:environment` accepts `(name . value)` string pairs; JSON accepts a string-valued object. Names are unique and exclude NUL and `=`. Empty values are allowed. `:max-environment` defaults to 256 and bounds pairs per session. These are raw tmux session values, not an exact child environment; tmux supplies variables such as `TMUX_PANE` and can update inherited values on attachment. Pane/window environment keys are unsupported. See the [creation-input contract](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/docs/design/workspace-v1.md#native-data). ## Plan locally, then apply explicitly This function is executable source from [`examples/workspace.lisp`](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/examples/workspace.lisp). Planning against the captured graph is local. Applying performs its own fresh capture before any mutation, then proves the final topology with another capture. ```lisp (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)))) ``` The result separates the important facts: | Question | API | | --- | --- | | Did this call create the session? | [`result-created-p`]() | | Which core commands completed? | [`result-outcomes`]() | | What topology passed final proof? | [`result-snapshot`]() | | What would an offline creation do? | [`plan-steps`](), then [`step-action`]() and [`step-data`]() | [`result-outcomes`]() returns a fresh vector in dispatch order. Each member is the core outcome with status, phase, diagnostics, elapsed time, and optional replacement observation. Successfully dispatched commands remain present even when later workspace state validation fails. An exact second apply returns false from [`result-created-p`]() and an empty outcome vector. ## Bound application time [`apply-workspace`](https://libtmux.org/en/lisp/latest/workspace/reference/libtmux-workspace-apply-workspace/) accepts `:total-timeout` in seconds, shared across its initial capture, core steps and final verification. The example supplies 30 seconds for application; its file loading and preview happen first. Omit the argument to keep only per-operation timeouts. An explicit value must be a nonnegative finite real; zero refuses the initial read. Each core call receives the smaller of the remaining application budget and `:timeout` or the client's default. `:output-limit` still applies separately to each core operation, and `:cancel-token` is shared. The application budget checks admission between operations and after local planning. It cannot interrupt core preparation, synchronous platform calls or additive cleanup; an admitted call may run or dispatch past it. It is not a hard caller wall-time limit. [`workspace-deadline-exceeded`](https://libtmux.org/en/lisp/latest/workspace/reference/libtmux-workspace-workspace-deadline-exceeded/) reports expiry between calls without inventing a command outcome. Before the first core-step invocation it is signalled directly. Execution begins with that invocation, which can itself fail before dispatch. Later, [`workspace-apply-failed`](https://libtmux.org/en/lisp/latest/workspace/reference/libtmux-workspace-workspace-apply-failed/) retains it as the cause with all completed outcomes. The failed step is the next step refused admission, or `nil` during final verification. Inspect the retained evidence before deciding what to do next; no automatic retry occurs. ## Use the CLI without an implicit server The source checkout ships `bin/libtmux-workspace`. Validation and offline planning do not contact tmux: ```console $ bin/libtmux-workspace validate examples/workspace.json ``` ```json {"status":"valid","schema":"libtmux.lisp.workspace/1","session":"declared","windows":2,"panes":3} ``` ```console $ bin/libtmux-workspace plan examples/workspace.json ``` ```json {"status":"planned","steps":5,"actions":["create-session","move-window","split-pane","select-layout","create-window"]} ``` Apply requires the socket pathname. It never falls back to the default server: ```console $ bin/libtmux-workspace apply examples/workspace.json \ --socket-path /path/to/owned/tmux.sock ``` The single JSON response reports whether the session was created and how many core outcomes were retained. The tested [`libtmux.workspace.cli:main`](https://libtmux.org/en/lisp/latest/workspace/reference/libtmux-workspace-cli-main/) function returns status 0, 1, or 2 and accepts caller-owned output streams; it does not quit the image or enter the debugger. Library failures return status 1, leave stdout empty, and write one JSON error to stderr. It retains condition names, messages, causes, workspace paths and limits, failed-step data, and every retained command outcome. Outcomes include status, dispatch phase, argv, elapsed seconds and output bytes; an unknown exit code is omitted. `stdoutBytes` and `stderrBytes` are integer arrays, preserving invalid UTF-8. Observation and cleanup failures retain their own diagnostics. Nested details stop after eight levels and set `detailsTruncated` to true. Usage errors return status 2 with usage text on stderr. Inspect retained statuses and phases before recovering from a failed apply. An `unknown` outcome can follow a mutation that already happened. Successful outcomes remain successful when a later step or observation fails. ## Handle conflicts and partial application [`plan-workspace`](https://libtmux.org/en/lisp/latest/workspace/reference/libtmux-workspace-plan-workspace/) matches an existing session by name, then proves the exact number of named window links, every declared index, and each pane count. Extra, missing, duplicate, or mismatched topology signals [`workspace-conflict`](). During creation, later explicit indexes are reserved before automatic windows, and redundant same-index moves dispatch nothing. Pane creation and final order follow the declaration. Each split uses the pane just created and its larger captured dimension; intermediate tiled layouts prevent repeatedly halving one shrinking pane. The requested layout runs after the final pane exists. No delete, restart, or compensating mutation runs. Command argv, explicit shell source, environment, cwd and layouts are creation inputs. They are not inferred from later pane state. A pane may change process or directory without turning an otherwise matching topology into a destructive repair plan. If a step fails, inspect the condition while preserving its uncertainty: ```lisp (handler-case (ws:apply-workspace client workspace) (ws:workspace-apply-failed (condition) (list :step (let ((step (ws:failed-step condition))) (and step (ws:step-action step))) :outcomes (map 'vector #'tm:outcome-status (ws:result-outcomes condition)) :cause (tm:error-cause condition)))) ``` A final-proof failure has no failed step because every planned command already completed. It still retains all outcomes. The workspace layer never retries or rolls back a mutation. ## Current boundary Reuse checks topology by name, index and pane count, including sessions created outside this library. Version 1 does not retain a canonical declaration or owned entity mapping. Logical IDs, ownership checks, declaration-versus-drift planning and explicit reconciliation remain unfinished. Session environment, shell source and custom layouts are creation inputs; reuse does not converge them. Version 1 omits YAML, plugins, lifecycle scripts, implicit shell expansion, deletion, process supervision, daemon startup and control-mode events. Use explicit core operations when the application needs behavior outside this bounded bootstrap/reuse contract. The FiveAM suite proves pure planning and parser bounds without tmux. Integration tests apply and reapply the same document on owned exact tmux 3.3a and 3.8 servers. `mise run package-examples` repeats the executable example from the source archive. --- # Native queries Source: https://libtmux.org/en/lisp/latest/guides/queries/ > Native queries: libtmux documentation. `libtmux/query` compiles inert keyword-headed data into a reusable Common Lisp function over captured entities and their registered relationships. Compilation and evaluation are local: they do not acquire, refresh or mutate tmux. Use an ordinary Lisp function when a one-off selection does not need validation or inspection. [Query API](https://libtmux.org/en/lisp/latest/reference/) · [Captured fields](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/docs/fields.md) · [Runnable tutorial](https://libtmux.org/en/lisp/latest/guides/tutorial/#select-and-project-locally) ## Load without global nicknames Load the separate query system. It loads [`libtmux`]() but no regex, JSON or thread library: ```lisp (asdf:load-system "libtmux/query") ``` Applications can choose package-local nicknames: ```lisp (defpackage #:my-tmux-tool (:use #:cl) (:local-nicknames (#:tm #:libtmux) (#:q #:libtmux.query))) (in-package #:my-tmux-tool) ``` The library does not claim the short global package names `tm` or `q`. ## Compile once and match locally Quoted lists are data. [`compile-filter`]() validates the root model, operator, field, arity and operand types; it never calls `eval`. ```lisp (defparameter *editors* (q:compile-filter :pane '(:and (:= :pane/active t) (:in :pane/current-command ("nvim" "vim")) (:>= :pane/width 100)))) ``` Use the returned genuine function with standard sequence operations: ```lisp (remove-if (complement (q:predicate *editors*)) panes) ``` Or request a fresh simple vector with original order and multiplicity: ```lisp (q:matching *editors* panes) ``` [`matching`]() accepts a proper finite list or a non-string vector. It honors fill pointers and displaced vectors, validates the complete outer container before evaluation, and shares one execution budget across the traversal. It retains the captured handles; it does not copy the object graph. ## Scalar semantics | Form | Fields | Meaning | | --- | --- | --- | | `(:= field value)` | Text, integer, Boolean | Typed equality | | `(:< field n)`, `(:<= field n)`, `(:> field n)`, `(:>= field n)` | Integer | Mathematical integer comparison | | `(:in field values)` | Text, integer, Boolean | Typed finite membership | | `(:contains field text)` | Text | Case-sensitive substring search | | `(:starts-with field text)` | Text | Case-sensitive prefix | | `(:and ...)`, `(:or ...)`, `(:not expression)` | Expressions | Ordered Boolean composition | | `(:present field)`, `(:missing field)` | Scalar | Captured presence | Text uses content comparison. Integers are Common Lisp integers; wire-decoding bounds do not truncate query operands. Ratios and floats are invalid. Empty AND is true, empty OR and empty membership are false. A bare `nil` filter is invalid. Presence is separate from truth. `(:= :pane/active nil)` compares a present false value. A scalar comparison on an absent optional field is false, so NOT of that comparison is true. Use `:present` when absence must be excluded. Missing required capture data signals [`field-unavailable`](). ## Add optional regex operators Load CL-PPCRE support without changing the dependency boundary of core or `libtmux/query`: ```lisp (asdf:load-system "libtmux/regex") ``` The operand is owned declarative data: the engine, pattern and closed scanner options. Search and full match are separate operations: ```lisp (q:compile-filter :pane '(:regex-search :pane/current-command (:cl-ppcre "^(n?vim)$" :case-insensitive-mode t))) ``` Supported Boolean options are `:case-insensitive-mode`, `:multi-line-mode`, `:single-line-mode` and `:extended-mode`. The scanner is compiled once. A match at position zero is true. An absent optional text field is false. Query visit limits do not interrupt CL-PPCRE scanner execution; use regex only with trusted local patterns and inputs appropriate to that engine. The separate [query interchange guide](https://libtmux.org/en/lisp/latest/topics/query-interchange/) shows bounded JSON round trips and the explicit regex read opt-in. ## Traverse captured relationships Relation forms compile their child expression for the registered target model: | Form | Relation | Empty or missing behavior | | --- | --- | --- | | `(:any relation expression)` | To-many | False on an empty relation | | `(:all relation expression)` | To-many | True on an empty relation | | `(:none relation expression)` | To-many | True on an empty relation | | `(:is relation expression)` | To-one | False when the target is absent | This expression requires one pane to be both active and running `nvim`: ```lisp (q:compile-filter :session '(:any :session/window-links (:is :link/window (:any :window/panes (:and (:= :pane/active t) (:= :pane/current-command "nvim")))))) ``` Two separate `:any` forms may match different panes. Missing or unloaded relationship data signals [`field-unavailable`](); it is not treated as a known empty collection. [`required-relations`]() reports every traversed relation in first-occurrence order. ## Keep eager local selections in a QuerySet QuerySet is an optional collection convenience, not a replacement for Common Lisp sequences. It owns a simple-vector container and remains eager: ```lisp (let* ((all (q:from panes :model :pane)) (editors (q:where all *editors*)) (wide (q:where-if editors (lambda (pane) (>= (tm:width pane) 120)))) (ordered (q:order-by wide #'> :key #'tm:width))) (q:items ordered)) ``` [`from`](https://libtmux.org/en/lisp/latest/reference/libtmux-query-from/), [`where`](https://libtmux.org/en/lisp/latest/reference/libtmux-query-where/), [`where-if`](https://libtmux.org/en/lisp/latest/reference/libtmux-query-where-if/) and [`order-by`](https://libtmux.org/en/lisp/latest/reference/libtmux-query-order-by/) return new objects. [`items`](https://libtmux.org/en/lisp/latest/reference/libtmux-query-items/) returns a fresh simple vector. [`order-by`]() uses `stable-sort` on a private copy, so equal keys retain input order; its predicate and key follow Common Lisp's ordinary sorting callback contract. Both operations accept function objects or symbols naming global functions. They resolve each designator once before traversal or sorting, so a redefinition during a call affects the next operation. Macros, special operators and unbound function names signal [`invalid-argument`](https://libtmux.org/en/lisp/latest/reference/libtmux-invalid-argument/). [`where-if`]() calls its native function once per item in input order on normal completion. A declared model is checked against every item; an empty QuerySet can carry its model explicitly, while [`where`]() can take the model from its compiled filter. [`one`]() and [`one-or-none`]() accept QuerySets directly. ## Inspect owned query data Inspection returns fresh ordinary data: ```lisp (q:expression *editors*) ``` ```lisp (q:required-fields *editors*) ``` ```lisp (q:explain *editors*) ``` Changing the source form after compilation, or changing an inspection result, does not alter the compiled filter. [`fields`]() returns fresh registry metadata for one of `:session`, `:window`, `:pane`, `:window-link` or `:client`. Here `:client` means a captured attached tmux client, not the connection object. ## Select strictly before mutation [`one`]() requires exactly one item. [`one-or-none`]() returns two values so an empty selection differs from a singleton containing `nil`: ```lisp (multiple-value-bind (pane present-p) (q:one-or-none (q:matching *editors* panes)) (when present-p (tm:capture-pane client pane))) ``` Both functions validate the complete bounded input before returning a candidate. Zero items signal [`no-match`]() from [`one`](); multiple positions signal [`ambiguous-match`](). Repeated references count as repeated positions. The condition reports whether its count is exact and exposes a fresh bounded sample. This ordering prevents a dependent mutation from running before uniqueness is established. ## Resource limits and failures The defaults bound nesting to 64 containers, compilation to 10,000 structure visits, one membership to 1,000 values, one string to 65,536 characters, retained query text to 1,048,576 characters, selection to 100,000 items and one execution scope to 1,000,000 expression and relation visits. [`compile-filter`](), [`matching`](), QuerySet constructors, [`one`]() and [`one-or-none`]() expose narrower keyword limits for untrusted in-process data. Cycles, dotted lists, invalid operators, wrong field types and unavailable optional systems signal [`invalid-filter`](). Limit exhaustion signals its subtype [`query-limit-exceeded`](). Applying a compiled filter to the wrong captured model or to a plist signals [`invalid-query-target`](). Diagnostics retain bounded type and path data; they do not print an unbounded caller object. External configuration files need the later bounded JSON codec. Disabling `*read-eval*` alone does not turn the Common Lisp reader into a query parser. --- # Explicit operations Source: https://libtmux.org/en/lisp/latest/guides/operations/ > Explicit operations: libtmux documentation. Named operations take the connection client first and a captured target where needed. They read current state to validate the target, then use its identity. Earlier captures remain unchanged. Every operation accepts `:cancel-token`, `:timeout` and `:output-limit`; the latter two default to the client settings and cover validation, dispatch and any follow-up acquisition together. The current APIs require a live daemon. [`new-session`](https://libtmux.org/en/tmux/latest/manual/new-session/) creates a session there; daemon bootstrap is a separate unfinished part of the implementation. | Operation | Target / result | | --- | --- | | [`rename-window`](https://libtmux.org/en/tmux/latest/manual/rename-window/), [`rename-session`](https://libtmux.org/en/tmux/latest/manual/rename-session/) | Physical entity; command outcome with optional replacement | | [`select-window`](https://libtmux.org/en/tmux/latest/manual/select-window/) | Window link, preserving session/index context | | [`select-pane`](https://libtmux.org/en/tmux/latest/manual/select-pane/) | Physical pane | | [`next-window`](https://libtmux.org/en/tmux/latest/manual/next-window/), [`previous-window`](https://libtmux.org/en/tmux/latest/manual/previous-window/), [`last-window`](https://libtmux.org/en/tmux/latest/manual/last-window/) | Session; outcome containing its selected window link | | [`last-pane`](https://libtmux.org/en/tmux/latest/manual/last-pane/) | Physical window; outcome containing its selected pane | | [`new-session`](https://libtmux.org/en/tmux/latest/manual/new-session/) | Selected live daemon; outcome containing the created session | | [`new-window`](https://libtmux.org/en/tmux/latest/manual/new-window/) | Captured session; outcome containing the physical window | | [`split-pane`]() | Captured pane; outcome containing the new pane | | [`capture-pane`](https://libtmux.org/en/tmux/latest/manual/capture-pane/) | Pane; UTF-8 text and diagnostic outcome as two values | | [`capture-pane-bytes`]() | Pane; owned octets and diagnostic outcome as two values | | [`send-text`](), [`send-keys`](https://libtmux.org/en/tmux/latest/manual/send-keys/), [`send-prefix`](https://libtmux.org/en/tmux/latest/manual/send-prefix/) | Pane; command outcome for the send request | | [`respawn-pane`](https://libtmux.org/en/tmux/latest/manual/respawn-pane/), [`respawn-window`](https://libtmux.org/en/tmux/latest/manual/respawn-window/) | Physical target; explicit process replacement and optional captured result | | [`pipe-pane`](https://libtmux.org/en/tmux/latest/manual/pipe-pane/) | Pane; explicit shell source, direction and daemon-owned pipe | | [`switch-client`](https://libtmux.org/en/lisp/latest/reference/libtmux-switch-client/) | Captured terminal client and destination session; outcome with optional replacement | | [`detach-client`](https://libtmux.org/en/lisp/latest/reference/libtmux-detach-client/), [`refresh-client`](https://libtmux.org/en/lisp/latest/reference/libtmux-refresh-client/), [`suspend-client`](https://libtmux.org/en/lisp/latest/reference/libtmux-suspend-client/), [`lock-client`](https://libtmux.org/en/lisp/latest/reference/libtmux-lock-client/) | Captured terminal client; outcome acknowledging the request | | [`lock-session`](https://libtmux.org/en/lisp/latest/reference/libtmux-lock-session/), [`lock-server`](https://libtmux.org/en/lisp/latest/reference/libtmux-lock-server/) | Frontends attached to a captured session or the addressed daemon; request outcome | | [`wait-for`](https://libtmux.org/en/lisp/latest/reference/libtmux-wait-for/) | Literal server channel; signal, wait, lock or unlock outcome | | [`clear-history`](https://libtmux.org/en/tmux/latest/manual/clear-history/), [`copy-mode`](https://libtmux.org/en/tmux/latest/manual/copy-mode/) | Pane; command outcome | | [`resize-pane`](https://libtmux.org/en/tmux/latest/manual/resize-pane/), [`resize-window`](https://libtmux.org/en/tmux/latest/manual/resize-window/) | Physical target; outcome with optional replacement | | [`select-layout`](https://libtmux.org/en/tmux/latest/manual/select-layout/), [`next-layout`](https://libtmux.org/en/tmux/latest/manual/next-layout/), [`previous-layout`](https://libtmux.org/en/tmux/latest/manual/previous-layout/) | Physical window; outcome with optional replacement | | [`link-window`](https://libtmux.org/en/tmux/latest/manual/link-window/), [`move-window`](https://libtmux.org/en/tmux/latest/manual/move-window/), [`break-pane`](https://libtmux.org/en/tmux/latest/manual/break-pane/) | Explicit link/session/index context; outcome containing the destination link | | [`swap-windows`](), [`swap-panes`]() | Two targets; outcome containing a fresh vector in argument order | | [`unlink-window`](https://libtmux.org/en/tmux/latest/manual/unlink-window/) | Window link; outcome without a replacement | | [`move-pane`](https://libtmux.org/en/tmux/latest/manual/move-pane/), [`join-pane`](https://libtmux.org/en/tmux/latest/manual/join-pane/), [`rotate-window`](https://libtmux.org/en/tmux/latest/manual/rotate-window/) | Physical targets; outcome with optional replacement | | [`list-buffers`](https://libtmux.org/en/tmux/latest/manual/list-buffers/) | Fresh vector of metadata plists and diagnostic outcome | | [`show-buffer`](https://libtmux.org/en/tmux/latest/manual/show-buffer/) | Owned octets and diagnostic outcome | | [`set-buffer`](https://libtmux.org/en/tmux/latest/manual/set-buffer/), [`load-buffer`](https://libtmux.org/en/tmux/latest/manual/load-buffer/), [`save-buffer`](https://libtmux.org/en/tmux/latest/manual/save-buffer/), [`delete-buffer`](https://libtmux.org/en/tmux/latest/manual/delete-buffer/), [`paste-buffer`](https://libtmux.org/en/tmux/latest/manual/paste-buffer/) | Explicit buffer data/path/name and optional pane; command outcome | | [`set-option`](https://libtmux.org/en/lisp/latest/reference/libtmux-set-option/), [`unset-option`](https://libtmux.org/en/lisp/latest/reference/libtmux-unset-option/) | Typed live-catalog option mutation in one explicit table | | [`set-hook`](https://libtmux.org/en/lisp/latest/reference/libtmux-set-hook/), [`unset-hook`](https://libtmux.org/en/lisp/latest/reference/libtmux-unset-hook/) | Tmux command-source installation/removal; never evaluates source as Lisp | | [`set-environment`](https://libtmux.org/en/lisp/latest/reference/libtmux-set-environment/), [`unset-environment`](https://libtmux.org/en/lisp/latest/reference/libtmux-unset-environment/), [`remove-environment`](https://libtmux.org/en/lisp/latest/reference/libtmux-remove-environment/) | Raw global/session environment value, deletion or tombstone | | [`kill-pane`](https://libtmux.org/en/lisp/latest/reference/libtmux-kill-pane/), [`kill-window`](https://libtmux.org/en/lisp/latest/reference/libtmux-kill-window/), [`kill-session`](https://libtmux.org/en/lisp/latest/reference/libtmux-kill-session/) | Exact captured entity; outcome without replacement | | [`kill-server`](https://libtmux.org/en/lisp/latest/reference/libtmux-kill-server/) | Retained live daemon; proven process exit or uncertain outcome | Creation defaults to detached mode. Selection uses explicit captured targets. [`new-window :index`](https://libtmux.org/en/tmux/latest/manual/new-window/) chooses a free session index. [`split-pane`]() accepts `:horizontal` (left/right) or `:vertical` (top/bottom), with a cell `:size` or `:percent`. Creation returns a fresh captured entity through [`outcome-value`](). For typed option/hook values, inherited table reads and environment presence, see [decoded configuration](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/docs/configuration.md). Raw printers remain available for unrestricted native inspection. ## Names and child commands Names and creation `:cwd` strings are literal, including `#[...]` and `#{...}` text. Rename's `:format-p t` explicitly requests tmux format expansion. Tmux can normalize or reject a name; inspect the replacement observation for the resulting name. Cwd text follows tmux's own relative-path rules. Tmux can fall back from an inaccessible cwd; use child-side evidence when the exact directory matters. Creation accepts a nonempty `:command` list/vector of argv strings or a separate `:shell-command` string. Omission selects tmux's default shell. Supplying an empty argv is invalid. Strings may contain spaces, quotes, semicolons, dollar signs, backslashes and Unicode; the encoder preserves their argument boundaries. One-element argv starts that executable with no added program argument. `:environment` is a list/vector of `(name . value)` string pairs. The final encoded tmux command is limited to 16,000 UTF-8 bytes, including NUL terminators between argv entries. Nested command quoting counts toward this limit. Oversized named commands fail before their payload is dispatched. They are never silently split into multiple mutations. ## Terminal input [`send-text`]() preserves literal text such as `C-c`; `:enter t` appends one Return key in the same request. [`send-keys`](https://libtmux.org/en/tmux/latest/manual/send-keys/) accepts a nonempty list/vector of tmux key strings and a positive `:repeat` count. Tmux recognizes names such as `C-c` and `Enter`, and falls back to literal input for unrecognized strings. [`send-prefix`](https://libtmux.org/en/tmux/latest/manual/send-prefix/) uses the configured prefix; `:second t` selects prefix2. The pane's current tmux mode handles input. Successful dispatch means tmux processed the send request; it does not establish application consumption or readiness. Coordinate dependent work through an application signal. [`copy-mode`](https://libtmux.org/en/tmux/latest/manual/copy-mode/) enters copy mode; `:page-up t` scrolls on entry, and `:exit t` exits. Page-up and exit are mutually exclusive. [`clear-history`](https://libtmux.org/en/tmux/latest/manual/clear-history/) removes retained tmux history; it does not promise to clear the application's current screen. ## Geometry and selection [`resize-pane`](https://libtmux.org/en/tmux/latest/manual/resize-pane/) and [`resize-window`](https://libtmux.org/en/tmux/latest/manual/resize-window/) accept positive cell `:width`/`:height`, or a `:direction` (`:left`, `:right`, `:up`, `:down`) with positive `:amount` (default one). Absolute dimensions range from 1 through 10,000, the common supported range. Relative amounts range from 1 through 2,147,483,647. Tmux may clamp the actual layout; the replacement observation contains the resulting dimensions. Pane `:zoom t` toggles zoom. Window `:fit :smallest` or `:fit :largest` fits the attached clients. These modes cannot be combined with dimensions or direction. A window resize sets tmux's [`window-size`](https://libtmux.org/en/tmux/latest/catalog/options/window-size/) option to manual. [`select-layout`](https://libtmux.org/en/tmux/latest/manual/select-layout/) accepts `:even-horizontal`, `:even-vertical`, `:main-horizontal`, `:main-vertical`, `:tiled`, or `(:custom "tmux-layout-description")`. Tmux validates custom checksums and geometry. Layout operations unzoom the window. [`next-layout`](https://libtmux.org/en/tmux/latest/manual/next-layout/) and [`previous-layout`](https://libtmux.org/en/tmux/latest/manual/previous-layout/) follow tmux's layout ordering. [`next-window`](https://libtmux.org/en/tmux/latest/manual/next-window/), [`previous-window`](https://libtmux.org/en/tmux/latest/manual/previous-window/) and [`last-window`](https://libtmux.org/en/tmux/latest/manual/last-window/) take a captured session and return its newly observed active **window link** through [`outcome-value`](). Next and previous accept `:activity t` to restrict selection to alerting windows. [`last-pane`](https://libtmux.org/en/tmux/latest/manual/last-pane/) takes a physical window and returns its observed active pane. Tmux signals when no last selection exists. These operations and geometry accept `:observe nil` to omit follow-up acquisition. ## Attached terminal clients [Fetch-clients](https://libtmux.org/en/lisp/latest/reference/libtmux-fetch-clients/) returns attached-client observations; they are distinct from the library connection passed as the first argument. The five client operations above require an ordinary terminal client captured through that connection. They reject control-mode observations. Preflight requires one matching name and the same PID, creation time and tty. It also rejects a name that matches another client's full or abbreviated tty. A missing, replaced or foreign observation signals [stale-target](https://libtmux.org/en/lisp/latest/reference/libtmux-stale-target/). Tmux has no dispatch-side client fingerprint guard across the supported floor, so replacement between preflight and dispatch remains possible. [`switch-client`](https://libtmux.org/en/tmux/latest/manual/switch-client/) takes an explicit destination session. It preserves that session's environment by default; `:update-environment t` applies tmux's [`update-environment`](https://libtmux.org/en/tmux/latest/catalog/options/update-environment/) option using the attached client's environment. Its default `:observe t` acquires a replacement client observation; `:observe nil` omits that read. The earlier observation still points to its captured session. [`refresh-client`](https://libtmux.org/en/tmux/latest/manual/refresh-client/) schedules a full redraw; `:status-only t` schedules only the status line. [Refresh](https://libtmux.org/en/lisp/latest/reference/libtmux-refresh/) instead acquires a replacement observation. [`detach-client`](https://libtmux.org/en/tmux/latest/manual/detach-client/) requests detachment of the selected client. [`suspend-client`](https://libtmux.org/en/tmux/latest/manual/suspend-client/) requests native SIGTSTP suspension; the controlling application owns resumption. [`lock-client`](https://libtmux.org/en/tmux/latest/manual/lock-client/) asks the frontend to execute its configured [`lock-command`](https://libtmux.org/en/tmux/latest/catalog/options/lock-command/) through a shell. An already suspended client can make suspend or lock a no-op; an empty or oversized lock command is also ignored by tmux. These four calls return no replacement observation. Success acknowledges the server's request. It does not prove frontend exit, terminal restoration, completed redraw, process suspension, lock startup or authentication. Coordinate dependent work through the frontend's own completion signal. Missing dispatch/completion evidence remains an unknown outcome; the library never retries these requests automatically. [`lock-session`](https://libtmux.org/en/lisp/latest/reference/libtmux-lock-session/) requests locking for frontends attached to the selected session when tmux executes the command. [`lock-server`](https://libtmux.org/en/lisp/latest/reference/libtmux-lock-server/) requests it across the addressed daemon. Both use live membership, including frontends attached after the caller's capture. They return one request outcome, with no per-frontend results. Control clients, already suspended frontends and empty or oversized lock commands are skipped; no eligible frontends is a successful no-op. These requests run the configured frontend lock programs. They do not block other tmux commands or suspend pane processes. Closing the library client leaves the frontend programs running. Hooks can add effects or fail after requests have been sent, so a failed outcome does not undo a lock request. See tmux's [`server_lock`](https://github.com/tmux/tmux/blob/3.8/server-fn.c#L175-L218). ## Message printing [`display-message`](https://libtmux.org/en/lisp/latest/reference/libtmux-display-message/) returns two values: owned strict UTF-8 text and its diagnostic command outcome. It uses tmux's ordinary `-p` printer and preserves whitespace, including the final newline. Message text is literal by default, including `#{...}`, `#(...)`, `#[...]` and `%Y`. Literal input disables expansion; it does not make the printer a byte-identical echo. Native versions can escape output, including `$` on 3.4. The API retains that presentation rather than interpreting it as decoded field data. An optional `:target` accepts a captured session, physical window, pane or window link owned by the supplied client. Use a link when its session and index matter. Physical windows and panes let tmux choose their session context; omitting the target lets tmux choose the complete context. Authority and stale link checks apply before the request. The call acquires no replacement. `:format-p t` explicitly enables tmux format and time expansion. A `#()` expression may start a shell job, but expansion does not wait for its result. A fresh command client may print an empty or incomplete result. Those jobs belong to the native command client and are cleaned up on its disconnect; this API does not provide job completion or background process ownership. After-hooks can print additional text or have effects. The returned text retains that output; it does not establish a single field's value or presence. Use captured getters for local field reads. Decoded configuration acquisition remains a separate capability. Invalid UTF-8 raises [`decoding-error`](https://libtmux.org/en/lisp/latest/reference/libtmux-decoding-error/) with the outcome's original bytes. Tmux 3.4 and newer provide native literal printing. The 3.3a fallback escapes format and time syntax and conservatively limits the intermediate after time expansion to 8,191 UTF-8 bytes. That limit is checked after live-version preflight and before payload dispatch. The encoded command limit also applies. See [`cmd_display_message_exec`](https://github.com/tmux/tmux/blob/3.8/cmd-display-message.c#L62-L176). ## Commands and key bindings [`list-commands`](https://libtmux.org/en/lisp/latest/reference/libtmux-list-commands/) and [`list-keys`](https://libtmux.org/en/lisp/latest/reference/libtmux-list-keys/) return ordinary UTF-8 printer text and the retained outcome. Both preserve native escaping, whitespace and after-hook output. The result is intended for inspection, not replay or decoded configuration lookup. [`list-commands`](https://libtmux.org/en/tmux/latest/manual/list-commands/) lists built-ins. Its optional `:command` accepts exact names or built-in aliases; configured [`command-alias`](https://libtmux.org/en/tmux/latest/catalog/options/command-alias/) entries are not part of that table. Through tmux 3.5a, an unknown selector produces no rows. From 3.6, unique prefixes work, while unknown or ambiguous selectors fail. [`list-keys`](https://libtmux.org/en/tmux/latest/manual/list-keys/) accepts an optional literal `:table` and `:key`. Omitted table means all tables. Unknown tables and invalid key syntax remain errors. A valid but unbound key is an error except on tmux 3.7 through 3.7c, which return empty text. Those four releases also send a single matching row to the server message log and any selected frontend's status line instead of stdout, including with `:format`. Multiple matching rows print normally. Empty text therefore does not establish that a binding is absent, and inspection can display a status message. The native [single-row branch](https://github.com/tmux/tmux/blob/3.7c/cmd-list-keys.c#L236-L248) changes in [tmux 3.8](https://github.com/tmux/tmux/blob/3.8/cmd-list-keys.c#L220-L248). Its `:format` requires tmux 3.7 or newer; older daemons reject it before payload dispatch. Both listing functions accept explicit tmux format source without time expansion, so `%Y` stays literal. Empty formatted rows may be omitted. Format jobs have the same asynchronous lifetime limits as message printing. [`bind-key`](https://libtmux.org/en/lisp/latest/reference/libtmux-bind-key/) replaces one binding in `:table`, which defaults to `"prefix"`. Supply exactly one of `:command` argv or `:command-source`, explicit tmux source. The argv form preserves token boundaries and rejects `=` in its command-name token. `:note` is literal text; `:repeat t` enables native repetition. The stored command nodes run when tmux dispatches that key, using the key event's context. This does not carry a captured target's authority into future execution. Tmux parses binding source immediately. Assignments and directives can alter the daemon's environment before a later parse error. Aliases resolve during binding and can introduce parser effects even through argv. A failed bind is not a rollback; missing completion evidence can leave its outcome unknown. Keep source and aliases under the application's control. See tmux's [`cmd_bind_key_exec`](https://github.com/tmux/tmux/blob/3.8/cmd-bind-key.c#L55-L107) and [assignment grammar](https://github.com/tmux/tmux/blob/3.8/cmd-parse.y#L224-L244). [`unbind-key`](https://libtmux.org/en/lisp/latest/reference/libtmux-unbind-key/) removes exactly one key, also from `"prefix"` by default. A missing key in an existing table is a no-op; an absent table is an error. Removing a table's last binding may delete the table, so a second removal need not succeed. Neither helper retries or implicitly removes all bindings. Binding success acknowledges storage and hooks, not later key execution. [`send-keys`](https://libtmux.org/en/lisp/latest/reference/libtmux-send-keys/) sends pane input and does not exercise a frontend's key-binding dispatch. ## Server access [`server-access`](https://libtmux.org/en/lisp/latest/reference/libtmux-server-access/) takes a literal account name and one access operation. `:allow` adds a writable entry; `:deny` removes one; `:read-only` and `:read-write` update or add an entry. Duplicate additions and missing removals remain errors. Tmux resolves names through the daemon's name service; strings are not treated as numeric UIDs. Root and the daemon-owner UID cannot be changed. `:group-p t` selects a group name and requires live tmux 3.8; older daemons reject that request before payload dispatch. Removal does not create a deny rule. From tmux 3.8, a user without an explicit entry can still match its connecting peer's GID. An explicit UID entry wins over the GID entry; supplementary groups are not searched. Socket and directory access must be configured separately; an ACL entry alone does not make the endpoint reachable. Permission changes can affect connected clients. In tmux 3.8, removal and read-only/read-write updates recompute every client's effective ACL. A writable entry can therefore clear an unrelated frontend's separately set read-only flag. A request can schedule disconnection without proving frontend exit, and a later grant does not cancel an already scheduled exit. `:allow` alone does not recompute connected-client flags. Earlier captures retain their values; use [`refresh`](https://libtmux.org/en/lisp/latest/reference/libtmux-refresh/) to acquire a replacement observation. See the native [command](https://github.com/tmux/tmux/blob/3.8/cmd-server-access.c#L61-L152) and [ACL update](https://github.com/tmux/tmux/blob/3.8/server-acl.c#L71-L110). [`list-server-access`](https://libtmux.org/en/lisp/latest/reference/libtmux-list-server-access/) returns ordinary UTF-8 listing text and an outcome. Native rows use `(R)`/`(W)` before 3.8 and include `U`/`G` account kinds from 3.8. UID 0 is omitted; a failed name lookup prints `unknown`. Empty text cannot establish that nobody has access. This listing is intended for inspection and is not a decoded authorization model. Tests cover entry lifecycle, literal input, version gates and the frontend flag effect on private sockets under one OS user. Cross-user connection, authorization and revocation behavior remain unverified by this library's suite. No test changes users, socket permissions or privileges. Calls retain uncertain outcomes and never retry automatically. ## Wait channels [`wait-for`](https://libtmux.org/en/lisp/latest/reference/libtmux-wait-for/) accepts `:operation :wait` (the default), `:signal`, `:lock` or `:unlock`, and a nonempty literal channel name. Signal and unlock work on the supported tmux floor. Wait and lock require tmux 3.8's client-loss cleanup; older daemons signal [`unsupported-capability`](https://libtmux.org/en/lisp/latest/reference/libtmux-unsupported-capability/) before dispatch. This is a capability restriction, not a change to the core tmux floor. A signal wakes existing waiters. With none waiting, one signal leaves a pending wake; a second can remove it. This is neither an idempotent latch nor a counting semaphore. Use an application protocol when acknowledgements or sender identity matter. Server shutdown can also flush waiting requests. Locks belong to the daemon channel, not a Lisp client or thread. They survive a completed call and client closure within the daemon's lifetime. Unlock explicitly; no resource macro or automatic cleanup unlocks them. Signals and locks share native channel state, so use different channel names for those protocols. Each call blocks its caller. Use a second connection client to signal or unlock another client's pending operation. Timeout and byte limits include preflight. Cancellation removes the owned command process; tmux 3.8 removes a disconnected queued waiter. Cancellation or lost receipts cannot prove that a signal was not consumed or a lock was not granted. Inspect the retained outcome and coordinate recovery through the application; the library does not retry a channel request. See [`cmd_wait_for_client_lost`](https://github.com/tmux/tmux/blob/3.8/cmd-wait-for.c#L483-L515). ## Links and pane placement [`link-window`](https://libtmux.org/en/tmux/latest/manual/link-window/) and [`move-window`](https://libtmux.org/en/tmux/latest/manual/move-window/) take a source **window link**, a destination session and an explicit free index. The source link preserves session group context. Neither overwrites an occupied index. Moving removes only the selected source link; removing a session's last link can destroy that session. [`unlink-window`](https://libtmux.org/en/tmux/latest/manual/unlink-window/) refuses a last reference unless `:kill-if-last t` is explicit; tmux session groups affect its last-reference check. [`swap-windows`]() takes two links and returns their replacement links in argument order. `:select t` selects the affected indexes; omission retains current indexes. An old link whose index now points at another window is stale. [`move-pane`](https://libtmux.org/en/tmux/latest/manual/move-pane/) and [`join-pane`](https://libtmux.org/en/tmux/latest/manual/join-pane/) take source and destination physical panes. `:direction`, `:size`, `:percent`, `:before` and `:full-size` control placement; `:detached t` is the default. An emptied source window and all its links are destroyed. With `:detached nil`, tmux chooses the session context of the physical destination. Percentage sizing follows the installed tmux semantics: with `:full-size t`, 3.3a uses the destination pane's dimensions while 3.8 uses the whole window. Use `:size` for cell-based control. [`break-pane`](https://libtmux.org/en/tmux/latest/manual/break-pane/) takes the pane, its explicit source link, a destination session and a free index. This source link matters when the pane is its window's only pane: tmux moves that link while preserving other links and the physical window. The outcome contains the destination link. Its optional `:name` is literal. Tmux 3.7 has an inverted [new-window](https://libtmux.org/en/tmux/latest/manual/new-window/) naming branch, corrected in 3.7a. The library selects the safe branch from the live pane count within the guarded request. For a new window with an explicit name, it follows the break with [`rename-window`](https://libtmux.org/en/tmux/latest/manual/rename-window/) targeting the moved pane. This adds no subprocess or retry, but can run an [`after-rename-window`](https://libtmux.org/en/tmux/latest/catalog/hooks/after-rename-window/) hook and is not transactional: a failing hook can leave the pane moved and renamed. Inspect the retained command outcome before deciding what to do next. Unnamed single-pane moves keep their existing name and automatic-rename setting. See [`cmd_break_pane_exec` in 3.7](https://github.com/tmux/tmux/blob/3.7/cmd-break-pane.c#L116-L123) and [the corrected 3.7a branch](https://github.com/tmux/tmux/blob/3.7a/cmd-break-pane.c#L116-L123). [`swap-panes`]() preserves physical IDs and returns replacements in argument order. [`rotate-window`](https://libtmux.org/en/tmux/latest/manual/rotate-window/) moves pane positions `:up` (default) or `:down` and advances the active pane. Both accept `:preserve-zoom t`. Placement operations preserve older observations; a successful operation may have a separate observation failure. Unlink has no replacement to observe. ## Pane processes [`respawn-pane`](https://libtmux.org/en/tmux/latest/manual/respawn-pane/) restarts its process. [`respawn-window`](https://libtmux.org/en/tmux/latest/manual/respawn-window/) restarts the physical window as one pane, retaining its first pane and destroying every other pane, including through other session links. Neither kills a running process unless `:kill t` is supplied. Omit both `:command` and `:shell-command` to reuse the stored command; explicit argv, shell text, cwd and environment follow creation. By default, [`outcome-value`]() supplies a new observation. Earlier captures remain local and unchanged. A fast-exiting process can disappear before acquisition. [`pipe-pane`](https://libtmux.org/en/tmux/latest/manual/pipe-pane/) accepts explicit `:shell-command` source. It preserves literal `#` and `%` text by default; `:format-p t` enables tmux format and time expansion before the shell runs. Literal commands must fit tmux's 8,191-byte time-format capacity after format escaping. `:direction` is `:output` (pane to shell), `:input` (shell to pane) or `:both`. Omitting the command closes the pipe. `:toggle t` closes an existing pipe without starting its replacement. Pipe success acknowledges configuration. It does not prove shell startup, command success or data consumption. Coordinate dependent work through an application signal. Tmux owns the pipe child, and closing the library client leaves it running. Pipe replacement or explicit closure releases the pane's connection to that child. ## Buffers [`list-buffers`](https://libtmux.org/en/tmux/latest/manual/list-buffers/) returns a fresh vector of plists with `:buffer/name`, `:buffer/size` in bytes and `:buffer/created` in Unix seconds. It preserves tmux's listing order, limits decoding to 10,000 rows by default, and returns `#()` when empty. Names are mutable locators, not captured entity identities. [`set-buffer`](https://libtmux.org/en/tmux/latest/manual/set-buffer/) accepts a literal NUL-free string or an octet vector. Octets travel through tmux's stdin buffer loader and preserve NUL and invalid UTF-8. `:append t` is supported for strings. [`show-buffer`](https://libtmux.org/en/tmux/latest/manual/show-buffer/) returns owned octets without decoding. Both accept a literal `:name`; setting without a name creates an automatic buffer. Creation through set/load rejects backslashes, ASCII controls and DEL before I/O because tmux versions otherwise rewrite or reject them. Spaces and Unicode are supported. Looking up an existing buffer preserves its actual name. [`show-buffer`](https://libtmux.org/en/tmux/latest/manual/show-buffer/) uses one ordinary unattached tmux client with stdout reserved for buffer bytes. The same live server guard, operation budget and alias audit apply. It succeeds only after normal process completion and complete pipe draining, with exit zero and empty stderr. A hook cannot hide an error by changing its exit status. Post-launch read failure retains partial output and diagnostics; it does not prove that the read was never dispatched. Buffer contents are never parsed as receipts or errors. This contract does not apply to control mode. [`load-buffer`](https://libtmux.org/en/tmux/latest/manual/load-buffer/) and [`save-buffer`](https://libtmux.org/en/tmux/latest/manual/save-buffer/) take literal file-path strings; `:name` selects the buffer. Save overwrites unless `:append t` is supplied. Relative paths follow the tmux command client's cwd. A literal file named `-` must be spelled `./-`; use set/show for in-memory bytes. Empty input or an empty loaded file is tmux's no-op and preserves any existing buffer. Save success records tmux's reported completion; it does not independently verify file contents or durability. Tmux 3.3a can suppress some file-write errors. [`paste-buffer`](https://libtmux.org/en/tmux/latest/manual/paste-buffer/) takes a physical pane and optional `:name`. Newlines become Return by default. `:raw t` preserves them; a separate `:separator` string replaces each newline. `:bracketed t` requests bracket markers when the pane enabled bracketed paste. `:verbatim t` preserves all bytes, disables newer tmux control-byte sanitization, and cannot add separators or bracket markers. It chooses flags from the live daemon version. `:delete t` removes the selected buffer after pasting. Input disablement and application consumption remain tmux/application concerns. [`delete-buffer :name`](https://libtmux.org/en/tmux/latest/manual/delete-buffer/) requires tmux 3.4 or newer. On 3.3a it signals [`unsupported-capability`]() before dispatch, because that release can delete an unrelated top buffer when a named buffer is missing. The condition exposes [`error-feature`](), [`error-required-version`]() and [`error-actual-version`](); the check uses the live daemon's version, independently of the selected executable. Omitting a buffer name for show, save, paste or delete uses tmux's default: the newest buffer on 3.3a, and the newest **automatic** buffer from 3.4 onward. It does not select the newest explicitly named buffer on modern tmux. ## Configuration inspection [`show-options-text`](https://libtmux.org/en/lisp/latest/reference/libtmux-show-options-text/), [`show-hooks-text`](https://libtmux.org/en/lisp/latest/reference/libtmux-show-hooks-text/) and [`show-environment-text`](https://libtmux.org/en/lisp/latest/reference/libtmux-show-environment-text/) return native UTF-8 printer text and a retained command outcome. Supply an explicit scope; local tables require the matching captured target. Normal client identity, preflight, last-moment target guards, cancellation and resource limits apply. These functions acquire state and do not change earlier observations. This function from the [executed example package](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/examples/workspace.lisp) returns three printer views plus their diagnostic outcomes. The [REPL tutorial](https://libtmux.org/en/lisp/latest/guides/tutorial/) supplies the package's `tm` nickname and setup. ```lisp (defun configuration-report (client session) "Read native configuration text; return an owned report and diagnostic outcomes." (let ((outcomes nil)) (flet ((inspect-text (reader &rest options) (multiple-value-bind (text outcome) (apply reader client options) (push outcome outcomes) text))) (let ((report (list :options (inspect-text #'tm:show-options-text :scope :session :target session :name "status" :include-inherited t) :hooks (inspect-text #'tm:show-hooks-text :scope :session :target session) :environment (inspect-text #'tm:show-environment-text :scope :session :target session)))) (values report (coerce (nreverse outcomes) 'simple-vector)))))) ``` Option and hook `:name` is a literal tmux reference: format-looking characters stay literal, while native abbreviations and array-reference syntax still apply. Omit it to list the table. `:scope` selects native flags; tmux can infer a built-in option's table from its name instead. These raw inspectors do not enforce the typed mutation catalog's name/type restrictions. A physical window's native format context need not identify one particular captured session link; use a session target for session-table inspection. [`show-options-text`]() exposes `:include-inherited` (`-A`), `:include-hooks` (`-H`) and `:value-only` (`-v`). Native whole-table `-A` output does not promise inherited user `@` options. [`show-hooks-text`]() has no server-scope flag; its named printer can return an ordinary `@` option without proving event registration. Its `:monitors` option selects native monitor presentation. Both printers' `:format` and hook monitor inspection require the live daemon to be tmux 3.8. Format text is explicit tmux source with no implicit time expansion; `#()` can start command-client jobs whose results may be incomplete and whose cleanup follows client disconnect. Environment scopes are `:global` and captured `:session`. Session output is the raw table, not a merged child environment. `:hidden t` selects hidden entries; the default selects ordinary entries. A visibility mismatch may succeed with empty text, which does not prove absence. `:shell-format t` returns native shell-looking text as data. Names may contain spaces, format punctuation and Unicode; only empty text, NUL and `=` are rejected. No reader executes this text. Escaping, whitespace and ordinary hook output stay in the returned presentation. Foreign names and multiline values can make bulk text ambiguous. Missing names remain native errors with outcome evidence; invalid UTF-8 signals a decoding condition retaining the client and original bytes. These operations do not claim parsed values, presence, inherited provenance or complete decoded records. Decoded acquisition remains under development behind its separate [transport admission gate](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/docs/adr/0001-native-common-lisp-engine.md#qualify-control-mode-and-source-filtering-before-exposing-them). ## Configuration mutations [`set-option`](https://libtmux.org/en/lisp/latest/reference/libtmux-set-option/) and [`unset-option`](https://libtmux.org/en/lisp/latest/reference/libtmux-unset-option/) address one explicit tmux option table. Server and global scopes reject a target. Session, window and pane scopes require a captured target of that type, so the normal generation and last-moment target guards apply. The exact live daemon version selects the reviewed option catalog after preflight; the executable's version cannot silently substitute for it. The current catalogs cover the twelve exact supported releases from 3.3a through 3.8. An unreviewed neighboring release signals an error; see [capabilities and current limits](https://libtmux.org/en/lisp/latest/guides/capabilities/). Literal writes validate the catalog's native Boolean, integer, choice and text type before payload dispatch. `nil` is a valid false option value and becomes tmux `off`. `:format-p t` instead accepts explicit tmux format text and lets the daemon validate its expanded value. Registered array elements use `:key` and never allocate a vector by the greatest numeric key. Without `:key`, an array value uses the option's native tmux list grammar and may create several entries; use `:key` when the value must remain one literal entry. `:append t` is accepted only where tmux implements it: strings, keyed string-array values and unkeyed array assignment. Public user-option writes accept the reversible `@[A-Za-z0-9_.-]+` name grammar. `:only-if-unset t` requests a local absent-only write. An inherited value permits a local override. An existing local value, including false or empty text, refuses replacement and retains the normal condition/outcome evidence. With `:key`, tmux tests that array element; other local keys may already exist. Without a key, it tests the local option entry. This condition applies once to the selected command; hooks and later operations remain independent. The default keeps ordinary replacement behavior. [`set-hook`](https://libtmux.org/en/lisp/latest/reference/libtmux-set-hook/) accepts explicit tmux command source; it does not parse the source as Lisp or execute it locally. Ordinary installation never uses tmux's immediate-run flag. Tmux can still run a registered native after-hook such as [`after-set-hook`](https://libtmux.org/en/tmux/latest/catalog/hooks/after-set-hook/) as a consequence of processing the command. Keyed hook entries do not support appending command text; unkeyed append adds hook entries through tmux's array grammar. Use [`unset-hook`](https://libtmux.org/en/lisp/latest/reference/libtmux-unset-hook/) to remove the hook. Registered hooks stay separate from ordinary option writes, and the live catalog gates dynamic event sources. [`set-environment`](https://libtmux.org/en/lisp/latest/reference/libtmux-set-environment/) writes one raw global or captured-session environment table. It accepts literal text by default, explicit format expansion through `:format-p t`, and tmux's hidden flag. [`unset-environment`](https://libtmux.org/en/lisp/latest/reference/libtmux-unset-environment/) deletes an entry; [`remove-environment`](https://libtmux.org/en/lisp/latest/reference/libtmux-remove-environment/) stores tmux's removal marker. A tombstone, absence and a present empty string remain distinct. Names use `[A-Za-z_][A-Za-z0-9_]*` so later named acquisition is reversible. All seven operations return a [`command-outcome`](https://libtmux.org/en/lisp/latest/reference/libtmux-command-outcome/), retain effective arguments and diagnostics, and never retry uncertain mutations. Use the guarded [text inspectors](https://libtmux.org/en/lisp/latest/guides/operations/#configuration-inspection) for native printer views and [decoded configuration](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/docs/configuration.md) for typed, presence-aware reads. Use [`run-command`](https://libtmux.org/en/lisp/latest/reference/libtmux-run-command/) when raw tmux argv and its weaker target contract are intentional. ## Explicit termination [`kill-pane`](https://libtmux.org/en/lisp/latest/reference/libtmux-kill-pane/), [`kill-window`](https://libtmux.org/en/lisp/latest/reference/libtmux-kill-window/) and [`kill-session`](https://libtmux.org/en/lisp/latest/reference/libtmux-kill-session/) accept one captured physical target. The same preflight and last-moment guards used by other named mutations reject a missing, replaced or foreign-generation target. These functions do not expose tmux's broad `-a` forms. Killing a physical window removes all its links; killing a session removes that session's links and any now-unreferenced panes. [`kill-server`](https://libtmux.org/en/lisp/latest/reference/libtmux-kill-server/) is deliberately separate from [`close-client`](https://libtmux.org/en/lisp/latest/reference/libtmux-close-client/). Closing releases library-owned resources and leaves tmux alive. Server termination succeeds only after a pre-dispatch receipt and readiness of the pidfd retained for the daemon accepted during this call's preflight prove both dispatch and process exit. Daemon death without the receipt remains `:unknown` because another actor could have terminated it. After observed death, the client remains open but its generation is invalidated; retained captures still support local traversal and projection. All four operations are destructive and never retry automatically. Select the target once, establish cardinality before dispatch, and inspect a retained condition outcome when completion is uncertain. ## Outcomes and target checks Mutators return [`command-outcome`](). [`outcome-value`]() returns a value and presence as two values. Rename and selection accept `:observe nil` to omit follow-up acquisition. Otherwise a successful mutation followed by failed acquisition still has status `:succeeded`, no value, and an [`outcome-observation-error`](). | Evidence | Outcome | | --- | --- | | Preflight failed or live guard explicitly rejected the target | `:not-dispatched` | | Completion receipt, successful tmux process exit and empty stderr | `:succeeded` | | Completion receipt and nonzero process exit | `:failed` | | Successful process exit with diagnostic output | `:unknown` | | Dispatched operation lacks required receipts, or transport lost completion | `:unknown` | | Server pidfd proves exit but the pre-dispatch receipt is missing | `:unknown` | | Server receipt and retained pidfd both prove explicit termination | `:succeeded`, phase `:server-terminated` | | Server exit wait fails after dispatch | `:unknown`, reason `:server-exit-unobserved`; cause retained | The outcome retains exit status, diagnostic bytes, effective arguments and elapsed time. A failed command queue can have completed effects; it does not imply rollback. An unknown mutation is never automatically retried. Validation checks the live server generation and captured entity identity. The dispatch connection checks the daemon again and checks physical targets or session/index/window mappings immediately before the payload. A reused link index signals [`stale-target`](). These checks do not make selection and mutation a transaction; tmux hooks and other clients can change state. Capture preserves whitespace and trailing lines. The text API strictly decodes UTF-8; decoding failures retain the client and raw command outcome. The byte API copies output without decoding. Its flags expose tmux's history, wrapping, alternate-screen, pending-output and escape modes; they do not reconstruct terminal bytes that tmux did not retain. ## Command aliases and daemon configuration Named APIs require the original built-in command meanings and stable [`command-alias`](https://libtmux.org/en/tmux/latest/catalog/options/command-alias/) configuration during each call. [`show-options`](https://libtmux.org/en/tmux/latest/manual/show-options/) is the trusted bootstrap spelling used to inspect aliases. It must remain the built-in command. The audit cannot safely inspect an arbitrary override of itself or prevent configuration changes after inspection. Before acquisition or a named operation, the library reads [`command-alias`](https://libtmux.org/en/tmux/latest/catalog/options/command-alias/) and signals [`unsupported-configuration`]() for collisions with reserved command names. [`error-setting`]() identifies the option; [`error-commands`]() returns the colliding spellings. The reserved list is maintained in [command-policy.lisp](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/src/command-policy.lisp). Other alias names are allowed. The check conservatively rejects an apparent reserved assignment on any line of a multiline alias value. Configured tmux hooks keep their ordinary behavior. The library does not remove aliases or hooks from the user's daemon. [`run-command`]() remains an explicit raw escape hatch with tmux's command grammar and no captured-target or alias audit. --- # Capabilities and limits Source: https://libtmux.org/en/lisp/latest/guides/capabilities/ > Capabilities and limits: libtmux documentation. The implemented systems suit bounded, blocking SBCL/Linux tmux automation with captured graph traversal, native queries, explicit commands and declarative workspace bootstrap. Persistent control requests, explicit reconnection and pane-output events are optional on tmux 3.8+. Ordinary notifications retain raw bytes; pane output is decoded to octets. Larger stream and retained-memory benchmarks remain open. | Area | Available now | Remaining work | | --- | --- | --- | | Runtime | SBCL 2.6.10, ASDF/UIOP 3.3.7, Linux x86-64, kernel 6.9+, Bash | Other implementation/OS support | | Acquisition | Finite CLOS snapshots, explicit window links, identity, bounds and replacement observations | Complete acceptance after further operations | | Local processing | Ordinary lists/vectors, functions, owned plists, presence via multiple values, validated relation/regex queries and eager QuerySets | Source-side filter lowering | | Operations | Creation, rename, selection, geometry, placement, input, buffers, respawn, shell pipes, decoded configuration reads, native printer inspection and typed mutations, captured terminal-client requests, session/server frontend locking, message/command/key printing, key binding/removal, server ACL entries, wait channels (blocking wait/lock require 3.8), explicit entity/server termination and private empty-config daemon scopes | Remaining named server-command helpers | | Concurrency | Synchronous core calls; one operation per client; optional nonblocking control submission/event waits and explicit generation-changing reconnect | Larger control workloads | | Streaming/events | Bounded captures and byte buffers; daemon-owned [`pipe-pane`](https://libtmux.org/en/tmux/latest/manual/pipe-pane/); optional raw notifications and native pane-output events with gap/end results and bounded drop-oldest delivery | Long-running streams and retained-memory benchmarks | | Workspace | Separate bounded JSON/native system, pure plans, explicit apply, topology reuse and explicit-endpoint CLI | Owned declaration/ID metadata, drift inspection, richer configuration and explicit reconciliation; deletion and process supervision remain outside v1 | | MCP | Intentionally excluded by project policy | Use a separately maintained external integration if one becomes suitable | | Optional integrations | Separate CL-PPCRE regex, bounded query JSON, workspace/CLI and scoped native control connections | Broader mode benchmarks | | Performance | [Optional native read-path comparison](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/docs/benchmarks.md) with separate acquisition/startup, launch counts and allocation samples | Large-graph, query and stream workloads; retained heap and broader latency qualification | The process backend services stdin/stdout/stderr through nonblocking descriptors and a private poll loop. Public operations still block their caller. The library has no Python [`asyncio`]() API or event-loop integration; applying an asyncio correctness rating to this native Common Lisp core would be misleading. Independent clients can perform independent operations, but their identity contexts differ. Do not pass one client's observations to another for mutation. Portable Common Lisp plus ASDF/UIOP is the default dependency boundary. The workspace consumer is a separate `libtmux/workspace` ASDF system over core and the locked Jzon parser; its CLI is another separate system. External libraries stay isolated in the optional systems that justify them. MCP is intentionally outside this port's product scope; it is not an unfinished package or release gate. An agent can still read the [public API](https://libtmux.org/en/lisp/latest/reference/), [query API](https://libtmux.org/en/lisp/latest/reference/), [field registry](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/docs/fields.md) and [tutorial](https://libtmux.org/en/lisp/latest/guides/tutorial/), but these documents are not a protocol server. No terminal output stream or lossless event-replay guarantee is implied by [`pipe-pane`](https://libtmux.org/en/tmux/latest/manual/pipe-pane/). ## Evidence and adoption `mise run examples` executes the shipped Lisp examples on an owned socket with empty tmux configuration. `mise run check` checks unit semantics, exported docstrings, generated API/field/module pages, executable excerpts and local documentation links. `mise run package` checks an isolated ASDF consumer; `mise run integration` exercises the native transport and named operations. See [verification](https://libtmux.org/en/lisp/latest/guides/verification/) for evidence boundaries and commands. The supported tmux range is 3.3a through 3.8, including intermediate releases. CI uses the latest stable SBCL pin and checks only the two endpoints, 3.3a and 3.8, to reduce build time. The library rejects plain 3.3; unit tests check that floor. Runtime flags and some operation semantics depend on the live daemon version as documented in the [operation contracts](https://libtmux.org/en/lisp/latest/guides/operations/). Decoded configuration reads and mutations require one of the twelve exact reviewed catalogs from 3.3a through 3.8; an unreviewed neighboring release is rejected rather than silently borrowing another version's schema. A build-versus-adopt decision should check the platform boundary, required operations, identity policy, synchronous concurrency model, diagnostics and cleanup behavior against the actual application. Teams requiring continuous updates should check the optional control system against their required event coverage and reconnect policy. MCP stays excluded. The single maintainer and unreleased status do not change those technical boundaries. Benchmark evidence must report workload, graph size, tmux/toolchain versions, acquisition counts, warm/cold timing, distributions and allocations. Test-loop timings measure verification cost; they are not throughput benchmarks. --- # Testing Source: https://libtmux.org/en/lisp/latest/guides/testing/ > Testing: libtmux documentation. Use FiveAM directly at the REPL for local tests. Use the separate command for live tmux tests so the process owner can check fixture cleanup. ## Run and redefine a local test Install the pinned tools and locked dependencies following the [contributor guide](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/.github/CONTRIBUTING.md), then start a REPL: ```console $ mise run repl ``` Load the unit tests: ```lisp (asdf:load-system "libtmux/tests") ``` Run one existing test: ```lisp (fiveam:run! 'libtmux.tests::record-codec-preserves-text-and-presence) ``` Re-evaluate its changed test definition in the editor, then rerun that name: ```lisp (fiveam:!) ``` [`fiveam:!!`](https://github.com/lispci/fiveam/blob/3ba1300a9dd6bafdaef2af76d261d73bdf7e4bf0/src/run.lisp) and [`fiveam:!!!`](https://github.com/lispci/fiveam/blob/3ba1300a9dd6bafdaef2af76d261d73bdf7e4bf0/src/run.lisp) rerun the second and third previous selections. These rerun functions do not shift their history. To enter the debugger on an unexpected condition, run the test with `debug!`: ```lisp (fiveam:debug! 'libtmux.tests::record-codec-preserves-text-and-presence) ``` FiveAM supplies `retest` and `ignore` restarts while the condition is active. Use the complete unit suite after a focused change: ```lisp (fiveam:run! 'libtmux.tests::unit) ``` Load and run the separate native-query suite without contacting tmux: ```lisp (asdf:test-system "libtmux/query") ``` For interactive redefinition, load the test system explicitly: ```lisp (asdf:load-system "libtmux/tests/query") ``` ```lisp (fiveam:run! 'libtmux.tests::query) ``` The optional systems keep their own suites and remain local: ```lisp (asdf:test-system "libtmux/regex") ``` ```lisp (asdf:test-system "libtmux/json") ``` The pinned FiveAM 1.4.3 compiles test bodies at definition time by default. After changing a macro used by a test, re-evaluate that test definition too. For a test specifically studying macro redefinition, `def-test` supports the `:compile-at :run-time` option. The upstream homepage's older macro-redefinition description does not describe this default. See the pinned [test definitions](https://github.com/lispci/fiveam/blob/3ba1300a9dd6bafdaef2af76d261d73bdf7e4bf0/src/test.lisp) and [runner](https://github.com/lispci/fiveam/blob/3ba1300a9dd6bafdaef2af76d261d73bdf7e4bf0/src/run.lisp). `run!` returns a Boolean; scripts must check it to return a failing process status. Unknown names and entirely skipped selections can report no failures, so select a known test or suite and read the check and skip counts. The shipped runner selects declared suites and checks the result before exiting. The mid loop runs the five local suites in one image, loading each system just before its suite. Core and query tests therefore run before optional libraries load. A failure stops the remaining suites. Package-consumer checks still use isolated images to verify loading boundaries. Live CLI apply/reapply runs through the extracted package's entry point in `mise run package-examples`; core workspace behavior stays in integration. ## Run external tests Run all local checks without contacting tmux: ```console $ mise run check ``` Run only the native-query inner loop: ```console $ mise run query ``` Run the regex or JSON inner loop: ```console $ mise run regex ``` ```console $ mise run json ``` Run the owned live fixtures with one explicit tmux executable: ```console $ LIBTMUX_TEST_TMUX=/path/to/tmux mise run integration ``` This outer loop uses a dedicated process owner and subreaper. It checks that no test-owned child or daemon survives the Lisp image. Tests coordinate through FIFOs, semaphores, pidfds and application signals. A send acknowledgment alone does not establish application consumption. See [verification](https://libtmux.org/en/lisp/latest/guides/verification/) for the loop budgets and current coverage. ## GitHub Actions The workflow uses the same mise tasks, pinned SBCL/ASDF, Qlot lock and FiveAM suites as local development. The supported tmux range is 3.3a through 3.8, including every intermediate release. CI samples only the two endpoints to reduce build time; that selection does not narrow compatibility. It uses one language version: the latest stable SBCL pin in [.tool-versions](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/.tool-versions), currently 2.6.10. Pushes to `master`, pull requests and manual runs check tmux 3.3a and 3.8. Unit tests cover rejection of plain 3.3 without another tmux build. The explicit Ubuntu 26.04 image supplies the Linux capabilities required by the process backend. Run the same endpoint checks manually: ```console $ gh workflow run tests.yml \ --ref master ``` Qlot dependencies use a cache keyed by the toolchain configuration and lock. It contains both `.qlot` and Qlot's shared cache, because installed systems link to source directories outside the project. Tmux builds use exact keys containing source/archive identities, build recipe, runner image, architecture, compiler and dependency versions. Runtime libraries are installed before restoring a binary. Reuse checks provenance, binary hash, ELF architecture and the exact version. A rejected cached artifact is rebuilt. Only a build that passes its Lisp checks is saved. Workflow artifacts retain build provenance, logs and the isolated package evidence. A passing save step alone does not prove that GitHub stored a cache; a second run of the same revision must restore the exact entry and report `reused: true` with no configure or make commands. GitHub cache entries are immutable; rebuilding a rejected exact hit cannot replace that entry. --- # Verification evidence Source: https://libtmux.org/en/lisp/latest/guides/verification/ > Verification evidence: libtmux documentation. Run commands from the repository root after `mise install` and `mise run setup`. Provisioning uses the network; verification does not install dependencies. `mise run integration` runs transport, core operations, control, decoded configuration and workspace groups in separate serialized child processes. Each retains the same 60-second cap and owned-process cleanup checks. ASDF test-op includes all five groups too. | Command | Tier | Current coverage | | --- | --- | --- | | `mise run unit` | Inner | Resource scopes, finite inputs, row/value decoding, graph relations, configuration schema/scope dispatch and defensive ownership | | `mise run query` | Inner | Scalar/reference agreement, relations, QuerySets, malformed data, limits, ownership and cardinality | | `mise run regex` | Inner | Optional CL-PPCRE search/full-match semantics, options, ownership and retained causes | | `mise run json` | Inner | Optional bounded filter documents, allowlists, false/empty/null distinctions and regex opt-in | | `mise run control` | Inner | Local codecs, byte fidelity, queued/active cancellation, terminal races, CLOS resource identity, live-witness admission, raw notification/reply routing and lexical cleanup; no live connection | | `tools/lisp.sh tools/test.lisp control-integration` | Outer | Persistent requests/events on 3.8, raw topology/configuration/flow notifications, binary pane output, reply separation, public cancellation, environment preservation, explicit reconnect/recapture without replay, cutover cancellation/cleanup recovery and 3.3a pre-launch rejection | | `tools/lisp.sh tools/test.lisp configuration-integration` | Outer | Decoded reads on both tmux endpoints, false/absence, sparse keys, inheritance/visibility races, partial evidence, cancellation and truncation | | `mise run check` | Mid | Source whitespace, all local unit suites, exported docstrings, generated references, excerpts and local documentation links | | `mise run lint-workflows` | Mid | GitHub Actions syntax, expressions and shell checks | | `mise run integration` | Outer | Bounded transport, cancellation/cleanup, managed daemon startup/shutdown and failure recovery, linked graphs, attached clients, daemon replacement, named operations, typed configuration reads/mutations, exact target/server termination and tutorial failure cleanup | | `mise run package` | Outer | Source archive plus isolated core/query/regex/JSON/control consumers and dependency-boundary checks | | `mise run examples` | Outer | Shipped Lisp recipes, all tutorial Lisp blocks, literal console setup/cleanup, worker consumption signals, optional control streaming and owned daemon cleanup | | `mise run package-examples` | Outer | Archive-local links, examples, and the installed workspace CLI's validate/plan/apply/reapply workflow | | `mise run docs` | Maintenance | Regenerate API signatures/docstrings, condition hierarchy, applicable models, fields, modules and executable excerpts | Performance uses a separate tier: `mise run benchmark` compares identical native reads and measures graph acquisition separately. It is not run by CI or these test loops. The mid loop includes only the reporter's four critical evidence checks. See the [benchmark guide](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/docs/benchmarks.md) for sampling, owned cleanup, source identity, resource accounting and interpretation limits. These commands exercise the implemented native core; they do not establish full-suite compatibility. New operation tests extend the integration suite as those APIs are implemented. `check` detects generated-document drift without rewriting files. `docs` deliberately rewrites generated pages and excerpts. Complete catalog reads exercise many native commands. Each of the five integration processes retains the same 60-second ceiling; the outer task runs every group and cleanup checks. No formatting tool is prescribed for Common Lisp; source checks and SBCL compilation catch whitespace and reader/compiler errors respectively. The [testing guide](https://libtmux.org/en/lisp/latest/guides/testing/) describes FiveAM's interactive workflow and the exact tmux build cache. Hosted results must identify the tested commit; local package or integration success does not establish a hosted CI result. ## Recorded local compatibility The [native matrix record](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/data/native-compatibility.json) identifies the exact source revision, native source/binary identities, runtime and per-group results. On 2026-11-09, the existing native integration suite at [0e1a6f3](https://github.com/libtmux/libtmux-lisp/tree/0e1a6f3eed926b5470692e098414f1b91acf7466) passed on all twelve supported releases: 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c and 3.8. The two-cell local sweep took 236.28 seconds, including startup. All cells checked owned-process cleanup. Eleven control cases are explicitly skipped below the optional feature's 3.8 floor; older-daemon refusal is tested. This record covers the implemented native integration surface at its pinned revision. It does not establish completion of the unfinished suite. CI continues to test only 3.3a and 3.8; source-archive consumers and complete examples have separate endpoint evidence. The record's timings are verification costs, not performance measurements or latency guarantees. ## Source archives and consumers The package command writes `dist/libtmux-source.tar.gz` and `dist/package-evidence.json`. The consumer loads the extracted artifact from a temporary directory with an isolated ASDF source registry and output cache. It checks that query, regex, JSON and thread packages were not loaded by core, then loads `libtmux/query` separately without pulling those optional libraries. Separate consumers load and exercise `libtmux/regex` and `libtmux/json` through the same locked dependency setup. The artifact includes its setup pins, tools, guides and executable examples. `package-examples` checks its local links and runs its own copy of the example runner with an isolated core registry. `dist/examples-evidence.json` records the runtime versions, source hashes, tutorial block count and cleanup result. The installed workspace entry point reads the shipped JSON file, creates its declared live topology, and reports zero retained mutations on repeat apply. These checks qualify the implemented v1 behavior; full ownership metadata, drift inspection and explicit reconciliation remain separate unfinished work. The [tutorial](https://libtmux.org/en/lisp/latest/guides/tutorial/) explains the recipes and expected values. Select a tmux binary explicitly when exercising a compatibility cell: ```console $ LIBTMUX_TEST_TMUX=/path/to/tmux mise run integration ``` Support covers every tmux release from 3.3a through 3.8. CI uses the latest stable SBCL pin and tests only tmux 3.3a and 3.8 to reduce execution time. Intermediate releases remain supported; they are not additional CI jobs. The fixture uses a unique `libtmux-lisp-` temporary directory, an explicit socket path and `/dev/null` configuration. Child environments omit `TMUX` and `TMUX_PANE`. A pidfd verifies termination before owned files are removed. Tests never address the default server. Attached-client tests launch ordinary PTY clients through SBCL's fork path, without control mode. They join captured PIDs to retained child pidfds, observe native SIGTSTP stops, and use an owned bounded FIFO lock command. Request acknowledgements and frontend effects have separate assertions. Partial acquisition and nonlocal exits check cleanup. Session/server locking uses two ordinary frontends in separate sessions. Native suspended flags establish recipient scope, and independent FIFO readiness proves each requested lock program started before owned cleanup releases it. Wait-channel tests prove a waiter is queued through native listing before cancellation, then inspect the daemon's remaining queue. Lost lock acknowledgements remain separate from queued-waiter disposal; neither closing nor cancellation automatically releases a granted lock. Message-printing tests distinguish literal and explicit format input, physical and link context, foreign authority, versioned intermediate limits and after-hook text in the ordinary printer stream. Key-binding tests send bytes through an owned ordinary PTY frontend. Native hook/channel acknowledgements prove stored command execution and order a second key after removal. Direct fixture reads verify literal values and parse-time environment effects retained after a failed binding. Printer profiles distinguish tmux 3.7 through 3.7c's single-row status routing and missing-key success from normal multi-row output and other releases. Server-access fixtures use the existing `nobody` account and its primary group on private sockets, without changing OS identities or permissions. They prove entry lifecycle, literal names, group gating and tmux 3.8's recomputation of an unrelated owner's frontend flags. Cross-user authorization, connection and revocation remain unverified. The integration runner is a dedicated child-process subreaper. It verifies that no test-owned worker or tmux daemon survives the test image, including an injected tutorial failure before its Lisp termination call. Production loading does not change the application's process-reaping policy. Readiness FIFOs, semaphores and pidfds coordinate tests without polling sleeps. Unit tests cover floor rejection without adding a third tmux build to CI. For an optional maintenance probe, use the exact 3.3 executable: ```console $ mise exec -- tools/lisp.sh tools/check-floor.lisp /path/to/tmux-3.3/tmux ``` This check asserts the executable's release, expects [`unsupported-tmux`](), and checks that opening the client created no daemon socket. Broader stream/retained-memory qualification, source filtering, shared-site rendering, broader benchmarks and integrated acceptance of all required capabilities on both CI endpoints remain pending. MCP is excluded by policy. The [capability table](https://libtmux.org/en/lisp/latest/guides/capabilities/) separates implemented behavior from those gaps. A passing core package or example does not cover them. --- # Linking and consuming our docs Source: https://libtmux.org/en/lisp/latest/meta/linking/ > Find documentation exports for search engines, bots, LLMs and other documentation sites. We welcome bots, search engines, SEO tools, LLMs and other clients to use our documentation. Read a page in your browser, fetch its Markdown, discover pages through a manifest, or link to documented objects from another site. ## Choose an export The page footer links to the exports for its documentation tree. Use those links to select a language, version or preview. | File or format | Use it for | | --- | --- | | [llms.txt](https://libtmux.org/en/llms.txt) | Discover guides, API references and agent prompts through a Markdown index. | | [llms-full.txt](https://libtmux.org/en/llms-full.txt) | Fetch guides and MCP contracts together as Markdown. API references are linked separately. | | [docs.json](https://libtmux.org/en/docs.json) | Discover page titles, URLs, descriptions, headings and Markdown URLs in a structured manifest. | | Markdown | Read one page as text using its footer's **Markdown** link. | | Raw source | Inspect the source file using its footer's **Raw source** link, where available. | | [objects-full.inv](https://libtmux.org/en/objects-full.inv) | Resolve Sphinx references across shared docs, each port's default version, or its latest until the default has published a catalog, and tmux reference versions. | | objects.inv | Resolve Sphinx references within one port and version, or one tmux documentation tree. | | [targets.json](https://libtmux.org/en/targets.json) | Browse inventory keys, display names and destination URLs as JSON. A scoped catalog sits beside each root inventory. The root one lists fallbacks and ports left out. | The root exports above cover the assembled site. Port exports describe the selected port and version. For a single API declaration, start with `docs.json` and follow its reference and symbol-index links. ## Link from Sphinx The [Intersphinx guide](https://libtmux.org/en/meta/intersphinx/) explains how to configure inventories, choose native roles or portable labels, and preserve references when a page moves or changes its title. Browse [Intersphinx examples](https://libtmux.org/en/meta/intersphinx/examples/) for copyable RST and MyST references to library APIs, tmux C declarations, hooks, format variables, CLI commands and schema fields, paired with their destinations. ## Discover and index pages Use the [sitemap](https://libtmux.org/en/sitemap-index.xml) to discover indexable pages. Follow [robots.txt](https://libtmux.org/robots.txt) and each page's robots metadata: pull-request previews, component demos and search-index shards are excluded from crawling, and alternate versions can carry `noindex`. Choose a versioned documentation tree when a reference needs a particular release. The full inventory follows each port's advertised default; a port's `objects.inv` describes the selected version. --- # Link to libtmux from Sphinx Source: https://libtmux.org/en/lisp/latest/meta/intersphinx/ > Use Intersphinx inventories to reference libtmux APIs and documentation. Sphinx can resolve references to libtmux APIs, guides, headings, MCP objects, schema fields, and other documented targets. The footer links to the full inventory and the inventory for the page's port or tmux version. Browse [Intersphinx examples](https://libtmux.org/en/meta/intersphinx/examples/) for copyable RST and MyST references paired with their destinations. For Markdown exports, manifests and crawler discovery, see [Linking and consuming our docs](https://libtmux.org/en/meta/linking/). ## Configure Intersphinx Enable the built-in extension in your project's `conf.py`: Python's [`None`]() discovers `objects.inv` at the configured base URL. The port mapping above selects the Python `latest` tree. Copy the base URL of another port or version to select its inventory. The full inventory includes shared documentation, the configured default version of each assembled port, and distinct tmux reference versions. It is generated after site assembly. Each port inventory includes the rendered targets in that exact version. The tmux inventory includes the shared tmux guides and the latest CLI manual, options, hooks, formats, and C source reference. A version mapping such as `tmux-3-7c` includes only that release's manual, catalog, and C declarations. Manual and C reference pages link to the same version inventory in the footer. Use mapping names without dots when writing MyST reference roles. ## Choose a reference Every exported target has a portable label that works with `std:ref`, without installing a language or MCP domain. Open `targets.json` beside the inventory and copy its `key`. Its `href` and `display` identify the destination. For example, the Python [`libtmux.Pane`]() declaration has this reStructuredText reference: ```rst :external+libtmux:ref:`Pane ` ``` With MyST enabled in your project, the same label works in Markdown: ```markdown {external+libtmux:ref}`Pane ` ``` Native Python, C++, JavaScript, and C objects also have aliases for their built-in roles. A scoped Python inventory supports: ```rst :external+libtmux-python:py:class:`libtmux.Pane` ``` The tmux mapping provides native C roles and portable manual references: ```rst :external+tmux:c:struct:`cmd` :external+tmux:ref:`capture-pane ` ``` Command options also work with Sphinx's program context: ```rst .. program:: tmuxp load :external+libtmux-python:option:`--append` ``` The full inventory qualifies native names by port, such as `py:libtmux.Pane`. A fully qualified alias includes the product too: `py:core:libtmux.Pane`. Short aliases are offered only when they identify one destination. Configuration values, command options, environment variables, and glossary terms use standard-domain types where their renderers identify those semantics. ## Stable labels A portable key contains `scope:product:category:identity`. Its encoding preserves uppercase letters, punctuation, and Unicode, because `std:ref` lowercases targets. `%50ane` and [`pane`]() therefore remain distinct. Copy the key from the catalog without decoding its percent escapes. Keys are independent of display titles and destination URLs. API public IDs, protocol wire identities, JSON Pointers, named parameters, and native method identities supply their names. Authored documents can retain `inventoryId` and explicit HTML fragment IDs when their titles or routes change. Contributors preserve renamed keys in the inventory alias catalog and use the existing page redirect mechanism for retired routes. ### Preserve published links If `meta/intersphinx.md` moves to `meta/linking.md`, retain its logical ID and list the retired route in the new page's frontmatter: ```yaml inventoryId: intersphinx aliases: - meta/intersphinx ``` The document key remains `site:core:doc:intersphinx`, and the old route redirects to the new page. Retain existing heading fragment IDs when their text changes. When an identity or fragment changes intentionally, map the former keys to current targets in `site/src/data/inventory-aliases.json`. For example, choosing `inventoryId: linking` and changing `stable-labels` to `stable-references` needs these entries: ```json [ { "scope": "site", "from": "site:core:doc:intersphinx", "to": "site:core:doc:linking" }, { "scope": "site", "from": "site:core:heading:intersphinx%23stable-labels", "to": "site:core:heading:linking%23stable-references", "fragment": "stable-labels" } ] ``` The document entry preserves `std:ref` and `std:doc` references. The heading entry preserves `std:ref`; `fragment` also installs the retired HTML anchor at the new destination. Keep the route alias too: inventory aliases do not redirect retired URLs. Copy `to` from the current target catalog. Retain earlier [`from`]() entries and update their destinations when renaming again. Former native role names, such as a Python class name, need preservation in the native metadata. Existing `objects.inv` and `reference/objects.inv` endpoints remain compatibility exports. New integrations should use the full inventory, a port's root inventory, or a tmux root inventory to obtain the complete target catalog and case-safe labels. See the [Sphinx Intersphinx documentation](https://www.sphinx-doc.org/en/master/usage/extensions/intersphinx.html) for named inventories and cross-reference syntax. --- # Intersphinx examples Source: https://libtmux.org/en/lisp/latest/meta/intersphinx/examples/ > Compare Sphinx and MyST references to APIs, tmux C declarations, hooks, format variables, CLI commands and schema fields. import { referenceHref } from '../../../../lib/api-models' Copy a reference and compare its destination below. These examples cover native language roles, portable labels, full and scoped inventories, and links into tmux C declarations, hooks, format variables and CLI commands. ## Configure the inventories Add these mappings to your Sphinx project's `conf.py`: Python's [`None`]() discovers `objects.inv` at the mapping's base URL. The full inventory uses each port's advertised default version. The `py` mapping explicitly selects `latest`; `tmux` selects the latest tmux manual and C reference. Use the root URL from an inventory's footer link when selecting a preview or another release. ## Native Python roles A named inventory selects the Python tree: ```rst :external+py:py:class:`libtmux.Pane` ``` Destination: [Pane](https://libtmux.org/en/py/latest/reference/libtmux-pane/#libtmux.Pane). The full inventory qualifies native names by port: ```rst :external+full:py:class:`py:libtmux.Pane` ``` Destination: Pane in the Python default version. For automatic lookup, use the ordinary role. A local definition takes precedence over Intersphinx: ```rst :py:class:`libtmux.Pane` ``` Destination: [Pane](https://libtmux.org/en/py/latest/reference/libtmux-pane/#libtmux.Pane). ## Portable labels Portable labels work with the standard reference role, including languages that have no built-in Sphinx domain. Copy the key from `targets.json`. ```rst :external+full:ref:`Pane ` :external+py:ref:`Pane ` ``` Destinations: Full inventory: Pane, [Scoped inventory: Pane](https://libtmux.org/en/py/latest/reference/libtmux-pane/#libtmux.Pane). The external role searches the configured inventories without selecting one. This native name is unqualified only in the scoped Python inventory: ```rst :external:py:class:`libtmux.Pane` ``` Destination: [Pane](https://libtmux.org/en/py/latest/reference/libtmux-pane/#libtmux.Pane). ## MyST roles and inventory URLs Enable `myst_parser` alongside [`sphinx.ext.intersphinx`](https://www.sphinx-doc.org/en/master/usage/extensions/intersphinx.html) for Markdown sources. Named roles accept the same targets as reStructuredText: ```markdown {external+py:py:class}`libtmux.Pane` ``` Destination: [Pane](https://libtmux.org/en/py/latest/reference/libtmux-pane/#libtmux.Pane). Portable labels retain their percent escapes inside reference roles: ```markdown {external+full:ref}`Pane ` ``` Destination: Pane in the Python default version. MyST's inventory URL syntax also supports native aliases: ```markdown [Pane](inv:py:py:class#libtmux.Pane) ``` Destination: [Pane](https://libtmux.org/en/py/latest/reference/libtmux-pane/#libtmux.Pane). Use role syntax for percent-encoded portable keys: MyST normalizes escapes in `inv:` URLs. Keep mapping names free of dots, as in `tmux-3-7c`, so MyST can parse the role name. When using an inventory URL, specify its domain and object type if a name is both a document and a label. ## tmux C declarations The tmux mapping provides Sphinx's built-in C roles: ```rst :external+tmux:c:struct:`cmd` :external+tmux:c:func:`cmd_find_target` ``` Destinations: [cmd](https://libtmux.org/en/tmux/latest/reference/c-struct-cmd/#c%3Astruct%3Acmd), [cmd_find_target](https://libtmux.org/en/tmux/latest/reference/c-function-cmd_find_target/#c%3Afunction%3Acmd_find_target). Select a release with its own mapping. The full inventory also offers version-qualified native aliases: ```rst :external+tmux-3-7c:c:func:`cmd_find_target` :external+full:c:func:`tmux@3.7c:cmd_find_target` :external+full:c:func:`tmux@latest:cmd_find_target` ``` Destinations: [Scoped: cmd_find_target in 3.7c](https://libtmux.org/en/tmux/3.7c/reference/c-function-cmd_find_target/#c%3Afunction%3Acmd_find_target), [Full: cmd_find_target in 3.7c](https://libtmux.org/en/tmux/3.7c/reference/c-function-cmd_find_target/#c%3Afunction%3Acmd_find_target), [Full: cmd_find_target in latest](https://libtmux.org/en/tmux/latest/reference/c-function-cmd_find_target/#c%3Afunction%3Acmd_find_target). MyST supports the same C roles and a typed inventory URL: ```markdown {external+tmux:c:struct}`cmd` {external+tmux:c:func}`cmd_find_target` {external+full:c:func}`tmux@3.7c:cmd_find_target` [cmd_find_target](inv:tmux:c:function#cmd_find_target) ``` Destinations: [MyST: cmd](https://libtmux.org/en/tmux/latest/reference/c-struct-cmd/#c%3Astruct%3Acmd), [MyST: cmd_find_target](https://libtmux.org/en/tmux/latest/reference/c-function-cmd_find_target/#c%3Afunction%3Acmd_find_target), [MyST full: cmd_find_target in 3.7c](https://libtmux.org/en/tmux/3.7c/reference/c-function-cmd_find_target/#c%3Afunction%3Acmd_find_target), [Inventory URL: cmd_find_target](https://libtmux.org/en/tmux/latest/reference/c-function-cmd_find_target/#c%3Afunction%3Acmd_find_target). The C reference links each declaration to its source file and revision. Portable labels also address its signatures, parameters, and sections. ## tmux hooks Link a hook name to its catalog page with a portable label. The same key works in the scoped tmux inventory and the full inventory: ```rst :external+tmux:ref:`after-new-session ` :external+full:ref:`after-new-session ` ``` Destinations: [Scoped: after-new-session](https://libtmux.org/en/tmux/latest/catalog/hooks/after-new-session/), [Full: after-new-session](https://libtmux.org/en/tmux/latest/catalog/hooks/after-new-session/). In MyST, use the same key inside a reference role. Preserve its percent escapes: ```markdown {external+tmux:ref}`after-new-session ` {external+full:ref}`after-new-session ` ``` Destinations: [MyST scoped: after-new-session](https://libtmux.org/en/tmux/latest/catalog/hooks/after-new-session/), [MyST full: after-new-session](https://libtmux.org/en/tmux/latest/catalog/hooks/after-new-session/). ## tmux formats Link a format variable such as [`#{session_name}`](https://libtmux.org/en/tmux/latest/catalog/formats/session_name/) to its catalog entry: ```rst :external+tmux:ref:`session_name ` :external+full:ref:`session_name ` ``` Destinations: [Scoped: session_name](https://libtmux.org/en/tmux/latest/catalog/formats/session_name/), [Full: session_name](https://libtmux.org/en/tmux/latest/catalog/formats/session_name/). The MyST references select the same scoped or full inventory: ```markdown {external+tmux:ref}`session_name ` {external+full:ref}`session_name ` ``` Destinations: [MyST scoped: session_name](https://libtmux.org/en/tmux/latest/catalog/formats/session_name/), [MyST full: session_name](https://libtmux.org/en/tmux/latest/catalog/formats/session_name/). ## tmux CLI manual The same tmux inventory covers command pages: ```rst :external+tmux:ref:`capture-pane ` ``` Destination: [capture-pane](https://libtmux.org/en/tmux/latest/manual/capture-pane/). The document role works when the target is a whole page: ```rst :external+tmux:doc:`Full manual ` ``` Destination: [Full tmux manual](https://libtmux.org/en/tmux/latest/manual/full/). Select a command's manual page with the document role. Its portable key also works in the full inventory: ```rst :external+tmux:doc:`tmux new-session ` :external+full:doc:`tmux new-session ` ``` Destinations: [Scoped: new-session](https://libtmux.org/en/tmux/latest/manual/new-session/), [Full: new-session](https://libtmux.org/en/tmux/latest/manual/new-session/). Use the same document role in MyST: ```markdown {external+tmux:doc}`tmux new-session ` {external+full:doc}`tmux new-session ` ``` Destinations: [MyST scoped: new-session](https://libtmux.org/en/tmux/latest/manual/new-session/), [MyST full: new-session](https://libtmux.org/en/tmux/latest/manual/new-session/). ## Command options, environment variables, and terms The Python port's workspace documentation exports standard-domain objects. Set the program context before referencing an option: ```rst .. program:: tmuxp load :external+py:option:`--append` ``` Destination: [tmuxp load --append](https://libtmux.org/en/py/latest/workspace/cli/load/#arguments-and-flags-a-append). Environment variables and glossary terms use their own roles. Qualify the product when a term appears in more than one glossary: ```rst :external+py:envvar:`COLUMNS` :external+py:term:`async ` ``` Destinations: [COLUMNS](https://libtmux.org/en/py/latest/workspace/configuration/environment/#cli-and-runtime-variables-columns-lines-rows), [async](https://libtmux.org/en/py/latest/reference/glossary/#async). ## MCP tools and schema fields Portable labels also address protocol objects and individual JSON Schema fields. No custom MCP domain is required: ```markdown {external+py:ref}`Read tools batch ` {external+py:ref}`on_error ` ``` Destinations: [Read tools batch](https://libtmux.org/en/py/latest/mcp/tools/call_read_tools_batch/#mcp.tool.call_read_tools_batch), [on_error](https://libtmux.org/en/py/latest/mcp/tools/call_read_tools_batch/#mcp.tool.call_read_tools_batch.input%2Fproperties%2Fon_error). ## Shared documentation and headings The full inventory includes the shared documentation: ```rst :external+full:doc:`Inventory guide ` :external+full:ref:`Configure Intersphinx ` ``` Destinations: [Inventory guide](https://libtmux.org/en/meta/intersphinx/), [Configure Intersphinx](https://libtmux.org/en/meta/intersphinx/#configure-intersphinx). ## Local files and fallback locations Download an inventory and pass its filename as the second mapping value. The first value remains the documentation URL used in generated links: An ordered list tries a local copy before the HTTP inventory: See the [inventory guide](https://libtmux.org/en/meta/intersphinx/) for key stability and the [Sphinx Intersphinx reference](https://www.sphinx-doc.org/en/master/usage/extensions/intersphinx.html) for configuration options. --- # AI policy Source: https://libtmux.org/en/lisp/latest/meta/ai-policy/ > Use the libtmux packages with AI tools. When contributing, check what you submit and wait for a maintainer's invitation before preparing a pull request. Last revised October 25, 2026 Use these packages with AI tools as you like. When contributing, check what you submit and wait for a maintainer's invitation before preparing a pull request. This policy covers the libraries, ports, and documentation in the [libtmux](https://github.com/libtmux) and [tmux-python](https://github.com/tmux-python) organizations, including their issues, comments, discussions, reviews, and pull requests. ## 1. For users: About these packages We encourage you to use AI tools with our libraries and documentation, including to generate code that calls the libraries. We want these projects to be useful in the way you work. Some of what we publish supports that workflow: - [MCP integrations](https://libtmux.org/en/py/latest/mcp/) that let assistants interact with tmux. - Markdown documentation and `llms.txt` and `llms-full.txt` resources linked from documentation pages. - [`AGENTS.md`]() and other repository instructions for tools working on the codebase. Use the documentation for the package, language, and version you are working with. ### How the ports are made The ports build on [tmuxp](https://tmuxp.git-pull.com/), started in 2013, and [libtmux](https://libtmux.git-pull.com/), started in 2016. Both projects predate today's LLM coding tools. Current work on the original projects and the ports uses AI assistance. Maintainers guide the design, review changes, test behavior against real tmux, and decide what ships. They remain responsible for what the projects include and maintain. ### Stability Packages in the [libtmux organization](https://github.com/libtmux) are experimental unless their README says otherwise. Names and APIs may change between releases. Pin an exact version and review changes before upgrading. ## 2. For contributors: How to report issues, ideas, suggestions, and security issues A useful issue is a contribution in its own right. Your experience helps us see what breaks, what feels awkward, and what is missing. You do not need to arrive with a patch. Open bugs, ideas, and suggestions in the repository of the package they concern. Report suspected security vulnerabilities privately using the route below. ### Bugs Tell us what you tried, what happened, and what you expected. Include a small example we can run, the library and tmux versions, and the actual output. Include relevant logs and error messages as they appeared, and distinguish what you observed from possible explanations. Bug reports are welcome whether the code you were running was handwritten or generated. Include the code that ran. A conversation transcript is usually unnecessary; include relevant prompts or tool calls when they are needed to reproduce the problem. ### Ideas and suggestions Explain what you wanted to accomplish and where the current design gets in your way. We welcome feedback on: - Usability: what was hard to find or get right, and where the documentation misled you. - Language conventions: whether a port feels natural in the language you use. - Aesthetics: how an API, a page, or an error message reads. - Missing capabilities: what you could not accomplish with the current API. If an API you expected is missing, describe the task and why that API would help. ### Security issues Report suspected security vulnerabilities privately. Use **Report a vulnerability** on the affected repository's security page where available. Include the affected package and version, the tmux version, what you observed, and reproduction steps or relevant code references if available. Distinguish verified behavior from suspected impact. Please coordinate public disclosure with us so we can investigate and, where needed, prepare a fix. Command execution explicitly requested through documented features is expected behavior and does not, by itself, establish a vulnerability. Explain any additional failure, such as data being interpreted as commands unexpectedly or an intended security boundary being crossed. ### Writing your report Short reports, typos, and imperfect English are fine. Translation and writing assistance are welcome; check that the result says what you mean and preserves the facts you observed. Your experience matters more than a polished report. ### Using AI tools If AI materially helped prepare your issue, comment, discussion, review, or pull request, say which tools you used, what they did, and what you checked yourself. Using these tools does not count against a contribution. You remain responsible for what you submit. Do not send generated code, reports, or explanations you have not checked. ### Pull requests Before preparing a pull request, open an issue or comment on an existing issue about the proposed change. Wait for a maintainer to invite a PR for an agreed scope. Opening an issue alone is not an invitation. PRs submitted without that agreement may be closed without review. Arrange security fixes through the private reporting route. Even when we agree that something should change, we may prefer to write the implementation ourselves. Review must consider correctness, supported tmux versions, language conventions, documentation, and ongoing maintenance. Checking those can take more time than writing the change, whether it was handwritten or generated. If we invite a PR, understand the changes, test them, and be able to explain them. Follow the project's contribution instructions, and describe the tests you ran and the versions you used. Generated code and tests can share the same mistake, so passing tests alone do not show that a change fits the project. Submit only material you have the right to contribute on terms compatible with the project's license. Identify outside code, tests, or text incorporated into the contribution, including material adapted with AI assistance, and provide its source and license. Keep required notices, and raise unclear permissions before including the material. These source and licensing requirements apply to maintainers too.

Acknowledgments: Informed by Pallets’ LLM and AI Policy, tmux’s AI contribution guidelines, and OpenJDK’s Interim Policy on Generative AI. Licensing references: the GNU GPL FAQ on translating code and license compatibility. All sources consulted .

--- # Third-party notices Source: https://libtmux.org/en/lisp/latest/meta/third-party-notices/ > Thanks to tmux's creator and contributors, with licences and attribution for the software behind libtmux.org. ## tmux First and foremost, thank you to **[Nicholas Marriott (nicm)](https://github.com/nicm)**, the creator of tmux, and to the many [tmux contributors](https://github.com/tmux/tmux/graphs/contributors). We are grateful for the work that makes tmux possible. libtmux is a separate project. Our libraries and supporting tools control a real tmux server; tmux itself provides the terminal multiplexer. This is the libtmux website. Visit the [official tmux website](https://github.com/tmux/tmux/wiki) for the upstream project and its documentation. See tmux's [COPYING file](https://github.com/tmux/tmux/blob/master/COPYING) and the copyright and permission notices in its source files for its licensing. ### tmux artwork The tmux logomark is by Jason Long. The site uses the unmodified [upstream SVG](https://github.com/tmux/tmux/blob/8f25579c5aef8d93924a20681f394e2a582fd3ad/logo/tmux-logomark.svg) under its [copyright and permission notice](https://libtmux.org/en/brand/tmux/LICENSE.txt). ## Programming-language artwork The homepage language selector uses local copies of the following artwork to identify the selected language. Each source record includes the download URL, retrieval time, file hash, copyright information and usage terms. These marks identify their respective languages and do not imply endorsement of libtmux. The SVG files are copied unchanged except for Scala and Rust. Scala's empty surrounding canvas is cropped; its paths, gradients and colors are preserved, and the original SVG is retained beside the cropped copy. Rust's supplied SVG turns its mark white when the device is in dark mode. A browser does not reliably apply that rule to this site's color setting when the file is shown as an image. The rule is removed, its paths are unchanged, and the page lightens the mark on a dark theme instead. | Language | Credit and terms | Local source record | |---|---|---| | Python | Python Software Foundation. The [PSF logo terms](https://www.python.org/psf/trademarks/) permit the unaltered mark to identify Python. | [Python provenance](https://libtmux.org/en/brand/languages/py/provenance.json) | | Ruby | Copyright © 2006, Yukihiro Matsumoto. The [Ruby logo](https://www.ruby-lang.org/en/about/logo/) is licensed under [CC BY-SA 2.5](https://creativecommons.org/licenses/by-sa/2.5/). | [Ruby provenance](https://libtmux.org/en/brand/languages/ruby/provenance.json) | | Lua | Copyright © 1998 Lua.org; graphic design by Alexandre Nakonechnyj. The Devicon copy carries its [MIT notice](https://libtmux.org/en/brand/languages/lua/LICENSE.txt); [Lua's logo terms](https://www.lua.org/images/) also apply. Visit [Lua.org](https://www.lua.org/). | [Lua provenance](https://libtmux.org/en/brand/languages/lua/provenance.json) | | TypeScript | Microsoft. The [official branding terms](https://www.typescriptlang.org/branding/) govern the mark; the website repository licenses exclude logo and trademark rights. | [TypeScript provenance](https://libtmux.org/en/brand/languages/ts/provenance.json) | | Rust | The Rust Foundation. [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) and the [Rust trademark policy](https://rustfoundation.org/policy/rust-trademark-policy/) apply. | [Rust provenance](https://libtmux.org/en/brand/languages/rs/provenance.json) | | Go | The [Go gopher](https://go.dev/blog/gopher) is by Renee French, licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). | [Go provenance](https://libtmux.org/en/brand/languages/go/provenance.json) | | C++ | Created by Jeremy Kratz and licensed by the Standard C++ Foundation under its [logo-use terms](https://isocpp.org/home/terms-of-use). | [C++ provenance](https://libtmux.org/en/brand/languages/cxx/provenance.json) | | Swift | Apple Inc., under the [Swift Logo Guidelines](https://developer.apple.com/swift/downloads/swift-logo.zip). Swift and the Swift logo are trademarks of Apple Inc. | [Swift provenance](https://libtmux.org/en/brand/languages/swift/provenance.json) | | Java | Devicon collection copyright (c) 2015 konpa, with its [MIT notice](https://libtmux.org/en/brand/languages/java/LICENSE.txt). This does not establish unrestricted rights to the underlying Java logo; [Oracle's logo terms](https://www.oracle.com/legal/logos/) apply. | [Java provenance](https://libtmux.org/en/brand/languages/java/provenance.json) | | Kotlin | Kotlin Foundation brand guidelines preserve JetBrains copyrights. The [icon-use terms](https://kotlinfoundation.org/guidelines/) permit identifying Kotlin alongside other programming-language icons. | [Kotlin provenance](https://libtmux.org/en/brand/languages/kotlin/provenance.json) | | Scala | Copyright EPFL. [Historical permission](https://groups.google.com/g/scala-user/c/bCC-R0FQn1w) covers noncommercial Scala promotion; the [general artwork-license question](https://github.com/scala/scala-lang/issues/1040) remains unresolved. | [Scala provenance](https://libtmux.org/en/brand/languages/scala/provenance.json) | | C# | Copyright the .NET authors. [Brand-use permission](https://github.com/dotnet/brand/issues/10#issuecomment-669465301) allows the unmodified logo to represent .NET. The repository's CC0 statement covers illustrations; no blanket CC0 claim is made for the logo. | [.NET provenance](https://libtmux.org/en/brand/languages/csharp/provenance.json) | | F# | The F# Software Foundation. Its [logo terms](https://foundation.fsharp.org/logo) require unchanged shape, colors and proportions, without implying Foundation representation. | [F# provenance](https://libtmux.org/en/brand/languages/fsharp/provenance.json) | ## Terminal artwork The tmux CLI selector displays the Windows Terminal artwork by Microsoft Corporation, licensed under [CC BY-ND 4.0](https://creativecommons.org/licenses/by-nd/4.0/). The SVG is unmodified. See the [source and provenance](https://libtmux.org/en/brand/tools/terminal/provenance.json) and [copyright and license](https://libtmux.org/en/brand/tools/terminal/LICENSE.txt). ## Documentation toolchain libtmux and this site are built with open-source software. The following tools have their own licences and attribution requirements. | Tool | Licence | Role | |---|---|---| | [Astro](https://astro.build/) | MIT | The site shell | | [Tailwind CSS](https://tailwindcss.com/) | MIT | Styling | | [Pagefind](https://pagefind.app/) | MIT | Site-wide search | | [Expressive Code](https://expressive-code.com/) | MIT | Code blocks | | [Sphinx](https://www.sphinx-doc.org/) | BSD-2-Clause | Python and C++ reference | | [Furo](https://github.com/pradyunsg/furo) | MIT | Sphinx theme | | [Breathe](https://github.com/breathe-doc/breathe) | BSD-3-Clause | Doxygen XML into Sphinx | | [Doxygen](https://www.doxygen.nl/) | GPL-2.0-only | Parses C++ headers to XML | | [API Extractor](https://api-extractor.com/) | MIT | TypeScript API model | | [IBM Plex](https://github.com/IBM/plex) | OFL-1.1 | Typeface | ### A note on Doxygen Doxygen is licensed GPL-2.0-only. It runs as a build step that reads libtmux's own headers and emits XML; that XML is rendered by Breathe and Sphinx, and no Doxygen-generated HTML is published. Running a GPL program over your own input does not place its licence on the output, and libtmux does not distribute Doxygen or any modified version of it. ## Reference hosting The [PyPI blocks logo](https://pypi.org/trademarks/) is a trademark of the Python Software Foundation and identifies links to the Python Package Index. Other package-host icons use [Simple Icons](https://simpleicons.org/) (CC0-1.0). The docs.rs mark is its [`docsdotrs` icon](https://github.com/simple-icons/simple-icons/blob/16.35.0/icons/docsdotrs.svg), inlined unchanged. The book beside javadoc.io links is original artwork for this site: the host publishes no vector logo, and the mark does not imitate its favicon. Some ports deep-link to the canonical host their ecosystem already uses, rather than duplicating it here: - Rust: [docs.rs](https://docs.rs/libtmux), for each published crate - Go: [pkg.go.dev](https://pkg.go.dev/github.com/libtmux/libtmux-go/tmux) - Java, Kotlin and Scala: [javadoc.io](https://javadoc.io/doc/io.github.libtmux/libtmux) - Swift: the [Swift Package Index](https://swiftpackageindex.com/libtmux/libtmux-swift/documentation/libtmux) copy of the DocC reference Those sites are operated independently of this project and carry their own terms. ## libtmux itself Each port is MIT licensed. See the `LICENSE` file in that port's repository for the authoritative text. --- # Query interchange Source: https://libtmux.org/en/lisp/latest/topics/query-interchange/ > Query interchange: libtmux documentation. `libtmux/regex` and `libtmux/json` are separate ASDF systems. Core and `libtmux/query` remain loadable with Common Lisp plus ASDF/UIOP. The optional systems add CL-PPCRE and Jzon only where their behavior is required. [Query guide](https://libtmux.org/en/lisp/latest/guides/queries/) · [Query API](https://libtmux.org/en/lisp/latest/reference/) · [JSON API](https://libtmux.org/en/lisp/latest/reference/) · [Testing](https://libtmux.org/en/lisp/latest/guides/testing/) ## Load only what the application uses Enable the two regex operators: ```lisp (asdf:load-system "libtmux/regex") ``` Enable versioned JSON reading and writing: ```lisp (asdf:load-system "libtmux/json") ``` Load both before reading a document containing regex nodes. Neither system contacts tmux. ## Compile a reusable regex ```lisp (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)))) ``` The compiled filter retains owned pattern/options data and one prepared CL-PPCRE scanner. `:regex-search` accepts a match anywhere; `:regex-full-match` requires the complete captured string. An absent field does not match. Parser and query visit limits do not interrupt scanner execution. ## Round-trip a compiled filter Applications can give `libtmux.json` a package-local nickname such as `j`. Writing returns a fresh string by default: ```lisp (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)))))) ``` Without `:allow-regex t`, [`read-filter`]() rejects a regex node before compiling its scanner. Non-regex documents need only `libtmux/json`. [`write-filter`]() returns a fresh string unless given an open output character stream. It does not open pathnames or close a caller-owned stream. ## Wire envelope The native schema is `libtmux.lisp.filter/1`: ```json {"schema":"libtmux.lisp.filter/1","model":"pane","registryVersion":1,"expression":["and",["=","pane/active",false],["in","pane/current-command",[]]]} ``` The root object is closed and requires exactly the named members. Duplicate or unknown members, unknown models/operators/fields, wrong arities and unsupported registry versions signal [`invalid-filter-document`](). External names remain strings until allowlist lookup; the decoder never interns them. Boolean false is JSON `false`. An empty membership is `[]`. JSON `null` is distinct and rejected because registry version 1 has no nullable query operand. Numeric operands are integers; decimal and exponent syntax is rejected before Jzon converts the token. Native closures and QuerySet callbacks are not serializable recipes. This schema belongs to the Common Lisp port. Similar JSON from another port is not compatible without operator-by-operator conformance evidence. ## Decoder limits [`read-filter`]() accepts one complete JSON string and defaults to: | Limit | Default | | --- | ---: | | UTF-8 input | 1 MiB | | Nesting depth | 64 | | JSON nodes | 10,000 | | One string | 65,536 characters | | Membership operands | 1,000 | | Decimal digits in one numeric token | 128 | Override a limit with the corresponding keyword argument. An exhausted bound signals [`filter-document-limit-exceeded`](); [`document-limit`]() identifies the bound and [`document-path`]() returns an owned location vector. Jzon syntax failures and native-query validation failures remain available through [`error-cause`](). --- # Topics Source: https://libtmux.org/en/lisp/latest/topics/ > Topics for libtmux. Understand object ownership, state, and operation behavior. - [Query interchange](https://libtmux.org/en/lisp/latest/topics/query-interchange/): Read about optional query interchange.