CLI.md 12 KB

CLI Reference

On AMD/ROCm hosts after scripts/install-rocm.sh, use .venv/bin/stt-daemon or uv run --no-sync stt-daemon. A plain uv run re-syncs the venv and reverts the ROCm wheels.

The stt-daemon CLI provisions the virtual sink, runs the daemon, and lists devices. Every subcommand accepts the common overrides --config, --device, --backend, --model, --language, and -v/--verbose.

Subcommands

The stt-daemon CLI supports these subcommands. Every subcommand also accepts the common overrides --config, --device, --backend, --model, --language, and -v/--verbose (for example stt-daemon setup --device ...).

setup — Create virtual sink and loopbacks

Interactive setup (shows device picker):

uv run stt-daemon setup

Non-interactive setup (specify sources directly):

uv run stt-daemon setup --sources "alsa_input.usb-Foo,alsa_output.pci-0000.monitor"

setup also accepts --yes to skip the confirmation prompt. Like every subcommand, it accepts the common overrides --config, --device, --backend, --model, --language, and -v/--verbose.

This creates:

  • A null sink (by default VirtualMicSink)
  • A remap-source to expose the sink's monitor as a microphone (by default VirtualMic)
  • Loopback modules routing each selected source into the sink
  • A state file tracking all module IDs for later cleanup

run — Start the daemon (auto-provisions if needed)

uv run stt-daemon run

If no saved provisioning exists and the sink is missing, the daemon will:

  1. Show the interactive device picker
  2. Create the sink and loopbacks (if sources are selected)
  3. Start listening and transcribing

You can also override settings:

uv run stt-daemon run --device "alsa_input.usb-..." --language es --model base
uv run stt-daemon run -v  # Verbose logging
uv run stt-daemon run --check  # Test audio capture + model, then exit

list — Show devices and provisioning status

uv run stt-daemon list

Output shows:

  • Input devices (real microphones)
  • Output devices (speakers/headphones; captured via their .monitor source)
  • Current provisioning state (if a sink has been created)

clear — Remove virtual sink and loopbacks

uv run stt-daemon clear
# or without confirmation prompt:
uv run stt-daemon clear --yes

The provisioning state lives in audio.json (or $STT_DAEMON_STATE) in the current directory. This unloads all tracked modules and removes the state file. If audio.json is already gone (reboot, manual delete, crash), clear --yes still finds orphaned modules by name (VirtualMicSink/VirtualMic via pactl list short modules) and unloads them in reverse order (loopbacks → remap-source → null-sink), so the command is idempotent — safe to re-run and to recover from Sink 'VirtualMicSink' already exists but is not tracked.

up — Launch daemon, assistant, and optionally TUI

uv run stt-daemon up              # Start daemon + assistant (separate processes)
uv run stt-daemon up --with-tui   # Also launch the interactive TUI viewer

This subcommand runs the entire system as a supervised set of child processes:

  • daemon — transcription engine (listens to audio, publishes transcriptions)
  • assistant — controller (subscribes to transcriptions, serves the control socket)
  • TUI (optional) — viewer/controller UI (connects to the assistant via control socket)

The supervisor manages their lifetimes: if any child exits or Ctrl+C is pressed, all remaining children are cleanly shut down. Without --with-tui, you can still run a separate uv run stt-tui in another terminal to view and control the assistant.

With --with-tui, the daemon and assistant output is discarded so it cannot corrupt the screen, while the TUI keeps the terminal. Without --with-tui, all output goes to the terminal. The supervisor writes nothing to disk.

Like all subcommands, up also accepts --config, --device, --backend, --model, --language, and -v (verbose) flags.

Example workflow:

# One-command startup with TUI (everything stops when you quit the TUI)
uv run stt-daemon up --with-tui

# Or, start daemon + assistant only
uv run stt-daemon up

# Then in another terminal, launch the TUI whenever you want
uv run stt-tui

speaker-enroll — Pre-enroll a speaker from WAV files

uv run stt-daemon speaker-enroll --name Efren --wav transcripts/2026-09-28-14-30-22/audio/143022-123-pause.wav

Registers a known speaker up front, without waiting for the daemon to mint a provisional id from a live voice. --name is required; pass --wav once per clip, and each file contributes one prototype (capped at max_prototypes, oldest evicted). On success the command prints something like enrolled 'Efren' as spk_0002 (2 clip(s), 7.4s).

Each WAV must be 16 kHz mono 16-bit PCM; the clips the daemon saves under <session_dir>/<id>/audio/ are already in that format, so they need no conversion, and each must be at least [speaker].min_enroll_s seconds. The store is single-writer and owned by the daemon, so stop it before enrolling, or pass --force to bypass the refusal. Like every subcommand, speaker-enroll also accepts --config.

--dedupe-threshold F (default 0.98) sets the cosine at or above which two prototypes of the same speaker count as duplicates. The command strips near-duplicate prototypes, including any pre-existing ones for the named speaker, before it finishes, and reports it as removed N duplicate prototype(s) from the ....

Example with several clips:

uv run stt-daemon speaker-enroll --name Efren \
  --wav transcripts/2026-09-28-14-30-22/audio/143022-123-pause.wav \
  --wav transcripts/2026-09-28-14-30-22/audio/150210-456-end_of_turn.wav

speaker-enroll-seed — Assisted enrollment from a seed clip

uv run stt-daemon speaker-enroll-seed --name "Efren Yevale Varela" --seed clips/efren-seed.wav

Enrolls from ONE clip you are certain is the speaker. The command scans --audio-dir (default [output].session_dir, the session root transcripts/, searched recursively for *.wav), embeds each *.wav, ranks them by cosine similarity to the seed, and offers the ones at or above --min-score (default 0.55) and at least [speaker].min_enroll_s seconds for review. --name and --seed are required; the seed must be 16 kHz mono 16-bit PCM.

Review is interactive: each candidate shows [i/N] <filename> (score X.XX, Y.Ys) and you choose Accept, Skip, Play, or Quit. --count N (default 5) caps how many reviewed clips are accepted; --yes skips the prompts and auto-accepts the top candidates (required when stdin is not a TTY); --no-play disables playback; --max-scan N caps how many files are embedded. Quit or Ctrl+C writes nothing and prints the equivalent stt-daemon speaker-enroll --wav ... command. Like speaker-enroll, this refuses to run while a daemon appears to be running unless --force is passed, and accepts --config.

Each candidate is flagged during review: duplicate when its embedding is already covered by one of that speaker's prototypes (cosine at or above --dedupe-threshold, default 0.98), and other-speaker:<name> when it scores highly against a different enrolled speaker (cosine at or above --other-threshold, default 0.45), a contamination hint. Accepting a duplicate is refused with skipping <name>: already covered by this profile, without consuming the accept budget. The incoming set (seed plus accepted clips) is de-duplicated before enrolling and pre-existing near-duplicate prototypes of that speaker are removed afterward, both reported on stdout.

Example, accepting three reviewed clips non-interactively:

uv run stt-daemon speaker-enroll-seed --name "Efren Yevale Varela" \
  --seed clips/efren-seed.wav --count 3 --yes

Tuning and rebuilding. Running either enrollment command for a name that already exists appends prototypes (the oldest is evicted once max_prototypes is reached) rather than resetting the profile, so you tune by enrolling more diverse clips and flush a bad prototype by adding enough new ones. To start clean, stop the daemon, delete speakers/, and re-enroll. After any enrollment, tune-up, or wipe, restart the daemon so it reloads the store. See the Tuning an existing profile and Wipe and rebuild sections of docs/SPEAKERS.md.

speaker-eval — Measure accuracy against labeled clips

uv run stt-daemon speaker-eval --labeled labeled/

Scores the store against a directory of labeled clips. Each immediate subdirectory of --labeled is a speaker display name and holds that person's *.wav clips (16 kHz mono 16-bit PCM). The command embeds each clip, labels it with the same matcher the daemon uses, and prints a plain-text report: total, accuracy, correct, wrong, uncertain (top-1 correct/total), unknown, a per-speaker table (name | n | correct | acc), the top confusions (true -> predicted (count), up to 10), and margins (count, mean, p10 of top1 - top2). The run is read-only, so it never writes the store and the daemon does not need to be stopped. It requires the model and uv sync --extra speaker, and fails clearly if the store was built with a different embedding model (dimension mismatch).

--min-accuracy F (optional) exits non-zero when measured accuracy is below F, so it works as a tuning gate.

Example, using it as a gate:

uv run stt-daemon speaker-eval --labeled labeled/ --min-accuracy 0.9

triage-eval — Calibrate the triage questions on a saved session

Replays a session's transcripts.jsonl through the same classifier state the assistant builds (speaker-labeled, condensed under state_char_budget) and prints, per question, the mean p and how often it would fire at each threshold. Use it before editing threshold values in questions.json — a question that fires on most windows carries no information.

stt-daemon triage-eval --session transcripts/2026-10-27-14-11-44
stt-daemon triage-eval --session transcripts/2026-10-27-14-11-44 --window 10 --stride 10
stt-daemon triage-eval --session transcripts/2026-10-27-14-11-44 --thresholds 0.6,0.7,0.8
Flag Default Meaning
--session required Session directory containing transcripts.jsonl
--window 12 Utterances per classifier window
--stride window size Utterances between windows; smaller overlaps them
--thresholds 0.5,0.55,...,0.95 Thresholds to sweep

It loads the Laya checkpoint (cached after the first run) and needs uv sync --extra triage. The trailing block suggests, per question, the lowest threshold that keeps the flag rare (<= 25% of windows); paste those into questions.json as threshold keys.

(Legacy) --list-sources flag

uv run stt-daemon --list-sources
# Equivalent to: uv run stt-daemon list

(Legacy) --check flag

uv run stt-daemon --check
# Equivalent to: uv run stt-daemon run --check

Interactive Device Picker

When you run setup or run without --sources, the daemon shows an interactive picker:

  • TTY mode (terminal): Uses questionary to show a checkbox UI. Pre-checked items are from the previously saved selection.
  • Non-TTY mode (piped input): Shows a numbered menu; enter space/comma-separated indices, all, none, or blank (to keep previous selection).

Device labels:

  • [IN] <description> (<name>) — Input device (real microphone)
  • [OUT] <description> (<name>) — Output device (monitor of a sink)

Running the daemon

The simplest workflow:

# Create/setup the sink (interactive)
uv run stt-daemon setup

# Start the daemon in another terminal
# (auto-uses the provisioned sink)
uv run stt-daemon run

# Stop and remove the sink when done
uv run stt-daemon clear --yes