hit-stream
v1.0.0
Published
Live-stream terminal sessions over WebSocket and UDP multicast — watch AI coding agents, multi-agent dashboard, recording, API usage tracking
Maintainers
Readme
hit-stream
Live-stream terminal sessions over local WebSocket. Watch AI coding agents (Claude, Codex, Aider, Goose, Cline, Roo) and any terminal program from another terminal or browser — with interactive input, multi-agent dashboard, recording, and API usage tracking.

Why hit-stream?
- See what your AI agent is doing without interrupting it — AI agents can run for minutes generating code, making decisions, and hitting APIs. Instead of staring at the terminal or risking an accidental keypress, watch from a separate terminal or browser.
- Control multiple agents from a single dashboard — run Claude, Codex, and Aider in parallel on different tasks. When one hits a rate limit, hand off context to another without losing momentum.
- Record sessions for review, auditing, or sharing — keep a record of everything an AI agent did to your codebase. Replay it later, share with your team, or upload to asciinema.
- Track API usage across Anthropic, OpenAI, and Google in real-time — know when you're approaching rate limits before your agent gets cut off mid-task.
- Broadcast on the LAN via UDP multicast — let your whole team watch a long-running agent session without SSH tunnels, port forwarding, or any setup beyond being on the same network.
- Zero config — just prefix your command with
hit-stream - Two dependencies (
ws,node-pty) — lightweight runtime
Quick Start
npm install -g hit-streamOr from source:
git clone https://github.com/vibehit/hit-stream.git
cd hit-stream
npm install
export PATH="$PWD/bin:$PATH"Start broadcasting and watching in seconds:
# Terminal 1: start an agent and broadcast
hit-stream claude
# Terminal 2: watch live
hit-stream watch
# Or open in browser
hit-stream watch --webArchitecture
┌─────────────────────────────┐
│ Service Discovery │
│ /tmp/hit-stream/<uid>/ │
│ sessions/<pid>.json │
└──────────┬──────────────────┘
│ read/write
┌────────────────────────┼────────────────────────┐
│ │ │
┌────────▼─────────┐ ┌────────▼─────────┐ ┌────────▼─────────┐
│ Broadcaster A │ │ Broadcaster B │ │ Broadcaster C │
│ (hit-stream bash)│ │(hit-stream claude)│ │(hit-stream codex)│
│ │ │ │ │ │
│ node-pty ────┐ │ │ node-pty ────┐ │ │ node-pty ────┐ │
│ or strace │ │ │ or strace │ │ │ or strace │ │
│ ┌────────▼─┐ │ │ ┌────────▼─┐ │ │ ┌────────▼─┐ │
│ │ WS Server│ │ │ │ WS Server│ │ │ │ WS Server│ │
│ │ :auto │ │ │ │ :auto │ │ │ │ :auto │ │
│ └────┬─────┘ │ │ └────┬─────┘ │ │ └────┬─────┘ │
└───────────┼───────┘ └───────────┼───────┘ └───────────┼───────┘
│ │ │
└────────────┬───────────┘────────────────────────┘
│ WebSocket
┌───────────────▼───────────────┐
│ Dashboard Hub │
│ (hit-stream dashboard) │
│ │
│ Upstream WS ──► Multiplexer │
│ Replay buffers (256KB each) │
│ Session polling (3s) │
│ │
│ ┌──────────────┐ │
│ │ WS Server │ │
│ │ + HTTP │ │
│ └──────┬───────┘ │
└─────────────┼─────────────────┘
│
┌──────────┴──────────┐
│ │
┌────────▼────────┐ ┌───────▼────────┐
│ Browser Client │ │ Console TUI │
│ (multi-pane │ │ (hub/focus │
│ xterm.js) │ │ switching) │
└──────────────────┘ └────────────────┘Each broadcaster runs independently. The dashboard hub is a pure consumer — it connects to broadcasters as a WebSocket client and multiplexes their output. No changes needed to broadcasters.
Single watchers (hit-stream watch) connect directly to a broadcaster. The dashboard connects to all of them.
Commands
| Command | Description |
|---|---|
| hit-stream <cmd> [args...] | Run command in a PTY and broadcast (recommended) |
| hit-stream attach [--pid N] | Attach to a running process via strace (needs sudo) |
| hit-stream watch | View a live broadcast (terminal, web, or interactive) |
| hit-stream dashboard | Multi-terminal dashboard — all sessions at once |
| hit-stream list | List active broadcasts |
| hit-stream stop [pid] | Stop broadcasts |
| hit-stream pipe | Broadcast stdin |
| hit-stream daemon | Auto-attach to new processes matching agent whitelist — run once and forget; any agent you start afterwards is automatically broadcast |
| hit-stream agents | Manage agent whitelist (built-in + custom) |
Every command supports --help for detailed options.
Broadcasting
Wrap mode (recommended)
Runs the command inside a PTY. No sudo, no strace, no limitations. Use this when you're starting a new session — it captures everything from the start and supports interactive input forwarding.
If a broadcast for the same command already exists, hit-stream automatically connects to it as an interactive watcher instead of creating a duplicate — so you can always just type hit-stream claude and it does the right thing.
hit-stream claude # start claude and broadcast
hit-stream claude --resume abc123 # pass flags through to claude
hit-stream bash # broadcast a bash session
hit-stream --record session.cast claude # record + broadcast
hit-stream --web claude # broadcast + open browser viewer
hit-stream --web --port 8080 claude # broadcast + browser on fixed portAttach mode (strace)
Attach to an already-running process. Requires sudo for strace. Use this when you forgot to start with hit-stream or want to peek at an agent that's already mid-task — you don't need to restart it.
sudo hit-stream attach # auto-find any known agent
sudo hit-stream attach --pid 1234 # specific PID
sudo hit-stream attach --name claude # search by process namePipe mode
Broadcast any stdin stream. Useful for sharing build logs, CI output, or any command output with watchers in real-time without needing a PTY.
tail -f /var/log/syslog | hit-stream pipe --label syslog
some_command 2>&1 | hit-stream pipeCommon options (wrap & attach)
| Option | Description |
|---|---|
| --record <file> | Record session in asciicast v2 format. Useful for auditing what an AI agent did, sharing sessions with teammates, or reviewing decisions later. |
| --replay-size <bytes> | Replay buffer size (default: 1048576 = 1MB). When a watcher connects late, it receives the last N bytes of output so it has context instead of seeing a blank screen. Increase this if your agent produces large outputs. |
Watching
Terminal mode
hit-stream watch # auto-discover broadcast
hit-stream watch --raw # raw bytes to stdout (pipe-friendly)
hit-stream watch -i # interactive: forward keystrokes (Ctrl-] to detach)
hit-stream watch --grep "error|warning" # regex filter on output
hit-stream watch --notify "error|fail" # desktop notification on regex match
hit-stream watch --all # watch all broadcasts with colored PID prefixes
hit-stream watch --url ws://host:port # connect to specific URL
hit-stream watch claude # watch claude (start if needed)
hit-stream watch claude aider # discover multiple agents (--all mode)--discover <cmd> [args...](or positional args) — ensure a broadcast for<cmd>is running: if one already exists, connect to it; if not, start it automatically and then connect. Arguments after<cmd>are passed through:--discover claude --resumediscovers/startsclaude --resume. Use multiple--discoverflags for several agents:--discover claude --resume --discover aider --model gpt4. Positional shorthand:hit-stream watch claudeis equivalent to--discover claude;hit-stream watch claude aiderdiscovers both.--raw— pipe agent output into other tools:hit-stream watch --raw | tee log.txtorhit-stream watch --raw | grep -i error-i(interactive) — type into the agent from another terminal. Useful for sending input to a stuck agent or answering a prompt without switching windows.--grep— only see output matching a pattern. Useful for monitoring an agent for errors without watching all the noise: leave it running, check back when there's output.--notify— get a desktop notification when the agent outputs something matching a pattern. Walk away from the keyboard and get alerted when the agent hits an error or finishes a task.--all— watch all running agents at once with colored[PID]prefixes. A quick way to monitor multiple agents without the full dashboard.
Keys (non-interactive terminal mode):
| Key | Action |
|---|---|
| p / Space | Pause/resume — output buffers up to 1MB while paused and replays on resume, so you don't miss anything. Useful for reading a section of output without it scrolling away. |
| q | Quit |
Web viewer
Opens a browser tab with a full terminal emulator. Useful when your terminal can't render the agent's output properly (colors, Unicode), when you want clickable links in the output, or when you want to share the view with someone who doesn't use the CLI.
hit-stream watch --web # open in browser (auto port)
hit-stream watch --web --port 8080 # fixed portFeatures:
- Full ANSI/color rendering via xterm.js
- Auto-fit to browser window
- Interactive input (local connections only — remote viewers are read-only)
- Clickable link detection
- Dark theme matching terminal aesthetics
UDP Multicast
Broadcast terminal output over UDP multicast on the LAN — like radio: anyone listening on the multicast group sees it, no connection needed. Multicast works in addition to WebSocket (both run simultaneously). Multicast receivers are read-only (no input forwarding).
When to use multicast instead of WebSocket: when your team is on the same LAN and you want zero-setup sharing. No SSH tunnels, no exchanging URLs, no port forwarding. A colleague just runs hit-stream watch --multicast and sees your session. Ideal for pair programming, team monitoring of long-running agents, or office environments where everyone should be able to check on a shared agent.
Broadcasting with multicast
hit-stream bash --multicast # LAN (239.0.0.1:4840, admin-scoped)
hit-stream bash --multicast-global # internet-ready (225.0.0.1:4840)
hit-stream claude --multicast 224.1.2.3:5000 # custom group:port
hit-stream bash --multicast --multicast-ttl 2 # TTL > 1 for routed multicastReceiving multicast
hit-stream watch --multicast # terminal mode (LAN group)
hit-stream watch --multicast-global # receive on global group
hit-stream watch --multicast --web # browser via local relay
hit-stream watch --multicast --all # all sources with colored PID prefixes
hit-stream watch --multicast --grep "error" # filter multicast output
hit-stream watch --multicast 224.1.2.3:5000 # custom group:port- Zero config on LAN — no SSH tunnels, no port forwarding, no discovery needed
- TTL 1 by default — packets stay on the local network segment
- Fragmentation — large events are automatically split and reassembled (max 1400 bytes per UDP packet)
- Same-machine loopback — works when sender and receiver are on the same host
| Option | Description |
|---|---|
| --multicast [group:port] | Enable multicast with admin-scoped group (default: 239.0.0.1:4840, LAN) |
| --multicast-global | Use globally routable group (225.0.0.1:4840, internet-ready) |
| --multicast [group:port] | Use a custom group:port |
| --multicast-ttl <n> | Multicast TTL (default: 1, LAN only) |
Two multicast groups
hit-stream ships with two predefined multicast groups, each designed for a different scope:
| Group | Range | Flag | Scope | Routers forward? |
|---|---|---|---|---|
| 239.0.0.1 | 239.0.0.0/8 (admin-scoped) | --multicast | LAN / organization | No — filtered at org boundary |
| 225.0.0.1 | 225.0.0.0/8 (globally routable) | --multicast-global | Internet | Yes — when TTL allows |
Admin-scoped (239.0.0.1) — the safe default. Routers are required by RFC 2365 to not forward these packets beyond organizational boundaries, regardless of TTL. Use this when you want multicast to stay within your LAN or enterprise network.
Globally routable (225.0.0.1) — for internet multicast. Routers can forward these packets across networks when TTL allows and multicast routing is enabled. Use this when you want anyone on the internet (or a multicast-enabled WAN) to receive your broadcast.
You can also pass any custom group address: --multicast 224.1.2.3:5000.
TTL controls the reach
The multicast group determines whether routers can forward packets. The TTL determines how far they travel:
| TTL | Reach | Use case | |---|---|---| | 1 | Same LAN segment (default) | Office, home network | | 15 | Same site / campus | Multi-building campus | | 32 | Same organization | Corporate WAN | | 64 | Same region | Regional ISP | | 128 | Internet-wide | Global broadcast |
# LAN only — admin-scoped, safe (default)
hit-stream claude --multicast
# LAN with globally routable group (TTL still 1, stays on LAN)
hit-stream claude --multicast-global
# Organization WAN — global group + higher TTL
hit-stream claude --multicast-global --multicast-ttl 32
# Internet-wide — global group + internet TTL (requires multicast-enabled ISPs)
hit-stream claude --multicast-global --multicast-ttl 128
# Custom group for team isolation
hit-stream claude --multicast 225.1.2.3:4840 --multicast-ttl 32Internet multicast: current state
Most ISPs do not enable multicast routing today. But the infrastructure standards exist (PIM-SM, IGMP/MLDv2, MBGP), IPv6 has native multicast support, and some research/enterprise networks already route multicast globally. By supporting a globally routable group, hit-stream is ready for internet multicast without code changes — just --multicast-global --multicast-ttl 128.
Security note: with --multicast-global and TTL > 1, packets leave your LAN. Anyone who joins the group can receive terminal output. Do not use high TTL values if terminal output contains sensitive data (API keys, credentials, proprietary code).
Authentication & TLS
All security features are opt-in. Without any auth/TLS flags, hit-stream behaves exactly as before — no authentication, no encryption, no TLS.
Shared-secret authentication (--token)
Require a token to connect. The broadcaster checks the token on every new WebSocket connection; watchers that don't provide the correct token are rejected.
--token is a flag (no argument). The token value is resolved securely:
HIT_TOKENenv var — not visible inps, recommended for scripts and automation- Interactive hidden prompt — if env var is not set, prompts from
/dev/tty(likepasswd)
The token is never passed as a CLI argument, so it never appears in ps aux or shell history.
# Broadcast with auth (env var — recommended)
export HIT_TOKEN=secret123
hit-stream bash --token
# Broadcast with auth (interactive prompt — no env var needed)
hit-stream bash --token
# Token: ********
# Watch — auto-detects auth from session file and prompts
hit-stream watch
# Token: ********When --token is set with multicast, the UDP payload is automatically encrypted with AES-256-GCM (key derived from the token via SHA-256). Receivers without the correct token silently ignore encrypted packets.
# Encrypted multicast
HIT_TOKEN=secret123 hit-stream bash --multicast --token
HIT_TOKEN=secret123 hit-stream watch --multicast --tokenTLS encryption (--tls)
Enable HTTPS + WSS with a self-signed certificate (auto-generated via openssl) or user-provided cert/key files.
# Auto-generated self-signed cert
hit-stream bash --tls
# User-provided cert/key
hit-stream bash --tls --cert server.pem --key server-key.pem
# Watch auto-detects TLS from session file
hit-stream watchCombined auth + TLS
# Full encryption: TLS on the wire + auth token
HIT_TOKEN=secret123 hit-stream bash --tls --token
HIT_TOKEN=secret123 hit-stream watch --tls --token
# Dashboard with auth + TLS
HIT_TOKEN=secret123 hit-stream dashboard --tls --tokenProtocol versioning
All WebSocket config messages and session files include a protocol field (currently 1). This enables future protocol changes to be detected by clients and servers without breaking backwards compatibility.
Session indicators
hit-stream list shows [auth] and [tls] badges for secured sessions. Share mode includes --token and --tls hints in the watch commands. Per-watcher token sessions show [per-token].
Per-watcher tokens (--per-token)
Generate individual random tokens for each watcher. Revoke any one without affecting others. Idle tokens auto-expire after 30 minutes.
# Start broadcaster with per-watcher tokens
HIT_TOKEN=master hit-stream bash --token --per-token
# Admin: generate a token (master token required)
curl -X POST -H "Authorization: Bearer master" http://127.0.0.1:PORT/api/tokens
# → {"token":"<random-base64url>"}
# Admin: list all tokens
curl -H "Authorization: Bearer master" http://127.0.0.1:PORT/api/tokens
# Admin: revoke a token (closes its WS with code 4008)
curl -X DELETE -H "Authorization: Bearer master" http://127.0.0.1:PORT/api/tokens/<token>
# Watcher: connect with per-watcher token
HIT_TOKEN=<per-watcher-token> hit-stream watch --token--per-tokenrequires--token(master token protects the admin API)- Master token is always accepted for WS auth (admin can still watch)
- Max 50 active tokens per broadcaster
- Idle timeout: 30 minutes (auto-sweep every 30s, WS close code
4008) - Tokens are in-memory only — they die with the broadcaster
- Multicast encryption unchanged (uses master token)
Dashboard
The dashboard shows all active sessions in a split-pane view. Sessions are auto-discovered and panes are added/removed as broadcasters start and stop. Use the dashboard when you're running multiple agents simultaneously — for example, Claude on the backend, Aider on the frontend, and Codex on tests. You see all three at once, interact with whichever needs attention, and pass context between them when one hits a rate limit.
hit-stream dashboard # console TUI mode
hit-stream dashboard --web # browser-based dashboard
hit-stream dashboard --web --port 8080 # fixed port
hit-stream dashboard --poll 5000 # poll every 5s instead of 3s
hit-stream dashboard --discover claude --resume --discover aider # with args
hit-stream dashboard claude aider # positional shorthand (simple names)Web dashboard
┌──────────────────────────────────────────────────────────┐
│ hit-stream dashboard 3 sessions │
├────────────────────────────┬─────────────────────────────┤
│ [1] claude --resume │ [2] aider │
│ ● connected interactive │ ● connected interactive │
│ │ │
│ Claude is analyzing the │ Aider> What would you like │
│ codebase and preparing │ to change? │
│ an implementation plan... │ > _ │
│ │ │
├────────────────────────────┴─────────────────────────────┤
│ [3] codex │
│ ● connected interactive │
│ │
│ codex> Working on task: implement auth middleware... │
│ │
└──────────────────────────────────────────────────────────┘- Auto-grid layout — 1=full, 2=side-by-side, 3-4=2x2, 5+=3xN
- Click to focus — red border indicates active pane, only focused pane receives keyboard input
- Per-pane xterm.js — same theme as web viewer, header with index, name, status badges, API usage
- Maximize/restore — expand any pane to full screen
- Context passing — select text in one terminal, send it as input to another (useful for rate limit handoff)
- Auto-discovery — panes appear/disappear as sessions start/stop
Keyboard shortcuts:
| Shortcut | Action |
|---|---|
| Ctrl+1..9 | Focus pane by index |
| Ctrl+Shift+F | Maximize / restore focused pane |
| Ctrl+Shift+S | Open "send context" modal (select text first) |
| Esc | Close modal or restore maximized pane |
Context passing
The killer feature for multi-agent workflows. When an agent hits a rate limit or you want another agent to continue its work:
- Select the relevant text (task description, error, context) in the source pane
- Press
Ctrl+Shift+S— a modal shows the selected text - Click the target agent — the text is injected as input into that terminal
Console TUI
The console dashboard runs entirely in the terminal, no browser needed.
- Hub view — numbered list of sessions with status. Press
1-9to focus a session,qto quit. - Focus view — full pass-through to selected session.
Ctrl-]returns to hub. - Yank/paste —
Ctrl-Yyanks last output line,Ctrl-Ppastes it as input to the focused session.
Recording & Playback
Record everything an AI agent does to your codebase. Useful for code review ("what did Claude actually change and why?"), auditing ("prove the agent didn't touch production configs"), onboarding ("watch how an experienced dev uses Claude to refactor"), or simply keeping a log for later reference.
Records in asciicast v2 format, compatible with asciinema:
# Record while broadcasting
hit-stream --record session.cast claude
sudo hit-stream attach --record session.cast
# Play back
asciinema play session.cast
# Upload to asciinema.org
asciinema upload session.castAPI Usage Tracking
Know when you're about to hit a rate limit before your agent gets cut off mid-task. Especially useful when running multiple agents that share the same API quota — the dashboard shows per-agent usage so you can pause one before it exhausts the limit for all of them.
Periodically displays remaining rate limits (tokens, requests) in both terminal and web viewer. Detects available API keys automatically:
| Variable | Provider |
|---|---|
| ANTHROPIC_API_KEY | Anthropic (Claude) |
| OPENAI_API_KEY | OpenAI (GPT, Codex) |
| GOOGLE_API_KEY / GEMINI_API_KEY | Google (Gemini) |
No tokens are consumed — uses lightweight endpoints (count_tokens, models listing) to read rate limit headers.
Example output:
[usage] 5m | 2048KB | anthropic: 90% | openai: 75%Remote Sharing
Share a broadcast with someone on another machine via SSH tunnel. Useful for remote pair programming, letting a teammate watch your agent session from home, or showing a demo to someone on a different network. The viewer is read-only by default — they can watch but not type into your agent.
# Show tunnel commands for all active sessions
hit-stream list --share
# Example output:
# PID 1234 (claude) on ws://127.0.0.1:54321
# Remote: ssh -L 54321:127.0.0.1:54321 user@hostOn the remote machine:
# Watch a single session directly
hit-stream watch --url ws://127.0.0.1:54321
# Discover remote sessions via HTTP metadata endpoint
hit-stream list --remote 127.0.0.1:54321
# Include remote sessions in the dashboard
hit-stream dashboard --remote 127.0.0.1:54321
# Multiple remote hosts
hit-stream list --remote 127.0.0.1:54321,127.0.0.1:54322
hit-stream dashboard --web --remote 127.0.0.1:54321,127.0.0.1:54322Each broadcaster exposes a GET /api/session HTTP endpoint on the same port as the WebSocket server. Remote discovery uses this endpoint to fetch session metadata without maintaining a persistent connection.
Security: remote connections are read-only by default. Only 127.0.0.1 connections can send input.
Agent Whitelist
Built-in agents: claude, aider, codex, goose, cline, roo.
Used by hit-stream attach (auto-find) and hit-stream daemon (auto-attach to new processes). The whitelist tells hit-stream which processes are AI agents so it can find them automatically — you don't have to know the PID or process name.
hit-stream agents list # show all agents (built-in + custom)
hit-stream agents init # create ~/.config/hit-stream/agents.jsonCustom agents config (~/.config/hit-stream/agents.json):
[
{ "name": "cursor", "pattern": "cursor" },
{ "name": "mybot", "pattern": "my-bot", "exclude": ["/tmp/mybot-"] }
]Custom entries with the same name override built-in ones.
How It Works
1. CAPTURE 2. BROADCAST 3. CONSUME
┌──────────┐ ┌──────────────┐ ┌──────────────┐
│ node-pty │ events │ WebSocket │ WS │ Terminal │
│ spawn ├──────────►│ Server ├───────►│ (watch) │
│ │ │ 127.0.0.1 │ └──────────────┘
│ or │ parsed │ │ ┌──────────────┐
│ │ output │ + Replay │ WS │ Browser │
│ strace ├──────────►│ Buffer ├───────►│ (xterm.js) │
│ attach │ │ + Usage │ └──────────────┘
└──────────┘ │ Tracker │ ┌──────────────┐
│ │ WS │ Dashboard │
│ + Session ├───────►│ (hub) │
│ Discovery │ └──────────────┘
└──────────────┘- Capture — PTY wrapper (via
node-pty) or strace (write/writevsyscalls) - Broadcast — JSON events over WebSocket on
127.0.0.1(auto-selected port), optionally also via UDP multicast on LAN - Replay buffer — ring buffer (default 1MB) so late-joining clients see recent output
- Input forwarding — PTY write (wrap mode) or TIOCSTI ioctl (attach mode)
- Service discovery — session files in
/tmp/hit-stream/<uid>/sessions/*.json - Usage tracking — polls API rate limit headers every 5 minutes
Event format
All communication uses JSON over WebSocket:
// Output event (broadcaster → client)
{"ts": "2026-02-07T12:34:56.789Z", "stream": "stdout", "encoding": "utf-8", "data": "..."}
// Input event (client → broadcaster)
{"type": "input", "data": "keystroke"}
// Config (on connect)
{"type": "config", "inputAllowed": true}
// Usage update
{"type": "usage", "uptime_s": 300, "total_bytes": 102400, "providers": [...]}Security
Design principles
- Local-only by default — all WebSocket servers bind to
127.0.0.1 - Opt-in authentication —
--tokenenables shared-secret auth;--per-tokenadds individual revocable tokens with idle expiry; WebSocket clients must authenticate before receiving data - Opt-in TLS —
--tlsenables HTTPS/WSS with auto-generated or user-provided certificates - Multicast encryption — when
--tokenis set, multicast payloads are AES-256-GCM encrypted; receivers without the correct token silently ignore packets - Input gating — only connections from
127.0.0.1/::1can send keystrokes; remote connections (via SSH tunnel) are read-only - Multicast is read-only — UDP multicast output only, no input channel; TTL 1 by default (LAN-scoped); default group
239.0.0.1is admin-scoped (stays within organization);--multicast-globaluses225.0.0.1(globally routable, internet-ready when TTL allows) - Session isolation — session files use mode
0600, session directory uses mode0700(user-only access) - No shell interpolation — all child processes use
execFilewith array arguments, never string interpolation - Path validation —
--recordpaths reject null bytes and..traversal - Token isolation — web viewer tokens are served via a same-origin
/api/tokenendpoint, never embedded in HTML source - Dashboard isolation — the hub never exposes broadcaster URLs to browser clients
CI/CD security gates
Every push and pull request runs through:
| Check | Tool | What it does |
|---|---|---|
| Unit + E2E tests | Node.js test runner | 434 tests across 10 suites on Node 18, 20, 22 |
| Syntax check | node -c | Validates all .js files parse correctly |
| Linting | ESLint + eslint-plugin-security | Detects eval, unsafe regex, non-literal RegExp, timing attacks |
| Dependency audit | npm audit | Checks for known vulnerabilities in dependencies |
| Dependency review | dependency-review-action | Blocks PRs that add high-severity vulnerable dependencies |
| SAST | CodeQL | GitHub's semantic code analysis (security + quality queries) |
| SAST | Semgrep | Pattern-based scanning for injection, command execution, etc. |
| Secret scan | Custom grep | Checks for hardcoded API keys, passwords, tokens, .env files |
Release security
Releases (triggered by v* tags) add additional gates before publishing:
| Step | Description |
|---|---|
| All CI checks | Tests, lint, CodeQL, Semgrep must pass |
| Version verification | Tag must match package.json version |
| SBOM generation | CycloneDX Software Bill of Materials |
| Build attestation | Sigstore provenance linking package to exact commit + workflow |
| npm provenance | npm publish --provenance — verifiable with npm audit signatures |
| GitHub Release | Auto-generated release notes with tarball + SBOM artifacts |
CI/CD Pipeline
Push / PR to main
│
┌───────────────┼───────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────────┐
│ Tests │ │ ESLint │ │ CodeQL │
│ Node 18 │ │ security │ │ Semgrep │
│ Node 20 │ │ rules │ │ Secret scan │
│ Node 22 │ │ + audit │ │ Dep review │
└──────────┘ └──────────┘ └──────────────┘
│ │ │
└───────┬───────┘───────────────┘
▼
All checks pass ✓
│
│ (only on v* tag push)
▼
┌─────────────────────────┐
│ Release Pipeline │
│ │
│ 1. Verify version tag │
│ 2. Build tarball │
│ 3. Generate SBOM │
│ 4. Attest provenance │
│ 5. npm publish │
│ 6. GitHub Release │
└─────────────────────────┘Publishing a new version
# 1. Bump version (creates commit + tag)
npm version patch # 1.0.0 → 1.0.1
npm version minor # 1.0.0 → 1.1.0
npm version major # 1.0.0 → 2.0.0
# 2. Push with tags — triggers release pipeline
git push origin main --tagsThe release pipeline runs all security checks, builds the package, generates SBOM and provenance attestation, then publishes to npm. No manual npm publish needed.
Testing
# Run all tests
npm test
# Run individually
node test/test-parser.js # strace write/writev parser (22 tests)
node test/test-replay.js # replay buffer ring behavior (8 tests)
node test/test-remote-discovery.js # remote session discovery (9 tests)
node test/test-multicast-transport.js # multicast transport (30 tests)
node test/test-auth.js # auth/encryption/TLS/protocol (75 tests)
node test/test-vendor.js # vendor xterm.js bundle (9 tests)
node test/test-parse-args.js # shared flag parser (56 tests)
node test/test-token-store.js # per-watcher token store (72 tests)
node test/test-dashboard.js # dashboard, web-viewer & browser (51 tests)
node test/test-e2e.js # end-to-end integration (103 tests)
# Lint
npm run lint # ESLint with security rules
npm run check-syntax # node -c on all files
# Full security check
npm run security # lint + npm auditWhat's tested
- Strace parser —
write()andwritev()syscall parsing, hex-escaped data decoding, PID prefix stripping, multi-line buffering, UTF-8 detection, base64 fallback - Replay buffer — ring buffer eviction, byte counting, minimum-one-event guarantee,
getAll()ordering - Multicast transport — address parsing, send/receive on loopback, fragmentation/reassembly, garbage packet handling
- Auth module — key derivation, AES-256-GCM encrypt/decrypt round-trips (small, large, unicode, empty), wrong key rejection, corrupted data handling (truncated, garbled IV/tag/ciphertext), self-signed TLS cert generation and validation, user-provided cert/key, HTTPS functional tests, WSS over TLS, WebSocket auth protocol (correct/wrong/missing token, timeout, non-JSON), TLS+auth combined, encrypted multicast round-trips (small, fragmented), wrong key silent drop, plaintext/encrypted mismatch handling, protocol versioning,
HIT_TOKENenv var resolution - Vendor bundle — xterm.js, addon-fit, addon-web-links CSS/JS files exist and are non-empty
- Shared flag parser —
tryParseBroadcasterFlag()for all shared flags (--record,--replay-size,--multicast,--multicast-global,--multicast-ttl,--token,--per-token,--tls,--cert,--key), path validation, unrecognized flag passthrough, multi-flag sequences - Token store —
TokenStoregenerate/validate/revoke/list, max limit (50), idle expiry sweep, touch updates lastSeen, bindWs+revoke closes WS (code 4008), destroy clears state, token uniqueness - Dashboard & web-viewer —
getDashboardHTML()pure function tests (null/token embedding), web-viewer HTTP endpoints (/,/api/token, vendor files, cache headers, token security), dashboard hub WS protocol (session list, output forwarding, input forwarding), dashboard auth (correct/wrong/timeout → close codes 4001/4003), session lifecycle events (add/remove), dashboard HTTP serving (HTML, vendor files), Playwright browser tests (page load + xterm rendering, terminal output visibility, Ctrl+Shift+F maximize/restore, multi-session grid layout, Ctrl+N focus switching) - End-to-end — wrap/pipe/watch/list/stop lifecycle, auth (correct/wrong/timeout/missing token), TLS, TLS+auth, recording (asciicast v2), replay buffer late-join,
/api/sessionHTTP endpoint, multicast send/receive, pipe+auth, multiple sessions, web viewer health, input forwarding, graceful WS close (code 1001),/api/tokenendpoint (token not in HTML source), per-watcher tokens (generate+connect, master accepted, revoke closes WS 4008, admin API auth required),--discover(single/multi/spawn), wrap auto-reconnect (terminal + web + flag ordering), dashboard--discover(console + web), positional args as discover (watch + dashboard)
Requirements
| Requirement | Notes |
|---|---|
| Linux | Uses /proc, node-pty, and optionally strace |
| Node.js >= 18 | Tested on 18, 20, and 22 |
| strace | Only for hit-stream attach, not needed for hit-stream <cmd> |
| openssl | Only for --tls with auto-generated certificates |
Runtime dependencies
| Package | Version | Purpose |
|---|---|---|
| node-pty | ^1.1.0 | PTY spawn for wrap mode |
| ws | ^8.19.0 | WebSocket server and client |
That's it. Two runtime dependencies. Browser features use xterm.js inline-embedded in the web UI — fully self-contained, no network needed.
Limitations
- Linux only —
/procfilesystem and strace are Linux-specific - Strace truncation — attach mode truncates payloads > 16 KiB per syscall (wrap mode has no limit)
- TIOCSTI — input forwarding in attach mode may be blocked on kernels >= 6.2 (
sysctl dev.tty.legacy_tiocsti) - Multicast network support — some WiFi networks and firewalls block or throttle UDP multicast; most ISPs do not route multicast traffic today; fallback to WebSocket + SSH tunnel for unsupported networks
- Terminal size — watcher terminal size may differ from source (xterm.js auto-fits, terminal mode does not)
Project Structure
hit-stream/
├── bin/
│ └── hit-stream CLI entry point & subcommand router
├── lib/
│ ├── agents.js Agent whitelist (built-in + custom config)
│ ├── auth.js Authentication, encryption, TLS primitives
│ ├── broadcast.js hit-stream attach (strace capture)
│ ├── wrap.js hit-stream <cmd> (PTY wrapper, auto-reconnect)
│ ├── pipe.js hit-stream pipe (stdin broadcast)
│ ├── watch.js hit-stream watch (terminal, web, --discover, grep, notify)
│ ├── dashboard.js hit-stream dashboard (hub server, --discover, console TUI)
│ ├── dashboard-web.js Dashboard browser UI (multi-pane xterm.js grid)
│ ├── web-viewer.js Single-session browser viewer (xterm.js)
│ ├── list.js hit-stream list (with --share SSH tunnels)
│ ├── stop.js hit-stream stop
│ ├── daemon.js hit-stream daemon (auto-attach)
│ ├── discovery.js Session files, discoverBroadcasts() auto-start
│ ├── parse-args.js Shared broadcaster flag parser
│ ├── find-agent.js Process discovery with agent whitelist
│ ├── strace-parser.js strace write/writev parser + PTY fd detection
│ ├── multicast-transport.js UDP multicast sender/receiver (dgram)
│ ├── replay-buffer.js Ring buffer for replay on connect
│ └── usage.js Multi-provider API usage tracking
├── test/
│ ├── test-parser.js strace parser tests (22 cases)
│ ├── test-replay.js replay buffer tests (8 cases)
│ ├── test-remote-discovery.js remote session discovery tests (9 cases)
│ ├── test-multicast-transport.js multicast transport tests (30 cases)
│ ├── test-auth.js auth/encryption/TLS/protocol tests (75 cases)
│ ├── test-vendor.js vendor bundle tests (9 cases)
│ ├── test-parse-args.js shared flag parser tests (56 cases)
│ ├── test-token-store.js per-watcher token store tests (72 cases)
│ ├── test-dashboard.js dashboard, web-viewer & browser tests (51 cases)
│ └── test-e2e.js end-to-end integration tests (103 cases)
├── .github/
│ ├── workflows/
│ │ ├── ci.yml CI: tests, lint, audit, CodeQL, Semgrep, secrets
│ │ └── release.yml Release: verify, SBOM, attest, npm publish
│ └── copilot-instructions.md GitHub Copilot agent instructions
├── CLAUDE.md Claude agent instructions
├── AGENTS.md Codex (OpenAI) agent instructions
├── GEMINI.md Gemini (Google) agent instructions
├── .cursorrules Cursor agent instructions
├── .windsurfrules Windsurf agent instructions
├── .clinerules Cline agent instructions
├── .goosehints Goose (Block) agent instructions
├── .grok/GROK.md Grok (xAI) agent instructions
├── .roo/rules/hit-stream.md Roo Code agent instructions
├── .claude/agents.md Claude sub-agent constraints
├── vendor/
│ └── xterm/ Bundled xterm.js (inline-embedded in web UI)
├── package.json
├── eslint.config.js ESLint security rules configuration
└── README.mdContributing
Contributions are welcome! See CONTRIBUTING.md for guidelines.
Quick version:
# Fork and clone
git clone https://github.com/<you>/hit-stream.git
cd hit-stream
npm install
# Make changes, then verify
npm test
npm run lint
npm run check-syntax
# Open a PR against mainAreas where contributions are especially welcome:
- macOS support — replacing
/procand strace with dtrace/dtruss - New agent integrations — adding patterns for more AI coding agents
- Accessibility — screen reader support in web viewer/dashboard
Security Policy
See SECURITY.md for vulnerability reporting instructions.
Built with
This entire project — every line of code, every test, this very README — was built using Claude Code. Yes, the tool for watching AI coding agents was itself written by an AI coding agent. We used hit-stream to watch Claude build hit-stream.
