* cli: hex dictation start --print keeps a dictation from pasting (labs) A client that starts a dictation to insert the text itself loses it when the person finishes the locked recording with the shortcut, or when a microphone loss salvages it: Hex pastes or copies into the focused app. `hex dictation start --print` (macOS, behind Labs › Print-only CLI dictation / `labs.print_start`) blocks and answers with the transcript however the recording ends, so nothing is pasted or copied. It is busy while a dictation records, a plain `stop` answers done, `stop --print` joins the same transcript, no speech exits 4, a cancel or failure exits 1, and it waits at most 31 minutes. Linux and Windows answer unsupported; CONTROL_VERSION stays 4. # Conflicts: # src/labs.rs # src/rune_ui/labs.rs # tests/golden/rune-mac-labs-dark.png # tests/golden/rune-mac-labs-light.png # tests/golden/rune-mac-labs-link-dark.png # tests/golden/rune-mac-labs-link-light.png * cli: harden hex dictation start --print's endings # Conflicts: # src/labs.rs * cli: bind the printing-start test's shortcut to Option * labs: regenerate the Labs goldens for both new cards
14 KiB
Control the running app from a terminal
Agent or terminal // control.cli; every OS
├── One tree and protocol over the local API's POST /control on every OS;
│ OS can't do it -> unsupported at runtime, exit 1
├── hex status [--json] -> Listener, capture, meeting, model, features,
│ permissions, window, update, version, pid, memory
│ ├── Wayland listener -> its warning names a missing wl-copy before paste fails
│ └── Hex not running -> exit 3, "Hex is not running; run `hex start`"
├── hex start -> Already running: nothing | else open Hex.app (Linux and
│ Windows: a detached `hex app --hidden`), wait ≤ 20 s
│ └── The caller's HEX_* variables -> open --env (values never logged)
├── hex quit | restart [--force]
│ ├── Meeting or dictation in progress, no --force -> refused, exit 1
│ ├── Listener didn't answer, no --force -> busy (it might be recording)
│ └── restart -> reopens with the running Hex's HEX_* variables (macOS);
│ Linux unit -> systemctl restart; else waits for the drain, then start
├── hex dictation start|stop|toggle|cancel
│ ├── Same shortcut state machine as the hotkey; start locks the capture
│ ├── start -> intentional at once: tone once the mic is open, recording
│ │ behavior, live stream
│ ├── stop -> done only when the clip went to transcription;
│ │ no audio yet or silent -> discarded, nothing transcribing
│ ├── cancel -> discards the recording in any mode, nothing pasted or saved;
│ │ jobs already transcribing finish; idle -> unchanged
│ ├── Repeat a command -> unchanged, exit 0
│ └── dictation stop --print -> stops at once, then waits ≤ 60 s and prints
│ the transcript (processed as a paste) instead of pasting it
│ ├── No speech -> no-speech, exit 4, prints nothing
│ ├── Not recording -> not-recording, exit 1
│ ├── Failure or timeout -> exit 1, prints nothing, never pastes
│ └── History keeps it as Printed; Paste Last skips it (also on Linux)
│ ├── dictation start --print -> Labs › Print-only CLI dictation (macOS, off
│ │ by default): starts the same locked recording, but its text comes back
│ │ here however it ends: the shortcut that finishes it, stop, or a kept
│ │ microphone loss. Never pasted or copied. No speech -> no-speech, exit 4;
│ │ cancel or any other end -> exit 1; already recording -> busy; lab off
│ │ -> disabled; Linux and Windows -> unsupported
│ └── dictation start|toggle --raw -> the dictation it starts pastes the
│ transcript as heard (Dictate without processing); History keeps it as Raw;
│ --raw with stop -> usage error, exit 2
├── hex paste-last -> pastes the last dictation
│ ├── Setup gates the listener -> not-ready
│ └── Recording or shortcut gesture in progress -> busy (menu bar: nothing)
├── hex typed-text (Linux) -> the Transform typed text shortcut's one job
│ ├── Labs › Transform typed text off -> disabled
│ ├── Terminal paste on -> disabled (it would send Ctrl+Shift+V)
│ ├── Setup gates the listener -> not-ready
│ └── Recording, a pending start, or a capture still finishing -> done,
│ but the listener drops the job: no keys are sent and no state changes
├── hex transcribe <file> [--model <id>] [--language <code>]
│ ├── Labs › Transcribe files off -> disabled, exit 1; the file is never read
│ ├── Not WAV, or over 64 MiB -> refused here, before Hex is asked
│ ├── Hex running -> the file to POST /transcriptions with the model named;
│ │ ≤30 s runs on the dictation engine's model, longer clips on the
│ │ transcription service's own copy; stdout gets the transcript
│ ├── Hex stopped -> Linux/Windows load the model here as `model transcribe`;
│ │ macOS answers not-running, exit 3
│ └── No speech -> no-speech, exit 4; nothing pasted, kept, or processed
├── hex meetings record [--title] | stop | copy-last
│ ├── Meetings off -> feature-off; recording shows in the menu bar and HUD
│ │ exactly like a menu-bar recording
│ └── No meetings cancel: Hex cannot discard a recording
├── hex settings list | get <key|prefix> | set <key> <value>
│ ├── The window's save, then ReloadSettings -> live, no restart
│ ├── Unknown key, bad value, read-only key -> refused, nothing written
│ ├── Shortcut conflict -> refused, as the window's recorder would
│ └── apply <file|-> -> a JSON object of key: value, all or none
│ ├── Undeclared settings keep their values; dependent keys in any order
│ ├── transcription.model / .language -> selected, never downloaded
│ ├── Hex running -> SettingsApply: saved and adopted live, like set
│ └── Hex stopped -> written to settings.json under the service lock
│ (Linux); a Hex holding it is waited for (≤ 35 s) instead
├── hex model list | use <id> | install [id] | cancel
│ ├── use -> installed models only; needs the running Hex; never downloads
│ │ or repairs; keeps the model's remembered language and hints
│ │ (--language overrides)
│ └── Linux/Windows: install with Hex stopped downloads and selects here
│ (a Hex that starts meanwhile makes the switch); status, devices,
│ check, transcribe run here (macOS: unsupported)
├── hex window show [pane] | hide
│ ├── settings -> the General pane (status window.pane stays "settings")
│ ├── meetings pane with Meetings off -> the pane's opt-in card
│ └── story <story> [--variant V] [--play] -> a playground story, as a
│ hex://story link opens (labs.md); playground off -> feature-off
├── hex history clear --yes -> without --yes refused before Hex is asked
├── hex check-for-updates -> The updater checks now
├── hex launch-at-login [on|off] -> SMAppService (changes need Hex.app);
│ Windows Run key; Linux XDG autostart entry
└── hex logs [--process] [-n N] [-f] -> local tail; works with Hex stopped
Every command except start, restart, logs, settings apply, and the
read commands talks only to the running Hex; none of them launch Hex. Every OS reaches it through
the local API's owner-only discovery file and the bearer-authenticated
POST /control route. Commands dispatch before logging and the instance lock on every OS,
so they never compete with the running app for its files. Opt-ins stay
explicit settings: no command turns Meetings on as a side effect.
hex transcribe is the one command that doesn't use /control: it reads and
checks the WAV here, then sends it to the same discovery file's
POST /transcriptions (Content-Type: audio/wav, 64 MiB cap), so the running
app transcribes it with the model the request names: a clip up to 30 s on its
dictation engine's model when it holds that one, a longer clip on the
transcription service's own copy. Its Labs toggle is read from
settings.json (AppSettings::read), the same read hex labs lists from, so
the check works with Hex running or stopped; a running Hex saves the toggle
there at once. Off, nothing is read.
On Linux and Windows control::host::answer carries every command out through
the OS's adapter (linux::app::LinuxControl, windows::rune::WindowsControl):
dictation, paste-last, and Linux's typed-text go to the listener's own queue
(Windows: the same hotkey loop, as a toggle press would), settings save and
reload as the window's do, and settings set refuses a key whose capability
the OS lacks
(settings list marks it "not on this OS"). meetings record|stop|copy-last
go to the meeting recorder (meeting/runtime) as the tray's items do;
window story answers unsupported; Windows sends every annotate
command to the running Hex. hex history, hex meetings list|show|search, and
hex mcp read files on every OS.
dictation start --print is macOS's, behind a Labs toggle: the listener
stores the command's reply with the recording (recognition::Hotkeys::print_capture)
and reads it at every end. The shortcut's finish passes it to
DictationWorker::transcribe_printed; dictation stop and toggle join it
with their own reply so one transcript answers both (a plain stop still
answers as a plain stop does); dictation cancel, a
discard, and a kept microphone loss answer it themselves. Only the dictation
machine's actions may take it, and a reply still held when a new recording
starts, or when the machines are suspended, is failed rather than left to
answer a later dictation. Nothing pastes or
copies it, History records it as Printed, and the reply is answered rather
than dropped on every path, including a discarded or silent clip and a
microphone loss. While it waits, it holds one of the local API's four
connection workers (local_api::HTTP_WORKERS) until it answers or reaches
its cap, even if the asking client is killed. Linux and Windows answer
unsupported before their host runs anything.
Sources: control.rs (protocol, status, quit rules),
control/host.rs (the Linux and Windows host),
control/settings.rs (settings registry),
control/cli.rs and
control/cli/human.rs (clap, output, exit codes, start,
logs), macos_rune/control.rs (the macOS host),
the /control route in local_api.rs, and early
dispatch in main.rs.
Command and JSON contracts document flags, exit codes, and schema.
Checks: control::tests round-trip every command and reply (including
a_printing_stop_answers_through_its_reply_channel, which also joins two
printing replies onto one transcript and pins start --print's flags and
deadline), refuse other
protocol versions, and cover the quit rules (an unanswered listener refuses)
and model_selection (remembered hints, wildcard-language validation); control::settings::tests include
every_persisted_setting_has_a_cli_key, value parsing, shortcut conflicts,
and zero volume; control::cli::tests pin
exit codes, output, and hex start's open
arguments; desktop::onboarding::tests::relaunch_forwards_only_the_hex_environment
pins restart's; macos_rune::control::tests check that model use prepares
its exact selection installed-only;
dictation::audio::tests::marked_captures_are_intentional_without_a_hold
covers the immediate stream and ReadyIntentional; recognition::tests
cover the stop replies and the paste-last refusal;
recognition::hotkeys::tests::a_printing_start_follows_the_shortcut_that_finishes_it
covers a printing start's reply surviving to the shortcut's finish and a
cancel, and
recognition::hotkeys::tests::a_held_print_reply_never_answers_a_later_dictation
covers another shortcut leaving it alone, a later start failing it, a discard,
and a suspension; control::tests::cancel_parses_and_takes_no_flags
covers cancel and its flags; local API's
control_requests_are_authenticated_versioned_and_typed covers authentication,
bounds, and error codes; tests::control_commands_parse_beside_the_read_commands
covers parsing next to history and meetings; control::host::tests cover
the quit refusal, printing stops (and Linux's and Windows' unsupported for a
printing start), and runtime unsupported replies;
linux::tests parse the shared tree beside Linux's own commands;
rune_ui::platform::parity_tests fail when a capability differs across OSes
without a recorded reason; settings_name_real_capabilities_and_keys checks
the settings-to-capability table; control::cli::transcribe::tests cover the
lab-off refusal, a non-WAV file refused before any transcription, the selected
model and language, and an empty transcript as no-speech.
Observed (Oct 30, apollo, an isolated data root and hex service without a
display): status, settings get/set (and refusing show_dock_icon), vocabulary
add/list/remove, model list/use/status, history clear, window hide,
check-for-updates, commands check, quit, start, and restart all answered as
documented. Windows compiled with cargo xwin clippy (x64 only; the ARM64
cross-build fails in rquickjs-sys's build script, unrelated); it has not run
on a Windows PC.
Gaps: no command has run against a live app; the unit checks substitute the
host. The control loop's start/stop wiring (tone timing, recording behavior)
has no end-to-end test. dictation start --print in particular has never run
on a Mac with a microphone: its reply routing through the shortcut finish, a
kept microphone loss, and a paused listener is covered only by
recognition::hotkeys' unit test and by reading every path that ends a
recording, so expect to verify it by hand (a locked recording finished by the
shortcut, by stop, and by stop --print) before trusting it. A CLI volume
change does not preview a tone as the window does. Onboarding
permission prompts, calendar connection, and meeting deletion or renaming have
no command. Linux and Windows dictation through the CLI has not run with a
microphone, and meeting recording through the CLI has not run on Linux or
Windows. hex transcribe's gate-off refusal, its non-WAV refusal, and a
running headless service's model-unavailable answer were observed on Linux;
no model was installed, so its transcript answer was not observed end to end,
and it has not run on macOS or Windows.