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.
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. |
term.tag and term.pid are None until the started event is received. If you need them before iterating, consume the first event explicitly.
term.write(b"echo hello\n")
term.write(b"\x03") # Ctrl-C
Resizing the terminal
Call resize() whenever the host terminal dimensions change:
term.resize(cols=200, rows=50)
Killing the process
term.kill() # sends SIGKILL
Session tag and PID
After the started event, term.tag and term.pid are populated:
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:
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
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.
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:
# 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
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