pi-voicekit
Voice in + voice out for Pi CLI — local-only hold-to-talk STT for Chinese and English (21 offline models) plus offline TTS (Kitten Nano, Piper, Kokoro); no audio is uploaded to a remote speech service
Package details
Install pi-voicekit from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:pi-voicekit- Package
pi-voicekit- Version
0.6.5- Published
- Oct 25, 2026
- Downloads
- 3,762/mo · 905/wk
- Author
- cyfeng16
- License
- MIT
- Types
- extension
- Size
- 788.7 KB
- Dependencies
- 0 dependencies · 2 peers
Pi manifest JSON
{
"extensions": [
"./extensions/voice.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
pi-voicekit
Community continuation of
codexstar69/pi-listen(upstream, MIT — dormant since v7.2.2 in May 2026). Not affiliated with the original author. Old name:pi-listen. This fork removes every cloud backend (0.6.0): dictation, punctuation and speech all run on your machine, so upstream's two-backend setup no longer applies — see Privacy.
Voice in and voice out for Pi. Hold-to-talk dictation with 21 offline models and a live preview above the editor, plus speech that reads the agent's replies aloud. Recognition, punctuation and synthesis all run in process on sherpa-onnx: there is no key, no endpoint and no account to set up, and no audio is uploaded to a remote speech service.
Language scope: Chinese and English. Recognition and punctuation are validated for Chinese, English and mixed Chinese–English dictation only. Every other language a model happens to cover is out of scope and unvalidated — see Language scope.
0.6.0 — one path, the local one. Both cloud backends are gone from the code: no streaming transport, no cloud voices, nothing to authenticate. Upgrading from 0.5.x erases the retired fields from
settings.jsonon the first start — see Privacy. Earlier releases are documented in the changelog.
See How It Works
Setup (2 minutes)
1. Install the extension
# In a regular terminal (not inside Pi)
pi install npm:pi-voicekit
2. Pick a model
Open the panel with /voice-settings, go to the Models tab and press ↵ on the model
you want. It downloads once (43 MB for the smallest, 671 MB for the recommended Parakeet)
and becomes the active model. The language row then offers exactly the languages that model
supports.
/voice-setup opens the same panel — it is an alias, not a separate wizard.
Nothing here costs money or needs an account: the models are public downloads and the only budget they take is disk space.
3. Check audio capture
pi-voicekit auto-detects your audio tool. No manual install needed if you already have sox or ffmpeg.
| Priority | Tool | Platforms | Install |
|---|---|---|---|
| 1 | SoX (rec) |
macOS, Linux, Windows | brew install sox / apt install sox / choco install sox |
| 2 | ffmpeg | macOS, Linux, Windows | brew install ffmpeg / apt install ffmpeg |
| 3 | arecord | Linux only | Pre-installed (ALSA) |
When
PULSE_SERVERis set (SSH audio tunnel or remote PulseAudio) the order becomes ffmpeg → sox → arecord — network Pulse sources need ffmpeg.
4. What first start looks like
On launch pi-voicekit reports what it found and what is missing:
- A local model. With none selected the status line reads
MIC SETUPand voice stays off — a config that finished setup without a model has no working path, so the extension sends you to/voice-setupinstead of guessing a default. With a model, it readsMIC LOCAL. - An audio tool.
/voice testreports which one was detected, whether the microphone actually produced audio, and whether the sherpa-onnx runtime loaded.
When both are in place, hold SPACE and dictate.
Settings Panel
Everyday configuration lives in one place: /voice-settings. Its five tabs are General, Models,
Downloaded, Speak and Device. A few optional fields have no row of their own and are set in
settings.json instead — the preview's three are listed under
Streaming preview.
General — model, language, scope, voice
Set the local model, the dictation language, the scope (global or this project) and the voice
switch. Every row is a keyboard shortcut away — ↵ changes, ← → switches tab.
Models — browse, search, install
Browse 22 models from Parakeet, Whisper, Moonshine, SenseVoice, GigaAM, Paraformer, and Qwen3. Each model shows accuracy and speed ratings (●●●●○/●●●●○), fitness badges, and download status. Fuzzy search to find models fast. Press Enter to activate and download. The preview entry is the one exception: Enter downloads it when it is missing, and it can never be activated.
One of the 22 sits in its own Preview (streaming) group: a Chinese–English streaming Paraformer
(226 MB) that is download-only and can never become the final model. Install it and, while you
record, the words appear in a six-line panel above the editor, scrolled to the newest line.
The final transcript still comes from your offline model — the preview never writes the editor.
With the preview model not installed the preview adds nothing: no panel, no download, no prompt,
no notice. previewEnabled (a settings.json field, on by default) turns the panel off without
deleting the model.
Screenshots were captured on a v7.0 build: the panel now has five tabs (Speak was added) and the catalogue has grown to 22 models.
Downloaded — manage installed models
See what's installed, total disk usage, and which model is active. Press Enter to activate, x to delete. The preview model appears here too, marked preview only; Enter does not activate it. Models from Handy are auto-detected and can be imported without re-downloading.
Speak — TTS models and voices
Twenty offline TTS models (Kitten, Kokoro, Piper) from ~13 MB, downloaded on selection;
then pick a voice and set the speed. Replies are read aloud automatically once TTS is on —
ttsAutoSpeak (on by default) is a settings.json field with no row in this tab. The panel
has a Test row that synthesises a sample sentence through the same pipeline.
Device — hardware profile and dependencies
See your hardware profile (RAM, CPU, GPU), dependency status (sherpa-onnx runtime), available disk space, and total downloaded models. Model recommendations are based on this profile.
Punctuation — offline, marks-only
Not a tab: punctuation is an automatic step between the recogniser and the editor, on by default. When a transcript contains Chinese and is essentially unpunctuated (fewer than one mark per 20 characters), an in-process model inserts full stops, commas and question marks. It can only insert marks — every other byte of the text it is given is preserved — and any problem leaves the text with no mark added. English is not supported (the model measured F1 0.175 on English), and a transcript whose punctuation is already dense enough — one mark per 20 characters or more — is left alone; a sparser one is punctuated even if it carries a mark or two.
The model is a one-time ~285 MB download, fetched in the background from the moment a session
starts — or, on a machine where that did not happen, the first time a dictation needs it.
Either way a dictation that arrives while the download is still running gets no mark added.
Once the download has completed, verified against its digest and the engine has been
constructed — any of which can fail — later qualifying dictations are punctuated; until
then they get no mark added, and /voice-punctuation status reports the state. The
switch is the punctuationEnabled setting (a settings.json field, on by default — the
panel has no row for it), and /voice-punctuation status shows whether the step ran, why
it did not, and the state of the model.
Usage
Keybindings
| Action | Key | Notes |
|---|---|---|
| Record to editor | Hold SPACE (≥0.7s) |
Release to finalize. Capture starts when the hold reaches the threshold. |
| Toggle recording | Ctrl+Shift+V |
Works in all terminals — press to start, press again to stop. |
| Clear editor | Escape × 2 |
Double-tap within 500ms to clear all text. |
How recording works
- Hold SPACE — a countdown shows how long is left. Audio capture starts when the threshold is reached, so begin speaking then: anything said during the countdown is not recorded
- Keep holding — audio streams into a memory buffer, and a live preview above the editor shows the words as the streaming model recognises them (only when the preview model is installed)
- Release SPACE — recording continues for 1.5s (tail recording) to catch your last word, then finalizes
- The active local model transcribes the buffer on a worker thread (2–10 seconds), the transcript is cleaned and punctuated, and the text is inserted at the cursor in chunks small enough for the editor to take whole, ready to send
Commands
| Command | Description |
|---|---|
/voice-settings |
Settings panel — model, language, scope, device |
/voice-models |
Settings panel (Models tab) |
/voice-setup |
Settings panel — alias of /voice-settings |
/voice-language |
Settings panel, to change the dictation language |
/voice-speak <text> |
Speak text out loud (TTS) |
/voice-speak-test |
Speak a sample sentence |
/voice-speak-toggle |
Enable / disable TTS |
/voice-speak-stop |
Stop in-flight TTS playback |
/voice-punctuation |
Offline punctuation: status, model state, last decision |
/voice-hold-delay |
Set hold-to-talk delay (200-3000 ms, default 700) |
/voice-speak-models |
Browse / install TTS voice models |
/voice-speak-info |
Diagnose TTS state |
/voice-help |
Keyboard + command reference (or press F1) |
/voice test |
Full diagnostics — audio tool, microphone, sherpa runtime |
/voice on / off |
Enable or disable voice |
/voice dictate |
Continuous dictation (no key hold) |
/voice stop |
Stop active recording or dictation |
/voice history |
Recent transcriptions, as written to the editor |
/voice info |
Settings panel |
/voice |
Toggle on/off |
v7.1 keyboard
While in the settings panel:
| Key | Action |
|---|---|
← → |
switch tab |
↑ ↓ |
navigate row (skips group headings) |
↵ |
select / activate |
esc |
back to main / close panel |
type |
filter (search) |
bksp |
clear last search char |
While an install widget or playback indicator is mounted (no overlay in front):
| Key | Action |
|---|---|
esc |
cancel active install (most-recent first), then stop playback |
F1 |
open help overlay (always available) |
Language scope
Supported and validated: Chinese, English, and mixed Chinese–English dictation. Every recognition, punctuation and quality decision in this repository is made against those two languages.
Other languages are not supported: the models that cover them stay in the catalogue because they are general-purpose models, but nothing about them is validated here — no accuracy figures, no punctuation behaviour, no device recommendation. They are on the roadmap, not in the support matrix.
The language row offers whatever the active local model supports — a single-language model auto-sets the language and an English-only model hides the others, so pick the model first if the language you want is missing.
| Area | In scope | Out of scope — unvalidated |
|---|---|---|
| Recognition | Chinese, English, mixed Chinese–English | every other language in the catalogue (Japanese, Korean, Russian, Arabic, Ukrainian, Vietnamese, Spanish, …) |
| Punctuation | Chinese today; English under evaluation | other languages |
Local Models
21 offline models across 7 families, plus the streaming preview entry described above. The tables here cover the offline ones, the only models that can produce the final transcript. Sorted by quality — best models first. Only Chinese and English are validated; every other language listed below is out of scope — see Language scope.
Top picks
| Model | Accuracy | Speed | Size | Languages | Notes |
|---|---|---|---|---|---|
| Parakeet TDT v3 | ●●●●○ | ●●●●○ | 671 MB | 25 (auto-detect) | Best overall. WER 6.3%. |
| Parakeet TDT v2 | ●●●●● | ●●●●○ | 661 MB | English | Best English. WER 6.0%. |
| Whisper Turbo | ●●●●○ | ●●○○○ | 1.0 GB | 57 | Broadest coverage, unvalidated. |
Fast and lightweight
| Model | Accuracy | Speed | Size | Languages | Notes |
|---|---|---|---|---|---|
| Moonshine v2 Tiny | ●●○○○ | ●●●●● | 43 MB | English | 34ms latency. Raspberry Pi friendly. |
| Moonshine Base | ●●●○○ | ●●●●● | 287 MB | English | Handles accents well. |
| SenseVoice Small | ●●●○○ | ●●●●● | 228 MB | zh/en/ja/ko/yue | Best small Chinese model. |
Specialist
| Model | Accuracy | Speed | Size | Languages | Notes |
|---|---|---|---|---|---|
| GigaAM v3 | ●●●●○ | ●●●●○ | 225 MB | Russian | 50% lower WER than Whisper on Russian. |
| Whisper Medium | ●●●●○ | ●●●○○ | 946 MB | 57 | Good accuracy, medium speed. |
| Whisper Large v3 | ●●●●○ | ●○○○○ | 1.8 GB | 57 | Highest Whisper accuracy. Slow on CPU. |
Plus 8 language-specialised Moonshine v2 variants — two for Japanese, one each for Korean, Arabic, Chinese, Ukrainian, Vietnamese and Spanish — and only the Chinese one is inside the supported scope.
How local models work
Hold SPACE → audio captured to memory buffer (plus the live preview, when installed)
↓
Release SPACE → buffer sent to sherpa-onnx inside a worker thread
↓
ONNX inference on CPU (2–10 seconds)
↓
Final transcript inserted at the cursor
Dictation is batch, not streaming: the final transcript appears when you finish speaking, a few
seconds later — the live preview above the editor is a separate streaming model and never becomes
the text that is inserted. Models download automatically on first use — resumable, verified after
completion, and deduplicated (no double-downloads). The streaming preview model is the exception:
it is fetched only when you press ↵ on its row in the Models tab, never on first use. The settings
panel shows real-time download progress with speed and ETA.
Models from Handy (~/Library/Application Support/com.pais.handy/models/) are auto-detected and can be imported via symlink (zero disk duplication).
Performance
Measured on the maintainer's machine, local CPU, no network, over 70 published utterances (28 Chinese, 28 English, 14 mixed Chinese–English). RTF is processing time divided by audio duration — lower is better, and below 1.0 is faster than real time.
| Recogniser | Chinese RTF | English RTF | Mixed RTF | Characters per second |
|---|---|---|---|---|
| paraformer-zh | 0.014 | 0.013 | 0.016 | 247–983 |
| sensevoice-small | 0.028 | 0.029 | 0.040 | 134–451 |
| whisper-turbo | 0.372 | 0.369 | 0.395 | 7–27 |
End to end, the punctuation step adds almost nothing when it runs: on 27–32 character
inputs the model call measures p50 2.9 ms / p95 3.1 ms after a one-time 544 ms load,
all on CPU (see docs/BENCHMARKS.md). whisper-turbo is the slowest of the three by an
order of magnitude and the least accurate on this corpus — measured for comparison, not
recommended for CPU-only use.
Protocol, all result tables and the honest limits live in docs/BENCHMARKS.md — GitHub only, because npm ships the extension and this README.
Features
| Feature | Description |
|---|---|
| Local-only | One path: in-process recognition and speech. No endpoint, no key, no account — see Privacy |
| 22 local models | 21 offline recognisers plus one streaming preview model — Parakeet, Whisper, Moonshine, SenseVoice, GigaAM, Paraformer, Qwen3 — with accuracy/speed ratings |
| Live streaming preview | Words appear above the editor while you record, scrolled to the newest line. The preview model is optional (226 MB), download-only, silent when absent, and never produces the final transcript |
| Offline speech | 20 local TTS models (Kitten, Kokoro, Piper) read the agent's replies through the system player |
| Unified settings panel | One overlay panel for everyday configuration — /voice-settings. Optional fields without a row live in settings.json — the preview's three are listed under Streaming preview |
| Device-aware recommendations | Scores models against your hardware. Only best-in-class models get [recommended]. |
| Enterprise download pipeline | Pre-checks (disk, network, permissions), live progress with speed/ETA, post-verification |
| Handy integration | Auto-detects models from Handy app, imports via symlink |
| Audio fallback chain | Tries sox → ffmpeg → arecord in order — ffmpeg first when PULSE_SERVER is set |
| Hold threshold | 200–3000 ms, default 700 ms (/voice-hold-delay). Capture starts at the threshold, not on the press |
| Tail recording | Keeps recording 1.5s after release so your last word isn't clipped |
| Offline punctuation | Chinese dictations that come back unpunctuated get full stops, commas and question marks from an in-process model — marks only, no wording changes, fail-open, English not supported. Both engines stay resident (~300 MB each) and the model is fetched once — see Engine residency |
| Chinese + English | The supported and validated scope, mixed Chinese–English included. Every other language in the catalogue is unvalidated — see Language scope. |
| Continuous dictation | /voice dictate for long-form input without holding keys |
| Typing cooldown | Space holds within 400ms of typing are ignored |
| Sound feedback | macOS system sounds for start, stop, and error events |
| Cross-platform | macOS, Windows, Linux — Kitty protocol + non-Kitty fallback |
Architecture
# core
extensions/voice.ts Main extension — state machine, recording, UI, command surface
extensions/voice/config.ts Config loading, saving, migration
extensions/voice/onboarding.ts Model and language pickers, first-run flow
extensions/voice/audio-tool.ts Capture tool detection (sox / ffmpeg / arecord)
extensions/voice/hold-to-talk.ts Hold detection, Kitty and non-Kitty terminals
extensions/voice/release-controller.ts Recording lifecycle, release handling
# speech-to-text
extensions/voice/local.ts Model catalog (22 models), in-process transcription
extensions/voice/sherpa-engine.ts sherpa-onnx bindings — recognizer lifecycle, inference
extensions/voice/sherpa-loader.ts Lazy native module loading
extensions/voice/model-download.ts Download manager — resume, progress, verification, Handy import
extensions/voice/device.ts Device profiling — RAM, GPU, CPU, container detection
extensions/voice/transcript-cleanup.ts Filler removal and spoken-numeral restoration
extensions/voice/transcript-join.ts Segment stitching — CJK-aware, no space inside a sentence
# live preview and workers
extensions/voice/preview-entrypoint.ts One preview session and one final pass per recording
extensions/voice/preview-session.ts Preview session — events, text, degradation, display lifetime
extensions/voice/preview-stream.ts Streaming segmentation — endpoint and 60 s segment cap
extensions/voice/preview-worker-client.ts Preview worker client — claims, partials, flushes, retirement
extensions/voice/local-preview-worker.ts Preview worker entry — streaming recogniser off the main thread
extensions/voice/final-pass-client.ts Final-pass worker client — timeouts, reuse, invalidation
extensions/voice/final-pass-worker.ts Final-pass worker entry — offline recogniser off the main thread
extensions/voice/transcript-composer.ts Committed prefix plus the mutable partial tail
extensions/voice/preview-panel.ts Live draft widget — six rows, tail visible, no header
extensions/voice/editor-sink.ts Cursor insertion in chunks the editor takes whole
# offline punctuation
extensions/voice/punctuation.ts Offline punctuation — marks-only splice, fail-open step
extensions/voice/punctuation-model.ts Punctuation model — catalogue, digest verification, download
# text-to-speech
extensions/voice/speak.ts Speak entry point, auto-speak wiring
extensions/voice/tts-engine.ts sherpa-onnx TTS synthesis
extensions/voice/tts-local-models.ts Local TTS catalog — 20 models (Kitten, Kokoro, Piper)
extensions/voice/tts-playback.ts Playback, buffering, player detection
extensions/voice/tts-text-filter.ts Code-block stripping, sentence prep
extensions/voice/tts-onboarding.ts TTS onboarding flow
extensions/voice/tts-onboarding-overlay.ts TTS onboarding overlay
extensions/voice/tts-install-progress.ts Model install progress widget
extensions/voice/tts-playback-indicator.ts Speaking indicator widget
# settings and UI
extensions/voice/settings-panel.ts Settings panel — overlay, 5 tabs
extensions/voice/ui-picker.ts Generic list picker
extensions/voice/ui-help-overlay.ts Keyboard and command reference
extensions/voice/ui-aura.ts Visual primitives (Liquid Braille, Aurora)
extensions/voice/ui-widget-base.ts Widget registry and base class
extensions/voice/ui-render-ticker.ts Shared render ticker
extensions/voice/ui-icons.ts Glyph and icon set
extensions/voice/ui-width.ts CJK-aware visual width helpers
extensions/voice/ui-locale-labels.ts Native language and voice labels
# benchmarks (repository only — never published)
bench/manifest.jsonl Benchmark slices: dataset, split, license, selection rule
bench/fetch.py Deterministic fetcher — writes bench/data/ (gitignored)
# types
extensions/voice/sherpa-onnx-node.d.ts Type declarations for the optional native module
Configuration
Settings stored in Pi's settings files under the voice key:
| Scope | Path |
|---|---|
| Global | ~/.pi/agent/settings.json |
| Project | <project>/.pi/settings.json |
Both paths follow Pi: the global one sits in Pi's agent directory, so a build run with
PI_CODING_AGENT_DIR set uses that directory instead of ~/.pi/agent; the project one uses
Pi's config-directory name rather than a literal .pi. Downloaded models are separate and always
live under ~/.pi/models.
{
"voice": {
"version": 6,
"enabled": true,
"language": "zh",
"localModel": "parakeet-v3",
"scope": "global",
"onboarding": { "completed": true, "schemaVersion": 6 }
}
}
A file carried over from 0.5.x may still hold the retired speech-backend fields; the first start erases them and says so — see Privacy.
Hold-to-talk delay defaults to 700 ms (/voice-hold-delay accepts 200–3000 ms).
Streaming preview
Three ordinary optional fields: an explicit project value wins, then the global one, then the
default. All three are settings.json fields — the panel has no rows for them.
| Setting | Default | Notes |
|---|---|---|
previewEnabled |
true |
Mount the live preview while recording. false also keeps the preview worker from starting. |
localPreviewModel |
streaming-paraformer-bilingual-zh-en |
The streaming catalogue entry. It is never usable as the final model. |
maxRecordingSeconds |
120 |
Budget for one recording, in seconds — a positive finite number. |
The recording budget and the preview's 60 s segment cap are separate limits: the cap ends a
recognition segment early, it does not stop the recording. The preview model lives in
~/.pi/models/streaming-paraformer-bilingual-zh-en/ and appears in the Downloaded tab like any
other download, marked preview only; pressing Enter does not activate it.
Punctuation
Punctuation is automatic and on by default. When a dictation finishes, the transcript is cleaned first — see the cleanup switches in the table below — then punctuated locally if the text contains Chinese and its punctuation density is below one mark per 20 characters; the step only inserts marks and changes nothing else. The model runs in process, and the one-time download of the model itself is the only traffic it causes. English is not supported in this version, and English text is skipped by the same rule. A transcript the rule declines gets no mark added, and neither does any transcript whose punctuation fails at any point.
Both engines stay resident for the session: the recogniser as before, and the punctuation engine
built when a session starts, so no dictation waits for it (measured 0.2 ms for the call, engine
usable within 100 ms, 50-544 ms to build). On a machine that has never fetched the model, that
first session fetches it: 285 MB on disk, and roughly 300 MB of memory per engine on the reference
machine — 292 MB for punctuation, 316 MB for the paraformer-zh recogniser. See
Engine residency for the numbers.
| Setting | Scope | Default | Notes |
|---|---|---|---|
punctuationEnabled |
global and project | true |
Runs the offline punctuation step on qualifying transcripts. |
fillerRemovalEnabled |
global and project | true |
Removes the filler particles 呃 and 嗯 from a finished transcript. |
numeralNormalizationEnabled |
global and project | true |
Restores digits in spoken identifiers (k 八 s → k8s, bf 十六 → bf16) and in a bare multi-point version (零点九九点一 → 0.99.1). |
punctuationNoticeShown |
global only | false |
Machine-local bookkeeping for the one-time upgrade notice. |
All three switches resolve the same way. A machine-wide false is final: no project
block can turn the step back on, even one that saved true before the global value was
set to false. Below that, an explicit project value wins — a repository that saved
false keeps the step off when the global value later turns on — then the global value,
then the default. An omitted value in a project block inherits the global one rather than
the default. The cleanup runs before punctuation and on its own switches, so turning
punctuation off still leaves a cleaned transcript.
The first project-scope save writes the values the config resolved to — including
inherited ones — into the repository's .pi/settings.json, so all three keys become
concrete booleans there. That pins the project value; it does not exempt the repository
from a later machine-wide false, which still applies everywhere.
/voice-punctuation status prints the punctuation switch state, whether the model is
downloaded and digest-verified, and what the step decided on the last dictation of the
session. The model lives in ~/.pi/models/punct-ct-transformer-zh-en/, appears in the
Downloaded tab like any other download, and selecting that row does not make it a
recogniser. The step writes one metadata-only voice-punctuation entry per dictation — no
transcript text — so a decision is fact; under PI_VOICE_DEBUG it also logs one line per
dictation with the character count, marks before and after, the gate, the reason when it
did not run, and the elapsed time. The removed postProcess* keys from older releases
are ignored when loading — never migrated, and left in place when the settings file is
saved.
Privacy
No audio is uploaded to a remote speech service. Recording, recognition, punctuation and speech synthesis all run inside the Pi process — sherpa-onnx is loaded natively and works on buffers in memory. There is no endpoint to configure, no key to paste, no account to create.
That is the guarantee the code keeps, and it is narrower than "nothing ever leaves the machine". One configuration this README supports sends data off it, and it is your own configuration rather than something the extension does by itself.
System audio routing. Playback hands the synthesised WAV to the system player, and that
process inherits your environment. Point PulseAudio at another host (PULSE_SERVER, an SSH
audio tunnel, a remote desktop session) and the player sends the speech there — the audio
tool honours the same variable, so a remote microphone means your voice
crosses that link before this extension ever sees it.
That route is not a speech service this extension chose. If you need the audio kept on this machine, local playback is only half of it: capture honours the same variable, so both ends — the player and the microphone — have to stay local.
Everything you send to the agent — typed, or dictated and then submitted — is text, and it goes to whatever model Pi is configured with. That is not an exception to the claim above: the claim is about audio, and sending a message is the path every request takes.
What the gate checks
tests/no-cloud-egress.test.ts is the enforceable half of that claim. It parses every
module under extensions/ and fails when one imports the removed WebSocket transport,
names a retired vendor identifier or host, or calls fetch outside the two
model-download modules (model-download.ts, tts-local-models.ts).
That is narrower than "this software can never open a socket", and it is worth saying so plainly: the gate binds the transport and the vendor this repository deleted, and it keeps ordinary code from growing a new call site. A future module could still build its own network client — which is why this is a test you can read, not a promise in prose.
The one outbound step this extension takes
Model files are downloaded from HuggingFace or GitHub releases: the speech models you pick, the
TTS models you pick, the ~285 MB punctuation model on a machine that needs it, and the optional
streaming preview model when you press ↵ on its row — never before. Downloads are resumable and
checked when they finish: the speech model files must all be present at their expected sizes, and
the punctuation model is verified file by file against the SHA-256 digests recorded in this
repository. After a model is on disk, dictation and speech work with the network down.
What stays on the machine
- A dictation is held in a memory buffer and dropped after the transcript is written; no
recording is stored. (
/voice testdoes write a one-second sample to the temp directory to prove the microphone works — it is deleted as soon as its size is read.) - Speech playback writes the synthesised audio to a temp WAV with mode
0600and hands it to the local player (afplayon macOS,paplay/aplayon Linux, PowerShell'sSoundPlayeron Windows), then deletes it. The player is local but it is a separate process that inherits your environment, which is why system audio routing is the one exception above instead of something this extension can promise about. /voice historykeeps this session's transcripts in memory only.PI_VOICE_DEBUG=1appends diagnostics — including a 100-character transcript preview — to$TMPDIR/pi-voice-debug.log. It is off unless you set it.- No telemetry, analytics or crash reporting is transmitted: there is no crash reporter and no remote logging.
Upgrading from 0.5.x
0.6.0 removed the cloud backends, so the first start erases a stored provider API key and a
localEndpoint from settings.json and tells you it did — the field names are listed in
SECURITY.md. The rest of the file is left exactly as it was, byte for byte,
including formatting JSON.parse/JSON.stringify would not preserve. An API key exported from
a shell profile or ~/.env.secrets is no longer read at all; the extension does not edit those
files, so delete the line yourself.
See SECURITY.md for vulnerability reporting.
Troubleshooting
Run /voice test inside Pi for full diagnostics.
| Problem | Solution |
|---|---|
| No microphone? "No audio capture tool found" | brew install sox or brew install ffmpeg |
| Remote microphone records silence | Audio over PulseAudio/SSH — install ffmpeg on the Pi side (capture then prefers ffmpeg) |
| Space doesn't activate voice | Run /voice-settings — voice may be disabled, or no model is selected |
| Nothing transcribes | /voice-settings → Device tab for sherpa-onnx status; /voice test names the failure |
| Download failed | Partial downloads auto-resume on retry. Check disk space in Device tab. |
| Unsupported platform (32-bit ARM, Windows on ARM64, musl) | The native runtime has no build for it; the message names the constraint. Use a glibc x64/arm64 system or container, or Windows on x64 |
dyld: Library not loaded: libsimdjson on macOS |
Homebrew Node ABI mismatch — run brew reinstall node or switch to version-managed Node (mise, fnm, nvm) |
More in docs/troubleshooting.md.
License
MIT — original by @baanditeagle, maintained by CyFeng16