Getting started
Install it, let macOS ask its questions, find the token, and record one take two ways — from the popover and from a shell.
Rheocles is a menu bar app that bundles a headless daemon, rheocles-core. The
app is a thin client of the daemon over the same API you get. Launch the app
and the daemon comes up on localhost:7447; quit the app and it stays until
nothing is talking to it.
Install
brew install --cask artisan-build/tap/rheoclesOr download the disk image — every release is on the releases page with a zip and checksums beside it. The app, every nested binary and the disk image are Developer ID signed, notarized and stapled; Gatekeeper opens it without a right-click.
Requirements. Apple Silicon, macOS 15 Sequoia or later. The floor is
Sequoia because that is where SCRecordingOutput and process-level Core
Audio taps became dependable, and Rheocles uses both.
Permissions
macOS asks for each device class the first time it is needed, and Rheocles does not ask ahead of that — with one exception it cannot avoid.
| macOS asks for | when | where it lives afterwards | |
|---|---|---|---|
| a camera | Camera | the first arm | System Settings → Privacy & Security → Camera |
| a microphone | Microphone | the first arm | … → Microphone |
| system audio | System Audio Recording | the first arm | … → Screen & System Audio Recording |
| displays and windows | Screen Recording | the first GET /streams — opening the popover |
… → Screen & System Audio Recording |
The exception is Screen Recording: without it there are no displays to list, so nothing could ever be armed to trigger the prompt. The first stream list raises it once, and the grant takes effect on the daemon’s next launch — quit the app and open it again, and the displays are there.
The token
Every request needs a bearer token, on every transport. You do not have to go and get it. It is written to
~/Library/Application Support/Rheocles/tokenwith mode 0600, so any app running as you can read it and is paired with
zero clicks. That is how Pteroprompter and the NativePHP app pair. Anything
that cannot read a file gets the same token as a pairing code in the popover’s
settings.
Rotating it — the button beside the code, or POST /token/rotate —
writes a new token to the file and refuses the old one from the very next
request, on both transports. Every client re-reads the file; a WebSocket
that is already up must send { "auth" } again before its next command.
Rotate it if the code was ever shown somewhere it should not have been.
TOKEN="$(cat ~/Library/Application\ Support/Rheocles/token)"curl -s -H "Authorization: Bearer $TOKEN" localhost:7447/{ "name": "Rheocles", "version": "0.1.0", "hostname": "studio.local", "machineId": "…", "outputRoot": "/Users/len/Movies/Rheocles", "freeBytes": 812345678912, "auth": "bearer", "ports": { "http": 7447, "ws": 7448 } }Everything Rheocles ever tells you about a file is a path relative to
outputRoot — the take folder to the root, each file to the take folder.
Store them as given; join them only if you need an absolute path. The root
and the codec are the daemon’s two settings, GET and PATCH /settings,
persisted beside the token in settings.json; the root cannot move while a
take is active.
The first take, from the popover
- Click the mark in the menu bar. Every stream on the machine is listed — cameras, microphones, displays, system audio. Windows are hidden until Show windows is on in settings.
- Toggle arm on the ones you want. The row changes to ochre and the menu bar mark draws in outline: the device is live and costing CPU.
- Press Record. Every armed stream starts writing. The mark fills, the elapsed clock runs.
- Press Stop. The take folder under the output root has one file per
stream and a
manifest.jsonthat describes them.
The first take, from a shell
The popover’s Record button is the one-click form, POST /record. The
two-step form is what a client uses when it needs to know where the files will
be before frame one:
H="Authorization: Bearer $(cat ~/Library/Application\ Support/Rheocles/token)"B=localhost:7447
curl -s -H "$H" $B/streams # ids and armed statecurl -s -H "$H" -X POST $B/streams/camera:0x2300000fd9009c/arm -d '{"armed":true}'
curl -s -H "$H" -X POST $B/takes -d '{"name":"Episode 12"}' # → the manifest: id, paths; not recordingT=$B/takes/20260912T040433-fd9qcurl -s -H "$H" -X POST $T/start # the cuecurl -s -H "$H" -X POST $T/stopcurl -s -H "$H" $T # the manifest