clemvault internals shipped April 14, 2026

Clembot-dictate

Windows dictation tool: hold a key and your words land at the cursor, Ctrl answers out loud, Shift cleans the clipboard. Local Whisper, no subscription.

Pythonfaster-whisperKokoro TTScustomtkinterOllamaClaude APIInno Setup
Clembot-dictate screenshot 1
Clembot-dictate screenshot 2

A Windows background tool that turns voice into typed text. Hold a hotkey, say something, release. The words appear at wherever your cursor is. No app switching, no clicks, no cloud.

One key carries three gestures. Hold it and speak, and the words are typed. Hold Ctrl with it and it answers out loud instead. Tap Shift with it and it cleans whatever is on your clipboard.

The Problem

Windows dictation is broken for power users. Windows Voice Typing requires manual activation for every session and doesn’t work universally across apps. Dragon NaturallySpeaking costs hundreds per year and sends your audio to the cloud. Other Whisper-based desktop tools still require interaction: open a window, click record, click stop, copy the text, switch back, paste. Five steps for something that should be zero.

The goal was zero steps: hold, speak, release, text appears.

How It Works

Clembot-dictate runs silently as a background process with a 44px header strip at the top of the screen. When you hold the configured hotkey (default: backtick), it starts capturing mic audio into an in-memory numpy buffer. On release, it passes the buffer directly to faster-whisper for local transcription, then routes the output through an optional AI refinement pass before pasting to the active window via pyperclip and a synthesized Ctrl+V.

The entire pipeline runs off the main thread. The hotkey listener never blocks.

hold hotkey → window_detector reads active app → sample Ctrl → Recorder captures audio
→ key release
   ├─ Ctrl held → sidecar over 127.0.0.1 → route → execute → spoken answer
   └─ otherwise → Transcriber (faster-whisper, tiny model, CPU)
                → Refiner (Ollama local or Claude Haiku) → Paster (pyperclip → Ctrl+V)
                → text at cursor

End-to-end latency target: under 3 seconds on CPU with the tiny model.

AI Refinement

Raw speech is messy. The optional refinement pass shapes the transcript based on context mode:

Modes are defined in config.py. Add your own by editing a single dict.

Auto-Context Detection

At the moment you press the hotkey, before recording starts, Clembot-dictate reads which process is in the foreground using win32gui + psutil and switches to the matching context mode automatically. Switch from VS Code to Outlook: the mode switches without touching the UI.

Dual Backend

History Panel

Every dictation stores both the raw transcript and the AI-refined version in a scrollable history panel. Re-run AI refinement with a different mode, copy any entry to clipboard, or switch backends mid-session. The panel is hidden by default and expands from the header strip on demand.

Ask Instead of Dictate

Dictation puts words at the cursor. It cannot answer anything. Asking “what’s blocked” used to mean stopping, opening a terminal, starting a session, and reading files that a parser could read in milliseconds.

So the same gesture got a second meaning. Hold Ctrl with the hotkey, speak, release, and the audio goes to a local process that transcribes it, decides how much machinery the question deserves, executes at that level, and speaks the answer back. Nothing is pasted.

Ctrl is sampled once at key-down, so this is per utterance rather than a mode. There is nothing to toggle and nothing to forget, which matters because a mode you forget you are in pastes an answer into a document.

Clean the Clipboard

Text that comes out of a language model carries characters you cannot see: zero-width spaces, word joiners, non-breaking spaces. They are invisible where you read them and loud everywhere else, breaking search, diffs and word counts in whatever you paste them into.

Tap Shift with the hotkey and the clipboard is cleaned in place. Every dictation gets the same pass before it reaches your cursor.

Visible punctuation is left alone on purpose. Em dashes, curly quotes and ellipses are ordinary characters that ordinary writing uses, and editing your prose is a different job from removing bytes you never chose.

It is not watermark removal, and it proves nothing about authorship. Text can be marked statistically, in word choice rather than in the bytes, and nothing here touches that. What it does is narrower: what you paste stops carrying invisible characters that confuse the next tool to read it.

Not every invisible character is junk. Emoji combine with zero-width joiners, flags are built from tag sequences, and Arabic, Persian, Devanagari, Hangul and Mongolian use joiners that carry meaning. Those survive; only free-floating carriers are taken.

The clipboard has no undo, so every clean is recorded in the history panel with a tally of what changed, and one click puts the original back.

Three Tiers, Cheapest First

Most questions people ask a repository are lookups, not reasoning problems. Sending those to a model costs money, adds seconds, and lets the model be confidently wrong about a fact sitting in a file.

TierWhat runsCostLatency
SkillsAn allowlisted read-only command, reimplemented in Python$0120 to 220 ms
MetricsDeterministic reads of repository state files$01 to 37 ms
AgentA coding-agent CLI in read-only plan mode$0.019 to $0.0743.6 to 95 s

The first two tiers are fully local, so the only thing that ever leaves the machine is a transcript bound for the third. Key-up to first spoken word measures about 3.2 seconds: 1.46 s to transcribe, effectively zero to route, 0.03 s to execute, 1.69 s to reach the first audio.

The measurement that set this design: running three commands through the agent CLI cost $1.0028 per utterance, because those commands are prompt files rather than scripts. The same three answers, reimplemented in Python, cost nothing and return in under a quarter of a second.

The tier is derived from the recognised intent, never reported by a model. One table maps intent to tier. An unrecognised request gets no tier at all and an honest refusal that repeats what it heard, because a display reading “tier 3” for something that never ran is worse than no display.

Two Seams That Make It Portable

Neither half assumes a particular repository or a particular AI subscription. Both assumptions were pulled out into data rather than code.

A backend has to prove its read-only mode took effect before its answer is accepted. That check exists because one CLI accepted a read-only flag, printed that it had overridden it, and exited 0 with an authentication error inside its payload. Accepted flags and exit codes are both worthless as evidence.

Why a Sidecar and Not a Merge

The two halves disagree about their dependencies. The answering half needs numpy>=2.0.2 for its text-to-speech; the app runs numpy 1.26.4 and ships as a 283 MB installer. Merging them meant resolving that conflict and adding 354 MB of speech models to a download most people will never point at a repository.

A loopback socket keeps both dependency trees intact and the installer the size it was. The sidecar publishes its host, port, token and process id to a user-level file; the client checks the process is alive before trusting it, so a stale file reads as “not running” rather than a hang. If the sidecar is down, a Ctrl-held utterance falls through to ordinary dictation. A missing sidecar never costs you words.

Verified across both environments: sidecar on numpy 2.5.2, client on numpy 1.26.4, spoken question answered in 2.88 seconds.

Safety

Voice is a low-friction input, which is exactly why the answering path is read-only by default. The agent tier runs in plan mode holding Read, Grep and Glob and nothing else. No writes, no commits, no pushes from a voice command.

A change to any file would need two independent things, not one: a spoken confirmation after the intent is read back, and a diff on screen. Voice confirmation supplements the diff rather than replacing it. That gate exists as an interface and nothing in this version passes through it.

Stack

ComponentRole
sounddeviceStreams float32 audio from mic into numpy buffer while hotkey is held
faster-whisperCTranslate2-based Whisper, 4x faster than openai-whisper on CPU. Tiny model, int8 quantized. Accepts numpy array directly, no WAV file I/O
keyboardGlobal hotkey listener, suppress=True prevents raw keystroke from typing. Works system-wide without admin rights
pyperclip + pyautoguiSaves previous clipboard, writes transcript, fires Ctrl+V, restores clipboard
win32gui + psutilReads active window process name for auto-context switching
customtkinterDark-themed UI on the Wanessa Labs palette: 44px frameless header strip, expandable history panel
pystraySystem tray icon, turns red while recording
ollamaLocal LLM client, Gemma 3 4B default
anthropicClaude API client for cloud refinement, Haiku by default
voice_commandStdlib-only client for the answering sidecar. Adds no dependency, so an optional feature cannot change what the installer carries
Kokoro (in the sidecar)Local text-to-speech for spoken answers. The fp32 build measured 3x faster than int8 on this CPU, which has no VNNI

Runtime: Python 3.10+, Windows 10/11.

Distribution

Ships as a Windows installer built with Inno Setup 6, no admin rights required (installs to %LOCALAPPDATA%). The faster-whisper model downloads on first launch rather than being bundled, keeping the installer under 100 MB. The build is 77.7 MB, and the voice-command feature did not change that, which is the point of keeping the answering half in its own process.

Production hardening: Single-instance mutex, crash handlers with log path dialog, rotating log file, model download progress UI, mic permission check, minimum recording gate, and VAD silence filtering.

Security: Anthropic API keys stored in Windows Credential Manager via the keyring library, not in environment variables. Optional model SHA256 integrity check after download. Auto-update version check runs at startup against a hosted JSON endpoint.

The Inno Setup uninstaller removes the startup registry entry and offers to delete history and logs from AppData.

Source and downloads: the project is open source at github.com/clemenswan/clembot-dictate, MIT licensed, with the signed-by-nobody installer attached to the latest release. The repository is a curated export rather than a mirror: the working tree it comes from holds real dictation transcripts, so what gets published is an explicit allowlist rather than everything minus a denylist.

Development timeline

13 logged updates over 146 days, 13 Apr 2026 to 5 Sept 2026.

  1. 1.2.1 released: the interface finally says what the app doesfeature

    The app was four features deep and the interface mentioned one. Both were in the README, which is not the same as discoverable.

  2. 1.3.0 released: a clean can be undoneimproved

    The clipboard has no undo, so cleaning was a one-way door: a tray balloon named what changed and then it and the original were both gone. Entry.kind now distinguishes dictation, question and clean; entries written earlier load as…

  3. 1.2.0 released: cleaning the clipboard, not just the outputimproved

    The checksum in the public README was taken from the uploaded asset rather than the local file, so the two cannot disagree.

  4. 1.1.1 released: the interface pass, shippedfeature

    Downloaded back over the public URL before the manifest moved: HTTP 200, correct length, MZ header, hash matching the release notes.

  5. UI pass: the palette belonged to someone elsefeature

    Reported as "clunky, doesn't feel professional". Three causes, in order of how much they were doing:

  6. Docs updated for 1.1.0, and three stale claims correctedfix

    Version drift fixed while in there. Three files still named Setup-1.0.0.exe and VERSION = "1.0.0" in their tables, one release after the fact.

  7. Public repository, curated rather than mirroredfeature

    It is an export, not a remote. This tree stays the source of truth and its git history never leaves, because that history is ClemVault's.

  8. Release runbook, and the update check that never workedfeature

    Two blockers found by probing rather than reading.

Show the earlier 5 entries
  1. Voice command mode (sidecar client)feature

    Dictation is unchanged and the dependency tree is untouched — no numpy upgrade, no models in the installer.

  2. Phase 1 complete: a stable executablefeature

    Everything here came from watching the thing fall over. A model that downloads on first run needed to say so rather than appear frozen, a denied microphone needed a dialog and a degraded mode rather than a crash, a key-repeat needed a…

  3. Phase 2 complete: distribution-readyfeature

    The work that turns a program into something another person can install. A no-admin installer that upgrades in place, the AI key moved out of an environment variable and into the Windows credential store, an integrity check on the…

  4. Project initializedfeature

    Promoted straight from a spoken brief with no inbox file behind it, pressure-tested, and drafted into a PRD the same day. Two decisions made here survived the whole build: record-then-transcribe rather than streaming, and paste through…

  5. Proof of work generatedfeature

    The threading model was settled on paper before any of it was written: suppress the hotkey so the key itself never reaches the document, lock around the recording flag so a repeat cannot double-fire, and transcribe on its own thread so…

Written from this project's decision log as work happened, not afterwards. 1 entry was withheld from publication. See every project →