Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions plugins/warp/scripts/on-notification.sh
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,6 @@ BODY=$(build_payload "$INPUT" "$NOTIF_TYPE" \
--arg summary "$MSG")

"$SCRIPT_DIR/warp-notify.sh" "warp://cli-agent" "$BODY"

# Windows fallback: native toast notification (OSC 777 fails on Windows)
[ -f "$SCRIPT_DIR/win-notify.sh" ] && "$SCRIPT_DIR/win-notify.sh" "$NOTIF_TYPE" "$INPUT"
3 changes: 3 additions & 0 deletions plugins/warp/scripts/on-permission-request.sh
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,6 @@ BODY=$(build_payload "$INPUT" "permission_request" \
--argjson tool_input "$TOOL_INPUT")

"$SCRIPT_DIR/warp-notify.sh" "warp://cli-agent" "$BODY"

# Windows fallback: native toast notification (OSC 777 fails on Windows)
[ -f "$SCRIPT_DIR/win-notify.sh" ] && "$SCRIPT_DIR/win-notify.sh" "permission_request" "$INPUT"
3 changes: 3 additions & 0 deletions plugins/warp/scripts/on-stop.sh
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,6 @@ BODY=$(build_payload "$INPUT" "stop" \
--arg transcript_path "$TRANSCRIPT_PATH")

"$SCRIPT_DIR/warp-notify.sh" "warp://cli-agent" "$BODY"

# Windows fallback: native toast notification (OSC 777 fails on Windows)
[ -f "$SCRIPT_DIR/win-notify.sh" ] && "$SCRIPT_DIR/win-notify.sh" "stop" "$INPUT"
10 changes: 8 additions & 2 deletions plugins/warp/scripts/warp-notify.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,11 @@ TITLE="${1:-Notification}"
BODY="${2:-}"

# OSC 777 format: \033]777;notify;<title>;<body>\007
# Write directly to /dev/tty to ensure it reaches the terminal
printf '\033]777;notify;%s;%s\007' "$TITLE" "$BODY" > /dev/tty 2>/dev/null || true
# Try /dev/tty first (macOS/Linux). On Windows, /dev/tty fails inside
# Claude Code's hook runner because stdio is captured. Fall back to stderr.
if printf '\033]777;notify;%s;%s\007' "$TITLE" "$BODY" > /dev/tty 2>/dev/null; then
exit 0
fi

# Last resort: stderr (may not reach terminal in sandboxed hook contexts)
printf '\033]777;notify;%s;%s\007' "$TITLE" "$BODY" >&2 2>/dev/null || true
97 changes: 97 additions & 0 deletions plugins/warp/scripts/win-notify.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#!/bin/bash
# Windows-only notification sender with deduplication
# Usage: win-notify.sh <event_type> <json_input>
#
# Only one notification per 8 seconds to prevent duplicates from
# multiple hooks firing on the same Claude Code event.

# Only run on Windows
[ -z "$WINDIR" ] && exit 0
command -v powershell &>/dev/null || exit 0

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

EVENT_TYPE="${1:-unknown}"
INPUT="${2:-{}}"

# === Deduplication ===
LOCK_DIR="/tmp/warp-notify-lock"
if mkdir "$LOCK_DIR" 2>/dev/null; then
# We got the lock — we're the first hook to fire
# Clean up lock after 8 seconds (background)
(sleep 8 && rmdir "$LOCK_DIR" 2>/dev/null) &
else
# Another hook already fired recently — skip
exit 0
fi

# === Extract context ===
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // empty' 2>/dev/null)
CWD=$(echo "$INPUT" | jq -r '.cwd // empty' 2>/dev/null)
PROJECT=""
if [ -n "$CWD" ]; then
PROJECT=$(basename "$CWD")
fi

# === Build notification title and body based on event type ===
case "$EVENT_TYPE" in
stop)
NOTIF_TITLE="✅ Task Completed"
# Try to get the response summary
TRANSCRIPT_PATH=$(echo "$INPUT" | jq -r '.transcript_path // empty' 2>/dev/null)
RESPONSE=""
QUERY=""
if [ -n "$TRANSCRIPT_PATH" ] && [ -f "$TRANSCRIPT_PATH" ]; then
RESPONSE=$(jq -rs '
[.[] | select(.type == "assistant" and .message.content)] | last |
[.message.content[] | select(.type == "text") | .text] | join(" ")
' "$TRANSCRIPT_PATH" 2>/dev/null)
QUERY=$(jq -rs '
[
.[] | select(.type == "user") |
if .message.content | type == "string" then .
elif [.message.content[] | select(.type == "text")] | length > 0 then .
else empty end
] | last |
if .message.content | type == "array"
then [.message.content[] | select(.type == "text") | .text] | join(" ")
else .message.content // empty end
' "$TRANSCRIPT_PATH" 2>/dev/null)
fi
if [ -n "$RESPONSE" ]; then
NOTIF_BODY="${RESPONSE:0:200}"
elif [ -n "$QUERY" ]; then
NOTIF_BODY="Done: ${QUERY:0:200}"
else
NOTIF_BODY="Claude finished the task"
fi
;;
idle_prompt)
NOTIF_TITLE="⏳ Input Needed"
MSG=$(echo "$INPUT" | jq -r '.message // empty' 2>/dev/null)
NOTIF_BODY="${MSG:-Claude is waiting for your input}"
;;
permission_request)
NOTIF_TITLE="🔐 Permission Required"
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // "a tool"' 2>/dev/null)
NOTIF_BODY="Claude wants to run: $TOOL_NAME"
;;
session_start)
# Don't notify on session start — not useful
rmdir "$LOCK_DIR" 2>/dev/null
exit 0
;;
*)
NOTIF_TITLE="Claude Code"
NOTIF_BODY="Needs your attention"
;;
esac

# === Add project context ===
if [ -n "$PROJECT" ]; then
NOTIF_TITLE="$NOTIF_TITLE — $PROJECT"
fi

# === Fire Windows notification ===
powershell -ExecutionPolicy Bypass -NoProfile -File "$SCRIPT_DIR/win-toast.ps1" \
-Title "$NOTIF_TITLE" -Body "$NOTIF_BODY" &>/dev/null &
47 changes: 47 additions & 0 deletions plugins/warp/scripts/win-toast.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Windows native toast notification branded as Warp
# Usage: powershell -ExecutionPolicy Bypass -File win-toast.ps1 -Title "title" -Body "body"
param(
[string]$Title = "Claude Code",
[string]$Body = "Task complete"
)

# --- Register Warp as a notification source (one-time, no admin needed) ---
$appId = "dev.warp.Warp"
$regPath = "HKCU:\SOFTWARE\Classes\AppUserModelId\$appId"
$iconPath = "$env:LOCALAPPDATA\Programs\Warp\icon.ico"

if (-not (Test-Path $regPath)) {
New-Item -Path $regPath -Force | Out-Null
}
New-ItemProperty -Path $regPath -Name "DisplayName" -Value "Warp" -PropertyType String -Force | Out-Null
if (Test-Path $iconPath) {
New-ItemProperty -Path $regPath -Name "IconUri" -Value $iconPath -PropertyType ExpandString -Force | Out-Null
}

# --- Load Windows Runtime types ---
[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null
[Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom, ContentType = WindowsRuntime] | Out-Null

# --- Build toast XML ---
$logoAttr = ""
if (Test-Path $iconPath) {
$logoAttr = "<image placement=`"appLogoOverride`" src=`"$iconPath`" hint-crop=`"circle`"/>"
}

$toastXml = @"
<toast>
<visual>
<binding template="ToastGeneric">
$logoAttr
<text>$Title</text>
<text>$Body</text>
</binding>
</visual>
</toast>
"@

# --- Show notification ---
$xml = New-Object Windows.Data.Xml.Dom.XmlDocument
$xml.LoadXml($toastXml)
$toast = [Windows.UI.Notifications.ToastNotification]::new($xml)
[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier($appId).Show($toast)
Loading