Hold a key, talk, and clean text lands at your cursor. The twist: Flow ships no AI. It's a thin shell that borrows your machine's intelligence through the Switchboard daemon — proving the "beyond web apps" path. No browser, no API key, no bundled model.
mic ──▶ daemon claude_transcribe ──▶ daemon claude_complete ──▶ your cursor
(local whisper, on-device) (local model if present, (paste)
else your own Claude)
100% on-device (optional): cleanup — stripping um/uh and fixing punctuation — is pure text,
so it runs on a small LOCAL model when one is available. Install Ollama and
pull a tiny model (ollama pull llama3.2:1b or qwen2.5:0.5b); Flow auto-picks it and the pill
reads "on-device." With no local model it falls back to your Claude, and if that's unavailable it
ships the raw transcript — dictation never fails. Point cleanupModel in ~/.flow/config.json at a
specific model to pin it.
Flow authenticates as its own least-privilege principal, native@ai.thelastprompt.flow, over the
daemon's native listener (a per-app token, never the browser's pairing key). It can only ever
act as itself.
- A local app talks straight to the daemon — the extension/browser is not involved.
- Speech-to-text runs on-device (
whisper), reached via the gatedclaude_transcribeverb. - The "Flow magic" — stripping um / uh / you know into clean prose — runs on your own Claude
via
claude_complete, gated by the same broker as every web wrapp. - The app holds no key and no model; setup grants it a scoped, revocable identity.
| Need | Install |
|---|---|
ffmpeg (mic capture) |
brew install ffmpeg |
whisper (on-device STT) |
pip install openai-whisper |
macOS say, osascript, pbcopy |
built in |
Build the daemon once: npm run -w @relay/sidekick build.
# No mic needed — macOS `say` generates rambly speech, runs the whole pipeline, prints before/after:
node examples/flow/flow.mjs demo "um so I think we should uh ship on friday you know"
# The real thing — push-to-talk mic, pastes cleaned text at your cursor:
node examples/flow/flow.mjs runrun is push-to-talk: ENTER to start recording, speak, ENTER to stop. The cleaned text is copied to
your clipboard and auto-pasted into the frontmost app (grant your terminal Accessibility for the
auto-paste; otherwise it's on the clipboard to ⌘V yourself).
- Setup (once):
flow.mjsreads the daemon's pairing token and calls theregisterNativeAppcontrol action — the native equivalent of the connect-consent. That mints Flow's per-app token (saved 0600 in~/.flow/token.json) and a grant for the local capabilities + your online models. - Every run after: Flow uses only its per-app token on the native socket. Least privilege.
- STT backend: the daemon runs whatever
RELAY_STT_CMDpoints at; Flow points it atwhisper-stt.mjs(audio path in → transcript out). Swap in Apple's Speech framework or a whisper.cpp server by changing that one env var — nothing else moves.
For a self-contained demo, Flow manages its own daemon instance (private ~/.flow/relay, ports
8795/8796) so it never touches your real ~/.relay. In production it would attach to the menubar
daemon once that ships STT configuration.
| Var | Default | Meaning |
|---|---|---|
FLOW_MIC |
:0 |
avfoundation audio device (ffmpeg -f avfoundation -list_devices true -i "" to list) |
FLOW_WHISPER_MODEL |
tiny |
whisper model (tiny/base/small…) |
FLOW_PORT / FLOW_NATIVE_PORT |
8795 / 8796 |
Flow's private daemon ports |
FLOW_HOME |
~/.flow |
Flow's state (token + private daemon dir) |