Switchboard

Active
On this page
  1. What it does
  2. Home dashboard
  3. OBS control
  4. Twitch & audience
  5. Overlays
  6. Architecture
  7. Engineering notes
  8. Status

Switchboard is my streaming cockpit: one desktop app that controls everything around a live Twitch stream — the successor to twotter, the Electron app it replaced.

Repository: https://gitlab.com/KirboDev/agentic-coding/switchboard.

What it does

  • Controls any number of OBS (Open Broadcaster Software) instances (scenes, stream/record, stream delay) and any number of Twitch accounts (per-scene stream info, EventSub events, chat, markers, audience tracking)
  • Bridges Spotify now-playing (MPRIS (Media Player Remote Interfacing Specification)/SMTC (System Media Transport Controls)/AppleScript per OS (Operating System)), Discord webhooks, and Home Assistant in both directions
  • Serves seven browser-source overlays from an embedded web server — now-playing, a live input visualizer with an in-app canvas editor, alerts, chat, and more
  • Runs an event → action rules engine: triggers, conditions, action sequences, templating, per-rule run history
  • Pairs multiple Switchboard machines into a LAN (Local Area Network) mesh over TLS (Transport Layer Security), so rules on one machine can drive actions on another

Home dashboard

The Home dashboard is the one screen that has to say "is everything actually okay" at a glance. Connection badges for the Database, the OS Keyring and the LAN mesh (1/1 peers) sit across the top, with Streaming MAC live 2h 47m into Live — Gameplay next to Gaming PC (Personal Computer) recording with a 30-second delay. The Twitch panel for Kirbo reads live with 42 watching and 9 in chat, Spotify's now-playing card sits mid-track, and a recent-activity feed timestamps every automation fire, follow and peer connection down to the second — falling back to a Previous sessions log from disk when the current one runs out of rows.

One screen, nothing to tab-switch between.

OBS control

The OBS panel is where scenes actually get switched from, and it's built to survive being on camera by accident: Streaming MAC's current scene is Live — Gameplay, holding a live title override — Kirbo ▸ Marvel Rivals, title — that reverts itself to whatever Streaming MAC was on before in 120 seconds, so a manual "brb, one sec" scene change can't get stuck live for the rest of the stream.

Starting Soon, Just Chatting, BRB (Be Right Back) and Ending sit unmodified beside the override.

Twitch & audience

The Twitch panel goes further than the stream-info fields it started as — the Audience card is a tracked snapshot of who follows and subscribes right now, not just a live count. It splits Followers from Subscribers by tier and, separately, tracks Departures with the actual reason attached (unfollowed versus sub ended (Tier 1)), because Twitch's API (Application Programming Interface) only ever tells you who's there today: follow dates come straight from Twitch and are exact, but subscription start dates aren't exposed at all, so the tracker stamps since the moment it first notices a sub and before for anything that already existed when tracking began.

Audience for @kirbo, live: 5 followers, 3 tiered subscribers, 3 departures each tagged with a cause.

Overlays

The Spotify bridge pulls now-playing from whatever OS-native source is available (MPRIS on Linux, SMTC on Windows, AppleScript on macOS) and pushes it out to the overlays and to Discord/Home Assistant — and the overlays themselves are just browser sources pointed at the embedded web server, so OBS never needs to know Switchboard is Rust and React underneath. The Overlays screen lists all seven with what's actually wired to them: the now-playing card is template-driven with three saved presets and shows live Spotify connected state, the input visualizer renders keyboard/mouse/controller onto an in-app canvas, and both Twitch chat (no login needed) and the event log double as full OBS custom docks rather than staying browser-only.

All seven, each wired to exactly what it needs — nothing generic.

Architecture

Under the hood it's Tauri 2 with a backend-heavy split: ~30k lines of Rust own all state, connections and scheduling in long-running tokio tasks; the React webview is a thin event-driven client. Secrets live in the OS keyring, config in SQLite, and there are zero app secrets anywhere — Twitch uses Device Code Grant and Spotify uses PKCE (Proof Key for Code Exchange), so the public repo and the binaries contain nothing sensitive. And since a stream setup is rarely one machine, multiple Switchboard installs pair into a LAN mesh over TLS — a rule firing on the streaming PC can trigger an action on whichever machine actually owns that integration.

Engineering notes

AppImage packaging is its own bug class. A released build once bundled a stale libwayland that shadowed the host's and crashed GPU (Graphics Processing Unit) rendering — fixed in CI (Continuous Integration) by stripping the bundled libs and re-signing the updater signature. The same class of bug hit audio: WebKitGTK's WebAudio needs GStreamer plugins that dependency-walking bundlers never discover, fixed by bundling more, not less. And "open in browser" silently did nothing on KDE Plasma 6, because Tauri ships a frozen xdg-open that only recognizes Plasma 4 and 5 — and exits 0 on failure, so the failure looked like success.

Fixed ports aren't fixed, on Linux. Switchboard's overlay and control ports sit inside the kernel's ephemeral range, so any process's ordinary outbound connection can squat one as its source port — another app's Discord plugin once held one for three hours. A related failure mode: a WebKit crash can kill the GTK main thread while the tokio runtime keeps running, leaving a headless husk that still holds the ports while single-instance detection thinks the app has quit.

Protocols have their own quirks. Twitch's IRC (Internet Relay Chat)-over-WebSocket bridge silently drops the session if CAP, NICK and JOIN land in a single frame — no error, just an endless reconnect loop, so each goes out as its own frame. Neither obs-websocket nor its client library sends a keepalive, so half-open connections report "connected" forever; Switchboard hand-rolls a heartbeat and tests it against a fake OBS server built to play dead.

The one that cost a day: cargo build --release doesn't produce a release Tauri app. Without the custom-protocol feature it still loads the Vite dev URL (Uniform Resource Locator), which in CI renders a blank webview that looks exactly like a rendering bug.

Status

Mostly written by Claude, with me as product owner and lead architect — the full story is in the Switchboard blog post.

In daily production use on two machines, developed actively. MIT (Massachusetts Institute of Technology) licensed, with a Home Assistant integration (HACS (Home Assistant Community Store)) and an OpenDeck plugin as first-party satellites.