Thanks for taking a look. Blurt is a small, open-source macOS dictation app, and contributions of all sizes are welcome — bug reports, fixes, docs, or a new idea.
Blurt is macOS-only (Intel or Apple Silicon, AppKit + AVFoundation). You need a Mac
with Xcode to build, test, or run it — see Local setup below.
On Linux you can still read and edit the Swift source, but you can't build or
verify it locally; CI on macos-26 is the authority on green.
AGENTS.md is the canonical guide to how the code is laid out —
the engine package, the AppKit shell, the dictation pipeline, and the
intentional design decisions behind them. Read it before a non-trivial change so
you don't reintroduce something that was deliberately removed.
You'll need:
- A Mac (Intel or Apple Silicon) on macOS 15+ (26 recommended).
- Xcode 26+ — the full app, not just the Command Line Tools.
xcode-select -pshould print a path insideXcode.app; if it doesn't, runsudo xcode-select -s /Applications/Xcode.app. - Homebrew, which
bootstrap.shuses to install everything else. - An AssemblyAI API key (free tier is plenty) if you want to actually dictate with your build. Blurt's setup wizard asks for it on first launch and stores it in the Keychain.
Then:
git clone https://github.com/AssemblyAI/blurt.git
cd blurt
scripts/bootstrap.sh # install the toolchain from Brewfile
scripts/dev-build.sh # build Blurt and install it to /Applications
open -a "Blurt Dev"That's the whole setup. Everything else is one of these scripts:
| Script | What it does |
|---|---|
scripts/bootstrap.sh |
brew bundle install from Brewfile — xcodegen, swiftlint, prettier, shellcheck, markdownlint-cli2, periphery, xcbeautify, node — then npm ci --prefix tools/jscpd for the pinned jscpd, and turns on the repo's pre-commit hook (.githooks/pre-commit: formats and lints staged Markdown), and verifies each one answers on PATH, because check.sh skips a missing linter rather than failing |
scripts/dev-build.sh |
Clean signed Debug-Local build, installed as /Applications/Blurt Dev.app. The everyday loop. |
scripts/check.sh |
The full health check CI runs. The source of truth for "is this green?" |
scripts/check.sh --portable |
Just the platform-independent subset (docs, site, scripts, workflows) — the part that runs off a Mac |
swift test |
Engine unit tests only (Swift Testing), no app build |
scripts/uitest.sh |
The XCUITest suite that drives the real app |
scripts/leaks.sh |
Whole-app leak check |
dev-build.sh installs to /Applications on purpose: macOS refuses to register
Accessibility and Input-Monitoring grants for an app living in a build
directory, so Blurt is unusable from DerivedData. It builds the Debug-Local
configuration — a debug build with the UI-test scaffolding compiled out, so it's
the real app.
Every debug configuration builds under the bundle id dev.alex.blurt.dev and
installs as /Applications/Blurt Dev.app; only Release is dev.alex.blurt
/ Blurt.app. So a dev build sits beside a released Blurt rather than replacing
it — you can run either, and each has its own Privacy & Security rows, its own
settings, and its own Dock icon. Grant "Blurt Dev" microphone and Accessibility
once; they stick across rebuilds.
Each keeps its own AssemblyAI key in the Keychain — blurt for the release,
blurt-dev for the dev build — so a dev build can't read, overwrite or (through
Settings → Advanced → Reset) delete the key you dictate with. You'll enter the
key once in each, and neither one triggers the "another app wants to use your
confidential information" prompt the shared item used to. The Library (saved
transcripts, recordings, read-aloud clips) is split the same way:
~/Library/Application Support/Blurt for the release, Blurt Dev beside it
for the dev build.
scripts/reset-install.sh wipes the release and scripts/reset-dev-install.sh
wipes the dev build — its Library included, so it starts empty; the release's
history is kept — each leaving the other alone (both run scripts/reset-lib.sh). Settings → Advanced → Reset does the same from
inside the app, for whichever build you're running — and restarts it, so the
permission prompts come back.
dev-build.sh signs with an Apple Development certificate from your login
keychain. Adding any Apple ID under Xcode → Settings → Accounts gets you one for
free. (The Developer ID release key is a different thing entirely and lives in a
separate, normally-locked keychain used only by releases — see
RELEASE.md.)
If you'd rather not set up a certificate, build ad-hoc signed instead:
xcodebuild -project App/Blurt/Blurt.xcodeproj -scheme Blurt \
-configuration Debug-Local -destination 'platform=macOS,arch=arm64' \
CODE_SIGN_IDENTITY="-" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=YES buildThat skips the /Applications install, and an ad-hoc signature changes on every
build — macOS keys the Accessibility grant to the signature, so you'd re-grant
permissions after each rebuild. (Blurt notices the signature changed and clears
the orphaned grant at launch; without that the Blurt Dev row stays switched on in
System Settings while the app keeps reporting it as denied.) Fine for checking
that a change compiles and launches; dev-build.sh is the better daily loop.
Run xcodegen generate and commit the regenerated Blurt.xcodeproj/project.pbxproj
alongside it. check.sh fails on drift between the two. Never hand-edit the
.pbxproj — it's generated.
App/BlurtiOS is the iPhone app and the keyboard it ships: the app listens and transcribes, the
keyboard inserts the words (iOS lets no keyboard use the microphone). Its logic lives in
BlurtiOSCore, apart from its views the way BlurtEngine is apart from the Mac shell.
xcodebuild -downloadPlatform iOS # one-time: the simulator runtime this Xcode builds for
scripts/ios-sim.sh # build, boot, install, grant the mic, launch (repo root)
scripts/ios-check.sh # its gates: tests + coverage floor, swiftlint analyze, peripheryNo team or signing is needed for the simulator. Two things trip people up:
- The runtime has to match Xcode's iOS SDK. Simulators on an older runtime still show in the list but are refused at build time.
- The simulator dictates through your Mac's microphone, so macOS has to allow the Simulator app the microphone (System Settings › Privacy & Security › Microphone), with the right input picked under the Simulator's I/O › Audio Input. Refused, it records silence rather than failing.
To use the keyboard: Settings › Apps › Blurt › Keyboards — turn on Blurt, then Allow Full
Access — and Start listening in Blurt. A phone needs a paid team for the App Group;
App/BlurtiOS/README.md has that, the tests and the design loop.
You can read and edit Swift, and scripts/check.sh --portable fully verifies
docs, site, script, and workflow changes. It skips the entire Swift side and
says so on its last line, so don't read a green portable run as "it builds" —
open the PR and let CI on macos-26 answer that.
- Branch off
mainand keep each PR focused on one thing. - Make sure
scripts/check.shpasses (or, if you're on Linux, say so in the PR and let CI verify). - Match the surrounding code: 2-space indent, Swift Testing for tests, no new external dependencies in the engine.
- Write a clear description of what changed and why.
Every PR that touches code gets an installable dev build: CI builds
Blurt.app from your branch and a bot comments a download link, so a reviewer
can try the change rather than imagine it. The build is ad-hoc signed and not
notarized, so it needs its quarantine flag cleared, and its permissions granted
once per build — the comment spells out the commands.
Open an issue using one of the
templates. For bugs, include your macOS version and steps to reproduce. For a
security issue, don't open a public issue — see SECURITY.md.
By contributing, you agree your work is licensed under the repo's MIT License.