libtmux-ts
Health Warn
- License — License: MIT
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Low visibility — Only 5 GitHub stars
Code Pass
- Code scan — Scanned 12 files during light audit, no dangerous patterns found
Permissions Pass
- Permissions — No dangerous permissions requested
No AI report is available for this listing yet.
Typed tmux library for Node.js, Bun, TypeScript. Alpha.
libtmux for TypeScript
Typed control of tmux for Bun and TypeScript — immutable snapshots, declarative queries, and a library that ships zero runtime dependencies.
Quickstart •
Querying •
Packages •
API reference •
Hosted API reference •
Examples •
Changelog
[!WARNING]
Releases are numbered0.1.N, and each release incrementsN. The API is not
settled: any release may change or remove exported identifiers, and the
changelog marks each break. Pin an exact version to avoid one.
Read the changelog before you upgrade.
Is this for you?
Yes, if you drive tmux from a program — an agent that runs commands and reads
what they printed, a workspace launcher, a test harness, a dashboard — and you
want the terminal's state as typed data rather than as parsed strings.
Probably not, if you want a .tmux.conf generator or a TUI. This library
controls a running server; it does not write your tmux configuration.
The idea in one line: read the whole server once into an immutable snapshot,
then query it like data.
Quickstart
$ bun add --exact [email protected]
npm, pnpm, yarn
$ npm i --save-exact [email protected]
$ pnpm add --save-exact [email protected]
$ yarn add --exact [email protected]
Requires Bun 1.3.14+ or Node 22+, and tmux 3.3a or newer.
- The version that counts is the running daemon's.
- An older daemon, or an executable that would start one, is refused with
VersionTooLowErrorbefore any command takes effect. - A client reporting a newer release does not change that.
Runs on Linux, including WSL. macOS is supported on a best-effort basis,
and CI coverage for it is highly limited.
import { Server } from "libtmux";
const server = new Server();
const snapshot = await server.snapshot();
// No further tmux calls: everything below resolves against the snapshot.
const editors = snapshot.panes.where({ currentCommand: "vim" });
console.log(editors.count(), editors.at(0)?.session?.name);
Building something rather than reading it looks like this — and this block is a
literal excerpt of examples/quickstart/quickstart.ts, which the
integration suite runs against a real tmux server:
const session = await server.newSession({ name: "quickstart" });
const editor = await session.newWindow({ name: "editor" });
await editor.split();
const snapshot = await server.snapshot();
const found = snapshot.windows.where({ name: "editor" }).one();
const paneCount = found.panes.length;
What querying looks like
This is the part worth judging the library on. .where() takes declarative,
serializable criteria; .filter() takes an ordinary predicate. They are
never overloaded into each other.
// Equality, string operators, AND/OR/NOT, and regular expressions as data.
snapshot.sessions.where({
AND: [
{ name: { startsWith: "prod" } },
{ windows: { some: { name: { regex: { pattern: "^log", flags: "" } } } } },
],
});
// Quantifiers over relations: some / every / none, and is / isNot.
snapshot.windows.where({ session: { is: { name: "work" } } });
// Case-insensitive when you ask for it.
snapshot.sessions.where({ name: { contains: "API", mode: "insensitive" } });
A Selection is immutable, ordered, replayable, and Iterable.
It is not an Array:
selection.length;
selection.at(-1);
selection.toArray();
[...selection];
selection.one({ name: "work" }); // throws NoMatchError / MultipleMatchesError
selection.oneOrUndefined({ name: "work" });
selection.exists({ name: "work" });
Criteria are data. encodeWhereDocument writes a model-tagged query;decodeWhereDocument validates one read from a config file, MCP call, or CLI.
Packages
The library, MCP server, workspace builder, and workspace CLI share one release version.
| Package | What it is | npm |
|---|---|---|
| libtmux | The library. Server, session, window, pane and client handles over a snapshot. | |
| @libtmux/mcp | An MCP server exposing tmux to an AI agent. | |
| @libtmux/workspace | Declarative workspace builder, tmuxp-shaped config. | |
| @libtmux/workspace-cli | The native tmux-workspace command for Node and Bun. |
|
| examples | Runnable examples, executed as tests. | — |
libtmux — the library
The library: Server, Session, Window, Pane and Client handles over an
immutable snapshot of tmux.
$ bun add --exact [email protected]
import { Server } from "libtmux";
const server = new Server();
const session = await server.newSession({ name: "work" });
const editor = await session.newWindow({ name: "editor" });
await editor.split();
await editor.panes.at(0)?.sendKeys("echo hello");
const lines = await editor.panes.at(0)?.capture();
Read next: Snapshots ·
Querying ·
Operations ·
Watching ·
Recipes ·
Errors ·
API reference
@libtmux/mcp — tmux for an AI agent
A stdio MCP server. Point it at a socket and an agent can list sessions, read a
pane, send keys, and wait for output rather than polling for it.
With no configuration it starts its own tmux server on the libtmux-mcp
socket:
$ npx -y @libtmux/[email protected]
Add it to any MCP client — this is the whole configuration:
{
"mcpServers": {
"tmux": {
"command": "npx",
"args": ["-y", "@libtmux/[email protected]"],
"env": { "LIBTMUX_SOCKET": "agent" }
}
}
}
Claude Code, in one command
$ claude mcp add tmux --env LIBTMUX_SOCKET=agent -- \
npx -y @libtmux/[email protected]
The tools an agent reaches for first:
| Tool | What it does |
|---|---|
list_sessions |
Lists stable session identities and metadata |
capture_since |
Returns only pane output after a cursor |
wait_for_text |
Waits for bounded literal or regular-expression matches |
run_shell_command |
Runs a command through a pane and reports output and exit status |
create_session |
Creates a session without accepting a command or environment value |
The tools are split into inspect, manage, execute, and teardown.tmux://capabilities is the only resource; the server exposes no prompts,
resource templates, background jobs, or generic mutation tools.
Read next: Why it exists ·
Configuration ·
Toolsets and trust ·
Choosing the right tool
@libtmux/workspace — declarative sessions
Describe a session; apply it. Applying twice converges rather than duplicating.
$ bun add --exact @libtmux/[email protected] [email protected]
import { Server } from "libtmux";
import { applyWorkspace } from "@libtmux/workspace";
const server = new Server();
await applyWorkspace(server, {
session_name: "api",
windows: [
{ window_name: "editor", panes: ["vim", "git status"] },
{ window_name: "server", panes: [{ shell_command: "bun dev", focus: true }] },
],
});
Read next: The config shape ·
Converging
@libtmux/workspace-cli — tmuxp workspaces from the shell
tmux-workspace loads, captures and converts tmuxp workspace files on Node or
Bun. Ordinary loads start no Python.
Save this document as workspace.yaml:
session_name: dev
windows:
- window_name: editor
layout: main-vertical
panes:
- vim
- top
Load it detached, on a socket of its own:
$ npx -y @libtmux/[email protected] load ./workspace.yaml \
-L workspace-cli-demo \
-d
Read next: Install ·
Load and capture ·
Command reference
examples — runnable, and run
Four programs covering acquisition, control-mode watching, the act-then-wait
loop an agent needs, and building a workspace. Each is executed by the
integration suite, so the code there is the code that runs.
$ bun test examples
How work is arranged
Observation, planning and concurrency compose around the same command engine —
the full table is here.
| Mode | Turn it on | When to use it |
|---|---|---|
| connected | await server.connect() |
Pairing commands with a persistent event observer |
| watching | server.watch() |
Reacting to a change rather than polling to find it |
| pipeline | server.pipeline([…]) |
Ordered commands when printed output is enough |
| planned | .plan + server.batch([…]) |
Ordered mutations that must return typed handles |
| concurrent | Promise.all |
Independent work that may safely overlap |
Creating twelve windows costs, each figure including the one query that reads
the result back:
- 25 tmux invocations one at a time, 13 through
pipeline, and 14 throughbatch, whose extra is the snapshot that turns printed ids
into typed handles. - A snapshot is one invocation whatever the server's size, and querying it is
none. - The wall-clock beside those counts, and why
Promise.allbuys nothing over
doing the same work in order, are in
the benchmarks.
$ bun packages/libtmux/scripts/bench-modes.ts
What this project promises
- The library ships zero runtime dependencies. A test holds it.
@libtmux/mcpand@libtmux/workspacedeclare their own. - Real tmux, every commit. CI runs the suite against every tmux release from 3.3a to 3.8a — no mocks stand in for a server.
- Documentation is a gate. Every public symbol carries a compiled example, the API reference is generated from the source that implements it, and every link, install command and recipe on this page is checked on each run.
- Softly tracks Python libtmux. Names and shapes follow 0.62.0 where TypeScript agrees with them; each departure is a decision a gate holds to the code.
Repository
packages/libtmux the library
packages/mcp the MCP server
packages/workspace the workspace builder
packages/workspace-cli the tmux-workspace command
examples runnable examples, used as tests
attic reference notes
Working here: AGENTS.md routes to the policy that governs a
change. The layout and the change discipline live there; how we write is in
.github/WRITING.md, and the gates, real tmux, and
releases are in .github/CONTRIBUTING.md.
Attribution
Please use the following BibTeX template to cite libtmux-ts in scientific discourse:
@misc{libtmux-ts,
author = {Tony Narlock},
year = {2026},
note = {https://libtmux.org/en/ts/},
title = {libtmux-ts: TypeScript wrapper for tmux}
}
License
MIT — a port of
tmux-python/libtmux.
Reviews (0)
Sign in to leave a review.
Leave a reviewNo results found