obsctl v0.7.0
π‘ 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
ποΈ Control Β· π Watch Β· π€ Automate Β· π Stay connected
Website Β· Install Β· First 5 minutes Β· Cheat sheet Β· Scripting Β· Docs
π 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 π):
- π₯ Grabs the right build for your CPU (
amd64orarm64) - π Checks it against the release's
SHA256SUMS.txtβ if the hash is wrong, nothing gets installed - π Drops the binary in
~/.local/bin(or/usr/local/binif you're root) - β Runs it once to make sure it works
[!TIP] Nervous about piping a script into a shell? Good instinct. It's
install.shright 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.
Type : for a command line, with completion for every scene and input you have.
29 built-in themes, previewed live before you commit. F2 or <leader>ut.
ποΈ 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_COLORand--color=neverturn it off;--color=alwaysforces it on. -q/--quietdrops the human message; the exit code is the signal.--timeout SECONDSbounds a single daemon round trip.obsctl watch | head -5ends cleanly with exit0.- 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.
obsctl
- 2
- 0
- 2
- 0
- 2
- 1 day ago
- November 24, 2019
MIT License
Fri, 07 Aug 2026 19:49:27 GMT