Voice alerts for Claude Code
Claude Code speaks up the moment it needs your approval, and again when it has been waiting on you, so you always know which window to switch to.
Plays through your device’s voice as a preview. On your Mac you’ll hear the Daniel voice.
Needs jq and terminal-notifier (small helpers). Install them first:
brew install jq terminal-notifier
Then run this one line. It sets up the shared engine and wires Claude Code:
curl -fsSL https://harktheherald.dev/install.sh | bash -s -- claude
Prefer to set it up by hand?
This does the same thing without the one-liner:
mkdir -p ~/.hark
cat > ~/.hark/hark.sh << 'EOF'
#!/usr/bin/env bash
VOICE="Daniel"
RATE="160"
MODE="${1:-Permission}"
PROJECT="${HARK_PROJECT:-}"
if [ -z "$PROJECT" ]; then
INPUT="$(cat 2>/dev/null)"
DIR=""
if command -v jq >/dev/null 2>&1; then
DIR="$(printf '%s' "$INPUT" | jq -r '.cwd // .workspaceRoot // empty' 2>/dev/null)"
fi
[ -z "$DIR" ] && DIR="${CLAUDE_PROJECT_DIR:-${GROK_WORKSPACE_ROOT:-$PWD}}"
PROJECT="$(basename "$DIR")"
fi
[ -z "$PROJECT" ] && PROJECT="a session"
if [ "$MODE" = "Idle" ]; then MSG="is ready for you"; else MSG="requires your attention"; fi
if [ -f "$HOME/.hark/quiet" ] || [ -f "$HOME/.claude/quiet" ]; then
if command -v terminal-notifier >/dev/null 2>&1; then
terminal-notifier -title "Hark the Herald" -message "$PROJECT $MSG, sir" -sender com.apple.Terminal >/dev/null 2>&1
else
osascript -e "display notification \"$PROJECT $MSG, sir\" with title \"Hark the Herald\"" >/dev/null 2>&1
fi
else
[ "$MODE" = "Idle" ] || afplay /System/Library/Sounds/Ping.aiff >/dev/null 2>&1
say -v "$VOICE" -r "$RATE" "$PROJECT $MSG, sir" >/dev/null 2>&1
fi
exit 0
EOF
chmod +x ~/.hark/hark.sh
mkdir -p ~/.claude
[ -f ~/.claude/settings.json ] || echo '{}' > ~/.claude/settings.json
cp ~/.claude/settings.json ~/.claude/settings.json.bak
jq 'del(.hooks.Stop) | .hooks.Notification=[{matcher:"permission_prompt",hooks:[{type:"command",command:"bash $HOME/.hark/hark.sh Permission"}]},{matcher:"idle_prompt",hooks:[{type:"command",command:"bash $HOME/.hark/hark.sh Idle"}]}]' ~/.claude/settings.json > ~/.claude/settings.tmp && mv ~/.claude/settings.tmp ~/.claude/settings.json
echo "Done. Restart your Claude Code sessions."
Backs up any config it touches to a .bak first, and is safe to run twice. Then restart Claude Code.
Every project says its own name, so overlapping sessions are easy to tell apart.
On a call? Switch the voice for silent desktop notifications, then switch it back. Takes effect on the next alert, no restart.
touch ~/.hark/quiet # meeting mode on: silent banners
rm -f ~/.hark/quiet # meeting mode off: voice back
One-word shortcuts
Add meeting and unmeeting to your shell:
cat >> ~/.zshrc << 'EOF'
alias meeting='touch ~/.hark/quiet && echo "meeting mode ON"'
alias unmeeting='rm -f ~/.hark/quiet && echo "meeting mode OFF"'
EOF
source ~/.zshrc
Menu bar toggle (optional)
Prefer clicking to typing? This adds a trumpet to your menu bar. Paste once:
brew install --cask swiftbar
mkdir -p ~/.swiftbar
cat > ~/.swiftbar/hark.5s.sh << 'EOF'
#!/bin/bash
# <xbar.title>Hark the Herald</xbar.title>
# <swiftbar.hideAbout>true</swiftbar.hideAbout>
# <swiftbar.hideRunInTerminal>true</swiftbar.hideRunInTerminal>
# <swiftbar.hideLastUpdated>true</swiftbar.hideLastUpdated>
# <swiftbar.hideDisablePlugin>true</swiftbar.hideDisablePlugin>
# <swiftbar.hideSwiftBar>true</swiftbar.hideSwiftBar>
ON="iVBORw0KGgoAAAANSUhEUgAAABgAAAALCAYAAABlNU3NAAAC90lEQVR4nK2STWhUVxzF//fd+2bevImTmXwaY6LJmDppKiKiIkFjgkoQrC04rkTc2UWhXZVSF5Mg7U4Xgou4FL8wIPgVEIQ4KijCmGBkLO1kMhAnGfOSmTcz7/O+d+8ttHHbRetZnMVZ/M5ZHIDPJHEniYUQWM/+kvrw4uzE7OPTYSEEkj4HfHp6iKBTkyz3+kLCdyxer1TuEss9gRAS/6tApFKSmE6R4eG0f39iqEWq5i/UDPdZ//F7j6lH1czNrzeh/wJ9egikkZFxX4h/sje3B3Z2tiWuyo19W4vL2iG3OJtz1daDGDAl/woTgGAshWAgiyCZBIAkRwhxGAc+nRoiPbvYF0rL5mRDbNuP4U190VqppClkwdhxLuO9unZ0FTjrI0LcwWNjVxBAmo8NJBG0fokyG5bR7nyFIzTJAMbXd07+7XNTo/Fo45ZdSoDEA+H245Hu/kG3arFyfo4ibjt/ZD92Xzy7PRpQpCXf4fsIQqfYp8Xj65BPKhYnVKW80oxlZwAxeb+EnHbBeRwLH4AzbOmlbk0rgF6cx/WajZs3dzLb9BK2Rwd5b/c5dzYXIsafv40uvptpkjaE0m0xJWFadItClP5IROnx1n7fbhr+V4AJOLbN62VtmdoWCwAty5jbrunMLa1YWR8CWK/SIJWqDbFIoF423Xc0/ZaoHU2cuOXSRatu53ndPOmskh2O4zElKBdKSBTXtOpMvWY/0CsV3TAs0+Oy5VAWNhw/N1+wcmpDYzQcC+gqY96vdwtLzy8fOKOqcldmRr5yeHiP6AhqHN36OfG9XmOg6Xa68P5DbW4G3G9O97acv55f2NkI8koVKAYQ345ukyVbk6JtUSUSYri5IxZfrXoRx3Q8ELiPI27FwkGpt6cpfuynI+czV5cVGlr9gRSq8o1ORWr+7sajxczUpUFrbcns6mwfGT15YMGj7orPBGH++qswErKgku0yq6tN2et7Iqe7UFODoYdPpl62rhn0WJPh1+FpVvXVjVJAkXN/AfJVgvknPI0ZAAAAAElFTkSuQmCC"
OFF="iVBORw0KGgoAAAANSUhEUgAAABgAAAALCAYAAABlNU3NAAADoUlEQVR4nI1SXWxTZRh+v+/8tGvpfrqWQWFbp6uDTLJlqLABKxrUGaMEwwgTCTeKYxpCdHE3mIYY4g2amEwxI4peKGYkxC2bigylEZgmbhJcGdlPW9auXdv195zT0/P7mSN64Q3xTd7kfS+e53l/HnSkseHQtFDa+Wc8/jEAzAAAUAjBSULwKaMB0OF/xPBwN9UN3TBeHD1ZLKRc9yT9rf7+H4v4iyY3d7axvndgs+fmBqfzm4qqqnc0QuA9AB3fJ2cMzQeR+3xe+sCBi9qF6NgmTRF1sVC8VC2qexFCBHU1Npq+f8FrDd26+9WCJNXqgJoHY8nsWGa1DwrCFQBIGySEAEIIkFH+k+DzAd4NXvzkKb/qO7rV4fE4hhBmPjr09pj/nK/j1RLAdwbgPxHb4z2MQX/tplDadSmVvj28mvlV1rRR4LhxGgC0++zGVsq/mM8G3C32mvohyrrBncjmduvBxIJSjTsxIPlvAQKArnm9lI3n0WNTUwaQIa+82HY7kjofLsk1GV23D8YSv08lU70gK9M0BqLoP9PnBvqa7K71+y3lrhPlztrKXDqd4lZnt/b0/xI5O9DaohHKg4znBAJJBODXm5u70cbJCBttr5WNmxrixqix9vaBFaT2Xi4U3d+mcl9eT6QCJ463Wl7ytj5jK6/tyPNFyK4sapiU4jN3lg/GFhOFtlbHikLofeiBzhjsWwM2zl5oqayum0i94bGxDX9cXdxVk9aZkZUcubPOFO702p3KagzXmpCFrXKEZ+dSp5ci+R3P7ax6PcGxx+gfvj7alVi4ay+z0n6z1bJJktR6lmE3Wy1Mg6InmyRee9Q1WYI8K4mXk8sR8xYtVFYgXM8tm5zjtfaL50MEyijxp0qhtGcbm3mkvnw1EkpPPw5OPEqLCI18ui8QvxcPAiEKzVBbFEXVaBqHaYyX8zmBEXkxyvNcTpRUUdeQWJRJWRbU+WvrsvNnlpqfPeioyQTSwpm5jFIxruR0f4n/cCmYHAKnk7v+/vb96N2eujd5XgFB1Pzzy8lCYhak7U/XOD6/kgi1ADBJAJkCIN4n7ExU0rC7kjWbKY1yOMofnhE4vMJL6lqWbTzMrH/+IdHaMZdXXFd5DtKaOhF0kQA69nJdVYXCVHeePh6JjkzsEApJwVnpeEoQ5JCkqUkKVFpVDXvSgDEilKZgRdGKjmrTNiJDsCjIGbN5TfiDqRtmT8ncdcSysW2vae3kb/H8JzfyPPwFRYTJdMk1kdYAAAAASUVORK5CYII="
FLAG="$HOME/.hark/quiet"
if [ -f "$FLAG" ] || [ -f "$HOME/.claude/quiet" ]; then
echo "| image=$OFF"
echo "---"
echo "Meeting mode is ON (silent banners)"
echo "Turn the voice back on | bash=/bin/rm param1=-f param2=$FLAG terminal=false refresh=true"
else
echo "| image=$ON"
echo "---"
echo "Voice is ON"
echo "Start meeting (mute the voice) | bash=/usr/bin/touch param1=$FLAG terminal=false refresh=true"
fi
EOF
chmod +x ~/.swiftbar/hark.5s.sh
APP="/Applications/SwiftBar.app"
[ -d "$APP" ] || APP="$(mdfind "kMDItemCFBundleIdentifier == 'com.ameba.SwiftBar'" 2>/dev/null | head -1)"
killall SwiftBar 2>/dev/null
defaults write com.ameba.SwiftBar PluginDirectory "$HOME/.swiftbar"
osascript -e "tell application \"System Events\" to if not (exists login item \"SwiftBar\") then make login item at end with properties {path:\"$APP\", hidden:false}" || echo "Could not add the login item. Add SwiftBar manually in System Settings, General, Login Items."
open -a "$APP"
SwiftBar starts right away and relaunches at every login, so the trumpet survives reboots. macOS will ask once for permission to control System Events — click Allow. If SwiftBar ever asks for a plugins folder, choose ~/.swiftbar. You can remove the auto-start anytime under System Settings → General → Login Items. The toggle syncs with the meeting and unmeeting commands and mutes every agent at once.
The voice and speed live in one place now, in the shared engine. Open it, change the VOICE or RATE line, save:
open -e ~/.hark/hark.sh
List every voice with say -v '?'. Higher RATE is faster; 175 is normal, 160 is calmer. For a richer voice, download Daniel (Enhanced) in System Settings, Accessibility, Spoken Content.
terminal-notifier is installed.rm -f ~/.hark/quiet.echo '{"cwd":"/x/demo"}' | bash ~/.hark/hark.sh Permission should say “demo requires your attention, sir”.cp ~/.claude/settings.json.bak ~/.claude/settings.json.