Terminal
The terminal is the primary interface in the command center. Each terminal tab gives you a real bash session. In legacy mode (Pi, optimal.miami), the PTY runs directly on the Pi via pty-host.py (Python os.forkpty()). In fabric mode (Hetzner, fabric.optimal.miami), most terminals are PTYs running on paired devices, relayed over the fabric multiplex — see Fabric mode: PTY relay.
How It Works
The terminal system uses a two-process architecture:
- pty.ts in the Hono server manages sessions, spawning a
pty-host.pysubprocess per terminal - pty-host.py runs as a separate Python process, using
os.forkpty()to create real PTYs
This avoids Bun's SIGHUP interference with forkpty() and provides reliable PTY sessions without node-pty. (Legacy mode runs this directly on the Pi; no Docker involved on that host.)
xterm.js <-> WebSocket /ws/terminal <-> handler.ts <-> pty.ts
|
Bun.spawn("python3 pty-host.py ...")
|
os.forkpty() -> /bin/bash or tmuxOpening Terminals
New Shell
Click the + button in the tab bar and select "New Shell" to open a new bash session. This is equivalent to opening a new terminal window in Termius or iTerm.
Attach to tmux
Click the + button and select a tmux session from the dropdown. This connects you to an existing tmux session, preserving all your scrollback and running processes.
Common tmux sessions:
claude-- attach to a running Claude Code sessionopenclaw-- attach to the OpenClaw gateway- Any
agent-*session launched from the orchestration board
TIP
You can create new tmux sessions from within any terminal tab:
tmux new -s my-sessionIt will then appear in the dropdown for future attachment.
Multiple Tabs
You can have multiple terminal tabs open simultaneously. Each tab has its own independent PTY process. Click a tab to switch to it, or click the X to close it.
The terminal automatically reconnects if the WebSocket connection drops (e.g., when switching networks on mobile). You will see a "Disconnected. Reconnecting in 3s..." message.
WebSocket Protocol
Connect: /ws/terminal?token=<session>&mode=<shell|tmux-attach>&session=<name>&cols=120&rows=40
Messages from server:
{ type: "ready", sessionId, label }-- PTY spawned successfully{ type: "exit", code }-- process exited{ type: "error", message }-- spawn failed- Raw bytes (ArrayBuffer) -- terminal output
Messages from client:
{ type: "resize", cols, rows }-- resize PTY- Raw text -- terminal input
The client uses binaryType = "arraybuffer" for efficient binary data handling.
Resize
Terminals resize automatically when:
- The browser window changes size (via ResizeObserver)
- You switch tabs (the terminal refits to its container)
- You rotate your phone/tablet
- The iOS virtual keyboard opens or closes
Font size scales dynamically based on viewport dimensions (10px to 16px), calculated as the minimum of width/50 and height/40.
Touch Helpers
On touch devices, a touch bar appears below the terminal with modifier toggles and shortcut keys.
Modifier toggles (Ctrl, Alt) -- tap to activate, then tap a character key to send the modified sequence (e.g., Ctrl then C sends interrupt). Modifiers clear automatically after each use.
Direct keys -- pre-encoded escape sequences for arrow keys, Tab, Esc, and other keys that are hard to type on soft keyboards.
Click-to-Focus
Clicking anywhere inside a terminal tab immediately focuses that tab's input. This applies even when the cursor lands on blank space outside the active content area.
WebGL Rendering
Terminal rendering uses xterm.js with the WebGL addon for GPU-accelerated drawing. If WebGL initialization fails, the terminal falls back to the canvas renderer automatically.
Keyboard Shortcuts
The command center supports keyboard shortcuts for fast tab management:
| Shortcut | Action |
|---|---|
Ctrl+1 through Ctrl+9 | Jump directly to tab 1-9 |
Ctrl+Tab | Cycle to the next tab |
Ctrl+Shift+Tab | Cycle to the previous tab |
These shortcuts work globally when any tab is focused. They are intercepted before the PTY, so they do not get forwarded to the shell.
External Keyboard
On iPad with an external keyboard, the terminal works exactly like a native terminal app. All key combinations (Ctrl, Alt, function keys) are forwarded.
On phones, the on-screen keyboard works but is more limited. The touch helpers provide the most common shortcuts that are hard to type on a soft keyboard.
Fabric mode: PTY relay
In fabric mode the cockpit (Hetzner) is not where your shell runs. Terminals open as live PTY relays to a paired device — the daemon on that device spawns a real PTY wrapping tmux attach (or a fresh shell), and bytes stream both ways through the existing tmux.attach.{start,data,end} envelopes on the /ws/device multiplex. The cockpit /ws/terminal endpoint grew a remote-attach mode that bridges browser WS ↔ device-router envelopes (commit 8da8e3a, src/server/tmux-attach-bridge.ts).
tmux attach across paired devices
Clicking a session in the fabric Sessions tab opens a real terminal tab connected to that session — on whichever paired device the session lives on. The cockpit never holds the PTY itself; it only proxies bytes. tmux.attach.end detaches the client but leaves the tmux server alone, so sessions persist across browser disconnects, tunnel hiccups, and device suspends.
Zombie-tmux-client size fix
Stale tmux clients (closed tabs, dropped Cloudflare tunnels) used to hijack session sizing because tmux's default window-size mode is largest. We switched to latest in src/terminal/pty-host.py and added a 30s server-sent WebSocket ping in src/terminal/handler.ts to surface dead half-open sockets that idleTimeout missed (commit 9954d0d). The result: the active browser tab always wins the sizing contest, and CF tunnels stay warm.
TIP
The Pi itself is now a paired-device node in fabric mode. So attaching to a tmux session "on the Pi" from the fabric cockpit goes through the same PTY relay as any other paired device.
Running CLI Commands
Once in a terminal, you have full access to everything on the host where the PTY runs — the Pi in legacy mode, or the paired device the session belongs to in fabric mode:
# optimal-cli
optimal board view
optimal finance kpis --months 2026-02
# System tools
htop
sudo systemctl status n8n
journalctl -u strapi -f
# Development
cd ~/.openclaw/workspace/optimal-cli
git status
# Claude Code
claude