A fully interactive shell in the browser. Keystrokes travel over a WebSocket to a real PTY on the host, and output streams back into an xterm.js terminal. Drop it into a dev environment when you need an embedded terminal without shelling out of the browser.
It is safe to clone and run: the backend binds to loopback by default, so a fresh checkout is never exposed to your network.
- Real shell sessions (PowerShell on Windows,
bashelsewhere) backed by a PTY - Full terminal emulation — colors, cursor, resizing — via xterm.js
- Bidirectional streaming over a single WebSocket
- Terminal size synced to the PTY so clears and line wraps render correctly
- Loopback-only by default, with an optional shared-token gate for exposed setups
- Zero configuration for local use
| Layer | Technology |
|---|---|
| Frontend | Next.js, React, xterm.js |
| Backend | Node.js, Express, ws, node-pty |
| Tooling | Turborepo, TypeScript, ESLint, Prettier, Jest |
| Package manager | pnpm |
Prerequisites:
- Node.js >= 18 and pnpm
- Native build tools for node-pty,
which compiles on install:
- Windows — the "Desktop development with C++" workload (Visual Studio Build Tools)
- macOS — Xcode Command Line Tools (
xcode-select --install) - Linux —
make, a C/C++ compiler (build-essential), and Python 3
git clone <your-repo-url>
cd recreated-browser-terminal
pnpm install
pnpm devOpen the storefront at http://localhost:3002. The API runs on port 5001.
No environment setup is required for local use — every setting falls back to a safe default. See Configuration to expose the server or point the frontend at a different backend.
Browser (xterm.js) <──WebSocket──> API (ws) <──PTY──> Shell
apps/storefront apps/api powershell / bash
The frontend renders xterm.js and forwards keystrokes as JSON frames. The API validates the WebSocket upgrade, spawns a PTY on connect, pipes shell output back to the client, and kills the shell when the socket closes.
apps/
api/ Express + ws server that spawns the shell (node-pty)
storefront/ Next.js app rendering the xterm.js terminal
packages/
logger/ Shared isomorphic logger
ui/ Shared React components
eslint-config, typescript-config, jest-presets
Local use needs no configuration. To customize, copy the example files:
cp apps/api/.env.example apps/api/.env
cp apps/storefront/.env.example apps/storefront/.env.localAPI (apps/api/.env)
| Variable | Default | Description |
|---|---|---|
PORT |
5001 |
Port the server listens on. |
HOST |
127.0.0.1 |
Bind address. Loopback keeps it off the network. |
TERMINAL_TOKEN |
(unset) | Shared secret required per connection. See Security. |
ALLOWED_ORIGINS |
localhost:3002 origins |
Origins permitted to open a WebSocket. |
Storefront (apps/storefront/.env.local)
| Variable | Default | Description |
|---|---|---|
NEXT_PUBLIC_TERMINAL_WS_URL |
ws://localhost:5001 |
WebSocket URL of the API server. |
NEXT_PUBLIC_TERMINAL_TOKEN |
(unset) | Must match TERMINAL_TOKEN when set. |
This server grants whoever connects a real shell running as the OS user that started it. Treat it as remote code execution and read this before exposing it.
- Loopback by default. Out of the box the server is reachable only from the same machine. Keep it that way unless you have a specific reason not to.
- Origin allowlist. WebSockets aren't bound by the browser's same-origin
policy, so a malicious page could otherwise open a socket to your loopback
server (cross-site WebSocket hijacking). The server rejects handshakes whose
Originisn't inALLOWED_ORIGINS. Add your frontend's origin there when you change ports or hosts. - Exposing beyond localhost (
HOST=0.0.0.0) requires settingTERMINAL_TOKENto a strong secret. Every client must then present that token or the connection is rejected during the WebSocket handshake, before any shell is spawned. Generate one withopenssl rand -hex 32. - The storefront token ships to the browser (
NEXT_PUBLIC_), which is fine for local or trusted use but is not a substitute for per-user authentication in a multi-user deployment. Add real auth before deploying for a team. .envand.env.localare gitignored — never commit real tokens.
pnpm test # run all tests
pnpm lint # lint every workspace
pnpm check-types # type-check every workspaceReleased under the MIT License.