voice dictation layer for wayland compositors with offline wakeword detection and cloud transcription.
- offline wakeword — always-listening via openWakeWord, with Vosk retained for command/control fallback
- cloud transcription — high-quality speech-to-text via groq (whisper-large-v3-turbo)
- compositor integration — hyprland (primary), niri (planned)
- window locking — pin output to a specific window, even when focused elsewhere
- visual feedback — green border on locked windows, notification indicators
- voice commands — natural voice control with built-in misrecognition tolerance
pipx install kasprgit clone https://github.com/kastheco/kaspr
cd kaspr
pipx install -e .- python 3.11+
- wtype — text injection on wayland
- hyprwhspr — transcription engine (until standalone mode is complete)
- pipewire — audio backend
after installing, run the setup command to download the wake assets, create your config, and optionally install systemd services + hyprland rules:
kaspr setup # basic: config + vosk model
kaspr setup --systemd # + install systemd user service
kaspr setup --hyprland # + install hyprland border rules
kaspr setup --systemd --hyprland # all of the abovethis creates:
~/.config/kaspr/config.toml— configuration~/.local/share/kaspr/— wake assets, training workspaces, and logs~/.local/state/kaspr/— runtime state (lock file, markers)
kaspr setup --hyprland creates ~/.config/hypr/kaspr-rules.conf. you need to source it in your hyprland config:
# add to ~/.config/hypr/hyprland.conf
source = ~/.config/hypr/kaspr-rules.confset your groq API key:
export GROQ_API_KEY=your_key_hereor configure a different env var name in config.toml.
most commands use the prefix scribe (configurable). built-in aliases handle common misrecognitions: scrape, scrub, kaspr, casper, kasper, kasparov, cast per, scribes, transcribe.
| command | action |
|---|---|
scribe on |
start recording |
scribe off |
stop recording, paste text to focused window |
scribe send |
stop recording, paste text, press enter |
scribe cancel |
stop recording, discard (nothing pasted) |
scribe lock |
lock output to the currently focused window |
scribe unlock |
clear window lock |
focus left |
shift focus left |
focus up |
shift focus up |
focus right |
shift focus right |
focus down |
shift focus down |
scribe lock captures the currently focused window. all subsequent dictation goes to that window, regardless of where your focus is.
- locked window gets a green border (via hyprland tag + window rule)
- a persistent notification shows which window is locked
- focus swaps to the locked window for paste, then returns to where you were
- if you're already on the locked window, no swap happens
scribe unlockclears everything
- speak clearly with a short pause before the command
- commands trigger on partial recognition (fast response)
scribe sendis useful for chat apps — dictate, then auto-submitscribe cancelis your "nevermind" — discards without pastingscribe commandsis reserved and intentionally does nothing
kaspr's custom wakeword workflow now targets the shortest practical local loop on the current Linux x86_64 workstation. We intentionally optimize for proving out hey kaspr locally first and defer portability/polish until the approach is useful.
Start with the dedicated guide: docs/custom-wakeword-local.md
You can also print the same checklist from the CLI:
kaspr wake local-test-loop --phrase "hey kaspr"# 1) bootstrap the pinned Python 3.10 training env
kaspr wake bootstrap-training
# 2) create or refresh the training workspace (safe to rerun; preserves existing hey-kaspr clips/models)
kaspr wake init-training --phrase "hey kaspr"
# 3) write the training config and patch the installed openWakeWord package
kaspr wake train-base bootstrap --phrase "hey kaspr"
# 4) run the staged local loop
kaspr wake train-base generate --phrase "hey kaspr" -v
kaspr wake train-base augment --phrase "hey kaspr" -v
kaspr wake train-base train --phrase "hey kaspr" -v
# 5) confirm the base model export
kaspr wake check-stage verify --phrase "hey kaspr"
# 6) after the base model exists, collect local verifier + false-activation clips
kaspr wake record-sample verifier-positive --phrase "hey kaspr" --seconds 2.0
kaspr wake record-sample verifier-negative --phrase "hey kaspr" --seconds 8.0
kaspr wake record-sample false-activation --phrase "hey kaspr" --seconds 5.0
kaspr wake train-verifier --phrase "hey kaspr"
# 7) patch ~/.config/kaspr/config.toml and restart kaspr
kaspr wake config-snippet --phrase "hey kaspr" --apply
systemctl --user restart kasprExpected outputs live under ~/.local/share/kaspr/training/hey-kaspr/models/:
hey-kaspr.onnxhey-kaspr_verifier.pkl
Use these checks while iterating:
kaspr wake doctor --phrase "hey kaspr"
kaspr wake check-stage verify --phrase "hey kaspr"
kaspr wake check-stage deploy --phrase "hey kaspr"check-stage deploy now verifies the base ONNX output only. The verifier and false_activations/ samples stay as optional local follow-up work in the same workspace.
For false-positive evaluation, keep kaspr running on the same workstation, record clips into false_activations/, and note every accidental trigger before you decide whether to retrain, tighten thresholds, or collect more verifier negatives.
kaspr listen # start the wakeword listener (foreground)
kaspr setup [--systemd] [--hyprland] # initial setup
kaspr status # show current state (recording, lock)
kaspr wake status # show wake listener state
kaspr wake local-test-loop # print the shortest local custom wakeword loop
kaspr wake init-training --phrase "hey kaspr"
kaspr wake train-base run-all --phrase "hey kaspr"
kaspr wake record-sample verifier-positive --phrase "hey kaspr" --seconds 2.0
kaspr wake train-verifier --phrase "hey kaspr"
kaspr lock # lock output to current window (CLI)
kaspr unlock # clear window lock (CLI)
kaspr --version # show version~/.config/kaspr/config.toml:
[transcription]
provider = "groq"
api_key_env = "GROQ_API_KEY"
model = "whisper-large-v3-turbo"
language = "en"
[wakeword]
enabled = true
backend = "openwakeword"
prefix = "scribe" # recording command prefix
prefix_aliases = ["scrape", "scrub", "kaspr", "casper", "kasper", "kasparov", "cast per", "cast percent"]
debounce_seconds = 2.0
oww_model = "hey mycroft" # or /path/to/custom/kaspr.onnx
oww_inference_framework = "onnx"
oww_verifier_model = "" # optional /path/to/custom_verifier.pkl
oww_verifier_threshold = 0.3
[compositor]
compositor = "auto" # auto, hyprland, niri
lock_border_color = "rgb(00ff88)" # green border on locked windows
[output]
paste_tool = "wtype" # wtype or ydotool
lowercase = true # lowercase all output
send_marker_ttl = 5.0 # seconds before send marker expires
fragment_min_length = 3 # drop transcripts <= this length
[log]
log_transcripts = true
log_events = true
# log_dir = "/custom/path" # defaults to ~/.local/share/kaspr/logsif you ran kaspr setup --systemd, enable the service:
systemctl --user enable --now kasprcheck status:
systemctl --user status kaspr
journalctl --user -u kaspr -fkaspr can run as a hyprwhspr plugin instead of standalone. create ~/.config/hyprwhspr/patches/kaspr_bridge.py:
from kaspr.hyprwhspr import install
install()this monkey-patches hyprwhspr to route all transcripts through kaspr's router (stripping, locking, send mode, logging).
┌─────────────────────┐ FIFO ┌──────────────────┐
│ kaspr wakeword │──────────────→│ hyprwhspr │
│ (vosk, offline) │ start/stop │ (groq cloud) │
│ always listening │ cancel │ records + sends │
└─────────────────────┘ └────────┬─────────┘
│
kaspr.hyprwhspr bridge
│
┌────────▼─────────┐
│ kaspr router │
│ ├ strip phrases │
│ ├ window lock │
│ ├ send/enter │
│ ├ transcript log │
│ └ plugin hooks │
└──────────────────┘
| issue | fix |
|---|---|
| wakeword not responding | check journalctl --user -u kaspr -f |
| "scribes and" leaking into text | already handled — report new variants as issues |
| lock border not showing | make sure kaspr-rules.conf is sourced in hyprland.conf |
GROQ_API_KEY not found |
set it in your shell profile or systemd environment |
| vosk model missing | run kaspr setup |
- standalone transcription engine (remove hyprwhspr dependency)
- niri compositor full support
- plugin system for post-transcription hooks (discord, obsidian, etc.)
- overlay/OSD for live transcript preview
- AUR package
- pipewire audio capture (replace sounddevice)
MIT