@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
|
@@ -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
|