termstage
termstage is a local browser terminal presentation tool. It starts a
loopback-only Rust server, opens a tokenized browser URL, and bridges the browser
terminal to a real local shell or tmux session. The main use case is live demos:
share a Chrome tab with large readable terminal text while keeping the session state
stable across browser refreshes.
Security boundary: this is a local shell bridge, not a sandbox. Browser input is sent to a real shell or tmux session with the current OS user's privileges.
What It Does
Presenter
|
| termstage --session presentation --open
v
+----------------------+ tokenized local URL +--------------------+
| termstage CLI | ------------------------------> | Browser tab |
| - validates options | | - xterm.js UI |
| - starts runtime | <======== WebSocket ==========> | - binary input |
| - starts server | | - resize controls |
+----------+-----------+ +--------------------+
|
| validated runtime commands
v
+----------------------+ PTY bytes +-----------------------------+
| Runtime actor | <===================> | local shell / tmux session |
| - owns PTY | | user's OS privileges |
| - bounded mailboxes | +-----------------------------+
| - reconnect replay |
+----------------------+
The current browser terminal mode provides:
- Loopback-only HTTP/WebSocket server.
- Per-start access token in the launch URL.
- Host, Origin, token, and peer-address checks.
- Binary terminal byte transport.
- JSON control frames for resize and heartbeat.
- tmux-backed session mode for demo state preservation.
- Fresh shell mode for local smoke tests or simple usage.
- Browser reconnect behavior with bounded recent-output replay.
- Slow-client backpressure handling that closes the browser client without killing the terminal session.
- Vite-built, first-party frontend assets embedded into the server binary.
Quick Start
Run a tmux-backed presentation session and open the browser:
Run a fresh shell instead of tmux:
Print the launch URL instead of opening the browser:
Tune readability for screen sharing:
Important Options
| Option | Default | Meaning |
|---|---|---|
--session <name> |
presentation |
Attach to or create this validated tmux session. |
| `--mode <tmux | shell>` | tmux |
--shell <path> |
$SHELL or /bin/sh |
Shell executable for shell mode. |
--host <loopback> |
127.0.0.1 |
Bind address. Non-loopback addresses are rejected. |
--port <port> |
0 |
0 lets the OS choose a free port. |
--open |
false |
Open the tokenized URL in the default browser. |
--font-size <px> |
24 |
Browser terminal font size. |
--theme <name> |
high-contrast |
Presentation theme preset. |
| `--keepalive <session | exit>` | session |
Safety Model
The browser terminal intentionally stays local-only.
Allowed:
127.0.0.1:<random-port> + per-start token + same-origin WebSocket
Rejected:
0.0.0.0, LAN addresses, mismatched Host, mismatched Origin, bad token,
non-loopback peer addresses
Do not expose the server through a LAN bind, tunnel, reverse proxy, or remote desktop helper unless a separate remote-sharing design adds TLS, authentication, authorization, rate limiting, audit logging, and read-only viewer semantics.
Documentation
- User Guide: installation assumptions, CLI usage, demo workflow, troubleshooting, and Chinese translation.
- Developer Guide: workspace layout, architecture, protocol/runtime flow, quality gates, and Chinese translation.
- Documentation Index: all project documentation.
- Specs Index: product, protocol, runtime, web, CLI, security, verification, roadmap, and implementation plan.
Development
Use the Makefile targets instead of ad hoc shell scripts:
make ci is the full local gate. It runs Rust build/test/fmt/clippy/doc/audit/deny
and frontend install/typecheck/build/Playwright tests.
中文
termstage 是给现场演示用的本地浏览器终端。它在本机启动一个只监听
loopback 的 Rust 服务,生成带访问令牌的本地 URL,然后把浏览器里的终端界面接到
真实的本地 shell 或 tmux 会话上。
最常见的用法是:演讲时只共享 Chrome 标签页,让观众看到字号更大、对比度更高的 终端;如果浏览器刷新或短暂断开,演示会话还能接回来,不至于丢掉刚才的状态。
需要先说清楚:这是本地 shell 桥接工具,不是沙箱。你在浏览器里输入的内容,会 以当前操作系统用户的权限进入真实 shell 或 tmux 会话。
它怎么工作
演讲者
|
| termstage --session presentation --open
v
+----------------------+ 带令牌的本地 URL +--------------------+
| termstage CLI | ---------------------------> | 浏览器标签页 |
| - 检查参数 | | - xterm.js 界面 |
| - 启动运行时 | <====== WebSocket =========> | - 输入字节 |
| - 启动本地服务 | | - 窗口尺寸消息 |
+----------+-----------+ +--------------------+
|
| 已校验的运行时命令
v
+----------------------+ PTY 字节流 +-----------------------------+
| Runtime actor | <===================> | 本地 shell / tmux 会话 |
| - 持有 PTY | | 当前系统用户权限 |
| - 有界客户端队列 | +-----------------------------+
| - 重连时回放近期输出 |
+----------------------+
目前已经具备这些能力:
- HTTP 和 WebSocket 服务只监听本机 loopback 地址。
- 每次启动都会生成新的访问令牌,令牌只出现在启动 URL 中。
- 服务端会检查 Host、Origin、token 和 peer 地址。
- 终端数据走二进制 WebSocket 帧,不绕成 JSON。
- resize 和 heartbeat 使用 JSON 控制帧。
- 默认使用 tmux,会话状态适合演示时保留。
- 也可以用 shell 模式,方便快速本地验证。
- 浏览器断开后会自动重连,并回放近期终端输出。
- 如果浏览器跟不上大量输出,会关闭这个慢客户端,但不会杀掉底层终端会话。
- 前端资源由 Vite 构建,并作为一方资源嵌进服务端二进制,不依赖 CDN。
快速开始
启动默认的 tmux 演示会话,并打开浏览器:
如果只想开一个新的 shell:
如果不想自动打开浏览器,只打印 URL:
演示时可以把字体调大一些:
安全边界
浏览器终端只面向本机使用,当前不提供远程共享能力。
允许的形态:
127.0.0.1:<随机端口> + 每次启动的 token + 同源 WebSocket
会被拒绝的形态:
0.0.0.0、局域网地址、Host 不匹配、Origin 不匹配、token 错误、
非 loopback peer 地址
不要把这个服务通过局域网绑定、隧道、反向代理等方式暴露出去。远程共享不是 “改个 host” 就能安全支持的功能,需要单独设计 TLS、认证、授权、限流、审计日志 以及只读观看者语义。
文档
- 用户指南:运行环境、CLI 用法、演示流程、刷新重连、故障排查。
- 开发者指南:工作区结构、架构、协议和运行时流程、质量门禁。
- 文档索引:项目文档入口。
- 规格索引:产品、协议、运行时、Web、CLI、安全、验证、路线图和实现计划。
License
This project is distributed under the terms of MIT.
See LICENSE for details.
Copyright 2025 Tyr Chen