Hold a hotkey anywhere on macOS, speak, and a Terminal window opens straight into an opencode TUI session with your voice as the first message.
Runs entirely on your machine. Microphone capture via sox, transcription via whisper.cpp. No API keys for transcription. The only network call is whatever opencode itself makes to your configured AI provider.
hold right-Option ── 🎙 recording overlay appears
speak (1-2s minimum)
release ─────────── overlay disappears
new Terminal window pops up showing "transcribing..."
whisper-cli runs (~1-2s)
Terminal cuts to the opencode TUI with your prompt
submitted as the first message
Everything works from any app: dictate prompts while you're in Slack, Spotify, your IDE, anywhere.
The installer pulls in everything else (Hammerspoon, whisper-cpp, sox, jq, plus a Whisper model).
git clone git@github.com:WillTaylorDev/opencode-voice.git
cd opencode-voice
./install.shThe installer is idempotent. It checks for Homebrew, installs the four
packages it needs if they're missing, downloads the recommended Whisper model
(ggml-large-v3-turbo-q5_0.bin, roughly 570MB), symlinks the runtime files
into ~/.hammerspoon/ and ~/.local/share/voice-prompt/, and adds a managed
block to ~/.hammerspoon/init.lua.
Re-run it any time. It detects what's already in place and skips the work.
macOS will prompt for these the first time they're needed.
- Accessibility for Hammerspoon, so it can detect the global hotkey. Pre-grant under System Settings, Privacy & Security, Accessibility.
- Microphone for Hammerspoon, so sox can record. Auto-prompts on first recording.
- Automation control of Terminal.app, so the pipeline can open a window. Auto-prompts on first run.
Hold right-Option, speak, release. The recording overlay shows while you hold. The Terminal window opens immediately on release with a "transcribing..." spinner, then drops into the opencode TUI when whisper finishes.
Hold for at least 250ms. Anything shorter is rejected as a stray tap.
Edit ~/.hammerspoon/voice-prompt-env.lua after install. The installer seeds
this file from a template once and never touches it again.
| Setting | What it does |
|---|---|
OPENCODE_DIR |
Working directory opencode runs in. Defaults to ~/voice-prompts. |
OPENCODE_BIN |
Path to the opencode binary. Defaults to ~/.opencode/bin/opencode. |
OPENCODE_MODEL |
Pin a specific model, e.g. "anthropic/claude-haiku-4-5" for cheap fast prompts. Leave commented to use opencode's default. |
WHISPER_MODEL |
Switch to ggml-small.en.bin for roughly 2x faster but slightly worse transcription. |
Reload Hammerspoon after editing. Click the menu bar icon, Reload Config, or from the shell:
hs -c "hs.reload()"voice-prompt.lua ships with two trigger modes. The installer wires
startHoldMode() (hold right-Option) by default. To switch to a toggle on
cmd+shift+space, edit the managed block in ~/.hammerspoon/init.lua:
-- vp.startHoldMode()
vp.startToggleMode()Hold mode feels natural for short prompts. Toggle mode is better if your keyboard lacks a separate right-Option key, or if you want to dictate long passages without holding a key the whole time.
Each invocation writes a log to ~/voice-prompts/YYYYMMDD-HHMMSS.log
containing the raw whisper transcript and the opencode invocation.
Tail the latest:
tail -f "$(ls -t ~/voice-prompts/*.log | head -1)"Test the pipeline without the hotkey:
sox -d -r 16000 -c 1 -b 16 /tmp/voice-prompt-test.wav trim 0 4
WAV_FILE=/tmp/voice-prompt-test.wav ~/.local/share/voice-prompt/pipeline.shIf the hotkey isn't firing, open the Hammerspoon Console (menu bar icon, then
Console) and watch for [voice-prompt] log lines while you press the key.
The most common cause is missing Accessibility permission for Hammerspoon.
Hammerspoon (right-Option flagsChanged event tap)
├─ on key down: spawn `sox` recording to /tmp/voice-prompt-{timestamp}.wav
└─ on key up: stop sox, run ~/.local/share/voice-prompt/pipeline.sh
pipeline.sh
├─ open new Terminal window with a watcher script (immediate)
├─ run whisper-cli on the WAV
└─ write the transcript to a temp file the watcher polls for
watcher (running in the new Terminal window)
├─ show a spinner while whisper runs
└─ exec opencode --prompt "<transcript>" once the file appears
The window opens before whisper finishes so you see motion immediately rather
than a delayed pop. opencode receives the prompt via the --prompt flag,
which submits it as the first user message and starts processing inside the
TUI.
opencode-voice/
├── install.sh Idempotent installer
├── README.md This file
├── LICENSE MIT
├── hammerspoon/
│ ├── voice-prompt.lua Hotkey + sox recording
│ └── voice-prompt-env.lua.example Config template
└── share/
├── pipeline.sh whisper -> spawn Terminal
├── run-in-terminal.sh Inner script that exec's into the opencode TUI
└── stt-prompt.md Optional LLM normalization prompt (unused by default)
After install, runtime files live at:
~/.hammerspoon/voice-prompt.lua(symlink tohammerspoon/voice-prompt.lua)~/.hammerspoon/voice-prompt-env.lua(real file, your local config)~/.local/share/voice-prompt/pipeline.sh(symlink)~/.local/share/voice-prompt/run-in-terminal.sh(symlink)~/.local/share/voice-prompt/stt-prompt.md(symlink)
There's a separate project, @renjfk/opencode-voice,
that does voice input inside the opencode TUI bound to ctrl+r. That's a great
fit when you're already in opencode. This project covers the case where
you're in any other app and want to fire a prompt without switching first.
You can run both side by side. They share the same whisper-cli and sox
binaries.
# remove symlinks
rm -f ~/.hammerspoon/voice-prompt.lua
rm -f ~/.local/share/voice-prompt/pipeline.sh
rm -f ~/.local/share/voice-prompt/run-in-terminal.sh
rm -f ~/.local/share/voice-prompt/stt-prompt.md
# remove the managed block from ~/.hammerspoon/init.lua manually,
# or delete the file if it only contains the opencode-voice block
# optional: remove your local config and work dir
rm ~/.hammerspoon/voice-prompt-env.lua
rm -rf ~/voice-prompts
# optional: remove the Whisper model (~570MB)
rm ~/.local/share/whisper-cpp/ggml-large-v3-turbo-q5_0.bin
# reload Hammerspoon
hs -c "hs.reload()"The Homebrew packages (hammerspoon, whisper-cpp, sox, jq) are left
alone since other tools may depend on them.
MIT. See LICENSE.