@quantiya/codevibe-claude-plugin 1.0.6 → 1.0.7
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/.claude-plugin/plugin.json +1 -1
- package/hooks/permission-request.sh +60 -81
- package/hooks/stop.sh +94 -106
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codevibe-claude",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "Sync Claude Code sessions with iOS mobile app via AWS backend. Control Claude Code from your iPhone with real-time bidirectional synchronization.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "CodeVibe Team"
|
|
@@ -39,92 +39,71 @@ SENT_UUIDS_FILE="${CODEVIBE_TMPDIR}/codevibe-claude-sent-uuids-${SESSION_ID}.txt
|
|
|
39
39
|
if [ -n "$TRANSCRIPT_PATH" ] && [ -f "$TRANSCRIPT_PATH" ]; then
|
|
40
40
|
log "DEBUG" "Reading transcript for assistant responses: $TRANSCRIPT_PATH"
|
|
41
41
|
|
|
42
|
-
#
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
# Track assistant messages in current turn (chained by parentUuid)
|
|
49
|
-
declare -a CURRENT_TURN_UUIDS
|
|
50
|
-
CURRENT_TURN_UUIDS=("$LAST_USER_UUID")
|
|
51
|
-
|
|
52
|
-
# First pass: build the complete UUID chain including both assistant and user (tool_result) messages
|
|
53
|
-
# This is needed because the chain goes: user_prompt -> assistant -> user(tool_result) -> assistant -> ...
|
|
54
|
-
while IFS= read -r line; do
|
|
55
|
-
PARENT_UUID=$(echo "$line" | jq -r '.parentUuid // empty')
|
|
56
|
-
MESSAGE_UUID=$(echo "$line" | jq -r '.uuid // empty')
|
|
57
|
-
|
|
58
|
-
# Check if parent is in current turn - if so, add this UUID to the chain
|
|
59
|
-
for turn_uuid in "${CURRENT_TURN_UUIDS[@]}"; do
|
|
60
|
-
if [ "$PARENT_UUID" = "$turn_uuid" ]; then
|
|
61
|
-
CURRENT_TURN_UUIDS+=("$MESSAGE_UUID")
|
|
62
|
-
break
|
|
63
|
-
fi
|
|
64
|
-
done
|
|
65
|
-
done < "$TRANSCRIPT_PATH"
|
|
66
|
-
|
|
67
|
-
log "DEBUG" "Built UUID chain with ${#CURRENT_TURN_UUIDS[@]} entries"
|
|
68
|
-
|
|
69
|
-
# Second pass: extract and send assistant text messages that are in the chain
|
|
70
|
-
while IFS= read -r line; do
|
|
71
|
-
MESSAGE_UUID=$(echo "$line" | jq -r '.uuid // empty')
|
|
72
|
-
|
|
73
|
-
# Check if this message is in our turn chain
|
|
74
|
-
IS_CURRENT_TURN=false
|
|
75
|
-
for turn_uuid in "${CURRENT_TURN_UUIDS[@]}"; do
|
|
76
|
-
if [ "$MESSAGE_UUID" = "$turn_uuid" ]; then
|
|
77
|
-
IS_CURRENT_TURN=true
|
|
78
|
-
break
|
|
79
|
-
fi
|
|
80
|
-
done
|
|
81
|
-
|
|
82
|
-
if [ "$IS_CURRENT_TURN" = false ]; then
|
|
83
|
-
continue
|
|
84
|
-
fi
|
|
85
|
-
|
|
86
|
-
# Skip if this message UUID was already sent
|
|
87
|
-
if [ -f "$SENT_UUIDS_FILE" ] && grep -q "^${MESSAGE_UUID}$" "$SENT_UUIDS_FILE"; then
|
|
88
|
-
log "DEBUG" "Skipping already sent message UUID: $MESSAGE_UUID"
|
|
89
|
-
continue
|
|
90
|
-
fi
|
|
91
|
-
|
|
92
|
-
MESSAGE_CONTENT=$(echo "$line" | jq -r '.message.content // empty')
|
|
42
|
+
# Load already-sent UUIDs for filtering
|
|
43
|
+
SENT_UUIDS_CONTENT=""
|
|
44
|
+
if [ -f "$SENT_UUIDS_FILE" ]; then
|
|
45
|
+
SENT_UUIDS_CONTENT=$(cat "$SENT_UUIDS_FILE")
|
|
46
|
+
fi
|
|
93
47
|
|
|
94
|
-
|
|
48
|
+
# Single jq invocation: find last user UUID, build chain, extract unsent assistant text
|
|
49
|
+
# This replaces two slow bash while-loops that spawned jq per line (O(n) jq processes → 1)
|
|
50
|
+
ASSISTANT_MESSAGES=$(jq -r --slurp --arg sent "$SENT_UUIDS_CONTENT" '
|
|
51
|
+
# Find last user prompt UUID
|
|
52
|
+
(map(select(.type == "user")) | last | .uuid // empty) as $lastUserUuid |
|
|
53
|
+
if ($lastUserUuid | length) == 0 then empty
|
|
54
|
+
else
|
|
55
|
+
# Build UUID chain from last user prompt
|
|
56
|
+
reduce .[] as $msg (
|
|
57
|
+
[$lastUserUuid];
|
|
58
|
+
if (. | any(. == ($msg.parentUuid // ""))) then
|
|
59
|
+
. + [($msg.uuid // "")]
|
|
60
|
+
else . end
|
|
61
|
+
) as $chain |
|
|
62
|
+
# Parse sent UUIDs
|
|
63
|
+
($sent | split("\n") | map(select(. != ""))) as $sentList |
|
|
64
|
+
# Filter: assistant messages in chain, not sent, with text content
|
|
65
|
+
.[] |
|
|
66
|
+
select(.type == "assistant") |
|
|
67
|
+
select(.uuid as $u | $chain | any(. == $u)) |
|
|
68
|
+
select(.uuid as $u | $sentList | any(. == $u) | not) |
|
|
69
|
+
{
|
|
70
|
+
uuid: .uuid,
|
|
71
|
+
text: ([.message.content[]? | select(.type == "text") | .text // empty] | join(" "))
|
|
72
|
+
} |
|
|
73
|
+
select(.text | length > 0) |
|
|
74
|
+
"\(.uuid)\t\(.text)"
|
|
75
|
+
end
|
|
76
|
+
' "$TRANSCRIPT_PATH" 2>/dev/null)
|
|
77
|
+
|
|
78
|
+
if [ -n "$ASSISTANT_MESSAGES" ]; then
|
|
79
|
+
while IFS=$'\t' read -r MSG_UUID MSG_TEXT; do
|
|
80
|
+
if [ -z "$MSG_UUID" ] || [ -z "$MSG_TEXT" ]; then
|
|
95
81
|
continue
|
|
96
82
|
fi
|
|
97
83
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
log "
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
if [ $? -eq 0 ]; then
|
|
120
|
-
log "INFO" "Assistant response sent successfully from PermissionRequest hook"
|
|
121
|
-
# Track that this message UUID was sent
|
|
122
|
-
echo "$MESSAGE_UUID" >> "$SENT_UUIDS_FILE"
|
|
123
|
-
else
|
|
124
|
-
log "ERROR" "Failed to send assistant response from PermissionRequest hook"
|
|
125
|
-
fi
|
|
84
|
+
ASSISTANT_PAYLOAD=$(jq -n \
|
|
85
|
+
--arg session_id "$SESSION_ID" \
|
|
86
|
+
--arg content "$MSG_TEXT" \
|
|
87
|
+
--arg hook_event_name "PermissionRequest" \
|
|
88
|
+
--arg type "ASSISTANT_RESPONSE" \
|
|
89
|
+
'{
|
|
90
|
+
session_id: $session_id,
|
|
91
|
+
hook_event_name: $hook_event_name,
|
|
92
|
+
type: $type,
|
|
93
|
+
content: $content
|
|
94
|
+
}')
|
|
95
|
+
|
|
96
|
+
log "DEBUG" "Sending assistant response from PermissionRequest: ${MSG_TEXT:0:100}..."
|
|
97
|
+
|
|
98
|
+
send_to_mcp "event" "$ASSISTANT_PAYLOAD" "$SESSION_ID"
|
|
99
|
+
|
|
100
|
+
if [ $? -eq 0 ]; then
|
|
101
|
+
log "INFO" "Assistant response sent successfully from PermissionRequest hook"
|
|
102
|
+
echo "$MSG_UUID" >> "$SENT_UUIDS_FILE"
|
|
103
|
+
else
|
|
104
|
+
log "ERROR" "Failed to send assistant response from PermissionRequest hook"
|
|
126
105
|
fi
|
|
127
|
-
done
|
|
106
|
+
done <<< "$ASSISTANT_MESSAGES"
|
|
128
107
|
fi
|
|
129
108
|
else
|
|
130
109
|
log "WARN" "Transcript path not found or file doesn't exist: $TRANSCRIPT_PATH"
|
package/hooks/stop.sh
CHANGED
|
@@ -31,123 +31,111 @@ fi
|
|
|
31
31
|
log "DEBUG" "Reading transcript: $TRANSCRIPT_PATH"
|
|
32
32
|
|
|
33
33
|
# Parse transcript and extract events since last user prompt
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
# 2. Find last user prompt (type=user with prompt content)
|
|
37
|
-
# 3. Extract all assistant messages after that prompt
|
|
38
|
-
# 4. Send each as separate event to MCP server
|
|
39
|
-
|
|
40
|
-
# Get the last user prompt UUID to know where assistant messages start
|
|
41
|
-
LAST_USER_UUID=$(grep '"type":"user"' "$TRANSCRIPT_PATH" | tail -1 | jq -r '.uuid // empty')
|
|
42
|
-
|
|
43
|
-
if [ -z "$LAST_USER_UUID" ]; then
|
|
44
|
-
log "WARN" "No user prompt found in transcript"
|
|
45
|
-
exit 0
|
|
46
|
-
fi
|
|
47
|
-
|
|
48
|
-
log "DEBUG" "Last user prompt UUID: $LAST_USER_UUID"
|
|
49
|
-
|
|
50
|
-
# Extract all assistant messages after the last user prompt
|
|
51
|
-
# We'll read the file and process assistant messages
|
|
34
|
+
# Single jq --slurp invocation: finds last user UUID, builds parent chain,
|
|
35
|
+
# extracts assistant text + tool_use events. ~200x faster than per-line bash+jq loops.
|
|
52
36
|
EVENTS_SENT=0
|
|
53
37
|
|
|
54
38
|
# Track sent message UUIDs to avoid duplicates (shared with PermissionRequest hook)
|
|
55
39
|
SENT_UUIDS_FILE="${CODEVIBE_TMPDIR}/codevibe-claude-sent-uuids-${SESSION_ID}.txt"
|
|
56
40
|
|
|
57
|
-
#
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
# First pass: build the complete UUID chain including both assistant and user (tool_result) messages
|
|
63
|
-
# This is needed because the chain goes: user_prompt -> assistant -> user(tool_result) -> assistant -> ...
|
|
64
|
-
while IFS= read -r line; do
|
|
65
|
-
PARENT_UUID=$(echo "$line" | jq -r '.parentUuid // empty')
|
|
66
|
-
MESSAGE_UUID=$(echo "$line" | jq -r '.uuid // empty')
|
|
67
|
-
|
|
68
|
-
# Check if parent is in current turn - if so, add this UUID to the chain
|
|
69
|
-
for turn_uuid in "${CURRENT_TURN_UUIDS[@]}"; do
|
|
70
|
-
if [ "$PARENT_UUID" = "$turn_uuid" ]; then
|
|
71
|
-
CURRENT_TURN_UUIDS+=("$MESSAGE_UUID")
|
|
72
|
-
break
|
|
73
|
-
fi
|
|
74
|
-
done
|
|
75
|
-
done < "$TRANSCRIPT_PATH"
|
|
76
|
-
|
|
77
|
-
log "DEBUG" "Built UUID chain with ${#CURRENT_TURN_UUIDS[@]} entries"
|
|
78
|
-
|
|
79
|
-
# Second pass: extract and send assistant messages that are in the chain
|
|
80
|
-
while IFS= read -r line; do
|
|
81
|
-
MESSAGE_UUID=$(echo "$line" | jq -r '.uuid // empty')
|
|
41
|
+
# Load already-sent UUIDs for filtering
|
|
42
|
+
SENT_UUIDS_CONTENT=""
|
|
43
|
+
if [ -f "$SENT_UUIDS_FILE" ]; then
|
|
44
|
+
SENT_UUIDS_CONTENT=$(cat "$SENT_UUIDS_FILE")
|
|
45
|
+
fi
|
|
82
46
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
47
|
+
# Single jq invocation: find last user UUID, build chain, extract assistant messages
|
|
48
|
+
# This replaces two slow bash while-loops that spawned jq per line (O(n) jq processes → 1)
|
|
49
|
+
# Output format: tab-separated lines: uuid\ttype\tcontent
|
|
50
|
+
# type is "text" for assistant text, "tool_use" for tool uses
|
|
51
|
+
TRANSCRIPT_EVENTS=$(jq -r --slurp --arg sent "$SENT_UUIDS_CONTENT" '
|
|
52
|
+
# Find last user prompt UUID
|
|
53
|
+
(map(select(.type == "user")) | last | .uuid // empty) as $lastUserUuid |
|
|
54
|
+
if ($lastUserUuid | length) == 0 then empty
|
|
55
|
+
else
|
|
56
|
+
# Build UUID chain from last user prompt
|
|
57
|
+
reduce .[] as $msg (
|
|
58
|
+
[$lastUserUuid];
|
|
59
|
+
if (. | any(. == ($msg.parentUuid // ""))) then
|
|
60
|
+
. + [($msg.uuid // "")]
|
|
61
|
+
else . end
|
|
62
|
+
) as $chain |
|
|
63
|
+
# Parse sent UUIDs
|
|
64
|
+
($sent | split("\n") | map(select(. != ""))) as $sentList |
|
|
65
|
+
# Filter: assistant messages in chain
|
|
66
|
+
.[] |
|
|
67
|
+
select(.type == "assistant") |
|
|
68
|
+
select(.uuid as $u | $chain | any(. == $u)) |
|
|
69
|
+
. as $msg |
|
|
70
|
+
# Emit text content
|
|
71
|
+
(
|
|
72
|
+
($msg.uuid) as $uuid |
|
|
73
|
+
($sentList | any(. == $uuid)) as $alreadySent |
|
|
74
|
+
if $alreadySent then
|
|
75
|
+
"\($uuid)\talready_sent\t"
|
|
76
|
+
else
|
|
77
|
+
# Text content
|
|
78
|
+
([$msg.message.content[]? | select(.type == "text") | .text // empty] | join(" ")) as $text |
|
|
79
|
+
if ($text | length) > 0 then
|
|
80
|
+
"\($uuid)\ttext\t\($text)"
|
|
81
|
+
else empty end
|
|
82
|
+
end
|
|
83
|
+
),
|
|
84
|
+
# Emit tool_use content (not affected by sent UUIDs)
|
|
85
|
+
(
|
|
86
|
+
$msg.message.content[]? | select(.type == "tool_use") |
|
|
87
|
+
"\($msg.uuid)\ttool_use\t\(. | tojson)"
|
|
88
|
+
)
|
|
89
|
+
end
|
|
90
|
+
' "$TRANSCRIPT_PATH" 2>/dev/null)
|
|
91
|
+
|
|
92
|
+
log "DEBUG" "Transcript parsing complete"
|
|
93
|
+
|
|
94
|
+
if [ -n "$TRANSCRIPT_EVENTS" ]; then
|
|
95
|
+
while IFS=$'\t' read -r MSG_UUID MSG_TYPE MSG_CONTENT; do
|
|
96
|
+
if [ -z "$MSG_UUID" ]; then
|
|
97
|
+
continue
|
|
89
98
|
fi
|
|
90
|
-
done
|
|
91
|
-
|
|
92
|
-
if [ "$IS_CURRENT_TURN" = false ]; then
|
|
93
|
-
continue
|
|
94
|
-
fi
|
|
95
99
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
fi
|
|
101
|
-
|
|
102
|
-
MESSAGE_CONTENT=$(echo "$line" | jq -r '.message.content // empty')
|
|
103
|
-
|
|
104
|
-
if [ -z "$MESSAGE_CONTENT" ] || [ "$MESSAGE_CONTENT" = "null" ]; then
|
|
105
|
-
continue
|
|
106
|
-
fi
|
|
107
|
-
|
|
108
|
-
# Extract text content from assistant message
|
|
109
|
-
TEXT_CONTENT=$(echo "$line" | jq -r '.message.content[] | select(.type == "text") | .text // empty' | tr '\n' ' ')
|
|
110
|
-
|
|
111
|
-
if [ -n "$TEXT_CONTENT" ] && [ "$TEXT_CONTENT" != "null" ]; then
|
|
112
|
-
# Create ASSISTANT_RESPONSE event (SESSION_ID already extracted at top)
|
|
113
|
-
EVENT_PAYLOAD=$(jq -n \
|
|
114
|
-
--arg session_id "$SESSION_ID" \
|
|
115
|
-
--arg content "$TEXT_CONTENT" \
|
|
116
|
-
--arg hook_event_name "Stop" \
|
|
117
|
-
--arg type "ASSISTANT_RESPONSE" \
|
|
118
|
-
'{
|
|
119
|
-
session_id: $session_id,
|
|
120
|
-
hook_event_name: $hook_event_name,
|
|
121
|
-
type: $type,
|
|
122
|
-
content: $content
|
|
123
|
-
}')
|
|
124
|
-
|
|
125
|
-
log "DEBUG" "Sending assistant response: ${TEXT_CONTENT:0:100}..."
|
|
126
|
-
|
|
127
|
-
send_to_mcp "event" "$EVENT_PAYLOAD" "$SESSION_ID"
|
|
128
|
-
|
|
129
|
-
if [ $? -eq 0 ]; then
|
|
130
|
-
EVENTS_SENT=$((EVENTS_SENT + 1))
|
|
131
|
-
log "INFO" "Assistant response sent successfully"
|
|
132
|
-
# Track that this message UUID was sent
|
|
133
|
-
echo "$MESSAGE_UUID" >> "$SENT_UUIDS_FILE"
|
|
134
|
-
else
|
|
135
|
-
log "ERROR" "Failed to send assistant response"
|
|
100
|
+
if [ "$MSG_TYPE" = "already_sent" ]; then
|
|
101
|
+
log "DEBUG" "Skipping already sent message UUID: $MSG_UUID"
|
|
102
|
+
EVENTS_SENT=$((EVENTS_SENT + 1)) # Count as sent to prevent fallback duplicate
|
|
103
|
+
continue
|
|
136
104
|
fi
|
|
137
|
-
fi
|
|
138
105
|
|
|
139
|
-
|
|
140
|
-
|
|
106
|
+
if [ "$MSG_TYPE" = "text" ] && [ -n "$MSG_CONTENT" ]; then
|
|
107
|
+
EVENT_PAYLOAD=$(jq -n \
|
|
108
|
+
--arg session_id "$SESSION_ID" \
|
|
109
|
+
--arg content "$MSG_CONTENT" \
|
|
110
|
+
--arg hook_event_name "Stop" \
|
|
111
|
+
--arg type "ASSISTANT_RESPONSE" \
|
|
112
|
+
'{
|
|
113
|
+
session_id: $session_id,
|
|
114
|
+
hook_event_name: $hook_event_name,
|
|
115
|
+
type: $type,
|
|
116
|
+
content: $content
|
|
117
|
+
}')
|
|
118
|
+
|
|
119
|
+
log "DEBUG" "Sending assistant response: ${MSG_CONTENT:0:100}..."
|
|
120
|
+
|
|
121
|
+
send_to_mcp "event" "$EVENT_PAYLOAD" "$SESSION_ID"
|
|
122
|
+
|
|
123
|
+
if [ $? -eq 0 ]; then
|
|
124
|
+
EVENTS_SENT=$((EVENTS_SENT + 1))
|
|
125
|
+
log "INFO" "Assistant response sent successfully"
|
|
126
|
+
echo "$MSG_UUID" >> "$SENT_UUIDS_FILE"
|
|
127
|
+
else
|
|
128
|
+
log "ERROR" "Failed to send assistant response"
|
|
129
|
+
fi
|
|
130
|
+
fi
|
|
141
131
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
TOOL_INPUT=$(echo "$tool_use" | jq -c '.input // {}')
|
|
132
|
+
if [ "$MSG_TYPE" = "tool_use" ] && [ -n "$MSG_CONTENT" ]; then
|
|
133
|
+
TOOL_NAME=$(echo "$MSG_CONTENT" | jq -r '.name // empty')
|
|
134
|
+
TOOL_INPUT=$(echo "$MSG_CONTENT" | jq -c '.input // {}')
|
|
146
135
|
|
|
147
136
|
if [ -n "$TOOL_NAME" ]; then
|
|
148
137
|
# Check if this is an interactive prompt (AskUserQuestion)
|
|
149
138
|
if [ "$TOOL_NAME" = "AskUserQuestion" ]; then
|
|
150
|
-
# Extract question text from input
|
|
151
139
|
QUESTION_TEXT=$(echo "$TOOL_INPUT" | jq -r '.questions[0].question // empty')
|
|
152
140
|
|
|
153
141
|
if [ -n "$QUESTION_TEXT" ]; then
|
|
@@ -181,7 +169,7 @@ while IFS= read -r line; do
|
|
|
181
169
|
fi
|
|
182
170
|
fi
|
|
183
171
|
|
|
184
|
-
# Send regular TOOL_USE event for all tools
|
|
172
|
+
# Send regular TOOL_USE event for all tools
|
|
185
173
|
TOOL_EVENT_PAYLOAD=$(jq -n \
|
|
186
174
|
--arg session_id "$SESSION_ID" \
|
|
187
175
|
--arg hook_event_name "Stop" \
|
|
@@ -209,10 +197,10 @@ while IFS= read -r line; do
|
|
|
209
197
|
log "ERROR" "Failed to send tool use event"
|
|
210
198
|
fi
|
|
211
199
|
fi
|
|
212
|
-
|
|
213
|
-
fi
|
|
200
|
+
fi
|
|
214
201
|
|
|
215
|
-
done
|
|
202
|
+
done <<< "$TRANSCRIPT_EVENTS"
|
|
203
|
+
fi
|
|
216
204
|
|
|
217
205
|
# Fallback: if no events were extracted from transcript but last_assistant_message is available,
|
|
218
206
|
# send it directly. This handles the race condition where the Stop hook fires before the
|
package/package.json
CHANGED