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.
@pty-server/server: embeddable TypeScript server andpty-serverexecutable@pty-server/client: TypeScript client SDKpty-server-c: Rust-implemented dynamic library with a stable C ABIexamples/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.
- Node.js 22 or newer and a C/C++ toolchain (for
node-pty) justfor 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.
just install
just serveIn a second terminal:
just client /tmp/pty-server.sockThe 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.
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.
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");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-simplejust install
just check
just test
just buildSee docs/protocol.md for wire format and method details.
The small Next.js example uses xterm.js with its Unicode 11 width provider and a WebSocket bridge to an embedded server:
just web 3100Open 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