# libtmux for TypeScript

Source: https://libtmux.org/en/ts/latest/

> libtmux for TypeScript, version latest.

![](https://libtmux.org/en/brand/typescript/library/logo.svg)

TypeScript-based tmux client

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

Product

Core API MCP Workspace Manager

## Start with TypeScript

Example details

\+ Error handling + Cleanup

```ts
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 });
}
```

```ts
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 });
}
```

```ts
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 });
}
```

```ts
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

```json
{"type":"module","private":true,"dependencies":{"libtmux":"file:./source/packages/libtmux"}}
```

### Setup and run

```bash
git init -q source
git -C source fetch -q --depth=1 \
  https://github.com/libtmux/libtmux-ts.git \
  46cccfef2a546de55ce8b308249b8c76339089a0
git -C source checkout -q --detach FETCH_HEAD
bun install
```

```bash
unset TMUX TMUX_PANE
export ENV=/dev/null BASH_ENV=/dev/null SHELL=/bin/sh
bun run quickstart.ts
```

### Expected output

```text
hello
```

Library revision: [46cccfef2a54](https://github.com/libtmux/libtmux-ts/tree/46cccfef2a546de55ce8b308249b8c76339089a0).

[Core Library documentation →](https://libtmux.org/en/ts/latest/core/)[More TypeScript examples →](https://libtmux.org/en/ts/latest/examples/)

### Install `libtmux`

Dependency cooldowns

The checkbox turns a cooldown on and off for every picker on the site. It applies to what an install pulls in; the package itself, and the libtmux it pins, stay current.

Cooldown type

Apply a cooldown of days Bypass any global cooldown

What are cooldowns?

Cooldowns delay picking up newly uploaded packages so the community has time to spot supply-chain attacks before you install. Most package managers don't apply one by default; you opt in through a global setting in your tool's configuration.

Learn more at [cooldowns.dev](https://cooldowns.dev/) · [Datadog Security Labs writeup](https://securitylabs.datadoghq.com/articles/dependency-cooldowns/).

```console
$ npm install libtmux
```

```console
$ npm install \
    --min-release-age=7 \
    --min-release-age-exclude=libtmux \
    libtmux
```

```console
$ npm install --min-release-age=0 libtmux
```

## MCP

The TypeScript server lets an agent work with tmux through 45 tools, grouped as Inspect, Manage, Execute and Teardown.

### Worked example

Given this prompt, an agent connected to the TypeScript server makes these calls and gets these results.

No sessions

After kill_session.

you

Create a tmux session called checks, split its window into two panes, run pytest -q in one and show checks.log in the other, show me the results, then remove the session.

agentCreate the session.

[`create_session`](https://libtmux.org/en/ts/latest/mcp/tools/create_session/) Execute `{"name":"checks"}`

Result

```json
{
  "paneId": "%0",
  "session": {
    "attachedClients": 0,
    "humanAttachedClients": 0,
    "id": "$0",
    "metadataComplete": true,
    "name": "checks",
    "omittedMetadataBytes": 0,
    "windows": 1
  },
  "windowId": "@0"
}
```

agentSplit the pane the create result returned (%0) in two.

[`split_window`](https://libtmux.org/en/ts/latest/mcp/tools/split_window/) Execute `{"paneId":"%0","direction":"right"}`

Result

```json
{
  "pane": {
    "active": false,
    "command": "sh",
    "cwd": "/home/user/project",
    "dead": false,
    "height": 24,
    "id": "%1",
    "index": 1,
    "isAttended": false,
    "isCallerPane": false,
    "metadataComplete": true,
    "omittedMetadataBytes": 0,
    "title": "host",
    "…": "6 more"
  }
}
```

agentRead the window's layout.

[`list_windows`](https://libtmux.org/en/ts/latest/mcp/tools/list_windows/) Inspect `{"session":"checks"}`

Result

```json
{
  "complete": true,
  "omittedEntries": 0,
  "windows": [
    {
      "id": "@0",
      "layout": "{\"V\":2,\"L\":{\"t\":\"h\",\"w\":80,\"h\":24,\"x\":0,\"y\":0,\"c\":[{\"t\":\"p\",\"w\":40,\"h\":24,\"x\":0,\"y\":0,\"a\":true,\"i\":0,\"I\":\"%0\"},{\"t\":\"p\",\"w\":39,\"h\":24,\"x\":41,\"y\":0,\"i\":1,\"I\":\"%1\"}]}}",
      "metadataComplete": true,
      "name": "sh",
      "omittedMetadataBytes": 0,
      "panes": 2,
      "zoomed": false,
      "omittedPlacements": 0,
      "placements": [
        {
          "active": true,
          "index": 0,
          "sessionId": "$0",
          "sessionName": "checks"
        }
      ],
      "placementsComplete": true
    }
  ]
}
```

agentRun the tests in the pane the create result returned (%0).

[`run_shell_command`](https://libtmux.org/en/ts/latest/mcp/tools/run_shell_command/) Execute `{"paneId":"%0","command":"pytest -q"}`

Result

```json
{
  "effectiveTimeoutMs": 30000,
  "exitStatus": 0,
  "foreignOutputSuspected": false,
  "droppedLines": 0,
  "missedBytes": 0,
  "omittedBytes": 0,
  "outcome": "completed",
  "output": "..                               [100%]\n2 passed in 0.00s",
  "outputComplete": true,
  "paneId": "%0",
  "returnedBytes": 57,
  "stillRunning": false
}
```

agentShow the log in the new pane the split returned (%1).

[`run_shell_command`](https://libtmux.org/en/ts/latest/mcp/tools/run_shell_command/) Execute `{"paneId":"%1","command":"cat checks.log"}`

Result

```json
{
  "effectiveTimeoutMs": 30000,
  "exitStatus": 0,
  "foreignOutputSuspected": false,
  "droppedLines": 0,
  "missedBytes": 0,
  "omittedBytes": 0,
  "outcome": "completed",
  "output": "2026-01-01 checks started\n2026-01-01 checks finished",
  "outputComplete": true,
  "paneId": "%1",
  "returnedBytes": 52,
  "stillRunning": false
}
```

agentRemove the session once the result is read.

[`kill_session`](https://libtmux.org/en/ts/latest/mcp/tools/kill_session/) Teardown `{"session":"checks"}`

Result

```json
{
  "killed": "$0"
}
```

agent in %0 it printed "2 passed in 0.00s"; in %1 it printed "2026-01-01 checks finished".

Calls and results are recorded against [`libtmux/libtmux-ts`](https://github.com/libtmux/libtmux-ts/tree/ecc3eaca0b4e05a77360da9d56790b409282c63b) at `ecc3eaca0`, on a private tmux 3.8a server, running pytest 9.2.0. The preview draws only what those results state. The agent's narration is illustrative. Long values in a result are shortened here; the recording keeps them. The sandbox's project directory is shown as `/home/user/project`.

### Install `@libtmux/mcp`

Choose your client and configuration scope, then run the command or paste the configuration.

```console
$ claude mcp add tmux -- npx -y @libtmux/mcp
```

Config file: `~/.claude.json (this project)`

* [All tools by tier →](https://libtmux.org/en/ts/latest/mcp/#tools-by-tier)
* [Every tool, with its arguments and result →](https://libtmux.org/en/ts/latest/mcp/tools/)
* [MCP documentation →](https://libtmux.org/en/ts/latest/mcp/)

Embed the server in a program

examples/mcp-agent/mcp-agent.ts

```typescript
export async function connectAgent(server: Server): Promise<Client> {
  const client = new Client({ name: "example", version: "0.0.0" });
  const [clientSide, serverSide] = InMemoryTransport.createLinkedPair();
  await Promise.all([
    createTmuxMcpServer(server, {
      environment: { LIBTMUX_TOOLSETS: "inspect,execute" },
    }).connect(serverSide),
    client.connect(clientSide),
  ]);
  return client;
}
```

Excerpt of [`examples/mcp-agent/mcp-agent.ts`](https://github.com/libtmux/libtmux-ts/blob/46cccfef2a546de55ce8b308249b8c76339089a0/examples/mcp-agent/mcp-agent.ts), which `bun run typecheck && bun run test` runs in the repository's CI (`.github/workflows/typescript.yml`).

## Workspace Manager

### Short-hand panes

A pane can be one command written as a string, and a command list can be one string.

```yaml
session_name: shorthands
windows:
  - window_name: long form
    panes:
      - shell_command:
          - echo 'did you know'
          - echo 'you can inline'
      - shell_command: echo 'single commands'
      - echo 'for panes'
```

```json
{
  "session_name": "shorthands",
  "windows": [
    {
      "window_name": "long form",
      "panes": [
        {
          "shell_command": [
            "echo 'did you know'",
            "echo 'you can inline'"
          ]
        },
        {
          "shell_command": "echo 'single commands'"
        },
        "echo 'for panes'"
      ]
    }
  ]
}
```

Converted from the YAML; the tests load the YAML.

0:long form

no layout set, so the port tiles the panes

Save the file as `workspace.yaml` and load it with:

```sh
tmux-workspace load workspace.yaml
```

Workspace file: [`packages/workspace-cli/tests/fixtures/tmuxp-examples/shorthands.yaml`](https://github.com/libtmux/libtmux-ts/blob/afd56d4dfa441b5bab9d9996ebd5b303fc5c4a3a/packages/workspace-cli/tests/fixtures/tmuxp-examples/shorthands.yaml). Tested by [`every tmuxp example workspace loads through the CLI`](https://github.com/libtmux/libtmux-ts/blob/afd56d4dfa441b5bab9d9996ebd5b303fc5c4a3a/packages/workspace-cli/tests/tmuxp-examples.test.ts#L304) (packages/workspace-cli/tests/tmuxp-examples.test.ts; a test runs the command).

### Install `@libtmux/workspace-cli`

The workspace package is separate from the core library.

Dependency cooldowns

The checkbox turns a cooldown on and off for every picker on the site. It applies to what an install pulls in; the package itself, and the libtmux it pins, stay current.

Cooldown type

Apply a cooldown of days Bypass any global cooldown

What are cooldowns?

Cooldowns delay picking up newly uploaded packages so the community has time to spot supply-chain attacks before you install. Most package managers don't apply one by default; you opt in through a global setting in your tool's configuration.

Learn more at [cooldowns.dev](https://cooldowns.dev/) · [Datadog Security Labs writeup](https://securitylabs.datadoghq.com/articles/dependency-cooldowns/).

```console
$ npx -y @libtmux/workspace-cli --help
```

```console
$ npx -y @libtmux/workspace-cli --help
```

npx ignores --min-release-age-exclude, so a cooldown here would also hold back the release this runs. This command runs without one.

```console
$ npx -y --min-release-age=0 @libtmux/workspace-cli --help
```

Runs tmux-workspace without installing it. Pass a command such as load ./workspace.yaml in place of --help.

* [Try other presets →](https://libtmux.org/en/ts/latest/workspace/#try-a-preset)
* [The `tmux-workspace load` command →](https://libtmux.org/en/ts/latest/workspace/cli/load/)
* [Workspace Manager documentation →](https://libtmux.org/en/ts/latest/workspace/)

Build the workspace from a program

examples/workspace/workspace.ts

```typescript
export const DEVELOPMENT_WORKSPACE = {
  session_name: "workspace-example",
  windows: [
    { panes: ["sleep 30", "sleep 30"], window_name: "editor" },
    { panes: ["sleep 30"], window_name: "server" },
    { window_name: "logs" },
  ],
} satisfies WorkspaceInput;


/** Apply the package's declarative workspace to a server. */
export function buildWorkspace(server: Server): Promise<Session> {
  return applyWorkspace(server, DEVELOPMENT_WORKSPACE);
}
```

Excerpt of [`examples/workspace/workspace.ts`](https://github.com/libtmux/libtmux-ts/blob/46cccfef2a546de55ce8b308249b8c76339089a0/examples/workspace/workspace.ts), which `bun run typecheck && bun run test` runs in the repository's CI (`.github/workflows/typescript.yml`).

### [Topics](https://libtmux.org/en/ts/latest/topics/)

[Subject-by-subject, with TypeScript in every code block.](https://libtmux.org/en/ts/latest/topics/)

### [Guides](https://libtmux.org/en/ts/latest/guides/)

[Task guides for TypeScript.](https://libtmux.org/en/ts/latest/guides/)

### [Examples](https://libtmux.org/en/ts/latest/examples/)

[Complete programs, sourced from the code each port tests.](https://libtmux.org/en/ts/latest/examples/)

### [Concepts](https://libtmux.org/en/ts/latest/concepts/)

[The domain model every port shares.](https://libtmux.org/en/ts/latest/concepts/)

## Packages and integrations

### [Workspace Manager](https://libtmux.org/en/ts/latest/workspace/)

[Load workspace files with the published tmux-workspace load CLI.](https://libtmux.org/en/ts/latest/workspace/)

### [MCP](https://libtmux.org/en/ts/latest/mcp/)

[In development. Connect an MCP client to tmux tools and inspect the server API.](https://libtmux.org/en/ts/latest/mcp/)

## API references

### [Official reference](https://libtmux.org/en/ts/latest/reference/)

[TypeScript API signatures and documentation, with links to related guides and APIs in other languages.](https://libtmux.org/en/ts/latest/reference/)

[All TypeScript versions and install instructions](https://libtmux.org/en/ts/)
