Skip to content

Repository files navigation

libtmux for TypeScript

Typed control of tmux for Bun and TypeScript — immutable snapshots, declarative queries, zero runtime dependencies.

QuickstartQueryingPackagesAPI referenceExamplesChangelog

npm downloads typescript tmux dependencies license


Warning

Alpha. Releases carry an -alpha prerelease tag. The API is not settled, and any release may change or remove exported identifiers without a deprecation period. Pin an exact version. Not recommended for production. 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 is a library for controlling a running server, not for configuring one.

The idea in one line: read the whole server once into an immutable snapshot, then query it like data.

Quickstart

$ bun add --exact libtmux@0.1.0-alpha.8
npm, pnpm, yarn
$ npm i --save-exact libtmux@0.1.0-alpha.8
$ pnpm add --save-exact libtmux@0.1.0-alpha.8
$ yarn add --exact libtmux@0.1.0-alpha.8

Requires Bun 1.3.14+ or Node 22+, and tmux 3.2a or newer.

Linux is the only supported host for real tmux control. The macOS CI lane checks package artifacts without exercising tmux; macOS runtime behavior is unproven. WSL is untested.

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 — but it is deliberately 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

Three packages, released together, each usable on its own.

Package What it is npm
libtmux The library. Server, session, window, pane and client handles over a snapshot. npm
@libtmux/mcp An MCP server exposing tmux to an AI agent. npm
@libtmux/workspace Declarative workspace builder, tmuxp-shaped config. npm
examples Runnable examples, executed as tests.

libtmux — the library

$ bun add --exact libtmux@0.1.0-alpha.8
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.

$ npx -y @libtmux/mcp@0.1.0-alpha.8

Add it to any MCP client — this is the whole configuration:

{
  "mcpServers": {
    "tmux": {
      "command": "npx",
      "args": ["-y", "@libtmux/mcp@0.1.0-alpha.8"],
      "env": { "LIBTMUX_SOCKET": "agent" }
    }
  }
}
Claude Code, in one command
$ claude mcp add tmux --env LIBTMUX_SOCKET=agent -- \
    npx -y @libtmux/mcp@0.1.0-alpha.8

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 45 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/workspace@0.1.0-alpha.8 libtmux@0.1.0-alpha.8
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

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

For its create-twelve-windows-and-query workload, the benchmark uses 25 invocations one at a time, 13 through pipeline, and 14 through batch; the last includes the snapshot that resolves typed handles. It reports machine-specific timings beside those deterministic counts:

$ bun packages/libtmux/scripts/bench-modes.ts

What this package promises

  • Zero runtime dependencies. A property under test, not an aspiration.
  • Real tmux, every commit. CI runs the suite against every tmux release the badge above names — 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
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.

License

MIT — a port of tmux-python/libtmux.

About

Alpha. Typed tmux control for Bun and TypeScript: immutable snapshots, declarative queries, zero runtime dependencies. Ships an MCP server for AI agents.

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages