Skip to content

kastheco/kaspr

Repository files navigation

kaspr

voice dictation layer for wayland compositors with offline wakeword detection and cloud transcription.

features

  • 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

install

pipx (recommended)

pipx install kaspr

from source

git clone https://github.com/kastheco/kaspr
cd kaspr
pipx install -e .

dependencies

  • python 3.11+
  • wtype — text injection on wayland
  • hyprwhspr — transcription engine (until standalone mode is complete)
  • pipewire — audio backend

setup

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 above

this creates:

  • ~/.config/kaspr/config.toml — configuration
  • ~/.local/share/kaspr/ — wake assets, training workspaces, and logs
  • ~/.local/state/kaspr/ — runtime state (lock file, markers)

hyprland border rule

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.conf

environment

set your groq API key:

export GROQ_API_KEY=your_key_here

or configure a different env var name in config.toml.

voice commands

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

window lock

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 unlock clears everything

tips

  • speak clearly with a short pause before the command
  • commands trigger on partial recognition (fast response)
  • scribe send is useful for chat apps — dictate, then auto-submit
  • scribe cancel is your "nevermind" — discards without pasting
  • scribe commands is reserved and intentionally does nothing

custom wakeword training

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"

shortest-path loop

# 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 kaspr

Expected outputs live under ~/.local/share/kaspr/training/hey-kaspr/models/:

  • hey-kaspr.onnx
  • hey-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.

cli

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

configuration

~/.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/logs

systemd

if you ran kaspr setup --systemd, enable the service:

systemctl --user enable --now kaspr

check status:

systemctl --user status kaspr
journalctl --user -u kaspr -f

hyprwhspr bridge mode

kaspr 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).

architecture

┌─────────────────────┐     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   │
                                      └──────────────────┘

troubleshooting

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

roadmap

  • 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)

license

MIT

About

No description, website, or topics provided.

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors

Languages