@codyswann/lisa 2.124.1 → 2.124.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/dist/agy/mcp-installer.d.ts +22 -5
  2. package/dist/agy/mcp-installer.d.ts.map +1 -1
  3. package/dist/agy/mcp-installer.js +52 -12
  4. package/dist/agy/mcp-installer.js.map +1 -1
  5. package/dist/codex/hooks-installer.d.ts.map +1 -1
  6. package/dist/codex/hooks-installer.js +0 -7
  7. package/dist/codex/hooks-installer.js.map +1 -1
  8. package/dist/core/lisa.d.ts +19 -11
  9. package/dist/core/lisa.d.ts.map +1 -1
  10. package/dist/core/lisa.js +44 -12
  11. package/dist/core/lisa.js.map +1 -1
  12. package/package.json +1 -1
  13. package/plugins/lisa/.claude-plugin/plugin.json +1 -10
  14. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  15. package/plugins/lisa/hooks/block-no-verify.agy.sh +45 -0
  16. package/plugins/lisa/hooks/hooks.json +0 -11
  17. package/plugins/lisa-agy/hooks/block-no-verify.agy.sh +45 -0
  18. package/plugins/lisa-agy/hooks.json +15 -0
  19. package/plugins/lisa-agy/plugin.json +1 -1
  20. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  21. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  22. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  23. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  24. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  25. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -12
  26. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -12
  27. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  28. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  29. package/plugins/lisa-expo-agy/plugin.json +1 -1
  30. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  31. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  32. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  33. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  34. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  35. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  36. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  37. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  38. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  39. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  40. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  41. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  42. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  43. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  44. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  45. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  46. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  47. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  48. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  49. package/plugins/lisa-rails-agy/plugin.json +1 -1
  50. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  51. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  52. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  53. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  54. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  55. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  56. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  57. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  58. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  59. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  60. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  61. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  62. package/plugins/src/base/.claude-plugin/plugin.json +0 -1
  63. package/plugins/src/base/hooks/block-no-verify.agy.sh +45 -0
  64. package/scripts/generate-agy-plugin-artifacts.mjs +158 -19
  65. package/scripts/generate-copilot-plugin-artifacts.mjs +1 -1
  66. package/scripts/lib/per-agent-hook-filter.mjs +29 -18
  67. package/dist/codex/scripts/notify-ntfy.sh +0 -18
  68. package/plugins/lisa/hooks/notify-ntfy.sh +0 -183
  69. package/plugins/lisa-copilot/hooks/notify-ntfy.sh +0 -183
  70. package/plugins/lisa-cursor/hooks/notify-ntfy.sh +0 -183
  71. package/plugins/lisa-expo-agy/.mcp.json +0 -8
  72. package/plugins/src/base/hooks/notify-ntfy.sh +0 -183
@@ -1,183 +0,0 @@
1
- #!/bin/bash
2
- # This file is managed by Lisa.
3
- # Do not edit directly — changes will be overwritten on the next `lisa` run.
4
- # =============================================================================
5
- # ntfy.sh Notification Hook for Claude Code
6
- # =============================================================================
7
- # Sends desktop and mobile notifications via ntfy.sh when Claude needs
8
- # attention or finishes a task.
9
- #
10
- # Setup:
11
- # 1. Install ntfy app on mobile (iOS App Store / Android Play Store)
12
- # 2. Subscribe to your unique topic in the app
13
- # 3. Set NTFY_TOPIC environment variable (e.g., in ~/.bashrc or ~/.zshrc):
14
- # export NTFY_TOPIC="my-claude-alerts-xyz123"
15
- #
16
- # @see https://ntfy.sh
17
- # =============================================================================
18
-
19
- # Read JSON input from stdin
20
- INPUT=$(cat)
21
-
22
- # Extract hook event name
23
- HOOK_EVENT=$(echo "$INPUT" | grep -o '"hook_event_name"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"//' | sed 's/"$//')
24
-
25
- # Extract notification type (for Notification hooks)
26
- NOTIFICATION_TYPE=$(echo "$INPUT" | grep -o '"notification_type"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"//' | sed 's/"$//')
27
-
28
- # Extract message if available
29
- MESSAGE=$(echo "$INPUT" | grep -o '"message"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"//' | sed 's/"$//')
30
-
31
- # Extract session ID (first 8 chars for brevity)
32
- FULL_SESSION_ID=$(echo "$INPUT" | grep -o '"session_id"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"//' | sed 's/"$//')
33
- SESSION_ID="${FULL_SESSION_ID:0:8}"
34
-
35
- # Extract transcript path for task summary
36
- TRANSCRIPT_PATH=$(echo "$INPUT" | grep -o '"transcript_path"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"//' | sed 's/"$//')
37
-
38
- # Determine source (Web vs Local)
39
- if [ "$CLAUDE_CODE_REMOTE" = "true" ]; then
40
- SOURCE="Web"
41
- else
42
- SOURCE="Local"
43
- fi
44
-
45
- # Get project name from current directory
46
- PROJECT_NAME=$(basename "$CLAUDE_PROJECT_DIR" 2>/dev/null || basename "$(pwd)")
47
-
48
- # Load NTFY_TOPIC from local config if not already set
49
- if [ -z "$NTFY_TOPIC" ]; then
50
- # Check for project-local config (gitignored)
51
- if [ -f "$CLAUDE_PROJECT_DIR/.claude/env.local" ]; then
52
- # shellcheck source=/dev/null
53
- source "$CLAUDE_PROJECT_DIR/.claude/env.local"
54
- fi
55
- # Check for user-global config
56
- if [ -z "$NTFY_TOPIC" ] && [ -f "$HOME/.claude/env.local" ]; then
57
- # shellcheck source=/dev/null
58
- source "$HOME/.claude/env.local"
59
- fi
60
- fi
61
-
62
- # Exit silently if still not configured
63
- if [ -z "$NTFY_TOPIC" ]; then
64
- exit 0
65
- fi
66
-
67
- # Extract task summary from transcript (last assistant message, truncated)
68
- TASK_SUMMARY=""
69
- if [ -n "$TRANSCRIPT_PATH" ] && [ -f "$TRANSCRIPT_PATH" ]; then
70
- # Get the last assistant message from the JSONL transcript
71
- # The transcript contains lines with "type":"assistant" and "message" content
72
- # Use awk for cross-platform compatibility (tac is not available on macOS)
73
- LAST_ASSISTANT=$(awk '/"type"[[:space:]]*:[[:space:]]*"assistant"/{line=$0} END{if(line) print line}' "$TRANSCRIPT_PATH" 2>/dev/null)
74
- if [ -n "$LAST_ASSISTANT" ]; then
75
- # Extract the message content - look for text content in the message
76
- # Format: {"message":{"content":[{"type":"text","text":"..."}]}}
77
- # Use jq for robust JSON parsing when available, fallback to grep/sed
78
- if command -v jq >/dev/null 2>&1; then
79
- RAW_SUMMARY=$(echo "$LAST_ASSISTANT" | jq -r '.message.content[] | select(.type == "text") | .text' 2>/dev/null | head -1)
80
- else
81
- # Fallback: simple regex extraction (may fail on escaped quotes)
82
- RAW_SUMMARY=$(echo "$LAST_ASSISTANT" | grep -o '"text"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*: *"//' | sed 's/"$//')
83
- fi
84
- if [ -n "$RAW_SUMMARY" ]; then
85
- # Truncate to 100 chars and clean up newlines
86
- TASK_SUMMARY=$(echo "$RAW_SUMMARY" | tr '\n' ' ' | cut -c1-100)
87
- # Add ellipsis if truncated
88
- if [ ${#RAW_SUMMARY} -gt 100 ]; then
89
- TASK_SUMMARY="${TASK_SUMMARY}..."
90
- fi
91
- fi
92
- fi
93
- fi
94
-
95
- # Build session info string (shown in body)
96
- SESSION_INFO=""
97
- if [ -n "$SESSION_ID" ]; then
98
- SESSION_INFO="Session: $SESSION_ID"
99
- fi
100
-
101
- # Determine notification title and body based on hook type
102
- case "$HOOK_EVENT" in
103
- "Notification")
104
- case "$NOTIFICATION_TYPE" in
105
- "permission_prompt")
106
- TITLE="Claude [$SOURCE] - Permission Required"
107
- BODY="${MESSAGE:-Claude needs your permission to continue}"
108
- if [ -n "$SESSION_INFO" ]; then
109
- BODY="$SESSION_INFO
110
- $BODY"
111
- fi
112
- PRIORITY="high"
113
- TAGS="warning"
114
- ;;
115
- "idle_prompt")
116
- TITLE="Claude [$SOURCE] - Waiting"
117
- BODY="${MESSAGE:-Claude is waiting for your input}"
118
- if [ -n "$SESSION_INFO" ]; then
119
- BODY="$SESSION_INFO
120
- $BODY"
121
- fi
122
- PRIORITY="default"
123
- TAGS="hourglass"
124
- ;;
125
- *)
126
- TITLE="Claude [$SOURCE] - Attention"
127
- BODY="${MESSAGE:-Claude needs your attention}"
128
- if [ -n "$SESSION_INFO" ]; then
129
- BODY="$SESSION_INFO
130
- $BODY"
131
- fi
132
- PRIORITY="default"
133
- TAGS="bell"
134
- ;;
135
- esac
136
- ;;
137
- "Stop")
138
- TITLE="Claude [$SOURCE] - Finished"
139
- BODY="$PROJECT_NAME"
140
- if [ -n "$SESSION_INFO" ]; then
141
- BODY="$SESSION_INFO | $BODY"
142
- fi
143
- if [ -n "$TASK_SUMMARY" ]; then
144
- BODY="$BODY
145
- $TASK_SUMMARY"
146
- fi
147
- PRIORITY="default"
148
- TAGS="white_check_mark"
149
- ;;
150
- "SubagentStop")
151
- TITLE="Claude [$SOURCE] - Subagent Done"
152
- BODY="$PROJECT_NAME"
153
- if [ -n "$SESSION_INFO" ]; then
154
- BODY="$SESSION_INFO | $BODY"
155
- fi
156
- if [ -n "$TASK_SUMMARY" ]; then
157
- BODY="$BODY
158
- $TASK_SUMMARY"
159
- fi
160
- PRIORITY="low"
161
- TAGS="checkered_flag"
162
- ;;
163
- *)
164
- TITLE="Claude [$SOURCE]"
165
- BODY="${MESSAGE:-Event: $HOOK_EVENT}"
166
- if [ -n "$SESSION_INFO" ]; then
167
- BODY="$SESSION_INFO
168
- $BODY"
169
- fi
170
- PRIORITY="default"
171
- TAGS="robot"
172
- ;;
173
- esac
174
-
175
- # Send notification via ntfy.sh
176
- curl -s \
177
- -H "Title: $TITLE" \
178
- -H "Priority: $PRIORITY" \
179
- -H "Tags: $TAGS" \
180
- -d "$BODY" \
181
- "https://ntfy.sh/$NTFY_TOPIC" > /dev/null 2>&1
182
-
183
- exit 0
@@ -1,183 +0,0 @@
1
- #!/bin/bash
2
- # This file is managed by Lisa.
3
- # Do not edit directly — changes will be overwritten on the next `lisa` run.
4
- # =============================================================================
5
- # ntfy.sh Notification Hook for Claude Code
6
- # =============================================================================
7
- # Sends desktop and mobile notifications via ntfy.sh when Claude needs
8
- # attention or finishes a task.
9
- #
10
- # Setup:
11
- # 1. Install ntfy app on mobile (iOS App Store / Android Play Store)
12
- # 2. Subscribe to your unique topic in the app
13
- # 3. Set NTFY_TOPIC environment variable (e.g., in ~/.bashrc or ~/.zshrc):
14
- # export NTFY_TOPIC="my-claude-alerts-xyz123"
15
- #
16
- # @see https://ntfy.sh
17
- # =============================================================================
18
-
19
- # Read JSON input from stdin
20
- INPUT=$(cat)
21
-
22
- # Extract hook event name
23
- HOOK_EVENT=$(echo "$INPUT" | grep -o '"hook_event_name"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"//' | sed 's/"$//')
24
-
25
- # Extract notification type (for Notification hooks)
26
- NOTIFICATION_TYPE=$(echo "$INPUT" | grep -o '"notification_type"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"//' | sed 's/"$//')
27
-
28
- # Extract message if available
29
- MESSAGE=$(echo "$INPUT" | grep -o '"message"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"//' | sed 's/"$//')
30
-
31
- # Extract session ID (first 8 chars for brevity)
32
- FULL_SESSION_ID=$(echo "$INPUT" | grep -o '"session_id"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"//' | sed 's/"$//')
33
- SESSION_ID="${FULL_SESSION_ID:0:8}"
34
-
35
- # Extract transcript path for task summary
36
- TRANSCRIPT_PATH=$(echo "$INPUT" | grep -o '"transcript_path"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"//' | sed 's/"$//')
37
-
38
- # Determine source (Web vs Local)
39
- if [ "$CLAUDE_CODE_REMOTE" = "true" ]; then
40
- SOURCE="Web"
41
- else
42
- SOURCE="Local"
43
- fi
44
-
45
- # Get project name from current directory
46
- PROJECT_NAME=$(basename "$CLAUDE_PROJECT_DIR" 2>/dev/null || basename "$(pwd)")
47
-
48
- # Load NTFY_TOPIC from local config if not already set
49
- if [ -z "$NTFY_TOPIC" ]; then
50
- # Check for project-local config (gitignored)
51
- if [ -f "$CLAUDE_PROJECT_DIR/.claude/env.local" ]; then
52
- # shellcheck source=/dev/null
53
- source "$CLAUDE_PROJECT_DIR/.claude/env.local"
54
- fi
55
- # Check for user-global config
56
- if [ -z "$NTFY_TOPIC" ] && [ -f "$HOME/.claude/env.local" ]; then
57
- # shellcheck source=/dev/null
58
- source "$HOME/.claude/env.local"
59
- fi
60
- fi
61
-
62
- # Exit silently if still not configured
63
- if [ -z "$NTFY_TOPIC" ]; then
64
- exit 0
65
- fi
66
-
67
- # Extract task summary from transcript (last assistant message, truncated)
68
- TASK_SUMMARY=""
69
- if [ -n "$TRANSCRIPT_PATH" ] && [ -f "$TRANSCRIPT_PATH" ]; then
70
- # Get the last assistant message from the JSONL transcript
71
- # The transcript contains lines with "type":"assistant" and "message" content
72
- # Use awk for cross-platform compatibility (tac is not available on macOS)
73
- LAST_ASSISTANT=$(awk '/"type"[[:space:]]*:[[:space:]]*"assistant"/{line=$0} END{if(line) print line}' "$TRANSCRIPT_PATH" 2>/dev/null)
74
- if [ -n "$LAST_ASSISTANT" ]; then
75
- # Extract the message content - look for text content in the message
76
- # Format: {"message":{"content":[{"type":"text","text":"..."}]}}
77
- # Use jq for robust JSON parsing when available, fallback to grep/sed
78
- if command -v jq >/dev/null 2>&1; then
79
- RAW_SUMMARY=$(echo "$LAST_ASSISTANT" | jq -r '.message.content[] | select(.type == "text") | .text' 2>/dev/null | head -1)
80
- else
81
- # Fallback: simple regex extraction (may fail on escaped quotes)
82
- RAW_SUMMARY=$(echo "$LAST_ASSISTANT" | grep -o '"text"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*: *"//' | sed 's/"$//')
83
- fi
84
- if [ -n "$RAW_SUMMARY" ]; then
85
- # Truncate to 100 chars and clean up newlines
86
- TASK_SUMMARY=$(echo "$RAW_SUMMARY" | tr '\n' ' ' | cut -c1-100)
87
- # Add ellipsis if truncated
88
- if [ ${#RAW_SUMMARY} -gt 100 ]; then
89
- TASK_SUMMARY="${TASK_SUMMARY}..."
90
- fi
91
- fi
92
- fi
93
- fi
94
-
95
- # Build session info string (shown in body)
96
- SESSION_INFO=""
97
- if [ -n "$SESSION_ID" ]; then
98
- SESSION_INFO="Session: $SESSION_ID"
99
- fi
100
-
101
- # Determine notification title and body based on hook type
102
- case "$HOOK_EVENT" in
103
- "Notification")
104
- case "$NOTIFICATION_TYPE" in
105
- "permission_prompt")
106
- TITLE="Claude [$SOURCE] - Permission Required"
107
- BODY="${MESSAGE:-Claude needs your permission to continue}"
108
- if [ -n "$SESSION_INFO" ]; then
109
- BODY="$SESSION_INFO
110
- $BODY"
111
- fi
112
- PRIORITY="high"
113
- TAGS="warning"
114
- ;;
115
- "idle_prompt")
116
- TITLE="Claude [$SOURCE] - Waiting"
117
- BODY="${MESSAGE:-Claude is waiting for your input}"
118
- if [ -n "$SESSION_INFO" ]; then
119
- BODY="$SESSION_INFO
120
- $BODY"
121
- fi
122
- PRIORITY="default"
123
- TAGS="hourglass"
124
- ;;
125
- *)
126
- TITLE="Claude [$SOURCE] - Attention"
127
- BODY="${MESSAGE:-Claude needs your attention}"
128
- if [ -n "$SESSION_INFO" ]; then
129
- BODY="$SESSION_INFO
130
- $BODY"
131
- fi
132
- PRIORITY="default"
133
- TAGS="bell"
134
- ;;
135
- esac
136
- ;;
137
- "Stop")
138
- TITLE="Claude [$SOURCE] - Finished"
139
- BODY="$PROJECT_NAME"
140
- if [ -n "$SESSION_INFO" ]; then
141
- BODY="$SESSION_INFO | $BODY"
142
- fi
143
- if [ -n "$TASK_SUMMARY" ]; then
144
- BODY="$BODY
145
- $TASK_SUMMARY"
146
- fi
147
- PRIORITY="default"
148
- TAGS="white_check_mark"
149
- ;;
150
- "SubagentStop")
151
- TITLE="Claude [$SOURCE] - Subagent Done"
152
- BODY="$PROJECT_NAME"
153
- if [ -n "$SESSION_INFO" ]; then
154
- BODY="$SESSION_INFO | $BODY"
155
- fi
156
- if [ -n "$TASK_SUMMARY" ]; then
157
- BODY="$BODY
158
- $TASK_SUMMARY"
159
- fi
160
- PRIORITY="low"
161
- TAGS="checkered_flag"
162
- ;;
163
- *)
164
- TITLE="Claude [$SOURCE]"
165
- BODY="${MESSAGE:-Event: $HOOK_EVENT}"
166
- if [ -n "$SESSION_INFO" ]; then
167
- BODY="$SESSION_INFO
168
- $BODY"
169
- fi
170
- PRIORITY="default"
171
- TAGS="robot"
172
- ;;
173
- esac
174
-
175
- # Send notification via ntfy.sh
176
- curl -s \
177
- -H "Title: $TITLE" \
178
- -H "Priority: $PRIORITY" \
179
- -H "Tags: $TAGS" \
180
- -d "$BODY" \
181
- "https://ntfy.sh/$NTFY_TOPIC" > /dev/null 2>&1
182
-
183
- exit 0
@@ -1,8 +0,0 @@
1
- {
2
- "mcpServers": {
3
- "expo": {
4
- "type": "http",
5
- "url": "https://mcp.expo.dev/mcp"
6
- }
7
- }
8
- }
@@ -1,183 +0,0 @@
1
- #!/bin/bash
2
- # This file is managed by Lisa.
3
- # Do not edit directly — changes will be overwritten on the next `lisa` run.
4
- # =============================================================================
5
- # ntfy.sh Notification Hook for Claude Code
6
- # =============================================================================
7
- # Sends desktop and mobile notifications via ntfy.sh when Claude needs
8
- # attention or finishes a task.
9
- #
10
- # Setup:
11
- # 1. Install ntfy app on mobile (iOS App Store / Android Play Store)
12
- # 2. Subscribe to your unique topic in the app
13
- # 3. Set NTFY_TOPIC environment variable (e.g., in ~/.bashrc or ~/.zshrc):
14
- # export NTFY_TOPIC="my-claude-alerts-xyz123"
15
- #
16
- # @see https://ntfy.sh
17
- # =============================================================================
18
-
19
- # Read JSON input from stdin
20
- INPUT=$(cat)
21
-
22
- # Extract hook event name
23
- HOOK_EVENT=$(echo "$INPUT" | grep -o '"hook_event_name"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"//' | sed 's/"$//')
24
-
25
- # Extract notification type (for Notification hooks)
26
- NOTIFICATION_TYPE=$(echo "$INPUT" | grep -o '"notification_type"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"//' | sed 's/"$//')
27
-
28
- # Extract message if available
29
- MESSAGE=$(echo "$INPUT" | grep -o '"message"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"//' | sed 's/"$//')
30
-
31
- # Extract session ID (first 8 chars for brevity)
32
- FULL_SESSION_ID=$(echo "$INPUT" | grep -o '"session_id"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"//' | sed 's/"$//')
33
- SESSION_ID="${FULL_SESSION_ID:0:8}"
34
-
35
- # Extract transcript path for task summary
36
- TRANSCRIPT_PATH=$(echo "$INPUT" | grep -o '"transcript_path"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"//' | sed 's/"$//')
37
-
38
- # Determine source (Web vs Local)
39
- if [ "$CLAUDE_CODE_REMOTE" = "true" ]; then
40
- SOURCE="Web"
41
- else
42
- SOURCE="Local"
43
- fi
44
-
45
- # Get project name from current directory
46
- PROJECT_NAME=$(basename "$CLAUDE_PROJECT_DIR" 2>/dev/null || basename "$(pwd)")
47
-
48
- # Load NTFY_TOPIC from local config if not already set
49
- if [ -z "$NTFY_TOPIC" ]; then
50
- # Check for project-local config (gitignored)
51
- if [ -f "$CLAUDE_PROJECT_DIR/.claude/env.local" ]; then
52
- # shellcheck source=/dev/null
53
- source "$CLAUDE_PROJECT_DIR/.claude/env.local"
54
- fi
55
- # Check for user-global config
56
- if [ -z "$NTFY_TOPIC" ] && [ -f "$HOME/.claude/env.local" ]; then
57
- # shellcheck source=/dev/null
58
- source "$HOME/.claude/env.local"
59
- fi
60
- fi
61
-
62
- # Exit silently if still not configured
63
- if [ -z "$NTFY_TOPIC" ]; then
64
- exit 0
65
- fi
66
-
67
- # Extract task summary from transcript (last assistant message, truncated)
68
- TASK_SUMMARY=""
69
- if [ -n "$TRANSCRIPT_PATH" ] && [ -f "$TRANSCRIPT_PATH" ]; then
70
- # Get the last assistant message from the JSONL transcript
71
- # The transcript contains lines with "type":"assistant" and "message" content
72
- # Use awk for cross-platform compatibility (tac is not available on macOS)
73
- LAST_ASSISTANT=$(awk '/"type"[[:space:]]*:[[:space:]]*"assistant"/{line=$0} END{if(line) print line}' "$TRANSCRIPT_PATH" 2>/dev/null)
74
- if [ -n "$LAST_ASSISTANT" ]; then
75
- # Extract the message content - look for text content in the message
76
- # Format: {"message":{"content":[{"type":"text","text":"..."}]}}
77
- # Use jq for robust JSON parsing when available, fallback to grep/sed
78
- if command -v jq >/dev/null 2>&1; then
79
- RAW_SUMMARY=$(echo "$LAST_ASSISTANT" | jq -r '.message.content[] | select(.type == "text") | .text' 2>/dev/null | head -1)
80
- else
81
- # Fallback: simple regex extraction (may fail on escaped quotes)
82
- RAW_SUMMARY=$(echo "$LAST_ASSISTANT" | grep -o '"text"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*: *"//' | sed 's/"$//')
83
- fi
84
- if [ -n "$RAW_SUMMARY" ]; then
85
- # Truncate to 100 chars and clean up newlines
86
- TASK_SUMMARY=$(echo "$RAW_SUMMARY" | tr '\n' ' ' | cut -c1-100)
87
- # Add ellipsis if truncated
88
- if [ ${#RAW_SUMMARY} -gt 100 ]; then
89
- TASK_SUMMARY="${TASK_SUMMARY}..."
90
- fi
91
- fi
92
- fi
93
- fi
94
-
95
- # Build session info string (shown in body)
96
- SESSION_INFO=""
97
- if [ -n "$SESSION_ID" ]; then
98
- SESSION_INFO="Session: $SESSION_ID"
99
- fi
100
-
101
- # Determine notification title and body based on hook type
102
- case "$HOOK_EVENT" in
103
- "Notification")
104
- case "$NOTIFICATION_TYPE" in
105
- "permission_prompt")
106
- TITLE="Claude [$SOURCE] - Permission Required"
107
- BODY="${MESSAGE:-Claude needs your permission to continue}"
108
- if [ -n "$SESSION_INFO" ]; then
109
- BODY="$SESSION_INFO
110
- $BODY"
111
- fi
112
- PRIORITY="high"
113
- TAGS="warning"
114
- ;;
115
- "idle_prompt")
116
- TITLE="Claude [$SOURCE] - Waiting"
117
- BODY="${MESSAGE:-Claude is waiting for your input}"
118
- if [ -n "$SESSION_INFO" ]; then
119
- BODY="$SESSION_INFO
120
- $BODY"
121
- fi
122
- PRIORITY="default"
123
- TAGS="hourglass"
124
- ;;
125
- *)
126
- TITLE="Claude [$SOURCE] - Attention"
127
- BODY="${MESSAGE:-Claude needs your attention}"
128
- if [ -n "$SESSION_INFO" ]; then
129
- BODY="$SESSION_INFO
130
- $BODY"
131
- fi
132
- PRIORITY="default"
133
- TAGS="bell"
134
- ;;
135
- esac
136
- ;;
137
- "Stop")
138
- TITLE="Claude [$SOURCE] - Finished"
139
- BODY="$PROJECT_NAME"
140
- if [ -n "$SESSION_INFO" ]; then
141
- BODY="$SESSION_INFO | $BODY"
142
- fi
143
- if [ -n "$TASK_SUMMARY" ]; then
144
- BODY="$BODY
145
- $TASK_SUMMARY"
146
- fi
147
- PRIORITY="default"
148
- TAGS="white_check_mark"
149
- ;;
150
- "SubagentStop")
151
- TITLE="Claude [$SOURCE] - Subagent Done"
152
- BODY="$PROJECT_NAME"
153
- if [ -n "$SESSION_INFO" ]; then
154
- BODY="$SESSION_INFO | $BODY"
155
- fi
156
- if [ -n "$TASK_SUMMARY" ]; then
157
- BODY="$BODY
158
- $TASK_SUMMARY"
159
- fi
160
- PRIORITY="low"
161
- TAGS="checkered_flag"
162
- ;;
163
- *)
164
- TITLE="Claude [$SOURCE]"
165
- BODY="${MESSAGE:-Event: $HOOK_EVENT}"
166
- if [ -n "$SESSION_INFO" ]; then
167
- BODY="$SESSION_INFO
168
- $BODY"
169
- fi
170
- PRIORITY="default"
171
- TAGS="robot"
172
- ;;
173
- esac
174
-
175
- # Send notification via ntfy.sh
176
- curl -s \
177
- -H "Title: $TITLE" \
178
- -H "Priority: $PRIORITY" \
179
- -H "Tags: $TAGS" \
180
- -d "$BODY" \
181
- "https://ntfy.sh/$NTFY_TOPIC" > /dev/null 2>&1
182
-
183
- exit 0