.env.persona, prompt, model. That's it.Open the Chrome Web Store listing, hit View source on GitHub, confirm the manifest permissions. Works in Chromium and Edge too — same file.
Click the puzzle icon, pin Peanut Gallery, then Ctrl+Shift+P to dock the panel. It stays on while you browse.
Load any tab with audio — a livestream, a podcast, a webinar, a Zoom call, a YouTube video. The staff clocks in automatically as soon as the first caption lands. No button, no signup, no modal.
That is the entire install path for the default experience. The hosted backend uses our keys, routes through our server, and covers new installs with a free lifetime trial. After the trial: paste your own provider keys (free forever, see § 03). A flat-rate Peanut Gallery Plus tier ($8/mo, 16 h/week of hosted reactions) is in the works. You can also self-host the whole stack via § 02.
Free trial: one 15-minute session, lifetime per install. After that, pick a path: My keys (free, you pay providers directly) or Peanut Gallery Plus ($8/mo, 16 h/week on our dime). Both options are in the settings drawer's backend toggle. Full breakdown on the pricing page.
.env file. Your machine is now the wire room.The backend is a small Node service that brokers between the extension and the three upstream APIs. It exists so your API keys never ship to the browser — and so you can swap models or add caching without touching extension code.
You need Node 20+ and git. The server has no database and no state — just a process and three outbound connections.
$ git clone https://github.com/peanut-gallery/server.git $ cd server $ npm install # ~200 deps, 20 seconds on a decent pipe
Copy .env.example to .env and drop in the three keys you collected in § 03. xAI covers both the persona slots and the fact-check grounding via Grok Live Search — one key, two jobs.
$ cp .env.example .env $ nano .env # paste three keys, save, exit
Dev mode reloads on change; production mode is what you pin to a tmux window or a systemd unit. Either way it binds to one port and listens on localhost by default.
$ npm run dev › peanut-gallery/server listening on :8787 › deepgram ready (nova-3) › anthropic ready (claude-haiku-4-5) › xai ready (grok-4-fast · live-search) › wire room is open. go heckle something.
In the extension's Tweaks panel, switch Backend from Hosted to Custom and paste http://localhost:8787. The panel badge flips to SELF-HOSTED when the health-check clears.
If you want to run the server somewhere other than localhost, put it behind a reverse proxy with TLS (Caddy does this in three lines) and restrict ALLOWED_ORIGIN to your extension ID. The extension refuses non-HTTPS custom backends except for localhost.
Deepgram bills by streaming minute, Anthropic & xAI by token. A 90-minute podcast at full staff runs roughly $0.18–$0.35 of API. Set usage caps on each provider's dashboard before you walk away from it.
Each vendor's signup is roughly the same ritual: create an account, verify email, create an API key, copy it into your .env. Free tiers on all three are enough to get through a few hours of testing — which is enough to decide whether you want to wire in a real card.
If you swap models — say, you prefer Claude Sonnet for the comedy writer or Grok Mini for the troll — the model string lives in each pack file, not the server. See § 04. The server doesn't care which model you pick as long as the vendor is configured.
Two packs ship by default: the Morning Crew (The Producer, The Troll, The Sound Guy, The Joke Writer) and the TWiST staff (Jason, Molly, Lon, Alex). Everything else you'd ever want is a pack you write yourself.
A note on the rosters. Pack names nod to public media figures. Peanut Gallery is an unofficial work of commentary and parody — not affiliated with, endorsed by, or approved by any of them. Any of those packs can be renamed, swapped, or replaced wholesale; the schema below is the only thing that's load-bearing.
The roles are fixed — fact, dunk, cue, bit — because they pair with specific UI treatments (the tag colors, the sound-cue playback, the receipts badge). The personas filling those roles are entirely up to you.
| Field | Type | Description | |
|---|---|---|---|
id |
string | Required | Lowercase, no spaces. Appears in the pack picker and the URL params. e.g. morning-crew, twist, mst3k. |
name |
string | Required | Human-readable label shown in the Tweaks menu. Keep it under 28 chars. |
tagline |
string | Optional | A one-line pitch for the pack picker. Italicized in the UI. |
staff |
array[4] | Required | Exactly four persona entries, one per role. Extras are ignored; missing roles throw. |
staff[].role |
enum | Required | One of fact · dunk · cue · bit. Exactly once each. |
staff[].persona |
string | Required | Display name. Shown on the mug card, the wire tag, and the clipboard export. |
staff[].model |
string | Required | Vendor-prefixed model ID. anthropic/claude-haiku-4-5, xai/grok-4-fast, etc. The server routes by prefix. |
staff[].prompt |
string | Required | The full system prompt. ~400 tokens is the sweet spot. Longer = slower. Include voice, cadence, and two or three example reactions in-line. |
staff[].temperature |
number | Optional | Default 0.7. Trolls & comedy writers like 0.9. Fact-checker wants 0.2. |
staff[].cooldown_ms |
number | Optional | Minimum gap between this persona's posts. Keeps the troll from monologuing. Default 4000. |
mst3k.json packHosted backend: upload via the Packs tab in the extension Tweaks panel. Self-hosted: drop it in ./packs/ on the server and it's picked up on next request — no restart.
Run npm run pack:lint packs/mst3k.json. It checks the schema, pings each model once to confirm the vendor is reachable, and prints token counts for each prompt.
In the extension, open Tweaks → Pack and pick your new entry. The feed clears, the mugs re-render, and the next transcript partial runs through the new cast.
Give each persona three short in-line examples of the shape of reaction you want. "Short zinger under 12 words" > "Be funny." Include a negative example or two: "Don't explain the joke. Don't caveat. Don't apologize." These cost you nothing and save the model from hedging.
| Action | How | What it does |
|---|---|---|
| Open the panel | Ctrl+Shift+P | Docks the wire room in Chrome's native side panel. Stays open across tabs. |
| Swap the pack | Tweaks → Pack | Instantly retires the current staff and brings in the new one. Feed clears; history kept per-session. |
| Mute a critic | Click the bottom-row pill | That persona stops posting. Other three keep going. Un-mute re-enables mid-stream. |
| Clip a line | Hover a line → clip icon | Copies a formatted quote block to clipboard. Includes timestamp, persona, source URL. |
| Export the feed | Tweaks → Export | Dumps the full session as Markdown. Useful for sharing a particularly brutal broadcast. |
| Kill the panel | Tweaks → Off-air | Extension stays installed; mic stays off; wire goes dark. Pick it back up later. |
The extension does not run on tabs you haven't opened the panel against. There is no background heckling. If the panel is closed, the mic is not listening, the server is not called, and nothing is happening.
SELF-HOSTED · UNREACHABLE./health on your backend. Confirm the server is up (curl http://localhost:8787/health), your ALLOWED_ORIGIN matches the extension ID, and you're not on a different network than you think (corporate VPNs love to rewrite localhost).npm run pack:lint <pack>.json, check token counts, confirm the model ID still exists at the vendor. Vendors rename models; packs need updating when they do.XAI_API_KEY is missing or invalid (Grok Live Search piggybacks on it). Fact-checker falls back to model-only claims when search fails; it flags itself with a small NO RX badge when it does. Add the key or accept the fallback.If none of the above applies, file an issue on the github with your server.log, the pack you're using, and the video URL. We read them. We do not promise to fix them. It's a newspaper run by four AIs.