libtmux for Lua
Lua
- Python Unavailable
- Ruby Unavailable
- Lua
- TypeScript Unavailable
- Rust Unavailable
- Go Unavailable
- Java Unavailable
- .NET Unavailable
- C++ Unavailable
- Swift Unavailable
Script tmux from Lua: create sessions and panes, capture terminal output, query server state, and watch pane output. Use standalone Lua with luv or Neovim’s event loop.
Alpha software. The core API is still changing. The separate MCP and workspace packages are unpublished scaffolds.
Install · Read a server · Query · Create panes · Neovim · Guides · CI · MIT license
InstallLink to section
Install the core alpha with LuaRocks configured for your Lua interpreter:
$ luarocks --local install libtmux 0.1.0alpha1-1To install from a checkout:
$ luarocks --local make rockspecs/libtmux-scm-1.rockspecSee the changelog and release instructions for release history and verification. Linux is tested; macOS remains unverified.
Add the local rocks tree to Lua’s module paths:
$ eval "$(luarocks --local path)"For standalone scripts, also install luv. Building it requires a C compiler and CMake. Neovim provides its own libuv binding.
$ luarocks --local install luv 1.52.1-0Core and local queries require only Lua. Importing a module does not start tmux or an event loop. CI runs unit tests on Lua 5.1–5.5 and LuaJIT, plus live tests across tmux 3.2a–3.7c. See the compatibility matrix for exact versions and remaining platform coverage.
Read a serverLink to section
Connect to an existing server by its explicit socket path. This is the full snapshot example, which prints pane and window IDs:
local adapter = require("libtmux.runtime.luv")
local function must(value, err) if err ~= nil then error(err, 0) end return valueend
must(adapter.run(function(runtime) local server = must(runtime :connect({ binary = assert(os.getenv("TMUX_BIN"), "set TMUX_BIN to an absolute tmux executable"), socket_path = assert(os.getenv("TMUX_SOCKET"), "set TMUX_SOCKET to an explicit socket"), }) :await()) local snapshot = must(server:snapshot({ strict = true }):await()) for _, pane in ipairs(snapshot.panes) do io.stdout:write(pane.id, "\t", pane.window_id, "\n") end must(server:close():await()) return trueend))Live operations return Requests; :await() yields inside the runtime body and
returns value, err. The must helper propagates errors. Closing the connection
leaves the tmux server and its sessions running.
Set TMUX_BIN and TMUX_SOCKET to absolute paths for your server, then run:
$ lua examples/snapshot.luaTry it on a temporary server
Run from the checkout after installing the dependencies above. This starts a private tmux server and removes it when the example finishes.
$ sh <<'SH'set -euunset TMUX TMUX_PANETMUX_BIN=$(command -v tmux)demo_dir=$(mktemp -d /tmp/libtmux-lua-XXXXXX)TMUX_SOCKET="$demo_dir/tmux.sock"export TMUX_BIN TMUX_SOCKETcleanup() { "$TMUX_BIN" -S "$TMUX_SOCKET" kill-server 2>/dev/null || true rm -rf "$demo_dir"}trap cleanup EXIT"$TMUX_BIN" -f /dev/null -S "$TMUX_SOCKET" new-session -d -s demolua examples/snapshot.luaSHQuery captured stateLink to section
After capturing snapshot in the example above, filter its panes with
structured criteria or an ordinary Lua function:
local editors = snapshot.panes:where({ current_command = { one_of = { "nvim", "vim" } },})local inactive = snapshot.panes:filter(function(pane) return not pane.activeend)
print(#editors, #inactive)for _, pane in ipairs(editors) do print(pane.id, pane.current_command)endSelections are dense, one-based Lua tables. Both queries read the snapshot without calling tmux. Capture again to refresh it; a snapshot spans multiple tmux commands and is not an atomic view.
See criteria, relationships and live queries, the field reference, or run the standalone table-query example:
$ lua examples/native_query.luaCreate sessions and panesLink to section
Before closing server in that runtime body, create a session and split a
window. These calls use the same must helper:
local work = must(server:new_session({ name = "work" }):await())local editor = must(work.session:new_window({ name = "editor" }):await())local split = must(editor.pane:split({ direction = "right", percent = 40 }):await())
must(split.pane:send_text("printf '%s\\n' hello"):await())must(split.pane:send_keys({ "Enter" }):await())Creation returns a table with session, window, pane and window_link
handles. These operations leave the new sessions and panes running. Sending
keys confirms that tmux accepted input; it does not wait for a shell command
to finish. See creation and pane operations
for literal argv, capture, resize and cleanup.
NeovimLink to section
From the checkout, start Neovim with the library on its runtimepath:
$ nvim --cmd 'set runtimepath+=.'With TMUX_SOCKET set to an existing server’s absolute socket path, run this
Lua code. start uses the editor’s loop and reports the result in a callback:
local adapter = require("libtmux.runtime.nvim")
adapter.start(function(runtime) local server, err = runtime:connect({ binary = vim.fn.exepath("tmux"), socket_path = assert(os.getenv("TMUX_SOCKET")), }):await() if err then return nil, err end return server:snapshot({ strict = true }):await()end, function(snapshot, err) if err then vim.notify(tostring(err), vim.log.levels.ERROR) return end vim.notify(("Panes: %d"):format(#snapshot.panes))end)The runtime closes its connections when the body finishes and leaves the tmux server running. See runtime ownership and cancellation.
GuidesLink to section
- Clients: switch sessions and detach terminals.
- Read and watch: snapshots, session notifications and pane streams.
- Run and arrange: commands and batches, session/window topology, binary buffers.
- Configure: options and hooks, option reference, environment values.
- Contribute: setup and validation, writing conventions.
Live tests use private sockets and clean up their own servers. See the contributing guide for the same offline checks that run in CI.