@quantiya/codevibe-claude-plugin 1.0.9 → 1.0.10
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/README.md +16 -8
- package/hooks/session-start.sh +186 -0
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codevibe-claude",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.10",
|
|
4
4
|
"description": "Sync Claude Code sessions with iOS mobile app via AWS backend. Control Claude Code from your phone with real-time bidirectional synchronization.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "CodeVibe Team"
|
package/README.md
CHANGED
|
@@ -6,8 +6,8 @@ Control Claude Code from your phone with real-time bidirectional synchronization
|
|
|
6
6
|
|
|
7
7
|
- **Real-time Sync** - See desktop conversations on mobile instantly (~100-500ms latency)
|
|
8
8
|
- **Mobile Control** - Send prompts from your phone that execute in the correct desktop session
|
|
9
|
-
- **Locked Screen Support** - Works even when your
|
|
10
|
-
- **Interactive Prompts** - Answer permission dialogs with numbered options (
|
|
9
|
+
- **Locked Screen Support** - Works even when your screen is locked (via tmux)
|
|
10
|
+
- **Interactive Prompts** - Answer permission dialogs with the same numbered options Claude Code shows in the terminal. Options are parsed dynamically from the live tmux pane at prompt time (Edit prompts typically show 3 options, Bash prompts show 2, and custom tools may offer more). Reply by typing or voice-inputting the option number on your phone — no dedicated approve/reject buttons.
|
|
11
11
|
- **Voice Input** - Dictate prompts using iOS speech-to-text
|
|
12
12
|
- **Image Attachments** - Send screenshots and photos with your messages
|
|
13
13
|
- **Markdown Rendering** - Code blocks and formatting displayed beautifully
|
|
@@ -18,9 +18,9 @@ Control Claude Code from your phone with real-time bidirectional synchronization
|
|
|
18
18
|
|
|
19
19
|
## Prerequisites
|
|
20
20
|
|
|
21
|
-
- **macOS** (
|
|
21
|
+
- **macOS, Linux, or WSL Ubuntu** (WSL users: IPv6/browser-launching fixes are built into codevibe-core 1.0.3+)
|
|
22
22
|
- **Node.js** 18.0.0 or higher
|
|
23
|
-
- **tmux** (for locked screen support): `brew install tmux`
|
|
23
|
+
- **tmux** (for locked screen support and mobile input): `brew install tmux` on macOS, `apt install tmux` on Linux/WSL
|
|
24
24
|
- **Claude Code** with plugin system enabled
|
|
25
25
|
|
|
26
26
|
## Installation
|
|
@@ -75,7 +75,7 @@ codevibe-claude
|
|
|
75
75
|
|
|
76
76
|
This wrapper:
|
|
77
77
|
- Creates a tmux session for the Claude Code instance
|
|
78
|
-
- Enables mobile prompts to work even when your
|
|
78
|
+
- Enables mobile prompts to work even when your screen is locked
|
|
79
79
|
- Supports multiple concurrent Claude Code sessions
|
|
80
80
|
|
|
81
81
|
**Note:** Running `claude` directly without the wrapper will prevent mobile prompts from working when your screen is locked.
|
|
@@ -214,9 +214,11 @@ Not just `claude`.
|
|
|
214
214
|
pkill -f "node dist/server.js"
|
|
215
215
|
```
|
|
216
216
|
|
|
217
|
-
##
|
|
217
|
+
## Power Settings (for Locked Screen Support)
|
|
218
218
|
|
|
219
|
-
|
|
219
|
+
For mobile prompts to keep working when your screen is locked, your computer needs to stay awake and its network connections need to survive sleep transitions.
|
|
220
|
+
|
|
221
|
+
### macOS
|
|
220
222
|
|
|
221
223
|
1. **System Settings** -> **Battery** (or **Energy**) -> Set "Computer sleep" to **Never** (when on power)
|
|
222
224
|
2. In Terminal:
|
|
@@ -225,7 +227,13 @@ To ensure mobile prompts work when your Mac screen is locked:
|
|
|
225
227
|
sudo pmset -a womp 1
|
|
226
228
|
```
|
|
227
229
|
|
|
228
|
-
|
|
230
|
+
### Linux / WSL Ubuntu
|
|
231
|
+
|
|
232
|
+
- Use your desktop environment's power settings to disable sleep on AC power (GNOME: Settings → Power; KDE: System Settings → Power Management)
|
|
233
|
+
- Or from the command line: `systemd-inhibit --what=sleep:idle sleep infinity &` as a no-sleep placeholder
|
|
234
|
+
- On WSL Ubuntu, the Windows host governs sleep. Configure Windows power settings to prevent the host from sleeping, and the WSL instance will remain active.
|
|
235
|
+
|
|
236
|
+
This keeps network connections alive during sleep so mobile messages can still reach the plugin.
|
|
229
237
|
|
|
230
238
|
## Architecture
|
|
231
239
|
|
package/hooks/session-start.sh
CHANGED
|
@@ -35,6 +35,192 @@ else
|
|
|
35
35
|
MAPPING_FILE=""
|
|
36
36
|
fi
|
|
37
37
|
|
|
38
|
+
# ─── Orphan daemon sweep ───────────────────────────────────────────────────
|
|
39
|
+
#
|
|
40
|
+
# When Claude Code terminates abnormally (crash, force-kill, window closed
|
|
41
|
+
# without /exit, network drop), the SessionEnd hook doesn't fire and the
|
|
42
|
+
# background daemon is never sent SIGTERM. Because the daemon is a PPID=1
|
|
43
|
+
# process (detached from the hook that launched it), nothing in the OS
|
|
44
|
+
# cleans it up — it keeps holding its AppSync subscription, sending
|
|
45
|
+
# heartbeats, and showing the session as "desktop connected" on iOS forever.
|
|
46
|
+
#
|
|
47
|
+
# This sweep runs on every SessionStart and cleans up orphan daemons whose
|
|
48
|
+
# tmux session no longer exists. Daemons belonging to live tmux sessions
|
|
49
|
+
# (including our own) are left alone.
|
|
50
|
+
#
|
|
51
|
+
# Scope: the sweep only considers daemons that have an instance mapping file
|
|
52
|
+
# (i.e. were launched under a tmux session). Daemons launched without tmux
|
|
53
|
+
# have no mapping and cannot be safely identified as orphans, so we leave
|
|
54
|
+
# them alone.
|
|
55
|
+
#
|
|
56
|
+
# Safety guards:
|
|
57
|
+
#
|
|
58
|
+
# (A) PID reuse: before killing a PID, verify via `ps -o command=` that the
|
|
59
|
+
# process is actually a node + codevibe-claude + server.js invocation.
|
|
60
|
+
# PIDs are recyclable (macOS cycles through ~32K), so a stale mapping
|
|
61
|
+
# from weeks ago could end up pointing at an unrelated user process.
|
|
62
|
+
# Without identity verification, we'd kill random things.
|
|
63
|
+
#
|
|
64
|
+
# (B) tmux unavailable: if the `tmux` binary is missing or fails for a reason
|
|
65
|
+
# other than "session doesn't exist", we conservatively treat the session
|
|
66
|
+
# as alive and leave the daemon alone. This prevents a degraded-tmux
|
|
67
|
+
# environment from causing a sweep-induced mass kill.
|
|
68
|
+
|
|
69
|
+
# Check whether a PID is still identifiable as a codevibe-claude daemon.
|
|
70
|
+
# Returns 0 if yes, non-zero otherwise.
|
|
71
|
+
is_codevibe_daemon() {
|
|
72
|
+
local pid="$1"
|
|
73
|
+
local cmd
|
|
74
|
+
cmd=$(ps -o command= -p "$pid" 2>/dev/null)
|
|
75
|
+
# Pattern: "node" + "codevibe-claude" + "server.js" somewhere in the command line.
|
|
76
|
+
# Matches both the cache path (...codevibe-marketplace/codevibe-claude/X.Y.Z/dist/server.js)
|
|
77
|
+
# and the dev path (...codevibe-claude-plugin/dist/server.js).
|
|
78
|
+
case "$cmd" in
|
|
79
|
+
*node*codevibe-claude*server.js*) return 0 ;;
|
|
80
|
+
*) return 1 ;;
|
|
81
|
+
esac
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
# Return 0 if the tmux session is alive OR we can't tell.
|
|
85
|
+
# Return non-zero ONLY when we can prove the session is gone: tmux binary exists,
|
|
86
|
+
# tmux server is reachable, and the server says this specific session doesn't exist.
|
|
87
|
+
# Every other failure mode (no binary, server down, socket error, permission issue)
|
|
88
|
+
# returns 0 — conservative, we'd rather leak a daemon than kill the wrong thing.
|
|
89
|
+
tmux_session_alive() {
|
|
90
|
+
local tmux_name="$1"
|
|
91
|
+
if ! command -v tmux >/dev/null 2>&1; then
|
|
92
|
+
return 0 # no tmux binary → can't verify → assume alive
|
|
93
|
+
fi
|
|
94
|
+
# Verify the tmux server itself is reachable before trusting has-session.
|
|
95
|
+
# `tmux ls` exits 0 when the server is running and has at least one session.
|
|
96
|
+
# If it fails (server not running, socket error, no sessions at all), we can't
|
|
97
|
+
# reliably distinguish "this session is gone" from "tmux is broken." Conservative
|
|
98
|
+
# choice: assume the session might still be alive (or recoverable when the server
|
|
99
|
+
# comes back).
|
|
100
|
+
if ! tmux ls >/dev/null 2>&1; then
|
|
101
|
+
return 0 # server unreachable or empty → can't verify → assume alive
|
|
102
|
+
fi
|
|
103
|
+
# Server is confirmed reachable and has sessions. has-session non-zero now
|
|
104
|
+
# reliably means "this specific session does not exist."
|
|
105
|
+
tmux has-session -t "$tmux_name" 2>/dev/null
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
sweep_orphan_daemons() {
|
|
109
|
+
local swept=0
|
|
110
|
+
local kept=0
|
|
111
|
+
local cleaned_stale=0
|
|
112
|
+
|
|
113
|
+
# Pass 1: walk mapping files, which are the source of truth for (tmux_session, pid) pairs
|
|
114
|
+
shopt -s nullglob
|
|
115
|
+
local map_file
|
|
116
|
+
for map_file in "${CODEVIBE_TMPDIR}"/codevibe-claude-instance-*.json; do
|
|
117
|
+
# Extract tmux session name from filename
|
|
118
|
+
local orphan_tmux
|
|
119
|
+
orphan_tmux=$(basename "$map_file" .json | sed 's/^codevibe-claude-instance-//')
|
|
120
|
+
|
|
121
|
+
# Skip our own tmux session — we handle it in the existing reuse path below
|
|
122
|
+
if [ -n "$TMUX_SESSION" ] && [ "$orphan_tmux" = "$TMUX_SESSION" ]; then
|
|
123
|
+
continue
|
|
124
|
+
fi
|
|
125
|
+
|
|
126
|
+
local orphan_pid
|
|
127
|
+
orphan_pid=$(jq -r '.pid // empty' "$map_file" 2>/dev/null)
|
|
128
|
+
|
|
129
|
+
# Empty or missing PID in mapping — clean up the mapping file
|
|
130
|
+
if [ -z "$orphan_pid" ]; then
|
|
131
|
+
log "INFO" "Orphan sweep: removing mapping with no PID: $map_file"
|
|
132
|
+
rm -f "$map_file"
|
|
133
|
+
cleaned_stale=$((cleaned_stale + 1))
|
|
134
|
+
continue
|
|
135
|
+
fi
|
|
136
|
+
|
|
137
|
+
# PID is no longer running — clean up the stale mapping.
|
|
138
|
+
# NOTE: do NOT touch the pidfile or portfile by session UUID here. If /resume
|
|
139
|
+
# or a UUID collision caused a stale mapping to reference the same session
|
|
140
|
+
# UUID as a currently-live pidfile, we'd delete the live one. Pass 2 below
|
|
141
|
+
# handles pidfile cleanup by verifying each pidfile's content independently.
|
|
142
|
+
if ! ps -p "$orphan_pid" > /dev/null 2>&1; then
|
|
143
|
+
log "INFO" "Orphan sweep: removing stale mapping for dead PID $orphan_pid (tmux $orphan_tmux)"
|
|
144
|
+
rm -f "$map_file"
|
|
145
|
+
cleaned_stale=$((cleaned_stale + 1))
|
|
146
|
+
continue
|
|
147
|
+
fi
|
|
148
|
+
|
|
149
|
+
# PID is alive. Is its tmux session still alive (or unknowable)?
|
|
150
|
+
if tmux_session_alive "$orphan_tmux"; then
|
|
151
|
+
# Live other session — or tmux is unavailable and we can't tell. Either
|
|
152
|
+
# way, the conservative choice is to leave the daemon alone.
|
|
153
|
+
log "DEBUG" "Orphan sweep: tmux $orphan_tmux is alive (or tmux unavailable), leaving daemon $orphan_pid"
|
|
154
|
+
kept=$((kept + 1))
|
|
155
|
+
continue
|
|
156
|
+
fi
|
|
157
|
+
|
|
158
|
+
# tmux is gone but the PID is alive — looks like an orphan. But before
|
|
159
|
+
# killing, verify the PID actually belongs to a codevibe-claude daemon.
|
|
160
|
+
# If PID reuse has handed this PID to an unrelated process, we must not
|
|
161
|
+
# touch it. Just clean up the stale mapping and move on.
|
|
162
|
+
if ! is_codevibe_daemon "$orphan_pid"; then
|
|
163
|
+
log "INFO" "Orphan sweep: PID $orphan_pid is alive but is not a CodeVibe daemon (likely PID reused), removing stale mapping only"
|
|
164
|
+
rm -f "$map_file"
|
|
165
|
+
cleaned_stale=$((cleaned_stale + 1))
|
|
166
|
+
continue
|
|
167
|
+
fi
|
|
168
|
+
|
|
169
|
+
# Verified: this is our daemon and its tmux is gone. Kill it.
|
|
170
|
+
log "WARN" "Orphan sweep: killing orphan daemon PID $orphan_pid (tmux $orphan_tmux is gone)"
|
|
171
|
+
|
|
172
|
+
# SIGTERM first — lets the daemon run its graceful shutdown (mark session INACTIVE, etc.)
|
|
173
|
+
kill "$orphan_pid" 2>/dev/null || true
|
|
174
|
+
|
|
175
|
+
# Give it a moment to exit cleanly, then SIGKILL if it's still alive
|
|
176
|
+
sleep 0.5
|
|
177
|
+
if ps -p "$orphan_pid" > /dev/null 2>&1; then
|
|
178
|
+
log "WARN" "Orphan sweep: daemon $orphan_pid did not exit from SIGTERM, sending SIGKILL"
|
|
179
|
+
kill -9 "$orphan_pid" 2>/dev/null || true
|
|
180
|
+
fi
|
|
181
|
+
|
|
182
|
+
# Remove the mapping file. Do NOT touch pidfile/portfile by session UUID —
|
|
183
|
+
# Pass 2 below will pick up the now-dead PID and clean the pidfile (and its
|
|
184
|
+
# matching portfile) safely, based on PID content, not mapping contents.
|
|
185
|
+
rm -f "$map_file"
|
|
186
|
+
swept=$((swept + 1))
|
|
187
|
+
done
|
|
188
|
+
|
|
189
|
+
# Pass 2: defensive cleanup of stale pidfiles that have no mapping and are dead
|
|
190
|
+
# (e.g. daemons that died before writing a mapping file, or legacy non-tmux launches)
|
|
191
|
+
local pid_file
|
|
192
|
+
for pid_file in "${CODEVIBE_TMPDIR}"/codevibe-claude-*.pid; do
|
|
193
|
+
# Skip the instance-mapping files' PID files (they have 'instance' in the name and are .json not .pid,
|
|
194
|
+
# but be explicit in case the glob matches something unexpected)
|
|
195
|
+
local base
|
|
196
|
+
base=$(basename "$pid_file")
|
|
197
|
+
case "$base" in
|
|
198
|
+
codevibe-claude-instance-*) continue ;;
|
|
199
|
+
esac
|
|
200
|
+
|
|
201
|
+
local pid
|
|
202
|
+
pid=$(cat "$pid_file" 2>/dev/null)
|
|
203
|
+
if [ -z "$pid" ] || ! ps -p "$pid" > /dev/null 2>&1; then
|
|
204
|
+
log "INFO" "Orphan sweep: removing stale pidfile $pid_file (PID ${pid:-empty} not alive)"
|
|
205
|
+
rm -f "$pid_file"
|
|
206
|
+
# Also clean up the corresponding portfile
|
|
207
|
+
local port_file="${pid_file%.pid}.port"
|
|
208
|
+
rm -f "$port_file"
|
|
209
|
+
cleaned_stale=$((cleaned_stale + 1))
|
|
210
|
+
fi
|
|
211
|
+
done
|
|
212
|
+
shopt -u nullglob
|
|
213
|
+
|
|
214
|
+
if [ $swept -gt 0 ] || [ $cleaned_stale -gt 0 ]; then
|
|
215
|
+
log "INFO" "Orphan sweep: killed $swept orphan daemon(s), cleaned $cleaned_stale stale file(s), kept $kept live session(s)"
|
|
216
|
+
else
|
|
217
|
+
log "DEBUG" "Orphan sweep: nothing to clean (kept $kept live session(s))"
|
|
218
|
+
fi
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
# Run the sweep before touching the mapping file for our own session
|
|
222
|
+
sweep_orphan_daemons || log "WARN" "Orphan sweep failed (non-fatal, continuing)"
|
|
223
|
+
|
|
38
224
|
# Check if there's an MCP server running for this Claude Code instance
|
|
39
225
|
EXISTING_SERVER_PORT=""
|
|
40
226
|
EXISTING_SERVER_PID=""
|
package/package.json
CHANGED