A lightweight macOS menu bar app for sending native notifications from the command line. Built for Claude Code hooks but works with any CLI workflow.
- Native macOS notifications with sound
- Menu bar icon with notification history
- Click notification to activate any app (by bundle ID)
- Single instance (lock file prevents duplicates)
- Auto-start at login via LaunchAgent
- Zero dependencies, single binary
./build.sh
cp -r .build/release/ClaudeNotify.app /Applications/
codesign --force --deep --sign - /Applications/ClaudeNotify.appAdd to ~/.zshrc:
alias claude-notify='/Applications/ClaudeNotify.app/Contents/MacOS/ClaudeNotify'cp com.claude.notify.plist ~/Library/LaunchAgents/
launchctl load ~/Library/LaunchAgents/com.claude.notify.plistOn first notification, macOS will prompt for notification permissions. Allow them in System Settings → Notifications → ClaudeNotify.
# Send a notification
claude-notify -m "Build complete!"
# With custom title
claude-notify -m "Done" -t "My Task"
# Open an app when clicked
claude-notify -m "Ready" -a com.apple.Terminal
# Silent notification
claude-notify -m "Background task done" --no-sound
# Run as daemon only (menu bar)
claude-notify --daemon| Flag | Description |
|---|---|
-m, --message <text> |
Notification message (required) |
-t, --title <text> |
Notification title (default: "Claude Code") |
-a, --activate <bundle-id> |
App to activate on click |
--no-sound |
Disable notification sound |
-d, --daemon |
Run as menu bar daemon |
Add to your Claude Code hooks (~/.claude/settings.json):
{
"hooks": {
"Notification": [
{
"hooks": [
{
"type": "command",
"command": "claude-notify -m 'Claude finished' -a com.todesktop.230313mzl4w4u92"
}
]
}
]
}
}Create ~/.claude/hooks/notify.sh:
#!/bin/bash
# Detect which app to activate based on terminal
if [[ -n "${CURSOR_TRACE_ID:-}" ]]; then
BUNDLE_ID="com.todesktop.230313mzl4w4u92" # Cursor
elif [[ "${TERM_PROGRAM:-}" == "ghostty" ]]; then
BUNDLE_ID="com.mitchellh.ghostty"
elif [[ "${TERM_PROGRAM:-}" == "vscode" ]]; then
BUNDLE_ID="com.microsoft.VSCode"
elif [[ "${TERM_PROGRAM:-}" == "Apple_Terminal" ]]; then
BUNDLE_ID="com.apple.Terminal"
elif [[ "${TERM_PROGRAM:-}" == "iTerm.app" ]]; then
BUNDLE_ID="com.googlecode.iterm2"
else
BUNDLE_ID="${__CFBundleIdentifier:-}"
fi
claude-notify -m "Claude finished" -a "$BUNDLE_ID" &Make it executable and reference in settings:
chmod +x ~/.claude/hooks/notify.sh{
"hooks": {
"Notification": [
{
"hooks": [
{
"type": "command",
"command": "~/.claude/hooks/notify.sh"
}
]
}
]
}
}This auto-detects which terminal you're using and activates the correct app when clicking the notification.
- First invocation starts a background daemon (menu bar app)
- Lock file (
/tmp/claude-notify.lock) ensures single instance - Subsequent calls send messages to the daemon via
DistributedNotificationCenter - Daemon displays native notifications and tracks history in menu bar
- Sound played via
afplayfor reliability
# Stop daemon
launchctl unload ~/Library/LaunchAgents/com.claude.notify.plist
# Start daemon
launchctl load ~/Library/LaunchAgents/com.claude.notify.plist
# Check status
launchctl list | grep claude| App | Bundle ID |
|---|---|
| Terminal | com.apple.Terminal |
| iTerm2 | com.googlecode.iterm2 |
| VS Code | com.microsoft.VSCode |
| Cursor | com.todesktop.230313mzl4w4u92 |
| Warp | dev.warp.Warp-Stable |
Find any app's bundle ID:
osascript -e 'id of app "AppName"'MIT