# Attach and send keys

Source: https://libtmux.org/en/ts/latest/examples/attach-and-send-keys/

> Get a session handle, send a command to a pane, and capture output.

Get a session handle, send a command to a pane, and capture output. These
examples use libtmux from your program; to attach your terminal interactively,
see [Attaching to tmux](https://libtmux.org/en/ts/latest/guides/attaching-to-tmux/).

The examples include setup, error handling, and cleanup. [Source and
verification](https://libtmux.org/en/ts/latest/examples/attach-and-send-keys/#where-this-comes-from) identifies their files and checks.

```typescript file="examples/quickstart/quickstart.ts"
import { Server, TmuxCommandError, type ServerSnapshot } from "libtmux";

/**
 * A runnable tour of the API, driven by the tests so it cannot rot.
 *
 * Every step here appears in README.md.
 */
export async function quickstart(server: Server): Promise<ServerSnapshot> {
  // Nothing is read until you ask. `snapshot()` is the only step that talks to
  // tmux; everything reachable from it resolves locally.
  const session = await server.newSession({ name: "quickstart" });
  const editor = await session.newWindow({ name: "editor" });
  await editor.split();

  const snapshot = await server.snapshot();

  // Declarative filtering, serializable and stable on the wire.
  const found = snapshot.windows.where({ name: "editor" }).one();

  // Relations are plain properties: no await, no tmux command.
  const paneCount = found.panes.length;
  if (paneCount !== 2) throw new Error(`expected two panes, saw ${String(paneCount)}`);

  // A criterion is spelled like the handle accessor it filters.
  if (snapshot.panes.count({ currentCommand: { contains: "" } }) === 0) {
    throw new Error("expected panes to report a current command");
  }
  const first = found.panes.at(0);
  if (first === undefined) throw new Error("expected a pane");
  await first.sendKeys("echo hello-from-libtmux", { literal: true });

  // Failures carry their parts rather than a formatted sentence.
  try {
    await server.setOption("not-a-real-option", "1");
  } catch (error) {
    if (!(error instanceof TmuxCommandError)) throw error;
    if (error.args[0] !== "set-option") throw error;
  }

  return snapshot;
}
```

## Finding an existing session instead

For a script that runs repeatedly, look up a session before creating it.
[Attaching to
tmux](https://libtmux.org/en/ts/latest/guides/attaching-to-tmux/#finding-a-session-instead-of-always-creating-one)
shows that pattern, and [Filtering and querying, in
practice](https://libtmux.org/en/ts/latest/guides/querying-and-filtering/) covers absent and ambiguous matches.

## Where this comes from

**Source:** [`examples/quickstart/quickstart.ts`](<https://github.com/libtmux/libtmux-ts/blob/f8ca14551254f81800faedc8bee9151565044ef1/examples/quickstart/quickstart.ts>)

**In this page:** read whole from the file

**Checked by:** run against real tmux by `bun test examples`; its first half is
also mirrored into README.md under a `<!-- runs: ... -->` marker, checked
line-for-line by [`scripts/check-doc-runnable.ts`](<https://github.com/libtmux/libtmux-ts/blob/f8ca14551254f81800faedc8bee9151565044ef1/scripts/check-doc-runnable.ts>)

### Source inclusion

A `file="..."` fence reads the named source during the site build. Hand-quoted
excerpts are copies; their source files and regions are listed above.
