Ich drücke ctrl+shift+m, spreche in das Mikrofon, drücke ctrl+shift+m erneut, und der Satz steht
im Eingabefeld des Pi Agent. Kein Umweg über die Cloud, kein API-Key nötig, keine spürbare Verzögerung.
Das klingt nach einem Feature aus dem Werbevideo, ist bei mir aber seit dieser Woche der normale Workflow.
Möglich macht das pi-dictate, eine minimale Pi-Erweiterung für Sprachdiktat im Terminal, die ich geforkt und auf komplett lokales STT umgestellt habe. Als Backend läuft ein Sherpa-ONNX-Server auf meinem Mac Studio; das Audio wandert per WebSocket über das LAN, der transkribierte Text kurz darauf zurück ins Chatfenster.
Warum ich pi-dictate geforkt habe#
Das Original von amosblomqvist ist ein durchdachtes Minimalismus-Projekt: Kein schwebendes Diktat-Fenster, keine
Menüleisten-App, keine Benachrichtigungen. alt+m drücken, sprechen, erneut alt+m drücken, und der Text landet im
fokussierten Eingabefeld. Ob Chat-Editor, Popup oder Dialog: Die Taste wird auf der TUI-Ebene abgefangen, bevor die
fokussierte Komponente sie sieht.
Zwei Dinge passten bei mir nicht. Erstens das Backend: Das Original streamt das Audio an Deepgram Nova-3, ein
Cloud-Service mit API-Key und rund 0,50 USD pro Stunde Sprechzeit. Zweitens die Plattform: Das Original ist auf macOS
zugeschnitten (pbcopy für den Clipboard-Fallback, sox für die Aufnahme), ich arbeite unter Arch Linux mit dwm.
Der Fork ersetzt das Backend daher vollständig durch einen lokalen Sherpa-ONNX-Server. Kein dualer Aufbau, kein zweiter Code-Pfad: Es ist einfach ein anderes WebSocket auf der anderen Seite. Alles, was das Original ausmacht, bleibt dabei erhalten: Pegelmeter, Fokus-Erkennung, Delivery an das richtige Eingabefeld. Nur die Seite, mit der das Audio redet, hat sich geändert.
So funktioniert das Diktat#
Zuerst fokussiere ich ein Eingabefeld, egal ob das Haupt-Chatfenster, ein Quiz-Notizfeld oder ein
ask_user_question-Antwortfeld ist. ctrl+shift+m startet die Aufnahme; die Statuszeile zeigt einen roten Punkt plus
einen Pegelmeter, der mit meiner Stimme mitschwingt: ● ▁▂▃▅ listening…. Wenn nichts fokussiert ist, startet die
Diktation gar nicht, eine Benachrichtigung erklärt warum.
Sprechen, und beim zweiten ctrl+shift+m stoppt alles. Kurz ein finalizing…-Spinner, der auf dem lokalen Server meist
zu schnell zum Sehen ist, dann ist der Text da. Das Transkribieren läuft dabei live mit: Der Server liefert während des
Sprechens fortlaufend Text, eingefügt wird er aber erst in einem einzigen Schritt beim Stopp. ctrl+shift+n bricht eine
laufende Diktation ab und wirft das Transkript weg, jederzeit sicher.
Wichtig ist das Ziel des Textes: Er wird immer angehängt, nie ersetzt, und zwar an das Feld, das zum Zeitpunkt des Stopps fokussiert ist.
Das erlaubt zum Beispiel folgenden Ablauf: Man startet ein Diktat, fügt danach per Copy & Paste noch eine Fehlermeldung aus dem Terminal ein, startet anschließend ein weiteres Diktat, und der neue Text wird einfach angehängt, statt etwas zu überschreiben. Am Ende lässt sich alles zusammen im Chat absenden.
Konkret läuft das Anhängen je nach Kontext so ab:
- Editor oder Popup-Eingabefeld: direkter Append
- Geschlossene Dialoge (Quiz,
ask_user_question): als synthetische Tastenanschläge, dafür vorab mit Tab in das Notizfeld wechseln - Kein Eingabefeld fokussiert: Clipboard via
xclip, plus Benachrichtigung
Optional lässt sich die Live-Vorschau aktivieren (LIVE_PREVIEW = true in index.ts). Dann zeigt die Statuszeile neben
dem Pegelmeter auch den laufenden Rolling-Text, das beste Feedback, wenn man sehen will, was der Server gerade versteht.
Der STT-Server: Sherpa-ONNX mit dem Kroko-Modell#
Das Herzstück ist ein schlanker Docker-Container mit dem Sherpa-ONNX WebSocket-Server auf Port 6006. Als Modell nutze ich das deutsche Streaming-Zipformer de-kroko-2025-08-06. In meinem Setup liegt die Latenz bei unter 300ms, der Text erscheint praktisch gleichzeitig mit dem Aussprechen, und die CPU-Last auf dem Mac Studio bleibt niedrig.
Das Modell hat noch einen zweiten Vorteil: Es punktiert und kapitalisiert nativ. Kommata, Punkte, Frage- und
Ausrufezeichen kommen direkt aus dem Modell, ein Post-Processing-Schritt à la smart_format ist nicht nötig. Auch
englische Wörter und Fachbegriffe werden dabei meist zuverlässig erkannt und korrekt geschrieben, ohne dass man die
Sprache manuell umschalten muss. Wer andere Sprachen diktieren will, tauscht serverseitig einfach ein anderes
sherpa-onnx-Modell ein.
Das Protokoll ist schlicht: 16 kHz Mono-PCM wird als binäre WebSocket-Frames hochgeschickt, zurück kommt JSON mit einem
Rolling-Text, der sich bei jedem Update selbst ersetzt. Nach dem String DONE kommt genau ein Final. Ein Detail, das
man kennen sollte, wenn man eigene Clients baut: Der Server schließt die Verbindung nach dem Final nicht selbst, der
Client muss das übernehmen. Wer das vergisst, lässt die Session auf dem Server offen. Das gilt für jeden Client, der
sich an diesen Server anbindet: Mein Voice-Assistent Konrad hängt mit seinem Wyoming-Proxy an genau derselben Instanz.
Installation#
Auf der Pi-Seite ist das Setup kurz:
pi install git:github.com/sebastianzehner/pi-dictate
pacman -S pulseaudio-utils xclippulseaudio-utils liefert parec für die native 48-kHz-Aufnahme, xclip den Clipboard-Fallback. Die Erweiterung
sampelt intern per FIR-Filter auf 16 kHz Mono herunter, das Format, das der Server erwartet. Der STT-Server startet
separat per Docker Compose:
docker compose -f <pfad-zum-sherpa-compose>/compose.yaml up -dZwei Umgebungsvariablen steuern die Erweiterung, gelesen beim Laden von Pi:
| Variable | Default | Effekt |
|---|---|---|
DICTATE_STT_URL | ws://mac-studio.lan:6006 | WebSocket-URL des Sherpa-ONNX-Servers |
DICTATE_DEBUG | aus | 1 loggt WebSocket-Events nach /tmp/dictate-debug.log |
Nach der Installation oder nach Änderungen an index.ts genügt /reload in Pi.
Stolpersteine#
Drei Punkte haben mir beim Aufbau Zeit gekostet, die ich dir gerne erspare:
- Hotkeys und Terminals.
ctrl+shift+mist als Legacy-Byte nicht darstellbar, das Terminal muss CSI-u (Kitty-Protokoll) senden. In st habe ichmappedkeys[]inconfig.hentsprechend angepasst, in tmux 3.5+ aktiviere ichextended-keys onundextended-keys-format csi-u. Die Pi-Doku zum tmux-Setup erklärt das ausführlich: pi.dev/docs/latest/tmux. - dwm-Kollision. Das Original hat bewusst
alt+m/alt+ngewählt, weilctrl+shift-Binds in Terminals ohne CSI-u-Unterstützung von Enter nicht unterscheidbar sind. Bei mir kollidierten die alt-Binds mit meinen dwm-Shortcuts, daher der Wechsel aufctrl+shiftin Kombination mit moderner CSI-u-Ausgabe. - Wayland. Noch nicht unterstützt.
xclipbraucht X11, einwl-copy- Fallback müsste nachgereicht werden, falls das jemanden interessiert.
Und Pi bleibt, wie es ist#
Ein Satz, der hier vielleicht überrascht: Ich liebe den Pi Agent einfach weiter. Sprachinput macht die Tastatur nicht überflüssig, aber lange Prompts und ausführliche Beschreibungen zu sprechen, ist schneller und entspannter als zu tippen. Das Diktat wandert direkt in das Chatfenster, in dem der Agent ohnehin auf mich wartet.
Der LLM-Stack unter Pi ist übrigens unverändert: llama.cpp mit llama-swap als Proxy, wie in meinem Artikel zum Umstieg
von Ollama beschrieben. Gewechselt hat nur das Modell. Statt Qwen3.6-27B läuft jetzt Qwen3.8-27B im Format
UD-Q4_K_XL, mit Vision-Projector und Multi-Token-Prediction wie gehabt. In der llama-swap-Konfiguration ist das ein
getauschter Modellpfad, und dank TTL wird der VRAM wie bisher automatisch freigegeben, sobald ich ihn für ComfyUI
brauche.
Diese Erweiterung, der Fork mit Diktat-Anbindung, ist übrigens selbst mit dem Pi Agent entstanden, lokal auf meiner Hardware unter Qwen3.8. Man kann sich inzwischen wirklich die Tools von seinen Tools bauen lassen.
Fazit#
Lokales STT ist für Sprachdiktat definitiv praxistauglich: unter 300ms Latenz, native Interpunktion, keine laufenden API-Kosten und keine Audio-Daten verlassen das LAN. Der Fork bleibt so schlank wie das Original, eine TypeScript-Datei, MIT-lizenziert, und läuft seit dieser Woche im Alltag. Wer Pi nutzt und lieber diktiert als tippt: ausprobieren lohnt sich.
Liebe Grüße
Sebastian






