> ## Documentation Index
> Fetch the complete documentation index at: https://docs.declaw.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# PTY (pseudo-terminals)

> Run interactive commands inside a sandbox with a real pseudo-terminal — ANSI colours, live streaming output, keystroke-level stdin, session reconnect.

A **PTY** (pseudo-terminal) attaches a real TTY to a process inside the
sandbox. Unlike [`commands.run`](/features/commands), which returns a
single `{stdout, stderr, exit_code}` blob when the process finishes, a
PTY lets you:

* **Stream bytes as they're produced** — the right choice for anything
  that paints the screen (progress bars, TUI apps, live logs).
* **Send keystrokes mid-execution** — required for password prompts,
  OAuth pasteback, confirmation dialogs, REPLs, and editors.
* **Resize on the fly** — if the user drags a terminal pane, the remote
  program sees a `SIGWINCH` and redraws.
* **Reconnect and fan out** — multiple clients can subscribe to the
  same PTY concurrently, and clients can disconnect without killing
  the shell.

Inside the sandbox the PTY runs an interactive `bash -l` (login shell)
with `TERM=xterm-256color` pre-set, so ANSI colour codes, cursor escapes,
`tput` queries, and `ncurses`-based TUIs (`vim`, `htop`, `less`, `nano`)
all render correctly.

## When to use PTY vs `commands.run`

| Use `commands.run` when...                              | Use `pty.create` when...                      |
| ------------------------------------------------------- | --------------------------------------------- |
| Command takes input once (argv / piped stdin) and exits | Command prompts for input mid-run             |
| You only care about the final stdout / exit code        | You need live output as bytes arrive          |
| Output is line-oriented plain text                      | Output has ANSI escapes or cursor movement    |
| `pip install`, `pytest`, `python script.py`, `go build` | `gh auth login`, `vim`, `htop`, `sudo`, `ssh` |

**Default to `commands.run`.** Reach for `pty.create` only when the
command *requires* a terminal.

## Architecture

`pty.create` takes four REST calls plus one SSE stream:

| Operation       | Method / route                                                                      |
| --------------- | ----------------------------------------------------------------------------------- |
| Create session  | `POST /sandboxes/{id}/pty` — returns the remote `pid`                               |
| Send stdin      | `POST /sandboxes/{id}/pty/{pid}/stdin`                                              |
| Resize          | `PATCH /sandboxes/{id}/pty/{pid}`                                                   |
| Kill            | `DELETE /sandboxes/{id}/pty/{pid}`                                                  |
| **Live output** | `GET  /sandboxes/{id}/pty/{pid}/stream` (Server-Sent Events, base64-encoded frames) |

The SSE stream stays open for the life of the session. Output bytes are
emitted as `event: data` frames with base64-encoded payload, and a final
`event: exit` frame announces the remote exit code.

## Session lifecycle

A PTY session is bounded by **two independent timeouts** — whichever
fires first ends the session:

* The **sandbox timeout** (set at `Sandbox.create(timeout=...)`, default
  300s) kills the whole sandbox and every PTY inside it.
* The **PTY timeout** (set at `sandbox.pty.create(timeout=...)`, default
  3600s) kills just that one PTY. Pass `0` for no PTY-level TTL —
  sessions then live until the sandbox itself expires.

For an interactive coding-agent session you typically raise *both*:

```python theme={null}
sbx = Sandbox.create(timeout=3600)          # VM lives an hour
handle = sbx.pty.create(timeout=0)          # PTY lives as long as VM
```

## Quick start

Callback-style — your function receives every chunk of PTY output as it
arrives. Good for forwarding to an `xterm.js` instance or your local
terminal.

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    import sys
    from declaw import Sandbox
    from declaw.sandbox.commands.models import PtySize

    with Sandbox.create() as sbx:
        handle = sbx.pty.create(
            size=PtySize(cols=120, rows=30),
            on_data=lambda chunk: sys.stdout.buffer.write(chunk),
            timeout=3600,
        )
        handle.send_stdin("echo hello && exit\n")
        result = handle.wait(timeout=10)
        print(f"\nexit: {result.exit_code}")
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    import { Sandbox } from "@declaw/sdk";

    const sbx = await Sandbox.create();
    try {
      const handle = await sbx.pty.create({
        size: { cols: 120, rows: 30 },
        onData: (chunk) => process.stdout.write(chunk),
        timeout: 3600,
      });
      await handle.sendInput("echo hello && exit\n");
      const { exitCode } = await handle.wait();
      console.log(`\nexit: ${exitCode}`);
    } finally {
      await sbx.kill();
    }
    ```
  </Tab>
</Tabs>

## Next steps

* [Python SDK: PTY reference](/sdks/python/pty) — `PtyHandle`, `PtyResult`, iterator-style streaming, `connect()`
* [TypeScript SDK: PTY reference](/sdks/typescript/pty) — same surface in TypeScript
* [Cookbook: interactive terminal](/cookbook/pty/interactive-terminal) — drop your local TTY into a sandbox shell, `ssh`-style
