Wake-word voice assistant daemon for Arch Linux.
Uses wake word detection (Porcupine) with local audio capture/VAD, and then routes speech to either an allow-listed command executor or an LLM.
Commands are executed only if explicitly defined in commands.json.
- Wake word detection (Porcupine)
- Local audio capture + VAD
- Intent routing with strict command allow-list
- LLM responses (Groq / Mistral)
- Web fallback via Tavily (only when required)
- OSD / notification support
- Safe command execution with confirmation
Install build + runtime deps:
sudo pacman -S --needed \
git \
rust \
cargo \
python \
python-virtualenv \
alsa-libAudio backends depend on your system:
- PipeWire users: ensure
pipewire+pipewire-pulseare installed/running. - ALSA-only users: ensure your ALSA device is working.
git clone https://github.com/Bumblebee-3/BTW-daemon.git
cd BTW-daemon/btwd
cargo build --releaseThe binary will be at:
target/release/btwd
If you want it on your PATH:
install -Dm755 target/release/btwd "$HOME/.local/bin/btwd"BTWd expects the Porcupine shared library and model files to be available locally.
- Download Porcupine 4.0 from Picovoice and extract it.
- Place the following files somewhere stable (example layout below):
$HOME/.local/lib/libpv_porcupine.so
$HOME/.local/share/porcupine/porcupine_params.pv
$HOME/.local/share/porcupine/btw.ppn
This repo already contains a wake-word file btw.ppn at the project root; you can use that path directly in config.
Make sure the loader can find libpv_porcupine.so.
Example (manual run):
export LD_LIBRARY_PATH="$HOME/.local/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"The ASR worker is a small Python process in ml/btw_ml.py.
cd btwd
python -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install groq numpyStart from example.config.toml and adjust paths.
# Identity (optional)
name = "btwd"
description = "Wake word voice assistant daemon"
[wake_word]
# Wake-word model (Porcupine)
ppn_path = "/home/you/.local/share/porcupine/btw.ppn"
model_path = "/home/you/.local/share/porcupine/porcupine_params.pv"
device = "cpu"
sensitivity = 0.6
[speech]
# VAD/utterance control
silence_threshold = 0.01 # normalized RMS (0.0..1.0)
silence_duration_ms = 700 # continuous silence required
max_utterance_seconds = 30 # hard safety cap
[execution]
# Command confirmation safety
confirmation_timeout_seconds = 10
dry_run = false
[ui]
# Notifications (works with swaync)
listening_notification = true # toast on wake
osd = true # allow text notifications
osd_timeout_ms = 2000 # auto-dismiss (ms)
[speech_output]
# TTS output (LLM provider dependent)
enabled = true
provider = "groq" # "groq" or "mistral"
voice = "alloy"
format = "wav"
rate = 1.0
[search]
# Web fallback via Tavily
enabled = true
timeout_ms = 3500
country = "india" # optional (e.g. "india", "us")
[llm]
# LLM backend used for intent + answering
provider = "mistral"Create .env in the project root (or export these in your service environment).
Start from example.env:
# Required for Porcupine wake word
PICOVOICE_ACCESS_KEY=pk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# Required for ASR (Groq Whisper) and TTS/summarization
GROQ_API_KEY=gsk_yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
MISTRAL_API_KEY=abcdefghijklmnopqrstuvwxyz123456
# enables read-only web answers via Tavily
TAVILY_API_KEY=ttttttttttttttttttttttttttttttttCommands are an allow-list: BTWd will only execute commands that exist in your commands.json.
dangerous: truecommands trigger a strict confirmation flow.- Templates use simple placeholders like
{value}/{delta}.
Start from example.commands.json:
[
{
"id": "lock_screen",
"category": "system",
"description": "Lock the current user session",
"examples": ["lock my computer", "lock the screen"],
"dangerous": false,
"parameters": {},
"shell_command_template": "loginctl lock-session"
},
{
"id": "system_shutdown",
"category": "power",
"description": "Shut down the system",
"examples": ["shut down", "power off"],
"dangerous": true,
"parameters": {},
"shell_command_template": "systemctl poweroff"
}
]Manual run (recommended while iterating):
export LD_LIBRARY_PATH="$HOME/.local/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
set -a
source ./.env
set +a
# BTWd uses XDG config paths by default:
# ~/.config/btw/config.toml
# ~/.config/btw/commands.json
# ~/.config/btw/.env
mkdir -p ~/.config/btw
cp -n ./example.config.toml ~/.config/btw/config.toml
cp -n ./example.commands.json ~/.config/btw/commands.json
cp -n ./example.env ~/.config/btw/.env
./target/release/btwdSystemd user service (example):
- The repo includes
btw.service(adjust paths to your user/home). - Optional drop-in for TTS config:
systemd/btw.service.d/override-tts.conf.
- Requires explicit command definitions (
commands.json); unknown commands are not executed.
- Picovoice (Porcupine)
- Groq
- Mistral
- Tavily
[ ] Add plugins integration
[ ] Google Calendar
[ ] Gmail