The control commands drive the one running Hex: start or stop a dictation or a
meeting, change any setting, switch models, open the window, quit, or read its
status. They are meant for agents and scripts as much as people, so every
command takes --json, is idempotent, and exits with a stable code.
One command tree (control::cli) is compiled on macOS, Linux, and Windows,
and one versioned protocol carries it on every OS through the running Hex's
local API: the owner-only discovery file (local-api.json) and the
bearer-authenticated POST /control (local_api).
On Linux and Windows one shared host (control::host) carries out the
commands through each OS's thin adapter (linux/app, windows/rune); macOS's
host is macos_rune::control. A command the running Hex can't do on its OS
answers unsupported at runtime (exit 1) instead of being missing from the
CLI; hex status --json lists the OS's capabilities. Today that is
playground stories (window story) outside macOS and the local model
tools on macOS.
On Windows a terminal runs hex as hex.com, a console program that runs
the app's hex.exe and waits for it, so commands print, return their exit
code, and can be captured like any other (docs/windows.md).
The read-only hex history and hex meetings list|show|chapters|search commands, and
hex mcp, read files rather than the running Hex; see docs/transcript-cli.md.
hex
├── status what Hex is doing (see Status below)
├── start open Hex if it isn't running (macOS Hex.app, else `hex app --hidden`); wait until it answers
├── quit [--force] refused while a meeting or dictation is active, or unconfirmed
├── restart [--force] quit and reopen (opens Hex when it isn't running)
├── dictation start|stop|toggle
│ ├── start --print (Labs, macOS) answer with the transcript when the recording ends
│ ├── stop --print print the transcript instead of pasting it (below)
│ └── start|toggle --raw paste the transcript as heard, skipping the Output steps
├── paste-last paste the last dictation (Option-Shift-V); refused while recording
├── typed-text Labs › Transform typed text (Linux): replace the focused field's text
├── transcribe <file> [--model <id>] [--language L] print an audio or video file's transcript
├── meetings
│ ├── record [--title T] as the menu bar's Record Meeting; needs meetings.enabled
│ ├── stop Stop & Transcribe
│ ├── copy-last copy the last meeting transcript to the clipboard
│ └── list | show | search read-only (docs/transcript-cli.md)
├── settings
│ ├── list every setting: key, value, kind, choices, writable, about
│ ├── get <key|prefix> one setting, or every key under a prefix (`meetings`)
│ └── set <key> <value>
├── model
│ ├── list every model, its languages and size, installed or not
│ ├── use <id> [--language L] installed models only; never downloads or repairs
│ ├── install [id] [--language L] download, then switch to it (default: the selected model)
│ ├── cancel stop a download or load in progress
│ ├── import <path> [--name N] Labs: check and copy in a GGUF; prints its custom-… id
│ ├── rename <id> <name> Labs: an import's picker name
│ ├── repair <id> Labs: rehash Hex's copy; ready again if it still matches
│ ├── remove <id> Labs: Hex's copy of an import (not the selected model)
│ └── status|devices|check|transcribe <wav> Linux and Windows, run in this process
├── commands
│ ├── init create ~/.config/hex (local, no running Hex needed)
│ └── check validate ~/.config/hex/hex.config.ts transformations
├── window
│ ├── show [settings|models|modes|history|meetings]
│ ├── hide close the window; Hex stays in the menu bar
│ └── story <story> [--variant V] [--play] the playground (Labs): open a story
├── onboarding reset (alias restart) replay the first run now; every OS (below)
├── history clear --yes delete every retained dictation
├── vocabulary the shared vocabulary (features/vocabulary.md)
│ ├── list [--json] every term and its "sounds like" spellings
│ ├── add <term> [--sounds-like a,b] add a term, or new spellings to an existing one
│ └── remove <term> any case
├── annotate (alias capture) Annotate (features/capture.md)
│ ├── start|stop|cancel|replay|status record a capture, end or discard it, save the last 15 s, or report
│ ├── list [--limit N] the newest captures in the captures folder
│ ├── show <capture> one capture's words, files, and where it went
│ ├── send <capture> [--to <session>] [--always] send it to an OpenCode session
│ └── route <capture> [--ask-jev] what routing sees and decides; sends nothing
├── screenshot [--pick] Labs (macOS): shoot the display under the pointer and print the PNG's path; --pick opens the ⌘⇧4 picker
├── labs [on|off <feature>] list Labs features or turn one on/off; edits settings.json (features/labs.md)
│ └── link <feature> macOS: print hex://labs/<feature>, the link that opens Hex's Labs page on that lab's group
├── check-for-updates check for an update now
├── launch-at-login [on|off] report or change Launch at Login (macOS: installed Hex.app only; Windows Run key; Linux XDG autostart)
├── completions <shell> Labs › Shell completions: print a completion script for hex
└── logs [--process] [-n N] [-f] tail live.ndjson (or process.log); no running Hex neededdictation start locks the recording, as a double tap would: dictation stop
or the next shortcut press finishes it, and dictation cancel discards it.
A CLI start has no press to judge, so its recording counts as
intentional at once: the start tone plays once the microphone is open, and the
recording behavior (mute or pause media, idle-sleep prevention) and the live
stream start as they do after a held shortcut. A stop replies done only
when the clip went to transcription; a clip with no audio yet (the microphone
was still opening) or a silent one is dropped with discarded, and nothing
shows as transcribing.
dictation stop --print stops the recording at once, exactly as stop
does, so a following start isn't blocked; only this command then waits, up
to 60 s, for the transcript and writes it to stdout instead of pasting it. The
text gets the same processing as a paste (global replacements, the mode's
corrections, OpenCode rewriting, and transformations). History keeps it as
printed, and Paste Last skips it. No speech (nothing recorded yet, a silent
clip, or an empty transcript) exits 4 with code no-speech and prints
nothing; with nothing recording it refuses with not-recording. A failure,
or the 60 s timeout prints nothing, exits 1, and never
falls back to pasting. --print with toggle or cancel is a usage error
(exit 2). The command is the same on Linux and Windows, where hex dictation
and hex paste-last reach the listener's own queue as its shortcut would;
Linux's hex typed-text does too.
dictation start --print (Labs › Print-only CLI dictation, off by default,
macOS) records a dictation that belongs to the command instead of the person's
focused app: the command blocks and answers only when the recording ends,
however it ends, with the transcript (transcript, exit 0) instead of pasting
or copying it. It starts exactly as start does and locks the recording; the
shortcut press that finishes it, dictation stop, dictation toggle, and a
microphone loss that keeps the audio all hand the text to that command, never
to whatever app has focus. A discarded or silent clip answers no-speech
(exit 4), a cancel (dictation cancel or Toggle's cancel shortcut) answers
failed (exit 1), and any other end, such as a microphone loss with no kept
audio or suspended shortcuts, answers failed too, so the command never
hangs. A printing start while anything already records is refused with
busy: it never takes over the person's own dictation or a programmatic one.
A plain dictation stop of a print-only recording answers as any plain stop
does (done, or discarded for no audio or silence) and the transcript still
goes to the start; stop --print of it answers done too, and both commands
wait for the one transcript. With the lab off, or on Linux
and Windows, it answers disabled or unsupported. The text gets the same
processing as a paste and History keeps it as printed, as stop --print
does; Paste Last skips it. A printing start waits at most 31 minutes; if it
gives up, the recording stays print-only and is never pasted. --raw is
allowed: the transcript comes back as heard. --print with toggle or
cancel is a usage error (exit 2).
dictation start --raw and toggle --raw start a dictation that pastes the
transcript as heard, skipping the Output pane's steps, as the Dictate without
processing shortcut (raw_dictation_hotkey) does. History keeps it as raw,
and Paste Last pastes it. --raw with stop is a usage error
(exit 2); stop --print of a raw dictation prints the transcript as heard.
dictation cancel discards the recording in any shortcut mode: nothing is
transcribed, pasted, or saved to History, and dictations already transcribing
finish. With nothing recording it answers unchanged. In Toggle mode the
cancel shortcut (cancel_hotkey, Escape by default) does the same; on Wayland,
bind hex dictation cancel to a key in the desktop instead (#786, #1022).
meetings cancel doesn't exist because Hex has no way to discard a
meeting recording.
onboarding reset (or onboarding restart) opens the window on the
first-run onboarding at once, as on a new install, for trying it again, on
macOS, Linux, and Windows. It removes only the onboarding markers (onboarding-complete, onboarding-step,
onboarding-relaunch) from the data folder. Settings, history, models, and
permissions are untouched, so the run finds them ready: the download passes at
once and the permission step is skipped. Labs' Run setup again row does the
same.
paste-last needs a listening Hex (not-ready during setup) and is refused
with busy while anything records or a shortcut gesture is in progress. The
menu bar's Paste Last Dictation does nothing in that case.
typed-text (Linux) runs the same job as Labs › Transform typed text's
shortcut, for desktops without the shortcuts portal. It needs a listening Hex
and the lab on (disabled when it is off), and refuses while Terminal paste
shortcut is on (disabled), because its Ctrl+V would become Ctrl+Shift+V. It
sends Ctrl+A and Ctrl+C, so it is not meant for terminals. While a capture
records or finishes, it answers done but the listener drops the job.
transcribe <file> prints a recording's transcript to stdout with the
selected dictation model, or the one --model names; --language picks a
language the model supports. It reads WAV, AIFF, CAF, FLAC, MP3, Ogg (Vorbis
and Opus), WebM/Matroska audio, and MP4/M4A/MOV audio (AAC-LC, HE-AAC, ALAC),
from audio files or a video's soundtrack, decoded in this process with no
external tool. The file is opened and its first audio decoded before anything
else runs, so a file Hex can't read is refused with unsupported. A
recording of any length is decoded and sent in pieces of at most 30 seconds,
so Hex holds about one piece rather than the whole file; the pieces' text is
joined with one space.
Each piece goes to the local API's POST /transcriptions with that model
named. With Hex running, that is the app's own API: a piece that short runs
on Hex's dictation engine when it already holds that model, so no second copy
loads and dictation can run between pieces. With Hex stopped, the command
starts its own hex service --embedded (the TypeScript SDK's direct child),
uses it for every piece, and ends it on every exit; it never publishes it or
starts a second service beside a running Hex. A model that isn't downloaded
is offered once, Download <model> (<size>)? [Y/n], only when stdin and
stderr are terminals and --json is off; yes downloads, verifies, and loads
it, then the piece is tried again. Otherwise the command answers
model-unavailable and names hex model install.
The service skips silence (a piece the speech detector hears nothing in, once
it is installed), and only a file with no speech at all answers no-speech
(exit 4). stdout carries only the transcript or the --json reply (the same
transcript status dictation stop --print uses); a progress line goes to
stderr only when it is a terminal. Ctrl-C ends the command and its service,
printing nothing, with exit 130. It never pastes, records History, runs mode
processing, or logs the transcript.
completions <shell> (Labs › Shell completions, off by default) prints a
completion script for hex to stdout and nothing else. The script is
generated at runtime from the same clap definitions every command parses, so
a new command or flag is in it without anyone writing the script out again;
bash, zsh, fish, elvish, and powershell are the shells.
hex completions bash > ~/.local/share/bash-completion/completions/hex
mkdir -p ~/.zfunc && hex completions zsh > ~/.zfunc/_hex
hex completions fish > ~/.config/fish/completions/hex.fishFor zsh, put ~/.zfunc on fpath before compinit runs, in ~/.zshrc:
fpath=(~/.zfunc $fpath). Elsewhere, write the script into a directory
already on fpath.
It is refused with disabled while the lab is off, and its Labs toggle is
what turns it on: hex labs on shell-completions. The command stays out of
hex --help; hex labs lists the toggle. The lab is read from
settings.json (AppSettings::read), as hex labs lists it, whether or not
Hex is running; a running Hex saves the toggle there at once.
model import, rename, repair, and remove run in this process on every
OS; hex models is an alias for hex model. An import's id is custom- and
the first 16 hex digits of its SHA-256, and every model command takes it. One
that is missing or changed answers model-unavailable with the reason.
model list also looks in the Hugging Face cache and says where each model
comes from (download, hex, imported, or found: owner/name@abcdef1); a
compatible file there that isn't a catalog model is listed as custom-<first 16 hex of its blob> (not tested by Hex), and model use with that id checks it
in full and uses it where it is. remove on a found file only forgets it
(own models).
model use switches only to a downloaded model and checks it on disk; it never
downloads or repairs one (model install does). It keeps that model's own
remembered language, as the menu bar does; --language
overrides only the language, and must be one the model supports.
vocabulary edits the shared vocabulary through the
running Hex, which saves settings and reloads them as a window edit would.
Terms match in any case; add of a term already listed only merges new
spellings, and replies changed: false when there are none.
quit and restart without --force are refused with busy when the
listener doesn't answer whether it's recording; no listener, or one waiting for
setup, counts as idle. On Linux a Hex its systemd unit runs restarts through
the unit (after writing this desktop's session.env); elsewhere restart
waits for the old Hex to drain and exit, then runs hex start.
On Linux, an older Hex an update left running (from before hex commands
moved to the local API) answers only on its service.sock. Every control
command and hex start then answer outdated (exit 1) instead of
not-running, and hex restart restarts its systemd unit into the installed
build and waits for it to answer (#915); one not run by the unit has to be
quit from its window or tray.
model install with Hex running downloads in the background, as Settings
does; with no Hex running it downloads here (on macOS through an embedded
service, as hex transcribe does) and then selects the model, printing progress (the source-install step hex model install). A Hex that starts while the download runs makes the switch instead.
model status, devices, check, and model transcribe are local tools on Linux
and Windows and take the selected model unless one is named; macOS answers
unsupported.
start and restart keep the HEX_* environment (HEX_RETAIN_DICTATION_AUDIO,
HEX_ENABLE_LLM, and so on): macOS opens apps without the caller's
environment, so Hex passes each HEX_* variable to open --env. start
takes them from the shell that runs it, restart from the running Hex. The
values are never logged.
annotate (or its old name capture) drives Annotate,
in every macOS, Linux, and Windows build; with capture.enabled off it refuses with
feature-off. On Linux, list, show, route, and send run in the
hex process itself and start, stop, and cancel are handed to the
running Hex's listener, as is status. On Windows every annotate command
goes to the running Hex. capture stop replies at once with the bundle
path while the words and files are written in the background; capture replay
(macOS 27+, Dev Lab annotate-replay on) exports the rolling 15 s
SCClipBufferingOutput display buffer to <bundle>/recording.mp4 and replies
with the bundle path while keyframes, clipboard copy, and routing finish on the
same queue (unsupported outside macOS 27+, feature-off while
labs.annotate_replay is off); capture status
lists bundles still writing and the last outcome (last: bundle, stage
of done, failed, or cancelled, and for a written bundle words, gaps,
and missing_transcript, why it has no words). A start or replay while two captures
are still writing is refused with busy. Every action is idempotent.
<capture> is a bundle name (1792933491953.scrawl, the extension
optional), a path, or latest. list, show, and route read the captures
folder and need only the experiment; send needs captures on and a listening
Hex. send with --to takes a session ID; for the newest capture it also
takes a name its route lists, and it acts as the capsule's pick, so the
capsule's Undo follows it. Without --to it goes where the capture's route
(its rule, or its first guess) or its last send says. --always, for the
newest capture only, also saves a rule sending its site or app there, as the
capsule's ⇧⏎ does. Its reply says what became of it (Sent to …,
Queued for … when OpenCode didn't answer, which Hex retries). route is
Scrawl's route --explain (--explain is accepted); --ask-jev asks Jev
again, one network call. An unknown capture is refused with not-found.
A CLI-started meeting is the same recording the menu bar starts: the menu-bar
state, the recording capsule, and Stop & Transcribe all apply to it.
meetings record replies once the recording has started (or after 8 s);
meetings stop replies once transcription has begun.
0 the command ran, or there was nothing to change (`changed: false`)
1 Hex refused or failed it (an error reply), or the transport failed
2 usage error (clap)
3 Hex isn't running (code `not-running`); control commands never launch it
4 `dictation start --print`, `dictation stop --print`, or `hex transcribe` heard no speech (code `no-speech`); nothing printed
130 `hex transcribe` or `hex model install` ended by Ctrl-C; nothing printedThe CLI reads the owner-only discovery file
~/Library/Application Support/hex/local-api.json (HEX_APPLICATION_SUPPORT_DIR
overrides the folder), checks that its pid is alive, and sends one
POST /control to 127.0.0.1:<port> with Authorization: Bearer <token>.
The route is always on in the macOS desktop app; the headless hex service
doesn't offer it. Linux and Windows Hex publish the same local API (the
discovery file lives in ~/.local/share/hex and %APPDATA%\hex) for
transcription and /streams, but answer /control with
404 control-unavailable until their hosts carry the commands.
- Request bodies are JSON up to 64 KiB; replies are at most 1 MiB.
- The app answers within 10 s (
504 control-timeoutotherwise); the CLI waits 15 s.dictation stop --printgets 65 s, and the CLI waits 70 s;dictation start --printgets 31 min 5 s, and the CLI waits 31 min 10 s. - A waiting
dictation start --printholds one of the local API's four connection workers until its recording ends or it hits that cap, even if the CLI that asked is killed: it answers a dropped receiver harmlessly, never pastes, and frees the worker only then. - Every answer the app gives is
200with aControlResponse, refusals included. Transport-level failures use HTTP status codes:400 invalid-control-command(malformed or unknown command),404 control-unavailable(no control host),413 control-too-large,503 control-busy(too many in flight) orcontrol-unavailable, and504 control-timeout. Bad or missing tokens get401like every other route.
hex transcribe uses that same owner-only discovery file and bearer token
(or, with Hex stopped, the endpoint and token its embedded service prints),
but sends the file one 30 s piece at a time, as 16 kHz mono WAV, to
POST /transcriptions?model=<id>&language=<code> with Content-Type: audio/wav. The route answers each piece's transcript
({"transcript": "…", "durationMs": …}) or a code such as
model-not-ready, unsupported-audio, queue-full, or resource-exhausted.
Requests are flat objects tagged by command (kebab-case):
{"version": 4, "command": "meeting-record", "title": "Design sync"}
{"version": 4, "command": "settings-set", "key": "sound_effects", "value": "off"}
{"version": 4, "command": "dictation", "action": "start"}
{"version": 4, "command": "dictation", "action": "start", "print": true}
{"version": 4, "command": "dictation", "action": "stop", "print": true}status | quit {force} | restart {force}
dictation {action, print?, raw?} action: start | stop | toggle | cancel;
print (start and stop) answers `transcript`
paste-last
typed-text
meeting-record {title?} | meeting-stop | meeting-copy-last
settings-get {key?} | settings-set {key, value}
model-list | model-use {model, language?} | model-install {model, language?} | model-cancel
window-show {pane?} | window-hide | window-story {story, variant?, play}
onboarding-reset
history-clear | check-for-updates | launch-at-login {enabled?}
vocabulary-list | vocabulary-add {term, sounds_like} | vocabulary-remove {term}
scrawl-capture {action} action: start | stop | cancel | status; `capture` experiment
scrawl-capture-list {limit?} | scrawl-capture-show {capture}
scrawl-capture-send {capture, to?, always?} | scrawl-capture-route {capture, ask_jev}Optional fields may be left out. hex start and hex logs are local and have
no wire form. history-clear has no confirmation field: the CLI asks for
--yes before it sends it. hex transcribe also has no /control wire form:
it sends the WAV itself to POST /transcriptions (see Transport above).
Replies are tagged by status:
{"version": 4, "status": "done", "message": "Recording “Design sync”; …", "changed": true}
{"version": 4, "status": "error", "code": "feature-off", "message": "Meetings are off; …"}
{"version": 4, "status": "settings", "settings": [{"key": "sound_effects", "value": true,
"kind": "bool", "writable": true, "about": "Play recording and error tones"}]}
{"version": 4, "status": "models", "models": [{"id": "parakeet_v3", "name": "…",
"languages": ["en", "…"], "size": "…", "installed": true, "selected": true,
"streams": true, "vocabulary": "bias", "supports_recognition_hints": false}]}
{"version": 4, "status": "vocabulary", "entries": [{"term": "OpenCode",
"sounds_like": ["open code"]}, {"term": "Rune"}]}
{"version": 4, "status": "scrawl-capture", "changed": true, "state": "idle",
"bundle": "…/captures/1792933491953.scrawl", "elapsed_ms": null,
"finishing": ["…/captures/1792933491953.scrawl"], "last": null}
{"version": 4, "status": "scrawl-captures", "captures": [{"id": "1792933491953.scrawl",
"bundle": "…", "created_at_ms": 1792933491953, "duration_ms": 4000, "mode": "window",
"app": "Brave", "words": 12, "sent_to": {"session": "ses_…", "title": "Hex"},
"queued": false}]}
{"version": 4, "status": "scrawl-capture-detail", "id": "…", "…": "the list's fields",
"window_title": "…", "transcript": "…", "note": null, "missing_transcript": null,
"marks": 2, "keyframes": 3, "video": "…/video.mp4", "audio": null,
"contact_sheet": "…/contact-sheet.jpg"}
{"version": 4, "status": "scrawl-capture-route", "explanation": "Capture …"}
{"version": 4, "status": "transcript", "text": "Ship it."}done.changed is false when the command found things already so ("Already
recording", "The window isn't open"). Setting kind is one of bool,
number, integer, choice (with choices), text, shortcut, path, or
json; value is null for an unset optional setting.
{
"version": 4, "status": "status",
"app_version": "2.3.1", "pid": 31708, "memory_footprint_bytes": 412000000,
"listener": {"running": true, "status": "Listening"},
"capture": {"state": "idle", "recording": null, "pending_jobs": 0},
"meeting": {"state": "recording", "title": "Design sync", "elapsed_seconds": 723},
"model": {"id": "parakeet_v3", "name": "…", "language": "en", "installed": true,
"preparing": null, "downloaded_bytes": 0, "error": null, "fallback": null},
"features": {"meetings": true, "release_microphone_while_idle": false,
"history_retention": "week"},
"permissions": {"ready": true, "microphone": "ready", "accessibility": "ready",
"transcription_model": true},
"window": {"open": false, "pane": null},
"update": "current",
"labs": ["live-text"]
}listener.status Enabled | Waiting for setup | No microphone | Stopped (running false)
listener.shortcuts Wayland portal only: [{"name": "Dictation", "trigger": "Super+D"}], the keys the desktop
granted (changed in its settings or not); trigger null when it refused one
capture.state idle | recording | transcribing | unknown (the listener didn't answer or stopped)
capture.recording dictation | service | null
meeting.state idle | starting | recording | transcribing
model the saved selection; installed is about that model
model.fallback {"id", "name", "language", "reason": "missing" | "changed" | "labs-off"}, the
model Hex runs while the saved one can't, or null
permissions.* ready | needs-request | needs-settings | needs-restart
window.pane settings | modes | history | meetings | …
update current | checking | available | unavailablememory_footprint_bytes is the physical footprint Activity Monitor shows.
permissions.transcription_model is about the model Hex runs, the fallback
included, so a missing import with a downloaded fallback is ready.
labs lists the Labs features turned on; older Hex omits it.
not-running Hex isn't running (CLI only; exit 3)
outdated Linux: an older Hex an update left running; run `hex restart` (CLI only)
transport the request couldn't reach Hex or its reply was unreadable (CLI only)
version-mismatch this hex speaks another control version; use the running app's hex
unknown-setting no such key (the message suggests a near match)
read-only the key can't be set here (the message says where)
invalid-value the value doesn't parse or is out of range
confirmation-required clears history; pass --yes
shortcut-in-use another Hex shortcut already uses that binding
not-found no such capture, or it has no scrawl.json yet
unknown-model no such model id (`hex model list`)
model-unavailable an imported model that is missing, changed, or behind its Labs toggle,
or one `hex transcribe` named that isn't downloaded or ready
not-installed `model use` on a model that isn't downloaded (`hex model install`)
feature-off the command needs an opt-in that is off (Meetings)
disabled a Labs toggle the command needs is off (the message names it)
not-ready Hex is waiting for setup (permissions or the dictation model)
meeting-active quit/restart while a meeting records (pass --force)
busy something is in progress (recording, transcribing, a full queue),
or the listener couldn't confirm it's idle
resource-exhausted a running Hex refused a `hex transcribe` piece as too large or too long
discarded `stop` dropped the clip: no audio yet, or silent; nothing transcribes
no-speech `dictation start --print` or `stop --print` heard no speech, or `hex transcribe`'s file had none; nothing printed (exit 4)
not-recording `dictation stop --print` with nothing recording
unavailable a part of Hex the command needs isn't available
unsupported this build or OS can't do it (e.g. restart from a development binary)
failed Hex tried and failed (the message has the cause)
internal reading or saving settings failedKeys are dotted paths into settings.json. hex settings list prints every
key with its kind and description; hex settings list --json adds choices.
Changes go through the same rules as the window and are saved by the app,
which then reloads them live; an open window adopts the change unless it has
an unsaved edit.
On macOS, Windows, and Linux on X11, keyboard_model_switching.enabled opts
into switching the shared speech-model picker on keyboard input-source changes.
The JSON setting keyboard_model_switching.mappings maps enabled input-source
IDs to a model and a language it supports, for example
{"com.apple.keylayout.Spanish":{"model":"parakeet_v3","language":"es"}}.
Source IDs are macOS input-source IDs, Windows keyboard layout handles
(04190419 for Russian), or X11 XKB layouts (ru, de(neo)). Omit a source
for "Don't switch"; Apple Speech can't be mapped. Automatic switches load
installed models only; they never download them. Enabling or editing the active
mapping applies it; launching Hex does not reapply it. Wayland has no general
protocol for the active layout, so there Hex keeps the settings but does not
observe input-source changes or show these controls.
On macOS and Windows, labs.app_model_switching opts into switching the
shared speech-model picker per dictation mode. Each mode, Global included, may
carry speech, a model and a language it supports, for example
{"model":"parakeet_v3","language":"es"} in
dictation_processing.default_mode or dictation_processing.modes[]; omit it
for "Don't switch". When the app in front changes, Hex matches the mode by
application, waits 300 ms, then prepares its choice through the same
installed-only path as a manual pick; it never downloads a model and a
recording holds a due switch until it is idle. Matching is by application on
both macOS and Windows, and a mode's website rules are ignored, so a mode
chosen only by website never switches. Hex's own window, and a moment with no
app in front, are ignored: they neither schedule nor cancel a switch, so
enabling the lab while Hex is in front applies nothing until you next focus
another app. Editing a mode's choice applies when you next focus an app whose
mode carries it. With keyboard switching also on, the switch that is due
later wins, and an app switch wins a tie in the same tick. Linux does not
support this lab.
- Booleans take
on/off,true/false,yes/no, or1/0. - Optional settings take
noneto clear them. microphonetakes a device name exactly as Settings lists it, orsystem-default(ornone) to follow the OS default input.avoid_bluetooth_microphone(default on) makes System default record from the built-in microphone, else another wired or USB input, when the OS default is a Bluetooth headset, so its audio stays out of call quality (on Linux, Hex moves its PipeWire stream off a Bluetooth default source after opening it, so the headset may still switch to hands-free mode; see Bluetooth headsets). A saved microphone and--deviceare always used as named.- Shortcuts take text such as
option,right-command,option+shift+v,control+space,fn,f5, orleft-option+right-option(both Options held together).left-andright-apply to shortcuts without a key. On macOS, Windows and X11,mouse4andoption+mouse4also work (Mouse 1/2/3 are left/right/middle). Key names resolve on the active keyboard layout, the OS's rules for Settings' recorder apply, and a binding another Hex shortcut uses is refused withshortcut-in-use.double_tap_onlyneeds a key chord, as in Settings. - On Linux with
labs.standalone_special_keyson (hex labs on special-keys, off by default), a shortcut may usemenu,pause, orscrolllock, alone or with modifiers, for examplehex settings set dictation_hotkey menuafter remapping Caps Lock to Menu. Off, they are refused withinvalid-value, and turning the toggle off puts a shortcut on one back to its default. Elsewhere one alone still needs a modifier. second_dictation_hotkey(unbound by default;noneclears it) is a second shortcut that dictates exactly asdictation_hotkeydoes, such as a key on an external keyboard. It acts only whilelabs.second_dictation_shortcutis on (hex labs on second-dictation-shortcut) and shares no key with another shortcut. Linux and Windows need a key in it, as every shortcut there does.extra_dictation_hotkeys(macOS, an empty array by default) holds up to four more dictation shortcuts, each{"binding": ..., "mode": ...}. A row acts only whilelabs.more_dictation_shortcutsis on (hex labs on more-dictation-shortcuts) and shares no key with another active shortcut.modeishold_or_double_tap(the default),hold,tap, ordouble_tap;double_tapneeds a binding with a key and otherwise reads ashold_or_double_tap, except that Labs › Double-tap on a lone modifier also allows a binding without one. Turning the Lab off keeps the rows saved.jsonsettings (modes, extra dictation shortcuts, meeting providers, excluded calendars) take the JSON value the file stores.ui_languagetakesen,pl,zh(Simplified Chinese),zh-Hant(Traditional Chinese),ja,de,es,uk,pt,fr,ru, ornone(follow the system language), the same languages Settings offers. Following the system, a Chinese locale with the Hant script or the TW, HK, or MO region shows Traditional Chinese; other Chinese locales show Simplified.updates.channeltakesstable,dev, ornone(follow the build: the channel it was published on), and checks for updates at once. Choosing another channel than the build's offers that channel's current release ("Switch to Stable 2.3.1" in the window); Hex installs it only once you confirm there. Versions are never compared.- With the
captureexperiment,capture.*keys appear:capture.enabled,capture.hotkey(defaultoption+shift+s),capture.list_hotkey(the capsule's send-to list, defaultoption+shift+v),capture.transcriber,capture.folder,capture.media_days,capture.bundle_days,capture.routing,capture.show_overlays_in_recordings(default off; on lets screen recordings, Hex's captures included, show its overlays), andcapture.capsule_flow(list, the default,toast,menu_bar,cursorornotch: the capsule's flow after a capture, prototypes to compare). Both capture shortcuts need a key, andcapture.enabled onneeds both free; on a Mac whose Paste Last is still ⌥⇧V, rebind one of the two first. meetings.retentionisday,week,month,forever, ornone(the default: meetings recorded since the default began are kept 24 hours, and older ones stay until you choose). Hex moves meetings that ended longer ago than that to the Trash at launch and hourly, Meetings on or off, andhex meetingsandhex mcpno longer list them.meetings.audio_retentionisday,week,month,forever, ornone(the default). With Labs › Expire meeting audio separately on, a finished meeting's WAVs move to the Trash on that window while its transcript, notes, summary, and Markdown stay;nonefollowsmeetings.retention. With the lab off it does nothing.transcription.modelandtranscription.languageare read-only here: usehex model use.transcription_recentsandmeetings.calendar.connectedare read-only (connecting a calendar asks macOS for access from Settings).
recording_audio_behavior also accepts duck (Lower audio): macOS and Linux
lower the default output, Windows lowers other apps' audio sessions.
recording_duck_volume sets the fraction of the original output volume (0–1,
default 0.2), applied on the next intentional capture when ducking is selected.
Writable keys: release_microphone_while_idle,
sound_effects, sound_effect_volume, sound_theme, microphone,
avoid_bluetooth_microphone, recording_audio_behavior, recording_duck_volume, double_tap_lock, double_tap_only,
shortcut_mode, dictation_hotkey, paste_last_hotkey, raw_dictation_hotkey,
second_dictation_hotkey, extra_dictation_hotkeys, labs.second_dictation_shortcut,
labs.more_dictation_shortcuts, labs.standalone_special_keys, labs.app_model_switching, show_dock_icon, vocabulary_boost,
labs.keep_microphone_open, labs.keep_microphone_open_seconds,
dictation_processing.default_mode, dictation_processing.modes,
history_retention, meetings.enabled,
meetings.output_folder, meetings.summaries, meetings.opencode_model,
meetings.show_recording_indicator, meetings.recording_indicator_corner,
meetings.retention, meetings.audio_retention, meetings.providers, meetings.calendar.excluded_calendars,
meetings.calendar.remind, meetings.calendar.lead_minutes,
experimental.hex_microphone, experimental.hex_microphone_mode,
experimental.hex_microphone_comfort_noise,
experimental.hex_microphone_strength, enable_on_launch,
paste_with_shift, ui_language, indicator_position, updates.channel.
transcription.recognition_hints is deprecated: recognition hints merged into
the vocabulary. get returns the vocabulary's terms,
comma-separated, and set (or apply) adds the comma-separated names to it;
list leaves the key out. Model listings keep supports_recognition_hints
(true where the model reads the vocabulary as its prompt) beside the newer
vocabulary: prompt, bias, or none.
meetings.summaries chooses who titles and summarizes meetings: off, mac
(Apple Intelligence; nothing leaves this Mac, and nothing falls back to
OpenCode), or opencode (sends the transcript to meetings.opencode_model,
or OpenCode's own default model when that is none, never another provider).
Off a Mac it is off or opencode. The retired
meetings.summarize_with_apple_intelligence and
meetings.summarize_with_opencode are refused with the meetings.summaries
command to use instead.
A test (control::settings::tests::every_persisted_setting_has_a_cli_key)
fails when a new persisted setting has no key here.
CONTROL_VERSION is 4. A change that breaks a request or reply shape
increments it; adding a command, an optional request field, or a reply field
does not. A hex from a different version gets version-mismatch; use the
hex inside the running Hex.app, or restart Hex after updating. Version 2
removed permissions.input_monitoring from the status reply; version 3 removed
voice-action and features.voice_action; version 4 removed commands-retry,
settings-set.confirm, window-show commands, capture.recording voice,
features.commands, and features.command_model.