Skip to content

UniPty v1 · PTY platform

The runtime-neutral PTY contract for Node, Bun, and Deno.

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.

One Core APIReplaceable BackendsEvidence-gated supportMIT
unipty — /bin/sh -i

$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 Backend. Spawn a shell.

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.

quick-start.mjs
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.

What’s inside

01

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.

02

Backends are replaceable

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.

03

Streams with honest backpressure

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.

04

Evidence, not promises

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

Every package states its substrate honestly

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.

PackageRuntimeSubstrateNotes
@unipty/backend-node-ptyNodenode-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-zigptyNodezigpty (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-bunBunBun.TerminalBun’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-ffiDeno@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.