@quantiya/codevibe-claude-plugin 1.0.0
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 +22 -0
- package/.env.example +28 -0
- package/LICENSE +21 -0
- package/README.md +301 -0
- package/bin/claude-companion-setup +65 -0
- package/bin/codevibe-claude +134 -0
- package/dist/appsync-client.d.ts +67 -0
- package/dist/appsync-client.d.ts.map +1 -0
- package/dist/appsync-client.js +858 -0
- package/dist/appsync-client.js.map +1 -0
- package/dist/auth-cli.d.ts +18 -0
- package/dist/auth-cli.d.ts.map +1 -0
- package/dist/auth-cli.js +472 -0
- package/dist/auth-cli.js.map +1 -0
- package/dist/command-executor.d.ts +20 -0
- package/dist/command-executor.d.ts.map +1 -0
- package/dist/command-executor.js +127 -0
- package/dist/command-executor.js.map +1 -0
- package/dist/config.d.ts +25 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +106 -0
- package/dist/config.js.map +1 -0
- package/dist/crypto-service.d.ts +115 -0
- package/dist/crypto-service.d.ts.map +1 -0
- package/dist/crypto-service.js +278 -0
- package/dist/crypto-service.js.map +1 -0
- package/dist/http-api.d.ts +35 -0
- package/dist/http-api.d.ts.map +1 -0
- package/dist/http-api.js +334 -0
- package/dist/http-api.js.map +1 -0
- package/dist/key-manager.d.ts +87 -0
- package/dist/key-manager.d.ts.map +1 -0
- package/dist/key-manager.js +287 -0
- package/dist/key-manager.js.map +1 -0
- package/dist/logger.d.ts +2 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +18 -0
- package/dist/logger.js.map +1 -0
- package/dist/prompt-responder.d.ts +22 -0
- package/dist/prompt-responder.d.ts.map +1 -0
- package/dist/prompt-responder.js +132 -0
- package/dist/prompt-responder.js.map +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +1154 -0
- package/dist/server.js.map +1 -0
- package/dist/token-storage.d.ts +39 -0
- package/dist/token-storage.d.ts.map +1 -0
- package/dist/token-storage.js +169 -0
- package/dist/token-storage.js.map +1 -0
- package/dist/types.d.ts +110 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +17 -0
- package/dist/types.js.map +1 -0
- package/hooks/common.sh +121 -0
- package/hooks/hooks.json +81 -0
- package/hooks/notification.sh +32 -0
- package/hooks/permission-request.sh +191 -0
- package/hooks/post-tool-use.sh +42 -0
- package/hooks/session-end.sh +57 -0
- package/hooks/session-start.sh +127 -0
- package/hooks/stop.sh +255 -0
- package/hooks/user-prompt.sh +32 -0
- package/package.json +70 -0
package/hooks/stop.sh
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Stop Hook
|
|
4
|
+
# Triggered when Claude finishes responding to a prompt
|
|
5
|
+
# Captures assistant responses and tool uses from transcript file
|
|
6
|
+
|
|
7
|
+
# Source common utilities
|
|
8
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
9
|
+
source "$SCRIPT_DIR/common.sh"
|
|
10
|
+
|
|
11
|
+
log "INFO" "Stop hook triggered"
|
|
12
|
+
|
|
13
|
+
# Read JSON input from stdin
|
|
14
|
+
INPUT=$(read_json_input)
|
|
15
|
+
|
|
16
|
+
log "DEBUG" "Input: $INPUT"
|
|
17
|
+
|
|
18
|
+
# Extract transcript path
|
|
19
|
+
TRANSCRIPT_PATH=$(echo "$INPUT" | jq -r '.transcript_path // empty')
|
|
20
|
+
|
|
21
|
+
# Extract session ID early for dynamic port lookup
|
|
22
|
+
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // empty')
|
|
23
|
+
|
|
24
|
+
if [ -z "$TRANSCRIPT_PATH" ] || [ ! -f "$TRANSCRIPT_PATH" ]; then
|
|
25
|
+
log "WARN" "Transcript path not found or file doesn't exist: $TRANSCRIPT_PATH"
|
|
26
|
+
# Fallback: send notification
|
|
27
|
+
send_to_mcp "event" "$INPUT" "$SESSION_ID"
|
|
28
|
+
exit $?
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
log "DEBUG" "Reading transcript: $TRANSCRIPT_PATH"
|
|
32
|
+
|
|
33
|
+
# Parse transcript and extract events since last user prompt
|
|
34
|
+
# Strategy:
|
|
35
|
+
# 1. Read all JSONL lines
|
|
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
|
|
52
|
+
EVENTS_SENT=0
|
|
53
|
+
|
|
54
|
+
# Track sent message UUIDs to avoid duplicates (shared with PermissionRequest hook)
|
|
55
|
+
SENT_UUIDS_FILE="${CODEVIBE_TMPDIR}/codevibe-claude-sent-uuids-${SESSION_ID}.txt"
|
|
56
|
+
|
|
57
|
+
# Track messages in current turn (chained by parentUuid)
|
|
58
|
+
# Includes both assistant and user (tool_result) messages to follow the complete chain
|
|
59
|
+
declare -a CURRENT_TURN_UUIDS
|
|
60
|
+
CURRENT_TURN_UUIDS=("$LAST_USER_UUID")
|
|
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')
|
|
82
|
+
|
|
83
|
+
# Check if this message is in our turn chain
|
|
84
|
+
IS_CURRENT_TURN=false
|
|
85
|
+
for turn_uuid in "${CURRENT_TURN_UUIDS[@]}"; do
|
|
86
|
+
if [ "$MESSAGE_UUID" = "$turn_uuid" ]; then
|
|
87
|
+
IS_CURRENT_TURN=true
|
|
88
|
+
break
|
|
89
|
+
fi
|
|
90
|
+
done
|
|
91
|
+
|
|
92
|
+
if [ "$IS_CURRENT_TURN" = false ]; then
|
|
93
|
+
continue
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
# Skip if this message UUID was already sent (by PermissionRequest hook)
|
|
97
|
+
if [ -f "$SENT_UUIDS_FILE" ] && grep -q "^${MESSAGE_UUID}$" "$SENT_UUIDS_FILE"; then
|
|
98
|
+
log "DEBUG" "Skipping already sent message UUID: $MESSAGE_UUID"
|
|
99
|
+
continue
|
|
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"
|
|
136
|
+
fi
|
|
137
|
+
fi
|
|
138
|
+
|
|
139
|
+
# Extract tool use content from assistant message
|
|
140
|
+
TOOL_USES=$(echo "$line" | jq -c '.message.content[] | select(.type == "tool_use")')
|
|
141
|
+
|
|
142
|
+
if [ -n "$TOOL_USES" ]; then
|
|
143
|
+
echo "$TOOL_USES" | while IFS= read -r tool_use; do
|
|
144
|
+
TOOL_NAME=$(echo "$tool_use" | jq -r '.name // empty')
|
|
145
|
+
TOOL_INPUT=$(echo "$tool_use" | jq -c '.input // {}')
|
|
146
|
+
|
|
147
|
+
if [ -n "$TOOL_NAME" ]; then
|
|
148
|
+
# Check if this is an interactive prompt (AskUserQuestion)
|
|
149
|
+
if [ "$TOOL_NAME" = "AskUserQuestion" ]; then
|
|
150
|
+
# Extract question text from input
|
|
151
|
+
QUESTION_TEXT=$(echo "$TOOL_INPUT" | jq -r '.questions[0].question // empty')
|
|
152
|
+
|
|
153
|
+
if [ -n "$QUESTION_TEXT" ]; then
|
|
154
|
+
INTERACTIVE_PROMPT_PAYLOAD=$(jq -n \
|
|
155
|
+
--arg session_id "$SESSION_ID" \
|
|
156
|
+
--arg hook_event_name "Stop" \
|
|
157
|
+
--arg type "INTERACTIVE_PROMPT" \
|
|
158
|
+
--arg content "$QUESTION_TEXT" \
|
|
159
|
+
--argjson tool_input "$TOOL_INPUT" \
|
|
160
|
+
'{
|
|
161
|
+
session_id: $session_id,
|
|
162
|
+
hook_event_name: $hook_event_name,
|
|
163
|
+
type: $type,
|
|
164
|
+
content: $content,
|
|
165
|
+
metadata: {
|
|
166
|
+
tool_name: "AskUserQuestion",
|
|
167
|
+
tool_input: $tool_input
|
|
168
|
+
}
|
|
169
|
+
}')
|
|
170
|
+
|
|
171
|
+
log "DEBUG" "Sending interactive prompt: ${QUESTION_TEXT:0:100}..."
|
|
172
|
+
|
|
173
|
+
send_to_mcp "event" "$INTERACTIVE_PROMPT_PAYLOAD" "$SESSION_ID"
|
|
174
|
+
|
|
175
|
+
if [ $? -eq 0 ]; then
|
|
176
|
+
EVENTS_SENT=$((EVENTS_SENT + 1))
|
|
177
|
+
log "INFO" "Interactive prompt event sent successfully"
|
|
178
|
+
else
|
|
179
|
+
log "ERROR" "Failed to send interactive prompt event"
|
|
180
|
+
fi
|
|
181
|
+
fi
|
|
182
|
+
fi
|
|
183
|
+
|
|
184
|
+
# Send regular TOOL_USE event for all tools (including AskUserQuestion for context)
|
|
185
|
+
TOOL_EVENT_PAYLOAD=$(jq -n \
|
|
186
|
+
--arg session_id "$SESSION_ID" \
|
|
187
|
+
--arg hook_event_name "Stop" \
|
|
188
|
+
--arg type "TOOL_USE" \
|
|
189
|
+
--arg tool_name "$TOOL_NAME" \
|
|
190
|
+
--argjson tool_input "$TOOL_INPUT" \
|
|
191
|
+
'{
|
|
192
|
+
session_id: $session_id,
|
|
193
|
+
hook_event_name: $hook_event_name,
|
|
194
|
+
type: $type,
|
|
195
|
+
content: "{\"tool_name\":\"\($tool_name)\",\"tool_input\":\($tool_input)}",
|
|
196
|
+
metadata: {
|
|
197
|
+
tool_name: $tool_name
|
|
198
|
+
}
|
|
199
|
+
}')
|
|
200
|
+
|
|
201
|
+
log "DEBUG" "Sending tool use: $TOOL_NAME"
|
|
202
|
+
|
|
203
|
+
send_to_mcp "event" "$TOOL_EVENT_PAYLOAD" "$SESSION_ID"
|
|
204
|
+
|
|
205
|
+
if [ $? -eq 0 ]; then
|
|
206
|
+
EVENTS_SENT=$((EVENTS_SENT + 1))
|
|
207
|
+
log "INFO" "Tool use event sent successfully: $TOOL_NAME"
|
|
208
|
+
else
|
|
209
|
+
log "ERROR" "Failed to send tool use event"
|
|
210
|
+
fi
|
|
211
|
+
fi
|
|
212
|
+
done
|
|
213
|
+
fi
|
|
214
|
+
|
|
215
|
+
done < <(grep '"type":"assistant"' "$TRANSCRIPT_PATH")
|
|
216
|
+
|
|
217
|
+
# Fallback: if no events were extracted from transcript but last_assistant_message is available,
|
|
218
|
+
# send it directly. This handles the race condition where the Stop hook fires before the
|
|
219
|
+
# assistant response is fully written to the transcript file.
|
|
220
|
+
if [ "$EVENTS_SENT" -eq 0 ]; then
|
|
221
|
+
LAST_ASSISTANT_MSG=$(echo "$INPUT" | jq -r '.last_assistant_message // empty')
|
|
222
|
+
if [ -n "$LAST_ASSISTANT_MSG" ] && [ "$LAST_ASSISTANT_MSG" != "null" ]; then
|
|
223
|
+
log "INFO" "No events from transcript, using last_assistant_message fallback"
|
|
224
|
+
FALLBACK_PAYLOAD=$(jq -n \
|
|
225
|
+
--arg session_id "$SESSION_ID" \
|
|
226
|
+
--arg content "$LAST_ASSISTANT_MSG" \
|
|
227
|
+
--arg hook_event_name "Stop" \
|
|
228
|
+
--arg type "ASSISTANT_RESPONSE" \
|
|
229
|
+
'{
|
|
230
|
+
session_id: $session_id,
|
|
231
|
+
hook_event_name: $hook_event_name,
|
|
232
|
+
type: $type,
|
|
233
|
+
content: $content
|
|
234
|
+
}')
|
|
235
|
+
|
|
236
|
+
send_to_mcp "event" "$FALLBACK_PAYLOAD" "$SESSION_ID"
|
|
237
|
+
|
|
238
|
+
if [ $? -eq 0 ]; then
|
|
239
|
+
EVENTS_SENT=$((EVENTS_SENT + 1))
|
|
240
|
+
log "INFO" "Assistant response sent via fallback"
|
|
241
|
+
else
|
|
242
|
+
log "ERROR" "Failed to send fallback assistant response"
|
|
243
|
+
fi
|
|
244
|
+
fi
|
|
245
|
+
fi
|
|
246
|
+
|
|
247
|
+
log "INFO" "Stop hook completed. Events sent: $EVENTS_SENT"
|
|
248
|
+
|
|
249
|
+
# Clean up sent UUIDs file after Stop hook completes (turn is finished)
|
|
250
|
+
if [ -f "$SENT_UUIDS_FILE" ]; then
|
|
251
|
+
rm -f "$SENT_UUIDS_FILE"
|
|
252
|
+
log "DEBUG" "Cleaned up sent UUIDs file"
|
|
253
|
+
fi
|
|
254
|
+
|
|
255
|
+
exit 0
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# UserPromptSubmit Hook
|
|
4
|
+
# Triggered when user submits a prompt in Claude Code
|
|
5
|
+
# Captures user prompts and sends to mobile app
|
|
6
|
+
|
|
7
|
+
# Source common utilities
|
|
8
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
9
|
+
source "$SCRIPT_DIR/common.sh"
|
|
10
|
+
|
|
11
|
+
log "INFO" "UserPromptSubmit hook triggered"
|
|
12
|
+
|
|
13
|
+
# Read JSON input from stdin
|
|
14
|
+
INPUT=$(read_json_input)
|
|
15
|
+
|
|
16
|
+
log "DEBUG" "Input: $INPUT"
|
|
17
|
+
|
|
18
|
+
# Extract session ID for dynamic port lookup
|
|
19
|
+
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // empty')
|
|
20
|
+
|
|
21
|
+
# Send to MCP server
|
|
22
|
+
send_to_mcp "event" "$INPUT" "$SESSION_ID"
|
|
23
|
+
|
|
24
|
+
EXIT_CODE=$?
|
|
25
|
+
|
|
26
|
+
if [ $EXIT_CODE -eq 0 ]; then
|
|
27
|
+
log "INFO" "UserPromptSubmit event sent successfully"
|
|
28
|
+
else
|
|
29
|
+
log "ERROR" "Failed to send UserPromptSubmit event (exit code: $EXIT_CODE)"
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
exit $EXIT_CODE
|
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@quantiya/codevibe-claude-plugin",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Mobile companion for Claude Code - monitor and control your Claude Code sessions from your iPhone with CodeVibe",
|
|
5
|
+
"main": "dist/server.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"codevibe-claude": "./bin/codevibe-claude"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"bin",
|
|
12
|
+
"hooks",
|
|
13
|
+
".claude-plugin",
|
|
14
|
+
"README.md",
|
|
15
|
+
".env.example"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsc",
|
|
19
|
+
"prepack": "node scripts/prepare-publish.js && npm run build",
|
|
20
|
+
"postpack": "node scripts/restore-dev.js",
|
|
21
|
+
"dev": "ts-node src/server.ts",
|
|
22
|
+
"start": "node dist/server.js",
|
|
23
|
+
"watch": "tsc --watch",
|
|
24
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
25
|
+
},
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "git+https://github.com/hendryyeh/quantiya-codevibe-claude-plugin.git"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"claude",
|
|
32
|
+
"claude-code",
|
|
33
|
+
"anthropic",
|
|
34
|
+
"plugin",
|
|
35
|
+
"mobile",
|
|
36
|
+
"ios",
|
|
37
|
+
"codevibe",
|
|
38
|
+
"remote-control",
|
|
39
|
+
"ai",
|
|
40
|
+
"developer-tools"
|
|
41
|
+
],
|
|
42
|
+
"author": "Quantiya <support@quantiya.ai>",
|
|
43
|
+
"license": "MIT",
|
|
44
|
+
"bugs": {
|
|
45
|
+
"url": "https://github.com/hendryyeh/quantiya-codevibe-claude-plugin/issues"
|
|
46
|
+
},
|
|
47
|
+
"homepage": "https://github.com/hendryyeh/quantiya-codevibe-claude-plugin#readme",
|
|
48
|
+
"engines": {
|
|
49
|
+
"node": ">=18.0.0"
|
|
50
|
+
},
|
|
51
|
+
"os": [
|
|
52
|
+
"darwin"
|
|
53
|
+
],
|
|
54
|
+
"dependencies": {
|
|
55
|
+
"@quantiya/codevibe-core": "^1.0.0",
|
|
56
|
+
"dotenv": "^16.6.1",
|
|
57
|
+
"express": "^5.1.0",
|
|
58
|
+
"graphql": "^16.12.0",
|
|
59
|
+
"uuid": "^13.0.0",
|
|
60
|
+
"ws": "^8.18.3"
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@types/express": "^5.0.5",
|
|
64
|
+
"@types/node": "^24.10.1",
|
|
65
|
+
"@types/uuid": "^10.0.0",
|
|
66
|
+
"@types/ws": "^8.18.1",
|
|
67
|
+
"ts-node": "^10.9.2",
|
|
68
|
+
"typescript": "^5.9.3"
|
|
69
|
+
}
|
|
70
|
+
}
|