Hark the Herald

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.

Set up Claude Code

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:

Terminal - zsh
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:

Terminal - zsh
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.

What you’ll hear

Every project says its own name, so overlapping sessions are easy to tell apart.

Meeting mode

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)

voice onmutedClick the trumpet to flip. Gold means the voice is on; dimmed with a slash means muted.

Prefer clicking to typing? This adds a trumpet to your menu bar. Paste once:

Terminal - zsh
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.

Make it yours

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.

If something’s off

Run several agents? Set up all four at once →