obsctl On air

obsctl.

Your OBS control room — right inside the terminal.

A Crystal TUI, an automation-friendly CLI, and a local daemon that owns the single OBS WebSocket connection — so your scripts, your dashboard, and your stream never fight over it.

obsctl · $XDG_RUNTIME_DIR/obsctl/obsctl.sock

        
Live render Not a screenshot — this frame is drawn by the real widget code
Console

Pick a look.
The whole room changes.

obsctl ships 29 built-in themes, and custom themes can override any subset of the palette. These eight are rendered above — and the page you are reading restyles itself from the same values, because there is only one palette and both are using it.

Press Ctrl-T or F2 in the running TUI to open the appearance lab.

What you get
Dashboard

Everything on one screen

Scenes, audio matrix, profiles, collections, output state, telemetry, stream health, a live log feed, and a command palette — in one responsive view.

Daemon

One OBS connection

A local daemon owns the WebSocket and holds authoritative state. The TUI and every CLI call are thin clients over a Unix socket. No competing handshakes.

Automation

Built for scripts

Stable command grammar, one JSON envelope per invocation, canonical error codes, and exit statuses that mean something in a shell conditional.

Resilience

Survives OBS restarts

Bounded reconnect backoff, explicit reconnect control, state subscriptions, and diagnostics that redact passwords and auth strings from every sink.

Interface

Made for humans

29 themes, custom colours, Unicode and ASCII modes, English and Ukrainian UI surfaces, and keyboard-first navigation throughout.

CryTUI

Its own TUI library

An in-tree immediate-mode Crystal library inspired by Ratatui, tested against memory, ANSI, and real PTY backends.

Automation

The same command grammar drives the palette and the shell. Aliases, shortcuts, exact OBS names, and case-insensitive names all resolve; quote anything with spaces.

# Scenes, audio, outputs
$ obsctl scene main
$ obsctl volume "Desktop Audio" 70
$ obsctl toggle-mute mic
$ obsctl record start
$ obsctl stream

# Follow daemon state from a script
$ obsctl watch --topics state \
    | jq -r '.data.current_scene'
Main Camera

# Run it as a user service
$ obsctl service install
$ obsctl service start
# One envelope per invocation, on stdout
$ obsctl scene main --json
{
  "ok": true,
  "result": { "message": "scene set: Main Camera" },
  "error": null,
  "exit_code": 0
}

# Failures are structured too
$ obsctl scene nope --json; echo $?
{
  "ok": false,
  "result": null,
  "error": { "code": "SCENE_NOT_FOUND", … },
  "exit_code": 4
}
4
Exit statuses
CodeClassMeaning
0SuccessThe command did what it said
1FailureUnclassified failure
2ConfigMissing or invalid configuration
3ConnectionDaemon or OBS unavailable
4ObsRequestOBS refused the request
5CommandParseThe command could not be parsed
6IpcLocal IPC protocol error
Stream health

Know before your viewers do.

While the stream is live, a stats panel opens beside the logs with the numbers that actually predict a bad broadcast: frames missed to rendering lag, frames skipped to encoding lag, and how much of each frame's budget the compositor is burning.

Drop ratios are scored the way the OBS stats dock scores them — under 1% nominal, 5% or more critical — and FPS is judged against the best rate seen this session, so a 30 fps profile reads as healthy at 30.

Stats — stream health

        
Reads the telemetry the daemon already polls — no extra OBS traffic
Signal path

One process owns the OBS connection. Everything else is a local client, so state, reconnects, validation, and secret handling all live in one place — and control never leaves your machine.

Source OBS Studio

obs-websocket 5.x, on localhost.

↓ WebSocket
Connection owner obsctl server

Holds the single socket and the authoritative state snapshot. Supervises reconnects.

↓ Unix socket · newline-delimited JSON
Client TUI dashboard

Subscribes to state, events, and logs. Opens no OBS connection of its own.

↑ subscribe
Client CLI & scripts

Short-lived processes. One request, one envelope, one exit code.

↑ command
Install
Prebuilt binary

Static musl builds for linux-amd64 and linux-arm64, published with SHA256SUMS.txt — they do not depend on your glibc.

$ tar xzf obsctl-v0.4.0-linux-amd64.tar.gz
$ install -Dm755 obsctl ~/.local/bin/obsctl
From source
$ git clone https://github.com/worxbend/obsctl.git
$ cd obsctl && shards install
$ make release

Needs Linux, Crystal 1.21+, and Shards.

First run
$ obsctl init
$ obsctl validate-config
$ export OBS_WEBSOCKET_PASSWORD='…'

# one terminal: the daemon
$ obsctl server --headless

# another: the control room
$ obsctl

Config lives at ~/.config/obsctl/config.yml. Secrets belong in environment variables — a plaintext password in the file earns you a warning.

Docs

Command grammar · Configuration · IPC protocol · Changelog