Wie ich meine erste macOS-App mit nur 2 Prompts in Codex gebaut habe
🚀 App herunterladen: Das fertige kompilierte Bundle steht direkt auf GitHub Releases bereit.
Ich benutze die Tastatur schon lange nicht mehr zu 100 %. Viel häufiger diktiere ich Texte einfach per Sprache und erwarte eine schnelle, präzise Transkription.
Hauptsächlich nutze ich das für die Kommunikation mit KI-Modellen. Meine Frau scherzt sogar, dass ich inzwischen mehr mit meinem Computer rede als mit ihr. Und ehrlich gesagt ist das nicht unbegründet: Ich bin der festen Überzeugung, dass ein Sprachmodell umso bessere Ergebnisse liefert, je mehr lebendigen Kontext, Nuancen und Details man hineingibt. All das von Hand auf der Tastatur zu tippen, ist schlicht mühsam und ineffizient.
Genau deshalb brauchte ich ein Sprachdiktat-Tool. Keine 08/15-App, sondern etwas, das exakt so funktioniert, wie ich es will: eine Taste gedrückt halten (Fn / Globe im Push-to-Talk-Modus), Gedanken einsprechen, loslassen – und der transkribierte Text erscheint sofort genau dort, wo der Cursor blinkt.
Was an bestehenden Lösungen störte
Auf dem Mac gibt es bereits hervorragende Lösungen. Ich habe mehrere getestet, und zwei davon gefielen mir wirklich gut.
Wispr Flow funktioniert klasse: ansprechende Benutzeroberfläche, sofortige Reaktion. Doch der kostenlose Tarif stößt schnell an harte Limits, und danach verlangt der Dienst ein monatliches Abonnement.
Dann habe ich MacWhisper ausprobiert:
MacWhisper ist ein großartiges Produkt. Es führt Modelle lokal und kostenlos auf dem Mac aus, was für den Datenschutz optimal ist. Allerdings wollte ich mein Audio an ein schnelles, dediziertes Remote-Cloud-Modell senden, zu dem ich bereits API-Zugang hatte. Und in MacWhisper ist das Eintragen eines eigenen API-Endpunkts und Tokens der kostenpflichtigen Pro-Version vorbehalten.
All diese Apps haben ihre Stärken, aber einen entscheidenden Nachteil: Sie wollen dein Geld.
Und im Zeitalter der KI gilt: Wenn eine App für einen einfachen Wrapper Geld verlangt, setzen wir uns einfach hin und bauen unseren eigenen Vibecoding-Fork.
Wie Simple Flow aufgebaut ist
Ich habe für diese Anwendung keine einzige Zeile Swift-Code selbst geschrieben. Die gesamte Architektur, Logik und das fertige Release-Bundle wurden von OpenAI Codex in nur zwei Prompts erstellt.
Auf hoher Ebene besteht die Pipeline aus vier einfachen Schritten:
- Hotkey abfangen: Die App läuft unauffällig in der Menüleiste und fängt das Drücken der
Fn-Taste (Globe) global im Push-to-Talk-Modus ab. - Audio aufnehmen: Solange die Taste gehalten wird, wird das Mikrofonsignal in einen temporären Puffer geschrieben.
- In die Cloud senden: Sobald die Taste losgelassen wird, wird das Audio asynchron an meinen API-Endpunkt mit Token-Authentifizierung gesendet.
- Ergebnis einfügen: Der transkribierte Text wird sofort über synthetische
⌘V-Tastaturanschläge in das aktive Fenster eingefügt.
Entwicklung in 2 Prompts mit Codex
Prompt 1: Kernlogik und Architektur
Im ersten Prompt habe ich die Anforderungen einfach in eigenen Worten beschrieben: Hintergrund-Daemon, Fn-Taste, Audioaufnahme, Netzwerk-Request und Texteinfügung.
Codex wählte die passenden macOS-Systemframeworks (AVFoundation, ApplicationServices, CoreGraphics) und baute eine saubere State Machine, Hotkey-Listener und Texteinfüger:
public enum DictationPhase: Equatable, Sendable {
case idle
case recording
case transcribing
case feedback(FeedbackKind)
}
public struct DictationStateMachine: Sendable {
public private(set) var phase: DictationPhase = .idle
public mutating func handle(_ event: DictationEvent) -> [DictationEffect] {
switch (phase, event) {
case (.idle, .hotkeyPressed):
phase = .recording
return [.captureFocus, .startAudio]
case (.recording, .hotkeyReleased):
phase = .transcribing
return [.stopAndTranscribe]
case (.recording, .escapePressed):
phase = .idle
return [.cancelAudio, .returnToIdle]
case (.transcribing, .transcriptionInserted):
phase = .feedback(.inserted)
return []
case (.transcribing, .failed(let message)):
phase = .feedback(.error(message))
return []
default:
return []
}
}
}import ApplicationServices
import CoreGraphics
import Foundation
public final class HotkeyMonitor: @unchecked Sendable {
private var eventTap: CFMachPort?
private var decoder: HotkeyEventDecoder
public init(hotkey: Hotkey = .fnKey) {
self.decoder = HotkeyEventDecoder(hotkey: hotkey)
}
public func start() {
let mask = (1 << CGEventType.flagsChanged.rawValue)
self.eventTap = CGEvent.tapCreate(
tap: .cghidEventTap,
place: .headInsertEventTap,
options: .listenOnly,
eventsOfInterest: CGEventMask(mask),
callback: { (proxy, type, event, refcon) -> Unmanaged<CGEvent>? in
guard let refcon = refcon else { return Unmanaged.passUnretained(event) }
let monitor = Unmanaged<HotkeyMonitor>.fromOpaque(refcon).takeUnretainedValue()
monitor.handleFlagsChanged(event.flags)
return Unmanaged.passUnretained(event)
},
userInfo: Unmanaged.passUnretained(self).toOpaque()
)
let runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0)
CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, .commonModes)
CGEvent.tapEnable(tap: eventTap!, enable: true)
}
}import CoreGraphics
import Foundation
public final class SystemEventPoster: EventPosting, @unchecked Sendable {
public func postPasteCommand() -> Bool {
let cmdKeyCode: CGKeyCode = 55 // Command key
let vKeyCode: CGKeyCode = 9 // 'V' key
let source = CGEventSource(stateID: .combinedSessionState)
guard let cmdDown = CGEvent(keyboardEventSource: source, virtualKey: cmdKeyCode, keyDown: true),
let vDown = CGEvent(keyboardEventSource: source, virtualKey: vKeyCode, keyDown: true),
let vUp = CGEvent(keyboardEventSource: source, virtualKey: vKeyCode, keyDown: false),
let cmdUp = CGEvent(keyboardEventSource: source, virtualKey: cmdKeyCode, keyDown: false) else {
return false
}
cmdDown.flags = .maskCommand
vDown.flags = .maskCommand
vUp.flags = .maskCommand
cmdUp.flags = []
// Post Command+V synthetic event tap directly to active focused application
cmdDown.post(tap: .cghidEventTap)
vDown.post(tap: .cghidEventTap)
vUp.post(tap: .cghidEventTap)
cmdUp.post(tap: .cghidEventTap)
return true
}
}Prompt 2: Einstellungsfenster und Produktions-Build
Im zweiten Prompt bat ich um eine vollwertige Benutzeroberfläche:
- Statusanzeige in der Menüleiste (
Ready/Recording). - Natives Einstellungsfenster: Mikrofonauswahl, Hotkey-Recorder, Berechtigungsprüfungen (Accessibility, Microphone) und Autostart bei der Anmeldung.
- Sichere Speicherung von Server-URL und API-Token im macOS-Schlüsselbund (Keychain).
- Build-Skripte zur Erstellung der fertigen
.app-Datei.
Codex schrieb das gesamte UI, kompilierte den Swift-Code direkt im Terminal und übergab mir die fertige SimpleFlow.app.
Das Ergebnis
Ich habe SimpleFlow.app einfach in meinen /Applications-Ordner gezogen, Token und Server-URL in den Einstellungen hinterlegt – und es funktionierte auf Anhieb perfekt.


Ich habe die Ressourcennutzung sogar während des Schreibens dieses Beitrags mit top gemessen:
- CPU:
0.0%im Ruhezustand (der Prozess schläft imRunLoopund wacht nur beiCGEventTap-Hotkey-Interrupts auf). - RAM:
~50 MB(der standardmäßige Basisspeicher der AppKit- und SwiftUI-Systemframeworks).
Die App verbraucht praktisch keine Systemressourcen, sitzt ruhig im Hintergrund und transkribiert meine Diktate ohne Abos oder Beschränkungen.
Quellcode und Releases auf GitHub
Ich habe den gesamten Quellcode des Projekts als Open Source veröffentlicht:
Das fertige Archiv (SimpleFlow-v1.0.0.zip) kann direkt von der GitHub-Releases-Seite heruntergeladen werden. Zur Installation einfach entpacken und SimpleFlow.app in den Ordner /Applications ziehen.
Bottom Text
Im Jahr 2026 ist Personal Software kein Mythos mehr, sondern Realität für jeden – selbst für Menschen ohne Programmiererfahrung. Ein eigenes Tool genau nach den eigenen Bedürfnissen zu bauen ist heute einfacher, als im Internet nach der perfekten Alternative zu suchen.
Ich weiß nicht, welches Fazit man sonst daraus ziehen soll – nutzt es einfach, Leute.