Core LibraryunreleasedHome

Choose documentation 2

latest

Current version

latest
English

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

Edit this page on GitHub

tmux client library for Common Lisp

Control tmux, navigate sessions, windows, and panes, send keys, capture pane contents.

Install

git clone https://github.com/libtmux/libtmux-lisp.git vendor/libtmux-lisp

At a glance

Server → Session → Window → Pane. Each level of the tmux hierarchy has its own entry in the Common Lisp API, with the tmux object it wraps.

TypeWraps
libtmux:client A tmux server
libtmux:session A session, ID $n
libtmux:window A window in a session, ID @n
libtmux:pane A pane in a window, ID %n

What it does

(require :asdf)
(defparameter *source-root* (or (uiop:getenv "LIBTMUX_SOURCE") "."))
(defparameter *program* (or (uiop:getenv "LIBTMUX_PROGRAM") "tmux"))
(defparameter *socket-path*
(or (uiop:getenv "LIBTMUX_EXAMPLE_SOCKET")
(error "Set LIBTMUX_EXAMPLE_SOCKET to the owned tmux socket path")))
(asdf:load-asd
(truename (merge-pathnames "libtmux.asd"
(uiop:ensure-directory-pathname *source-root*))))
(asdf:load-system "libtmux")
(defun observed-value (outcome)
"Return OUTCOME's observed value, or signal its observation failure."
(when (libtmux:outcome-observation-error outcome)
(error (libtmux:outcome-observation-error outcome)))
(multiple-value-bind (value present-p) (libtmux:outcome-value outcome)
(unless present-p
(error "The operation did not return an observation"))
value))
(libtmux:with-client (client :program *program* :socket-path *socket-path*
:config-file "/dev/null")
(let* ((before (libtmux:fetch-snapshot client))
(demo (find "demo" (libtmux:sessions before)
:key #'libtmux:name :test #'string=))
(editor-link (aref (libtmux:window-links demo) 0))
(editor (libtmux:window editor-link))
(left (aref (libtmux:panes editor) 0))
(right (observed-value
(libtmux:split-pane
client left :direction :horizontal
:command '("/bin/cat"))))
(logs (observed-value
(libtmux:new-window
client demo :name "logs"
:command '("/bin/cat")))))
(declare (ignore logs))
(libtmux:send-text client right "hello from tmux" :enter t)
(multiple-value-bind (text outcome)
(libtmux:capture-pane client right)
(assert (eq :succeeded (libtmux:outcome-status outcome)))
(assert (search "hello from tmux" text)))
(let* ((snapshot (libtmux:fetch-snapshot client))
(pane (find (libtmux:id right)
(libtmux:panes snapshot)
:key #'libtmux:id :test #'string=))
(window (libtmux:window pane))
(owner-link (aref (libtmux:window-links window) 0))
(owner (libtmux:session owner-link)))
(loop for session across (libtmux:sessions snapshot)
do (format t "session ~A~%" (libtmux:name session))
(loop for link across
(libtmux:window-links session)
for each = (libtmux:window link)
do (format t " window ~D:~A:"
(libtmux:index link)
(libtmux:name each))
(loop for item across
(libtmux:panes each)
do (format t " ~A"
(libtmux:id item)))
(terpri)))
(format t "~A is in window ~A of session ~A~%"
(libtmux:id pane) (libtmux:name window)
(libtmux:name owner))))
(libtmux:kill-server client))
(require :asdf)
(defparameter *source-root* (or (uiop:getenv "LIBTMUX_SOURCE") "."))
(defparameter *program* (or (uiop:getenv "LIBTMUX_PROGRAM") "tmux"))
(defparameter *socket-path*
(or (uiop:getenv "LIBTMUX_EXAMPLE_SOCKET")
(error "Set LIBTMUX_EXAMPLE_SOCKET to the owned tmux socket path")))
(asdf:load-asd
(truename (merge-pathnames "libtmux.asd"
(uiop:ensure-directory-pathname *source-root*))))
(asdf:load-system "libtmux")
(defun observed-value (outcome)
"Return OUTCOME's observed value, or signal its observation failure."
(when (libtmux:outcome-observation-error outcome)
(error (libtmux:outcome-observation-error outcome)))
(multiple-value-bind (value present-p) (libtmux:outcome-value outcome)
(unless present-p
(error "The operation did not return an observation"))
value))
(libtmux:with-client (client :program *program* :socket-path *socket-path*
:config-file "/dev/null")
(let* ((before (libtmux:fetch-snapshot client))
(demo (find "demo" (libtmux:sessions before)
:key #'libtmux:name :test #'string=))
(editor-link (aref (libtmux:window-links demo) 0))
(editor (libtmux:window editor-link))
(left (aref (libtmux:panes editor) 0))
(right (observed-value
(libtmux:split-pane
client left :direction :horizontal
:command '("/bin/cat"))))
(logs (observed-value
(libtmux:new-window
client demo :name "logs"
:command '("/bin/cat")))))
(declare (ignore logs))
(libtmux:send-text client right "hello from tmux" :enter t)
(multiple-value-bind (text outcome)
(libtmux:capture-pane client right)
(assert (eq :succeeded (libtmux:outcome-status outcome)))
(assert (search "hello from tmux" text)))
(let* ((snapshot (libtmux:fetch-snapshot client))
(pane (find (libtmux:id right)
(libtmux:panes snapshot)
:key #'libtmux:id :test #'string=))
(window (libtmux:window pane))
(owner-link (aref (libtmux:window-links window) 0))
(owner (libtmux:session owner-link)))
(loop for session across (libtmux:sessions snapshot)
do (format t "session ~A~%" (libtmux:name session))
(loop for link across
(libtmux:window-links session)
for each = (libtmux:window link)
do (format t " window ~D:~A:"
(libtmux:index link)
(libtmux:name each))
(loop for item across
(libtmux:panes each)
do (format t " ~A"
(libtmux:id item)))
(terpri)))
(format t "~A is in window ~A of session ~A~%"
(libtmux:id pane) (libtmux:name window)
(libtmux:name owner))))
(libtmux:kill-server client))
(require :asdf)
(defparameter *source-root* (or (uiop:getenv "LIBTMUX_SOURCE") "."))
(defparameter *program* (or (uiop:getenv "LIBTMUX_PROGRAM") "tmux"))
(defparameter *socket-path*
(or (uiop:getenv "LIBTMUX_EXAMPLE_SOCKET")
(error "Set LIBTMUX_EXAMPLE_SOCKET to the owned tmux socket path")))
(asdf:load-asd
(truename (merge-pathnames "libtmux.asd"
(uiop:ensure-directory-pathname *source-root*))))
(asdf:load-system "libtmux")
(defun observed-value (outcome)
"Return OUTCOME's observed value, or signal its observation failure."
(when (libtmux:outcome-observation-error outcome)
(error (libtmux:outcome-observation-error outcome)))
(multiple-value-bind (value present-p) (libtmux:outcome-value outcome)
(unless present-p
(error "The operation did not return an observation"))
value))
(libtmux:with-client (client :program *program* :socket-path *socket-path*
:config-file "/dev/null")
(let ((normal nil))
(unwind-protect
(multiple-value-prog1
(let* ((before (libtmux:fetch-snapshot client))
(demo (find "demo" (libtmux:sessions before)
:key #'libtmux:name :test #'string=))
(editor-link (aref (libtmux:window-links demo) 0))
(editor (libtmux:window editor-link))
(left (aref (libtmux:panes editor) 0))
(right (observed-value
(libtmux:split-pane
client left :direction :horizontal
:command '("/bin/cat"))))
(logs (observed-value
(libtmux:new-window
client demo :name "logs"
:command '("/bin/cat")))))
(declare (ignore logs))
(libtmux:send-text client right "hello from tmux" :enter t)
(multiple-value-bind (text outcome)
(libtmux:capture-pane client right)
(assert (eq :succeeded (libtmux:outcome-status outcome)))
(assert (search "hello from tmux" text)))
(let* ((snapshot (libtmux:fetch-snapshot client))
(pane (find (libtmux:id right)
(libtmux:panes snapshot)
:key #'libtmux:id :test #'string=))
(window (libtmux:window pane))
(owner-link (aref (libtmux:window-links window) 0))
(owner (libtmux:session owner-link)))
(loop for session across (libtmux:sessions snapshot)
do (format t "session ~A~%" (libtmux:name session))
(loop for link across
(libtmux:window-links session)
for each = (libtmux:window link)
do (format t " window ~D:~A:"
(libtmux:index link)
(libtmux:name each))
(loop for item across
(libtmux:panes each)
do (format t " ~A"
(libtmux:id item)))
(terpri)))
(format t "~A is in window ~A of session ~A~%"
(libtmux:id pane) (libtmux:name window)
(libtmux:name owner))))
(setf normal t))
(handler-case (libtmux:kill-server client)
(error (condition)
(if normal
(error condition)
(warn "Owned server cleanup also failed: ~A" condition)))))))
(require :asdf)
(defparameter *source-root* (or (uiop:getenv "LIBTMUX_SOURCE") "."))
(defparameter *program* (or (uiop:getenv "LIBTMUX_PROGRAM") "tmux"))
(defparameter *socket-path*
(or (uiop:getenv "LIBTMUX_EXAMPLE_SOCKET")
(error "Set LIBTMUX_EXAMPLE_SOCKET to the owned tmux socket path")))
(asdf:load-asd
(truename (merge-pathnames "libtmux.asd"
(uiop:ensure-directory-pathname *source-root*))))
(asdf:load-system "libtmux")
(defun observed-value (outcome)
"Return OUTCOME's observed value, or signal its observation failure."
(when (libtmux:outcome-observation-error outcome)
(error (libtmux:outcome-observation-error outcome)))
(multiple-value-bind (value present-p) (libtmux:outcome-value outcome)
(unless present-p
(error "The operation did not return an observation"))
value))
(libtmux:with-client (client :program *program* :socket-path *socket-path*
:config-file "/dev/null")
(let ((normal nil))
(unwind-protect
(multiple-value-prog1
(let* ((before (libtmux:fetch-snapshot client))
(demo (find "demo" (libtmux:sessions before)
:key #'libtmux:name :test #'string=))
(editor-link (aref (libtmux:window-links demo) 0))
(editor (libtmux:window editor-link))
(left (aref (libtmux:panes editor) 0))
(right (observed-value
(libtmux:split-pane
client left :direction :horizontal
:command '("/bin/cat"))))
(logs (observed-value
(libtmux:new-window
client demo :name "logs"
:command '("/bin/cat")))))
(declare (ignore logs))
(libtmux:send-text client right "hello from tmux" :enter t)
(multiple-value-bind (text outcome)
(libtmux:capture-pane client right)
(assert (eq :succeeded (libtmux:outcome-status outcome)))
(assert (search "hello from tmux" text)))
(let* ((snapshot (libtmux:fetch-snapshot client))
(pane (find (libtmux:id right)
(libtmux:panes snapshot)
:key #'libtmux:id :test #'string=))
(window (libtmux:window pane))
(owner-link (aref (libtmux:window-links window) 0))
(owner (libtmux:session owner-link)))
(loop for session across (libtmux:sessions snapshot)
do (format t "session ~A~%" (libtmux:name session))
(loop for link across
(libtmux:window-links session)
for each = (libtmux:window link)
do (format t " window ~D:~A:"
(libtmux:index link)
(libtmux:name each))
(loop for item across
(libtmux:panes each)
do (format t " ~A"
(libtmux:id item)))
(terpri)))
(format t "~A is in window ~A of session ~A~%"
(libtmux:id pane) (libtmux:name window)
(libtmux:name owner))))
(setf normal t))
(handler-case (libtmux:kill-server client)
(error (condition)
(if normal
(error condition)
(warn "Owned server cleanup also failed: ~A" condition)))))))
Example details
Run this example

mise 2026.11.3, Git, POSIX shell and tmux 3.3a or newer on Linux x86-64. The source lock installs SBCL 2.6.10 and ASDF/UIOP 3.3.7.

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

Cleanup is omitted; the private tmux server and its temporary files may remain. Select Cleanup to remove them when the program finishes. The program stops its private tmux server and removes any temporary files it created when it finishes. Unhandled conditions already end the script with SBCL's diagnostic and backtrace, so there is no separate error wrapper to add.

Setup and run

git init -q source
git -C source fetch -q --depth=1 \
https://github.com/libtmux/libtmux-lisp.git \
99874b44dbe29b3651597a5debbacccde235d96c
git -C source checkout -q --detach FETCH_HEAD
mise trust source
mise -C source install
unset TMUX TMUX_PANE
export ENV=/dev/null BASH_ENV=/dev/null SHELL=/bin/sh
export LIBTMUX_SOURCE="$PWD/source"
export LIBTMUX_PROGRAM="${LIBTMUX_NATIVE_TMUX:-$(command -v tmux)}"
LIBTMUX_DEMO_DIR="$(mktemp -d)"
export LIBTMUX_EXAMPLE_SOCKET="$LIBTMUX_DEMO_DIR/tmux.sock"
trap 'rm -rf "$LIBTMUX_DEMO_DIR"' EXIT
tmux -S "$LIBTMUX_EXAMPLE_SOCKET" -f /dev/null \
new-session -d -s demo -n editor -x 80 -y 24 -- /bin/cat
LIBTMUX_ASDF="$(mise -C source where http:asdf-lisp)/asdf.lisp"
mise -C source exec -- sbcl --noinform --no-sysinit --no-userinit \
--load "$LIBTMUX_ASDF" --script "$PWD/quickstart.lisp"

Expected output

session demo
window 0:editor: %0 %1
window 1:logs: %2
%1 is in window editor of session demo

Typed API

What you get over the tmux CLI

Esc

Type to search.

Reference previewOpen reference