A web-based terminal emulator that integrates Ghostty's VT100 parser via WebAssembly.
npm install @coder/ghostty-webOr install from GitHub:
npm install github:coder/ghostty-webimport { Terminal } from '@coder/ghostty-web';
const term = new Terminal({ cols: 80, rows: 24 });
await term.open(document.getElementById('terminal'));
term.write('Hello, World!\r\n');See INSTALL.md for complete usage guide.
- ✅ Full xterm.js-compatible API
- ✅ Production-tested VT100 parser (via Ghostty)
- ✅ ANSI colors (16, 256, RGB true color)
- ✅ Canvas rendering at 60 FPS
- ✅ Scrollback buffer
- ✅ Text selection & clipboard
- ✅ FitAddon for responsive sizing
- ✅ TypeScript declarations included
Requires server
# Terminal 1: Start PTY shell server
cd demo/server
bun install
bun run start
# Terminal 2: Start web server (from project root)
bun run dev
# Open: http://localhost:8000/demo/This provides a real persistent shell session! You can:
- Use
cdand it persists between commands - Run interactive programs like
vim,nano,top,htop - Use tab completion and command history (↑/↓)
- Use pipes, redirects, and background jobs
- Access all your shell aliases and environment
Alternative: Command-by-Command Mode
For the original file browser (executes each command separately):
cd demo/server
bun run file-browserRemote Access: If you're accessing via a forwarded hostname (e.g., mux.coder), make sure to forward both ports:
- Port 8000 (web server - Vite)
- Port 3001 (WebSocket server)
The terminal will automatically connect to the WebSocket using the same hostname you're accessing the page from.
Colors Demo (no server needed)
bun run dev
# Open: http://localhost:8000/demo/colors-demo.htmlSee all ANSI colors (16, 256, RGB) and text styles in action.
import { Terminal } from './lib/index.ts';
import { FitAddon } from './lib/addons/fit.ts';
// Create terminal
const term = new Terminal({
cols: 80,
rows: 24,
cursorBlink: true,
theme: {
background: '#1e1e1e',
foreground: '#d4d4d4',
},
});
// Add FitAddon for responsive sizing
const fitAddon = new FitAddon();
term.loadAddon(fitAddon);
// Open in container
await term.open(document.getElementById('terminal'));
fitAddon.fit();
// Write output (supports ANSI colors)
term.write('Hello, World!\r\n');
term.write('\x1b[1;32mGreen bold text\x1b[0m\r\n');
// Handle user input
term.onData((data) => {
console.log('User typed:', data);
// Send to backend, echo, etc.
});const ws = new WebSocket('ws://localhost:3001/ws');
// Send user input to backend
term.onData((data) => {
ws.send(JSON.stringify({ type: 'input', data }));
});
// Display backend output
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
term.write(msg.data);
};See AGENTS.md for development guide and code patterns.
DON'T re-implement VT100 parsing from scratch (years of work, thousands of edge cases).
DO use Ghostty's proven parser:
- ✅ Battle-tested by thousands of users
- ✅ Handles all VT100/ANSI quirks correctly
- ✅ Modern features (RGB colors, Kitty keyboard protocol)
- ✅ Get bug fixes and updates for free
You build: Screen buffer, rendering, UI (the "easy" parts in TypeScript)
Ghostty handles: VT100 parsing (the hard part via WASM)
┌─────────────────────────────────────────┐
│ Terminal (lib/terminal.ts) │
│ - Public xterm.js-compatible API │
│ - Event handling (onData, onResize) │
└───────────┬─────────────────────────────┘
│
├─► ScreenBuffer (lib/buffer.ts)
│ - 2D grid, cursor, scrollback
│
├─► VTParser (lib/vt-parser.ts)
│ - ANSI escape sequence parsing
│ └─► Ghostty WASM (SGR parser)
│
├─► CanvasRenderer (lib/renderer.ts)
│ - Canvas-based rendering
│ - 60 FPS, supports all colors
│
└─► InputHandler (lib/input-handler.ts)
- Keyboard events → escape codes
└─► Ghostty WASM (Key encoder)
WebSocket Server (server/file-browser-server.ts)
└─► Executes shell commands (ls, cd, cat, etc.)
├── lib/
│ ├── terminal.ts - Main Terminal class (xterm.js-compatible)
│ ├── buffer.ts - Screen buffer with scrollback
│ ├── vt-parser.ts - VT100/ANSI escape sequence parser
│ ├── renderer.ts - Canvas-based renderer
│ ├── input-handler.ts - Keyboard input handling
│ ├── ghostty.ts - Ghostty WASM wrapper
│ ├── types.ts - TypeScript type definitions
│ ├── interfaces.ts - xterm.js-compatible interfaces
│ └── addons/
│ └── fit.ts - FitAddon for responsive sizing
│
├── demo/
│ ├── index.html - File browser terminal
│ ├── colors-demo.html - ANSI colors showcase
│ └── server/
│ ├── file-browser-server.ts - WebSocket server
│ ├── package.json
│ └── start.sh - Startup script (auto-kills port conflicts)
│
├── docs/
│ └── API.md - Complete API documentation
│
└── ghostty-vt.wasm - Ghostty VT100 parser (122 KB)
The WASM binary is built from source, not committed to the repo.
Requirements:
- Zig 0.15.2+
- Git submodules initialized
Build:
# Initialize submodule (first time only)
git submodule update --init --recursive
# Build WASM
./scripts/build-wasm.sh
# or
bun run build:wasmWhat it does:
- Initializes
ghostty/submodule (ghostty-org/ghostty) - Applies patches from
patches/ghostty-wasm-api.patch - Builds WASM with Zig (takes ~20 seconds)
- Outputs
ghostty-vt.wasm(404 KB) - Reverts patch to keep submodule clean
Updating Ghostty:
cd ghostty
git fetch origin
git checkout <commit-or-tag>
cd ..
./scripts/build-wasm.sh
# Test, then commit the updated submodule pointerCI: The WASM is built as part of the test and build jobs.
Run the test suite:
bun test # Run all tests
bun test --watch # Watch mode
bun run typecheck # Type checking
bun run build # Build distributionTest Coverage:
- ✅ ScreenBuffer (63 tests, 163 assertions)
- ✅ VTParser (45 tests)
- ✅ CanvasRenderer (11 tests)
- ✅ InputHandler (35 tests)
- ✅ Terminal integration (25 tests)
- ✅ FitAddon (12 tests)
- AGENTS.md - Development guide for AI agents and developers
See cmux LICENSE (AGPL-3.0)