Terminal broadcasting for AI agents. Watch your agents work live, replay sessions, embed anywhere.
shout broadcasts terminal sessions to the web in real time — built for AI agents and developers. Agents stream via SDKs and MCP servers; developers stream via the CLI. Viewers watch in a browser with no installs. Sessions are recorded for replay and export.
Think of it like a window into what your AI agent is doing. tmux is for private pair programming, asciinema is for recorded demos — shout is for watching agents work and broadcasting live sessions.
Website: shout.run
npm install -g shout-run
shout login
shoutYour terminal is now live. Viewers watch at shout.run/<you>/<sessionId>.
Running shout with no arguments spawns a PTY shell. Everything you type and see goes out live.
shout # prompts for title and visibility
shout -t "building my app" # skip the title prompt
shout -v private # unlisted session, link-only accessWhen stdin is piped, shout detects it and broadcasts the output directly.
npm run build | shout
pytest -v | shout
tail -f /var/log/app.log | shout| Visibility | What happens |
|---|---|
public |
Listed on the feed, anyone can watch |
followers |
Only your followers see it |
private |
Unlisted. Live-only — no replay after it ends |
Shout automatically redacts sensitive values from the broadcast stream. Your local terminal sees everything — only the broadcast output is scrubbed.
CLI — enabled by default. Collects values of sensitive env vars (AWS keys, GitHub tokens, OpenAI keys, etc.) at session start and replaces them with [REDACTED] in the stream.
shout # auto-redacts env var secrets
shout --redact-value "my-custom-secret" # add extra values to redact
shout --redact-file .env # load secrets from a .env file
shout --no-redact # disable redaction entirelySDKs — opt-in via constructor options or at runtime:
const session = new ShoutSession({
apiKey: 'shout_sk_...',
redactSecrets: [process.env.DB_PASSWORD],
});
session.addSecret(dynamicSecret); // add at runtimesession = ShoutSession(
api_key="shout_sk_...",
redact_secrets=[os.environ["DB_PASSWORD"]],
)
session.add_secret(dynamic_secret) # add at runtimeThis uses exact string matching (not regex) so it won't mangle ANSI escape sequences or terminal control codes.
After a session ends, the full recording is available for replay at the same URL. Late joiners during a live session receive a terminal state snapshot so they see the current screen immediately. Private sessions don't store replay data.
Drop a session into a blog post, docs page, or README:
<iframe
src="https://shout.run/embed/SESSION_ID"
width="800" height="500"
frameborder="0" allowfullscreen>
</iframe>| Param | Default | What it does |
|---|---|---|
autoplay |
1 |
0 to pause on load |
speed |
1 |
Playback speed multiplier |
t |
0 |
Start time in seconds |
controls |
1 |
0 to hide the player bar |
Broadcast programmatically from scripts, CI pipelines, or AI agents.
npm install shout-run-sdkimport { ShoutSession } from 'shout-run-sdk';
const session = new ShoutSession({
apiKey: 'shout_sk_...',
title: 'My Build',
});
const info = await session.start();
console.log(`Live at: ${info.url}`);
session.write('Hello from the SDK!\r\n');
await session.end();
// Search for sessions
const results = await ShoutSession.searchSessions('shout_sk_...', 'deploy');
// Read a session transcript (ANSI codes stripped)
const content = await ShoutSession.getSessionContent('shout_sk_...', 'session-id');
console.log(content.transcript);Full reference: packages/sdk/README.md
pip install shout-run-sdkfrom shout_sdk import ShoutSession
with ShoutSession(api_key="shout_sk_...") as session:
info = session.start(title="My Build")
print(f"Live at: {info['url']}")
session.write("Hello from Python!\r\n")
# Search for sessions
results = ShoutSession.search_sessions("shout_sk_...", "deploy")
# Read a session transcript (ANSI codes stripped)
content = ShoutSession.get_session_content("shout_sk_...", "session-id")
print(content["transcript"])Full reference: packages/sdk-python/README.md
Let AI agents (Claude Code, Cursor, Windsurf) broadcast their terminal work. Available in TypeScript and Python.
{
"mcpServers": {
"shout": {
"command": "npx",
"args": ["-y", "shout-run-mcp"],
"env": { "SHOUT_API_KEY": "shout_sk_..." }
}
}
}{
"mcpServers": {
"shout": {
"command": "uvx",
"args": ["shout-run-mcp"],
"env": { "SHOUT_API_KEY": "shout_sk_..." }
}
}
}Exposed tools: shout_start_broadcast, shout_write, shout_end_broadcast, shout_broadcast_status, shout_delete_session, shout_search_sessions, shout_read_session
Docs: packages/mcp/README.md and packages/mcp-python/README.md
shout login
shout api-key create "My Agent"Keys start with shout_sk_. List with shout api-key list, revoke with shout api-key revoke <id>.
Usage: shout [options] [command]
Commands:
broadcast [options] Start broadcasting (default command)
login Authenticate with GitHub
logout Remove stored credentials
whoami Show current user
api-key Manage API keys
help [command] Display help
Broadcast options:
-t, --title <title> Session title
-v, --visibility <visibility> public, followers, or private
--tags <tags> Comma-separated tags
--no-redact Disable secret redaction
--redact-file <path> Load secrets from a .env file
--redact-value <value...> Add values to redact
packages/
shared/ Types, binary protocol, constants (build first)
cli/ CLI tool — published as shout-run on npm
sdk/ TypeScript SDK — shout-run-sdk on npm
mcp/ TypeScript MCP server — shout-run-mcp on npm
worker/ Cloudflare Workers + Durable Objects backend
web/ Next.js 15 / React 19 frontend
sdk-python/ Python SDK — shout-run-sdk on PyPI
mcp-python/ Python MCP server — shout-run-mcp on PyPI
vt-wasm/ Rust WASM terminal parser (late-join snapshots)
The CLI captures terminal output from a PTY, encodes it into a compact binary frame protocol, and sends it over WebSocket to a Cloudflare Worker. A Durable Object (SessionHub) fans the frames out to all connected viewer WebSockets. The Next.js frontend decodes frames and renders them into an xterm.js terminal.
SDKs and MCP servers do the same thing programmatically.
Every WebSocket message: [type: 1 byte][timestamp: 4 bytes][payload: variable]
| Type | Byte | Purpose |
|---|---|---|
| Output | 0x01 |
Terminal output data |
| Meta | 0x02 |
Session metadata |
| ViewerCount | 0x03 |
Current viewer count |
| End | 0x04 |
Session ended |
| Ping | 0x05 |
Keepalive |
| Pong | 0x06 |
Keepalive response |
| Error | 0x07 |
Error message |
| Resize | 0x08 |
Terminal dimensions changed |
| Snapshot | 0x09 |
Terminal state for late joiners |
During a live session, binary frames accumulate in DO memory (pendingChunks). Every 30 seconds, an alarm flushes them to R2 as numbered part files. On session end, parts are consolidated into a single replay.json with a manifest.json index. Replay requests read from R2 with a DO storage fallback for older sessions.
Export produces asciicast v2 .cast files (NDJSON format).
The CLI strips 26 sensitive environment variable prefixes before spawning the broadcast shell — things like AWS_SECRET, GITHUB_TOKEN, OPENAI_API_KEY, STRIPE_SECRET, and others. The spawned shell gets a clean environment with SHOUT_SESSION=1 set.
Bytes-per-second throttling (100 KB/s) runs in both the CLI and the Durable Object. Sessions are capped at 4 hours and 50 per user per day.
- Node.js >= 20, pnpm >= 9
- A Cloudflare account (Workers free tier works)
- A Turso database
- A GitHub OAuth App
- Vercel (for the web frontend)
git clone https://github.com/pavanmadiraju91/shout-run.git
cd shout-run
pnpm install
cp .env.example .envcd packages/worker
wrangler secret put TURSO_URL
wrangler secret put TURSO_AUTH_TOKEN
wrangler secret put JWT_SECRET
wrangler secret put GITHUB_CLIENT_ID
wrangler secret put GITHUB_CLIENT_SECRET| Variable | What it is |
|---|---|
GITHUB_CLIENT_ID |
GitHub OAuth App client ID |
GITHUB_CLIENT_SECRET |
GitHub OAuth App client secret |
TURSO_URL |
Turso database URL |
TURSO_AUTH_TOKEN |
Turso auth token |
JWT_SECRET |
Random secret for signing JWTs |
NEXT_PUBLIC_API_URL |
Public URL of deployed worker |
NEXT_PUBLIC_WS_URL |
WebSocket URL of deployed worker |
pnpm --filter @shout/worker deploy # Cloudflare Worker
cd packages/web && vercel deploy # Web frontendpnpm install # install everything
pnpm dev # all dev servers in parallel
pnpm build # build all packages (Turborepo orders them)
pnpm lint # lint
pnpm typecheck # type-checkPer-package:
pnpm --filter @shout/shared build # types + protocol (build first)
pnpm --filter @shout/cli build # CLI
pnpm --filter @shout/sdk build # TypeScript SDK
pnpm --filter @shout/mcp build # MCP server (needs SDK built)
pnpm --filter @shout/worker dev # Worker dev server
pnpm --filter @shout/web dev # Next.js on :3000
pnpm --filter @shout/worker test # Vitest testsPython:
cd packages/sdk-python && python -m build
cd packages/mcp-python && python -m build| Package | Registry | Install |
|---|---|---|
shout-run |
npm | npm i -g shout-run |
shout-run-sdk |
npm | npm i shout-run-sdk |
shout-run-mcp |
npm | npx shout-run-mcp |
shout-run-sdk |
PyPI | pip install shout-run-sdk |
shout-run-mcp |
PyPI | pip install shout-run-mcp |
- Fork the repo
- Create a feature branch
- Make your changes
- Open a pull request