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

# Interactive PTY Sessions Inside Wrenn Capsules

> Open an interactive terminal (PTY) session inside a capsule using capsule.pty(). Sessions survive WebSocket disconnects and can be reconnected by tag.

`capsule.pty()` opens a real pseudo-terminal (PTY) inside the capsule over a WebSocket connection. You can run any interactive program — a shell, a REPL, a TUI — send keystrokes, resize the terminal, and read raw output bytes in real time. Sessions survive WebSocket disconnects: the process keeps running on the capsule, and you can reconnect to it later using its tag.

## Opening a PTY session

`capsule.pty()` is a context manager. On entry it opens a WebSocket and starts the specified command inside a PTY. On exit it sends `SIGKILL` to the process and closes the connection.

```python theme={null}
import sys
from wrenn import Capsule

with Capsule(template="minimal", wait=True) as capsule:
    with capsule.pty(cmd="/bin/bash", cols=120, rows=40, cwd="/home/user") as term:
        term.write(b"ls -la\n")
        for event in term:
            if event.type == "output":
                sys.stdout.buffer.write(event.data)
            elif event.type == "exit":
                break
```

### Parameters

| Parameter | Type                     | Default       | Description                        |
| --------- | ------------------------ | ------------- | ---------------------------------- |
| `cmd`     | `str`                    | `"/bin/bash"` | Command to run inside the PTY.     |
| `args`    | `list[str] \| None`      | `None`        | Additional arguments for `cmd`.    |
| `cols`    | `int`                    | `80`          | Initial terminal column count.     |
| `rows`    | `int`                    | `24`          | Initial terminal row count.        |
| `envs`    | `dict[str, str] \| None` | `None`        | Additional environment variables.  |
| `cwd`     | `str \| None`            | `None`        | Working directory for the process. |

## Iterating events

Iterating over the session yields `PtyEvent` objects:

| Event type | Fields          | Description                                                           |
| ---------- | --------------- | --------------------------------------------------------------------- |
| `started`  | `pid`, `tag`    | Process has started. `pid` and `tag` become available on the session. |
| `output`   | `data: bytes`   | Raw bytes written to the PTY by the process.                          |
| `exit`     | `exit_code`     | Process has exited. Iteration stops automatically.                    |
| `error`    | `data`, `fatal` | A stream-level error. Fatal errors stop the iterator.                 |

<Note>
  `term.tag` and `term.pid` are `None` until the `started` event is received. If you need them before iterating, consume the first event explicitly.
</Note>

## Sending input

```python theme={null}
term.write(b"echo hello\n")
term.write(b"\x03")  # Ctrl-C
```

## Resizing the terminal

Call `resize()` whenever the host terminal dimensions change:

```python theme={null}
term.resize(cols=200, rows=50)
```

## Killing the process

```python theme={null}
term.kill()  # sends SIGKILL
```

## Session tag and PID

After the `started` event, `term.tag` and `term.pid` are populated:

```python theme={null}
with capsule.pty(cmd="/bin/bash") as term:
    for event in term:
        if event.type == "started":
            print(f"shell PID: {term.pid}, tag: {term.tag}")
            term.write(b"echo reconnect-demo\n")
        elif event.type == "output":
            sys.stdout.buffer.write(event.data)
        elif event.type == "exit":
            break

saved_tag = term.tag  # save for reconnect
```

## Reconnecting to an existing session

The PTY process keeps running on the capsule after you disconnect. Use `capsule.pty_connect()` with the saved tag to reattach:

```python theme={null}
with capsule.pty_connect(saved_tag) as term:
    term.write(b"echo reconnected\n")
    for event in term:
        if event.type == "output":
            sys.stdout.buffer.write(event.data)
        elif event.type == "exit":
            break
```

<Note>
  Sessions persist until the process exits or the capsule is destroyed. You can disconnect and reconnect as many times as you need without losing the process state.
</Note>

## Accessing services running inside the capsule

If a process inside the capsule listens on a port, use `capsule.get_url()` to get a proxy URL you can connect to from outside:

```python theme={null}
# Start a server inside the capsule
capsule.commands.run("python -m http.server 8080", background=True)

# Get the proxy URL
url = capsule.get_url(8080)
print(url)  # "wss://8080-cl-abc123.app.wrenn.dev"
```

## Complete example

```python theme={null}
import sys
from wrenn import Capsule

with Capsule(template="minimal", wait=True) as capsule:
    # Open an interactive bash session
    with capsule.pty(cmd="/bin/bash", cols=120, rows=40) as term:
        term.write(b"export PS1='$ '\n")
        term.write(b"cd /tmp && python3 -c \"print('hello from PTY')\"\n")
        term.write(b"exit\n")

        for event in term:
            if event.type == "started":
                print(f"[started pid={term.pid} tag={term.tag}]")
            elif event.type == "output":
                sys.stdout.buffer.write(event.data)
                sys.stdout.buffer.flush()
            elif event.type == "exit":
                print(f"\n[exited code={event.exit_code}]")
                break
```
