Files
wild-cloud/.claude/tools/notify.sh

219 lines
8.9 KiB
Bash
Executable File

#!/usr/bin/env bash
# Claude Code notification hook script
# Reads JSON from stdin and sends desktop notifications
#
# Expected JSON input format:
# {
# "session_id": "abc123",
# "transcript_path": "/path/to/transcript.jsonl",
# "cwd": "/path/to/project",
# "hook_event_name": "Notification",
# "message": "Task completed successfully"
# }
set -euo pipefail
# Check for debug flag
DEBUG=false
LOG_FILE="/tmp/claude-code-notify-$(date +%Y%m%d-%H%M%S).log"
if [[ "${1:-}" == "--debug" ]]; then
DEBUG=true
shift
fi
# Debug logging function
debug_log() {
if [[ "$DEBUG" == "true" ]]; then
local msg="[DEBUG] $(date '+%Y-%m-%d %H:%M:%S') - $*"
echo "$msg" >&2
echo "$msg" >> "$LOG_FILE"
fi
}
debug_log "Script started with args: $*"
debug_log "Working directory: $(pwd)"
debug_log "Platform: $(uname -s)"
# Read JSON from stdin
debug_log "Reading JSON from stdin..."
JSON_INPUT=$(cat)
debug_log "JSON input received: $JSON_INPUT"
# Parse JSON fields (using simple grep/sed for portability)
debug_log "Parsing JSON fields..."
MESSAGE=$(echo "$JSON_INPUT" | grep -o '"message"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*"message"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/')
CWD=$(echo "$JSON_INPUT" | grep -o '"cwd"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*"cwd"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/')
SESSION_ID=$(echo "$JSON_INPUT" | grep -o '"session_id"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*"session_id"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/')
debug_log "Parsed MESSAGE: $MESSAGE"
debug_log "Parsed CWD: $CWD"
debug_log "Parsed SESSION_ID: $SESSION_ID"
# Get project name from cwd
PROJECT=""
debug_log "Determining project name..."
if [[ -n "$CWD" ]]; then
debug_log "CWD is not empty, checking if it's a git repo..."
# Check if it's a git repo
if [[ -d "$CWD/.git" ]]; then
debug_log "Found .git directory, attempting to get git remote..."
cd "$CWD"
PROJECT=$(basename -s .git "$(git config --get remote.origin.url 2>/dev/null || true)" 2>/dev/null || true)
[[ -z "$PROJECT" ]] && PROJECT=$(basename "$CWD")
debug_log "Git-based project name: $PROJECT"
else
debug_log "Not a git repo, using directory name"
PROJECT=$(basename "$CWD")
debug_log "Directory-based project name: $PROJECT"
fi
else
debug_log "CWD is empty, PROJECT will remain empty"
fi
# Set app name
APP_NAME="Claude Code"
# Fallback if message is empty
[[ -z "$MESSAGE" ]] && MESSAGE="Notification"
# Add session info to help identify which terminal/tab
SESSION_SHORT=""
if [[ -n "$SESSION_ID" ]]; then
# Get last 6 chars of session ID for display
SESSION_SHORT="${SESSION_ID: -6}"
debug_log "Session short ID: $SESSION_SHORT"
fi
debug_log "Final values:"
debug_log " APP_NAME: $APP_NAME"
debug_log " PROJECT: $PROJECT"
debug_log " MESSAGE: $MESSAGE"
debug_log " SESSION_SHORT: $SESSION_SHORT"
# Platform-specific notification
PLATFORM="$(uname -s)"
debug_log "Detected platform: $PLATFORM"
case "$PLATFORM" in
Darwin*) # macOS
if [[ -n "$PROJECT" ]]; then
osascript -e "display notification \"$MESSAGE\" with title \"$APP_NAME\" subtitle \"$PROJECT${SESSION_SHORT:+ ($SESSION_SHORT)}\""
else
osascript -e "display notification \"$MESSAGE\" with title \"$APP_NAME\""
fi
;;
Linux*)
debug_log "Linux platform detected, checking if WSL..."
# Check if WSL
if grep -qi microsoft /proc/version 2>/dev/null; then
debug_log "WSL detected, will use Windows toast notifications"
# WSL - use Windows toast notifications
if [[ -n "$PROJECT" ]]; then
debug_log "Sending WSL notification with project: $PROJECT"
powershell.exe -Command "
[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null
[Windows.UI.Notifications.ToastNotification, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null
[Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime] | Out-Null
\$APP_ID = '$APP_NAME'
\$template = @\"
<toast><visual><binding template='ToastText02'>
<text id='1'>$PROJECT${SESSION_SHORT:+ ($SESSION_SHORT)}</text>
<text id='2'>$MESSAGE</text>
</binding></visual></toast>
\"@
\$xml = New-Object Windows.Data.Xml.Dom.XmlDocument
\$xml.LoadXml(\$template)
\$toast = New-Object Windows.UI.Notifications.ToastNotification \$xml
[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier(\$APP_ID).Show(\$toast)
" 2>/dev/null || echo "[$PROJECT${SESSION_SHORT:+ ($SESSION_SHORT)}] $MESSAGE"
else
debug_log "Sending WSL notification without project (message only)"
powershell.exe -Command "
[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null
[Windows.UI.Notifications.ToastNotification, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null
[Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime] | Out-Null
\$APP_ID = '$APP_NAME'
\$template = @\"
<toast><visual><binding template='ToastText01'>
<text id='1'>$MESSAGE</text>
</binding></visual></toast>
\"@
\$xml = New-Object Windows.Data.Xml.Dom.XmlDocument
\$xml.LoadXml(\$template)
\$toast = New-Object Windows.UI.Notifications.ToastNotification \$xml
[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier(\$APP_ID).Show(\$toast)
" 2>/dev/null || echo "$MESSAGE"
fi
else
# Native Linux - use notify-send
if command -v notify-send >/dev/null 2>&1; then
if [[ -n "$PROJECT" ]]; then
notify-send "<b>$PROJECT${SESSION_SHORT:+ ($SESSION_SHORT)}</b>" "$MESSAGE"
else
notify-send "Claude Code" "$MESSAGE"
fi
else
if [[ -n "$PROJECT" ]]; then
echo "[$PROJECT${SESSION_SHORT:+ ($SESSION_SHORT)}] $MESSAGE"
else
echo "$MESSAGE"
fi
fi
fi
;;
CYGWIN*|MINGW*|MSYS*) # Windows
if [[ -n "$PROJECT" ]]; then
powershell.exe -Command "
[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null
[Windows.UI.Notifications.ToastNotification, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null
[Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime] | Out-Null
\$APP_ID = '$APP_NAME'
\$template = @\"
<toast><visual><binding template='ToastText02'>
<text id='1'>$PROJECT${SESSION_SHORT:+ ($SESSION_SHORT)}</text>
<text id='2'>$MESSAGE</text>
</binding></visual></toast>
\"@
\$xml = New-Object Windows.Data.Xml.Dom.XmlDocument
\$xml.LoadXml(\$template)
\$toast = New-Object Windows.UI.Notifications.ToastNotification \$xml
[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier(\$APP_ID).Show(\$toast)
" 2>/dev/null || echo "[$PROJECT${SESSION_SHORT:+ ($SESSION_SHORT)}] $MESSAGE"
else
powershell.exe -Command "
[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null
[Windows.UI.Notifications.ToastNotification, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null
[Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime] | Out-Null
\$APP_ID = '$APP_NAME'
\$template = @\"
<toast><visual><binding template='ToastText01'>
<text id='1'>$MESSAGE</text>
</binding></visual></toast>
\"@
\$xml = New-Object Windows.Data.Xml.Dom.XmlDocument
\$xml.LoadXml(\$template)
\$toast = New-Object Windows.UI.Notifications.ToastNotification \$xml
[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier(\$APP_ID).Show(\$toast)
" 2>/dev/null || echo "$MESSAGE"
fi
;;
*) # Unknown OS
if [[ -n "$PROJECT" ]]; then
echo "[$PROJECT${SESSION_SHORT:+ ($SESSION_SHORT)}] $MESSAGE"
else
echo "$MESSAGE"
fi
;;
esac
debug_log "Script completed"
if [[ "$DEBUG" == "true" ]]; then
echo "[DEBUG] Log file saved to: $LOG_FILE" >&2
fi