web-terminal-engine

module
v1.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 30, 2026 License: GPL-2.0, GPL-3.0

README

web-terminal-engine

Go Reference npm JSR Go version Go Report Card Test coverage Mutation OpenSSF Best Practices OpenSSF Scorecard

Cross-language terminal emulator and session engine (Go) with browser renderer (TypeScript).

A standalone library that bridges a PTY to a browser WebSocket. The Go packages provide a VT100/VT500 screen buffer with SGR support and a WebSocket-based terminal session handler with reconnect, scrollback replay, and adaptive ping. The TypeScript package provides the browser-side renderer, keyboard mapper, mouse encoder, and binary wire decoder. No app-specific dependencies — only the standard library, github.com/coder/websocket, and github.com/creack/pty.

Install

Go: go get github.com/cplieger/web-terminal-engine@latest — TS: npx jsr add @cplieger/web-terminal-engine or npm i @cplieger/web-terminal-engine

Usage

import (
    "log/slog"
    "net/http"

    "github.com/cplieger/web-terminal-engine/terminal"
)

h := terminal.NewHandler(
    []string{"/bin/bash"},
    terminal.WithWorkDir("/home/user"),
    terminal.WithLogger(slog.Default()),
)
mux := http.NewServeMux()
h.RegisterRoutes(mux)
// or use h as an http.Handler directly:
// mux.Handle("/ws", h)
import { render, keyboard, mouse, decodeWireBinary } from "@cplieger/web-terminal-engine";

render.init({
  output: document.getElementById("term-output")!,
  termWrap: document.getElementById("term")!,
});
// On WebSocket binary message:
const msg = decodeWireBinary(event.data);
if (msg?.type === "screen") render.handleScreen(msg);

API

Go packages
  • vt — VT100/VT500 screen buffer: New(rows, cols), Write([]byte), Resize(rows, cols), RenderRowWire(y), DrainScrollback(), CursorPos(), HoldFlush(), ReleaseFlush(), IsFlushHeld(), RenderViewport(), RowString(y). Public fields: Cells, Width, Height, Title, MouseMode, InAltScreen, cursor/mode state.
  • terminal — WebSocket session handler: NewHandler(command, ...Option), RegisterRoutes(mux), ServeHTTP(w, r), Shutdown(). Options: WithWorkDir, WithLogger, WithEnv, WithScrollbackCapacity, WithAcceptOptions, WithOnProcessExit. Handles PTY lifecycle, binary wire protocol, reconnect with scrollback replay, adaptive ping.
TypeScript (web/ — published as @cplieger/web-terminal-engine on NPM and JSR)
  • render — DOM renderer driven by ScreenMessage / ScrollMessage frames: init, handleScreen, handleScroll, updateFontMetrics, computeSize, getCursorPx, setPredictedCursor, resetScreen, resetScrollback, getScrollbackRowCount, updateReverseVideo.
  • keyboard — Translates KeyboardEvent to terminal byte sequences: mapKeyboardEvent, bracketTextForPaste, prepareTextForTerminal. Honors applicationCursor, applicationKeypad, bracketedPaste.
  • mouse — SGR 1006 mouse + focus reporting encoder: init, encodeSGR, MouseInputHandler.
  • scroll — Auto-follow tracker for the scroll container: init, scrollToBottom, suppressScroll, isUserScrolledUp, isInUserScroll.
  • modes — DEC private mode state (synced from server's ModesMessage): setModes, isBracketedPaste, isApplicationCursor, getMouseMode, isMouseSGR, isFocusReporting, isApplicationKeypad, isReverseVideo.
  • decodeWireBinary(buf) — Top-level decoder for binary WebSocket frames; returns a ServerMessage or null for invalid/truncated frames.
  • connection — Client → server WebSocket lifecycle: socket ownership, exponential-backoff reconnect, and the resume/inputAck reliability layer (outbox + server-restart detection). init(callbacks), connect, sendBinary, sendResize, reconnectNow; wsPath callback option defaults to "/ws". Decodes frames and applies modes.setModes internally, so consumers only dispatch screen/scroll to render. Pairs with the Go terminal handler's resume protocol. (controlFrame / wsURL are also exported for advanced use.)
  • Wire typesWireRun, ScreenMessage, ScrollMessage, ModesMessage, TitleMessage, ResumeAckMessage, ServerMessage, ControlMessage re-exported from the package root.

Wire Protocol

The Go server and TypeScript client communicate over a binary WebSocket frame format rather than shared code. The authoritative byte-level definition is the code itself — the Go encoder (terminal/wire_binary.go), the Go WireRun types (vt/wire.go), and the TS decoder (web/src/wire-binary.ts), all guarded by the round-trip fuzz tests and the wire-golden/*.bin fixtures. The design rationale, which a prose byte-table cannot capture and tends to drift from, is:

  • Binary, not JSON. Frames are WebSocket binary messages with little-endian integers; an earlier JSON encoding produced >100 KB frames on a full repaint (a high-latency regression), and the compact binary format is the fix. Client → server, raw terminal input flows unframed, while control messages (resize, resume) are a 0x00 prefix byte + a JSON body — no valid terminal input starts with NUL, so the prefix is unambiguous.
  • Absolute line indexing. Every line the server produces gets a monotonic absolute index that does not change as the screen scrolls, so the client keeps one buffer keyed by absolute index. Applying a line is idempotent (re-delivery overwrites its slot, never duplicates), and resume aligns by absolute index rather than a fragile count — which also makes an eviction gap detectable (the client compares the server's oldest-retained index against its highest-held index and shows a "history trimmed" marker instead of stitching misaligned lines). A server-epoch value detects restarts across reconnects.
  • Versioning by lockstep. There is no version byte in the frame header: the Go module and the npm/JSR package release together from this one repository, so a breaking wire change must land in the Go encoder/decoder and the TS decoder in a single release (feat!: / BREAKING CHANGE:). A version byte is added only if a break ever cannot be coordinated in one release.

Client → server input for the DEC modes (SGR 1006 mouse, focus reporting, application keypad) is encoded by the TS mouse / keyboard modules and consumed server-side by vt; the sequences live in those sources. For VT/DEC features intentionally absent from the wire, see Unsupported by Design.

License

GPL-3.0 — see LICENSE.

The web-terminal family builds on this engine:

Apps built on the engine:

Unsupported by Design

The following VT/DEC features are intentionally not implemented. Input bytes for these sequences are consumed (not echoed or half-rendered) but produce no effect. This is a deliberate design choice — not a TODO.

Category Sequences Rationale
Selective erase DECSCA, DECSED, DECSEL Requires per-cell "protected" attribute; no modern CLI tool uses this legacy VT feature.
Double-width/height lines DECDWL, DECDHL Requires line-level rendering attribute + renderer changes; purely legacy VT220 feature unused by modern apps.
DCS device control XTGETTCAP, tmux passthrough Terminfo capability queries and tmux control-mode passthrough are not modeled; these DCS strings are consumed silently. (DECRQSS, which shares the same DCS parser, is supported — see the note below.)
Graphics protocols Sixel, ReGIS, Kitty image protocol, iTerm inline images Massive feature (1000+ LOC each); specialized rendering pipeline incompatible with the DOM-based renderer.
NRCS national charsets All national replacement character sets (only DEC Special Graphics + ASCII are supported) Legacy internationalization mechanism superseded by UTF-8. No modern app emits these.
Exotic SGR attributes Fonts 10-20, framed/encircled (51/52/54), superscript/subscript (73-75), ideogram (60-65) No modern terminal or app uses these attributes; they have no visual representation in standard monospace fonts.
ZWJ emoji grapheme clustering Zero-width joiner sequences are not clustered into single cells Requires ICU-level grapheme segmentation (~500+ LOC or a runtime dependency). Individual emoji codepoints render correctly; only multi-codepoint ZWJ sequences (family emoji, skin-tone modifiers) may misalign.

Note on DECRQSS: unlike the other DCS sequences above, DECRQSS (DCS $ q … ST, Request Status String) is supported. The emulator answers SGR (m), DECSTBM scroll region (r), and DECSCUSR cursor style (SP q) queries with a valid DCS 1 $ r … ST reply and returns DCS 0 $ r ST for unrecognized selectors (see vt/dcs.go).

Directories

Path Synopsis
Package terminal bridges a PTY to a browser WebSocket.
Package terminal bridges a PTY to a browser WebSocket.
Package vt handles OSC (Operating System Command) dispatch.
Package vt handles OSC (Operating System Command) dispatch.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL