obsctl logo

๐Ÿ“ก obsctl

Your OBS control roomโ€”right inside the terminal.

A fast Crystal TUI, automation-friendly CLI, and resilient local daemon for OBS Studio.

๐ŸŽ›๏ธ Control ยท ๐Ÿ“Š Observe ยท ๐Ÿค– Automate ยท ๐Ÿ” Stay connected

Release Crystal obs-websocket License

Visit the site ยท More worxbend tools ยท Get started ยท Explore the TUI ยท Automate with the CLI ยท Read the docs


obsctl gives streamers, operators, and automation scripts one dependable control surface for OBS Studio. Use the responsive terminal dashboard while you are live, run precise one-shot commands from scripts, or keep the local daemon running as a user service.

โ•ญโ”€ OBSCTL // BROADCAST COMMAND CENTER โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
โ”‚ โ— daemon: connected   โ— OBS: connected   scene: Main Camera      โ”‚
โ”œโ”€ LIVE TELEMETRY โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ CPU  3.2%    FPS 60.0    MEM 742MB    NET 5842kbps               โ”‚
โ”œโ”€ Scenes โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€ Audio Matrix โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ โ–ถ Main Camera           โ”‚ โ— Mic/Aux          72%  โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–‘โ–‘โ–‘     โ”‚
โ”‚   Screen Share          โ”‚ โ—‹ Desktop Audio    48%  โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–‘โ–‘โ–‘โ–‘โ–‘     โ”‚
โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ

โœจ Why obsctl?

| | Capability | What it gives you | | --- | --- | --- | | ๐ŸŽ›๏ธ | Live terminal dashboard | Scenes, audio, profiles, collections, output state, telemetry, stream health, logs, and a command palette in one responsive view. | | โšก | Fast interactions | Optimistic UI updates, debounced volume changes, incremental rendering, and safe terminal resize reflow. | | ๐Ÿง  | One OBS connection | A local daemon owns the WebSocket, eliminating competing CLI/TUI connections and centralizing reconnect behavior. | | ๐Ÿค– | Automation-ready CLI | Stable commands, JSON envelopes, canonical error codes, and meaningful exit statuses for scripts and CI. | | ๐Ÿ” | Resilient operation | Bounded reconnect backoff, explicit reconnect control, state subscriptions, and secret-safe diagnostics. | | ๐ŸŽจ | Made for humans | 29 built-in themes, custom colors, Unicode and ASCII modes, English/Ukrainian UI surfaces, and keyboard-first navigation. | | ๐Ÿงฑ | Built on CryTUI | An in-tree immediate-mode Crystal TUI library inspired by Ratatui and tested with memory, ANSI, and real PTY backends. |

๐Ÿš€ Quick start

Prerequisites

1. Install

curl -fsSL https://worxbend.github.io/obsctl/install.sh | sh

That downloads the static binary for your architecture, checks it against the release's SHA256SUMS.txt, and installs it to ~/.local/bin โ€” or /usr/local/bin when run as root. Nothing is installed if the checksum does not match.

Pass options after sh -s --:

# a specific release, somewhere specific
curl -fsSL https://worxbend.github.io/obsctl/install.sh | sh -s -- --version v0.5.0 --dir /usr/local/bin

OBSCTL_VERSION and OBSCTL_INSTALL_DIR do the same thing. The script is also attached to every release, so https://github.com/worxbend/obsctl/releases/latest/download/install.sh fetches it too. Read it before you run it โ€” it is install.sh in this repository, and the copy on the site is deployed from it.

Prebuilt static binaries for linux-amd64 and linux-arm64, with SHA256SUMS.txt, are on the Releases page if you would rather do it by hand. They are linked against musl, so they do not depend on your glibc version.

Prefer to build it yourself?

git clone https://github.com/worxbend/obsctl.git
cd obsctl
shards install
make release
install -Dm755 bin/obsctl ~/.local/bin/obsctl

The release binary is written to bin/obsctl.

2. Initialize

obsctl init
obsctl validate-config

The default configuration lives at ~/.config/obsctl/config.yml. Override it with --config PATH or OBSCTL_CONFIG.

If OBS authentication is enabled, export its password before starting the server:

export OBS_WEBSOCKET_PASSWORD='your OBS WebSocket password'

No password? No problemโ€”when the variable is absent, obsctl attempts the connection with an empty password.

3. Start the daemon and TUI

In one terminal:

obsctl server --headless

In another:

obsctl

Once connected, import live scene and audio names while preserving your local settings:

obsctl dump-config

[!TIP] Scene and audio names discovered from OBS work immediately. Running dump-config is useful for adding memorable aliases and shortcuts, but it is not required before using the TUI.

๐ŸŽ›๏ธ The control room

The TUI is a thin local client: it subscribes to daemon state, OBS events, and logs, then renders them through CryTUI. It does not open another OBS WebSocket connection.

Stream health

While the stream is live, a Stats pane opens beside the logs with the numbers that tell you whether viewers are seeing a clean feed:

โ”œ ๐Ÿ“ก  Logs // Event Stream  02   live daemon feed โ”€โ”ฌ ๐Ÿ“Š  Stats  352   dropped frames โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ โ— 21:14:02 INFO  obs_connected  connected to OBS โ”‚ โšก 59.94 fps  โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–‡โ–ˆโ–ˆ  โฑ 1.42 ms             โ”‚
โ”‚ โ–ฒ 21:14:09 WARN  obs_event  scene switched -> 'M โ”‚ โ–ธ RENDER missed     12 / 128,400   0.01%      โ”‚
โ”‚                                                  โ”‚ โ–ธ OUTPUT skipped   340 / 128,000   0.27%      โ”‚
โ”‚                                                  โ”‚ โ—† HEALTH         nominal  ยท budget 9%         โ”‚

Active FPS is graphed against the best rate seen this session, so a 30 fps profile reads as healthy at 30. Average frame render time is colored by how much of a single frame's budget it consumes. Frames missed to rendering lag and skipped to encoding lag each carry their drop ratio, scored the way the OBS stats dock scores them โ€” under 1% is nominal, 5% or more is critical โ€” and the verdict line reports the worst of the three signals. On a narrow terminal the rows abbreviate, and below 80 columns the pane yields its space back to the logs.

Keyboard map

The dashboard is keyed like AstroNvim: : is the command line, Space is the leader and opens a which-key menu, and the motions are the vim ones.

| Key | Action | | --- | --- | | โ†‘ โ†“ or j k | Move within the focused panel | | gg / G | Jump to the first or last item | | Ctrl-D / Ctrl-U | Move half a panel down or up | | Ctrl-W + h/j/k/l | Move between panels | | Ctrl + arrows or Ctrl-h/j/k/l | Move between panels | | Enter | Activate the focused scene, profile, or collection | | m | Toggle the focused audio input mute state | | โ† โ†’ or h l | Lower or raise focused input volume | | : or / | Open the command line | | Tab / Shift-Tab or Ctrl-N / Ctrl-P | Cycle command completions | | Ctrl-U / Ctrl-W on the command line | Clear the line or delete a word | | s / a / p / c | Focus scenes, audio, profiles, or collections | | Ctrl-T or F2 | Open the appearance lab | | r / D / R | Reload config, dump config, or reconnect OBS | | q or Ctrl-C | Quit |

Press Space and the which-key menu lists what can follow it; Esc, or any key that leads nowhere, closes it.

| Leader key | Action | | --- | --- | | <leader>f s a p c | Find: focus scenes, audio, profiles, collections | | <leader>u t | UI: open the appearance lab | | <leader>o r d c | OBS: reload config, dump config, reconnect | | <leader>m | Toggle the focused input's mute | | <leader>q | Quit |

The single-letter bindings (s, a, p, c, r, D, R, m, q) still work, so nothing that was in your fingers before has been taken away.

Mouse

The dashboard also takes mouse input where the terminal supports it.

| Action | Result | | --- | --- | | Click a row | Focus that panel and select the row | | Click the selected row | Activate it โ€” switch scene, profile, or collection | | Click the speaker icon | Toggle that input's mute | | Wheel over an audio input | Raise or lower its volume | | Shift + wheel over audio | Move through the input list | | Wheel over any other panel | Move within it | | Click a which-key entry | Run that binding, or open its group | | Click a completion chip | Put it on the command line | | Wheel over the command line | Cycle completions | | Click away from the command line | Close it | | Click a theme in the appearance lab | Preview it; click it again to apply | | Wheel in the appearance lab | Move through the themes |

Clicking a row selects it and clicking it again activates it, so a stray click cannot cut the program scene mid-broadcast. The appearance lab follows the same rule: the first click previews a theme and the second commits it.

The wheel is a gain control over the audio matrix, the way it is on a mixer โ€” it acts on whichever input the pointer is over, without selecting it first, and coalesces into one OBS command the same way the โ†/โ†’ keys do.

Rapid volume keypresses update the display immediately and coalesce into one OBS command 120 ms after input stops. Terminal shrinking and expansion trigger a full safe repaint; normal frames return to cell-level incremental updates.

Command line

Press : (or /) and use the same grammar as the CLI. The line keeps whichever leader opened it, and completions follow it:

:scene "Main Camera"
:mute Mic/Aux
:vol "Desktop Audio" 65
:profile Streaming
:collection Gaming
:stream
:rec start
:status
:reconnect

The vim spellings resolve too: :q, :qa, :wq, and :x quit, and :h lists the commands.

๐Ÿงฐ CLI & automation

The daemon-backed CLI is designed to be pleasant interactively and predictable inside scripts.

# Status
obsctl status
obsctl obs-status
obsctl server-status

# Scenes and audio
obsctl scene main
obsctl mute mic
obsctl unmute mic
obsctl toggle-mute mic
obsctl volume "Desktop Audio" 70

# Studio and output controls
obsctl stream
obsctl record
obsctl reconnect

# Diagnostics
obsctl doctor
obsctl doctor --json

# Recording
obsctl record start
obsctl record pause
obsctl record resume
obsctl record stop
obsctl record status --json

# Event stream for scripts
obsctl watch --topics state | jq -r '.data.current_scene'

# Shell completions (bash, zsh, fish)
obsctl completions bash > /etc/bash_completion.d/obsctl

# Configuration and lifecycle
obsctl validate-config
obsctl dump-config
obsctl reload-config
obsctl config explain
obsctl config diff
obsctl config migrate --dry-run
obsctl shutdown-server

Aliases, shortcuts, exact OBS names, and case-insensitive names are supported. Quote names containing spaces.

JSON mode

Add --json before or after a scriptable command:

obsctl --json status
obsctl scene main --json

Every JSON invocation writes exactly one envelope to stdout:

{
  "ok": true,
  "result": {"message": "scene set: Main Camera"},
  "error": null,
  "exit_code": 0
}

Failures use canonical error codes and matching process exit statuses, making them straightforward to handle in shell scripts and other tools. See the command reference for the full grammar, supported JSON commands, error codes, and status schema.

Scripting behavior

Human output is decorated only when stdout is a terminal, so redirecting or piping gives you plain text; NO_COLOR and --color=never also disable it, and --color=always forces it back on. -q/--quiet drops the human message and leaves the exit code as the only signal, and --timeout SECONDS bounds a single daemon round trip. Streaming into a short-lived reader, as in obsctl watch | head -5, ends cleanly with exit 0.

Names are passed to the daemon exactly as the shell delivers them, so scenes and inputs containing quotes or backslashes work without special handling:

obsctl scene 'Camera "A"'

๐Ÿ—๏ธ Architecture

flowchart LR
    OBS["๐ŸŽฅ OBS Studio<br/>obs-websocket 5.x"]
    D["๐Ÿง  obsctl server<br/>single connection owner"]
    T["๐ŸŽ›๏ธ CryTUI dashboard"]
    C["โš™๏ธ CLI / scripts"]
    S["๐Ÿ› ๏ธ systemd --user"]

    OBS <-->|WebSocket| D
    D <-->|Unix socket IPC| T
    D <-->|Unix socket IPC| C
    S -. supervises .-> D

The daemon is the authoritative state owner. This boundary provides:

The Unix socket lives under $XDG_RUNTIME_DIR/obsctl/ when an XDG runtime directory is available, with a user-specific /tmp/obsctl-$UID/ fallback. Control remains localโ€”there is no network-facing obsctl API.

๐Ÿ”ง Configuration

A minimal configuration looks like this:

version: 1

connection:
  host: 127.0.0.1
  port: 4455
  password_env: OBS_WEBSOCKET_PASSWORD

reconnect:
  enabled: true
  endless: true

ui:
  theme: default
  advanced_ui: true
  show_icons: true
  locale: en

scenes:
  - name: Main Camera
    alias: main
    shortcut: "1"

audio:
  inputs:
    - name: Mic/Aux
      alias: mic
      shortcut: m

Important behavior:

See the complete configuration reference.

๐Ÿ› ๏ธ Run as a user service

No root daemon is required. Install a systemd --user unit using the current binary path:

obsctl service install
obsctl service start
obsctl service status

Other lifecycle commands:

obsctl service stop
obsctl service restart
obsctl service uninstall

The generated unit lives at ~/.config/systemd/user/obsctl.service.

๐Ÿฉบ Troubleshooting

Show connection and retry diagnostics

obsctl --log-level debug server --headless

Server logs are mirrored to stderr and persisted at ~/.local/state/obsctl/obsctl.log. Passwords and generated authentication strings are redacted from both sinks. WebSocket resets include the close code, reason, reconnect attempt, and next delay.

The CLI says the server is unavailable

Start it directly:

obsctl server --headless

Or install/start the user service. Thin client commands exit with status 3 when the daemon or OBS connection is unavailable.

OBS was offline during startup

Reconnect is enabled by default. Check obsctl server-status, then request an immediate attempt with obsctl reconnect. If reconnect.enabled: false, restart the server after OBS becomes available.

๐Ÿง‘โ€๐Ÿ’ป Development

shards install
make format
make test
make build
make release
make lint

The local test suite is deterministic and does not require OBS Studio. It uses a fake obs-websocket server, Unix-socket integration tests, memory rendering, ANSI assertions, and real PTY lifecycle probes.

Optional cross-implementation fixture verification against a sibling ../obsctl-rs checkout:

make contract-rs-compat

Strict compatibility mode requires a recognized fixture root in both repositories. See docs/protocol.md for the contract model.

๐Ÿ“š Documentation

| Guide | Contents | | --- | --- | | Microsite | Project landing page, and the installer the one-liner fetches | | worxbend tools | The sibling streaming utilities this one belongs with | | Commands | Complete CLI/palette grammar, JSON envelopes, errors, and status semantics | | Configuration | Connection, reconnect, UI, theme, alias, and persistence settings | | Protocol | IPC framing, subscriptions, state contracts, and compatibility fixtures | | CryTUI research | Ratatui analysis, Crystal library evaluation, rendering architecture, and parity evidence | | Contributing | Setup, the four build gates, project layout, and release process | | Changelog | Release history | | Security | Reporting vulnerabilities, credential handling, and the daemon's trust boundary |

๐Ÿค Contributing

Issues, focused bug reports, documentation improvements, and pull requests are welcome. For behavior changes, include a regression test at the narrowest useful layer and run make check before opening a PR โ€” see CONTRIBUTING.md for the full workflow.

Useful reports include:

Please never include OBS passwords or generated authentication strings.

For security vulnerabilities, do not open a public issue โ€” follow SECURITY.md instead.

๐Ÿ“œ License

obsctl is released under the MIT license.


Built with ๐Ÿ’Ž Crystal, terminal escape sequences, and an unreasonable love for reliable broadcast controls.

If obsctl improves your control room, consider starring the repository. โญ