Offline, semantic voice control for your development workflow.
Say what you mean, not what the manual says.
$ voxide say "make sure everything still passes"
-> cargo.test (0.91)
running 80 tests ...
$ voxide say "checkout the login branch"
-> git.checkout (0.94) branch = "login"
Switched to branch 'login'Neither of those phrasings appears anywhere in a config file.
Every established option — Talon, Cursorless, Numen, Dragonfly/Caster — matches speech against a grammar. You define patterns; you then have to say one of them. The near-universal complaint is the ramp: reviewers describe a fortnight of frustration while a custom phonetic alphabet and command grammar become second nature.
voxide replaces the grammar with a sentence-embedding model. Training phrases and your utterance are both embedded into a vector space and compared by cosine similarity, so a phrasing nobody wrote down still lands on the right command.
This is a measurable claim, so voxide measures it. voxide eval scores both
backends against a golden corpus of 40 utterances written the way people
actually talk:
| backend | overall top-1 | on the 5 paraphrase cases |
|---|---|---|
lexical (token + trigram overlap — the conventional approach) |
87.5% | 0 / 5 |
semantic (MiniLM embeddings) |
92.5% | 5 / 5 |
hybrid (both, blended — the default with --features embed) |
100% | 5 / 5 |
Each backend is scored at its own threshold, because the scales are not comparable: lexical overlap is near-binary, cosine similarity is continuous.
The blend wins because the two backends fail in almost disjoint ways. Semantic
matching reads a {slot} marker as a literal word, so "switch to develop" is
scored on the branch name and drifts toward cargo.build; lexical matching
drops the marker and gets it right. Lexical cannot recognise a rewording it was
never shown; semantic can. Neither is a subset of the other.
The lexical baseline is not a strawman: it gets 35/35 on utterances close to a
training phrase. It fails every one of the five that drift — "make sure
everything still passes" reaches 0.054 against cargo.test, "clean up the
indentation" loses outright to cargo.test.filter. That cliff is the entire
problem, and it is what voxide is built to remove.
Run it yourself:
$ voxide eval --compare --sweepNo network calls, no telemetry, no account. Speech recognition, embeddings, and
matching all run on your machine. The only thing voxide ever downloads is the
model itself, once, via voxide models pull — pinned to an exact upstream
artifact and verified against a recorded SHA-256, so a corrupted or swapped
download is a named error rather than a model that quietly misbehaves.
That downloader is part of the binary. No Python, no pip install, no
virtualenv in the setup path.
Early, and honest about it. What works today:
| ✅ | Command packs, slot extraction, shell execution |
| ✅ | Lexical matcher, ranked results, voxide why |
| ✅ | Eval harness with threshold sweep and backend comparison |
| ✅ | Audio capture, resampling, VAD, pre-roll — all trait-based |
| ✅ | Wake word detection, pipeline state machine, voxide run |
| ✅ | Cross-platform CI, cargo-deny license gating, 145 tests |
| 🚧 | Semantic matcher and microphone backend — implemented, CI-verified (see Development) |
| ✅ | voxide models pull — pinned, checksummed, atomic, zip-slip safe |
| 📋 | Lua actions, keystroke injection, dictation mode |
| 📋 | Acoustic wake word, so the recogniser can idle until spoken to |
Text mode is not scaffolding that goes away: it is how the eval harness runs, how packs get debugged, and a usable entry point in its own right.
Grab an archive for your platform from Releases — Apple Silicon macOS, x86_64 Linux, or x86_64 Windows. Each is a single self-contained binary: ONNX Runtime is statically linked, so there is nothing to install alongside it.
Intel Macs have no prebuilt archive, because ONNX Runtime ships no
x86_64-apple-darwin binaries for the version voxide links. Build from source
below, omitting --features embed, for the lexical backend.
$ tar xzf voxide-*.tar.gz && cd voxide-*
$ install -m755 voxide /usr/local/bin/
$ mkdir -p ~/.config/voxide && cp -r packs ~/.config/voxide/The second line matters: the binary has no commands of its own. It looks for
./packs in the current directory first, then ~/.config/voxide/packs, so
copying the bundled packs there is what makes it work from anywhere.
Verify a download against the SHA256SUMS published with the release. On Linux,
microphone support needs ALSA at runtime (sudo apt-get install libasound2).
$ git clone https://github.com/MansurPro/voxide
$ cd voxide
$ cargo install --path crates/voxide --features embedOmit --features embed for a dependency-free build that uses lexical matching.
The embedding model (~90 MB) is downloaded once, into ~/.cache/voxide/models,
and shared by every project on the machine. Nothing else leaves your computer.
$ voxide packs list # what commands exist
$ voxide say "run the linter" # match and execute
$ voxide say --dry-run "..." # show the command line, run nothing
$ voxide why "..." # ranked candidates and why they scored
$ voxide eval --compare --sweep # score the matcher against the corpus
$ voxide run --wake voxide # listen on the microphone
$ voxide run --from recording.wav # replay a recording through the pipeline
$ voxide models list # what models are available / installed
$ voxide models pull vosk-en-small # fetch one, verified against a pinned hashvoxide run needs a build with --features mic,vosk and a speech model. Without
them it says so and tells you what to do, rather than failing obscurely.
voxide why is the debugging tool. It shows the ranking, which training phrase
was responsible, and the margin over the runner-up:
$ voxide why "what changed"
phrase: "what changed"
backend: lexical
threshold: 0.62
-> 1.000 git.status via "what changed"
0.410 git.diff via "what did I change"
0.233 git.log via "what's the history"
margin over runner-up: 0.590A narrow margin is the signal to add a distinguishing phrase to the pack.
A pack is one TOML file. Phrases are training examples, not a grammar — three or four naturally different wordings is plenty.
[pack]
name = "docker"
[[command]]
id = "docker.ps"
description = "List running containers"
phrases = ["what containers are running", "show me the containers"]
action = { type = "shell", run = "docker ps" }
[[command]]
id = "docker.logs"
phrases = ["show logs for {service}", "tail the {service} logs"]
slots = [{ name = "service", entity = "container or service name" }]
action = { type = "shell", run = "docker compose logs -f --tail=100 {{service}}" }Drop it in ./packs/, $VOXIDE_PACKS, or ~/.config/voxide/packs/.
A malformed pack is a hard error naming the file and the problem. Packs are never silently skipped — that failure mode wastes hours.
microphone ──► VAD ──► speech-to-text ──► wake word ──► matcher ──► slots ──► action
(cpal) (energy) (vosk) (transcript) (embedding) (shell)
Every stage is a trait, and that is load-bearing rather than decorative:
pub trait AudioSource { fn next_frame(&mut self, out: &mut [i16]) -> Result<usize>; }
pub trait Transcriber { fn accept(&mut self, samples: &[i16]) -> Option<Utterance>; }
pub trait Matcher { fn rank(&self, text: &str, limit: usize) -> Vec<Match>; }
pub trait Executor { fn execute(&self, cmd: &LoadedCommand, slots: &Slots) -> ...; }Because AudioSource has a WavSource implementation, the entire pipeline is
testable from recorded fixtures — no microphone, no CI hardware, no flakes.
Backends that need native libraries (mic, vosk, embed) are Cargo features
that are off by default, so cargo test works anywhere.
| crate | role |
|---|---|
voxide-core |
Pack format, slots, action specs, template rendering |
voxide-audio |
AudioSource trait, WAV and microphone sources, resampling, VAD |
voxide-asr |
Transcriber trait, Vosk backend, scriptable mock |
voxide-wake |
WakeDetector trait, always-on and transcript spotting |
voxide-intent |
Matcher trait, lexical baseline, semantic and hybrid backends, slot extraction |
voxide-models |
Pinned, checksum-verified model downloads and zip extraction |
voxide-actions |
Executor trait, shell backend with real deadlines |
voxide-pipeline |
The listening state machine. Emits events, performs no I/O |
voxide |
CLI |
$ cargo test --workspace # no native dependencies needed
$ cargo clippy --workspace --all-targets -- -D warnings
$ cargo run -p voxide -- eval --sweepThree backends cannot be compiled in the primary development environment and are therefore verified only by CI. This is deliberate, and those CI jobs gate the merge:
embedlinks ONNX Runtime, whose prebuilt binaries come from a CDN that the development sandbox's network policy blocks.micneeds ALSA headers, which that environment does not provide.vosklinkslibvosk, a native library installed separately.
If one of those jobs goes red, the corresponding feature is broken — a green
test job does not cover them.
The approach to embedding-based intent matching with a fingerprint-keyed vector
cache, and the shape of a capability-tiered scripting sandbox, were informed by
the author's earlier work on a fork of Priler/jarvis
by Abraham Tugalov. voxide shares no source code with that project and is
licensed independently. See NOTICE.
Dual-licensed under MIT or Apache-2.0, at your option.