Open source · MIT · macOS 26+

voice-mode

Talk to your Mac and have it actually do things. No hotkey to hold, no third-party app, no subscription. Five minutes to working, and a diagnostic that tells you why a command failed instead of leaving you guessing.

§ 01 — What this is

macOS already does this. Nobody tells you how.

Apple ships a feature called Voice Control that can drive your entire Mac by voice — type into any app, press any button, switch apps, edit text. It's free, it's built in, and it runs on-device.

It is also almost completely undocumented past the settings pane, and it has a handful of traps that make it look broken when it isn't. voice-mode is the missing manual plus the tools.

Two audiences, both covered on this page. If you just want to talk to your computer, do § 02 and stop. If you want to build your own command sets or understand the format, keep going to § 05.

The trap that wastes everyone's evening

A voice command bound to a plain Return is silently ignored by Claude, Cursor, Slack, VS Code, Discord and Notion. It doesn't error. The command is recognized. Nothing happens.

Bind it to Cmd+Return instead and it works in all of them. Same mechanism, same event, one modifier key. That single fact is most of what this project exists to tell you.

§ 02 — Quickstart

Working in five minutes

No terminal required for this part. You'll turn on Voice Control, import a set of commands, and say one phrase to send a message.

Turn on Voice Control

System Settings → Accessibility → Voice Control → switch it on. macOS downloads a small on-device model the first time. Nothing you say leaves your Mac.

Free up the microphone key, if you use one

Voice Control listens continuously — there is no key to hold. If you'd previously set up a push-to-talk dictation app, turn it off so the two don't fight over the microphone.

Download the starter command set

Grab sets/core.voicecommands from the repo. It's a plain file — ten commands, every one bound to a mechanism that works.

# or clone the whole thing
git clone https://github.com/justinfowler925/voice-mode

Import it

System Settings → Accessibility → Voice Control → Commands → Import, and pick that file. Your commands appear immediately, already switched on.

Say “punch it”

Click into any message box, talk normally, then say punch it. It sends. That works in Claude, Cursor, Slack — anywhere, with no per-app setup.

§ 03 — The commands

What to say

Say thisIt doesNotes
punch itSends the messageThe one you'll use constantly. Works everywhere.
click <name>Presses any labelled buttonBuilt in. Say what's written on it: click Save.
show numbersNumbers everything clickableBuilt in. For buttons with no label — say the number.
show commandsApple's full listBuilt in, and app-specific. Your best reference.
scratch thatDeletes your last phraseBuilt in. Faster than any correction.
go to cursorSwitches appsOne per app you use.
handsStop transcribingCommands still work. Say this before talking about a command.
talkResume transcribing
select allCmd+AThen copy that, paste that, undo that.
go to sleepStop listeningBuilt in. wake up to resume.

Why “punch it” and not “send it.” A trigger phrase competes with the sentence being transcribed — the recognizer has to decide whether your words are a command or more dictation, and a conversational phrase loses that call about half the time. Worse, phrases like that's it and go ahead occur in ordinary speech, so they fire mid-thought and submit half-written messages. Pick something you would never say in a sentence.

§ 04 — When it doesn't work

Troubleshooting, by symptom

I say the command and absolutely nothing happens

Two completely different bugs look like this, and they have nothing in common. Either it never heard you, or it heard you and the action is wrong.

Run voice-mode doctor. It reads Apple's own recognition counter — a number macOS keeps and shows in no interface — and tells you which one you're in. If the count moved, the binding is wrong; if it never moved, the phrase is wrong.

It works in some apps but not others

You've almost certainly bound it to a plain unmodified key. Electron apps drop those silently.

Rebind to a modified key — Cmd+Return rather than Return. doctor flags every command in this category by name.

My words get typed out instead of running the command

The phrase is too conversational, so the recognizer treated it as dictation.

Rename it to something unsayable. Two or more words, no single syllables — that's Apple's guidance too.

Commands fire when I didn't mean them

Same root cause, opposite direction: your trigger occurs in normal speech.

Rename it. And say hands before you discuss commands out loud, talk to resume.

“click Send” does nothing in one particular app

That app's button has no accessible name, so there's nothing to match. Cursor's send arrow is exactly this.

Use show numbers and say the number, or bind the app's keyboard shortcut instead.

A built-in command stopped working after I added my own

A custom command silently disables the built-in that shares its phrase — and the native one usually rides a better mechanism than whatever you bound.

Delete your custom version. doctor lists built-ins that have been switched off this way.

§ 05 — For developers

The format, and the tiers

There is no public API. No framework, no XPC service, no entitlement. Voice Control commands live in one undocumented property list, and the supported interface is the settings pane plus .voicecommands import/export.

The repo carries the complete per-command schema, derived by reading a live command table and writing to it until every field's behaviour was known. It isn't documented anywhere else.

Reliability is a property of the binding

Sort commands into these tiers and you can predict which of yours will fail before testing any of them.

VerdictMechanismWhy
WorksModified keystroke — Cmd+ReturnSurvives Electron composers. Verified.
WorksAccessibility action — click by label, menu itemNeeds the control to have a name.
WorksApp / URL launchNothing in the path to swallow it.
FailsBare unmodified key — Return, EscapeSilently dropped. Verified: 8 fires, 0 sends.

Two stores, two shapes — the trap that eats your config

The export file wraps commands in a CommandsTable key. The live store is flat — command ids sit at the top level. Assume the wrapper when writing the live store and you create an empty table, orphaning every command you have, with no error anywhere.

# read the live store — there is no CommandsTable key here
defaults export com.apple.speech.recognition.AppleSpeechRecognition.CustomCommands -

# import MERGES: edits stick, deletions do not. Remove one command by key:
defaults delete <domain> Custom.808351004.970543146

# reload the recognizer. Do NOT also kill cfprefsd — it can undo your write.
killall DictationIM

The CLI

# diagnose every command by tier and fire count
./bin/voice-mode doctor

# readable JSON set -> importable .voicecommands
./bin/voice-mode build sets/core.json -o core.voicecommands

# run the gates (31 checks)
./tests/run

Python 3 standard library only, no dependencies. build refuses a bare-key binding rather than warning about it — you have to pass allow_bare to force one through.

Also in the repo: the undocumented notify control surface, with five recognizer modes rather than the two Settings exposes — including a code-dictation mode with its own language model that Apple never surfaced. Plus the full 161-command built-in catalog and the 82 nameable keys.

§ 06 — Honest limits

What this doesn't solve

Accuracy is the OS's, not mine. Expect roughly 95% on clear speech with a decent microphone in a quiet room, and noticeably worse with background noise. An external mic is the single biggest improvement available.

Proper nouns get mangled until you add them to Voice Control's vocabulary. That pane exists and syncs across devices — but its import file format is undiscovered, so words go in one at a time, or by voice with Text.AddSelectionToVocabulary.

Everything here is unsupported. No API means Apple can change the plist shape in any release. The format doc records how each field was verified so a future reader can tell what's fact and what's inference.

Local speech-to-text apps can't replace this. MacParakeet, superwhisper, Wispr Flow — the whole category is push-to-talk or toggle. Better transcription, but none of them do continuous hands-free, so none of them meet the bar.