One contract, three runtimes
Core owns streams, bootstrap buffering, UTF-8 conversion, backpressure, common errors, and lifecycle. Your code stays runtime-neutral across Node, Bun, and Deno.
UniPty v1 · PTY platform
One Core API for pseudo-terminals. Bring your own native substrate — node-pty, zigpty, Bun.Terminal, or @sigma/pty-ffi — through developer-selectable, replaceable Backends. Support claims come only from the release evidence catalog, never from metadata.
$node quick-start.mjs
backend ready: @unipty/backend-node-pty
spawn: /bin/sh -i · 80x24 cells
stream: utf8 text · bootstrap buffered
exit: code 0 · signal null
Quick start
Acquire a ready Backend, hand it to Core, spawn a shell with a structured argv — no string commands, no implicit shell. The same Core contract runs on every official Backend route: swapping routes means swapping the Backend you acquire, nothing else changes.
import { UniPty } from "unipty";
import { createNodePtyBackend } from "@unipty/backend-node-pty";
// 1. Acquire a ready Backend (one-time, asynchronous).
const backend = await createNodePtyBackend();
// 2. Core accepts only a structurally ready Backend.
const unipty = new UniPty({ backend });
// 3. Spawn: argv is non-empty, argv[0] is the executable.
const pty = unipty.spawn(["/bin/sh", "-i"], {
terminal: { cols: 80, rows: 24 },
});
// 4. Consume Terminal Text (or Terminal Bytes with "bytes").
for await (const chunk of pty.stream({ encoding: "utf8" })) {
process.stdout.write(chunk);
}
// 5. Write with boolean readiness; drain when Core says pause.
if (!pty.write("echo hello\r")) {
await pty.drain();
}
// 6. Observe process exit independently of the stream.
const { exitCode, signal } = await pty.exited;Walk the full contract — streams, backpressure, lifecycle, capabilities — on the documentation page.
Core owns streams, bootstrap buffering, UTF-8 conversion, backpressure, common errors, and lifecycle. Your code stays runtime-neutral across Node, Bun, and Deno.
The native substrate — node-pty, zigpty, Bun.Terminal, @sigma/pty-ffi — lives behind a Backend Endpoint seam. Persistent or remote hosts arrive as Backends, not a second plugin lifecycle.
write() returns boolean readiness, drain() waits for recovery, and queues stay bounded. Saturation rejects one whole value with a typed failure — never a partial accept, silent drop, or unbounded queue.
Metadata declares targets; it never claims support. Only a full-suite conformance pass against an installed package on an exact runtime/platform tuple becomes verified in the release catalog.
Official routes
Official Backend packages use the uniform @unipty/backend-* namespace; provenance describes the implementation kind and substrate, and none of these declarations is a support claim.
| Package | Runtime | Substrate | Notes |
|---|---|---|---|
@unipty/backend-node-pty | Node | node-pty (via @lydell/node-pty prebuilds) | A third-party native addon with prebuilt binaries. Node has no native PTY API; this route wraps the ecosystem standard substrate honestly. |
@unipty/backend-zigpty | Node | zigpty (Zig-built NAPI prebuilds) | A second Node route over the Zig-implemented substrate: prebuilds for eight tuples bundled in the tarball, zero install scripts, and a hard native gate that never falls back to a pipe pseudo-PTY. |
@unipty/backend-bun | Bun | Bun.Terminal | Bun’s built-in terminal API: Linux/macOS since Bun 1.3.13, Windows via ConPTY since 1.3.14. Support is versioned evidence, not a blanket claim. |
@unipty/backend-deno-sigma__pty-ffi | Deno | @sigma/pty-ffi (Rust portable-pty) | An npm-only package whose build vendors the @sigma/pty-ffi/noinit closure and targeted dynamic libraries. Requires explicit Deno FFI permission. |
Which tuples are actually verified for the current release? See the compatibility catalog.