WHISRS(1)
π WHISRS(1) Β· User Commands Β· Linux-first voice-to-text dictation CLI π
β¨ NAME β¨
whisrs β Linux-first voice-to-text dictation CLI. π€β¨
πΈ SYNOPSIS πΈ
whisrs subcommand
π¦ DESCRIPTION π¦
Okay so whisrs is a thin CLI client, and it talks to whisrsd(1) over a Unix domain socket. It sends commands (toggle, cancel, status) and prints the daemon's response. π
The setup subcommand runs an interactive onboarding flow and does not require the daemon. π«Ά
π SUBCOMMANDS π
setup
Interactive onboarding β select a transcription backend, enter an API key, test the microphone, and write config.toml. Does not require the daemon to be running. π
config
Open an interactive menu to edit any section of config.toml β backend, language, hotkeys, overlay, command-mode LLM, and more. On save, writes the file and restarts the daemon via systemd. Unlike setup, this does not touch udev rules, the systemd unit, or compositor keybindings. β¨
toggle
Start or stop dictation. If idle, it begins audio capture. If recording, it stops capture and sends the audio for transcription. Text is typed at the cursor in the previously focused window. π€
-l,--language CODEβ override the transcription language for this session only β no config edit or daemon restart.CODEis an ISO 639-1 code (e.g.en,pl), optionally with a region (e.g.en-US), orautofor automatic detection. Applies to the toggle that starts recording; the override ends with the session. A plain toggle keeps using the configuredgeneral.language, so an existing hotkey is unaffected. Bind a separate key per language to dictate in each without switching settings. π
cancel
Cancel the current dictation and discard captured audio. Returns the daemon to the idle state. Has no effect if the daemon is not recording. π
log
Show recent transcription history. Nothing is recorded when [general] history is false. π
-n,--limit Nβ number of entries to show. Defaults to20.--clearβ clear all history instead of showing it.
command
Command mode: select text, speak an instruction, and the LLM rewrites it in place. Requires a selection. To write text where there is none, configure an [[llm_commands]] entry with a generic βtreat the following text as a request and output only what is askedβ instruction β that path never reads the selection. See https://github.com/y0sif/whisrs/blob/main/docs/configuration.md for the recipe. πͺ
llm-command NAME
Toggle a named custom LLM command (see [[llm_commands]] in config.toml): dictate, the LLM applies the configured instruction, and the result is typed at the cursor. Press again to stop recording, same as toggle. π
llm-command-set NAME
Reprogram a named LLM command from the current selection: highlight the new instruction text, then run this β it is saved to config.toml and applied live. Pairs with an entry's set_hotkey. πΎ
speak
Read the current text selection aloud via text-to-speech. A second speak or a cancel stops playback. Alias: read. π
status
Query the dictation state (for a recording, see whisrs recording status). Prints one of: idle, recording, transcribing, synthesizing, or speaking. π
restart
Restart the whisrs daemon. Uses systemctl --user restart whisrs.service on systemd systems and rc-service --user whisrs restart on OpenRC systems, when the corresponding user service is installed; otherwise prints guidance for manual restart. Does not require the daemon to be running: restart will start a stopped service. π
recording
Record the microphone and the system audio (the default output's monitor) as two separate tracks and turn them into a transcript, for a meeting or call, a lecture, a video or language practice. ποΈ The tracks are transcribed after stop into a Markdown transcript whose speaker labels are your name ([recording] user_name) and βOthersβ, or, when [recording] diarization tells the voices on the system audio apart, βSpeaker 1β, βSpeaker 2β and so on (a single voice keeps βOthersβ), unless a voice is named: by the people given with --with, or, with [recording] name_speakers (off by default; it sends transcript excerpts to the [llm] endpoint), by the LLM from a quote of the transcript that shows the name. With [recording] summary (off by default; it sends the whole transcript to the [llm] endpoint), each transcript also gets a summary from the LLM, and a recording you gave no title gets one. Each track is transcribed in its own language: [recording] mic_language and system_language, which default to [recording] language and then to [general] language. A recording keeps the languages it started with. While a recording is in progress, whisrs toggle, whisrs command and whisrs llm-command refuse to start, since the microphone is shared with the recording. Each recording lives in its own folder under the recordings directory (see FILES), named by its id (YYYY-MM-DD_HHMM, plus a slug of the title). Wherever an ID is taken, it may be a full id, a unique id prefix, or last for the newest recording.
Requires an action: π«
start
start [-t, --title TITLE] [-l, --language LANG] [--mic-language LANG] [--system-language LANG] [--others NAME] [--shared-mic[=BOOL]] [--num-speakers N] [--with NAMES]
-t,--title TITLEβ the optional title goes into the transcript and the folder name. π·οΈ-l,--language LANGβ sets the language of both tracks for this recording (an ISO 639 code such asde, optionally with a region, orauto).--mic-language LANGβ sets one track's language, over-l.--system-language LANGβ sets one track's language, over-l.--others NAMEβ names the system track in the transcript instead of βOthersβ.--shared-mic[=BOOL]β says several people share your mic, so its voices are told apart too (--shared-mic=falseturns[recording] shared_micoff for this recording).--num-speakers Nβ tells a diarization sidecar ([recording] diarization_url) how many voices the system audio holds (your mic, when there is no system audio), from0(let it decide) to20.--with NAMESβ says who the recording is with, names separated by commas (at most20), to name the voices on the system audio: one voice and one name, or the last voice left once the others have names. π«Ά
stop
stop β stop the recording and queue it for transcription. π
toggle
toggle [-t TITLE] [-l LANG] [--mic-language LANG] [--system-language LANG] [--others NAME] [--shared-mic[=BOOL]] [--num-speakers N] [--with NAMES]
Start a recording when none is in progress, otherwise stop the one that is. Meant for a compositor hotkey. The options are those of start and apply when this starts a recording. ποΈ
cancel
cancel β stop the recording and delete it. ποΈ
status
status β print whether a recording is in progress or processing, and which recordings are waiting for the network. π‘
list
list [--json]
List recordings, newest first, with id, start time, duration, status and title.
--jsonβ prints a JSON array instead.
show
show ID [-f, --format md|json|txt]
Print a transcript.
-f,--format md|json|txtβmd(the default) prints the Markdown file,jsonprintsrecording.json, andtxtprints plain text. π
path
path ID β print the recording's folder. π
copy
copy ID β copy the transcript to the clipboard. π
name
name ID SPEAKER NAME
Name a speaker of a transcribed recording. SPEAKER is its id (such as S2), βSpeaker 2β, or the name it has now; NAME may be several words, and an empty one ("") clears the name you gave. The transcript and recording.json are rewritten at once, nothing is transcribed again, and retry keeps the name, which works while a summary is being made; a summary made before the name is marked as such. Refused while the recording is queued or being transcribed. π·οΈ
retry
retry ID [-l LANG] [--mic-language LANG] [--system-language LANG] [--shared-mic[=BOOL]] [--num-speakers N] [--with NAMES]
Transcribe the recording again, for example after a failure or in another language. π₯
-l,--language LANGβ a language given here replaces the one the recording saved; as forstart,-lcovers both tracks; a track left out keeps its own.--mic-language LANGβ a language given here replaces the saved one; a track left out keeps its own.--system-language LANGβ a language given here replaces the saved one; a track left out keeps its own.--shared-mic[=BOOL]β replaces the saved setting; leaving it out keeps it.--num-speakers Nβ replaces the saved setting; leaving it out keeps it.--with NAMESβ replaces the saved setting; leaving it out keeps it (--with ""clears the names).
A recording whose transcription was interrupted three times (by a crash each time) is marked failed instead of being resumed at startup; retry resets that count and queues it again. After a failure, chunks already transcribed with the same backend and settings, language included, are reused; a recording that finished keeps none, so its retry sends both tracks again. With [recording] summary on, a retry makes a new summary; with it off, the summary is kept and marked as from an earlier transcript. π
β¨ summarize ID β¨
Okay so here's the vibe: this one summarizes a recording you already transcribed, using the [llm] right now β whatever [recording] summary happens to say, it does not transcribe the thing again. Iconic, honestly.
Where does the summary go? Two places, bestie:
- In the Markdown file, right before the transcript π
- Into
recording.json, undersummary
Also, a recording you never gave a title to gets one β a whole brand-new title of 3 to 6 words. And a title you gave? Never touched. Never changed. Periodt. π«Ά
The default rules it follows are:
- a
TL;DR - sections by topic
- decisions and next steps, only when there are any
...and you can replace all of that with [recording] summary_instruction if you want your own flavor. πΈ
Important tea: a transcript of 50 words or fewer is not sent at all. Nope. Not happening.
The job waits behind any recording that's being transcribed, and a notification tells you when it's done. If it fails? The recording keeps the summary it already had and notes why it failed. A summary interrupted by a shutdown gets made at the next start β it comes back for you, lowkey loyal. π§
One job runs at a time: a running summary delays the next transcription until it ends. So yeah, patience, babe.
β¨ mcp β¨
Serve your recordings to an AI agent, because why gatekeep π It's a read-only MCP (Model Context Protocol) server on standard input and output, for a client such as Claude Code:
claude mcp add whisrs -- whisrs mcp
Its tools can:
- list recordings
- give one recording's details and summary
- page through a transcript
- search titles, summaries and transcripts
It reads the recordings directory directly, works fine without the daemon, and never, ever changes a recording. The mcp feature needs the recording feature, and a build without mcp says so and exits 1.
Also β quick reminder from the girls: list, show and path read the recordings directory directly and work without the daemon. A build without the recording feature says so and exits 1, whatever the action. π
β¨ LLM OUTPUT β¨
This whole section applies to command and to every [[llm_commands]] entry. Both of them type the model's reply right at your cursor. π
The fence thing. A reply wrapped entirely in a Markdown code fence gets unwrapped before it's typed β a fence is never what you actually wanted sitting at your cursor, let's be real. But a reply with several fenced blocks? That's prose about code, not a wrapper, so it's left alone. π
Stripping has no opt-out. So an entry whose instruction asks for a fenced block literally cannot produce one. That's just how it is, bestie.
Multi-line replies. A reply spanning several lines is typed normally β translating a paragraph or drafting an email is exactly what these commands are for β¨
The one exception: a terminal. In a terminal, a line break is an Enter that runs a command you haven't even read yet. Spooky. So a multi-line reply aimed at a terminal is refused rather than typed π
- It gets written to the history, where
whisrs logcan recover it. - With
[general] historyset tofalsethere's no copy, and it's just discarded. - Ask for a one-liner if you want an injected result. π―
The refusal also applies with [input] paste set, since bracketed paste is the foreground program's choice and cannot be relied on. And its notification fires even with [general] notify turned off, because nothing was typed β so it has to tell you somehow, right? π
With [input] clipboard_only set, there is no refusal: whisrs types and pastes nothing, so the reply gets copied to the clipboard like any other result. Pasting it into a shell yourself carries the same caveat about bracketed paste, though. You've been warned. π
Terminal detection needs WindowTracker::get_focused_window_class, which Hyprland, Niri, Sway and X11 implement. On GNOME and KDE it reports nothing, so a terminal there is treated as an ordinary target and multi-line replies are typed. So it goes. π¦
β¨ ENVIRONMENT β¨
WHISRS_GROQ_API_KEYβ your Groq API key. Overrides the value inconfig.toml.WHISRS_OPENAI_API_KEYβ your OpenAI API key. Overrides the value inconfig.toml.XDG_RUNTIME_DIRβ directory for the Unix socket. The socket gets created at$XDG_RUNTIME_DIR/whisrs.sock. Falls back to/tmp/whisrs-<uid>.sock.XDG_CONFIG_HOMEβ directory for configuration. The config file is read from$XDG_CONFIG_HOME/whisrs/config.toml. Defaults to~/.config/whisrs/config.toml.
β¨ FILES β¨
~/.config/whisrs/config.tomlβ the main configuration file, created bywhisrs setup. Contains backend selection, API keys, language, audio settings, and hooks.
Permissions should be 0600 since it may contain API keys. Lock it down, babe π
$XDG_RUNTIME_DIR/whisrs.sockβ the Unix domain socket for CLI-daemon IPC.~/.cache/whisrs/recovery/β the recovery directory for audio that failed transcription. π$XDG_DATA_HOME/whisrs/recordings/β recordings and their transcripts, one folder per recording. Defaults to~/.local/share/whisrs/recordings/; override with[recording] dirinconfig.toml. π
β¨ EXIT STATUS β¨
0β Success. Slay. β¨1β Error: daemon not running, invalid state transition, or setup failure. π
β¨ EXAMPLES β¨
Run interactive setup:
whisrs setup
Start dictation (bind this to a hotkey):
whisrs toggle
Start dictation in Polish for this session only:
whisrs toggle -l pl
Cancel a dictation:
whisrs cancel
Check daemon state:
whisrs status
β¨ SEE ALSO β¨
whisrsd(1) β go say hi to the daemon's own page π«Ά
β¨ AUTHORS β¨
whisrs contributors β the whole squad π
β¨ LICENSE β¨
MIT π
Okay that's the whole page, bestie β you ate that. This was whisrs version 0.1.30, dated 2026-10-20. Go forth and dictate, mwah π
π¦ where this page comes from
- nixpkgs unstable: whisrs 0.1.30
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms