@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
@@ -0,0 +1,32 @@
1
+ #!/bin/bash
2
+
3
+ # Notification Hook
4
+ # Triggered when Claude Code shows a notification
5
+ # Forwards notifications 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" "Notification 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" "Notification event sent successfully"
28
+ else
29
+ log "ERROR" "Failed to send Notification event (exit code: $EXIT_CODE)"
30
+ fi
31
+
32
+ exit $EXIT_CODE
@@ -0,0 +1,191 @@
1
+ #!/bin/bash
2
+
3
+ # PermissionRequest Hook
4
+ # Triggered when Claude asks for user permission (Y/n prompts)
5
+ # Captures interactive prompts and can forward to mobile for remote approval
6
+ # Also extracts any assistant responses from the transcript that haven't been sent yet
7
+
8
+ # Source common utilities
9
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
10
+ source "$SCRIPT_DIR/common.sh"
11
+
12
+ log "INFO" "PermissionRequest hook triggered"
13
+
14
+ # Read JSON input from stdin
15
+ INPUT=$(read_json_input)
16
+
17
+ log "DEBUG" "Input: $INPUT"
18
+
19
+ # Extract fields from permission request
20
+ SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // empty')
21
+ TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty')
22
+ TOOL_INPUT=$(echo "$INPUT" | jq -c '.tool_input // {}')
23
+ FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
24
+ TRANSCRIPT_PATH=$(echo "$INPUT" | jq -r '.transcript_path // empty')
25
+
26
+ if [ -z "$SESSION_ID" ]; then
27
+ log "ERROR" "No session_id found in permission request"
28
+ exit 1
29
+ fi
30
+
31
+ # ============================================================
32
+ # FIRST: Extract and send any assistant responses from transcript
33
+ # This captures assistant text written BEFORE the tool permission request
34
+ # ============================================================
35
+
36
+ # Track sent message UUIDs to avoid duplicates across multiple PermissionRequest calls
37
+ SENT_UUIDS_FILE="${CODEVIBE_TMPDIR}/codevibe-claude-sent-uuids-${SESSION_ID}.txt"
38
+
39
+ if [ -n "$TRANSCRIPT_PATH" ] && [ -f "$TRANSCRIPT_PATH" ]; then
40
+ log "DEBUG" "Reading transcript for assistant responses: $TRANSCRIPT_PATH"
41
+
42
+ # Get the last user prompt UUID to know where assistant messages start
43
+ LAST_USER_UUID=$(grep '"type":"user"' "$TRANSCRIPT_PATH" | tail -1 | jq -r '.uuid // empty')
44
+
45
+ if [ -n "$LAST_USER_UUID" ]; then
46
+ log "DEBUG" "Last user prompt UUID: $LAST_USER_UUID"
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')
93
+
94
+ if [ -z "$MESSAGE_CONTENT" ] || [ "$MESSAGE_CONTENT" = "null" ]; then
95
+ continue
96
+ fi
97
+
98
+ # Extract text content from assistant message
99
+ TEXT_CONTENT=$(echo "$line" | jq -r '.message.content[] | select(.type == "text") | .text // empty' | tr '\n' ' ')
100
+
101
+ if [ -n "$TEXT_CONTENT" ] && [ "$TEXT_CONTENT" != "null" ]; then
102
+ # Create ASSISTANT_RESPONSE event
103
+ ASSISTANT_PAYLOAD=$(jq -n \
104
+ --arg session_id "$SESSION_ID" \
105
+ --arg content "$TEXT_CONTENT" \
106
+ --arg hook_event_name "PermissionRequest" \
107
+ --arg type "ASSISTANT_RESPONSE" \
108
+ '{
109
+ session_id: $session_id,
110
+ hook_event_name: $hook_event_name,
111
+ type: $type,
112
+ content: $content
113
+ }')
114
+
115
+ log "DEBUG" "Sending assistant response from PermissionRequest: ${TEXT_CONTENT:0:100}..."
116
+
117
+ send_to_mcp "event" "$ASSISTANT_PAYLOAD" "$SESSION_ID"
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
126
+ fi
127
+ done < <(grep '"type":"assistant"' "$TRANSCRIPT_PATH")
128
+ fi
129
+ else
130
+ log "WARN" "Transcript path not found or file doesn't exist: $TRANSCRIPT_PATH"
131
+ fi
132
+
133
+ # ============================================================
134
+ # SECOND: Send the INTERACTIVE_PROMPT event for the tool permission
135
+ # ============================================================
136
+
137
+ # Create question text based on tool type
138
+ # Include file path for file operations, command for Bash, etc.
139
+ QUESTION_TEXT=""
140
+ if [ "$TOOL_NAME" = "Edit" ]; then
141
+ QUESTION_TEXT="Do you want to allow Edit operation on ${FILE_PATH}?"
142
+ elif [ "$TOOL_NAME" = "Write" ]; then
143
+ QUESTION_TEXT="Do you want to allow Write operation to ${FILE_PATH}?"
144
+ elif [ "$TOOL_NAME" = "Read" ]; then
145
+ QUESTION_TEXT="Do you want to allow Read operation on ${FILE_PATH}?"
146
+ elif [ "$TOOL_NAME" = "Bash" ]; then
147
+ # Keep it simple - detailed command visible via "Tap to see details"
148
+ QUESTION_TEXT="Do you want to allow Bash operation?"
149
+ elif [ -n "$TOOL_NAME" ] && [ -n "$FILE_PATH" ]; then
150
+ QUESTION_TEXT="Do you want to allow ${TOOL_NAME} operation on ${FILE_PATH}?"
151
+ elif [ -n "$TOOL_NAME" ]; then
152
+ QUESTION_TEXT="Do you want to allow ${TOOL_NAME} operation?"
153
+ else
154
+ QUESTION_TEXT="Do you want to allow this operation?"
155
+ fi
156
+
157
+ log "DEBUG" "Creating INTERACTIVE_PROMPT for: $QUESTION_TEXT"
158
+
159
+ # Create INTERACTIVE_PROMPT event (options will be added dynamically by server via tmux parsing)
160
+ EVENT_PAYLOAD=$(jq -n \
161
+ --arg session_id "$SESSION_ID" \
162
+ --arg hook_event_name "PermissionRequest" \
163
+ --arg type "INTERACTIVE_PROMPT" \
164
+ --arg content "$QUESTION_TEXT" \
165
+ --arg tool_name "$TOOL_NAME" \
166
+ --argjson tool_input "$TOOL_INPUT" \
167
+ '{
168
+ session_id: $session_id,
169
+ hook_event_name: $hook_event_name,
170
+ type: $type,
171
+ content: $content,
172
+ metadata: {
173
+ tool_name: $tool_name,
174
+ tool_input: $tool_input
175
+ }
176
+ }')
177
+
178
+ log "DEBUG" "Event payload: $EVENT_PAYLOAD"
179
+
180
+ # Send INTERACTIVE_PROMPT event to MCP server (pass session_id for dynamic port lookup)
181
+ send_to_mcp "event" "$EVENT_PAYLOAD" "$SESSION_ID"
182
+
183
+ EXIT_CODE=$?
184
+
185
+ if [ $EXIT_CODE -eq 0 ]; then
186
+ log "INFO" "PermissionRequest INTERACTIVE_PROMPT event sent successfully"
187
+ else
188
+ log "ERROR" "Failed to send PermissionRequest event (exit code: $EXIT_CODE)"
189
+ fi
190
+
191
+ exit $EXIT_CODE
@@ -0,0 +1,42 @@
1
+ #!/bin/bash
2
+
3
+ # PostToolUse Hook
4
+ # Triggered after Claude uses a tool (Read, Edit, Write, etc.)
5
+ # Captures tool usage and file changes
6
+
7
+ # Source common utilities
8
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
9
+ source "$SCRIPT_DIR/common.sh"
10
+
11
+ log "INFO" "PostToolUse hook triggered"
12
+
13
+ # Read JSON input from stdin
14
+ INPUT=$(read_json_input)
15
+
16
+ log "DEBUG" "Input: $INPUT"
17
+
18
+ # Extract tool name to check if it's a file operation
19
+ TOOL_NAME=$(echo "$INPUT" | grep -o '"tool_name"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/"tool_name"[[:space:]]*:[[:space:]]*"\([^"]*\)"/\1/')
20
+
21
+ log "DEBUG" "Tool name: $TOOL_NAME"
22
+
23
+ # Extract session ID for dynamic port lookup
24
+ SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // empty')
25
+
26
+ # Send tool use event to MCP server
27
+ send_to_mcp "event" "$INPUT" "$SESSION_ID"
28
+ EVENT_EXIT_CODE=$?
29
+
30
+ if [ $EVENT_EXIT_CODE -eq 0 ]; then
31
+ log "INFO" "PostToolUse event sent successfully"
32
+ else
33
+ log "ERROR" "Failed to send PostToolUse event (exit code: $EVENT_EXIT_CODE)"
34
+ fi
35
+
36
+ # Note: File operations (Read, Edit, Write) are already captured in the TOOL_USE event above
37
+ # The tool_input and tool_response contain all necessary information including file paths and diffs
38
+ # iOS app can filter TOOL_USE events to display file changes
39
+
40
+ log "DEBUG" "PostToolUse event processing completed"
41
+
42
+ exit $EVENT_EXIT_CODE
@@ -0,0 +1,57 @@
1
+ #!/bin/bash
2
+
3
+ # SessionEnd Hook
4
+ # Triggered when a Claude Code session ends
5
+ # Cleans up sync session and unsubscribes from mobile events
6
+
7
+ # Source common utilities
8
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
9
+ source "$SCRIPT_DIR/common.sh"
10
+
11
+ log "INFO" "SessionEnd 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" "SessionEnd event sent successfully"
28
+
29
+ # Clean up session-specific files
30
+ if [ -n "$SESSION_ID" ]; then
31
+ PID_FILE="${CODEVIBE_TMPDIR}/codevibe-claude-${SESSION_ID}.pid"
32
+ PORT_FILE="${CODEVIBE_TMPDIR}/codevibe-claude-${SESSION_ID}.port"
33
+
34
+ # Kill the MCP server for this session
35
+ if [ -f "$PID_FILE" ]; then
36
+ PID=$(cat "$PID_FILE")
37
+ if ps -p "$PID" > /dev/null 2>&1; then
38
+ log "INFO" "Stopping MCP server for session $SESSION_ID (PID: $PID)"
39
+ kill "$PID" 2>/dev/null || true
40
+ fi
41
+ rm -f "$PID_FILE"
42
+ fi
43
+
44
+ # Remove port file (should also be cleaned up by server, but just in case)
45
+ rm -f "$PORT_FILE"
46
+
47
+ # Remove sent UUIDs tracking file
48
+ SENT_UUIDS_FILE="${CODEVIBE_TMPDIR}/codevibe-claude-sent-uuids-${SESSION_ID}.txt"
49
+ rm -f "$SENT_UUIDS_FILE"
50
+
51
+ log "INFO" "Session cleanup completed for $SESSION_ID"
52
+ fi
53
+ else
54
+ log "ERROR" "Failed to send SessionEnd event (exit code: $EXIT_CODE)"
55
+ fi
56
+
57
+ exit $EXIT_CODE
@@ -0,0 +1,127 @@
1
+ #!/bin/bash
2
+
3
+ # SessionStart Hook
4
+ # Triggered when a Claude Code session starts or resumes
5
+ # Initializes sync session and subscribes to mobile events
6
+
7
+ # Source common utilities
8
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
9
+ source "$SCRIPT_DIR/common.sh"
10
+
11
+ log "INFO" "SessionStart hook triggered"
12
+
13
+ # Read JSON input from stdin first so we can get the session ID
14
+ INPUT=$(read_json_input)
15
+ log "DEBUG" "Input: $INPUT"
16
+
17
+ # Extract session ID from input
18
+ SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // empty')
19
+ if [ -z "$SESSION_ID" ]; then
20
+ log "ERROR" "No session_id in input"
21
+ exit 1
22
+ fi
23
+
24
+ log "INFO" "Session ID: $SESSION_ID"
25
+
26
+ # Find the plugin root directory (parent of hooks directory)
27
+ PLUGIN_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
28
+
29
+ # Mapping file tracks which MCP server is running for this Claude Code instance (tmux session)
30
+ TMUX_SESSION="${CODEVIBE_TMUX_SESSION:-}"
31
+ if [ -n "$TMUX_SESSION" ]; then
32
+ MAPPING_FILE="${CODEVIBE_TMPDIR}/codevibe-claude-instance-${TMUX_SESSION}.json"
33
+ else
34
+ # Fallback: use session-specific files if no tmux session
35
+ MAPPING_FILE=""
36
+ fi
37
+
38
+ # Check if there's an MCP server running for this Claude Code instance
39
+ EXISTING_SERVER_PORT=""
40
+ EXISTING_SERVER_PID=""
41
+ if [ -n "$MAPPING_FILE" ] && [ -f "$MAPPING_FILE" ]; then
42
+ EXISTING_SERVER_PORT=$(jq -r '.port // empty' "$MAPPING_FILE" 2>/dev/null)
43
+ EXISTING_SERVER_PID=$(jq -r '.pid // empty' "$MAPPING_FILE" 2>/dev/null)
44
+ OLD_SESSION_ID=$(jq -r '.session // empty' "$MAPPING_FILE" 2>/dev/null)
45
+
46
+ if [ -n "$EXISTING_SERVER_PID" ] && ps -p "$EXISTING_SERVER_PID" > /dev/null 2>&1; then
47
+ log "INFO" "Found running MCP server for this instance (session: $OLD_SESSION_ID, port: $EXISTING_SERVER_PORT, pid: $EXISTING_SERVER_PID)"
48
+ else
49
+ log "INFO" "MCP server from mapping not running, will start new one"
50
+ EXISTING_SERVER_PORT=""
51
+ EXISTING_SERVER_PID=""
52
+ rm -f "$MAPPING_FILE"
53
+ fi
54
+ fi
55
+
56
+ # If existing server found, send event to it and let it handle session transition
57
+ if [ -n "$EXISTING_SERVER_PORT" ]; then
58
+ log "INFO" "Using existing MCP server on port $EXISTING_SERVER_PORT for session $SESSION_ID"
59
+ RESPONSE=$(curl -s -X POST "http://localhost:${EXISTING_SERVER_PORT}/event" \
60
+ -H "Content-Type: application/json" \
61
+ -d "$INPUT" 2>&1)
62
+ CURL_EXIT=$?
63
+ if [ $CURL_EXIT -eq 0 ]; then
64
+ log "INFO" "SessionStart event sent to existing server"
65
+ # Update mapping with new session ID
66
+ echo "{\"session\":\"$SESSION_ID\",\"port\":$EXISTING_SERVER_PORT,\"pid\":$EXISTING_SERVER_PID}" > "$MAPPING_FILE"
67
+ log "INFO" "Updated mapping for session $SESSION_ID"
68
+ exit 0
69
+ else
70
+ log "WARN" "Failed to send to existing server (exit: $CURL_EXIT), will start new one"
71
+ fi
72
+ fi
73
+
74
+ # No running server found, start a new one
75
+ PID_FILE="${CODEVIBE_TMPDIR}/codevibe-claude-${SESSION_ID}.pid"
76
+ PORT_FILE="${CODEVIBE_TMPDIR}/codevibe-claude-${SESSION_ID}.port"
77
+
78
+ # Start the MCP server with the session ID
79
+ # Pass CODEVIBE_TMUX_SESSION so server can use tmux send-keys for mobile input
80
+ # Change to plugin directory so .env file can be found by dotenvx
81
+ log "INFO" "Starting MCP server for session $SESSION_ID..."
82
+ log "DEBUG" "CODEVIBE_TMUX_SESSION=${CODEVIBE_TMUX_SESSION:-not set}"
83
+ log "DEBUG" "ENVIRONMENT=${ENVIRONMENT:-development}"
84
+ log "DEBUG" "Starting server from directory: $PLUGIN_ROOT"
85
+ # Use exec to replace subshell with node process so $! captures correct PID
86
+ # Pass through ENVIRONMENT variable for correct Cognito configuration
87
+ (cd "$PLUGIN_ROOT" && exec env CODEVIBE_TMUX_SESSION="${CODEVIBE_TMUX_SESSION}" ENVIRONMENT="${ENVIRONMENT:-development}" node "$PLUGIN_ROOT/dist/server.js" "$SESSION_ID") >> "${CODEVIBE_TMPDIR}/codevibe-claude-mcp.log" 2>&1 &
88
+ SERVER_PID=$!
89
+ echo "$SERVER_PID" > "$PID_FILE"
90
+
91
+ # Wait for server to start and port file to be created
92
+ MAX_WAIT=10
93
+ WAITED=0
94
+ while [ ! -f "$PORT_FILE" ] && [ $WAITED -lt $MAX_WAIT ]; do
95
+ sleep 0.5
96
+ WAITED=$((WAITED + 1))
97
+ log "DEBUG" "Waiting for port file... ($WAITED)"
98
+ done
99
+
100
+ if [ -f "$PORT_FILE" ]; then
101
+ PORT=$(cat "$PORT_FILE")
102
+ log "INFO" "MCP server started (PID: $SERVER_PID, Port: $PORT)"
103
+ # Create mapping file for this Claude Code instance
104
+ if [ -n "$MAPPING_FILE" ]; then
105
+ echo "{\"session\":\"$SESSION_ID\",\"port\":$PORT,\"pid\":$SERVER_PID}" > "$MAPPING_FILE"
106
+ log "INFO" "Created mapping file: $MAPPING_FILE"
107
+ fi
108
+ else
109
+ log "WARN" "Port file not created after ${MAX_WAIT}s, server may have failed to start"
110
+ fi
111
+
112
+ # Send to MCP server
113
+ send_to_mcp "event" "$INPUT" "$SESSION_ID"
114
+
115
+ EXIT_CODE=$?
116
+
117
+ if [ $EXIT_CODE -eq 0 ]; then
118
+ log "INFO" "SessionStart event sent successfully"
119
+ else
120
+ # Don't fail the hook if auth is missing - just warn
121
+ # This allows users to run claude without logging in first
122
+ log "WARN" "Failed to send SessionStart event (exit code: $EXIT_CODE)"
123
+ log "WARN" "Run 'codevibe-claude login' to enable mobile sync"
124
+ fi
125
+
126
+ # Always exit 0 to avoid blocking Claude Code startup
127
+ exit 0