Home /

I let Codex build and test my first native Mac app

Codex carried a local speech-to-text experiment through model selection, native Mac development, installed-app failures and physical audio testing.

I built Billie Flow because I already used Wispr Flow and wondered whether I could make the useful part run locally. What led me to it was just curiosity. Could I really?

I did not need a replacement. Wispr Flow gives me enough value that replacing it was never the plan. Cost, latency and privacy would all matter if I were turning the idea into a real product, but that is a cleaner motivation than the one I actually had. I wanted to see what would happen.

Billie Flow became my first native Mac app. The part I keep coming back to, though, is not that Codex wrote it. It is how far beyond the code I let Codex go.

The model I expected to use lost

The project started with a voice memo, not a menu-bar UI.

I had an audio-capable Gemma 12B path in my head. Rather than build around that assumption, I ran the same 35.3-second memo through several local recognition paths and kept speech recognition separate from text cleanup.

That separation mattered. A cleanup model can make a transcript look convincing while quietly preserving the important words the recogniser got wrong. The raw recognition had to remain visible before a second model was allowed to improve the wording.

Gemma completed the memo in 258.79 seconds and introduced drift around an overlapping chunk. MLX Whisper large-v3-turbo produced the most useful recognition in about 3.68 seconds. Qwen2.5 1.5B then ran the winning light-cleanup pass in about 0.62 seconds. The full Billie Flow model analysis contains the other branches, timings, vocabulary failures and the evidence behind the fixed app defaults.

The model I first had in my head was not the right answer. A combination of two smaller models gave the quicker result and the more useful practical result.

That does not make the pair right for every setting. This was one memo on one machine, and every recognition path still got at least one important project name wrong. The app therefore keeps recognition, cleanup and deterministic known-name corrections as separate stages instead of asking a polished model response to hide the mistake.

The app left the repository

My first native Mac app meant Swift, global hotkeys, microphone capture, clipboard handling, a persistent local worker and macOS permission boundaries I had not worked with before.

I think the most interesting part of the project was how involved I let Codex be with everything. In 5.6 it could literally write the thing, build the thing, install the dependencies for the thing, test the thing.

Repository tests are the comfortable version of that claim. The stranger version is Codex installing the built app, opening the real macOS surfaces, using Computer Use to work through permissions, recording through the physical audio path and checking what reached the clipboard.

At one point I was lying there waiting for it to work away when speech started coming out of my speakers. Billie Flow recorded it and transcribed it.

I do not know what produced that speech, so I am not going to invent a tidy mechanism after the fact. What I observed was Codex deciding that it needed to exercise the voice path and then doing it. I remember thinking that 5.5 would not have taken that initiative. That is an impression, not a controlled comparison.

A compact Billie Flow HUD reads Recording, 0:01, release to finish, beside a five-bar level meter.
The global shortcut records only while held; releasing it submits the temporary audio for local processing.

The physical flow eventually completed with non-empty text on the clipboard, the app and persistent worker still healthy, no new crash and no temporary recording left behind.

Billie Flow's HUD reads Copied, Light cleanup, and Ready on the clipboard beside a document icon.
This is the visible end state from the installed app after local recognition and light cleanup completed.

Codex captured those states too. The screenshots are not desktop crops or bits of unrelated development UI. They are clean records from the installed v0.2.1 app, with other apps, notifications, the cursor and machine-identifying chrome kept out of frame.

Green checks were not enough

None of that autonomy made the first package magically correct.

The first public v0.2.0 build launched, stayed alive and passed its process, signature, source and model checks. A replacement install then showed that a new user could not see the Settings window needed to install the local worker. The process was healthy. The app was still unusable.

The same installed-app pass exposed cleanup silently falling back to raw recognition because the pinned MLX library was still receiving an obsolete argument. The request returned success, but the UI was claiming to have done work that had not happened. A fast final setup check could also exit before the app registered its completion and leave setup stuck at verification.

Earlier physical microphone attempts had already found an actor-isolation crash on the first audio buffer and then a format mismatch while writing the converted WAV. Each failure crossed a different boundary: Swift concurrency, Core Audio, a pinned Python library, process lifecycle, or visible macOS presentation.

This is where Codex impressed me more than code generation. It could keep following the problem after the repository checks had turned green, rebuild the app, operate the installed copy and turn each observed failure into a release assertion. The current gate checks for an actual first-launch window and requires warning-free cleanup rather than treating a fallback as success.

Useful enough to release, not enough to productise

The source is public on GitHub, along with the install-tested v0.2.1 build. I want Billie Flow to be usable should you want to, but I do not really care if anyone does.

Billie Flow's Install local speech models dialog says it will download about 3.5 GB from Hugging Face and requires Apple Silicon and macOS 26.
Nothing large starts until Install is chosen; the disclosure also limits the proof of concept to English speech on Apple Silicon and macOS 26.

That dialog is the honest boundary. The proof of concept is for Apple Silicon on macOS 26, with English recognition and about 3.5 GB of consented runtime and model setup. Inference is local after setup, but the setup still downloads Python dependencies and fixed models. The app is ad-hoc signed and unnotarised. There is no updater, App Store release, compatibility promise or support plan. I am definitely not getting an Apple Developer Programme membership at this point.

Billie Flow does not prove that rebuilding Wispr Flow is a sensible use of everybody's time. I still use Wispr Flow. It proves something smaller: if you want a personal local version of the loop, you can now make one surprisingly quickly, and it can be surprisingly good.

Mostly, I wanted to record the fact that while I was lying there, Codex decided it needed to test the microphone and speech started coming out of my speakers. That still feels ridiculous. It was really really impressive.