xterm.js + PTY over WebSocket
Browser renders a terminal; PTY subprocess does the actual shell work; WebSocket pipes bytes between them.
Code example
# server
import pty, os
master, slave = pty.openpty()
proc = subprocess.Popen(['bash'], stdin=slave, stdout=slave, stderr=slave, start_new_session=True)
async def pipe_to_ws(master_fd, ws):
while True:
data = os.read(master_fd, 1024)
await ws.send_bytes(data)
How it works
A real browser terminal needs three pieces. (1) xterm.js — a JS library that renders a terminal UI in a div, handles keystrokes, and writes bytes for the cursor / colors / scrollback. (2) A PTY (pseudo-terminal) — a Unix kernel feature that lets you spawn bash as if it were connected to a real terminal. bash thinks it's talking to a real screen; the master side gives you a file descriptor you can read/write. (3) A WebSocket — pipes keystrokes from xterm to the PTY stdin, and PTY stdout bytes back to xterm. The notebook terminal uses this exact stack.
🔍 Where this is used in OpenBenchML
app/routes/notebook.py — /api/notebook/terminal WS endpoint spawns a PTY bash subprocess.