Skip to content

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:

  1. pty.ts in the Hono server manages sessions, spawning a pty-host.py subprocess per terminal
  2. 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 tmux

Opening 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 session
  • openclaw -- 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:

bash
tmux new -s my-session

It 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:

ShortcutAction
Ctrl+1 through Ctrl+9Jump directly to tab 1-9
Ctrl+TabCycle to the next tab
Ctrl+Shift+TabCycle 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:

bash
# 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

Built by Carlos Lenis in Miami