@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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codevibe-claude",
3
- "version": "1.0.9",
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 Mac screen is locked (via tmux)
10
- - **Interactive Prompts** - Answer permission dialogs with numbered options (1=Yes, 2=Yes for project, 3=Reject)
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** (darwin platform)
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 Mac screen is locked
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
- ## Mac Power Settings (for Locked Screen Support)
217
+ ## Power Settings (for Locked Screen Support)
218
218
 
219
- To ensure mobile prompts work when your Mac screen is locked:
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
- This keeps network connections alive during sleep.
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
 
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quantiya/codevibe-claude-plugin",
3
- "version": "1.0.9",
3
+ "version": "1.0.10",
4
4
  "description": "Mobile companion for Claude Code - monitor and control your Claude Code sessions from your phone with CodeVibe",
5
5
  "main": "dist/server.js",
6
6
  "bin": {