obsctl v0.7.0

CLI/TUI for controlling OBS Studio through obs-websocket 5.x.
obsctl logo

πŸ“‘ obsctl

Drive OBS Studio from your terminal. No mouse, no clicking around. πŸ–±οΈπŸš«

A snappy dashboard, a scriptable CLI, and a little daemon that keeps the OBS connection warm.

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

Release Crystal obs-websocket License

πŸŽ›οΈ Control Β· πŸ“Š Watch Β· πŸ€– Automate Β· πŸ” Stay connected

Website Β· Install Β· First 5 minutes Β· Cheat sheet Β· Scripting Β· Docs

The obsctl dashboard: scenes, audio matrix, profiles, collections, logs and stream stats

πŸ‘† Not a mockup β€” every screenshot here is rendered from the real widget code by make readme-shots.


πŸ€” What even is this?

You're live. Your mic is hot, your scene is wrong, and OBS is buried behind a game on another monitor. obsctl is OBS in a terminal window:

  • πŸŽ›οΈ A dashboard β€” scenes, audio levels, profiles, collections, logs and stream health on one screen. Switch scenes with Enter, ride the mic gain with ↑/↓.
  • πŸ€– A CLI β€” obsctl scene brb, obsctl mute mic. Perfect for hotkeys, Stream Deck buttons, cron jobs, or a chat bot.
  • 🧠 A tiny daemon β€” one background process holds the OBS connection and reconnects when OBS restarts. Everything else just talks to it. Fast, and no connection storm.

Think of it as kubectl, but for your stream. And it's keyed like Neovim (AstroNvim, specifically), so : runs commands and Space opens a menu of what you can press next.

You'll like it if you: live on a keyboard ⌨️ Β· run OBS on a small or headless machine πŸ–₯️ Β· want stream actions in scripts πŸ“œ Β· think a terminal is a perfectly nice place to be 🧑


⚑ Install

One line. Works on any Linux, any distro β€” the binaries are static.

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

What that actually does (no magic, promise πŸ™‚):

  1. πŸ“₯ Grabs the right build for your CPU (amd64 or arm64)
  2. πŸ” Checks it against the release's SHA256SUMS.txt β€” if the hash is wrong, nothing gets installed
  3. πŸ“ Drops the binary in ~/.local/bin (or /usr/local/bin if you're root)
  4. βœ… Runs it once to make sure it works

[!TIP] Nervous about piping a script into a shell? Good instinct. It's install.sh right here in this repo β€” read it first, or download and run it yourself.

🎚️ Options, pinned versions, and other ways to install
# a specific release, into a specific folder
curl -fsSL https://worxbend.github.io/obsctl/install.sh | sh -s -- --version v0.6.0 --dir /usr/local/bin

# same thing with environment variables
OBSCTL_VERSION=v0.6.0 OBSCTL_INSTALL_DIR=/usr/local/bin sh install.sh

The installer is also attached to every release, so this URL works too: https://github.com/worxbend/obsctl/releases/latest/download/install.sh

By hand: download a tarball from the Releases page, check it against SHA256SUMS.txt, unpack, and put obsctl on your PATH.

From source (needs Crystal 1.21+ and Shards):

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

Check it worked:

obsctl --version

Nothing happened? ~/.local/bin probably isn't on your PATH. Add this to your ~/.bashrc or ~/.zshrc:

export PATH="$HOME/.local/bin:$PATH"

🐣 Your first five minutes

Never touched obs-websocket before? Start here. Five steps, nothing skipped.

1️⃣ Turn on the OBS WebSocket server

In OBS Studio: Tools β–Έ WebSocket Server Settings

  • β˜‘οΈ Tick Enable WebSocket server
  • πŸ”Œ Leave the port at 4455 (that's what obsctl expects)
  • πŸ”‘ Click Show Connect Info and copy the password β€” or untick Enable Authentication if this machine is only yours
  • πŸ’Ύ Hit Apply, then OK

Don't see the menu item? You're on OBS older than 28. Update OBS β€” 28 and up ship obs-websocket 5 built in.

2️⃣ Create a config file

obsctl init

That writes ~/.config/obsctl/config.yml with sensible defaults. You don't have to edit it yet.

3️⃣ Hand over the password

obsctl never stores your password in a file. Put it in the environment:

export OBS_WEBSOCKET_PASSWORD='the password you copied'

Put that line in your ~/.bashrc / ~/.zshrc so it sticks around. πŸ”’ No authentication? Skip this β€” obsctl connects with an empty password.

4️⃣ Start the daemon

In a terminal, leave this running:

obsctl server --headless

You should see a log line like obs_connected connected to OBS 31.0.0. πŸŽ‰ (Later, make it automatic: obsctl service install && obsctl service start.)

5️⃣ Open the dashboard

In a second terminal:

obsctl

That's it β€” you're in. Now play:

Press this And…
j / k move down / up the list
Enter switch to that scene 🎬
a then ← β†’ pick the audio panel, walk the channels 🎚️
↑ ↓ on a channel ride its gain πŸ”Š
m mute / unmute the selected input πŸ”‡
Space open the leader menu β€” it shows you every option ✨
: command line, e.g. :scene brb
F2 pick a prettier theme 🎨
q quit

[!NOTE] Nothing on screen or an error? Jump to Something broke? β€” it's almost always one of three things.


πŸ“Έ The tour

Press Space and obsctl tells you what's next β€” the which-key menu, exactly like AstroNvim.

The leader menu open, listing find, mute, obs, quit and ui groups

Type : for a command line, with completion for every scene and input you have.

The command line open with :scene and four completions

29 built-in themes, previewed live before you commit. F2 or <leader>ut.

The appearance lab, previewing a theme

🎚️ A mixing desk, not a list

Every audio input is a vertical channel strip, the way OBS' own vertical mixer lays them out:

β”œ β£Ύ  Audio Matrix  03  [a] Mic/Aux (mic) Β· ←/β†’ chan ↑/↓ gain m mute ──
┃  mic    desktop   guest                                            ┃
┃ β–”β–”β–” β”‚    β–‚β–‚β–‚ β”‚    β–‘Β·β–‘ β”‚        ← peak hold, meter, fader           ┃
┃ β–ˆβ–ˆβ–ˆ ━    β–ˆβ–ˆβ–ˆ β”‚    β–‘Β·β–‘ ━                                            ┃
┃ β–ˆβ–ˆβ–ˆ ┃    β–ˆβ–ˆβ–ˆ ━    ░●░ ┃                                            ┃
┃-4.2 dB  -12.4 dB    β€“βˆž                                             ┃
┃  72%      48%      60%                                             ┃
┃ β™ͺ LIVE   β™ͺ LIVE   βœ– MUTE                                           ┃

The meter fills bottom-up in eighth-height blocks, so it reads eight times finer than the rows it has, and each cell is coloured by the level it stands for β€” green up to βˆ’20 dBFS, yellow to βˆ’9, red above it. The bar over the top is the peak hold: it jumps to your loudest moment and slides back down on its own. A channel that clips flashes its name; a channel that has never sent a reading shows a searching dot, so silent and not there don't look alike. πŸŸ’πŸŸ‘πŸ”΄

Short on rows? The panel drops the dB readout first, then the gain, then the mute button. The meters are the last thing to go.

πŸ“Š Stream health, while you're live

When the stream starts, a Stats panel opens next to the logs:

β”œ πŸ“Š  Stats  352   dropped frames ───────────────
β”‚ ⚑ 59.94 fps  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‡β–ˆβ–ˆ  ⏱ 1.42 ms             β”‚
β”‚ β–Έ RENDER missed     12 / 128,400   0.01%      β”‚
β”‚ β–Έ OUTPUT skipped   340 / 128,000   0.27%      β”‚
β”‚ β—† HEALTH         nominal  Β· budget 9%         β”‚

Frames missed to rendering lag, frames skipped to encoding lag, and how much of each frame's budget you're burning. Green is fine, yellow means look at it, red means your viewers are already seeing it. πŸŸ’πŸŸ‘πŸ”΄


🎹 Cheat sheet

The dashboard is keyed like AstroNvim: : is the command line, Space is the leader, and the motions are vim's.

Moving around

Key What happens
j k or ↑ ↓ Move inside the focused panel
gg / G Jump to the first / last item
Ctrl-D / Ctrl-U Half a panel down / up
Ctrl-W + h j k l Move between panels (also plain Ctrl-h/j/k/l)
Enter Activate β€” switch scene, profile, or collection βœ…
m Mute / unmute the selected input πŸ”‡
q or Ctrl-C Quit πŸ‘‹

On the audio matrix the axes follow the desk, not the list β€” the strips stand side by side, so the keys do too:

Key What happens
← β†’ or h l Move to the previous / next channel 🎚️
↑ ↓ or k j Gain up / down on that channel πŸ”Š
m Mute / unmute it πŸ”‡

The leader menu (Space)

Press Space and a menu appears showing every next key. No memorising.

Keys What it does
<leader>f β†’ s a p c πŸ” Find: scenes, audio, profiles, collections
<leader>u β†’ t 🎨 UI: theme picker
<leader>o β†’ r d c πŸ”§ OBS: reload config, dump config, reconnect
<leader>m πŸ”‡ Toggle mute
<leader>q πŸ‘‹ Quit

Esc (or any key that leads nowhere) closes it. The old single letters β€” s a p c r D R β€” still work too.

The command line (:)

Key What it does
: or / Open it
Tab / Shift-Tab or Ctrl-N / Ctrl-P Cycle completions
Ctrl-U / Ctrl-W Clear the line / delete a word
Enter Run it
Esc Never mind
:scene "Main Camera"      :vol "Desktop Audio" 65
:mute Mic/Aux             :profile Streaming
:stream                   :collection Gaming
:rec start                :status
:q                        :h

Yes, :q, :qa, :wq and :x all quit. :h lists everything. πŸ˜„

πŸ–±οΈ Mouse works too

Do this Get that
Click a row Focus the panel, select the row
Click it again Activate it (two steps, so a stray click can't cut your program scene πŸ˜…)
Click a channel's MUTE button Mute / unmute
Wheel over a channel strip Volume up / down, like a real mixer 🎚️
Shift + wheel over audio Scroll through the channels instead
Click a which-key entry Run it, or open its group
Click a completion chip Put it on the command line
Click a theme Preview it; click again to keep it

πŸ€– Scripting

Everything the dashboard does, the CLI does β€” and it's built to be called from scripts, hotkeys, and bots.

# Scenes and audio
obsctl scene brb                    # aliases and shortcuts work
obsctl mute mic
obsctl volume "Desktop Audio" 70

# Outputs
obsctl stream                       # toggle streaming
obsctl record start                 # start | stop | toggle | pause | resume | status

# What's going on?
obsctl status
obsctl doctor                       # checks everything, tells you how to fix it 🩺

# Live event feed, one JSON object per line
obsctl watch --topics state | jq -r '.data.current_scene'

# Tab completion for your shell
obsctl completions zsh > ~/.zfunc/_obsctl

πŸ“¦ JSON for robots

Add --json to any scriptable command and get exactly one envelope on stdout:

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

Exit codes are stable, so if obsctl scene main; then … just works:

Code Meaning
0 🟒 Did what you asked
1 πŸ”΄ Failed
2 βš™οΈ Config problem
3 πŸ”Œ Daemon or OBS unreachable
4 πŸ“‘ OBS refused the request
5 ✍️ Couldn't parse the command
6 πŸ”— IPC problem
πŸ”¬ Behaviour that matters inside scripts
  • Colour is on only when stdout is a terminal. NO_COLOR and --color=never turn it off; --color=always forces it on.
  • -q / --quiet drops the human message; the exit code is the signal.
  • --timeout SECONDS bounds a single daemon round trip.
  • obsctl watch | head -5 ends cleanly with exit 0.
  • Names go to the daemon exactly as your shell delivers them, so quotes and backslashes are fine: obsctl scene 'Camera "A"'.

Full grammar, every JSON command, and the error codes: docs/commands.md.


🎨 Make it yours

~/.config/obsctl/config.yml β€” short, human, and validated:

version: 1

connection:
  host: 127.0.0.1
  port: 4455
  password_env: OBS_WEBSOCKET_PASSWORD   # πŸ”’ the name of the variable, never the password

reconnect:
  enabled: true
  endless: true

ui:
  theme: ember          # 29 to choose from, or roll your own colours
  advanced_ui: true     # false = plain ASCII, no fancy glyphs
  show_icons: true
  locale: en            # en | uk

scenes:
  - name: Main Camera
    alias: main         # so you can type `obsctl scene main`
    shortcut: "1"

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

πŸ’‘ Shortcut: run obsctl dump-config once you're connected and obsctl fills in your real scene and input names for you. It keeps your settings and makes a backup.

Handy commands: obsctl config explain (where every setting came from) Β· obsctl config diff (what you changed) Β· obsctl validate-config (is it sane?). Full reference: docs/config.md.

πŸ” Keep the daemon running

Don't babysit a terminal β€” let systemd do it:

obsctl service install
obsctl service start
obsctl service status

No root needed; it's a --user unit at ~/.config/systemd/user/obsctl.service. stop, restart, and uninstall do what you'd expect.


🩺 Something broke?

Start here β€” it fixes most things:

obsctl doctor

It checks your config, credentials, socket, daemon, OBS connection, and service unit, and prints a fix for anything it doesn't like.

Symptom Usually means Fix
😴 server unavailable (exit 3) The daemon isn't running obsctl server --headless or obsctl service start
πŸ”‘ authentication failed Wrong or missing password Re-copy it from Tools β–Έ WebSocket Server Settings, export OBS_WEBSOCKET_PASSWORD=…
πŸ”Œ connection refused WebSocket server is off, or wrong port Enable it in OBS; check connection.port matches (4455)
🀷 unknown scene Name doesn't match OBS obsctl status to see real names, or obsctl dump-config
🧟 OBS was closed when it started Nothing β€” it retries obsctl reconnect to hurry it up
πŸ”£ Boxes instead of icons Font has no glyphs Install a Nerd Font, or set ui.advanced_ui: false

Still stuck? Turn up the volume and read along:

obsctl --log-level debug server --headless

Logs also land in ~/.local/state/obsctl/obsctl.log. Passwords are redacted from both, so they're safe to paste into an issue. πŸ”


🧠 How it works

One process owns the OBS connection. Everyone else asks it nicely.

flowchart LR
    OBS["πŸŽ₯ OBS Studio<br/>obs-websocket 5.x"]
    D["🧠 obsctl server<br/>the only connection owner"]
    T["πŸŽ›οΈ dashboard"]
    C["βš™οΈ CLI / scripts"]
    S["πŸ› οΈ systemd --user"]

    OBS <-->|WebSocket| D
    D <-->|Unix socket| T
    D <-->|Unix socket| C
    S -. keeps alive .-> D

Why bother? Because it means πŸ”Œ exactly one WebSocket per session Β· πŸ”„ every client sees the same state instantly Β· 🧯 reconnect logic lives in one place Β· ⚑ CLI commands don't pay for a handshake every time.

The socket lives in $XDG_RUNTIME_DIR/obsctl/ (falling back to /tmp/obsctl-$UID/). Nothing listens on the network β€” this is a local tool.

Under the hood it's Crystal, and the TUI is drawn with CryTUI, an in-tree immediate-mode library inspired by Ratatui, tested against memory, ANSI, and real PTY backends.


πŸ“š More docs

πŸ“– What's inside
Website The pretty version of this page (and where the installer lives)
Commands Every command, flag, JSON envelope, and error code
Configuration Every setting, with defaults and examples
Protocol IPC framing, topics, and the compatibility fixtures
CryTUI notes How the rendering layer came to be
Contributing Setup, the four build gates, project layout
Changelog What changed, and why
Security Reporting holes, and how credentials are handled
More worxbend tools The sibling streaming utilities 🧰

🀝 Contributing

Bugs, docs, ideas, code β€” all welcome. πŸ™Œ

shards install
make check      # format + lint + build + test, the same four gates CI runs

The test suite is deterministic and doesn't need OBS running β€” there's a fake obs-websocket server, real Unix sockets, and real PTYs in the repo. Add a regression test at the narrowest layer that catches your bug, and you're golden.

Changed a widget or a theme? Redraw the pictures:

make readme-shots     # the screenshots above
make site-frames      # the website's terminal frames

Filing an issue? Include your OBS and obs-websocket versions, your terminal and $TERM, what you ran, what you expected, and what you got. For rendering issues, terminal size and font help a lot. Never paste passwords or auth strings β€” and for security holes, use SECURITY.md instead of a public issue. πŸ”’


πŸ“œ License

MIT. Do what you like with it.


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

If obsctl saves your stream even once, a ⭐ would be lovely.

Repository

obsctl

Owner
Statistic
  • 2
  • 0
  • 2
  • 0
  • 2
  • 1 day ago
  • November 24, 2019
License

MIT License

Links
Synced at

Fri, 07 Aug 2026 19:49:27 GMT

Languages