# Architecture (/docs/architecture)
Warble is a Swift package with two targets: `WarbleKit`, the library with all the logic and UI, and `warble`, a thin executable for the CLI and app entry point.
```text
hotkey ─▶ DictationController ─▶ AudioRecorder ─▶ ParakeetTranscriber
│ │
▼ ▼
AppState ◀──────── DictionaryEngine ◀── raw text
┌────────┼─────────┐
▼ ▼ ▼
menu bar pill windows ─▶ HistoryStore, DictionaryStore, SettingsModel
```
## Modules [#modules]
| Piece | Responsibility |
| ------------------------- | ------------------------------------------------------------------------------------------- |
| `AppDelegate` | Wires everything together and walks launch: settings, permissions, model, hotkey. |
| `DictationController` | Key down to typed text: record, transcribe, apply the dictionary, paste, save to history. |
| `AppState` | Observable single source of truth: dictation phase, model status, waveform levels. |
| `AudioRecorder` | Microphone via AVAudioEngine and system audio via ScreenCaptureKit, written as 16 kHz mono. |
| `ParakeetTranscriber` | FluidAudio's `AsrManager` with Parakeet TDT v3, loaded once and shared. |
| `TextInserter` | Pastes through the clipboard and restores what was there before. |
| `RecordingPillController` | A click-through `NSPanel` hosting the SwiftUI pill on every Space. |
| `WindowCoordinator` | Main and setup windows; shows a Dock icon only while one is open. |
## Design choices [#design-choices]
* **One phase enum.** `DictationPhase` drives the icon, the pill and the sidebar status, so they cannot disagree.
* **Plain files.** History, dictionary and settings are JSON a user can read, back up or delete.
* **No silent data loss.** A file that fails to decode is moved aside as `*.corrupt-.json` before anything new is written.
* **Testable core.** Dictionary matching, stats, audio levels and the phase machine are pure and covered by unit tests.
# Command line (/docs/cli)
`scripts/install.sh` links `warble` into `~/.local/bin`. The command talks to the same config file as the app.
| Command | What it does |
| ------------------------------- | ------------------------------------------------------------------------- |
| `warble start` | Start the dictation app (what opening Warble.app does). |
| `warble status` | Show hotkey, language, audio input, mode and autostart. |
| `warble set-hotkey ` | Set the hotkey, for example `globe`, `rightoption`, `f5` or `ctrl+space`. |
| `warble get-hotkey` | Print the current hotkey. |
| `warble set-language ` | Set the language, for example `pl`, `en` or `auto`. |
| `warble transcribe-file ` | Transcribe an audio file with Parakeet and print the text. |
| `warble enable-autostart` | Start Warble at login. |
| `warble disable-autostart` | Stop starting Warble at login. |
Settings changed from the command line are picked up the next time Warble starts.
```bash
warble set-hotkey rightoption
warble set-language pl
warble transcribe-file ~/Downloads/voice-memo.m4a
```
# Configuration file (/docs/configuration)
Settings are plain JSON in `~/.config/warble/config.json`. The app writes it whenever you change something, so editing it by hand is optional.
```json
{
"hotkeys": [{ "keyCode": 63, "modifiers": [] }],
"language": "en",
"toggleMode": false,
"spokenPunctuation": false,
"audioCaptureSource": "microphone",
"maxRecordings": 0,
"shouldShowRecordingPill": true,
"shouldPlaySounds": true,
"hasCompletedOnboarding": true
}
```
| Key | Values |
| ------------------------- | ---------------------------------------------------------------------------------------------------- |
| `hotkeys` | List of `{ keyCode, modifiers }`. Modifiers are `cmd`, `shift`, `ctrl`, `option`. Key code 63 is fn. |
| `language` | A [language code](/docs/languages) or `auto`. |
| `toggleMode` | `true` to press once to start and again to stop. |
| `spokenPunctuation` | `true` to turn spoken words into punctuation. |
| `audioCaptureSource` | `microphone`, `systemAudio` or `microphoneAndSystemAudio`. |
| `audioInputDeviceUID` | Core Audio device UID; leave it out for the system default. |
| `maxRecordings` | `0` deletes audio after transcription; `1` to `100` keeps that many. |
| `shouldShowRecordingPill` | Show the floating pill while dictating. |
| `shouldPlaySounds` | Play start and stop sounds. |
| `hasCompletedOnboarding` | Set to `false` to see setup again at next launch. |
Other data lives next to it:
| Path | Contents |
| ------------------------------------------------------ | ---------------------- |
| `~/.config/warble/recordings` | Kept audio recordings. |
| `~/Library/Application Support/Warble/history.json` | Dictation history. |
| `~/Library/Application Support/Warble/dictionary.json` | Your dictionary. |
| `~/Library/Application Support/FluidAudio/Models` | The Parakeet model. |
# Contributing (/docs/contributing)
Contributions are welcome, from typo fixes to new features. Keep Warble local-first: no feature should send audio or text off the Mac.
## Build and test [#build-and-test]
```bash
swift build
swift test --disable-sandbox
scripts/install.sh # build, bundle and install to ~/Applications
```
## Preview mode and screenshots [#preview-mode-and-screenshots]
`WARBLE_PREVIEW` opens any screen on sample data, without touching your real history, dictionary or settings:
```bash
WARBLE_PREVIEW=history .build/Warble.app/Contents/MacOS/warble start
```
Scenes: `menubar`, `history`, `dictionary`, `settings`, `onboarding-0` to `onboarding-5`, and `pill-recording`, `pill-transcribing`, `pill-inserted`, `pill-error`. Permissions always read as granted in preview mode, so every Mac renders the same screens.
`scripts/capture-screenshots.sh` renders them all into `docs-site/public/screenshots`. It uses `screencapture`, so the terminal running it needs Screen Recording permission. Each scene is launched with `open`, which brings the window to the front, so captures show it active even when the terminal is in the background.
## Docs [#docs]
The site lives in `docs-site`, built with [Fumadocs](https://fumadocs.dev) and exported as static HTML.
```bash
cd docs-site
bun install
bun run dev
```
## Pull requests [#pull-requests]
1. Branch from `main`.
2. Add tests for logic changes; UI and permissions need a manual check on a Mac.
3. Run `swift test` and, for docs changes, `bun run build`.
4. Open the pull request with a short description and a screenshot for UI changes.
## Credits [#credits]
Warble grew out of [open-wispr](https://github.com/human37/open-wispr) by human37 and uses [FluidAudio](https://github.com/FluidInference/FluidAudio) to run NVIDIA Parakeet. Warble and open-wispr are MIT licensed; FluidAudio is Apache 2.0. The Parakeet model has its own license, listed on its Hugging Face page.
# Dictating (/docs/dictating)
## Hold to talk [#hold-to-talk]
Hold the hotkey, speak, release. Warble plays a soft tick when it starts listening and a pop when it stops, then types the text at the cursor.
Taps shorter than a quarter of a second are ignored, so brushing the key does nothing.
## Toggle mode [#toggle-mode]
For long dictation, switch **Settings → Dictation → Mode** to **Press to start, press to stop**. Press once to start and again to finish.
## What happens to your words [#what-happens-to-your-words]
Audio is recorded as 16 kHz mono while the key is held.
Parakeet TDT v3 transcribes it on the Neural Engine.
If spoken punctuation is on, words like “comma” become punctuation.
Your
[dictionary](/docs/dictionary)
fixes spellings and expands shortcuts.
The text is pasted at the cursor, and your clipboard is restored a second later.
The dictation is saved to
[History](/docs/history)
.
## Spoken punctuation [#spoken-punctuation]
Turn on **Settings → Dictation → Spoken punctuation** to say punctuation out loud.
| Say | Get |
| ----------------------------------- | ----------- |
| period, full stop | `.` |
| comma | `,` |
| question mark | `?` |
| exclamation mark, exclamation point | `!` |
| colon / semicolon | `:` / `;` |
| ellipsis | `...` |
| hyphen | `-` |
| dash | a long dash |
| open quote / close quote | `"` |
| open paren / close paren | `(` / `)` |
| new line | line break |
| new paragraph | blank line |
## Dictating system audio [#dictating-system-audio]
**Settings → Audio → Listen to** can also capture audio playing on your Mac, for example to transcribe a video call. This needs the Screen & System Audio Recording permission.
# Dictionary (/docs/dictionary)
The dictionary rewrites every transcription before it is typed. It has two kinds of entries. Add them with the bar at the bottom of the **Dictionary** pane: pick a word or a replacement, type, and press Return. Hover an entry to remove it, or right-click it.
## Words [#words]
Add a word with the spelling you want, such as `Kubernetes`, `SwiftUI` or `GitHub`. Whenever Parakeet produces it in any capitalization, Warble types your version.
## Replacements [#replacements]
Map a short spoken phrase to longer text:
| When I say | Warble types |
| -------------- | --------------------------- |
| my email | `hello@example.com` |
| office address | `221B Baker Street, London` |
| my sign off | `Best,` + new line + `Alex` |
## How matching works [#how-matching-works]
* Case-insensitive and whole-word only: `ai` never matches inside `rain`.
* Word boundaries understand accented letters, so Polish and other languages work.
* Longer phrases win, so `work email` is applied before `email`.
* Replacement text is typed literally, including `$` and backslashes.
The dictionary corrects text after transcription. It does not yet bias Parakeet toward your words while it listens; FluidAudio supports that with an extra CTC model, which is on the roadmap.
The dictionary is stored in `~/Library/Application Support/Warble/dictionary.json`.
# Your first dictation (/docs/first-run)
The first time Warble starts, it opens a short setup. You can run it again later from **Settings → System → Run Setup Again**.
### Microphone [#microphone]
Warble records only while you hold the dictation key. Click **Allow Microphone** and accept the macOS prompt.
### Accessibility [#accessibility]
Accessibility lets Warble see your hotkey from any app and paste the text it transcribed. Click **Open Accessibility Settings**, switch on Warble, and setup continues by itself.
### Speech model [#speech-model]
Parakeet TDT v3 downloads once from Hugging Face, then gets optimized for your Mac. After that, Warble works offline.
### Your key and language [#your-key-and-language]
Click the hotkey button and press the key you want to hold. fn 🌐 is the default; Right Option works well on keyboards without it. Pick a language, or leave Auto-Detect.
### Try it [#try-it]
Click the text box, hold your key, say something, and let go. When the text appears, you are done.
# History (/docs/history)
Open Warble from the menu bar and choose **History**. Dictations are grouped by day, newest first, with the app they went into, word count and length.
* **Search** with the field in the toolbar; it matches the text and the app name.
* **Hover** a row to copy it, or play its audio if you keep recordings.
* **Right-click** for Copy, Play, Transcribe Again and Delete.
* **Clear History** in the toolbar deletes every entry after asking.
Your latest dictation is also in the menu bar panel, with a button to copy it. The subtitle above the list shows your total words, speaking pace and the minutes saved compared to typing.
## Keeping audio [#keeping-audio]
By default audio is deleted right after transcription. To replay or re-transcribe later, set **Settings → Privacy → Keep audio recordings** to the last 10, 25, 50 or 100. Older files are removed as new ones arrive.
## Where it is stored [#where-it-is-stored]
History is a JSON file at `~/Library/Application Support/Warble/history.json`, capped at the latest 5,000 dictations. Audio lives in `~/.config/warble/recordings`.
# Introduction (/docs)
Hold a key, speak, let go. Warble records while the key is down, transcribes with NVIDIA Parakeet TDT v3 on your Mac, and types the result wherever your cursor is.
Warble lives in the menu bar. Click its icon for a small panel with:
* **Status** and the key to hold, so you know Warble is ready.
* **Your last dictation**, with a button to copy it again.
* **Quick toggles** for the language, Hold or Toggle mode, the recording pill and sounds.
* Links to **History**, **Dictionary** and **Settings**, which open in one window.
Right-click the icon for Settings and Quit.
## Why Warble [#why-warble]
## What you get [#what-you-get]
* **Hold-to-talk or toggle** dictation on fn 🌐 or any key you choose.
* A **recording pill** with a live waveform, so you always know when Warble is listening.
* **History** of every dictation, searchable, with the app it went into and optional audio.
* A **dictionary** for names, jargon and text shortcuts.
* **Onboarding** that handles permissions and the model download for you.
## Next steps [#next-steps]
# Installation (/docs/installation)
Warble is distributed as source for now. Building takes one command.
## Requirements [#requirements]
* macOS 26 or later.
* Xcode 26 or the matching Command Line Tools (Swift 6.2).
* About 500 MB of free space for the Parakeet model (`parakeet-tdt-0.6b-v3`, int8), downloaded on first launch.
## Install [#install]
### Clone the repository [#clone-the-repository]
```bash
git clone https://github.com/pieralukasz/warble.git warble
cd warble
```
### Build and install [#build-and-install]
```bash
scripts/install.sh
```
This builds a release binary, bundles `Warble.app`, copies it to `~/Applications` and links the `warble` command into `~/.local/bin`. Warble opens when it finishes.
### Follow the setup window [#follow-the-setup-window]
The first launch opens setup, which asks for the Microphone and Accessibility permissions and downloads Parakeet. See [Your first dictation](/docs/first-run).
Warble copies `~/.config/open-wispr/config.json` on first launch, so your hotkey, language and microphone carry over. Quit the old app before using Warble, because both listen to the same key.
## For agents [#for-agents]
A coding agent such as Claude Code or Codex can do the install for you. Paste this prompt into it:
```text title="Prompt"
Install Warble, the free local voice dictation app for macOS, from
https://github.com/pieralukasz/warble.
1. Check that this Mac runs macOS 26 or later (`sw_vers -productVersion`) and
that Swift 6.2 or later is available (`swift --version`). If not, stop and
tell me what is missing.
2. Clone the repository into ~/Projects/warble, or pull it if it is already
there, and run `scripts/install.sh` from its root.
3. Run `~/.local/bin/warble status` and show me the output.
4. Tell me that Warble opened a setup window, and that I have to allow the
Microphone and Accessibility permissions there myself.
The full documentation, as plain text for agents:
https://warble.lucaspiera.com/llms-full.txt
```
Or run the same steps yourself:
```bash
git clone https://github.com/pieralukasz/warble.git ~/Projects/warble \
|| git -C ~/Projects/warble pull --ff-only
cd ~/Projects/warble && scripts/install.sh
~/.local/bin/warble status
```
The install needs no input until the end, when macOS asks a person to allow the Microphone and Accessibility permissions. An agent cannot click those for you.
For agents that read documentation, the site publishes an index at [`/llms.txt`](/llms.txt), every page in one file at [`/llms-full.txt`](/llms-full.txt), and each page as Markdown through the **Copy Markdown** button at the top.
## Start at login [#start-at-login]
Turn on **Settings → System → Start Warble at login**, or run `warble enable-autostart`.
## Update [#update]
Pull the latest changes and run `scripts/install.sh` again. Your settings, history and dictionary are kept.
## Uninstall [#uninstall]
```bash
scripts/uninstall.sh # removes the app, keeps your data
scripts/uninstall.sh --purge # also deletes settings, history and dictionary
```
The Parakeet model lives in `~/Library/Application Support/FluidAudio` and is left in place, since other FluidAudio apps can share it.
# Languages (/docs/languages)
Choose a language in **Settings → Dictation → Language**, from the menu bar, or with `warble set-language `.
FluidAudio documents Parakeet TDT v3 as covering 25 European languages. Warble lists 28 codes, so if one works poorly for you, please open an issue.
**Auto-Detect** (`auto`) lets Parakeet decide per dictation. If it guesses wrong, pick your language explicitly.
| Language | Code |
| ---------- | ---- |
| English | `en` |
| Polish | `pl` |
| Spanish | `es` |
| French | `fr` |
| German | `de` |
| Italian | `it` |
| Portuguese | `pt` |
| Romanian | `ro` |
| Dutch | `nl` |
| Danish | `da` |
| Swedish | `sv` |
| Finnish | `fi` |
| Hungarian | `hu` |
| Estonian | `et` |
| Latvian | `lv` |
| Lithuanian | `lt` |
| Maltese | `mt` |
| Czech | `cs` |
| Slovak | `sk` |
| Slovenian | `sl` |
| Croatian | `hr` |
| Bosnian | `bs` |
| Russian | `ru` |
| Ukrainian | `uk` |
| Belarusian | `be` |
| Bulgarian | `bg` |
| Serbian | `sr` |
| Greek | `el` |
# Privacy (/docs/privacy)
Warble is built so that there is nothing to trust: it has no server.
## What happens to your audio [#what-happens-to-your-audio]
* Recording starts when you press the hotkey and stops when you release it.
* Audio is written to a temporary 16 kHz WAV, transcribed on your Mac, and deleted, unless you choose to keep recordings.
* Transcription runs in Parakeet on the Neural Engine. No audio or text is sent anywhere.
## Network access [#network-access]
The only network request is the model download from Hugging Face on first launch, handled by FluidAudio. After that Warble works with networking turned off.
## What is stored [#what-is-stored]
| Data | Location | How to remove |
| ---------- | ------------------------------------------------------ | -------------------------------------- |
| Settings | `~/.config/warble/config.json` | `scripts/uninstall.sh --purge` |
| History | `~/Library/Application Support/Warble/history.json` | **History → Clear History** |
| Dictionary | `~/Library/Application Support/Warble/dictionary.json` | Remove entries in **Dictionary** |
| Kept audio | `~/.config/warble/recordings` | Set **Keep audio recordings** to Never |
## Permissions [#permissions]
| Permission | Why |
| ------------------------------- | --------------------------------------------------------------- |
| Microphone | Record while the hotkey is held. |
| Accessibility | Notice the hotkey in other apps and paste the transcribed text. |
| Screen & System Audio Recording | Only if you choose to transcribe system audio. |
Warble has no analytics, crash reporting or account. The source is on [GitHub](https://github.com/pieralukasz/warble) if you want to check.
# Recording pill (/docs/recording-pill)
While you dictate, a small Liquid Glass pill floats above the Dock on the screen your pointer is on. It never takes focus and clicks pass straight through it.
It is one piece of glass that changes shape rather than a set of separate views: a wide capsule while you speak, a small one while Parakeet works, and a circle with a check mark once the text is typed.
| State | What you see |
| ------------ | ------------------------------------------------------------ |
| Listening | A red dot and bars that follow your voice. |
| Transcribing | Three rippling dots while Parakeet works. |
| Typed | A green check for a moment, then the pill fades. |
| Problem | A warning with a short reason, such as a missing permission. |
Turn it off in **Settings → Feedback → Show recording pill**. The menu bar icon animates the same states either way.
# Settings (/docs/settings)
## Dictation [#dictation]
| Setting | What it does |
| ------------------ | ------------------------------------------------------------------------------------------------------------- |
| Hotkey | Click, then press the key to hold. Modifier-only keys like fn or Right Option work on their own; Esc cancels. |
| Mode | **Hold to talk**, or **Press to start, press to stop** for long dictation. |
| Language | One of the [supported languages](/docs/languages), or Auto-Detect. |
| Spoken punctuation | Say “comma” or “new line” to insert them. See [Dictating](/docs/dictating#spoken-punctuation). |
## Feedback [#feedback]
| Setting | What it does |
| ------------------- | ---------------------------------------------------------- |
| Show recording pill | The floating [pill](/docs/recording-pill) while you speak. |
| Play sounds | A quiet tick and pop when recording starts and stops. |
## Audio [#audio]
| Setting | What it does |
| ---------- | --------------------------------------------------------------- |
| Listen to | Microphone, system audio, or both mixed together. |
| Microphone | A specific input device, remembered across reboots and replugs. |
## Privacy [#privacy]
| Setting | What it does |
| --------------------- | ------------------------------------------------------------ |
| Keep audio recordings | Never, or the last 10 to 100 recordings for replay. |
| History | How many dictations are stored, with a button to clear them. |
| Data folder | Opens `~/Library/Application Support/Warble` in Finder. |
## System [#system]
| Setting | What it does |
| --------------------- | ------------------------------------------------ |
| Start Warble at login | Installs a LaunchAgent for your user. |
| Run Setup Again | Reopens the setup window to recheck permissions. |
# Troubleshooting (/docs/troubleshooting)
Warble needs Accessibility access to see the key. Open **System Settings → Privacy & Security → Accessibility** and make sure Warble is on. The menu bar icon shows a lock while it waits for this.
After rebuilding Warble from source, macOS can treat the new build as a different app. If the switch is on but nothing happens, remove Warble from the list with the minus button and add it again.
In **System Settings → Keyboard**, set **Press 🌐 key to** to **Do Nothing**. Or pick another hotkey such as Right Option in Warble’s settings.
Another dictation app is listening to the same key. If you used open-wispr before, quit it (and remove its login item) before using Warble.
The first launch needs an internet connection to fetch Parakeet from Hugging Face. Check the connection and press **Try Again** in the menu bar panel or in setup.
Warble pastes into whatever has keyboard focus when you release the key. Make sure the cursor is in a text field. Some password fields block pasting on purpose.
Capturing system audio needs **Screen & System Audio Recording** permission. Grant it in System Settings, then quit and reopen Warble.
Still stuck? [Open an issue](https://github.com/pieralukasz/warble/issues) with the output of `warble status`.