Bot-free meeting recorder + transcriber for macOS. Records both sides of your online meeting into one stereo file — the remote party on the left channel, you on the right — so every line is speaker-tagged for free. No diarization model, no meeting bot joining your call, nothing leaves your Mac.
- New app icon & mascot — modern kawaii headphone-wearing parrot; while recording the mascot flaps its wings and sings, and goes still when Reduce Motion is on.
- Transcription status on the recording row — transcribe/convert progress now shows on the recording's own row (with a spinner), not tucked above the transcript pane.
- Better Chinese transcription — the guidance prompt now includes meeting-domain vocabulary, fixing homophone errors on core terms like 逐字稿.
- UI: footer links are underlined with a proper pointer cursor, the build number links to this GitHub repo, the mini-mode level meters use the same dB scale as the main window, the recent list shows 20 items, and the minimum window width is now 380pt so the status row never wraps.
- CLI security fix (important) — hook placeholders (
{audio}etc.) are now shell-quoted on expansion, so malicious file names can no longer inject commands; do not add your own quotes around placeholders in templates. Also fixed: hook-reported transcript paths are honored, Ctrl-C in menus restores the terminal,hooks run transcribevalidates its product, and timeout rejects invalid values.
- Waveform + playback progress — playing a past recording draws a stereo waveform right in the row (top = them, bottom = you), so you can see who spoke when; click anywhere to seek, ▶⇄❚❚ pause/resume, ↻ replay when done. Waveforms are cached locally, so they're instant after the first play.
- Keyboard shortcuts — ⌘R start / ⌘P pause / ⌘E stop / ⌘N new / ⌘T transcribe / ⌘O Finder / ⌘1 window mode / ⌘L 3-min·full transcript / ⌘K collapse list / ⌘, settings — printed on the buttons, nothing to memorize.
- Three window modes on one key (⌘1): standard (remembers your size) → narrow 320pt → floating mini bar.
- dB-scaled level meters — normal speech lights more than half the bar, silence stays at one segment; no more "I'm talking but it looks dead".
myparrotCLI — for terminal users and AI agents:record / stop / status / transcribe / list / devices / config / hooks,--jsonevent stream; hooks hand the finished transcript to any command (e.g. Claude Code writing the minutes). See below.- Update notice — one background check of GitHub Releases at launch; a new version shows only as a non-blocking banner (can be disabled in Settings; hits
api.github.comonly, sends nothing). - Fixes: transcribing right after stopping no longer fails; two recordings in the same minute with the same title no longer overwrite each other; the document button opens the transcript once one exists.
"Who said what" is the hard part of meeting transcription. MyParrot sidesteps it with physics instead of ML: the other side comes from a system-audio tap, you come from the microphone, and each gets its own channel. Split the file, transcribe each track, interleave by time — perfect speaker attribution, fully offline.
- The other side — Core Audio Process Tap (macOS 14.4+): captures system audio with no driver, no kernel extension, no virtual audio device
- You — AVAudioEngine microphone capture that survives device/route changes mid-recording (auto-heal on configuration change + a liveness watchdog; hot-swap mics without stopping — the file stays continuous)
- Live transcript while recording — three swappable engines: macOS 26 SpeechAnalyzer (long-form streaming, automatic fallback to SFSpeechRecognizer on older systems), or a fully local Whisper engine (whisper.cpp + Metal, large-v3 family) for stronger mixed-language (Mandarin/English) accuracy — recording auto-upgrades to a higher-precision re-transcribe once you stop
- Whisper models download in-app (Settings) or drop a
ggml-*.bininto Application Support yourself — nothing is bundled, nothing calls home unless you ask it to - Transcribe past recordings →
.txt+ timestamped, speaker-prefixed.srt, with automatic simplified→traditional Chinese normalization on Whisper output - Offline echo cancellation (optional, non-destructive — writes
*_aec.m4a, never touches the original) - Auto CAF→m4a conversion, quit-protection while recording, floating mini mode, input-gain with soft limiter
- UI in English / 繁體中文 / 简体中文 / 日本語 / 한국어
- A build stamp in the footer, so you always know exactly which build you're running
- Privacy: fully offline. The only network calls are the optional update check (GitHub Releases) and Whisper model downloads you trigger yourself
- macOS 14.4+ (system-audio Process Tap API) — macOS 26.1+ recommended for live transcription (26.0 has a known process-tap regression)
- Swift 6 toolchain — Xcode Command Line Tools are enough to build, run, and self-test. Full Xcode is only needed for
swift test. - cmake (
brew install cmake) — one-time only, to build the local Whisper engine from source (see Quick start)
Grab the latest signed & notarized build from Releases — download the .zip, unzip, drag MyParrot.app into /Applications. No build tools needed; Gatekeeper won't complain (Developer ID signed + notarized).
Prefer to build from source? See below.
git clone <this-repo> && cd MyParrot
bash scripts/fetch-whisper.sh # one-time: builds the local Whisper engine from source (~5-10 min, needs Xcode + cmake)
swift build # compile check
bash scripts/build-app.sh # assemble, sign, install to ~/Applications
open ~/Applications/MyParrot.appFirst launch asks for Microphone, Speech Recognition, and System Audio Recording permissions (it will not ask for Screen Recording).
- Signing & permission persistence. macOS ties permission grants (TCC) to the signing identity.
build-app.shauto-picks the best available: an Apple Development / Developer ID certificate (has a Team ID → permissions survive rebuilds) → a self-signed cert → ad-hoc (permissions re-asked on every rebuild). If you have any Apple certificate, the script will find and use it. - Keep the
.appout of iCloud-synced folders. iCloud Drive re-applies Finder xattrs that repeatedly break code signatures. This is why the script installs to~/Applications. - Don't use a Bluetooth mic. The moment any app opens a Bluetooth microphone, the whole link drops from A2DP to narrowband HFP — the other side's audio degrades to phone quality too. Wear the headset for listening; speak into the built-in or a wired/USB mic. (MyParrot's auto-selection already avoids Bluetooth mics; picking one manually is allowed but warned.)
- First
swift buildfails right after clone? Runbash scripts/fetch-whisper.shfirst. The Whisper engine binary (~35MB) isn't checked into git — the script builds it from source once (needs Xcode +cmake).
bash scripts/build-cli.sh # build + sign, installs ~/bin/myparrot (Whisper engine borrows the framework from ~/Applications/MyParrot.app)
myparrot # in a terminal: interactive menu (pick mic, record, watch levels)
myparrot record --name "Weekly sync" --max-duration 2h # foreground; Ctrl-C or `myparrot stop` from another terminal
myparrot transcribe "~/Documents/MyParrot/2026-08-20 1400 Weekly sync.m4a"
myparrot list --json | jq . # for agents/scripts: one JSON event per line, zero ANSI when not a TTYHooks — the hooks section of ~/.config/myparrot/config.json hands the transcript to any command after a recording ends (automatically, or on demand via myparrot hooks run). Placeholders {audio} {transcript} {name} {dir}; full metadata arrives as JSON on stdin. Approve once with myparrot hooks approve (trust-on-first-use; re-approve after edits). Since 0.3.1 placeholders are shell-quoted on expansion — do not add your own quotes around placeholders.
A transcribe hook can replace the built-in transcription (bring your own ASR) and falls back to the built-in engine on failure; another template is to drop a "recording ready" card into an existing AI session / todo system that picks it up. Permissions are attributed to your terminal app (it will ask for Microphone / System Audio once).
| Area | Status |
|---|---|
| Long meetings (>30 min) | The two capture clocks drift slowly; sample-accurate sync is planned |
| Bluetooth headset as output while any app holds a BT mic | Phone-quality ceiling — protocol limitation (HFP), not fixable in software |
| Hot-swapping to a Bluetooth mic mid-recording | One audible pop at the switch (OS profile change) |
| Capture gaps shorter than 0.3 s | Not silence-padded (trade-off that eliminates padding clicks); the track may shift ≤0.3 s early in that window |
| Echo cancellation | Offline post-process only, off by default |
Recording calls without consent is illegal in many jurisdictions. You are responsible for informing participants and obtaining consent where required.
Sources/MyParrotCore/ Library: capture, mux, transcription, export
SystemAudioCapture.swift Other side: global process tap (+ true-rate relabeling for BT/HFP)
MicCapture.swift You: AVAudioEngine + configuration-change self-heal
StereoRecorder.swift Two feeds → one stereo file (L=them, R=you), min-paced drain
AudioUtils.swift SampleQueue (ramped padding), 48 kHz mono converter, RMS
RecordingController.swift State machine, device watchdog, conversion, playback
EchoCleanup / EchoCanceller / DelayEstimator Offline AEC (bulk-delay + NLMS)
Transcription/ SpeechAnalyzer (live + file) + SFSpeech fallback,
local Whisper engine (whisper.cpp/Metal) + model manager
SelfTest.swift 15 self-test cases (shared by CLI runner & swift test)
Sources/MyParrot/ SwiftUI app (main window, mini mode, settings, mascot)
Sources/MyParrotSelfTest/ CLI test runner — works with Command Line Tools only
Tests/MyParrotTests/ Swift Testing wrappers (one @Test per self-test case)
scripts/build-app.sh Assemble + sign + install + build stamp
See docs/TESTING.md for the test pyramid and how to verify changes.
Eric Lu ("Uncle Eric") — 30 years in product, now a product consultant, headhunter, and career coach. Several online interviews a week, and inviting a bot into the call always felt wrong — so I built this instead.
- Long-form writing: uncleric.com (zh-TW)
- Find me: Threads @tingyulu | LinkedIn
MIT © 2026 Eric Lu — see LICENSE.




{ "hooks": { "minutes": { // runs once the transcript is ready — let Claude Code write the minutes "command": "claude -p \"Read {transcript} and write a one-page meeting summary to {dir}/{name}.md\"", "cwd": "~/Documents/MyParrot", "auto": true } } }