An AI that yaps at you. Voice notifications for Claude Code, Claude desktop, and Cowork on macOS.
Project description
YappAI
An AI that yaps at you.
A unified voice notification system for Anthropic's tools on macOS. Hear when Claude Code finishes a task, when the Claude desktop app needs your attention, or when Cowork wraps up — in any voice personality you like.
$ # Claude Code finishes a long task
🔊 "All sorted, sir."
$ # Claude Code pauses for permission
🔊 "Sir, your input is required."
$ # Switch personality, no code changes
$ yappai config set personality=happy
$ # next event:
🔊 "Yay, task complete!"
What it watches
| Source | Mechanism |
|---|---|
| Claude Code CLI | Official hook events (UserPromptSubmit, Stop, Notification) |
| Claude desktop | macOS Notification Center DB polling (Full Disk Access) |
| Cowork | macOS Notification Center DB polling (Full Disk Access) |
macOS only. Python 3.10+. macOS 14 (Sonoma) or newer.
Install
Prerequisites
- macOS 14 (Sonoma) or newer
- Python 3.10+ (
python3 --version) ~/.local/binon yourPATH(the installer warns if it isn't — see step 3 below)- Optional:
jqfor a cleaner Claude Code settings merge (brew install jq). If absent, the installer falls back to a Python merge that does the same thing.
1. Clone and run the installer
git clone https://github.com/Thu-Min/yappai.git
cd yappai
./install.sh
What the installer does:
- Copies the package to
~/.local/share/yappai/and creates a venv there - Installs a
yappaishim at~/.local/bin/yappai - Writes default config to
~/.config/yappai/config.json - Detects which Anthropic apps are installed (Claude Code CLI, Claude desktop, Cowork) and enables only those sources
- Merges three hook entries (
UserPromptSubmit,Stop,Notification) into~/.claude/settings.json— your existing settings are backed up assettings.json.bak.<ts> - Installs and loads a launchd agent (
com.yappai.watcher) that polls the macOS Notification Center DB for Claude desktop / Cowork events - Probes Full Disk Access and prints instructions if it's missing
- Plays a final test yap so you know it's wired up
2. Grant Full Disk Access (one-time)
The desktop/Cowork watcher reads ~/Library/Group Containers/group.com.apple.usernoted/db2/db, which macOS guards behind FDA. If the installer's probe failed:
- Open System Settings → Privacy & Security → Full Disk Access
- Click +, press ⌘⇧G, and paste:
~/.local/share/yappai/.venv/bin - Add
python3(orpython3.x) from that folder - Reload the watcher:
launchctl kickstart -k gui/$UID/com.yappai.watcher
Skip this step if you only want Claude Code yaps — those go through hooks and don't need FDA.
3. Make sure ~/.local/bin is on PATH
If the installer warned that it isn't:
# zsh (default on macOS)
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc
source ~/.zshrc
4. Verify
yappai test --kind stop --summary "the integration is wired up"
yappai test --kind needs_input
yappai test --personality happy --kind stop
yappai watcher check # should print {"ok": true, ...}
yappai status # full state dump
If you don't hear anything: check that you're not in a "skip when frontmost" terminal (see unfocused_skip_apps in Configuration) and that you're not muted (yappai status).
Day-to-day
yappai mute # silence yappai
yappai unmute
yappai status # config + watcher state
yappai list-personalities
yappai list-voices --locale en # browse edge-tts voices
yappai say "Hello sir" # one-off
yappai say "Hi" --personality happy
yappai config show
yappai config set personality=jarvis
yappai config set summary_max_chars=80
yappai config set only_when_unfocused=false
yappai watcher start
yappai watcher stop
yappai watcher restart
yappai watcher run --once # run one poll iteration in foreground
yappai watcher check # probe Notification DB access
Personality system
Each .md file in personalities/ is a separate persona. Drop in a new one — no code changes — and switch:
yappai config set personality=happy
See personalities/README.md for the file format. Quick recipe:
# Pirate
## voice
edge-tts: en-GB-RyanNeural
## completions
- Arr! Done, captain. {summary}
- Yo ho, that be finished.
## needs_input
- Captain on deck! Yer needed.
Missing sections fall back to jarvis.md.
Configuration
~/.config/yappai/config.json:
{
"personality": "jarvis",
"default_engine": "edge-tts",
"elevenlabs_api_key": null,
"enabled_sources": ["claude_code", "claude_desktop", "cowork"],
"muted": false,
"summary_max_chars": 120,
"dedupe_window_seconds": 3.0,
"only_when_unfocused": true,
"claude_desktop_bundle_id": "com.anthropic.claudefordesktop",
"cowork_bundle_id": null,
"unfocused_skip_apps": ["iTerm2", "Terminal", "Warp", "Ghostty", "Claude", "Cowork"]
}
only_when_unfocused: true means yappai stays quiet when one of these apps is frontmost — no point yapping when you're already looking at the terminal.
Full Disk Access
The watcher reads ~/Library/Group Containers/group.com.apple.usernoted/db2/db, which macOS guards behind FDA. The installer probes this and prints instructions if it fails. Manual fix:
- System Settings → Privacy & Security → Full Disk Access
- Add the binary that runs the watcher — for the launchd agent that's
~/.local/share/yappai/.venv/bin/python3(or the symlinked target). For ad-hoc terminal testing, add your terminal app. - Reload:
launchctl kickstart -k gui/$UID/com.yappai.watcher
If FDA is too painful, see CLAUDE.md for the Plan B (PyObjC UNUserNotificationCenter listener).
Uninstall
./uninstall.sh # keeps your config
./uninstall.sh --purge # nukes config too
The Claude Code settings file gets restored from the most recent .bak.<ts> if present, otherwise yappai entries are stripped surgically. Other users' hooks are preserved.
How it sounds
Default Jarvis voice is en-GB-RyanNeural (edge-tts, free). That's what you get out of the box, no setup, no account.
Optional: ElevenLabs (bring your own key)
If you want a more theatrical voice — proper Jarvis tones, custom cloned voices, etc. — yappai can route through ElevenLabs instead. You'll need your own API key (yappai does not ship one, and your key never leaves your machine):
- Sign up at elevenlabs.io and grab an API key from your profile settings.
- Pick a voice ID from your ElevenLabs voice library.
- Configure yappai:
yappai config set elevenlabs_api_key=sk_your_key_here
# then in any personality .md file, set:
# elevenlabs: <voice_id>
# instead of the edge-tts line.
Or, if you'd rather not touch the CLI, paste this prompt into Claude Code from the yappai folder and let it do the wiring:
I have an ElevenLabs API key (
sk_...) and the voice ID<paste_voice_id>. Please add the key to my~/.config/yappai/config.json, then updatepersonalities/jarvis.mdto use that ElevenLabs voice instead of edge-tts. Don't print the key back to me.
The integration code lives in yappai/tts.py. Free tier is fine for casual use; check ElevenLabs' character limits for your plan.
Acknowledgements
Heavily inspired by emaspa/claudevoice-macos — same family of problems, different multi-source architecture.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file yappai_tts-0.1.0.tar.gz.
File metadata
- Download URL: yappai_tts-0.1.0.tar.gz
- Upload date:
- Size: 24.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
60bcd44311763315f2e5dff296219b7a3ec565f880b19fd4cd51fdaf98757300
|
|
| MD5 |
9cb4a7e2d9a66838f9ceedfa02b48331
|
|
| BLAKE2b-256 |
edde9c8ee5aa64a433e556ff0413803f090613aabce182316b92646d8e6537ad
|
File details
Details for the file yappai_tts-0.1.0-py3-none-any.whl.
File metadata
- Download URL: yappai_tts-0.1.0-py3-none-any.whl
- Upload date:
- Size: 25.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3b960df963bb458fa9661e33464f457ae1591b1f4652b29c451672f974e024d7
|
|
| MD5 |
8349b5b152ab7379f005460387a720dc
|
|
| BLAKE2b-256 |
9702269d42b6183e0df440513cf3167427f65ba82fa275a7067aa42510a8857b
|