@codyswann/lisa 2.124.1 → 2.124.3
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.
- package/dist/agy/mcp-installer.d.ts +22 -5
- package/dist/agy/mcp-installer.d.ts.map +1 -1
- package/dist/agy/mcp-installer.js +52 -12
- package/dist/agy/mcp-installer.js.map +1 -1
- package/dist/codex/hooks-installer.d.ts.map +1 -1
- package/dist/codex/hooks-installer.js +12 -11
- package/dist/codex/hooks-installer.js.map +1 -1
- package/dist/core/lisa.d.ts +19 -11
- package/dist/core/lisa.d.ts.map +1 -1
- package/dist/core/lisa.js +44 -12
- package/dist/core/lisa.js.map +1 -1
- package/package.json +1 -1
- package/plugins/lisa/.claude-plugin/plugin.json +1 -10
- package/plugins/lisa/{hooks → .codex-plugin}/hooks.json +0 -11
- package/plugins/lisa/.codex-plugin/plugin.json +2 -2
- package/plugins/lisa/hooks/block-no-verify.agy.sh +45 -0
- package/plugins/lisa-agy/hooks/block-no-verify.agy.sh +45 -0
- package/plugins/lisa-agy/hooks.json +15 -0
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -12
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -12
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +2 -2
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +2 -2
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +2 -2
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +2 -2
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/src/base/.claude-plugin/plugin.json +0 -1
- package/plugins/src/base/hooks/block-no-verify.agy.sh +45 -0
- package/scripts/generate-agy-plugin-artifacts.mjs +158 -19
- package/scripts/generate-codex-plugin-artifacts.mjs +45 -25
- package/scripts/generate-copilot-plugin-artifacts.mjs +6 -5
- package/scripts/generate-cursor-plugin-artifacts.mjs +5 -3
- package/scripts/lib/per-agent-hook-filter.mjs +29 -18
- package/dist/codex/scripts/notify-ntfy.sh +0 -18
- package/plugins/lisa/hooks/notify-ntfy.sh +0 -183
- package/plugins/lisa-copilot/hooks/notify-ntfy.sh +0 -183
- package/plugins/lisa-cursor/hooks/notify-ntfy.sh +0 -183
- package/plugins/lisa-expo-agy/.mcp.json +0 -8
- package/plugins/src/base/hooks/notify-ntfy.sh +0 -183
- /package/plugins/lisa-harper-fabric/{hooks → .codex-plugin}/hooks.json +0 -0
- /package/plugins/lisa-nestjs/{hooks → .codex-plugin}/hooks.json +0 -0
- /package/plugins/lisa-rails/{hooks → .codex-plugin}/hooks.json +0 -0
- /package/plugins/lisa-typescript/{hooks → .codex-plugin}/hooks.json +0 -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,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
|