cli reference

share-terminal

The share-terminal command shares a live shell from your laptop to any browser, peer-to-peer over WebRTC. No relay server holds your terminal bytes. Once installed globally, it's available anywhere on your $PATH.

Node ≥ 20macOS, Linuxv0.1.0 · npmjs.com/share-terminal

Install

Install globally with any package manager. The package is unscoped; share-terminal lands on your $PATH.

npm  install -g share-terminal
pnpm add    -g share-terminal
yarn global add share-terminal

Native dependencies. share-terminal bundles in node-pty and node-datachannel. Both ship prebuilds for common Node + OS combos; if a prebuild isn't available, npm will compile from source — you'll need Xcode Command Line Tools on macOS or build-essential + Python on Linux.

Verify:

$ share-terminal --version
0.1.0
$ share-terminal --help
Usage: share-terminal [options] [command]
  start    Start a shared terminal session
  login    Sign in with Google (device flow)
  logout   Forget the stored credentials

Quick start

  1. Sign in

    $ share-terminal login

    Opens your browser to Google's device verification page. The CLI polls the server until you approve, then saves your token to ~/.config/st/token.json (chmod 0600).

  2. Start a session

    $ share-terminal start

    Prints a 6-digit pairing code in a box, with a live countdown. The CLI sits on a signaling WebSocket waiting for a viewer.

  3. Open the web terminal

    On any device, open the web app, sign in with the same Google account, and type the code. Once both sides finish the WebRTC handshake, the shell streams directly into the browser tab.

  4. End the session

    Close the browser tab, or type exit in the shell, or hit Ctrl-D. The CLI exits with the shell's exit code.

Commands

share-terminal start

Spawn $SHELL in a PTY and share it. Prints a pairing code; waits for a viewer to claim it; on success, hands the PTY over to the WebRTC DataChannel.

FlagWhat it does
--echoDon't spawn a PTY. Echo whatever the viewer sends back as a string. Used to validate the wire end-to-end without a shell.

Lifecycle:

  • Pairing code expires in 2 minutes if unclaimed — the CLI prints the countdown and exits with code 1 on expiry.
  • When the viewer disconnects (closes the tab, network drop), the CLI kills the PTY and exits.
  • When the PTY exits (you typed exit), the CLI sends a BYE frame and exits with the shell's exit code.

share-terminal login

Runs Google's OAuth Device Authorization Grant (RFC 8628). The server brokers the request — your CLI never sees the OAuth client secret. The resulting ID token is stored at ~/.config/st/token.json (or $XDG_CONFIG_HOME/st/token.json if set).

FlagWhat it does
--forceRun the flow even if a fresh token is already on disk (useful for switching accounts).

share-terminal logout

Deletes the local token file. Does not revoke it with Google.

Configuration

Where state lives

~/.config/st/token.jsonStored Google identity (access_token, id_token, refresh_token, expires_at, sub, email). File mode 0600, dir 0700.
$XDG_CONFIG_HOMEIf set, overrides the default ~/.config base.

Environment

VariableEffect
SHELLShell to spawn inside the PTY. Falls back to /bin/sh if unset.
XDG_CONFIG_HOMEBase dir for the token file (see above).

The CLI does not read its own .env file. The signaling URL, Google client IDs, ICE servers, and pairing TTL are configured on the server, and returned to the CLI in the /pair response.

The wire

Terminal data and control flow as a single binary stream over one ordered+reliable WebRTC DataChannel. Frames are 1 byte of type followed by an opaque payload.

TypeHexDirectionPayload
OUTPUT0x00host → viewerRaw PTY stdout bytes (UTF-8).
INPUT0x01viewer → hostRaw keystroke bytes (UTF-8).
RESIZE0x02bothuint16 cols, uint16 rows, big-endian. Drives SIGWINCH on the PTY.
PING / PONG0x03 / 0x04bothuint32 nonce. Liveness probe; PONG echoes the same nonce.
HELLO0x05host → viewerFirst frame after channel open. JSON {role, ver, term}.
BYE0x06bothFinal frame. JSON {reason}.

The host applies backpressure using DataChannel bufferedAmount: pauses send when above 1 MiB, resumes below 256 KiB. The viewer batches incoming OUTPUT bytes on a 16ms timer (or at 16 KB) so heavy bursts don't saturate the main thread.

Troubleshooting

“Pairing code expired”

Codes live for 2 minutes. Run share-terminal start again — old codes are dropped from Redis as soon as they expire.

“This code was created by a different Google account”

The viewer must sign in with the same Google account as the host. This is by design — see the security model below.

Connection sits at "connecting…"

Means the WebRTC handshake never completed. Usually one of:

  • Restrictive NAT/firewall on one side. The server returns a STUN/TURN list in /pair; if no TURN is configured and direct connection fails, the session can't form.
  • The host's clock is significantly skewed (token verification fails).

Garbled output / missing box-drawing chars

The CLI requires a UTF-8 locale (LANG=*.UTF-8). If your shell prints Latin-1 bytes, multi-byte UTF-8 won't round-trip cleanly.

“Cannot find module 'node-pty'” after install

npm couldn't download a prebuild for your Node version and failed to compile from source. Install build tooling and reinstall:

# macOS
xcode-select --install
npm install -g share-terminal

# Debian / Ubuntu
sudo apt install build-essential python3
npm install -g share-terminal

Security model

  • Terminal bytes never touch the server. They flow on a WebRTC DataChannel, which is DTLS-secured SCTP.
  • Two layers of credentials. A Google ID token authenticates the HTTP layer (/pair, /pair/claim). A short-lived session JWT (10 min TTL) authenticates the signaling WebSocket.
  • Identity-locked pairing. The viewer's Google sub must match the host's. Mismatches return 403 identity_mismatch.
  • No logs of terminal content. Server keeps only the pairing record in Redis (2 min TTL) — never any frame data.

Full design write-up: security pillars on the landing.