Es gibt diesen Moment, wenn die Hände auf der Tastatur liegen, aber der Kopf schon zehn Schritte voraus ist. Dann willst du dem Agenten einfach sagen, was er bauen soll — und er legt los. Das geht, schneller als du denkst, und du brauchst dafür kein Framework, keine neue App und kein Abo. Nur den Pi-Coding-Agenten, einen OpenRouter-Key und ein günstiges Speech-to-Text-Modell. There's that moment when your hands are on the keyboard but your head is already ten steps ahead. Then you just want to tell the agent what to build — and it starts. That's possible, faster than you'd think, and you need no framework, no new app, no subscription. Just the Pi coding agent, an OpenRouter key and a cheap speech-to-text model.
01Was ist Pi überhaupt?What is Pi, anyway?
Ein Coding-Agent, der in deinem Terminal lebt — und sein ganzes Verhalten in ein paar Ordnern steckt, die du editieren darfst. A coding agent that lives in your terminal — and keeps all of its behavior in a few folders you're allowed to edit.
Pi (@mariozechner/pi-coding-agent) ist ein Coding-Agent für die Kommandozeile: Du tippst eine Aufgabe, Pi liest deinen Code, plant, editiert Dateien, führt Kommandos aus. Was Pi von vielen anderen unterscheidet: Es ist auf Eigenbau eingestellt. Seine Konfiguration liegt als normale Dateien unter ~/.pi/agent/ — und alles davon ist dazu da, dass du es anfasst.
Pi (@mariozechner/pi-coding-agent) is a coding agent for the command line: you type a task, Pi reads your code, plans, edits files, runs commands. What sets Pi apart from many others: it's built for DIY. Its config lives as plain files under ~/.pi/agent/ — and all of it is meant for you to touch.
Das Killer-Feature ist das Extensions-System: TypeScript-Dateien im Ordner extensions/, die von Pi beim Start geladen werden. Damit kannst du in den Lebenszyklus des Agenten eingreifen — und genau dort liegt unser Hebel für die Spracheingabe.
The killer feature is the extensions system: TypeScript files in the extensions/ folder that Pi loads on startup. With that you can hook into the agent's lifecycle — and that's exactly our lever for voice input.
02Die vier BausteineThe four building blocks
Bevor wir bauen, die Landkarte von ~/.pi/agent/.
Before we build, the map of ~/.pi/agent/.
| BausteinBlock | ZweckPurpose | Beispiel aus der PraxisReal-world example |
|---|---|---|
| extensions/ | TypeScript-Hooks in den Agent-LebenszyklusTypeScript hooks into the agent lifecycle | Permission-Gate, Project-Rulespermission gate, project rules |
| skills/ | Wiederverwendbares Fachwissen (SKILL.md)reusable know-how (SKILL.md) | web-app, trading-bot, brave-searchweb-app, trading-bot, brave-search |
| prompts/ | Modus-Prompts: plan / implement / reviewmode prompts: plan / implement / review | plan.md, implement.md, review.mdplan.md, implement.md, review.md |
| presets.json | Provider + Modell + Tools pro Modusprovider + model + tools per mode | zai / glm-5, thinking level, toolszai / glm-5, thinking level, tools |
Für unsere Spracheingabe ist extensions/ der wichtigste Ort. Schauen wir uns an, wie so eine Extension wirklich aussieht — das permission-gate.ts von der Standard-Installation.
For our voice input, extensions/ is the most important place. Let's look at how such an extension really looks — the permission-gate.ts from the default install.
ts// ~/.pi/agent/extensions/permission-gate.ts
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
export default function permissionGate(pi: ExtensionAPI) {
const dangerous = [/\brm\s+(-rf?)/i, /\bsudo\b/i, /\bmkfs\b/i];
pi.on("tool_call", async (event, ctx) => {
if (event.toolName !== "bash") return undefined;
const cmd = event.input.command;
if (dangerous.some(p => p.test(cmd))) {
if (!ctx.hasUI) return { block: true, reason: "Dangerous, blocked." };
const choice = await ctx.ui.select("Allow?", ["Yes", "No"]);
if (choice !== "Yes") return { block: true, reason: "Blocked by user" };
}
return undefined;
});
}
Das Muster ist klar: Du exportierst eine Funktion, die das ExtensionAPI-Objekt bekommt, und registrierst mit pi.on(...) Hooks. Pi selbst liefert dir ctx.ui zum Interagieren (Select, Notify) und erwartet, dass du undefined (okay) oder {block, reason} zurückgibst.
The pattern is clear: you export a function that receives the ExtensionAPI object, and register hooks with pi.on(...). Pi hands you ctx.ui to interact (select, notify) and expects you to return undefined (okay) or {block, reason}.
03Der Trick: Lass Pi die Extension selbst bauenThe trick: let Pi build the extension itself
Du musst die TypeScript-API nicht auswendig lernen. Die ehrlich schnellste Art, eine Extension zu bekommen, ist: Pi in einem normalen Gespräch darum bitten. Das ist der „selbst gepromptete" Teil — du erklärst Pi im Chat, was die Extension können soll, und Pi schreibt sie dir nach ~/.pi/agent/extensions/.
You don't need to memorize the TypeScript API. The honestly fastest way to get an extension is: ask Pi for it in a normal conversation. That's the "self-prompted" part — you explain to Pi in chat what the extension should do, and Pi writes it into ~/.pi/agent/extensions/.
de / prompt# an Pi gerichtet / to Pi
Baue mir eine Extension ~/.pi/agent/extensions/task-notify.ts.
Sie soll nach jedem bash-Tool-Call, der länger als 20s dauert,
eine Termin-Nachricht über ctx.ui.notify ausgeben.
Nutze die ExtensionAPI aus @mariozechner/pi-coding-agent.
Pi kennt sein eigenes API besser als du — und kann dir danach direkt sagen, wie du die Datei testest (normalerweise: Pi neu starten oder Session neu laden). Der Punkt: Extensions sind erweitertes Prompting. Du steuerst Pi nicht nur über die Tastatur, sondern gibst ihm Dateien, die sein Verhalten permanent prägen. Pi knows its own API better than you do — and can then tell you directly how to test the file (usually: restart Pi or reload the session). The point: extensions are extended prompting. You don't just steer Pi via the keyboard, you give it files that permanently shape its behavior.
Wer seinen Agenten promptet, bekommt Antworten. Wer ihm Extensions baut, bekommt einen Mitarbeiter. Prompt your agent and you get answers. Build it extensions and you get a colleague. — antai.live
DeepSeek V4 Flash für den Agenten DeepSeek V4 Flash for the agent
Der Agent selbst verbraucht die meisten Tokens. Setz dort ein günstiges, starkes Reasoning-Modell ein — aktueller Tipp: DeepSeek V4 Flash (deepseek/deepseek-v4-flash-0731), die aktuellste Flash-Version.
The agent itself burns most of the tokens. Put a cheap, strong reasoning model there — current pick: DeepSeek V4 Flash (deepseek/deepseek-v4-flash-0731), the latest flash release.
Eintrag in presets.json: "provider": "deepseek", "model": "deepseek-v4-flash-0731".
Entry in presets.json: "provider": "deepseek", "model": "deepseek-v4-flash-0731".
04Das Beispiel: Spracheingabe im TerminalThe example: voice input in the terminal
Genug Theorie. Wir bauen jetzt die Spracheingabe: Mikrofon → Transkription → automatisch in die Pi-Konsole. Enough theory. Now we build the voice input: mic → transcription → automatically into the Pi console.
Warum OpenRouter + ein günstiges STT-Modell?Why OpenRouter + a cheap STT model?
OpenRouter ist ein Aggregator: ein Key, viele Modelle. Unter den Audio-Modellen findest du ein sehr günstiges Sprach-zu-Text-Modell — extrem genau und mit 0,04 US$ pro Stunde Audio praktisch kostenlos. Und der Clou: OpenRouter nimmt die Anfrage als JSON entgegen (kein lästiges multipart/form-data), Audio wird als Base64 übergeben. OpenRouter is an aggregator: one key, many models. Among the audio models sits a very cheap speech-to-text model — extremely accurate and at $0.04 per audio hour practically free. And the kicker: OpenRouter accepts the request as JSON (no fiddly multipart/form-data), audio is passed as base64.
Schritt für SchrittStep by step
Audiodatei aufnehmenRecord audio
Mit ffmpeg vom Mikrofon in ein OGG (16 kHz, Mono — perfekt für Speech-to-Text).With ffmpeg from the mic into OGG (16 kHz, mono — perfect for speech-to-text).
bashffmpeg -y -f pulse -i default -ac 1 -ar 16000 /tmp/voice.ogg
# Auf dem Mac: -f avfoundation -i ":0"
An OpenRouter schickenSend to OpenRouter
Audio als Base64, als JSON — Endpunkt /v1/audio/transcriptions.Audio as base64, as JSON — endpoint /v1/audio/transcriptions.
bashB64=$(base64 -w0 /tmp/voice.ogg)
curl -s https://openrouter.ai/api/v1/audio/transcriptions \
-H "Authorization: Bearer $OPENROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"model\":\"\",\"input_audio\":{\"data\":\"$B64\",\"format\":\"ogg\"}}" \
| jq -r '.text' > /tmp/transcript.txt
In Pi autopastenAuto-paste into Pi
Zwei Wege — je nachdem, ob Pi gerade läuft oder nicht.Two ways — depending on whether Pi is running or not.
bash# Variante A: frisch starten mit gesprochenem Prompt
TRANSCRIPT=$(cat /tmp/transcript.txt)
pi "$TRANSCRIPT"
# Variante B: in ein laufendes Pi-Terminal (tmux) tippen
tmux send-keys -t pi "`cat /tmp/transcript.txt`" Enter
Und das Ganze in einem Skript verpackt — der eigentliche Sprach-zu-Text-Workflow: And the whole thing bundled into one script — the actual speech-to-text workflow:
bash · voice.sh#!/usr/bin/env bash
set -euo pipefail
OUT=/tmp/voice.ogg
ffmpeg -y -f pulse -i default -ac 1 -ar 16000 "$OUT" 2>/dev/null
B64=$(base64 -w0 "$OUT")
TXT=$(curl -s https://openrouter.ai/api/v1/audio/transcriptions \
-H "Authorization: Bearer ${OPENROUTER_API_KEY:?}" \
-H "Content-Type: application/json" \
-d "{\"model\":\"\",\"input_audio\":{\"data\":\"$B64\",\"format\":\"ogg\"}}" \
| jq -r '.text')
printf '%s' "$TXT"
# Nutzung: pi "$(voice.sh)" → sprich, und Pi legt los
Der F3-Workflow: sprechen → einfügen → editieren → sendenThe F3 workflow: speak → paste → edit → send
Das oben tippt den Text sofort ab. Fürs Arbeiten willst du aber nichts automatisch senden, sondern zuerst einfügen und dann selbst entscheiden. Dafür gibt's den F3-Toggle: Ein Druck startet die Aufnahme, der nächste schickt sie an STT und fügt das Transkript in die Pi-Eingabe ein — ohne zu senden. Danach kannst du weiterreden (wieder F3), weitertippen oder manuell korrigieren, bis du Enter drückst. The above types the text out immediately. For real work you want nothing auto-sent, but pasted first so you decide. That's what the F3 toggle is for: one press starts recording, the next sends it to STT and pastes the transcript into Pi's input — without sending. Afterwards you can keep speaking (F3 again), keep typing, or edit manually until you hit Enter.
bash · voice-key.sh#!/usr/bin/env bash
set -euo pipefail
OUT=/tmp/voice.ogg
if [[ -f "$OUT" ]]; then
# 2. F3-Druck: Aufnahme stoppen → STT → EINFÜGEN ohne Senden
pkill -f "ffmpeg.*voice.ogg" || true
sleep 0.2
B64=$(base64 -w0 "$OUT")
TXT=$(curl -s https://openrouter.ai/api/v1/audio/transcriptions \
-H "Authorization: Bearer ${OPENROUTER_API_KEY:?}" \
-H "Content-Type: application/json" \
-d "{\"model\":\"\",\"input_audio\":{\"data\":\"$B64\",\"format\":\"ogg\"}}" \
| jq -r '.text')
rm -f "$OUT"
tmux send-keys -t pi -l "$TXT" # -l = literal, NICHTS absenden
else
# 1. F3-Druck: Aufnahme starten
ffmpeg -y -f pulse -i default -ac 1 -ar 16000 "$OUT" 2>/dev/null &
fi
Der entscheidende Teil ist tmux send-keys -t pi -l "$TXT": Das -l (literal) schickt den Text als Tipp-Eingabe, ohne Return. Das Transkript landet also in der Eingabezeile von Pi — aber Pi startet nicht. Du kannst jetzt weiterreden, tippen oder editieren und erst mit Enter wirklich senden.
The crucial part is tmux send-keys -t pi -l "$TXT": the -l (literal) sends the text as typed input, without a Return. So the transcript lands in Pi's input line — but Pi does not start. You can now keep speaking, typing, or editing and only send for real with Enter.
F3 an voice-key.sh (z. B. über deinen Fenstermanager, sxhkd oder ein Terminal-Hotkey-Tool). Erster Druck = aufnehmen, zweiter Druck = transkribieren + einfügen.
Bind F3 to voice-key.sh (e.g. via your window manager, sxhkd, or a terminal hotkey tool). First press = record, second press = transcribe + paste.
OPENROUTER_API_KEY in deiner Shell (z. B. in ~/.bashrc). Der Key ist geheim — nie einchecken, nie teilen. OpenRouter keyed nach dem Account, nicht nach Gerät.
Set OPENROUTER_API_KEY in your shell (e.g. in ~/.bashrc). The key is secret — never commit it, never share it. OpenRouter keys to the account, not the device.
05Vom Skript zur Extension (Bonus)From script to extension (bonus)
Das Skript ist eine schnelle Lösung. Elegant wird es, wenn du Pi beibringst, von sich aus auf Sprachbefehle zu reagieren — per before_agent_start-Hook: Du kannst in den System-Prompt schreiben, dass bei Keyboard-Shortcut Ctrl+R die Sprachaufnahme läuft. So wird aus einem Tippen ein Workflow.
The script is the quick fix. It gets elegant when you teach Pi to respond to voice commands on its own — via a before_agent_start hook: you can write into the system prompt that pressing Ctrl+R starts the voice recording. That turns typing into a workflow.
ts// ~/.pi/agent/extensions/voice-hint.ts (von Pi selbst gepromptet)
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
export default function voiceHint(pi: ExtensionAPI) {
pi.on("before_agent_start", async (event) => {
return {
systemPrompt: event.systemPrompt + `
## Voice Input
Ctrl+R records the mic and transcribes via voice.sh.
If the user says "voice", run: pi "$(bash ~/bin/voice.sh)"
`,
};
});
}
Jetzt gibt es keinen Unterschied mehr zwischen „tippen" und „sprechen" für Pi — und das ist genau die Freiheit, für die man sich einen eigenen Agenten baut. Now there's no difference between "typing" and "speaking" for Pi — and that's exactly the freedom you build your own agent for.
06Was du daraus machstWhat you make of it
Die Spracheingabe ist nur das einfachste Beispiel. Dasselbe Muster — Extension, die du Pi im Chat beschreibst — bildet auch einen Brave-Search-Hook, ein automatisches Web-App-Scaffold oder einen Trading-Bot-Auslöser. Und weil alles nur Dateien in ~/.pi/agent/ sind, kannst du es jederzeit weitergeben, versionieren oder löschen.
Voice input is just the simplest example. The same pattern — an extension you describe to Pi in chat — also forms a Brave-search hook, an automatic web-app scaffold, or a trading-bot trigger. And because it's all just files in ~/.pi/agent/, you can share, version, or delete them anytime.
Kurz & knackigThe short version
- Pi wird über
extensions/,skills/,prompts/undpresets.jsonerweitert.Pi is extended viaextensions/,skills/,prompts/,presets.json. - Extensions = erweitertes Prompting. Beschreib sie Pi im Chat, Pi schreibt dir den TypeScript-Code.Extensions = extended prompting. Describe them to Pi in chat, Pi writes the TypeScript.
- Spracheingabe: OpenRouter-Key + günstiges STT-Modell (0,04 $/Std.) + ffmpeg → Sprache in die Pi-Konsole.Voice input: OpenRouter key + cheap STT model ($0.04/hr) + ffmpeg → speech into the Pi console.
- Auto-Paste per
pi "$(...)"odertmux send-keys.Auto-paste viapi "$(...)"ortmux send-keys.
Probier es aus — dein Agent wartet schon. Try it out — your agent is already waiting.
Wir bauen KI-Agenten, Sprach-Assistenten & Automatisierung We build AI agents, voice assistants & automation
Aus diesem Tutorial wird bei dir ein einsatzbereiter Workflow — für Vertrieb, Support, Backoffice. Von der kostenlosen Erstberatung bis zum laufenden System. Hamburg & bundesweit. Turn this tutorial into a production workflow for your sales, support, or back office — from a free first consultation to a running system. Hamburg & nationwide.
digibit-it.de · Jetzt KontaktGet in touch