tmux client library for TypeScript
Control tmux, connect to and create servers, navigate sessions, windows, and panes, send keys, watch for changes via control mode, capture pane contents.
Quickstart
Install the package and run a complete program.
Guides
Task guides for TypeScript.
Topics
Subject-by-subject, with TypeScript in every code block.
Examples
Complete programs, sourced from the code each port tests.
API reference
Types and functions of the TypeScript API.
Testing
Test code that uses tmux with an isolated server.
Install
bun add libtmuxlibtmux 0.1.0 is pre-1.0 and can change between minor versions. Pin to a range:
"libtmux": "~0.1.0"At a glance
Server → Session → Window → Pane. Each level of the tmux hierarchy has its own entry in the TypeScript API, with the tmux object it wraps.
| Type | Wraps |
|---|---|
server.Server | A tmux server |
session.Session | A session, ID $n |
window.Window | A window in a session, ID @n |
pane.Pane | A pane in a window, ID %n |
What it does
Start with TypeScript
import { mkdtemp } from "node:fs/promises";import { tmpdir } from "node:os";import { join } from "node:path";import { setTimeout as delay } from "node:timers/promises";import { Server } from "libtmux";
const directory = await mkdtemp(join(tmpdir(), "ltx-home-"));const server = new Server({ socketPath: join(directory, "tmux.sock"), configFile: "/dev/null", timeoutMs: 5_000,});
const signal = AbortSignal.timeout(5_000);const session = await server.newSession({ name: "work", signal });
const editor = await session.newWindow({ name: "editor", shellCommand: "sh", signal,});await editor.split({ signal });
const pane = editor.activePane;if (!pane) throw new Error("The window has no active pane");
await pane.sendKeys("printf '\\nhello\\n'", { signal });
for (;;) { const lines = await pane.capture({ signal }); if (lines.includes("hello")) { console.log("hello"); break; }
await delay(20, undefined, { signal });}import { mkdtemp } from "node:fs/promises";import { tmpdir } from "node:os";import { join } from "node:path";import { setTimeout as delay } from "node:timers/promises";import { Server } from "libtmux";
const directory = await mkdtemp(join(tmpdir(), "ltx-home-"));const server = new Server({ socketPath: join(directory, "tmux.sock"), configFile: "/dev/null", timeoutMs: 5_000,});
try { const signal = AbortSignal.timeout(5_000); const session = await server.newSession({ name: "work", signal });
const editor = await session.newWindow({ name: "editor", shellCommand: "sh", signal, }); await editor.split({ signal });
const pane = editor.activePane; if (!pane) throw new Error("The window has no active pane");
await pane.sendKeys("printf '\\nhello\\n'", { signal });
for (;;) { const lines = await pane.capture({ signal }); if (lines.includes("hello")) { console.log("hello"); break; }
await delay(20, undefined, { signal }); }} catch (error) { throw new Error("tmux quickstart failed", { cause: error });}import { mkdtemp, readdir, rm } from "node:fs/promises";import { tmpdir } from "node:os";import { join } from "node:path";import { setTimeout as delay } from "node:timers/promises";import { Server } from "libtmux";
const directory = await mkdtemp(join(tmpdir(), "ltx-home-"));const server = new Server({ socketPath: join(directory, "tmux.sock"), configFile: "/dev/null", timeoutMs: 5_000,});
try { const signal = AbortSignal.timeout(5_000); const session = await server.newSession({ name: "work", signal });
const editor = await session.newWindow({ name: "editor", shellCommand: "sh", signal, }); await editor.split({ signal });
const pane = editor.activePane; if (!pane) throw new Error("The window has no active pane");
await pane.sendKeys("printf '\\nhello\\n'", { signal });
for (;;) { const lines = await pane.capture({ signal }); if (lines.includes("hello")) { console.log("hello"); break; }
await delay(20, undefined, { signal }); }} finally { if ((await readdir(directory)).includes("tmux.sock")) { await server.kill(); } await rm(directory, { recursive: true });}import { mkdtemp, readdir, rm } from "node:fs/promises";import { tmpdir } from "node:os";import { join } from "node:path";import { setTimeout as delay } from "node:timers/promises";import { Server } from "libtmux";
const directory = await mkdtemp(join(tmpdir(), "ltx-home-"));const server = new Server({ socketPath: join(directory, "tmux.sock"), configFile: "/dev/null", timeoutMs: 5_000,});
const failures: unknown[] = [];try { const signal = AbortSignal.timeout(5_000); const session = await server.newSession({ name: "work", signal });
const editor = await session.newWindow({ name: "editor", shellCommand: "sh", signal, }); await editor.split({ signal });
const pane = editor.activePane; if (!pane) throw new Error("The window has no active pane");
await pane.sendKeys("printf '\\nhello\\n'", { signal });
for (;;) { const lines = await pane.capture({ signal }); if (lines.includes("hello")) { console.log("hello"); break; }
await delay(20, undefined, { signal }); }} catch (error) { failures.push(error);}
try { if ((await readdir(directory)).includes("tmux.sock")) { await server.kill(); }
await rm(directory, { recursive: true });} catch (error) { failures.push(new Error(`Cleanup failed: ${directory}`, { cause: error, }));}
if (failures.length) throw new AggregateError(failures);Run this example
POSIX shell, Git, tmux 3.3a or newer and Bun 1.4.2.
Save the program above as quickstart.ts 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. Error handling adds diagnostics; every version checks required results and deadlines.
package.json
{"type":"module","private":true,"dependencies":{"libtmux":"file:./source/packages/libtmux"}}Setup and run
git init -q sourcegit -C source fetch -q --depth=1 \ https://github.com/libtmux/libtmux-ts.git \ 46cccfef2a546de55ce8b308249b8c76339089a0git -C source checkout -q --detach FETCH_HEADbun installunset TMUX TMUX_PANEexport ENV=/dev/null BASH_ENV=/dev/null SHELL=/bin/shbun run quickstart.tsExpected output
helloLibrary revision: 46cccfef2a54.
Typed API
- Typed objects instead of parsed format strings:
pane.Pane - Addressing by tmux ID:
common.PaneId - Filtering and queries over tmux objects:
selection.Selection.where - Typed errors instead of exit codes and stderr:
errors.LibTmuxError
What you get over the tmux CLI
- Connect to or start a tmux server:
server.Server - Traversal from a server down to its panes:
server.Server.panes - Send keys to a pane:
pane.Pane.sendKeys - Watch for changes through tmux control mode:
server.Server.watch - Capture pane contents:
pane.Pane.capture - Arguments passed as a list, with no shell quoting:
server.Server.cmd - A typed error when the running tmux lacks a feature:
errors.VersionTooLowError - Async operations:
server.Server.newSession