@quantiya/codevibe-claude-plugin 1.0.25 → 1.0.26

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.25",
3
+ "version": "1.0.26",
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"
@@ -43,23 +43,118 @@ done
43
43
  SCRIPT_DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
44
44
  PLUGIN_DIR="$(dirname "$SCRIPT_DIR")"
45
45
 
46
+ # ─── Wrapper telemetry (GA4 Measurement Protocol) ─────────────────────
47
+ # Diagnoses agent CLI failures: pre-flight bailouts, fast-die patterns,
48
+ # whether SessionStart hook fired, exit code. Background curl, fail
49
+ # silently, no PII (hashed hostname + per-run random id only). Honors
50
+ # CODEVIBE_TELEMETRY_SOURCE=test for internal testing.
51
+ _CV_MID="G-GS74YEQTB8"
52
+ _CV_SEC="lAfOF6OxRzSQ-NsLBRjhAg"
53
+ _CV_CID="$(echo "$(uname -n)-$(id -u)" | (sha256sum 2>/dev/null || shasum -a 256 2>/dev/null || echo "anonymous-fallback ") | cut -c1-36)"
54
+ _CV_RUN_ID="$(head -c 16 /dev/urandom 2>/dev/null | od -An -tx1 | tr -d ' \n' | cut -c1-32)"
55
+ [ -z "$_CV_RUN_ID" ] && _CV_RUN_ID="fallback-$(date +%s)-$$"
56
+ _CV_AGENT="claude"
57
+ _CV_SOURCE="${CODEVIBE_TELEMETRY_SOURCE:-production}"
58
+ _CV_STARTED_AT="$(date +%s)"
59
+ _CV_EXITED="" # set by terminal events; suppresses trap double-fire
60
+ _CV_PLUGIN_VERSION="$(node -p "require('$PLUGIN_DIR/package.json').version" 2>/dev/null || echo unknown)"
61
+ _CV_MCP_LOG="${CODEVIBE_TMPDIR}/codevibe-claude-mcp.log"
62
+ _CV_MCP_LOG_BASELINE=0
63
+ if [ -f "$_CV_MCP_LOG" ]; then
64
+ _CV_MCP_LOG_BASELINE=$(wc -l < "$_CV_MCP_LOG" 2>/dev/null | tr -d ' ')
65
+ [ -z "$_CV_MCP_LOG_BASELINE" ] && _CV_MCP_LOG_BASELINE=0
66
+ fi
67
+ _CV_TMUX_STARTED="false"
68
+ _CV_AGENT_INVOKED="false"
69
+ _CV_AGENT_STARTED_AT=0
70
+ _CV_CLAUDE_EXIT_FILE="${CODEVIBE_TMPDIR}/codevibe-claude-exit-$$"
71
+
72
+ # Strip an arbitrary string down to a JSON-safe identifier alphabet.
73
+ # Removes anything that could break the hand-built JSON payload below
74
+ # (quotes, backslashes, ANSI escapes, control bytes, tabs, newlines).
75
+ # Truncates to 40 chars to bound the impact of pathological CLI version
76
+ # output. Caller is responsible for emptiness check after sanitize.
77
+ cv_sanitize() {
78
+ printf '%s' "$1" | LC_ALL=C tr -cd 'A-Za-z0-9._\- ' | cut -c1-40
79
+ }
80
+
81
+ # Sanitize trusted-but-still-string values that go into the payload
82
+ # (plugin version, source label) so future schema additions can't
83
+ # accidentally reintroduce a JSON-injection path.
84
+ _CV_PLUGIN_VERSION="$(cv_sanitize "$_CV_PLUGIN_VERSION")"
85
+ [ -z "$_CV_PLUGIN_VERSION" ] && _CV_PLUGIN_VERSION="unknown"
86
+ _CV_SOURCE="$(cv_sanitize "$_CV_SOURCE")"
87
+ [ -z "$_CV_SOURCE" ] && _CV_SOURCE="production"
88
+
89
+ cv_telem() {
90
+ local event="$1"; shift
91
+ local params="$*"
92
+ curl -s -X POST \
93
+ "https://www.google-analytics.com/mp/collect?measurement_id=${_CV_MID}&api_secret=${_CV_SEC}" \
94
+ -H "Content-Type: application/json" \
95
+ -d "{\"client_id\":\"${_CV_CID}\",\"events\":[{\"name\":\"${event}\",\"params\":{\"agent\":\"${_CV_AGENT}\",\"plugin_version\":\"${_CV_PLUGIN_VERSION}\",\"source\":\"${_CV_SOURCE}\",\"run_id\":\"${_CV_RUN_ID}\"${params:+,$params}}}]}" \
96
+ </dev/null >/dev/null 2>&1 &
97
+ }
98
+
99
+ cv_failed() {
100
+ [ -n "$_CV_EXITED" ] && return 0
101
+ _CV_EXITED="failed"
102
+ cv_telem "wrapper_failed" "\"reason\":\"$1\",\"lifetime_seconds\":$(( $(date +%s) - _CV_STARTED_AT ))"
103
+ }
104
+
46
105
  # Handle auth commands (login, logout, status, reset-device)
47
- # Delegate to codevibe-core CLI
106
+ # Delegate to codevibe-core CLI (shared auth across all plugins).
107
+ # Check both the local node_modules path AND the hoisted location used
108
+ # when the plugin is installed via the @quantiya/codevibe meta-package
109
+ # (parity with the codex/gemini wrappers).
48
110
  case "$1" in
49
111
  login|logout|status|reset-device)
50
- # Use codevibe-core CLI
51
112
  CORE_CLI="$PLUGIN_DIR/node_modules/@quantiya/codevibe-core/bin/codevibe.js"
113
+ if [ ! -f "$CORE_CLI" ]; then
114
+ CORE_CLI="$PLUGIN_DIR/../codevibe-core/bin/codevibe.js"
115
+ fi
52
116
  if [ -f "$CORE_CLI" ]; then
117
+ cv_telem "wrapper_started" "\"invocation\":\"auth_$1\",\"os\":\"$(uname -s | cv_sanitize)\",\"arch\":\"$(uname -m | cv_sanitize)\""
53
118
  exec node "$CORE_CLI" "$1"
54
119
  else
55
- echo "Error: codevibe-core not found. Run 'npm install' in the plugin directory first."
120
+ echo "Error: codevibe-core not found. Try reinstalling: npm install -g @quantiya/codevibe"
121
+ cv_failed "core_not_found"
122
+ sleep 1
56
123
  exit 1
57
124
  fi
58
125
  ;;
59
126
  esac
60
127
 
61
- # Check authentication before launching
128
+ # Capture environment facts for the session-flow wrapper_started event.
129
+ # Each probe is non-fatal — if a CLI is missing we record "missing" rather
130
+ # than aborting; pre-flight checks below still gate execution. Every
131
+ # string that lands in the JSON payload goes through cv_sanitize so an
132
+ # agent CLI emitting ANSI escapes or quotes in `--version` can't break
133
+ # the hand-built payload.
134
+ _CV_CLAUDE_VER="missing"
135
+ command -v claude >/dev/null 2>&1 && _CV_CLAUDE_VER="$(claude --version 2>/dev/null | cv_sanitize)"
136
+ [ -z "$_CV_CLAUDE_VER" ] && _CV_CLAUDE_VER="unknown"
137
+ _CV_NODE_VER="missing"
138
+ command -v node >/dev/null 2>&1 && _CV_NODE_VER="$(node -v 2>/dev/null | cv_sanitize)"
139
+ [ -z "$_CV_NODE_VER" ] && _CV_NODE_VER="unknown"
140
+ _CV_TMUX_VER="missing"
141
+ command -v tmux >/dev/null 2>&1 && _CV_TMUX_VER="$(tmux -V 2>/dev/null | cv_sanitize)"
142
+ [ -z "$_CV_TMUX_VER" ] && _CV_TMUX_VER="unknown"
143
+ _CV_OS_VER="$(uname -s | cv_sanitize)"
144
+ [ -z "$_CV_OS_VER" ] && _CV_OS_VER="unknown"
145
+ _CV_ARCH_VER="$(uname -m | cv_sanitize)"
146
+ [ -z "$_CV_ARCH_VER" ] && _CV_ARCH_VER="unknown"
147
+ _CV_CLAUDE_CREDS="false"; [ -f "$HOME/.claude/.credentials.json" ] && _CV_CLAUDE_CREDS="true"
148
+ _CV_INSIDE_TMUX="false"; [ -n "$TMUX" ] && _CV_INSIDE_TMUX="true"
149
+ _CV_IS_TTY="false"; { [ -t 0 ] && [ -t 1 ]; } && _CV_IS_TTY="true"
150
+ cv_telem "wrapper_started" "\"invocation\":\"session\",\"os\":\"$_CV_OS_VER\",\"arch\":\"$_CV_ARCH_VER\",\"claude_version\":\"$_CV_CLAUDE_VER\",\"node_version\":\"$_CV_NODE_VER\",\"tmux_version\":\"$_CV_TMUX_VER\",\"claude_credentials_present\":$_CV_CLAUDE_CREDS,\"inside_tmux\":$_CV_INSIDE_TMUX,\"is_terminal\":$_CV_IS_TTY"
151
+
152
+ # Check authentication before launching. Same dual-path lookup as the
153
+ # auth case above so the hoisted meta-package install location works.
62
154
  CORE_CLI="$PLUGIN_DIR/node_modules/@quantiya/codevibe-core/bin/codevibe.js"
155
+ if [ ! -f "$CORE_CLI" ]; then
156
+ CORE_CLI="$PLUGIN_DIR/../codevibe-core/bin/codevibe.js"
157
+ fi
63
158
  if [ -f "$CORE_CLI" ]; then
64
159
  AUTH_STATUS=$(node "$CORE_CLI" status 2>/dev/null)
65
160
  if echo "$AUTH_STATUS" | grep -q "Not authenticated"; then
@@ -67,6 +162,8 @@ if [ -f "$CORE_CLI" ]; then
67
162
  echo "⚠️ CodeVibe: Not authenticated."
68
163
  echo " Run 'codevibe-claude login' to sign in first."
69
164
  echo ""
165
+ cv_failed "codevibe_not_authenticated"
166
+ sleep 1
70
167
  exit 1
71
168
  fi
72
169
  fi
@@ -83,26 +180,126 @@ log() {
83
180
  if ! command -v tmux &> /dev/null; then
84
181
  echo "Error: tmux is required but not installed."
85
182
  echo "Install with: brew install tmux"
183
+ cv_failed "tmux_missing"
184
+ sleep 1
185
+ exit 1
186
+ fi
187
+
188
+ # Check if claude CLI is installed.
189
+ # The meta-package wrapper (codevibe/bin/codevibe-claude) does this same
190
+ # check at a higher layer, but a user invoking the plugin wrapper
191
+ # directly (e.g., when developing locally) will hit this fallback. Without
192
+ # it, missing claude becomes an inner-tmux exit-127 which leaves the
193
+ # diagnostic ambiguity that wrapper telemetry is meant to remove.
194
+ if ! command -v claude &> /dev/null; then
195
+ echo "Error: claude CLI is not installed."
196
+ echo "Install Claude Code from https://docs.anthropic.com/claude/docs/claude-code"
197
+ cv_failed "claude_missing"
198
+ sleep 1
86
199
  exit 1
87
200
  fi
88
201
 
202
+ # Check if the plugin's MCP server bundle is built. The daemon is
203
+ # launched by Claude Code's SessionStart hook, not by this wrapper, but
204
+ # if dist/server.js is missing the hook will silently fail and "no
205
+ # SessionStart hook fired" will be misattributed to claude itself.
206
+ if [ ! -f "$PLUGIN_DIR/dist/server.js" ]; then
207
+ echo "Error: MCP server bundle not built (missing $PLUGIN_DIR/dist/server.js)."
208
+ echo "Run 'npm run build' in the plugin directory, or reinstall:"
209
+ echo " npm install -g @quantiya/codevibe"
210
+ cv_failed "server_not_built"
211
+ sleep 1
212
+ exit 1
213
+ fi
214
+
215
+ # Cleanup function for telemetry — Claude has no server lifecycle in
216
+ # the wrapper (daemon is managed by Claude Code's SessionStart hook),
217
+ # so this trap exists purely to fire wrapper_exited on EXIT and to
218
+ # remove the per-run exit-code file.
219
+ cleanup() {
220
+ local wrapper_exit_code=$?
221
+
222
+ if [ -z "$_CV_EXITED" ]; then
223
+ _CV_EXITED="exited"
224
+ local claude_exit="unknown"
225
+ if [ -f "$_CV_CLAUDE_EXIT_FILE" ]; then
226
+ claude_exit="$(cat "$_CV_CLAUDE_EXIT_FILE" 2>/dev/null | head -c 10 | tr -d '\n\r ')"
227
+ [ -z "$claude_exit" ] && claude_exit="unknown"
228
+ fi
229
+ local lifetime=$(( $(date +%s) - _CV_STARTED_AT ))
230
+ local claude_lifetime=0
231
+ if [ "$_CV_AGENT_STARTED_AT" -gt 0 ] 2>/dev/null; then
232
+ claude_lifetime=$(( $(date +%s) - _CV_AGENT_STARTED_AT ))
233
+ fi
234
+ local hook_fired="false"
235
+ if [ -f "$_CV_MCP_LOG" ]; then
236
+ if tail -n "+$((_CV_MCP_LOG_BASELINE + 1))" "$_CV_MCP_LOG" 2>/dev/null \
237
+ | grep -q "SessionStart" 2>/dev/null; then
238
+ hook_fired="true"
239
+ fi
240
+ fi
241
+ # Outcome priority: SIGINT/SIGTERM beats everything (user intent).
242
+ # Then "we never got far enough to invoke claude" — distinct from
243
+ # "we invoked claude via passthrough but never started a tmux of
244
+ # our own" (the latter is a normal direct-run, not an abort).
245
+ local outcome
246
+ if [ "$wrapper_exit_code" = "130" ] || [ "$wrapper_exit_code" = "143" ]; then
247
+ outcome="interrupted"
248
+ elif [ "$_CV_AGENT_INVOKED" = "false" ]; then
249
+ outcome="pre_invoke_abort"
250
+ elif [ "$claude_exit" != "unknown" ] && [ "$claude_exit" != "0" ]; then
251
+ outcome="error_exit"
252
+ elif [ "$claude_lifetime" -lt 5 ] 2>/dev/null; then
253
+ outcome="early_exit"
254
+ elif [ "$claude_lifetime" -lt 60 ] 2>/dev/null; then
255
+ outcome="clean_short"
256
+ else
257
+ outcome="clean_long"
258
+ fi
259
+ cv_telem "wrapper_exited" "\"exit_code\":$wrapper_exit_code,\"lifetime_seconds\":$lifetime,\"claude_exit_code\":\"$claude_exit\",\"claude_lifetime_seconds\":$claude_lifetime,\"tmux_session_started\":$_CV_TMUX_STARTED,\"agent_invoked\":$_CV_AGENT_INVOKED,\"session_start_hook_fired\":$hook_fired,\"terminal_outcome\":\"$outcome\""
260
+ fi
261
+
262
+ rm -f "$_CV_CLAUDE_EXIT_FILE"
263
+ }
264
+
265
+ trap cleanup EXIT INT TERM
266
+
89
267
  # Generate a unique session name
90
268
  SESSION_NAME="${TMUX_SESSION_PREFIX}-$$"
91
269
 
92
270
  log "Starting codevibe-claude with session: $SESSION_NAME"
93
271
  log "Arguments: $*"
94
272
 
95
- # Check if we're already inside tmux
273
+ # Check if we're already inside tmux.
274
+ # We deliberately do NOT `exec` here — running claude as a child process
275
+ # lets the EXIT trap fire after it returns so wrapper_exited still gets
276
+ # emitted on these direct-run paths. Behaviorally identical for the user
277
+ # (claude remains the foreground process for the duration).
96
278
  if [ -n "$TMUX" ]; then
97
279
  log "Already inside tmux, running claude directly"
98
- # Already in tmux, just run claude
99
- exec claude "$@"
280
+ _CV_AGENT_INVOKED="true"
281
+ _CV_AGENT_STARTED_AT="$(date +%s)"
282
+ # `|| _CV_RC=$?` is load-bearing: with `set -e`, a non-zero exit
283
+ # from claude would abort the wrapper before we capture the exit
284
+ # code, leaving wrapper_exited with claude_exit_code="unknown". The
285
+ # `||` form catches non-zero without triggering set -e, while exit
286
+ # 0 leaves _CV_RC at its 0 default. printf's `|| true` keeps a
287
+ # disk-full failure from clobbering diagnostics.
288
+ _CV_RC=0
289
+ claude "$@" || _CV_RC=$?
290
+ printf '%s' "$_CV_RC" > "$_CV_CLAUDE_EXIT_FILE" 2>/dev/null || true
291
+ exit "$_CV_RC"
100
292
  fi
101
293
 
102
- # Check if running in a terminal
294
+ # Check if running in a terminal — same direct-run treatment as above.
103
295
  if [ ! -t 0 ] || [ ! -t 1 ]; then
104
296
  log "Not running in a terminal, running claude directly"
105
- exec claude "$@"
297
+ _CV_AGENT_INVOKED="true"
298
+ _CV_AGENT_STARTED_AT="$(date +%s)"
299
+ _CV_RC=0
300
+ claude "$@" || _CV_RC=$?
301
+ printf '%s' "$_CV_RC" > "$_CV_CLAUDE_EXIT_FILE" 2>/dev/null || true
302
+ exit "$_CV_RC"
106
303
  fi
107
304
 
108
305
  # Create tmux session and run claude
@@ -125,7 +322,10 @@ done
125
322
  # 3. Exits the tmux session when claude exits
126
323
 
127
324
  tmux new-session -d -s "$SESSION_NAME" -x "$(tput cols)" -y "$(tput lines)" \
128
- "export CODEVIBE_TMUX_SESSION='$SESSION_NAME'; export ENVIRONMENT='$ENVIRONMENT'; $CLAUDE_CMD; exit"
325
+ "export CODEVIBE_TMUX_SESSION='$SESSION_NAME'; export ENVIRONMENT='$ENVIRONMENT'; $CLAUDE_CMD; printf '%s' \"\$?\" > '$_CV_CLAUDE_EXIT_FILE'; exit"
326
+ _CV_TMUX_STARTED="true"
327
+ _CV_AGENT_INVOKED="true"
328
+ _CV_AGENT_STARTED_AT="$(date +%s)"
129
329
 
130
330
  # Enable mouse support for scrolling
131
331
  tmux set-option -t "$SESSION_NAME" -g mouse on
@@ -153,6 +353,9 @@ echo "$SESSION_NAME" > "${CODEVIBE_TMPDIR}/codevibe-claude-tmux-session-$$"
153
353
 
154
354
  log "Attaching to tmux session: $SESSION_NAME"
155
355
 
156
- # Attach to the session
157
- # This will show claude's UI to the user
158
- exec tmux attach-session -t "$SESSION_NAME"
356
+ # Attach to the session.
357
+ # Note: we deliberately do NOT `exec` tmux here — letting bash continue
358
+ # means the EXIT trap fires after detach, which is how the wrapper_exited
359
+ # telemetry event gets recorded. The behavioral difference is invisible
360
+ # to the user (tmux attach is still the foreground command).
361
+ tmux attach-session -t "$SESSION_NAME"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quantiya/codevibe-claude-plugin",
3
- "version": "1.0.25",
3
+ "version": "1.0.26",
4
4
  "description": "Control Claude Code from your iPhone and Android — real-time sync, approve file edits, send prompts by voice. Part of CodeVibe.",
5
5
  "main": "dist/server.js",
6
6
  "bin": {