obsctl v0.8.4

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

An obsctl session: the splash, the dashboard, scene switching, the audio matrix with its vertical channel strips, the command palette, and live stream stats

๐Ÿ‘† An unedited session, sped up 2ร—. The same recording plays as sharp, scrubbable text on the website โ€” or locally with asciinema play docs/demo/obsctl.cast.


๐Ÿค” 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 beacon in the header corner animates differently for idle, recording, on air, and both โ€” so you can tell what OBS is doing at a glance.
  • ๐Ÿค– 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 reads the password from an environment variable โ€” OBS_WEBSOCKET_PASSWORD โ€” and never writes it anywhere itself. ๐Ÿ”’

For right now, type it into this shell only. read -rs means "read a line without printing it", so the password never appears on screen and never lands in your shell history:

printf 'OBS WebSocket password: '
read -rs OBS_WEBSOCKET_PASSWORD
echo
export OBS_WEBSOCKET_PASSWORD

That lasts until you close the terminal, which is exactly what you want while you're finding your feet. No authentication turned on? Skip this entirely โ€” obsctl connects with an empty password.

[!WARNING] Don't put that export line in ~/.bashrc or ~/.zshrc. Shell startup files are usually world-readable (mode 644), they very often end up in a public dotfiles repository, and a variable exported there is handed to every program you launch from that shell โ€” not just obsctl. A password that only the daemon needs shouldn't be sitting in the environment of your text editor.

๐Ÿ” Making it stick โ€” two ways that don't leak it

Ask a password manager at launch. Nothing is stored in plaintext at all; the secret exists only in the daemon's own environment, for as long as it runs:

# pass (https://www.passwordstore.org/)
OBS_WEBSOCKET_PASSWORD="$(pass show obs/websocket)" obsctl server --headless

# GNOME Keyring / any libsecret store
OBS_WEBSOCKET_PASSWORD="$(secret-tool lookup service obs-websocket)" obsctl server --headless

Or give systemd a private env file, if you want the daemon started for you by obsctl service install. The file is readable only by you, and only the service reads it:

# 1. write the password into a file only you can read
mkdir -p ~/.config/obsctl
touch ~/.config/obsctl/obsctl.env
chmod 600 ~/.config/obsctl/obsctl.env
printf 'OBS WebSocket password: '
read -rs pw
echo
printf 'OBS_WEBSOCKET_PASSWORD=%s\n' "$pw" > ~/.config/obsctl/obsctl.env
unset pw

# 2. tell the service unit to read it
mkdir -p ~/.config/systemd/user/obsctl.service.d
cat > ~/.config/systemd/user/obsctl.service.d/password.conf <<'CONF'
[Service]
EnvironmentFile=%h/.config/obsctl/obsctl.env
CONF
systemctl --user daemon-reload
systemctl --user restart obsctl

chmod 600 is the part that matters: it means "only my user account can read this file". A drop-in under obsctl.service.d/ survives obsctl service install rewriting the unit, so you only do this once.

One quirk of systemd's env-file format: a value that starts with " or ' is treated as quoted and the matching quote is stripped. Spaces anywhere else are fine. If your password begins with a quote character, wrap the whole value in single quotes.

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 then s jump straight to any scene by its number ๐ŸŽฌ
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

Everything below happens in the recording at the top of this page โ€” it's the whole tour, and it's deliberately the only showcase here. A screenshot goes stale the moment a panel is redrawn and nothing catches it; a recording gets re-made from the code it documents. ๐ŸŽฌ

  • 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.
  • 41 built-in themes, previewed live before you commit. F2 or <leader>ut. Twelve of them are vivid: near-black grounds under a gradient that swings across the colour wheel โ€” violet into toxic green, orange into gold, cyan into hot pink. The website will repaint itself in eight of them if you click a swatch.

๐ŸŽš๏ธ 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>s ๐ŸŽฌ Switch scene โ€” the quick picker, see below
<leader>p ๐Ÿ—‚ Scene profiles โ€” pick one, switch, rename, delete
<leader>P ๐Ÿ—‚ Edit profile โ€” choose which scenes it hides
<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.

๐ŸŽฌ Switch scenes in two keys (<leader>s)

Press Space then s. Every scene appears with one key next to it โ€” and that key switches to it. No cursor, no Enter.

โ•ญโ”€ ๐ŸŽฌ  Switch scene  03  โ†‘โ†“ move  โ†ต switch  esc close โ”€โ•ฎ
โ”‚ 1  Main Camera                                       โ”‚
โ”‚ 2  Screen Share                                      โ”‚
โ”‚ 3  BRB                                               โ”‚
โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ

Space s 2 and you're on Screen Share. Three keystrokes, mid-stream.

Key What it does
1โ€“9 then aโ€“z Switch to that scene, immediately
โ†‘ โ†“ Move the cursor (for scene 36 and beyond, which run out of keys)
Enter Switch to the one under the cursor
Esc Never mind
Click a row Switch to it

Two things worth knowing:

  • The picker takes every key while it's open. q doesn't quit and m doesn't mute โ€” they're scene labels in here. Only Esc leaves. A key that isn't a label does nothing at all, so a typo costs you a keystroke and not your place.
  • Your configured shortcuts win. If a scene has shortcut: b in your config, it keeps b here, and the automatic labels work around it. The key the scenes panel shows you as [b] is the key that works. Shortcuts longer than one character (brb) still work on the command line and in the CLI โ€” they just can't be a single press.

๐Ÿ—‚ Hide the scenes you never switch to (<leader>P)

Scene lists grow. Half of a real OBS collection is scenes that exist only to be nested inside another scene โ€” a lower third, a looping background, a camera group. They are scenes as far as OBS is concerned, but you never switch to one, and every time you scroll past them you're scrolling past furniture.

A scene profile is a name plus the scenes to keep out of the list. It is obsctl's own idea, saved in your obsctl config file โ€” not an OBS profile and not an OBS scene collection. Those two are OBS's, the dashboard shows them in their own panels, and a profile never touches either.

The daemon owns them, so a profile you switch on here also applies to obsctl scene-profile on the command line and to a second dashboard, without either having to reload.

Press Space then P. Every scene shows up, including the hidden ones โ€” this is the one place that lists them, because it's the one place that can bring them back. Hidden scenes are dimmed and carry a hollow โ—‹ instead of a filled โ—:

โ•ญโ”€ ๐Ÿ—‚  Edit profile  streaming โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
โ”‚ โ—  Main Camera                                        โ”‚
โ”‚ โ—  Screen Share                                       โ”‚
โ”‚ โ—‹  Utility Cam                                        โ”‚
โ”‚ โ—‹  BRB Loop                                           โ”‚
โ”‚ t hide ยท n name ยท โ‡ฅ load profile ยท โ†ต save ยท esc close  โ”‚
โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ

The bottom row is every key the editor answers to, including the one that loads a profile you've already saved into the draft โ€” the title is where the profile's name goes, and a name of any length would have pushed the keys off the end of it.

Key What it does
t or Space Hide / unhide the scene under the cursor
j k or โ†‘ โ†“ Move the cursor
n Name the profile you're building
Tab / Shift-Tab Load the next / previous profile you've already saved
Enter Save it โœ…
Esc Leave without saving
Click a row Toggle it

Three things worth knowing:

  • Nothing is saved until Enter. Toggling scenes builds up a draft that lives in the dashboard; Enter sends it to the daemon, which writes it to your config file, and Esc throws it away. Loading another profile with Tab replaces the draft too, and says so in the title.
  • Saving doesn't switch to it. The editor lets you work on a profile you're not currently using, and having the scene list change under you would be a surprise. <leader>p is how you put one into effect.
  • Renaming a profile renames it. If you load streaming with Tab, press n and type podcast, saving moves that profile to the new name and leaves it where it was in the list โ€” you get one profile called podcast, not two. A name another profile already has is refused, because two profiles can't share one.
  • A profile changes which scenes are drawn, not which exist. OBS still has every scene and obsctl scene "Utility Cam" still switches to a hidden one from the command line. Wherever a scene list is drawn a hidden scene is hidden โ€” the <leader>s picker and the palette's completions both go by the list you can see โ€” so <leader>p 0 is how you get it back on screen. Hiding is a view, not a lock.

๐Ÿ—‚ Pick a profile, or put every scene back (<leader>p)

Building a profile is one key; living with them is the other. Press Space then a lower-case p and every profile you've saved is on screen, with the one in effect marked โ–ถ and the number of scenes it hides beside its name. The top row isn't a profile at all โ€” it's the way back to the full list:

โ•ญโ”€ ๐Ÿ—‚  Scene profiles  03   โ†ต use ยท esc close โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
โ”‚ 0  โ—‡  Show every scene                                   โ”‚
โ”‚ 1  โ–ถ  streaming ยท hides 2                                โ”‚
โ”‚ 2  โ—‡  editing ยท hides 0                                  โ”‚
โ”‚ โ†ต use ยท e edit ยท r rename ยท n new ยท d delete ยท esc close โ”‚
โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ

Press 1 and the scene list is streaming's. Press 0 and every scene is back. The row numbers are the keys, so nothing has to be remembered.

Key What it does
0 Show every scene again โ€” no profile in effect
1โ€“9 Put that profile into effect, immediately
โ†‘ โ†“ or j k Move the cursor (for a tenth profile, which runs out of digits)
Enter Use the one under the cursor
e Open it in the editor, to change which scenes it hides
r Open it in the editor with the name field up, to rename it
n Start a new profile from scratch
d then d Delete it โ€” the first d asks, the second does it
Esc or q Never mind
Click a row Use it

Three things worth knowing:

  • d asks before it deletes. The first press turns the bottom row into delete โ€œstreamingโ€? ยท d confirm ยท esc cancel, naming the profile you're about to lose. The second d does it; any other key calls it off, Enter included โ€” it means "use this row" everywhere else in the list, so it isn't allowed to be the press that destroys a profile โ€” as does a stray click. Deleting the profile that was in effect puts every scene back rather than leaving the list filtered by something that's gone.
  • This one takes effect as you go. Unlike the editor, where nothing is sent until Enter, activating a row or confirming a delete asks the daemon straight away โ€” so the profile you were using is still the profile you're using after a restart, and the CLI agrees. If the daemon refuses, the status line says why.
  • You can always see which one is on. The scenes panel title reads Scenes ยท streaming instead of Scenes, and the header adds ๐Ÿ—‚ scene profile: streaming next to the OBS profile. A short scene list is never a mystery.

The saved shape lives under the top-level scene_profiles key; docs/config.md describes it field by field, and editing it by hand is as supported as editing it from the dashboard. From a shell, obsctl scene-profile lists them, obsctl scene-profile <name> switches, and obsctl scene-profile --off puts every scene back โ€” see docs/commands.md.

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 row in the scene picker Switch to that scene; click outside to close
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          # 41 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 and hand it over again โ€” see step 3 of the first-run walkthrough
๐Ÿ”Œ 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
Demo recording How the cast above is recorded, trimmed, and turned into a GIF
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 theme? Re-export the palettes the website styles itself from (CI fails if the committed copy is stale):

make site-themes      # site/themes.js, read from the real theme table

Changed the dashboard enough that the demo no longer matches? Re-record it โ€” docs/demo/README.md has the exact commands, including how to trim the recording and regenerate the GIF above. ๐ŸŽฌ

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
  • 3
  • 0
  • 1
  • 0
  • 1
  • about 7 hours ago
  • November 24, 2019
License

MIT License

Links
Synced at

Mon, 31 Aug 2026 21:32:45 GMT

Languages