Voice alerts for Codex
Codex announces when a turn finishes, so a long run in a tab you stopped watching tells you it is ready for your next move.
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 Codex:
curl -fsSL https://harktheherald.dev/install.sh | bash -s -- codex
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
cat > ~/.hark/codex.sh << 'EOF'
#!/usr/bin/env bash
DIR=""
command -v jq >/dev/null 2>&1 && DIR="$(printf '%s' "${1:-}" | jq -r '.cwd // empty' 2>/dev/null)"
[ -z "$DIR" ] && DIR="$PWD"
HARK_PROJECT="$(basename "$DIR")" bash "$HOME/.hark/hark.sh" Idle
EOF
chmod +x ~/.hark/codex.sh
mkdir -p ~/.codex
touch ~/.codex/config.toml
cp ~/.codex/config.toml ~/.codex/config.toml.bak 2>/dev/null || true
grep -q '^[[:space:]]*notify' ~/.codex/config.toml || { { echo "notify = [\"bash\", \"$HOME/.hark/codex.sh\"]"; cat ~/.codex/config.toml; } > ~/.codex/config.toml.tmp && mv ~/.codex/config.toml.tmp ~/.codex/config.toml; }
echo "Done. Restart your Codex sessions."
Backs up any config it touches to a .bak first, and is safe to run twice. Then restart Codex.
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 ~/.codex/config.toml.bak ~/.codex/config.toml.