@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.
Files changed (64) hide show
  1. package/.claude-plugin/plugin.json +22 -0
  2. package/.env.example +28 -0
  3. package/LICENSE +21 -0
  4. package/README.md +301 -0
  5. package/bin/claude-companion-setup +65 -0
  6. package/bin/codevibe-claude +134 -0
  7. package/dist/appsync-client.d.ts +67 -0
  8. package/dist/appsync-client.d.ts.map +1 -0
  9. package/dist/appsync-client.js +858 -0
  10. package/dist/appsync-client.js.map +1 -0
  11. package/dist/auth-cli.d.ts +18 -0
  12. package/dist/auth-cli.d.ts.map +1 -0
  13. package/dist/auth-cli.js +472 -0
  14. package/dist/auth-cli.js.map +1 -0
  15. package/dist/command-executor.d.ts +20 -0
  16. package/dist/command-executor.d.ts.map +1 -0
  17. package/dist/command-executor.js +127 -0
  18. package/dist/command-executor.js.map +1 -0
  19. package/dist/config.d.ts +25 -0
  20. package/dist/config.d.ts.map +1 -0
  21. package/dist/config.js +106 -0
  22. package/dist/config.js.map +1 -0
  23. package/dist/crypto-service.d.ts +115 -0
  24. package/dist/crypto-service.d.ts.map +1 -0
  25. package/dist/crypto-service.js +278 -0
  26. package/dist/crypto-service.js.map +1 -0
  27. package/dist/http-api.d.ts +35 -0
  28. package/dist/http-api.d.ts.map +1 -0
  29. package/dist/http-api.js +334 -0
  30. package/dist/http-api.js.map +1 -0
  31. package/dist/key-manager.d.ts +87 -0
  32. package/dist/key-manager.d.ts.map +1 -0
  33. package/dist/key-manager.js +287 -0
  34. package/dist/key-manager.js.map +1 -0
  35. package/dist/logger.d.ts +2 -0
  36. package/dist/logger.d.ts.map +1 -0
  37. package/dist/logger.js +18 -0
  38. package/dist/logger.js.map +1 -0
  39. package/dist/prompt-responder.d.ts +22 -0
  40. package/dist/prompt-responder.d.ts.map +1 -0
  41. package/dist/prompt-responder.js +132 -0
  42. package/dist/prompt-responder.js.map +1 -0
  43. package/dist/server.d.ts +2 -0
  44. package/dist/server.d.ts.map +1 -0
  45. package/dist/server.js +1154 -0
  46. package/dist/server.js.map +1 -0
  47. package/dist/token-storage.d.ts +39 -0
  48. package/dist/token-storage.d.ts.map +1 -0
  49. package/dist/token-storage.js +169 -0
  50. package/dist/token-storage.js.map +1 -0
  51. package/dist/types.d.ts +110 -0
  52. package/dist/types.d.ts.map +1 -0
  53. package/dist/types.js +17 -0
  54. package/dist/types.js.map +1 -0
  55. package/hooks/common.sh +121 -0
  56. package/hooks/hooks.json +81 -0
  57. package/hooks/notification.sh +32 -0
  58. package/hooks/permission-request.sh +191 -0
  59. package/hooks/post-tool-use.sh +42 -0
  60. package/hooks/session-end.sh +57 -0
  61. package/hooks/session-start.sh +127 -0
  62. package/hooks/stop.sh +255 -0
  63. package/hooks/user-prompt.sh +32 -0
  64. 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
+ }