Skip to main content
The TypeScript SDK exposes PTY support through sandbox.pty. The module has three callables you’ll use directly: pty.create() to start a new session, pty.connect() to reattach to an existing one, and the low-level pty.sendStdin / resize / kill trio when you already hold a pid. For conceptual background see the PTY feature overview.

sandbox.pty.create(opts?)Promise<PtyHandle>

Create a new PTY session. The sandbox spawns an interactive bash -l login shell with TERM=xterm-256color pre-set.
const handle = await sandbox.pty.create({
  size: { cols: 120, rows: 30 },
  user: "user",
  cwd: "/workspace",
  envs: { FOO: "bar" },
  timeout: 3600,            // seconds; 0 = indefinite
  onData: (bytes) => process.stdout.write(bytes),
});

PtyCreateOpts

FieldTypeDefaultDescription
sizePtySize{ cols: 80, rows: 24 }Initial terminal size.
userstring"user"User the shell runs as.
cwdstringundefinedStarting working directory.
envsRecord<string, string>undefinedEnvironment variables merged into the shell env. TERM defaults to xterm-256color.
timeoutnumber3600PTY session TTL in seconds. 0 keeps the session alive until the sandbox itself expires.
onData(data: Uint8Array) => voidundefinedIf provided, the handle auto-opens the output stream and invokes this for each chunk.
requestTimeoutnumberundefinedPer-request timeout in milliseconds for the create POST.

sandbox.pty.connect(pid, opts?)PtyHandle

Reattach to a PTY session that’s already running. Returns a fresh PtyHandle that streams live output from the moment it connects. Multiple clients can be attached to the same pid concurrently.
const handle = sandbox.pty.connect(pid, {
  onData: (bytes) => term.write(bytes),  // e.g. forward to xterm.js
});

PtyConnectOpts

FieldTypeDescription
onData(data: Uint8Array) => voidOptional callback invoked with every output chunk. Omit to drive the stream manually via handle.stream().

PtyHandle

Returned by both create() and connect().

Properties

  • handle.pid: number — remote process id.

Methods

handle.sendInput(data: Uint8Array | string, requestTimeout?: number): Promise<void>

Forward input to the shell. Strings are UTF-8 encoded.
await handle.sendInput("echo hello\n");
await handle.sendInput(new Uint8Array([0x03]));  // Ctrl-C

handle.resize(size: PtySize, requestTimeout?: number): Promise<void>

Change the remote terminal dimensions. Fires SIGWINCH inside the sandbox so ncurses apps redraw.

handle.disconnect(): void

Stop consuming output without killing the remote process. A later sandbox.pty.connect(pid) reattaches cleanly.

handle.kill(requestTimeout?: number): Promise<boolean>

Terminate the remote shell. Returns true if the session existed at the time of the call.

handle.wait(): Promise<PtyResult>

Resolves when the remote shell exits.

handle.stream(): AsyncGenerator<Uint8Array>

Async iterator over raw output chunks — for when you want to drive the stream manually instead of via onData:
for await (const chunk of handle.stream()) {
  term.write(chunk);
}
Don’t mix onData and stream() on the same handle; they consume the same underlying connection.

PtyResult

interface PtyResult {
  exitCode: number;   // -1 if the stream dropped without a clean exit frame
}

Low-level API (by pid)

When you don’t hold a PtyHandle, use the module-level methods:
await sandbox.pty.sendStdin(pid, data);     // Uint8Array | string
await sandbox.pty.resize(pid, { cols, rows });
await sandbox.pty.kill(pid);                // → boolean

Example: wire a sandbox PTY into xterm.js

import { Terminal } from "@xterm/xterm";
import { FitAddon } from "@xterm/addon-fit";
import { Sandbox } from "@declaw/sdk";

const term = new Terminal({ cursorBlink: true });
const fit = new FitAddon();
term.loadAddon(fit);
term.open(document.getElementById("terminal")!);
fit.fit();

const sbx = await Sandbox.create();
const handle = await sbx.pty.create({
  size: { cols: term.cols, rows: term.rows },
  onData: (bytes) => term.write(bytes),
});

// keystrokes → sandbox PTY
term.onData((d) => handle.sendInput(d));

// pane resize → TIOCSWINSZ
new ResizeObserver(() => {
  fit.fit();
  void handle.resize({ cols: term.cols, rows: term.rows });
}).observe(document.getElementById("terminal")!);
That’s the full wiring for a browser terminal talking to a sandbox PTY — every keystroke round-trips through sendInput, every output byte comes back via onData, every pane resize fires TIOCSWINSZ inside the microVM.