docs / protocol

Protocol Reference

The complete WebSocket message protocol. All messages are JSON over a single WebSocket connection at /ws.

shared/src/protocol.ts

Connection

Connect to the server at ws://host:port/ws. The client has 10 seconds to send an auth or auth:resume message, or the connection is closed.

connect.ts
// HTTP URLs are converted to WS automatically by the client library
// http://localhost:3456 → ws://localhost:3456/ws
// https://example.com   → wss://example.com/ws

const ws = new WebSocket('ws://localhost:3456/ws');

Constants

shared/src/constants.ts
ConstantValueDescription
DEFAULT_PORT3456Default server port
WS_PATH/wsWebSocket endpoint path
AUTH_TIMEOUT_MS10,000Time to authenticate before disconnect
TOKEN_TTL_MS300,000Token validity (5 minutes)
HEARTBEAT_INTERVAL_MS30,000Ping/pong keepalive interval
RECONNECT_BASE_MS1,000Initial reconnect delay
RECONNECT_MAX_MS30,000Maximum reconnect delay

Client → Server

auth

Authenticate with a one-time token.

interface AuthMessage {
  type: 'auth';
  token: string;  // e.g. "a1b2-c3d4-e5f6-7890"
}

auth:resume

Resume an existing session. The server replays the terminal list and scrollback.

interface AuthResumeMessage {
  type: 'auth:resume';
  sessionId: string;  // UUID from a previous auth:ok
}

terminal:create

Spawn a new pseudo-terminal.

interface TerminalCreateMessage {
  type: 'terminal:create';
  cols: number;      // Terminal width in columns
  rows: number;      // Terminal height in rows
  shell?: string;    // Optional: e.g. "/bin/zsh", "powershell.exe"
}
If shell is omitted, the server uses $SHELL on Unix or powershell.exe on Windows.

terminal:input

Send data (keystrokes, pasted text) to a terminal.

interface TerminalInputMessage {
  type: 'terminal:input';
  terminalId: string;
  data: string;  // Raw terminal data (including escape sequences)
}

terminal:resize

Resize a terminal. The PTY is resized to match.

interface TerminalResizeMessage {
  type: 'terminal:resize';
  terminalId: string;
  cols: number;
  rows: number;
}

terminal:kill

Kill a terminal process.

interface TerminalKillMessage {
  type: 'terminal:kill';
  terminalId: string;
}

terminal:list (request)

Request the list of active terminals for the current session.

interface TerminalListMessage {
  type: 'terminal:list';
}

Server → Client

auth:ok

Authentication succeeded. Save the sessionId for resume.

interface AuthOkMessage {
  type: 'auth:ok';
  sessionId: string;  // UUID — use this for auth:resume
}

auth:fail

Authentication failed. The connection is closed after this message.

interface AuthFailMessage {
  type: 'auth:fail';
  reason: string;
  // "invalid_token" — token doesn't exist, expired, or already used
  // "invalid_session" — session ID not found
  // "auth_timeout" — 10s timeout exceeded
}

terminal:created

A new terminal was successfully spawned.

interface TerminalCreatedMessage {
  type: 'terminal:created';
  terminal: TerminalInfo;
}

interface TerminalInfo {
  id: string;         // UUID
  pid: number;        // OS process ID
  shell: string;      // e.g. "/bin/zsh"
  cols: number;
  rows: number;
  cwd: string;        // Working directory
  createdAt: number;  // Unix timestamp (ms)
}

terminal:output

Output data from a terminal. This is the primary data channel.

interface TerminalOutputMessage {
  type: 'terminal:output';
  terminalId: string;
  data: string;  // Raw terminal output (includes ANSI codes)
}

terminal:exited

A terminal process has exited.

interface TerminalExitedMessage {
  type: 'terminal:exited';
  terminalId: string;
  exitCode: number;
}

terminal:list (response)

List of active terminals. Sent in response to a terminal:list request, and also automatically on session resume.

interface TerminalListResponseMessage {
  type: 'terminal:list';
  terminals: TerminalInfo[];
}

error

A general error message.

interface ErrorMessage {
  type: 'error';
  message: string;
  code?: string;  // e.g. "spawn_failed"
}

Message Flow

Typical connection lifecycle:

message-flow.txt
Client                          Server
  │                               │
  │──── WebSocket connect ───────▶│
  │                               │
  │──── auth { token } ──────────▶│
  │◀──── auth:ok { sessionId } ───│
  │                               │
  │──── terminal:create ─────────▶│
  │◀──── terminal:created ────────│
  │                               │
  │──── terminal:input ──────────▶│  (user types)
  │◀──── terminal:output ─────────│  (pty responds)
  │◀──── terminal:output ─────────│
  │                               │
  │──── terminal:resize ─────────▶│  (window resized)
  │                               │
  │──── terminal:kill ───────────▶│
  │◀──── terminal:exited ─────────│
  │                               │

Session Resume Flow

resume-flow.txt
Client                          Server
  │                               │
  │──── WebSocket connect ───────▶│
  │                               │
  │──── auth:resume { sid } ─────▶│
  │◀──── auth:ok { sessionId } ───│
  │◀──── terminal:list ───────────│  (active terminals)
  │◀──── terminal:output ─────────│  (scrollback replay)
  │◀──── terminal:output ─────────│  (scrollback replay)
  │                               │
  │  (terminals restored)         │