Meeting Transcriber watches for meetings on its own, but sometimes you want a physical control: a Stream Deck key to arm it before a call, a hotkey to stop it when you switch to something private, a menu bar of your own.
This guide wires that up. Everything here goes through /v1/watch in the
local automation API — one endpoint that reports whether the
watch loop is running and turns it on or off.
Homebrew build only. The automation API is compiled out of the App Store build, which is sandboxed and cannot host a local server.
Turn the API on: Settings → Advanced → Local Automation API. It is off by
default and binds to 127.0.0.1 only.
Enabling it writes a bearer token to
~/Library/Application Support/MeetingTranscriber/.rpc-token (mode 0600).
Everything below reads that file at run time rather than copying the value, so
nothing breaks when the token rotates — which it does every time you switch the
toggle off and back on.
Meeting Transcriber for Stream Deck
is a plugin that polls /v1/watch and repaints the key, so it shows whether
watching is on even when it was turned on from the app. Three actions — toggle,
start, stop.
Download the latest .streamDeckPlugin
and double-click it. The actions then appear under Meeting Transcriber in
the Stream Deck app. It reads the token file itself, so there is no pairing
step and nothing to configure.
Worth knowing before you do: that token is not scoped to watching. It is the
single bearer token for the whole automation server — all of /v1, the
/action/* UI-driving surface and /screenshot, which can capture the app's
windows. Anything that can read the file can do all of it. The plugin helping
itself to the token is the reason it needs no setup, and it is also the thing to
weigh before installing.
It is a separate project rather than part of this repo: it ships on Elgato's release cadence, not this app's, and nobody without a Stream Deck should have to download it.
If you would rather not install a plugin, the rest of this guide does the same job with a Shortcut — minus the live state.
A Stream Deck key that runs a Shortcut. No plugin, no extra software.
1. Build the Shortcut.
In Shortcuts.app, create a new Shortcut named Start Meeting Watching
containing a single Run Shell Script action (shell /bin/zsh) with:
TOKEN_FILE="$HOME/Library/Application Support/MeetingTranscriber/.rpc-token"
if [ ! -r "$TOKEN_FILE" ]; then
echo "Meeting Transcriber automation API is off. Enable it in Settings > Advanced." >&2
exit 1
fi
TOKEN=$(cat "$TOKEN_FILE")
curl -sS --fail-with-body -X POST http://127.0.0.1:9876/v1/watch \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"action":"start"}'The token is read at run time rather than baked in, so the Shortcut keeps working when the token rotates — which happens every time the API toggle is switched off and back on.
Then duplicate it as Stop Meeting Watching with "action":"stop". Two keys
rather than one is the recommendation of this guide, not an accident — see
Start and stop beat toggle. If you only have one
key to spare, swap the verb for toggle and accept the caveat described there.
Dropping -X POST and the -d line turns the same call into a status read.
2. Point a Stream Deck key at it.
Drag a System → Website action onto a key and set:
| Field | Value |
|---|---|
| URL | shortcuts://run-shortcut?name=Start%20Meeting%20Watching |
| Access in background | ticked |
Repeat for a second key pointing at Stop%20Meeting%20Watching.
Leaving "Access in background" unticked opens your browser on every press.
If you renamed the Shortcut, URL-encode the new name (spaces become %20).
3. Press it.
The first press asks macOS for permission to run the Shortcut; allow it. After that the key is silent and takes about a second.
The Shortcut is only a wrapper. Raycast, Alfred, BetterTouchTool, Keyboard
Maestro, Hammerspoon, a .zshrc alias — anything that can run a command can do
this directly:
TOKEN=$(cat "$HOME/Library/Application Support/MeetingTranscriber/.rpc-token")
curl -sS -X POST http://127.0.0.1:9876/v1/watch \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"action":"toggle"}'If you have the repo checked out, mt-cli is shorter and
finds the token itself:
mt-cli watch toggle
mt-cli watch statusEverything above wires a key to watching, which arms the detector and waits
for an app to hold a call. A meeting around a table has no app to detect, so it
has its own resource, /v1/record, which records the system microphone and no
app audio. Same recipe, one path changed:
TOKEN=$(cat "$HOME/Library/Application Support/MeetingTranscriber/.rpc-token")
curl -sS --fail-with-body -X POST http://127.0.0.1:9876/v1/record \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"action":"start"}'Or mt-cli record start / mt-cli record stop.
Two things differ from a watch key, and both matter for a button:
- Nothing stops it but you. A detected meeting ends when the call does; a
room does not, so the recording runs until a
stop(or the duration cap). This is the one place where having a second key really is worth it. 412means it is not recording and never started. Either "No Microphone (app audio only)" is on in Settings, or the microphone permission is denied. Unlike the other error codes this one does not clear by pressing again.
toggle is the obvious binding and the worst one, because a button press is a
wish, not a delta. Bind a key to toggle and its meaning depends on state you
cannot see: if watching stopped on its own — an error, a restart, a meeting that
ended — your "start" press stops nothing and you sit through the next call
unrecorded.
start and stop say what you want and are idempotent. Pressing start twice
is not an error; the second press returns the same 200 with watching: true.
Two keys, or one key per Stream Deck profile, will misfire less than one clever
one. Use toggle when you genuinely have only one key to spare.
Every call — GET and POST alike — returns the state after it, so a button
never has to guess or re-fetch:
{ "watching": true, "state": "watching", "badge": "inactive",
"manualRecording": false, "permissionsHealthy": true }watching is the on/off answer. badge is what the menu bar icon is showing
(inactive, recording, transcribing, diarizing, processing,
userAction, done, error, updateAvailable) — the field to render if you
want more than a binary light. permissionsHealthy is worth surfacing
somewhere: watching can be on while a missing permission means nothing is
actually being captured.
Full field reference: docs/automation-api.md.
The key cannot show you the current state. A Shortcut fires and forgets;
nothing polls, so the key's image never changes when the app's state does. The
Multi Action Switch action looks like a fix and is a trap — it alternates two
static images by counting presses, so it desynchronises the first time watching
starts or stops without you pressing anything, and then lies.
That is the whole reason the plugin above exists. If you only ever start watching by pressing the key, a Shortcut is fine. If the app also starts and stops on its own — which is the point of a watch loop — you want the key to be told.
| Symptom | Cause |
|---|---|
401 |
Token rotated, or the API was toggled off and on. Re-read the file — do not cache it. |
| Connection refused | App not running, or Settings → Advanced → Local Automation API is off. |
| Token file missing | Same — the file is written when the API starts. |
409 |
A recording already owns the loop: from the app picker, or a detected meeting. Stop it first. |
412 |
/v1/record only. "No Microphone" is set, or the mic permission is denied. Pressing again will not help. |
| Browser opens on every press | "Access in background" is unticked on the Website action. |
| Nothing happens, no error | The Shortcut name in the URL does not match, or is not URL-encoded. |
Watching turns on but nothing records? That is a permissions problem rather than
an API one — permissionsHealthy: false in the response, and the menu bar icon
carries a red badge. See the README's permissions section.