Skip to content

Repository files navigation

pty-server

A UI-independent, headless terminal session server. It owns PTYs and lets GUI, TUI, IDE, web, and agent clients create, inspect, attach to, and share terminal sessions. Window, pane, tab, layout, key binding, and rendering policy stay in clients.

What is included

  • @pty-server/server: embeddable TypeScript server and pty-server executable
  • @pty-server/client: TypeScript client SDK
  • pty-server-c: Rust-implemented dynamic library with a stable C ABI
  • examples/simple-client: small interactive attach client
  • Unix domain socket and TCP transports using the same protocol
  • ANSI screen snapshots so newly attached clients reconstruct terminal state

The protocol separates JSON control frames from raw binary PTY input/output. There is no line parsing, base64 conversion, or terminal emulation in the server, so escape sequences and arbitrary byte values remain intact.

Prerequisites

  • Node.js 22 or newer and a C/C++ toolchain (for node-pty)
  • just for the project commands
  • Rust stable (only for the C SDK)

mise.toml is an optional convenience for lightweight local environments that do not already provide these tools. It is not a development requirement and is normally unnecessary inside a full development container.

Quick start

just install
just serve

In a second terminal:

just client /tmp/pty-server.sock

The example creates a shell when no session ID is passed. Pass an existing ID after the socket arguments to attach another client to the same session.

For TCP, run just serve-tcp 8022 127.0.0.1 and start the built example with node examples/simple-client/dist/index.js --port 8022. TCP is intentionally unauthenticated at this layer; bind to loopback or put it behind an authenticated transport such as SSH, TLS proxy, or application-owned connection broker.

Embedding the server

import { PtyServer } from "@pty-server/server";

const server = new PtyServer({ socketPath: "/tmp/my-app-pty.sock" });
await server.listen();

For a machine-global installation, use the package executable with a managed Unix socket path or loopback TCP port. For an application embed, construct PtyServer and own its lifecycle with the rest of the application.

TypeScript SDK

import { PtyClient } from "@pty-server/client";

const client = new PtyClient({ socketPath: "/tmp/pty-server.sock" });
await client.connect();
const session = await client.create({ command: "/bin/zsh", name: "build" });
await client.attach(session.id);
client.on("data", (bytes: Buffer) => process.stdout.write(bytes));
client.write("printf 'hello\\n'\n");

C SDK

just build-c creates libpty_server_c.dylib on macOS, libpty_server_c.so on Linux, or the corresponding DLL on Windows. Public declarations are in sdk/c/include/pty_server.h. The generic pty_client_request API exposes every protocol method without ABI churn.

cc sdk/c/examples/simple.c -Isdk/c/include -Lsdk/c/target/release \
  -lpty_server_c -o target/c-simple

Development

just install
just check
just test
just build

See docs/protocol.md for wire format and method details.

Web multiplexer example

The small Next.js example uses xterm.js with its Unicode 11 width provider and a WebSocket bridge to an embedded server:

just web 3100

Open http://localhost:3100, create a few shells, and switch between them with the buttons at the top. Pass a different port as the first argument. The complete example lives in examples/web-multiplexer.

just web owns an embedded server, so stopping it also ends its sessions. To keep sessions while restarting the web UI, run the server separately and use the external mode:

# terminal 1
just serve /tmp/pty-server.sock

# terminal 2; safe to stop and restart without ending sessions
just web-external 3100 /tmp/pty-server.sock

About

Headless terminal session server, for other apps, TUIs, agents, ...

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages