PTY

August 28, 2026 · View on GitHub

Drive interactive terminal programs from AI agents — REPLs, TUI wizards, anything that needs a real pseudoterminal.

Overview

agents pty allocates a real pseudoterminal (PTY) via forkpty, runs a shell or program inside it, and exposes read/write/screen operations over a local HTTP sidecar. Agents interact through simple CLI calls rather than raw terminal I/O.

The primary use cases are:

  • Drive REPLs (Python, Node, Ruby irb, psql) from agent code
  • Automate TUI programs (npm init, git add -p, interactive installers)
  • Test CLI tools that require a real PTY
  • Run the agents CLI itself from inside another agent

The sidecar server auto-starts on the first pty command and runs in the background until you stop it explicitly. You do not need to start it manually.

Architecture

agent process

     │  agents pty <subcommand> <id>

  CLI (pty.ts)

     │  HTTP  →  localhost:<pty-port>

  PTY sidecar (pty-server.ts)
  [auto-started on first use]

     │  forkpty()

  child process  (zsh / python3 / node / ...)

     ├── stdin  ← write / exec
     ├── stdout → ring buffer → read / screen
     └── signals ← signal

The screen command renders the current terminal buffer as clean text (no ANSI escape codes). This is the interface intended for LLM consumption — parse it without a terminal emulator.

Setup

No installation step. The sidecar starts automatically:

SID=$(agents pty start)

The session ID is a short random string. Store it in a variable and pass it to every subsequent command.

Verify the server is running at any time:

agents pty server status

If the sidecar cannot boot, the failing command prints the exact server command, the PTY log path, and the recent log tail. The log path is also shown by agents pty server status and normally lives under ~/.agents/.cache/helpers/pty/logs.jsonl.

Native binding

The sidecar drives a real PTY through @homebridge/node-pty-prebuilt-multiarch, a native N-API addon. Linux prebuilds (glibc + musl, every arch and Node ABI) are baked into the npm tarball, so Linux needs no compiler or network fetch. The macOS/Windows binaries are downloaded per host + Node ABI at install time by the package's own prebuild-install postinstall — which is why the dep sits in package.json's trustedDependencies (bun otherwise skips install scripts).

That fetch has no prebuild to grab when your Node runtime is newer than any the package published (the PHNX-2740 case: @homebridge/node-pty-prebuilt-multiarch@0.13.1 shipped no darwin-arm64 binary above Node 24, so Node 25/26 on Apple Silicon had nothing to load). When the binding genuinely can't load, the sidecar now fails loud with your platform, the running Node ABI, and a remediation instead of a raw MODULE_NOT_FOUND. To recover:

npm rebuild @homebridge/node-pty-prebuilt-multiarch   # fetch/build for your Node
# or reinstall the CLI so the postinstall reruns:
npm i -g @phnx-labs/agents-cli

If your Node is newer than every published prebuild, install a current LTS Node (or ensure a C++ toolchain is present so the source build can run) and retry.

Command Reference

Session lifecycle

CommandDescription
agents pty startStart a new PTY session; prints session ID to stdout
agents pty stop <id>Stop a session and clean it up; ID becomes invalid
agents pty listList all active sessions

start flags:

FlagDefaultDescription
-r, --rows <n>24Terminal height in rows
-c, --cols <n>120Terminal width in columns
-s, --shell <shell>$SHELLShell or program to launch (e.g., python3, zsh)
-d, --cwd <dir>current dirWorking directory
--jsonOutput full session metadata as JSON

I/O

CommandDescription
agents pty exec <id> <command>Send a command string (non-blocking; returns immediately)
agents pty write <id> <input>Send raw keystrokes; processes \n, \t, \e, \xHH escape codes
agents pty read <id>Read raw output including ANSI codes; use screen for clean text
agents pty screen <id>Render the terminal buffer as clean text (no ANSI)

exec flags:

FlagDescription
--wait <ms>Wait this many milliseconds then return the screen (convenience; default 0)
--jsonOutput as JSON

read flags:

FlagDefaultDescription
-m, --ms <ms>200Wait up to this many milliseconds for new output (50–5000)
--jsonOutput as JSON

write flags:

FlagDescription
--rawSend input literally without processing \n \t \e \xHH escape codes
--jsonOutput as JSON

screen flags:

FlagDescription
--jsonOutput as JSON including cursor position and dimensions

Control

CommandDescription
agents pty signal <id> [signal]Send POSIX signal: INT (default), TERM, or KILL
agents pty resize <id>Resize the terminal; -r <rows>, -c <cols>

Server management

CommandDescription
agents pty server statusShow PID, session count, and log path
agents pty server startStart the server manually (auto-starts anyway)
agents pty server stopStop the server and kill all active sessions

Recipes

1. Start a Python REPL and run code

SID=$(agents pty start --shell python3)
sleep 1 && agents pty screen $SID   # see the >>> prompt

agents pty write $SID "import math\n"
agents pty write $SID "math.sqrt(144)\n"
sleep 0.5 && agents pty screen $SID  # see 12.0

agents pty stop $SID

2. Send a multi-line program

SID=$(agents pty start --shell python3)
sleep 1

# Each \n is processed as a real newline character
agents pty write $SID "def greet(name):\n    return f'hello, {name}'\n\n"
agents pty write $SID "greet('world')\n"
sleep 0.5 && agents pty screen $SID

agents pty stop $SID

3. Snapshot the screen after a slow command

SID=$(agents pty start)

# exec + --wait avoids a manual sleep in the caller
agents pty exec $SID "git log --oneline -20" --wait 500

# Or poll manually with screen
agents pty exec $SID "npm install"
sleep 5 && agents pty screen $SID

agents pty stop $SID

4. Send Ctrl-C to interrupt a running program

# Via signal subcommand
agents pty signal $SID INT

# Or via write with the ETX byte
agents pty write $SID "\x03"

Demo

See also

  • docs/browser.md — drive real browsers via CDP; part of the automation triad
  • docs/computer.md — drive native macOS apps via Accessibility; part of the automation triad
  • docs/concepts.md — DotAgents repos, resource resolution model