@quantiya/codevibe-claude-plugin 1.0.24 → 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.24",
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/dist/server.js CHANGED
@@ -1,6 +1,6 @@
1
- "use strict";var Y=Object.create;var P=Object.defineProperty;var z=Object.getOwnPropertyDescriptor;var W=Object.getOwnPropertyNames;var J=Object.getPrototypeOf,Q=Object.prototype.hasOwnProperty;var Z=(u,e)=>{for(var s in e)P(u,s,{get:e[s],enumerable:!0})},F=(u,e,s,t)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of W(e))!Q.call(u,i)&&i!==s&&P(u,i,{get:()=>e[i],enumerable:!(t=z(e,i))||t.enumerable});return u};var y=(u,e,s)=>(s=u!=null?Y(J(u)):{},F(e||!u||!u.__esModule?P(s,"default",{value:u,enumerable:!0}):s,u)),ee=u=>F(P({},"__esModule",{value:!0}),u);var se={};Z(se,{parseInteractivePromptInput:()=>B});module.exports=ee(se);var v=y(require("fs")),E=y(require("path")),T=y(require("os")),V=require("child_process");var O=y(require("os")),D=y(require("path")),N=require("@quantiya/codevibe-core"),n=(0,N.createLogger)({name:"codevibe-claude",logFile:D.default.join(O.default.tmpdir(),"codevibe-claude-mcp.log"),level:"info"});var a=require("@quantiya/codevibe-core");var R=y(require("express")),w=y(require("fs")),M=y(require("path")),k=y(require("os")),$=require("@quantiya/codevibe-core");var l=require("@quantiya/codevibe-core");var I=class{constructor(){this.assignedPort=0;this.app=(0,R.default)(),this.setupMiddleware(),this.setupRoutes()}setSessionId(e){this.sessionId=e}getPort(){return this.assignedPort}setupMiddleware(){this.app.use(R.default.json({limit:"1mb"})),this.app.use((e,s,t)=>{n.debug(`${e.method} ${e.path}`,{body:e.body,query:e.query}),t()}),this.app.use((e,s,t,i)=>{n.error("Express error:",e);let r={success:!1,error:e.message||"Internal server error"};t.status(500).json(r)})}setupRoutes(){this.app.get("/health",this.handleHealth.bind(this)),this.app.post("/event",this.handleEvent.bind(this)),process.env.NODE_ENV!=="production"&&this.app.post("/test/execute",this.handleTestExecute.bind(this))}handleHealth(e,s){let t={success:!0,data:{status:"healthy",uptime:process.uptime(),version:"0.1.0",timestamp:new Date().toISOString()}};s.json(t)}async handleEvent(e,s){try{let t=e.body;if(!t.session_id){let o={success:!1,error:"Missing required field: session_id"};s.status(400).json(o);return}if(!t.hook_event_name){let o={success:!1,error:"Missing required field: hook_event_name"};s.status(400).json(o);return}let i=this.transformHookToEvent(t);n.info("Received event from hook",{sessionId:t.session_id,hookEvent:t.hook_event_name,type:i.type}),this.eventHandler?await this.eventHandler(i):n.warn("No event handler registered");let r={success:!0,message:"Event processed successfully"};s.json(r)}catch(t){n.error("Error handling event:",t);let i={success:!1,error:t instanceof Error?t.message:"Unknown error"};s.status(500).json(i)}}async handleTestExecute(e,s){try{let{sessionId:t,prompt:i}=e.body;if(!t||!i){let o={success:!1,error:"Missing required fields: sessionId, prompt"};s.status(400).json(o);return}n.info("Test execute request",{sessionId:t,prompt:i});let r={success:!0,message:"Test execution endpoint - not implemented yet",data:{sessionId:t,prompt:i}};s.json(r)}catch(t){n.error("Error in test execute:",t);let i={success:!1,error:t instanceof Error?t.message:"Unknown error"};s.status(500).json(i)}}transformHookToEvent(e){let s,t,i={cwd:e.cwd,hook_event_name:e.hook_event_name,...e.metadata||{}};if(e.type&&e.content!==void 0)s=e.type,t=e.content;else switch(e.hook_event_name){case"SessionStart":s=l.EventType.NOTIFICATION,t="Session started",i.source=e.source;break;case"SessionEnd":s=l.EventType.NOTIFICATION,t=`Session ended: ${e.reason||"unknown"}`,i.reason=e.reason;break;case"UserPromptSubmit":s=l.EventType.USER_PROMPT,t=e.prompt||"";break;case"PostToolUse":s=l.EventType.TOOL_USE,t=JSON.stringify({tool_name:e.tool_name,tool_input:e.tool_input,tool_response:e.tool_response}),i.tool_name=e.tool_name;break;case"Notification":s=l.EventType.NOTIFICATION,t=e.message||"",i.notification_type=e.notification_type;break;default:s=l.EventType.NOTIFICATION,t=`Hook event: ${e.hook_event_name}`}return{session_id:e.session_id,hook_event_name:e.hook_event_name,type:s,source:l.EventSource.DESKTOP,content:t,metadata:i}}onEvent(e){this.eventHandler=e}async start(e){let s=e||this.sessionId;return s&&(this.sessionId=s),new Promise((t,i)=>{try{let r=(0,$.getConfig)(),o=r.server.dynamicPort?0:r.server.port;this.server=this.app.listen(o,r.server.host,()=>{let p=this.server.address();this.assignedPort=p.port,n.info(`HTTP API listening on http://${r.server.host}:${this.assignedPort}`),this.sessionId&&this.writePortFile(this.sessionId,this.assignedPort),t(this.assignedPort)}),this.server.on("error",p=>{n.error("HTTP server error:",p),i(p)})}catch(r){i(r)}})}writePortFile(e,s){let t=M.join(k.tmpdir(),`codevibe-claude-${e}.port`);try{w.writeFileSync(t,s.toString()),n.info(`Port file written: ${t} -> ${s}`)}catch(i){n.error(`Failed to write port file: ${t}`,i)}}removePortFile(){if(this.sessionId){let e=M.join(k.tmpdir(),`codevibe-claude-${this.sessionId}.port`);try{w.existsSync(e)&&(w.unlinkSync(e),n.info(`Port file removed: ${e}`))}catch(s){n.warn(`Failed to remove port file: ${e}`,s)}}}async stop(e){return new Promise((s,t)=>{this.sessionId&&e?.protectedSessionIds?.has(this.sessionId)?n.info("Skipping port file removal \u2014 another daemon still serves this session",{sessionId:this.sessionId}):this.removePortFile(),this.server?this.server.close(i=>{i?(n.error("Error stopping HTTP server:",i),t(i)):(n.info("HTTP API stopped"),s())}):s()})}};var U=require("child_process"),K=require("@quantiya/codevibe-core");var b=class{async executePrompt(e,s){let t=(0,K.getConfig)(),i=t.claude.defaultTimeout;return n.info("Executing prompt from mobile",{sessionId:e,promptLength:s.length,timeout:i}),new Promise(r=>{let o=["--resume",e,"--print","--output-format","stream-json",s];n.debug("Spawning Claude command",{command:t.claude.command,args:o});let p=(0,U.spawn)(t.claude.command,o,{stdio:["pipe","pipe","pipe"],shell:!0}),c="",d="",m=!1,h=setTimeout(()=>{m=!0,n.warn("Command execution timed out",{sessionId:e,timeout:i}),p.kill("SIGTERM")},i);p.stdout?.on("data",g=>{let f=g.toString();c+=f,n.debug("Command stdout",{output:f.slice(0,200)})}),p.stderr?.on("data",g=>{let f=g.toString();d+=f,n.debug("Command stderr",{output:f.slice(0,200)})}),p.on("close",g=>{clearTimeout(h);let f={success:g===0&&!m,output:c,error:d,exitCode:g||void 0,timedOut:m};f.success?n.info("Command executed successfully",{sessionId:e,exitCode:g,outputLength:c.length}):n.error("Command execution failed",{sessionId:e,exitCode:g,timedOut:m,error:d.slice(0,500)}),r(f)}),p.on("error",g=>{clearTimeout(h),n.error("Failed to spawn command",{error:g.message}),r({success:!1,error:g.message,timedOut:!1})})})}detectInteractivePrompt(e){return[/\[Y\/n\]/i,/\[y\/N\]/i,/\(y\/n\)/i,/Continue\?/i,/Proceed\?/i].some(t=>t.test(e))}extractPromptText(e){let s=e.split(`
2
- `);for(let t=s.length-1;t>=0;t--){let i=s[t].trim();if(this.detectInteractivePrompt(i))return i}return null}};var j=require("child_process"),H=require("util");var L=(0,H.promisify)(j.exec),C=class{async answerInteractivePrompt(e,s){n.info("Attempting to answer interactive prompt",{sessionId:e,response:s});try{let t=process.env.CODEVIBE_TMUX_SESSION;return n.info("Checking tmux session environment",{tmuxSession:t||"(not set)",allEnvKeys:Object.keys(process.env).filter(i=>i.includes("CODEVIBE")||i.includes("TMUX"))}),t?(n.info("Using tmux send-keys",{tmuxSession:t}),await this.sendViaTmux(t,s),n.info("Successfully sent response to interactive prompt",{sessionId:e,response:s}),!0):(n.error("No tmux session found - codevibe-claude wrapper is required",{sessionId:e,hint:"Start Claude Code using the codevibe-claude wrapper script"}),!1)}catch(t){return n.error("Failed to answer interactive prompt",{sessionId:e,error:t instanceof Error?t.message:String(t)}),!1}}async sendViaTmux(e,s){let t=s.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/\$/g,"\\$").replace(/`/g,"\\`");n.info("Sending via tmux",{sessionName:e,inputLength:s.length});try{let i=`tmux send-keys -t "${e}" -l "${t}"`,r=await L(i);n.info("tmux send-keys (text) completed",{stdout:r.stdout||"(empty)",stderr:r.stderr||"(empty)"}),await this.delay(500);let o=`tmux send-keys -t "${e}" Enter`,p=await L(o);n.info("tmux send-keys (Enter) completed",{stdout:p.stdout||"(empty)",stderr:p.stderr||"(empty)"})}catch(i){throw n.error("tmux send-keys failed",{sessionName:e,error:i}),i}}delay(e){return new Promise(s=>setTimeout(s,e))}isPromptResponse(e){let s=e.trim().toLowerCase();return!!(s==="y"||s==="n"||s==="yes"||s==="no"||/^[0-9]+$/.test(s)||/^[a-z]$/.test(s)||["exit","quit","q","continue","skip","abort","retry","cancel"].includes(s))}};var A=class u{constructor(e){this.activeSessions=new Map;this.assignedPort=0;this.sessionKey=null;this.claudeToBackendSessionId=new Map;this.pendingMobilePrompts=new Map;this.httpApi=new I,this.commandExecutor=new b,this.promptResponder=new C,this.initialSessionId=e}static{this.MOBILE_PROMPT_EXPIRY_MS=3e3}getPort(){return this.assignedPort}generateBackendSessionId(e){return`claude-${e}`}trackMobilePrompt(e,s){this.pendingMobilePrompts.has(e)||this.pendingMobilePrompts.set(e,[]),this.pendingMobilePrompts.get(e).push({prompt:s.trim(),timestamp:Date.now()}),n.debug("Tracking mobile prompt for deduplication",{sessionId:e,promptLength:s.length})}isRecentMobilePrompt(e,s){let t=this.pendingMobilePrompts.get(e);if(!t)return!1;let i=Date.now(),r=s.trim(),o=[],p=!1;for(let c of t)if(!(i-c.timestamp>u.MOBILE_PROMPT_EXPIRY_MS)){if(!p&&c.prompt===r){p=!0,n.debug("Found matching mobile prompt, filtering duplicate",{sessionId:e});continue}o.push(c)}return o.length>0?this.pendingMobilePrompts.set(e,o):this.pendingMobilePrompts.delete(e),p}writePortFile(e){let s=E.join(T.tmpdir(),`codevibe-claude-${e}.port`);try{v.writeFileSync(s,this.assignedPort.toString()),n.info(`Port file written: ${s} -> ${this.assignedPort}`)}catch(t){n.error(`Failed to write port file: ${s}`,t)}}removePortFile(e){let s=E.join(T.tmpdir(),`codevibe-claude-${e}.port`);try{v.existsSync(s)&&(v.unlinkSync(s),n.info(`Port file removed: ${s}`))}catch(t){n.warn(`Failed to remove port file: ${s}`,t)}}hasOtherLiveDaemonForSession(e){try{let s=(0,V.execSync)("ps -eww -o pid= -o args=",{encoding:"utf8",timeout:2e3}),t=process.pid;for(let i of s.split(`
3
- `)){let r=i.trim();if(!r)continue;let o=r.indexOf(" ");if(o<0)continue;let p=parseInt(r.substring(0,o),10);if(isNaN(p)||p===t)continue;let c=r.substring(o+1);if(/node.*codevibe-claude.*server\.js/.test(c)&&c.includes(e))return!0}return!1}catch(s){return n.warn('hasOtherLiveDaemonForSession: ps query failed; falling back to "no other daemon"',{error:String(s)}),!1}}async start(){try{n.info("Starting CodeVibe MCP Server...",{environment:(0,a.getEnvironment)()}),this.appSyncClient=new a.AppSyncClient,await this.appSyncClient.authenticateWithStoredTokens()?(n.info("Authenticated with stored OAuth tokens",{userId:this.appSyncClient.getCurrentUserId(),email:this.appSyncClient.getCurrentUserEmail()}),await(0,a.registerDeviceEncryptionKey)(this.appSyncClient,n),(0,a.startDeviceKeyWatcher)(this.appSyncClient,n)):(n.error('Authentication failed. Run "codevibe-claude login" first.'),console.error('Not authenticated. Run "codevibe-claude login" to sign in.'),process.exit(1)),this.httpApi.onEvent(this.handleEventFromHook.bind(this)),this.assignedPort=await this.httpApi.start(this.initialSessionId),n.info("MCP Server started successfully",{port:this.assignedPort,host:(0,a.getConfig)().server.host,dynamicPort:(0,a.getConfig)().server.dynamicPort,sessionId:this.initialSessionId,authenticated:this.appSyncClient.isAuthenticated(),userId:this.appSyncClient.getCurrentUserId()})}catch(e){throw n.error("Failed to start MCP Server:",e),e}}async stop(){n.info("Stopping MCP Server...");let e=Array.from(this.activeSessions.keys()),s=new Set;n.info(`Marking ${e.length} active session(s) as INACTIVE...`);for(let t of e)try{let i=this.activeSessions.get(t);if(i&&this.hasOtherLiveDaemonForSession(i.claudeSessionId)){n.info("Another daemon serves this session \u2014 skipping mark INACTIVE AND port file removal during shutdown",{sessionId:t,claudeSessionId:i.claudeSessionId,myPid:process.pid}),s.add(i.claudeSessionId);continue}await this.appSyncClient.updateSession({sessionId:t,status:a.SessionStatus.INACTIVE}),n.info("Session marked as INACTIVE during shutdown",{sessionId:t}),i&&this.removePortFile(i.claudeSessionId)}catch(i){n.warn("Failed to mark session as INACTIVE during shutdown",{sessionId:t,error:i})}this.appSyncClient.cleanupSubscriptions(),this.activeSessions.clear(),await this.httpApi.stop({protectedSessionIds:s}),n.info("MCP Server stopped")}async handleEventFromHook(e){let{session_id:s,hook_event_name:t,type:i,content:r}=e;n.info("Processing hook event",{sessionId:s,hookEvent:t,type:i});try{t==="SessionStart"?await this.handleSessionStart(e):t==="SessionEnd"&&await this.handleSessionEnd(e);let o=this.claudeToBackendSessionId.get(s)||this.generateBackendSessionId(s);if(i===a.EventType.USER_PROMPT&&e.source===a.EventSource.DESKTOP&&t==="UserPromptSubmit"&&r&&this.isRecentMobilePrompt(o,r)){n.info("Skipping duplicate USER_PROMPT from mobile-originated prompt",{sessionId:o,contentLength:r.length});return}if(i===a.EventType.INTERACTIVE_PROMPT){let h=this.activeSessions.get(o);h&&(h.waitingForPromptResponse=!0,h.pendingPromptId=e.prompt_id,n.info("Interactive prompt detected - will parse options from tmux",{sessionId:o,promptId:e.prompt_id})),this.sendInteractivePromptAsync(o,e,r).catch(g=>{n.error("Failed to send interactive prompt with dynamic options",{error:g})});return}let p=r,c=e.metadata,d=!1;n.info("Hook event encryption state",{type:i,sessionId:o,hasSessionKey:!!this.sessionKey,sessionKeyLength:this.sessionKey?.length||0}),this.sessionKey?(p=a.cryptoService.encryptContent(r,this.sessionKey),c&&(c={encrypted:a.cryptoService.encryptMetadata(c,this.sessionKey)}),d=!0,n.info("Event encrypted for hook",{type:i,sessionId:o,isEncrypted:!0})):n.warn("No session key - event will NOT be encrypted",{type:i,sessionId:o});let m=await this.appSyncClient.createEvent({sessionId:o,type:i,source:e.source,content:p,metadata:c,promptId:e.prompt_id,isEncrypted:d?!0:void 0});if(i===a.EventType.USER_PROMPT&&e.source===a.EventSource.DESKTOP){let h=this.activeSessions.get(o);h?.waitingForPromptResponse&&(h.waitingForPromptResponse=!1,h.pendingPromptId=void 0,h.pendingSubmitMap=void 0,n.info("Clearing prompt wait state - new desktop prompt received",{sessionId:o}))}n.debug("Event sent to AppSync successfully")}catch(o){throw n.error("Failed to process hook event:",o),o}}async handleSessionStart(e){let s=e.session_id,t=this.generateBackendSessionId(s),i=e.metadata?.cwd||process.cwd();this.claudeToBackendSessionId.set(s,t),n.info("Session started",{claudeSessionId:s,sessionId:t,cwd:i});let r=Array.from(this.activeSessions.keys()).filter(c=>c!==t);if(r.length>0){n.info(`Marking ${r.length} previous session(s) as INACTIVE`);for(let c of r){try{await this.appSyncClient.updateSession({sessionId:c,status:a.SessionStatus.INACTIVE}),n.info("Previous session marked INACTIVE",{prevId:c,newSessionId:t})}catch(m){n.warn("Failed to mark previous session as INACTIVE",{prevId:c,error:m})}let d=this.activeSessions.get(c);d&&this.removePortFile(d.claudeSessionId),this.activeSessions.delete(c)}}this.writePortFile(s);let o=this.appSyncClient.getCurrentUserId(),p={sessionId:t,claudeSessionId:s,userId:o,projectPath:i,cwd:i,createdAt:new Date,subscriptionActive:!1,waitingForPromptResponse:!1,metadata:e.metadata||{}};this.activeSessions.set(t,p);try{let c=await(0,a.resumeOrCreateSession)({sessionId:t,userId:p.userId,agentType:a.AgentType.CLAUDE,projectPath:i,metadata:e.metadata||{}},this.appSyncClient,n);if(this.sessionKey=c.sessionKey,c.resumed&&!c.sessionKey){let d=await a.keychainManager.getDeviceId();n.error("Device key not found in session encryptedKeys",{sessionId:t,pluginDeviceId:d}),console.error(`
1
+ "use strict";var Y=Object.create;var P=Object.defineProperty;var z=Object.getOwnPropertyDescriptor;var W=Object.getOwnPropertyNames;var J=Object.getPrototypeOf,Q=Object.prototype.hasOwnProperty;var Z=(u,e)=>{for(var s in e)P(u,s,{get:e[s],enumerable:!0})},F=(u,e,s,t)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of W(e))!Q.call(u,i)&&i!==s&&P(u,i,{get:()=>e[i],enumerable:!(t=z(e,i))||t.enumerable});return u};var y=(u,e,s)=>(s=u!=null?Y(J(u)):{},F(e||!u||!u.__esModule?P(s,"default",{value:u,enumerable:!0}):s,u)),ee=u=>F(P({},"__esModule",{value:!0}),u);var se={};Z(se,{parseInteractivePromptInput:()=>B});module.exports=ee(se);var v=y(require("fs")),E=y(require("path")),T=y(require("os")),H=require("child_process");var O=y(require("os")),D=y(require("path")),N=require("@quantiya/codevibe-core"),n=(0,N.createLogger)({name:"codevibe-claude",logFile:D.default.join(O.default.tmpdir(),"codevibe-claude-mcp.log"),level:"info"});var a=require("@quantiya/codevibe-core");var R=y(require("express")),w=y(require("fs")),M=y(require("path")),A=y(require("os")),$=require("@quantiya/codevibe-core");var l=require("@quantiya/codevibe-core");var I=class{constructor(){this.assignedPort=0;this.app=(0,R.default)(),this.setupMiddleware(),this.setupRoutes()}setSessionId(e){this.sessionId=e}getPort(){return this.assignedPort}setupMiddleware(){this.app.use(R.default.json({limit:"1mb"})),this.app.use((e,s,t)=>{n.debug(`${e.method} ${e.path}`,{body:e.body,query:e.query}),t()}),this.app.use((e,s,t,i)=>{n.error("Express error:",e);let r={success:!1,error:e.message||"Internal server error"};t.status(500).json(r)})}setupRoutes(){this.app.get("/health",this.handleHealth.bind(this)),this.app.post("/event",this.handleEvent.bind(this)),process.env.NODE_ENV!=="production"&&this.app.post("/test/execute",this.handleTestExecute.bind(this))}handleHealth(e,s){let t={success:!0,data:{status:"healthy",uptime:process.uptime(),version:"0.1.0",timestamp:new Date().toISOString()}};s.json(t)}async handleEvent(e,s){try{let t=e.body;if(!t.session_id){let o={success:!1,error:"Missing required field: session_id"};s.status(400).json(o);return}if(!t.hook_event_name){let o={success:!1,error:"Missing required field: hook_event_name"};s.status(400).json(o);return}let i=this.transformHookToEvent(t);n.info("Received event from hook",{sessionId:t.session_id,hookEvent:t.hook_event_name,type:i.type}),this.eventHandler?await this.eventHandler(i):n.warn("No event handler registered");let r={success:!0,message:"Event processed successfully"};s.json(r)}catch(t){n.error("Error handling event:",t);let i={success:!1,error:t instanceof Error?t.message:"Unknown error"};s.status(500).json(i)}}async handleTestExecute(e,s){try{let{sessionId:t,prompt:i}=e.body;if(!t||!i){let o={success:!1,error:"Missing required fields: sessionId, prompt"};s.status(400).json(o);return}n.info("Test execute request",{sessionId:t,prompt:i});let r={success:!0,message:"Test execution endpoint - not implemented yet",data:{sessionId:t,prompt:i}};s.json(r)}catch(t){n.error("Error in test execute:",t);let i={success:!1,error:t instanceof Error?t.message:"Unknown error"};s.status(500).json(i)}}transformHookToEvent(e){let s,t,i={cwd:e.cwd,hook_event_name:e.hook_event_name,...e.metadata||{}};if(e.type&&e.content!==void 0)s=e.type,t=e.content;else switch(e.hook_event_name){case"SessionStart":s=l.EventType.NOTIFICATION,t="Session started",i.source=e.source;break;case"SessionEnd":s=l.EventType.NOTIFICATION,t=`Session ended: ${e.reason||"unknown"}`,i.reason=e.reason;break;case"UserPromptSubmit":s=l.EventType.USER_PROMPT,t=e.prompt||"";break;case"PostToolUse":s=l.EventType.TOOL_USE,t=JSON.stringify({tool_name:e.tool_name,tool_input:e.tool_input,tool_response:e.tool_response}),i.tool_name=e.tool_name;break;case"Notification":s=l.EventType.NOTIFICATION,t=e.message||"",i.notification_type=e.notification_type;break;default:s=l.EventType.NOTIFICATION,t=`Hook event: ${e.hook_event_name}`}return{session_id:e.session_id,hook_event_name:e.hook_event_name,type:s,source:l.EventSource.DESKTOP,content:t,metadata:i}}onEvent(e){this.eventHandler=e}async start(e){let s=e||this.sessionId;return s&&(this.sessionId=s),new Promise((t,i)=>{try{let r=(0,$.getConfig)(),o=r.server.dynamicPort?0:r.server.port;this.server=this.app.listen(o,r.server.host,()=>{let p=this.server.address();this.assignedPort=p.port,n.info(`HTTP API listening on http://${r.server.host}:${this.assignedPort}`),this.sessionId&&this.writePortFile(this.sessionId,this.assignedPort),t(this.assignedPort)}),this.server.on("error",p=>{n.error("HTTP server error:",p),i(p)})}catch(r){i(r)}})}writePortFile(e,s){let t=M.join(A.tmpdir(),`codevibe-claude-${e}.port`);try{w.writeFileSync(t,s.toString()),n.info(`Port file written: ${t} -> ${s}`)}catch(i){n.error(`Failed to write port file: ${t}`,i)}}removePortFile(){if(this.sessionId){let e=M.join(A.tmpdir(),`codevibe-claude-${this.sessionId}.port`);try{w.existsSync(e)&&(w.unlinkSync(e),n.info(`Port file removed: ${e}`))}catch(s){n.warn(`Failed to remove port file: ${e}`,s)}}}async stop(e){return new Promise((s,t)=>{this.sessionId&&e?.protectedSessionIds?.has(this.sessionId)?n.info("Skipping port file removal \u2014 another daemon still serves this session",{sessionId:this.sessionId}):this.removePortFile(),this.server?this.server.close(i=>{i?(n.error("Error stopping HTTP server:",i),t(i)):(n.info("HTTP API stopped"),s())}):s()})}};var U=require("child_process"),K=require("@quantiya/codevibe-core");var b=class{async executePrompt(e,s){let t=(0,K.getConfig)(),i=t.claude.defaultTimeout;return n.info("Executing prompt from mobile",{sessionId:e,promptLength:s.length,timeout:i}),new Promise(r=>{let o=["--resume",e,"--print","--output-format","stream-json",s];n.debug("Spawning Claude command",{command:t.claude.command,args:o});let p=(0,U.spawn)(t.claude.command,o,{stdio:["pipe","pipe","pipe"],shell:!0}),c="",d="",m=!1,h=setTimeout(()=>{m=!0,n.warn("Command execution timed out",{sessionId:e,timeout:i}),p.kill("SIGTERM")},i);p.stdout?.on("data",g=>{let f=g.toString();c+=f,n.debug("Command stdout",{output:f.slice(0,200)})}),p.stderr?.on("data",g=>{let f=g.toString();d+=f,n.debug("Command stderr",{output:f.slice(0,200)})}),p.on("close",g=>{clearTimeout(h);let f={success:g===0&&!m,output:c,error:d,exitCode:g||void 0,timedOut:m};f.success?n.info("Command executed successfully",{sessionId:e,exitCode:g,outputLength:c.length}):n.error("Command execution failed",{sessionId:e,exitCode:g,timedOut:m,error:d.slice(0,500)}),r(f)}),p.on("error",g=>{clearTimeout(h),n.error("Failed to spawn command",{error:g.message}),r({success:!1,error:g.message,timedOut:!1})})})}detectInteractivePrompt(e){return[/\[Y\/n\]/i,/\[y\/N\]/i,/\(y\/n\)/i,/Continue\?/i,/Proceed\?/i].some(t=>t.test(e))}extractPromptText(e){let s=e.split(`
2
+ `);for(let t=s.length-1;t>=0;t--){let i=s[t].trim();if(this.detectInteractivePrompt(i))return i}return null}};var j=require("child_process"),V=require("util");var L=(0,V.promisify)(j.exec),C=class{async answerInteractivePrompt(e,s){n.info("Attempting to answer interactive prompt",{sessionId:e,response:s});try{let t=process.env.CODEVIBE_TMUX_SESSION;return n.info("Checking tmux session environment",{tmuxSession:t||"(not set)",allEnvKeys:Object.keys(process.env).filter(i=>i.includes("CODEVIBE")||i.includes("TMUX"))}),t?(n.info("Using tmux send-keys",{tmuxSession:t}),await this.sendViaTmux(t,s),n.info("Successfully sent response to interactive prompt",{sessionId:e,response:s}),!0):(n.error("No tmux session found - codevibe-claude wrapper is required",{sessionId:e,hint:"Start Claude Code using the codevibe-claude wrapper script"}),!1)}catch(t){return n.error("Failed to answer interactive prompt",{sessionId:e,error:t instanceof Error?t.message:String(t)}),!1}}async sendViaTmux(e,s){let t=s.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/\$/g,"\\$").replace(/`/g,"\\`");n.info("Sending via tmux",{sessionName:e,inputLength:s.length});try{let i=`tmux send-keys -t "${e}" -l "${t}"`,r=await L(i);n.info("tmux send-keys (text) completed",{stdout:r.stdout||"(empty)",stderr:r.stderr||"(empty)"}),await this.delay(500);let o=`tmux send-keys -t "${e}" Enter`,p=await L(o);n.info("tmux send-keys (Enter) completed",{stdout:p.stdout||"(empty)",stderr:p.stderr||"(empty)"})}catch(i){throw n.error("tmux send-keys failed",{sessionName:e,error:i}),i}}delay(e){return new Promise(s=>setTimeout(s,e))}isPromptResponse(e){let s=e.trim().toLowerCase();return!!(s==="y"||s==="n"||s==="yes"||s==="no"||/^[0-9]+$/.test(s)||/^[a-z]$/.test(s)||["exit","quit","q","continue","skip","abort","retry","cancel"].includes(s))}};var k=class u{constructor(e){this.activeSessions=new Map;this.assignedPort=0;this.sessionKey=null;this.claudeToBackendSessionId=new Map;this.pendingMobilePrompts=new Map;this.httpApi=new I,this.commandExecutor=new b,this.promptResponder=new C,this.initialSessionId=e}static{this.MOBILE_PROMPT_EXPIRY_MS=3e3}getPort(){return this.assignedPort}generateBackendSessionId(e){return`claude-${e}`}trackMobilePrompt(e,s){this.pendingMobilePrompts.has(e)||this.pendingMobilePrompts.set(e,[]),this.pendingMobilePrompts.get(e).push({prompt:s.trim(),timestamp:Date.now()}),n.debug("Tracking mobile prompt for deduplication",{sessionId:e,promptLength:s.length})}isRecentMobilePrompt(e,s){let t=this.pendingMobilePrompts.get(e);if(!t)return!1;let i=Date.now(),r=s.trim(),o=[],p=!1;for(let c of t)if(!(i-c.timestamp>u.MOBILE_PROMPT_EXPIRY_MS)){if(!p&&c.prompt===r){p=!0,n.debug("Found matching mobile prompt, filtering duplicate",{sessionId:e});continue}o.push(c)}return o.length>0?this.pendingMobilePrompts.set(e,o):this.pendingMobilePrompts.delete(e),p}writePortFile(e){let s=E.join(T.tmpdir(),`codevibe-claude-${e}.port`);try{v.writeFileSync(s,this.assignedPort.toString()),n.info(`Port file written: ${s} -> ${this.assignedPort}`)}catch(t){n.error(`Failed to write port file: ${s}`,t)}}removePortFile(e){let s=E.join(T.tmpdir(),`codevibe-claude-${e}.port`);try{v.existsSync(s)&&(v.unlinkSync(s),n.info(`Port file removed: ${s}`))}catch(t){n.warn(`Failed to remove port file: ${s}`,t)}}hasOtherLiveDaemonForSession(e){try{let s=(0,H.execSync)("ps -eww -o pid= -o args=",{encoding:"utf8",timeout:2e3}),t=process.pid;for(let i of s.split(`
3
+ `)){let r=i.trim();if(!r)continue;let o=r.indexOf(" ");if(o<0)continue;let p=parseInt(r.substring(0,o),10);if(isNaN(p)||p===t)continue;let c=r.substring(o+1);if(/node.*codevibe-claude.*server\.js/.test(c)&&c.includes(e))return!0}return!1}catch(s){return n.warn('hasOtherLiveDaemonForSession: ps query failed; falling back to "no other daemon"',{error:String(s)}),!1}}async start(){try{if(n.info("Starting CodeVibe MCP Server...",{environment:(0,a.getEnvironment)()}),this.appSyncClient=new a.AppSyncClient,await this.appSyncClient.authenticateWithStoredTokens()){n.info("Authenticated with stored OAuth tokens",{userId:this.appSyncClient.getCurrentUserId(),email:this.appSyncClient.getCurrentUserEmail()}),await(0,a.registerDeviceEncryptionKey)(this.appSyncClient,n),(0,a.startDeviceKeyWatcher)(this.appSyncClient,n);try{let s=await this.appSyncClient.sweepOrphanSessions({agentType:"CLAUDE"});s>0&&n.info("Orphan sweep: marked stale Claude sessions INACTIVE",{swept:s})}catch(s){n.warn("Orphan sweep failed, continuing startup",{error:s instanceof Error?s.message:String(s)})}}else n.error('Authentication failed. Run "codevibe-claude login" first.'),console.error('Not authenticated. Run "codevibe-claude login" to sign in.'),process.exit(1);this.httpApi.onEvent(this.handleEventFromHook.bind(this)),this.assignedPort=await this.httpApi.start(this.initialSessionId),n.info("MCP Server started successfully",{port:this.assignedPort,host:(0,a.getConfig)().server.host,dynamicPort:(0,a.getConfig)().server.dynamicPort,sessionId:this.initialSessionId,authenticated:this.appSyncClient.isAuthenticated(),userId:this.appSyncClient.getCurrentUserId()})}catch(e){throw n.error("Failed to start MCP Server:",e),e}}async stop(){n.info("Stopping MCP Server...");let e=Array.from(this.activeSessions.keys()),s=new Set;n.info(`Marking ${e.length} active session(s) as INACTIVE...`);for(let t of e)try{let i=this.activeSessions.get(t);if(i&&this.hasOtherLiveDaemonForSession(i.claudeSessionId)){n.info("Another daemon serves this session \u2014 skipping mark INACTIVE AND port file removal during shutdown",{sessionId:t,claudeSessionId:i.claudeSessionId,myPid:process.pid}),s.add(i.claudeSessionId);continue}await this.appSyncClient.updateSession({sessionId:t,status:a.SessionStatus.INACTIVE}),n.info("Session marked as INACTIVE during shutdown",{sessionId:t}),i&&this.removePortFile(i.claudeSessionId)}catch(i){n.warn("Failed to mark session as INACTIVE during shutdown",{sessionId:t,error:i})}this.appSyncClient.cleanupSubscriptions(),this.activeSessions.clear(),await this.httpApi.stop({protectedSessionIds:s}),n.info("MCP Server stopped")}async handleEventFromHook(e){let{session_id:s,hook_event_name:t,type:i,content:r}=e;n.info("Processing hook event",{sessionId:s,hookEvent:t,type:i});try{t==="SessionStart"?await this.handleSessionStart(e):t==="SessionEnd"&&await this.handleSessionEnd(e);let o=this.claudeToBackendSessionId.get(s)||this.generateBackendSessionId(s);if(i===a.EventType.USER_PROMPT&&e.source===a.EventSource.DESKTOP&&t==="UserPromptSubmit"&&r&&this.isRecentMobilePrompt(o,r)){n.info("Skipping duplicate USER_PROMPT from mobile-originated prompt",{sessionId:o,contentLength:r.length});return}if(i===a.EventType.INTERACTIVE_PROMPT){let h=this.activeSessions.get(o);h&&(h.waitingForPromptResponse=!0,h.pendingPromptId=e.prompt_id,n.info("Interactive prompt detected - will parse options from tmux",{sessionId:o,promptId:e.prompt_id})),this.sendInteractivePromptAsync(o,e,r).catch(g=>{n.error("Failed to send interactive prompt with dynamic options",{error:g})});return}let p=r,c=e.metadata,d=!1;n.info("Hook event encryption state",{type:i,sessionId:o,hasSessionKey:!!this.sessionKey,sessionKeyLength:this.sessionKey?.length||0}),this.sessionKey?(p=a.cryptoService.encryptContent(r,this.sessionKey),c&&(c={encrypted:a.cryptoService.encryptMetadata(c,this.sessionKey)}),d=!0,n.info("Event encrypted for hook",{type:i,sessionId:o,isEncrypted:!0})):n.warn("No session key - event will NOT be encrypted",{type:i,sessionId:o});let m=await this.appSyncClient.createEvent({sessionId:o,type:i,source:e.source,content:p,metadata:c,promptId:e.prompt_id,isEncrypted:d?!0:void 0});if(i===a.EventType.USER_PROMPT&&e.source===a.EventSource.DESKTOP){let h=this.activeSessions.get(o);h?.waitingForPromptResponse&&(h.waitingForPromptResponse=!1,h.pendingPromptId=void 0,h.pendingSubmitMap=void 0,n.info("Clearing prompt wait state - new desktop prompt received",{sessionId:o}))}n.debug("Event sent to AppSync successfully")}catch(o){throw n.error("Failed to process hook event:",o),o}}async handleSessionStart(e){let s=e.session_id,t=this.generateBackendSessionId(s),i=e.metadata?.cwd||process.cwd();this.claudeToBackendSessionId.set(s,t),n.info("Session started",{claudeSessionId:s,sessionId:t,cwd:i});let r=Array.from(this.activeSessions.keys()).filter(c=>c!==t);if(r.length>0){n.info(`Marking ${r.length} previous session(s) as INACTIVE`);for(let c of r){try{await this.appSyncClient.updateSession({sessionId:c,status:a.SessionStatus.INACTIVE}),n.info("Previous session marked INACTIVE",{prevId:c,newSessionId:t})}catch(m){n.warn("Failed to mark previous session as INACTIVE",{prevId:c,error:m})}let d=this.activeSessions.get(c);d&&this.removePortFile(d.claudeSessionId),this.activeSessions.delete(c)}}this.writePortFile(s);let o=this.appSyncClient.getCurrentUserId(),p={sessionId:t,claudeSessionId:s,userId:o,projectPath:i,cwd:i,createdAt:new Date,subscriptionActive:!1,waitingForPromptResponse:!1,metadata:e.metadata||{}};this.activeSessions.set(t,p);try{let c=await(0,a.resumeOrCreateSession)({sessionId:t,userId:p.userId,agentType:a.AgentType.CLAUDE,projectPath:i,metadata:e.metadata||{}},this.appSyncClient,n);if(this.sessionKey=c.sessionKey,c.resumed&&!c.sessionKey){let d=await a.keychainManager.getDeviceId();n.error("Device key not found in session encryptedKeys",{sessionId:t,pluginDeviceId:d}),console.error(`
4
4
  \u26A0\uFE0F E2E ENCRYPTION WARNING: Cannot decrypt this session!`),console.error(` Your device ID (${d.substring(0,8)}...) is not in session's encryption keys.`),console.error(" This happens if your device key was regenerated after the session was created."),console.error(` SOLUTION: Start a new Claude Code session instead of resuming this one.
5
5
  `)}}catch(c){if(this.isSessionLimitExceeded(c)){this.displaySubscriptionLimitError(c,"session"),this.activeSessions.delete(t),this.removePortFile(s);return}n.error("Failed to create/resume session:",c)}this.subscribeToMobileEvents(t),this.appSyncClient.startHeartbeat(t)}async handleSessionEnd(e){let s=e.session_id,t=this.claudeToBackendSessionId.get(s)||this.generateBackendSessionId(s);n.info("Session ended",{claudeSessionId:s,sessionId:t,reason:e.metadata?.reason}),this.removePortFile(s);let i=this.activeSessions.get(t);if(i?.waitingForPromptResponse&&(n.info("Clearing prompt wait state - session ending",{sessionId:t}),i.waitingForPromptResponse=!1,i.pendingPromptId=void 0),this.appSyncClient.stopHeartbeat(t),i)try{await this.appSyncClient.updateSession({sessionId:t,status:a.SessionStatus.INACTIVE}),n.info("Session marked as INACTIVE in AppSync",{sessionId:t})}catch(r){n.warn("Failed to update session in AppSync:",r)}else n.warn("Cannot update session - session state not found",{sessionId:t});this.activeSessions.delete(t),this.claudeToBackendSessionId.delete(s),n.debug("Session cleanup completed",{sessionId:t})}subscribeToMobileEvents(e){n.info("Subscribing to mobile events",{sessionId:e});let s=this.activeSessions.get(e);if(!s){n.error("Session not found",{sessionId:e});return}this.appSyncClient.subscribeToEvents(e,async t=>{n.info("Received mobile event",{eventId:t.eventId,type:t.type,sessionId:t.sessionId,isEncrypted:t.isEncrypted});let i=t.content||"";if(t.isEncrypted&&this.sessionKey)try{i=a.cryptoService.decryptContent(t.content,this.sessionKey),n.debug("Event decrypted successfully",{eventId:t.eventId})}catch(o){n.error("Failed to decrypt event:",{eventId:t.eventId,error:o}),i=t.content}let r={...t,content:i};try{await this.appSyncClient.updateEventStatus({eventId:t.eventId,sessionId:t.sessionId,timestamp:t.timestamp,deliveryStatus:a.DeliveryStatus.DELIVERED}),n.info("Event marked as DELIVERED",{eventId:t.eventId})}catch(o){n.warn("Failed to mark event as DELIVERED",{eventId:t.eventId,error:o})}if(t.type===a.EventType.USER_PROMPT){let o=this.activeSessions.get(e);if(o?.waitingForPromptResponse){let p=i.trim(),c=o.pendingSubmitMap?Object.keys(o.pendingSubmitMap).length:3,d=this.parseInteractivePromptInput(p,c);if(n.info("Parsed interactive prompt input",{sessionId:e,content:p,parsed:d,hasSubmitMap:!!o.pendingSubmitMap}),d.action==="select_option"){let m=o.pendingSubmitMap?.[d.option]||d.option;n.info("User selected option",{option:d.option,terminalInput:m}),await this.promptResponder.answerInteractivePrompt(e,m)?(await this.markEventExecuted(t),o.waitingForPromptResponse=!1,o.pendingPromptId=void 0,o.pendingSubmitMap=void 0,await this.appSyncClient.createEvent({sessionId:e,type:a.EventType.NOTIFICATION,source:a.EventSource.DESKTOP,content:`Selected option ${d.option}`,metadata:{promptAnswered:!0}})):await this.sendPromptError(e,"Failed to select option")}else if(d.action==="option_with_followup"){let m=o.pendingSubmitMap?.[d.option]||d.option;n.info("User selected option with follow-up",{option:d.option,terminalInput:m,followUpText:d.followUpText});let h=await this.promptResponder.answerInteractivePrompt(e,m);if(o.waitingForPromptResponse=!1,o.pendingPromptId=void 0,o.pendingSubmitMap=void 0,h){if(await this.appSyncClient.createEvent({sessionId:e,type:a.EventType.NOTIFICATION,source:a.EventSource.DESKTOP,content:`Selected option ${d.option}`,metadata:{promptAnswered:!0}}),d.followUpText){await new Promise(f=>setTimeout(f,1e3));let g={...t,content:d.followUpText};await this.executeMobilePrompt(e,g)}await this.markEventExecuted(t)}else await this.sendPromptError(e,"Failed to select option")}else n.info("Sending as free-form response to interactive prompt",{response:p}),await this.promptResponder.answerInteractivePrompt(e,p)?(await this.markEventExecuted(t),o.waitingForPromptResponse=!1,o.pendingPromptId=void 0,o.pendingSubmitMap=void 0,await this.appSyncClient.createEvent({sessionId:e,type:a.EventType.NOTIFICATION,source:a.EventSource.DESKTOP,content:"Response sent to interactive prompt",metadata:{promptAnswered:!0}})):await this.sendPromptError(e,"Failed to send response")}else await this.executeMobilePrompt(e,r)}},t=>{n.error("Subscription error",{sessionId:e,error:t})}),s.subscriptionActive=!0,n.info("Subscription active",{sessionId:e})}async sendInteractivePromptAsync(e,s,t){await new Promise(m=>setTimeout(m,500));let i=process.env.CODEVIBE_TMUX_SESSION,r={...s.metadata||{}};if(i)try{let{exec:m}=await import("child_process"),h=x=>new Promise((q,X)=>{m(x,{timeout:5e3},(_,G)=>{_?X(_):q({stdout:G||""})})}),{stdout:g}=await h(`tmux capture-pane -p -e -S -30 -t '${i}'`),f=g.split(`
6
6
  `);n.info("tmux capture result",{tmuxSession:i,totalLines:f.length,lastLines:f.slice(-15).map(x=>x.replace(/\x1B[^m]*m/g,"").trim()).filter(Boolean)});let S=(0,a.parseInteractivePrompt)(g);S&&S.options.length>0?(r.options=S.options,r.submitMap=S.submitMap,r.instructions=this.buildPromptInstructions(S),n.info("Parsed dynamic options from tmux",{optionCount:S.options.length,kind:S.kind,options:S.options})):(n.info("No dynamic options parsed from tmux, using fallback",{parsedResult:S}),this.addFallbackOptions(r))}catch(m){n.warn("Failed to capture tmux pane for options",{error:m}),this.addFallbackOptions(r)}else n.warn("No tmux session \u2014 using fallback options"),this.addFallbackOptions(r);let o=this.activeSessions.get(e);o&&r.submitMap&&(o.pendingSubmitMap=r.submitMap);let p=t,c=r,d=!1;this.sessionKey&&(p=a.cryptoService.encryptContent(t,this.sessionKey),c={encrypted:a.cryptoService.encryptMetadata(c,this.sessionKey)},d=!0),await this.appSyncClient.createEvent({sessionId:e,type:a.EventType.INTERACTIVE_PROMPT,source:s.source,content:p,metadata:c,promptId:s.prompt_id,isEncrypted:d?!0:void 0}),n.info("Interactive prompt sent to AppSync with dynamic options",{sessionId:e})}addFallbackOptions(e){e.options=[{number:"1",text:"Yes"},{number:"2",text:"Yes, and don't ask again"},{number:"3",text:"Reject and tell Claude what to do differently"}],e.submitMap={1:"1",2:"2",3:"3"},e.instructions="Reply with 1, 2, or 3. Append a message to provide alternative instructions."}buildPromptInstructions(e){return`Reply with ${e.options.map(t=>t.number).join(", ")}. Append a message to provide alternative instructions.`}parseInteractivePromptInput(e,s=3){return B(e,s)}async markEventExecuted(e){try{await this.appSyncClient.updateEventStatus({eventId:e.eventId,sessionId:e.sessionId,timestamp:e.timestamp,deliveryStatus:a.DeliveryStatus.EXECUTED}),n.info("Event marked as EXECUTED",{eventId:e.eventId})}catch(s){n.warn("Failed to mark event as EXECUTED",{eventId:e.eventId,error:s})}}async sendPromptError(e,s){await this.appSyncClient.createEvent({sessionId:e,type:a.EventType.NOTIFICATION,source:a.EventSource.DESKTOP,content:s,metadata:{error:!0}})}isSessionLimitExceeded(e){return this.getErrorMessage(e).includes("SESSION_LIMIT_EXCEEDED")}isUsageLimitExceeded(e){let s=this.getErrorMessage(e);return s.includes("MESSAGE_LIMIT_EXCEEDED")||s.includes("IMAGE_LIMIT_EXCEEDED")}getErrorMessage(e){if(e instanceof Error)return e.message;if(typeof e=="object"&&e!==null){let s=e;if(s.errors&&Array.isArray(s.errors))return s.errors.map(t=>t.message||"").join(" ");if(typeof s.message=="string")return s.message}return String(e)}displaySubscriptionLimitError(e,s){let t=this.getErrorMessage(e),i="",r=t.match(/for your (\w+) plan/i);r&&(i=` (${r[1]} tier)`);let o="",p=t.match(/of (\d+)/);switch(p&&(o=` [Limit: ${p[1]}]`),console.log(`
@@ -14,4 +14,4 @@ Note: You can still use Claude Code normally from your desktop.`),console.log("T
14
14
 
15
15
  ${t}`:t=`${o}
16
16
 
17
- Please analyze the attached file(s).`,n.info("Prompt updated with attachment paths",{attachmentCount:r.length,newPromptLength:t.length})}}this.trackMobilePrompt(e,t);try{if(await this.promptResponder.answerInteractivePrompt(e,t)){try{await this.appSyncClient.updateEventStatus({eventId:s.eventId,sessionId:s.sessionId,timestamp:s.timestamp,deliveryStatus:a.DeliveryStatus.EXECUTED}),n.info("Event marked as EXECUTED",{eventId:s.eventId})}catch(c){n.warn("Failed to mark event as EXECUTED",{eventId:s.eventId,error:c})}n.info("Mobile prompt sent successfully",{sessionId:e});let p=r.length>0?`Prompt with ${r.length} attachment(s) sent to Claude Code`:`Prompt "${t.substring(0,50)}${t.length>50?"...":""}" sent to Claude Code`;await this.appSyncClient.createEvent({sessionId:e,type:a.EventType.NOTIFICATION,source:a.EventSource.DESKTOP,content:p,metadata:{mobilePrompt:!0,attachmentCount:r.length}})}else n.error("Failed to send mobile prompt",{sessionId:e}),await this.appSyncClient.createEvent({sessionId:e,type:a.EventType.NOTIFICATION,source:a.EventSource.DESKTOP,content:"Failed to send prompt to Claude Code",metadata:{error:!0}})}catch(o){n.error("Failed to execute mobile prompt:",o)}}};async function te(){let u=process.argv[2]||process.env.CLAUDE_SESSION_ID;u?n.info(`Starting MCP server for session: ${u}`):n.info("Starting MCP server without initial session ID (will be set on SessionStart)");let e=new A(u);try{await e.start();let s=e.getPort();console.log(`PORT=${s}`);let t=!1,i=async r=>{if(t){n.info("Shutdown already in progress, ignoring additional signal");return}t=!0,n.info(`Received ${r} signal, stopping server...`);try{await e.stop(),n.info("Graceful shutdown completed"),process.exit(0)}catch(o){n.error("Error during shutdown:",o),process.exit(1)}};process.on("SIGINT",()=>i("SIGINT")),process.on("SIGTERM",()=>i("SIGTERM")),process.on("SIGHUP",()=>i("SIGHUP")),process.on("uncaughtException",async r=>{n.error("Uncaught exception:",r),await i("uncaughtException")}),process.on("unhandledRejection",async r=>{n.error("Unhandled rejection:",r),await i("unhandledRejection")})}catch(s){n.error("Failed to start MCP Server:",s),process.exit(1)}}function B(u,e=3){let s=u.trim(),t=s.match(/^(\d+)$/);if(t){let r=parseInt(t[1]);if(r>=1&&r<=e)return{action:"select_option",option:t[1]}}let i=s.match(/^(\d+)[,.:;\-\s\n]+(.+)$/s);if(i){let r=parseInt(i[1]);if(r>=1&&r<=e)return{action:"option_with_followup",option:i[1],followUpText:i[2].trim()}}return{action:"send_as_response"}}te().catch(u=>{n.error("Unhandled error in main:",u),process.exit(1)});0&&(module.exports={parseInteractivePromptInput});
17
+ Please analyze the attached file(s).`,n.info("Prompt updated with attachment paths",{attachmentCount:r.length,newPromptLength:t.length})}}this.trackMobilePrompt(e,t);try{if(await this.promptResponder.answerInteractivePrompt(e,t)){try{await this.appSyncClient.updateEventStatus({eventId:s.eventId,sessionId:s.sessionId,timestamp:s.timestamp,deliveryStatus:a.DeliveryStatus.EXECUTED}),n.info("Event marked as EXECUTED",{eventId:s.eventId})}catch(c){n.warn("Failed to mark event as EXECUTED",{eventId:s.eventId,error:c})}n.info("Mobile prompt sent successfully",{sessionId:e});let p=r.length>0?`Prompt with ${r.length} attachment(s) sent to Claude Code`:`Prompt "${t.substring(0,50)}${t.length>50?"...":""}" sent to Claude Code`;await this.appSyncClient.createEvent({sessionId:e,type:a.EventType.NOTIFICATION,source:a.EventSource.DESKTOP,content:p,metadata:{mobilePrompt:!0,attachmentCount:r.length}})}else n.error("Failed to send mobile prompt",{sessionId:e}),await this.appSyncClient.createEvent({sessionId:e,type:a.EventType.NOTIFICATION,source:a.EventSource.DESKTOP,content:"Failed to send prompt to Claude Code",metadata:{error:!0}})}catch(o){n.error("Failed to execute mobile prompt:",o)}}};async function te(){let u=process.argv[2]||process.env.CLAUDE_SESSION_ID;u?n.info(`Starting MCP server for session: ${u}`):n.info("Starting MCP server without initial session ID (will be set on SessionStart)");let e=new k(u);try{await e.start();let s=e.getPort();console.log(`PORT=${s}`);let t=!1,i=async r=>{if(t){n.info("Shutdown already in progress, ignoring additional signal");return}t=!0,n.info(`Received ${r} signal, stopping server...`);try{await e.stop(),n.info("Graceful shutdown completed"),process.exit(0)}catch(o){n.error("Error during shutdown:",o),process.exit(1)}};process.on("SIGINT",()=>i("SIGINT")),process.on("SIGTERM",()=>i("SIGTERM")),process.on("SIGHUP",()=>i("SIGHUP")),process.on("uncaughtException",async r=>{n.error("Uncaught exception:",r),await i("uncaughtException")}),process.on("unhandledRejection",async r=>{n.error("Unhandled rejection:",r),await i("unhandledRejection")})}catch(s){n.error("Failed to start MCP Server:",s),process.exit(1)}}function B(u,e=3){let s=u.trim(),t=s.match(/^(\d+)$/);if(t){let r=parseInt(t[1]);if(r>=1&&r<=e)return{action:"select_option",option:t[1]}}let i=s.match(/^(\d+)[,.:;\-\s\n]+(.+)$/s);if(i){let r=parseInt(i[1]);if(r>=1&&r<=e)return{action:"option_with_followup",option:i[1],followUpText:i[2].trim()}}return{action:"send_as_response"}}te().catch(u=>{n.error("Unhandled error in main:",u),process.exit(1)});0&&(module.exports={parseInteractivePromptInput});
@@ -114,6 +114,45 @@ export declare class AppSyncClient {
114
114
  * List events for a session
115
115
  */
116
116
  listEvents(sessionId: string, source?: EventSource, limit?: number): Promise<Event[]>;
117
+ /**
118
+ * List the authenticated user's sessions. Paginates automatically
119
+ * via nextToken so callers always get the complete set.
120
+ */
121
+ listSessions(limit?: number): Promise<Array<{
122
+ sessionId: string;
123
+ agentType: string;
124
+ status: string;
125
+ lastHeartbeatAt: string | null;
126
+ }>>;
127
+ /**
128
+ * Mark stale ACTIVE sessions of a given agentType INACTIVE so they
129
+ * stop appearing in the mobile app's session list. Called at daemon
130
+ * startup to clean up after daemons that died without running their
131
+ * graceful shutdown (crash, auth-loop death, force-kill, power loss).
132
+ *
133
+ * Staleness rule: lastHeartbeatAt is older than `staleThresholdMs`
134
+ * (default 15 min — a conservative ~7.5× the 2-min heartbeat
135
+ * interval, giving legitimately-active daemons on other machines
136
+ * ample margin before we consider their session abandoned).
137
+ *
138
+ * Safety:
139
+ * - Only sessions with status === 'ACTIVE' are candidates.
140
+ * - Sessions explicitly listed in `excludeSessionIds` are skipped
141
+ * (caller can pass the session the daemon is about to attach to
142
+ * if the ID is known before the sweep).
143
+ * - Absent `lastHeartbeatAt` (never-heartbeated sessions — should
144
+ * only happen for rows created within the last few seconds)
145
+ * treats the session as fresh and skips.
146
+ * - updateSession failures are logged as warnings and don't abort
147
+ * the sweep — best-effort cleanup.
148
+ *
149
+ * Returns the number of sessions actually marked INACTIVE.
150
+ */
151
+ sweepOrphanSessions(opts: {
152
+ agentType: string;
153
+ staleThresholdMs?: number;
154
+ excludeSessionIds?: string[];
155
+ }): Promise<number>;
117
156
  /**
118
157
  * List user device keys
119
158
  */
@@ -2,6 +2,14 @@ export declare const queries: {
2
2
  getSession: string;
3
3
  listEvents: string;
4
4
  listUserDeviceKeys: string;
5
+ /**
6
+ * Minimal session listing used by the orphan-sweep path. Only the
7
+ * fields needed to decide whether a session row is stale — sessionId
8
+ * for the INACTIVE mutation, agentType for the per-plugin filter,
9
+ * status to skip non-ACTIVE rows, and lastHeartbeatAt for the age
10
+ * check.
11
+ */
12
+ listSessions: string;
5
13
  };
6
14
  export declare const mutations: {
7
15
  createSession: string;
@@ -1,9 +1,9 @@
1
- "use strict";var mt=Object.create;var ie=Object.defineProperty;var ht=Object.getOwnPropertyDescriptor;var ft=Object.getOwnPropertyNames;var vt=Object.getPrototypeOf,St=Object.prototype.hasOwnProperty;var D=(n,e)=>()=>(n&&(e=n(n=0)),e);var Ne=(n,e)=>{for(var t in e)ie(n,t,{get:e[t],enumerable:!0})},$e=(n,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of ft(e))!St.call(n,i)&&i!==t&&ie(n,i,{get:()=>e[i],enumerable:!(r=ht(e,i))||r.enumerable});return n};var m=(n,e,t)=>(t=n!=null?mt(vt(n)):{},$e(e||!n||!n.__esModule?ie(t,"default",{value:n,enumerable:!0}):t,n)),wt=n=>$e(ie({},"__esModule",{value:!0}),n);function kt(n,e){if(e instanceof Error){let t={name:e.name,message:e.message};e.stack&&(t.stack=e.stack);for(let r of Object.keys(e))r in t||(t[r]=e[r]);return t}return e}function ue(n){return new O(n)}var F,se,Ue,Le,O,a,Be=D(()=>{"use strict";F=m(require("fs")),se=m(require("path")),Ue=m(require("os")),Le={debug:0,info:1,warn:2,error:3};O=class{constructor(e){this.name=e.name,this.logFile=e.logFile,this.level=e.level||"info",this.enableConsole=e.console??!1,this.logFile&&this.ensureLogDir()}ensureLogDir(){if(this.logFile){let e=se.dirname(this.logFile);F.existsSync(e)||F.mkdirSync(e,{recursive:!0})}}shouldLog(e){return Le[e]>=Le[this.level]}formatMessage(e,t,r){let i=new Date().toISOString(),s=e.toUpperCase().padEnd(5),o=`[${i}] [${s}] [${this.name}] ${t}`;return r!==void 0&&(r instanceof Error?(o+=` ${r.name}: ${r.message}`,r.stack&&(o+=`
1
+ "use strict";var mt=Object.create;var ie=Object.defineProperty;var ht=Object.getOwnPropertyDescriptor;var ft=Object.getOwnPropertyNames;var vt=Object.getPrototypeOf,St=Object.prototype.hasOwnProperty;var D=(n,e)=>()=>(n&&(e=n(n=0)),e);var Ne=(n,e)=>{for(var t in e)ie(n,t,{get:e[t],enumerable:!0})},$e=(n,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of ft(e))!St.call(n,i)&&i!==t&&ie(n,i,{get:()=>e[i],enumerable:!(r=ht(e,i))||r.enumerable});return n};var m=(n,e,t)=>(t=n!=null?mt(vt(n)):{},$e(e||!n||!n.__esModule?ie(t,"default",{value:n,enumerable:!0}):t,n)),wt=n=>$e(ie({},"__esModule",{value:!0}),n);function kt(n,e){if(e instanceof Error){let t={name:e.name,message:e.message};e.stack&&(t.stack=e.stack);for(let r of Object.keys(e))r in t||(t[r]=e[r]);return t}return e}function ge(n){return new N(n)}var F,se,Ue,Le,N,c,Me=D(()=>{"use strict";F=m(require("fs")),se=m(require("path")),Ue=m(require("os")),Le={debug:0,info:1,warn:2,error:3};N=class{constructor(e){this.name=e.name,this.logFile=e.logFile,this.level=e.level||"info",this.enableConsole=e.console??!1,this.logFile&&this.ensureLogDir()}ensureLogDir(){if(this.logFile){let e=se.dirname(this.logFile);F.existsSync(e)||F.mkdirSync(e,{recursive:!0})}}shouldLog(e){return Le[e]>=Le[this.level]}formatMessage(e,t,r){let i=new Date().toISOString(),s=e.toUpperCase().padEnd(5),o=`[${i}] [${s}] [${this.name}] ${t}`;return r!==void 0&&(r instanceof Error?(o+=` ${r.name}: ${r.message}`,r.stack&&(o+=`
2
2
  ${r.stack}`)):typeof r=="object"?o+=` ${JSON.stringify(r,kt)}`:o+=` ${r}`),o}log(e,t,r){if(!this.shouldLog(e))return;let i=this.formatMessage(e,t,r);if(this.logFile)try{F.appendFileSync(this.logFile,i+`
3
- `)}catch{}if(this.enableConsole)switch(e){case"error":console.error(i);break;case"warn":console.warn(i);break;default:console.log(i)}}debug(e,t){this.log("debug",e,t)}info(e,t){this.log("info",e,t)}warn(e,t){this.log("warn",e,t)}error(e,t){this.log("error",e,t)}setLevel(e){this.level=e}};a=new O({name:"codevibe-core",logFile:se.join(Ue.tmpdir(),"codevibe-core.log"),level:"info"})});var N=D(()=>{"use strict";Be()});function bt(n){for(let e of n)try{process.stderr.write(e+`
4
- `)}catch{}}function Et(){ge=me.join(Me.homedir(),".codevibe");try{_.mkdirSync(ge,{recursive:!0,mode:448})}catch{}$="file"}function It(){if($!==null||ye!==null)return;let optedIn=process.env.CODEVIBE_ALLOW_FILE_KEYCHAIN==="1";if(optedIn){bt(["","\u26A0 CodeVibe: file-based credential storage selected (CODEVIBE_ALLOW_FILE_KEYCHAIN=1).","\u26A0 Location: ~/.codevibe/ (directory 0700, files 0600)","\u26A0 Trust level: equivalent to ~/.ssh/id_rsa \u2014 weaker than OS keyring.","\u26A0 To use the OS keyring instead, unset CODEVIBE_ALLOW_FILE_KEYCHAIN and","\u26A0 install libsecret-1-0 + a running keyring daemon (Linux) or use the","\u26A0 native Keychain (macOS) / Credential Manager (Windows).",""]),a.warn("[keychain-backend] Using file-based storage at ~/.codevibe (CODEVIBE_ALLOW_FILE_KEYCHAIN=1 explicit opt-in)"),Et();return}let keytarLoadError=null;try{let nodeRequire=eval("require");C=nodeRequire("keytar")}catch(n){keytarLoadError=n instanceof Error?n.message:String(n),C=null}if(C){$="keytar",a.info("[keychain-backend] Using keytar (OS-native keyring)");return}ye=new oe(["CodeVibe could not load the OS-native keyring (keytar).",`Reason: ${keytarLoadError??"unknown"}`,"","Options to fix this:"," 1. (Linux) Install libsecret and a keyring daemon:"," sudo apt install libsecret-1-0 gnome-keyring"," Then unlock the keyring for your user session.",""," 2. (Headless / CI / Docker) Opt in to file-based credential"," storage at ~/.codevibe/ (0600 files). This is equivalent"," in trust to ~/.ssh/id_rsa \u2014 not the OS keyring:"," export CODEVIBE_ALLOW_FILE_KEYCHAIN=1"].join(`
5
- `))}function At(n){return n.replace(/[^a-zA-Z0-9._-]/g,"_")}function We(n){return me.join(ge,`${At(n)}.json`)}function he(n){try{let e=_.readFileSync(We(n),"utf-8"),t=JSON.parse(e);return t&&typeof t=="object"?t:{}}catch{return{}}}function Fe(n,e){let t=We(n);_.writeFileSync(t,JSON.stringify(e,null,2),{mode:384});try{_.chmodSync(t,384)}catch{}}function fe(){if(It(),$===null)throw ye??new oe("Keychain backend not initialized")}async function ve(n,e){return fe(),$==="keytar"&&C?C.getPassword(n,e):he(n)[e]??null}async function Se(n,e,t){if(fe(),$==="keytar"&&C){await C.setPassword(n,e,t);return}let r=he(n);r[e]=t,Fe(n,r)}async function we(n,e){if(fe(),$==="keytar"&&C)return C.deletePassword(n,e);let t=he(n);return e in t?(delete t[e],Fe(n,t),!0):!1}var Me,me,_,oe,$,C,ge,ye,qe=D(()=>{"use strict";Me=m(require("os")),me=m(require("path")),_=m(require("fs"));N();oe=class extends Error{constructor(e){super(e),this.name="KeychainBackendUnavailableError"}},$=null,C=null,ge="",ye=null});var I,L,ke,xt,q,k,He=D(()=>{"use strict";I=m(require("crypto")),L=class extends Error{constructor(e){super(e),this.name="CryptoError"}},ke=1,xt="CodeVibe E2E v1",q=class n{constructor(){}static getInstance(){return n.instance||(n.instance=new n),n.instance}generateKeyPair(){let e=I.createECDH("prime256v1");e.generateKeys();let r=e.getPublicKey().subarray(1).toString("base64");return{privateKey:e.getPrivateKey().toString("base64"),publicKey:r}}generateSessionKey(){return I.randomBytes(32).toString("base64")}deriveSharedKey(e,t){try{let r=I.createECDH("prime256v1"),i=Buffer.from(e,"base64");r.setPrivateKey(i);let s=Buffer.concat([Buffer.from([4]),Buffer.from(t,"base64")]),o=r.computeSecret(s),c=I.hkdfSync("sha256",o,Buffer.alloc(0),Buffer.from(xt,"utf8"),32);return Buffer.from(c)}catch(r){throw new L(`Failed to derive shared key: ${r}`)}}encryptSessionKey(e,t){let r=this.generateKeyPair(),i=this.deriveSharedKey(r.privateKey,t),s=Buffer.from(e,"base64");return{encryptedKey:this.encrypt(s,i).toString("base64"),ephemeralPublicKey:r.publicKey}}decryptSessionKey(e,t){let r=this.deriveSharedKey(t,e.ephemeralPublicKey),i=Buffer.from(e.encryptedKey,"base64");return this.decrypt(i,r).toString("base64")}encryptContent(e,t){let r=Buffer.from(t,"base64"),i=Buffer.from(e,"utf8");return this.encrypt(i,r).toString("base64")}decryptContent(e,t){let r=Buffer.from(t,"base64"),i=Buffer.from(e,"base64");return this.decrypt(i,r).toString("utf8")}encryptMetadata(e,t){let r=JSON.stringify(e);return this.encryptContent(r,t)}decryptMetadata(e,t){let r=this.decryptContent(e,t);return JSON.parse(r)}encryptData(e,t){let r=Buffer.from(t,"base64");return this.encrypt(e,r)}decryptData(e,t){let r=Buffer.from(t,"base64");return this.decrypt(e,r)}encrypt(e,t){let r=I.randomBytes(12),i=I.createCipheriv("aes-256-gcm",t,r),s=Buffer.concat([i.update(e),i.final()]),o=i.getAuthTag();return Buffer.concat([r,s,o])}decrypt(e,t){let r=e.subarray(0,12),i=e.subarray(e.length-16),s=e.subarray(12,e.length-16),o=I.createDecipheriv("aes-256-gcm",t,r);o.setAuthTag(i);try{return Buffer.concat([o.update(s),o.final()])}catch{throw new L("Decryption failed: Invalid ciphertext or authentication tag")}}serializePrivateKey(e){return e}deserializePrivateKey(e){return e}},k=q.getInstance()});var G=D(()=>{"use strict";He()});function b(){let n=process.env.ENVIRONMENT;return n==="development"||n==="production"?n:"production"}function ce(n){let e=n||b();return ae={...U[e],aws:{...U[e].aws,region:process.env.AWS_REGION||U[e].aws.region,appsyncUrl:process.env.APPSYNC_URL||U[e].aws.appsyncUrl,cognitoUserPoolId:process.env.COGNITO_USER_POOL_ID||U[e].aws.cognitoUserPoolId,cognitoClientId:process.env.COGNITO_CLIENT_ID||U[e].aws.cognitoClientId,cognitoDomain:process.env.COGNITO_DOMAIN||U[e].aws.cognitoDomain}},je=!0,ae}function f(){return(!je||!ae)&&ce(),ae}var Y,X,U,ae,je,Ve=D(()=>{"use strict";Y=m(require("os")),X=m(require("path")),U={development:{environment:"development",aws:{region:"us-east-1",appsyncUrl:"https://te6rjr37sbfpjc4fiunmb2tgy4.appsync-api.us-east-1.amazonaws.com/graphql",cognitoUserPoolId:"us-east-1_yVwWDPvvJ",cognitoClientId:"e9r5apv6v5uui3l928r2ris0r",cognitoDomain:"codevibe-development.auth.us-east-1.amazoncognito.com"},keychain:{serviceName:"ai.quantiya.app.codevibe"},server:{port:3456,host:"127.0.0.1",dynamicPort:!0},claude:{command:"claude",defaultTimeout:6e4},codex:{command:"codex",defaultTimeout:6e4,sessionsDir:X.default.join(Y.default.homedir(),".codex","sessions"),approvalTimeoutMs:5e3},gemini:{command:"gemini",defaultTimeout:6e4,transcriptDir:X.default.join(Y.default.homedir(),".gemini","tmp")}},production:{environment:"production",aws:{region:"us-east-1",appsyncUrl:"https://jwhyxq4sgrgcdosewp5k4ns5ca.appsync-api.us-east-1.amazonaws.com/graphql",cognitoUserPoolId:"us-east-1_mNRO0j5og",cognitoClientId:"5p04dbc9ojptc5r8n7605fg78f",cognitoDomain:"codevibe-production.auth.us-east-1.amazoncognito.com"},keychain:{serviceName:"ai.quantiya.app.codevibe"},server:{port:3456,host:"127.0.0.1",dynamicPort:!0},claude:{command:"claude",defaultTimeout:6e4},codex:{command:"codex",defaultTimeout:6e4,sessionsDir:X.default.join(Y.default.homedir(),".codex","sessions"),approvalTimeoutMs:5e3},gemini:{command:"gemini",defaultTimeout:6e4,transcriptDir:X.default.join(Y.default.homedir(),".gemini","tmp")}}},ae=null,je=!1});var H=D(()=>{"use strict";Ve()});var de,Je,K,be,Dt,B,g,ze=D(()=>{"use strict";de=m(require("os")),Je=require("uuid");qe();G();H();N();K=class extends Error{constructor(e){super(e),this.name="KeychainError"}},be="device-identity",Dt="tokens-",B=class n{constructor(){this.deviceIdentity=null;this.sessionKeyCache=new Map;this.isRegistered=!1;this._serviceName=null}get serviceName(){return this._serviceName||(this._serviceName=f().keychain.serviceName),this._serviceName}static getInstance(){return n.instance||(n.instance=new n),n.instance}async getDeviceIdentity(){if(this.deviceIdentity)return this.deviceIdentity;let e=await ve(this.serviceName,be);return e?(this.deviceIdentity=JSON.parse(e),a.info(`[KeychainManager] Loaded device identity: ${this.deviceIdentity.deviceId}`),this.deviceIdentity):null}async setDeviceIdentity(e){try{await Se(this.serviceName,be,JSON.stringify(e)),this.deviceIdentity=e,a.info(`[KeychainManager] Saved device identity: ${e.deviceId}`)}catch(t){throw a.error(`[KeychainManager] Failed to save device identity: ${t}`),new K(`Failed to save device identity: ${t}`)}}async getOrCreateDeviceIdentity(){let e=await this.getDeviceIdentity();if(e)return e;let t=k.generateKeyPair();return e={deviceId:(0,Je.v4)().toUpperCase(),privateKey:t.privateKey,publicKey:t.publicKey,createdAt:new Date().toISOString()},await this.setDeviceIdentity(e),a.info(`[KeychainManager] Generated new device identity: ${e.deviceId}`),e}async getDeviceId(){return(await this.getOrCreateDeviceIdentity()).deviceId}async getDevicePublicKey(){return(await this.getOrCreateDeviceIdentity()).publicKey}async getDevicePrivateKey(){return(await this.getOrCreateDeviceIdentity()).privateKey}async hasDeviceIdentity(){return await this.getDeviceIdentity()!==null}async deleteDeviceIdentity(){try{await we(this.serviceName,be),this.deviceIdentity=null,this.sessionKeyCache.clear(),this.isRegistered=!1,a.info("[KeychainManager] Deleted device identity")}catch(e){throw a.error(`[KeychainManager] Failed to delete device identity: ${e}`),new K(`Failed to delete device identity: ${e}`)}}getTokenAccount(e){return`${Dt}${e}`}async getTokens(e="production"){let t=await ve(this.serviceName,this.getTokenAccount(e));if(!t)return null;let r=JSON.parse(t);return a.debug(`[KeychainManager] Loaded tokens for ${e}`),r}async setTokens(e,t="production"){try{await Se(this.serviceName,this.getTokenAccount(t),JSON.stringify(e)),a.info(`[KeychainManager] Saved tokens for ${t}`,{userId:e.userId,email:e.email})}catch(r){throw a.error(`[KeychainManager] Failed to save tokens: ${r}`),new K(`Failed to save tokens: ${r}`)}}async deleteTokens(e="production"){try{let t=await we(this.serviceName,this.getTokenAccount(e));return t&&a.info(`[KeychainManager] Deleted tokens for ${e}`),t}catch(t){return a.error(`[KeychainManager] Failed to delete tokens: ${t}`),!1}}isTokenExpired(e){return Date.now()>=e.expiresAt-3e5}async getSessionKey(e,t){let r=this.sessionKeyCache.get(e);if(r)return r;if(!t||t.length===0)return null;let i=await this.getDeviceId(),s=t.find(d=>d.deviceId===i);if(!s)return a.warn(`[KeychainManager] Device ${i} not found in encryptedKeys`),null;let o=await this.getDevicePrivateKey(),c=k.decryptSessionKey(s,o);return this.sessionKeyCache.set(e,c),a.info(`[KeychainManager] Decrypted and cached session key for ${e}`),c}createSessionKey(e){let t=k.generateSessionKey(),r=e.map(i=>{let s=k.encryptSessionKey(t,i.publicKey);return{deviceId:i.deviceId,encryptedKey:s.encryptedKey,ephemeralPublicKey:s.ephemeralPublicKey}});return a.info(`[KeychainManager] Created session key for ${e.length} devices`),{sessionKey:t,encryptedKeys:r}}cacheSessionKey(e,t){this.sessionKeyCache.set(e,t)}getCachedSessionKey(e){return this.sessionKeyCache.get(e)??null}getCachedSessionIds(){return Array.from(this.sessionKeyCache.keys())}clearSessionKey(e){this.sessionKeyCache.delete(e)}clearAllSessionKeys(){this.sessionKeyCache.clear()}getIsRegistered(){return this.isRegistered}setIsRegistered(e){this.isRegistered=e}getDeviceName(){return de.hostname()||"CLI Client"}getDevicePlatform(){let e=de.platform();return e==="darwin"?"MACOS":e==="linux"?"LINUX":e==="win32"?"WINDOWS":"CLI"}async clearAllData(){await this.deleteDeviceIdentity(),await this.deleteTokens("development"),await this.deleteTokens("production"),this.sessionKeyCache.clear(),this.isRegistered=!1,a.info("[KeychainManager] Cleared all data")}},g=B.getInstance()});var Ge={};Ne(Ge,{KeychainError:()=>K,KeychainManager:()=>B,keychainManager:()=>g});var P=D(()=>{"use strict";ze()});var Yt={};Ne(Yt,{AgentType:()=>et,AppSyncClient:()=>ee,AuthService:()=>J,CryptoError:()=>L,CryptoService:()=>q,DeliveryStatus:()=>Ze,ENCRYPTION_VERSION:()=>ke,EventSource:()=>Ee,EventType:()=>Qe,KeychainError:()=>K,KeychainManager:()=>B,Logger:()=>O,SessionStatus:()=>Ie,authService:()=>R,createLogger:()=>ue,cryptoService:()=>k,errorWasBeaconed:()=>re,fireAuthCompletedBeacon:()=>te,fireAuthFailedBeacon:()=>v,getConfig:()=>f,getEnvironment:()=>b,getErrorReason:()=>Te,keychainManager:()=>g,loadConfig:()=>ce,logger:()=>a,markErrorBeaconed:()=>E,mutations:()=>T,normalizeSnapshot:()=>Re,parseInteractivePrompt:()=>pt,prepareSessionEncryption:()=>pe,queries:()=>M,registerDeviceEncryptionKey:()=>Oe,rekeySessionForNewDevices:()=>W,resumeOrCreateSession:()=>_e,runAuthCli:()=>le,startDeviceKeyWatcher:()=>Pe,subscriptions:()=>V});module.exports=wt(Yt);P();G();var Q=m(require("ws")),Z=require("uuid");H();N();P();var Ye=m(require("dns")),Xe=m(require("fs"));if(Ct())try{Ye.setDefaultResultOrder("ipv4first")}catch{}function Ct(){if(process.platform!=="linux")return!1;try{let n=Xe.readFileSync("/proc/sys/kernel/osrelease","utf8");return/microsoft|wsl/i.test(n)}catch{return!1}}async function j(n,e,t){try{return await fetch(n,e)}catch(r){let i=r?.cause?.code,s=r?.cause?.message,o=i||s||r?.message||"unknown",c=Kt(i),d=t?`${t}: `:"",p=`Node ${process.version} on ${process.platform}`,y=[`${d}Cannot reach ${n}`,` Underlying error: ${o}`];c&&y.push(` Suggested fix: ${c}`),y.push(` Platform: ${p}`);let u=new Error(y.join(`
6
- `));throw u.cause=r,u}}function Kt(n){if(!n)return null;switch(n){case"ENOTFOUND":case"EAI_AGAIN":return'DNS resolution failed. On WSL Ubuntu, check /etc/resolv.conf, or try running with NODE_OPTIONS="--dns-result-order=ipv4first".';case"ETIMEDOUT":case"ECONNREFUSED":case"ECONNRESET":case"EHOSTUNREACH":case"ENETUNREACH":return`Network unreachable. On WSL Ubuntu, try NODE_OPTIONS="--dns-result-order=ipv4first" (WSL's IPv6 is often broken). If behind a corporate proxy, set HTTPS_PROXY.`;case"CERT_HAS_EXPIRED":case"CERT_NOT_YET_VALID":return"TLS certificate time error \u2014 likely system clock drift. On WSL, run `sudo hwclock -s`, or shut down WSL from PowerShell with `wsl --shutdown` and restart.";case"UNABLE_TO_GET_ISSUER_CERT_LOCALLY":case"SELF_SIGNED_CERT_IN_CHAIN":case"UNABLE_TO_VERIFY_LEAF_SIGNATURE":case"DEPTH_ZERO_SELF_SIGNED_CERT":return"Corporate HTTPS proxy detected \u2014 the TLS cert is not trusted by Node. Set NODE_EXTRA_CA_CERTS=/path/to/corporate-ca.pem, or configure HTTPS_PROXY if a proxy is required.";default:return null}}var M={getSession:`
3
+ `)}catch{}if(this.enableConsole)switch(e){case"error":console.error(i);break;case"warn":console.warn(i);break;default:console.log(i)}}debug(e,t){this.log("debug",e,t)}info(e,t){this.log("info",e,t)}warn(e,t){this.log("warn",e,t)}error(e,t){this.log("error",e,t)}setLevel(e){this.level=e}};c=new N({name:"codevibe-core",logFile:se.join(Ue.tmpdir(),"codevibe-core.log"),level:"info"})});var $=D(()=>{"use strict";Me()});function bt(n){for(let e of n)try{process.stderr.write(e+`
4
+ `)}catch{}}function It(){ye=he.join(Be.homedir(),".codevibe");try{_.mkdirSync(ye,{recursive:!0,mode:448})}catch{}L="file"}function Et(){if(L!==null||me!==null)return;let optedIn=process.env.CODEVIBE_ALLOW_FILE_KEYCHAIN==="1";if(optedIn){bt(["","\u26A0 CodeVibe: file-based credential storage selected (CODEVIBE_ALLOW_FILE_KEYCHAIN=1).","\u26A0 Location: ~/.codevibe/ (directory 0700, files 0600)","\u26A0 Trust level: equivalent to ~/.ssh/id_rsa \u2014 weaker than OS keyring.","\u26A0 To use the OS keyring instead, unset CODEVIBE_ALLOW_FILE_KEYCHAIN and","\u26A0 install libsecret-1-0 + a running keyring daemon (Linux) or use the","\u26A0 native Keychain (macOS) / Credential Manager (Windows).",""]),c.warn("[keychain-backend] Using file-based storage at ~/.codevibe (CODEVIBE_ALLOW_FILE_KEYCHAIN=1 explicit opt-in)"),It();return}let keytarLoadError=null;try{let nodeRequire=eval("require");C=nodeRequire("keytar")}catch(n){keytarLoadError=n instanceof Error?n.message:String(n),C=null}if(C){L="keytar",c.info("[keychain-backend] Using keytar (OS-native keyring)");return}me=new oe(["CodeVibe could not load the OS-native keyring (keytar).",`Reason: ${keytarLoadError??"unknown"}`,"","Options to fix this:"," 1. (Linux) Install libsecret and a keyring daemon:"," sudo apt install libsecret-1-0 gnome-keyring"," Then unlock the keyring for your user session.",""," 2. (Headless / CI / Docker) Opt in to file-based credential"," storage at ~/.codevibe/ (0600 files). This is equivalent"," in trust to ~/.ssh/id_rsa \u2014 not the OS keyring:"," export CODEVIBE_ALLOW_FILE_KEYCHAIN=1"].join(`
5
+ `))}function At(n){return n.replace(/[^a-zA-Z0-9._-]/g,"_")}function We(n){return he.join(ye,`${At(n)}.json`)}function fe(n){try{let e=_.readFileSync(We(n),"utf-8"),t=JSON.parse(e);return t&&typeof t=="object"?t:{}}catch{return{}}}function Fe(n,e){let t=We(n);_.writeFileSync(t,JSON.stringify(e,null,2),{mode:384});try{_.chmodSync(t,384)}catch{}}function ve(){if(Et(),L===null)throw me??new oe("Keychain backend not initialized")}async function Se(n,e){return ve(),L==="keytar"&&C?C.getPassword(n,e):fe(n)[e]??null}async function we(n,e,t){if(ve(),L==="keytar"&&C){await C.setPassword(n,e,t);return}let r=fe(n);r[e]=t,Fe(n,r)}async function ke(n,e){if(ve(),L==="keytar"&&C)return C.deletePassword(n,e);let t=fe(n);return e in t?(delete t[e],Fe(n,t),!0):!1}var Be,he,_,oe,L,C,ye,me,He=D(()=>{"use strict";Be=m(require("os")),he=m(require("path")),_=m(require("fs"));$();oe=class extends Error{constructor(e){super(e),this.name="KeychainBackendUnavailableError"}},L=null,C=null,ye="",me=null});var E,U,be,xt,H,k,qe=D(()=>{"use strict";E=m(require("crypto")),U=class extends Error{constructor(e){super(e),this.name="CryptoError"}},be=1,xt="CodeVibe E2E v1",H=class n{constructor(){}static getInstance(){return n.instance||(n.instance=new n),n.instance}generateKeyPair(){let e=E.createECDH("prime256v1");e.generateKeys();let r=e.getPublicKey().subarray(1).toString("base64");return{privateKey:e.getPrivateKey().toString("base64"),publicKey:r}}generateSessionKey(){return E.randomBytes(32).toString("base64")}deriveSharedKey(e,t){try{let r=E.createECDH("prime256v1"),i=Buffer.from(e,"base64");r.setPrivateKey(i);let s=Buffer.concat([Buffer.from([4]),Buffer.from(t,"base64")]),o=r.computeSecret(s),a=E.hkdfSync("sha256",o,Buffer.alloc(0),Buffer.from(xt,"utf8"),32);return Buffer.from(a)}catch(r){throw new U(`Failed to derive shared key: ${r}`)}}encryptSessionKey(e,t){let r=this.generateKeyPair(),i=this.deriveSharedKey(r.privateKey,t),s=Buffer.from(e,"base64");return{encryptedKey:this.encrypt(s,i).toString("base64"),ephemeralPublicKey:r.publicKey}}decryptSessionKey(e,t){let r=this.deriveSharedKey(t,e.ephemeralPublicKey),i=Buffer.from(e.encryptedKey,"base64");return this.decrypt(i,r).toString("base64")}encryptContent(e,t){let r=Buffer.from(t,"base64"),i=Buffer.from(e,"utf8");return this.encrypt(i,r).toString("base64")}decryptContent(e,t){let r=Buffer.from(t,"base64"),i=Buffer.from(e,"base64");return this.decrypt(i,r).toString("utf8")}encryptMetadata(e,t){let r=JSON.stringify(e);return this.encryptContent(r,t)}decryptMetadata(e,t){let r=this.decryptContent(e,t);return JSON.parse(r)}encryptData(e,t){let r=Buffer.from(t,"base64");return this.encrypt(e,r)}decryptData(e,t){let r=Buffer.from(t,"base64");return this.decrypt(e,r)}encrypt(e,t){let r=E.randomBytes(12),i=E.createCipheriv("aes-256-gcm",t,r),s=Buffer.concat([i.update(e),i.final()]),o=i.getAuthTag();return Buffer.concat([r,s,o])}decrypt(e,t){let r=e.subarray(0,12),i=e.subarray(e.length-16),s=e.subarray(12,e.length-16),o=E.createDecipheriv("aes-256-gcm",t,r);o.setAuthTag(i);try{return Buffer.concat([o.update(s),o.final()])}catch{throw new U("Decryption failed: Invalid ciphertext or authentication tag")}}serializePrivateKey(e){return e}deserializePrivateKey(e){return e}},k=H.getInstance()});var G=D(()=>{"use strict";qe()});function b(){let n=process.env.ENVIRONMENT;return n==="development"||n==="production"?n:"production"}function ce(n){let e=n||b();return ae={...M[e],aws:{...M[e].aws,region:process.env.AWS_REGION||M[e].aws.region,appsyncUrl:process.env.APPSYNC_URL||M[e].aws.appsyncUrl,cognitoUserPoolId:process.env.COGNITO_USER_POOL_ID||M[e].aws.cognitoUserPoolId,cognitoClientId:process.env.COGNITO_CLIENT_ID||M[e].aws.cognitoClientId,cognitoDomain:process.env.COGNITO_DOMAIN||M[e].aws.cognitoDomain}},je=!0,ae}function f(){return(!je||!ae)&&ce(),ae}var Y,X,M,ae,je,Ve=D(()=>{"use strict";Y=m(require("os")),X=m(require("path")),M={development:{environment:"development",aws:{region:"us-east-1",appsyncUrl:"https://te6rjr37sbfpjc4fiunmb2tgy4.appsync-api.us-east-1.amazonaws.com/graphql",cognitoUserPoolId:"us-east-1_yVwWDPvvJ",cognitoClientId:"e9r5apv6v5uui3l928r2ris0r",cognitoDomain:"codevibe-development.auth.us-east-1.amazoncognito.com"},keychain:{serviceName:"ai.quantiya.app.codevibe"},server:{port:3456,host:"127.0.0.1",dynamicPort:!0},claude:{command:"claude",defaultTimeout:6e4},codex:{command:"codex",defaultTimeout:6e4,sessionsDir:X.default.join(Y.default.homedir(),".codex","sessions"),approvalTimeoutMs:5e3},gemini:{command:"gemini",defaultTimeout:6e4,transcriptDir:X.default.join(Y.default.homedir(),".gemini","tmp")}},production:{environment:"production",aws:{region:"us-east-1",appsyncUrl:"https://jwhyxq4sgrgcdosewp5k4ns5ca.appsync-api.us-east-1.amazonaws.com/graphql",cognitoUserPoolId:"us-east-1_mNRO0j5og",cognitoClientId:"5p04dbc9ojptc5r8n7605fg78f",cognitoDomain:"codevibe-production.auth.us-east-1.amazoncognito.com"},keychain:{serviceName:"ai.quantiya.app.codevibe"},server:{port:3456,host:"127.0.0.1",dynamicPort:!0},claude:{command:"claude",defaultTimeout:6e4},codex:{command:"codex",defaultTimeout:6e4,sessionsDir:X.default.join(Y.default.homedir(),".codex","sessions"),approvalTimeoutMs:5e3},gemini:{command:"gemini",defaultTimeout:6e4,transcriptDir:X.default.join(Y.default.homedir(),".gemini","tmp")}}},ae=null,je=!1});var q=D(()=>{"use strict";Ve()});var de,Je,K,Ie,Dt,B,g,ze=D(()=>{"use strict";de=m(require("os")),Je=require("uuid");He();G();q();$();K=class extends Error{constructor(e){super(e),this.name="KeychainError"}},Ie="device-identity",Dt="tokens-",B=class n{constructor(){this.deviceIdentity=null;this.sessionKeyCache=new Map;this.isRegistered=!1;this._serviceName=null}get serviceName(){return this._serviceName||(this._serviceName=f().keychain.serviceName),this._serviceName}static getInstance(){return n.instance||(n.instance=new n),n.instance}async getDeviceIdentity(){if(this.deviceIdentity)return this.deviceIdentity;let e=await Se(this.serviceName,Ie);return e?(this.deviceIdentity=JSON.parse(e),c.info(`[KeychainManager] Loaded device identity: ${this.deviceIdentity.deviceId}`),this.deviceIdentity):null}async setDeviceIdentity(e){try{await we(this.serviceName,Ie,JSON.stringify(e)),this.deviceIdentity=e,c.info(`[KeychainManager] Saved device identity: ${e.deviceId}`)}catch(t){throw c.error(`[KeychainManager] Failed to save device identity: ${t}`),new K(`Failed to save device identity: ${t}`)}}async getOrCreateDeviceIdentity(){let e=await this.getDeviceIdentity();if(e)return e;let t=k.generateKeyPair();return e={deviceId:(0,Je.v4)().toUpperCase(),privateKey:t.privateKey,publicKey:t.publicKey,createdAt:new Date().toISOString()},await this.setDeviceIdentity(e),c.info(`[KeychainManager] Generated new device identity: ${e.deviceId}`),e}async getDeviceId(){return(await this.getOrCreateDeviceIdentity()).deviceId}async getDevicePublicKey(){return(await this.getOrCreateDeviceIdentity()).publicKey}async getDevicePrivateKey(){return(await this.getOrCreateDeviceIdentity()).privateKey}async hasDeviceIdentity(){return await this.getDeviceIdentity()!==null}async deleteDeviceIdentity(){try{await ke(this.serviceName,Ie),this.deviceIdentity=null,this.sessionKeyCache.clear(),this.isRegistered=!1,c.info("[KeychainManager] Deleted device identity")}catch(e){throw c.error(`[KeychainManager] Failed to delete device identity: ${e}`),new K(`Failed to delete device identity: ${e}`)}}getTokenAccount(e){return`${Dt}${e}`}async getTokens(e="production"){let t=await Se(this.serviceName,this.getTokenAccount(e));if(!t)return null;let r=JSON.parse(t);return c.debug(`[KeychainManager] Loaded tokens for ${e}`),r}async setTokens(e,t="production"){try{await we(this.serviceName,this.getTokenAccount(t),JSON.stringify(e)),c.info(`[KeychainManager] Saved tokens for ${t}`,{userId:e.userId,email:e.email})}catch(r){throw c.error(`[KeychainManager] Failed to save tokens: ${r}`),new K(`Failed to save tokens: ${r}`)}}async deleteTokens(e="production"){try{let t=await ke(this.serviceName,this.getTokenAccount(e));return t&&c.info(`[KeychainManager] Deleted tokens for ${e}`),t}catch(t){return c.error(`[KeychainManager] Failed to delete tokens: ${t}`),!1}}isTokenExpired(e){return Date.now()>=e.expiresAt-3e5}async getSessionKey(e,t){let r=this.sessionKeyCache.get(e);if(r)return r;if(!t||t.length===0)return null;let i=await this.getDeviceId(),s=t.find(d=>d.deviceId===i);if(!s)return c.warn(`[KeychainManager] Device ${i} not found in encryptedKeys`),null;let o=await this.getDevicePrivateKey(),a=k.decryptSessionKey(s,o);return this.sessionKeyCache.set(e,a),c.info(`[KeychainManager] Decrypted and cached session key for ${e}`),a}createSessionKey(e){let t=k.generateSessionKey(),r=e.map(i=>{let s=k.encryptSessionKey(t,i.publicKey);return{deviceId:i.deviceId,encryptedKey:s.encryptedKey,ephemeralPublicKey:s.ephemeralPublicKey}});return c.info(`[KeychainManager] Created session key for ${e.length} devices`),{sessionKey:t,encryptedKeys:r}}cacheSessionKey(e,t){this.sessionKeyCache.set(e,t)}getCachedSessionKey(e){return this.sessionKeyCache.get(e)??null}getCachedSessionIds(){return Array.from(this.sessionKeyCache.keys())}clearSessionKey(e){this.sessionKeyCache.delete(e)}clearAllSessionKeys(){this.sessionKeyCache.clear()}getIsRegistered(){return this.isRegistered}setIsRegistered(e){this.isRegistered=e}getDeviceName(){return de.hostname()||"CLI Client"}getDevicePlatform(){let e=de.platform();return e==="darwin"?"MACOS":e==="linux"?"LINUX":e==="win32"?"WINDOWS":"CLI"}async clearAllData(){await this.deleteDeviceIdentity(),await this.deleteTokens("development"),await this.deleteTokens("production"),this.sessionKeyCache.clear(),this.isRegistered=!1,c.info("[KeychainManager] Cleared all data")}},g=B.getInstance()});var Ge={};Ne(Ge,{KeychainError:()=>K,KeychainManager:()=>B,keychainManager:()=>g});var P=D(()=>{"use strict";ze()});var Yt={};Ne(Yt,{AgentType:()=>et,AppSyncClient:()=>ee,AuthService:()=>J,CryptoError:()=>U,CryptoService:()=>H,DeliveryStatus:()=>Ze,ENCRYPTION_VERSION:()=>be,EventSource:()=>Ee,EventType:()=>Qe,KeychainError:()=>K,KeychainManager:()=>B,Logger:()=>N,SessionStatus:()=>le,authService:()=>R,createLogger:()=>ge,cryptoService:()=>k,errorWasBeaconed:()=>re,fireAuthCompletedBeacon:()=>te,fireAuthFailedBeacon:()=>v,getConfig:()=>f,getEnvironment:()=>b,getErrorReason:()=>Te,keychainManager:()=>g,loadConfig:()=>ce,logger:()=>c,markErrorBeaconed:()=>I,mutations:()=>T,normalizeSnapshot:()=>Re,parseInteractivePrompt:()=>pt,prepareSessionEncryption:()=>ue,queries:()=>O,registerDeviceEncryptionKey:()=>Oe,rekeySessionForNewDevices:()=>W,resumeOrCreateSession:()=>_e,runAuthCli:()=>pe,startDeviceKeyWatcher:()=>Pe,subscriptions:()=>V});module.exports=wt(Yt);P();G();var Q=m(require("ws")),Z=require("uuid");q();$();P();var Ye=m(require("dns")),Xe=m(require("fs"));if(Ct())try{Ye.setDefaultResultOrder("ipv4first")}catch{}function Ct(){if(process.platform!=="linux")return!1;try{let n=Xe.readFileSync("/proc/sys/kernel/osrelease","utf8");return/microsoft|wsl/i.test(n)}catch{return!1}}async function j(n,e,t){try{return await fetch(n,e)}catch(r){let i=r?.cause?.code,s=r?.cause?.message,o=i||s||r?.message||"unknown",a=Kt(i),d=t?`${t}: `:"",l=`Node ${process.version} on ${process.platform}`,y=[`${d}Cannot reach ${n}`,` Underlying error: ${o}`];a&&y.push(` Suggested fix: ${a}`),y.push(` Platform: ${l}`);let u=new Error(y.join(`
6
+ `));throw u.cause=r,u}}function Kt(n){if(!n)return null;switch(n){case"ENOTFOUND":case"EAI_AGAIN":return'DNS resolution failed. On WSL Ubuntu, check /etc/resolv.conf, or try running with NODE_OPTIONS="--dns-result-order=ipv4first".';case"ETIMEDOUT":case"ECONNREFUSED":case"ECONNRESET":case"EHOSTUNREACH":case"ENETUNREACH":return`Network unreachable. On WSL Ubuntu, try NODE_OPTIONS="--dns-result-order=ipv4first" (WSL's IPv6 is often broken). If behind a corporate proxy, set HTTPS_PROXY.`;case"CERT_HAS_EXPIRED":case"CERT_NOT_YET_VALID":return"TLS certificate time error \u2014 likely system clock drift. On WSL, run `sudo hwclock -s`, or shut down WSL from PowerShell with `wsl --shutdown` and restart.";case"UNABLE_TO_GET_ISSUER_CERT_LOCALLY":case"SELF_SIGNED_CERT_IN_CHAIN":case"UNABLE_TO_VERIFY_LEAF_SIGNATURE":case"DEPTH_ZERO_SELF_SIGNED_CERT":return"Corporate HTTPS proxy detected \u2014 the TLS cert is not trusted by Node. Set NODE_EXTRA_CA_CERTS=/path/to/corporate-ca.pem, or configure HTTPS_PROXY if a proxy is required.";default:return null}}var O={getSession:`
7
7
  query GetSession($sessionId: ID!) {
8
8
  getSession(sessionId: $sessionId) {
9
9
  sessionId
@@ -66,6 +66,18 @@ ${r.stack}`)):typeof r=="object"?o+=` ${JSON.stringify(r,kt)}`:o+=` ${r}`),o}log
66
66
  lastUsedAt
67
67
  }
68
68
  }
69
+ `,listSessions:`
70
+ query ListSessions($userId: ID!, $limit: Int, $nextToken: String) {
71
+ listSessions(userId: $userId, limit: $limit, nextToken: $nextToken) {
72
+ items {
73
+ sessionId
74
+ agentType
75
+ status
76
+ lastHeartbeatAt
77
+ }
78
+ nextToken
79
+ }
80
+ }
69
81
  `},T={createSession:`
70
82
  mutation CreateSession($input: CreateSessionInput!) {
71
83
  createSession(input: $input) {
@@ -182,7 +194,7 @@ ${r.stack}`)):typeof r=="object"?o+=` ${JSON.stringify(r,kt)}`:o+=` ${r}`),o}log
182
194
  lastUsedAt
183
195
  }
184
196
  }
185
- `};var Qe=(c=>(c.USER_PROMPT="USER_PROMPT",c.ASSISTANT_RESPONSE="ASSISTANT_RESPONSE",c.TOOL_USE="TOOL_USE",c.NOTIFICATION="NOTIFICATION",c.INTERACTIVE_PROMPT="INTERACTIVE_PROMPT",c.PROMPT_RESPONSE="PROMPT_RESPONSE",c.REASONING="REASONING",c))(Qe||{}),Ee=(t=>(t.DESKTOP="DESKTOP",t.MOBILE="MOBILE",t))(Ee||{}),Ze=(r=>(r.SENT="SENT",r.DELIVERED="DELIVERED",r.EXECUTED="EXECUTED",r))(Ze||{});var Ie=(r=>(r.ACTIVE="ACTIVE",r.INACTIVE="INACTIVE",r.PAUSED="PAUSED",r))(Ie||{}),et=(r=>(r.CLAUDE="CLAUDE",r.GEMINI="GEMINI",r.CODEX="CODEX",r))(et||{});var x={urgentMaxAttempts:10,baseDelayMs:1e3,maxDelayMs:6e4,backoffMultiplier:2,persistentDelayMs:300*1e3},ee=class n{constructor(){this.authenticated=!1;this.currentUserId=null;this.currentEmail=null;this.tokens=null;this.activeSubscriptions=new Map;this.pendingRefresh=null;this.lastRefreshFailureAt=null;this.deviceKeyWatcher=null;this.heartbeatTimers=new Map;this.environment=b(),a.info("[AppSyncClient] Initialized",{environment:this.environment})}static{this.REFRESH_BACKOFF_MS=3e4}getCurrentUserId(){if(!this.currentUserId)throw new Error("Not authenticated. Call authenticateWithStoredTokens() first.");return this.currentUserId}getCurrentUserEmail(){return this.currentEmail}async authenticateWithStoredTokens(){try{let e=await g.getTokens(this.environment);if(!e)return a.debug("[AppSyncClient] No stored tokens found"),!1;if(a.info("[AppSyncClient] Found stored OAuth tokens",{userId:e.userId,email:e.email,expired:g.isTokenExpired(e)}),g.isTokenExpired(e)){if(a.info("[AppSyncClient] Tokens expired, attempting refresh..."),!await this.refreshTokens(e))return a.warn("[AppSyncClient] Token refresh failed"),!1}else this.tokens=e;return this.currentUserId=this.tokens.userId,this.currentEmail=this.tokens.email,this.authenticated=!0,a.info("[AppSyncClient] Authenticated successfully",{userId:this.currentUserId,email:this.currentEmail}),!0}catch(e){return a.error("[AppSyncClient] Authentication failed:",e),!1}}async refreshTokens(e){if(this.pendingRefresh)return this.pendingRefresh;if(this.lastRefreshFailureAt!==null&&Date.now()-this.lastRefreshFailureAt<n.REFRESH_BACKOFF_MS)return!1;this.pendingRefresh=this.performRefresh(e);try{return await this.pendingRefresh}finally{this.pendingRefresh=null}}async performRefresh(e){let t=await this.callCognitoRefresh(e.refreshToken);if(t!==null)return this.applyRefreshedTokens(e,t);let r=null;try{r=await g.getTokens(this.environment)}catch(i){a.warn("[AppSyncClient] Failed to re-read tokens from storage during refresh recovery",{error:i instanceof Error?i.message:String(i)})}if(r&&r.refreshToken&&r.refreshToken!==e.refreshToken){a.info("[AppSyncClient] In-memory refresh token rejected; retrying with storage-backed token (likely out-of-band re-auth)");let i=await this.callCognitoRefresh(r.refreshToken);if(i!==null)return this.applyRefreshedTokens(r,i)}return this.lastRefreshFailureAt=Date.now(),!1}async callCognitoRefresh(e){try{let t=f(),r=`https://${t.aws.cognitoDomain}/oauth2/token`,i=new URLSearchParams({grant_type:"refresh_token",client_id:t.aws.cognitoClientId,refresh_token:e}),s=await j(r,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:i.toString()},"Token refresh");return s.ok?await s.json():(a.error("[AppSyncClient] Token refresh failed",{status:s.status}),null)}catch(t){return a.error("[AppSyncClient] Token refresh error:",t),null}}async applyRefreshedTokens(e,t){let r={...e,accessToken:t.access_token,idToken:t.id_token,expiresAt:Date.now()+t.expires_in*1e3};this.tokens=r,this.lastRefreshFailureAt=null;try{await g.setTokens(r,this.environment),a.info("[AppSyncClient] Tokens refreshed",{expiresAt:new Date(r.expiresAt).toISOString()})}catch(i){a.warn("[AppSyncClient] Tokens refreshed but persistence failed; daemon keeps using fresh tokens in memory. A restart while persistence is still broken would lose them.",{error:i instanceof Error?i.message:String(i),expiresAt:new Date(r.expiresAt).toISOString()})}return!0}isAuthenticated(){return this.authenticated}signOut(){this.authenticated=!1,this.tokens=null,this.currentUserId=null,this.currentEmail=null,this.cleanupSubscriptions(),a.info("[AppSyncClient] Signed out")}async graphqlRequest(e,t,r=!1){let i=f();if(!this.tokens?.idToken)throw new Error('Not authenticated. Run "codevibe login" first.');let s={"Content-Type":"application/json",Authorization:this.tokens.idToken},o=await j(i.aws.appsyncUrl,{method:"POST",headers:s,body:JSON.stringify({query:e,variables:t})},"AppSync GraphQL request"),c=await o.json();if(o.status===401&&!r&&this.tokens){if(a.info("[AppSyncClient] 401 Unauthorized, refreshing token..."),await this.refreshTokens(this.tokens))return this.graphqlRequest(e,t,!0);throw new Error("Token expired and refresh failed")}if(!o.ok)throw new Error(`GraphQL request failed: ${o.status}`);if(c.errors?.length)throw new Error(`GraphQL error: ${c.errors[0].message}`);return c}async createSession(e){let t={...e,metadata:e.metadata?JSON.stringify(e.metadata):void 0},r=await this.graphqlRequest(T.createSession,{input:t});return a.info("[AppSyncClient] Session created",{sessionId:r.data.createSession.sessionId}),r.data.createSession}async updateSession(e){let t={...e,metadata:e.metadata?JSON.stringify(e.metadata):void 0},r=await this.graphqlRequest(T.updateSession,{input:t});return a.debug("[AppSyncClient] Session updated",{sessionId:r.data.updateSession.sessionId}),r.data.updateSession}async getSession(e){return(await this.graphqlRequest(M.getSession,{sessionId:e})).data.getSession}async createEvent(e){let t={...e,metadata:e.metadata?JSON.stringify(e.metadata):void 0},r=await this.graphqlRequest(T.createEvent,{input:t});return a.debug("[AppSyncClient] Event created",{eventId:r.data.createEvent.eventId,type:r.data.createEvent.type}),r.data.createEvent}async updateEventStatus(e){return(await this.graphqlRequest(T.updateEventStatus,{input:e})).data.updateEventStatus}async listEvents(e,t,r){return(await this.graphqlRequest(M.listEvents,{sessionId:e,source:t,limit:r})).data.listEvents.items}async listUserDeviceKeys(){return(await this.graphqlRequest(M.listUserDeviceKeys,{})).data.listUserDeviceKeys||[]}async registerDeviceKey(e,t,r,i){let s={deviceId:e,publicKey:t,platform:r,deviceName:i};await this.graphqlRequest(T.registerDeviceKey,{input:s}),a.info("[AppSyncClient] Device key registered",{deviceId:e,platform:r})}async grantSessionKey(e){await this.graphqlRequest(T.grantSessionKey,{input:e}),a.info("[AppSyncClient] Session key granted",{sessionId:e.sessionId,deviceId:e.deviceId})}async getAttachmentDownloadUrl(e){return(await this.graphqlRequest(T.getAttachmentDownloadUrl,{s3Key:e})).data.getAttachmentDownloadUrl}subscribeToEvents(e,t,r){a.info("[AppSyncClient] Subscribing to events",{sessionId:e});let i=this.activeSubscriptions.get(e);i&&(this.cleanupSubscriptionState(i),this.activeSubscriptions.delete(e));let s={ws:null,subscriptionId:(0,Z.v4)(),sessionId:e,onEvent:t,onError:r,reconnectAttempts:0,isReconnecting:!1,destroyed:!1};return this.activeSubscriptions.set(e,s),this.createSubscription(s),()=>{this.cleanupSubscriptionState(s),this.activeSubscriptions.delete(e)}}buildRealtimeUrl(){let e=f(),t=e.aws.appsyncUrl.replace("https://","wss://").replace("appsync-api","appsync-realtime-api"),r={host:new URL(e.aws.appsyncUrl).host};this.tokens?.idToken&&(r.Authorization=this.tokens.idToken);let i=Buffer.from(JSON.stringify(r)).toString("base64"),s=Buffer.from(JSON.stringify({})).toString("base64");return`${t}?header=${i}&payload=${s}`}createSubscription(e){let{sessionId:t,subscriptionId:r,onEvent:i,onError:s}=e;try{let o=this.buildRealtimeUrl(),c=new Q.default(o,["graphql-ws"]);c.on("open",()=>{a.info("[AppSyncClient] WebSocket connected",{sessionId:t}),c.send(JSON.stringify({type:"connection_init"}))}),c.on("message",d=>{try{let p=JSON.parse(d.toString());switch(p.type){case"connection_ack":this.sendSubscriptionStart(c,e);break;case"start_ack":a.info("[AppSyncClient] Subscription started",{sessionId:t}),e.isReconnecting=!1,e.reconnectAttempts=0,this.startHeartbeat(t);break;case"data":this.resetKeepAliveTimer(e);let y=p.payload?.data?.onEventCreated;y&&y.source==="MOBILE"&&i(y);break;case"ka":this.resetKeepAliveTimer(e);break;case"error":let u=p.payload?.errors?.[0]?.message||"Unknown error";this.handleSubscriptionError(e,new Error(u));break}}catch(p){a.error("[AppSyncClient] Failed to parse message",{error:p})}}),c.on("error",d=>{a.error("[AppSyncClient] WebSocket error",{sessionId:t,error:d.message}),this.handleSubscriptionError(e,d)}),c.on("close",(d,p)=>{a.info("[AppSyncClient] WebSocket closed",{sessionId:t,code:d}),e.keepAliveTimer&&clearTimeout(e.keepAliveTimer),!e.destroyed&&this.activeSubscriptions.get(t)===e&&this.handleSubscriptionError(e,new Error(`WebSocket closed: ${d}`))}),e.ws=c,this.resetKeepAliveTimer(e)}catch(o){this.handleSubscriptionError(e,o)}}sendSubscriptionStart(e,t){let r=f(),{sessionId:i,subscriptionId:s}=t,o={host:new URL(r.aws.appsyncUrl).host};this.tokens?.idToken&&(o.Authorization=this.tokens.idToken),e.send(JSON.stringify({id:s,type:"start",payload:{data:JSON.stringify({query:V.onEventCreated,variables:{sessionId:i}}),extensions:{authorization:o}}}))}resetKeepAliveTimer(e){e.keepAliveTimer&&clearTimeout(e.keepAliveTimer),e.keepAliveTimer=setTimeout(()=>{this.handleSubscriptionError(e,new Error("Keep-alive timeout"))},300*1e3)}handleSubscriptionError(e,t){let{sessionId:r,onError:i}=e;if(e.isReconnecting||!this.activeSubscriptions.has(r))return;e.isReconnecting=!0,e.reconnectAttempts++,this.stopHeartbeat(r);let s=e.reconnectAttempts<=x.urgentMaxAttempts,o;if(s?o=Math.min(x.baseDelayMs*Math.pow(x.backoffMultiplier,e.reconnectAttempts-1),x.maxDelayMs):(o=x.persistentDelayMs,e.reconnectAttempts===x.urgentMaxAttempts+1&&a.info("[AppSyncClient] Switching to persistent reconnect (every 5min)",{sessionId:r})),a.info("[AppSyncClient] Scheduling reconnect",{sessionId:r,attempt:e.reconnectAttempts,phase:s?"urgent":"persistent",delayMs:o}),e.ws){try{e.ws.close(1e3)}catch{}e.ws=null}e.keepAliveTimer&&clearTimeout(e.keepAliveTimer),e.reconnectTimer=setTimeout(async()=>{if(e.isReconnecting=!1,e.destroyed||this.activeSubscriptions.get(r)!==e){a.info("[AppSyncClient] Reconnect skipped \u2014 state is no longer canonical",{sessionId:r});return}try{let c=await g.getTokens(this.environment);c&&(g.isTokenExpired(c)?await this.refreshTokens(c)&&a.info("[AppSyncClient] Tokens refreshed before reconnect",{sessionId:r}):this.tokens=c)}catch{a.warn("[AppSyncClient] Token refresh failed before reconnect, using existing tokens",{sessionId:r})}if(e.destroyed||this.activeSubscriptions.get(r)!==e){a.info("[AppSyncClient] Reconnect skipped after token refresh \u2014 state no longer canonical",{sessionId:r});return}e.subscriptionId=(0,Z.v4)(),this.createSubscription(e)},o)}cleanupSubscriptionState(e){if(e.destroyed=!0,e.reconnectTimer&&(clearTimeout(e.reconnectTimer),e.reconnectTimer=void 0),e.keepAliveTimer&&(clearTimeout(e.keepAliveTimer),e.keepAliveTimer=void 0),e.ws){try{e.ws.readyState===Q.default.OPEN&&e.ws.send(JSON.stringify({id:e.subscriptionId,type:"stop"}))}catch{}try{e.ws.close(1e3)}catch{}try{e.ws.removeAllListeners()}catch{}e.ws=null}}subscribeToDeviceKeyRegistered(e,t,r,i){a.info("[AppSyncClient] Subscribing to device key registrations",{userId:e}),this.deviceKeyWatcher&&this.stopDeviceKeyWatcherInternal();let s={userId:e,subscriptionId:(0,Z.v4)(),ws:null,onNewDevice:t,onReconnect:r,onError:i,reconnectAttempts:0,isReconnecting:!1,destroyed:!1};return this.deviceKeyWatcher=s,this.createDeviceKeyWatcherConnection(s),()=>{this.stopDeviceKeyWatcherInternal()}}stopDeviceKeyWatcher(){this.stopDeviceKeyWatcherInternal()}stopDeviceKeyWatcherInternal(){let e=this.deviceKeyWatcher;if(e){if(e.destroyed=!0,e.reconnectTimer&&(clearTimeout(e.reconnectTimer),e.reconnectTimer=void 0),e.keepAliveTimer&&(clearTimeout(e.keepAliveTimer),e.keepAliveTimer=void 0),e.ws){try{e.ws.readyState===Q.default.OPEN&&e.ws.send(JSON.stringify({id:e.subscriptionId,type:"stop"}))}catch{}try{e.ws.close(1e3)}catch{}try{e.ws.removeAllListeners()}catch{}e.ws=null}this.deviceKeyWatcher=null,a.info("[AppSyncClient] Device key watcher stopped")}}createDeviceKeyWatcherConnection(e){try{let t=this.buildRealtimeUrl(),r=new Q.default(t,["graphql-ws"]);r.on("open",()=>{a.info("[AppSyncClient] Device key watcher WebSocket connected",{userId:e.userId}),r.send(JSON.stringify({type:"connection_init"}))}),r.on("message",i=>{try{let s=JSON.parse(i.toString());switch(s.type){case"connection_ack":this.sendDeviceKeyWatcherStart(r,e);break;case"start_ack":a.info("[AppSyncClient] Device key watcher subscription started",{userId:e.userId});let o=e.isReconnecting;if(e.isReconnecting=!1,e.reconnectAttempts=0,o&&e.onReconnect)try{e.onReconnect()}catch(p){a.warn("[AppSyncClient] Device key watcher onReconnect handler threw",{error:p})}break;case"data":this.resetDeviceKeyWatcherKeepAlive(e);let c=s.payload?.data?.onDeviceKeyRegistered;if(c){a.info("[AppSyncClient] Device key registration observed",{userId:e.userId,newDeviceId:c.deviceId,platform:c.platform});try{e.onNewDevice(c)}catch(p){a.warn("[AppSyncClient] Device key watcher onNewDevice handler threw",{error:p})}}break;case"ka":this.resetDeviceKeyWatcherKeepAlive(e);break;case"error":let d=s.payload?.errors?.[0]?.message||"Unknown error";this.handleDeviceKeyWatcherError(e,new Error(d));break}}catch(s){a.error("[AppSyncClient] Failed to parse device key watcher message",{error:s})}}),r.on("error",i=>{a.error("[AppSyncClient] Device key watcher WebSocket error",{userId:e.userId,error:i.message}),this.handleDeviceKeyWatcherError(e,i)}),r.on("close",i=>{a.info("[AppSyncClient] Device key watcher WebSocket closed",{userId:e.userId,code:i}),e.keepAliveTimer&&clearTimeout(e.keepAliveTimer),!e.destroyed&&this.deviceKeyWatcher===e&&this.handleDeviceKeyWatcherError(e,new Error(`WebSocket closed: ${i}`))}),e.ws=r,this.resetDeviceKeyWatcherKeepAlive(e)}catch(t){this.handleDeviceKeyWatcherError(e,t)}}sendDeviceKeyWatcherStart(e,t){let r=f(),{userId:i,subscriptionId:s}=t,o={host:new URL(r.aws.appsyncUrl).host};this.tokens?.idToken&&(o.Authorization=this.tokens.idToken),e.send(JSON.stringify({id:s,type:"start",payload:{data:JSON.stringify({query:V.onDeviceKeyRegistered,variables:{userId:i}}),extensions:{authorization:o}}}))}resetDeviceKeyWatcherKeepAlive(e){e.keepAliveTimer&&clearTimeout(e.keepAliveTimer),e.keepAliveTimer=setTimeout(()=>{this.handleDeviceKeyWatcherError(e,new Error("Device key watcher keep-alive timeout"))},300*1e3)}handleDeviceKeyWatcherError(e,t){if(e.isReconnecting||e.destroyed||this.deviceKeyWatcher!==e)return;if(e.isReconnecting=!0,e.reconnectAttempts++,e.onError)try{e.onError(t)}catch{}if(e.ws){try{e.ws.removeAllListeners()}catch{}try{e.ws.close(1e3)}catch{}e.ws=null}e.keepAliveTimer&&(clearTimeout(e.keepAliveTimer),e.keepAliveTimer=void 0);let i=e.reconnectAttempts<=x.urgentMaxAttempts?Math.min(x.baseDelayMs*Math.pow(x.backoffMultiplier,e.reconnectAttempts-1),x.maxDelayMs):x.persistentDelayMs;a.warn("[AppSyncClient] Device key watcher reconnect scheduled",{userId:e.userId,attempts:e.reconnectAttempts,delayMs:i,error:t.message}),e.reconnectTimer=setTimeout(async()=>{if(e.isReconnecting=!1,e.destroyed||this.deviceKeyWatcher!==e){a.info("[AppSyncClient] Device key watcher reconnect skipped \u2014 state no longer canonical",{userId:e.userId});return}try{let s=await g.getTokens(this.environment);s&&(g.isTokenExpired(s)?await this.refreshTokens(s)&&a.info("[AppSyncClient] Tokens refreshed before device key watcher reconnect",{userId:e.userId}):this.tokens=s)}catch{a.warn("[AppSyncClient] Token refresh failed before device key watcher reconnect, using existing tokens",{userId:e.userId})}e.destroyed||this.deviceKeyWatcher!==e||(e.subscriptionId=(0,Z.v4)(),this.createDeviceKeyWatcherConnection(e))},i)}startHeartbeat(e,t=120*1e3){this.stopHeartbeat(e),this.sendHeartbeat(e);let r=setInterval(()=>{this.sendHeartbeat(e)},t);this.heartbeatTimers.set(e,r),a.info("[AppSyncClient] Heartbeat started",{sessionId:e,intervalMs:t})}stopHeartbeat(e){let t=this.heartbeatTimers.get(e);t&&(clearInterval(t),this.heartbeatTimers.delete(e),a.info("[AppSyncClient] Heartbeat stopped",{sessionId:e}))}async sendHeartbeat(e){try{await this.updateSession({sessionId:e,lastHeartbeatAt:new Date().toISOString()}),a.debug("[AppSyncClient] Heartbeat sent",{sessionId:e})}catch(t){a.warn("[AppSyncClient] Heartbeat failed",{sessionId:e,error:t})}}cleanupSubscriptions(){this.activeSubscriptions.forEach(e=>{this.cleanupSubscriptionState(e)}),this.activeSubscriptions.clear(),this.stopDeviceKeyWatcherInternal(),this.heartbeatTimers.forEach(e=>clearInterval(e)),this.heartbeatTimers.clear()}};var at=m(require("crypto")),ct=m(require("fs")),De=m(require("http")),dt=require("child_process");H();P();N();var tt=m(require("crypto")),rt=m(require("https")),nt=m(require("os")),Rt="G-GS74YEQTB8",_t="lAfOF6OxRzSQ-NsLBRjhAg",Pt="www.google-analytics.com",Ot=`/mp/collect?measurement_id=${Rt}&api_secret=${_t}`,Nt={port_in_use:"server_start",server_listen_failed:"server_start",browser_open_failed:"browser_open",login_timeout:"awaiting_callback",cognito_rejected:"awaiting_callback",state_mismatch:"awaiting_callback",no_authorization_code:"awaiting_callback",token_exchange_failed:"exchanging_code",token_exchange_network_error:"exchanging_code",keychain_write_failed:"storing_tokens",user_aborted:"unknown",unknown:"unknown"};function $t(){let n=typeof process.getuid=="function"?process.getuid():0;return tt.createHash("sha256").update(`${nt.hostname()}-${n}`).digest("hex").substring(0,36)}function it(){return{platform:process.platform,source:process.env.CODEVIBE_TELEMETRY_SOURCE||"production"}}async function st(n,e){try{let t=JSON.stringify({client_id:$t(),events:[{name:n,params:e}]});await new Promise(r=>{let i=rt.request({hostname:Pt,path:Ot,method:"POST",headers:{"Content-Type":"application/json"}},()=>r());i.on("error",()=>r()),i.write(t),i.end(),setTimeout(r,2e3)})}catch{}}async function te(n){await st("auth_completed",{...it(),user_id:n})}async function v(n,e){let t={...it(),reason:n,stage:e?.stage??Nt[n]};typeof e?.httpStatus=="number"&&(t.http_status=e.httpStatus),await st("auth_failed",t)}var Ae=Symbol.for("codevibe.auth.beaconed"),ot=Symbol.for("codevibe.auth.failureReason");function E(n,e){try{Object.defineProperty(n,Ae,{value:!0,enumerable:!1,configurable:!0,writable:!1}),Object.defineProperty(n,ot,{value:e,enumerable:!1,configurable:!0,writable:!1})}catch{}return n}function re(n){return!!(n&&typeof n=="object"&&n[Ae])}function Te(n){if(n&&typeof n=="object"&&n[Ae]){let e=n[ot];if(typeof e=="string")return e}}var ne=8080,lt="/callback",xe=`http://localhost:${ne}${lt}`,J=class n{constructor(){}static getInstance(){return n.instance||(n.instance=new n),n.instance}openBrowser(e){console.log(""),console.log("Opening your browser for sign-in..."),this.isRunningInWSL()?console.log("If your browser does not open, paste this URL in your Windows browser:"):console.log("If your browser does not open automatically, visit this URL:"),console.log(` ${e}`),console.log("");let t=this.getBrowserCommands();this.tryBrowserCommand(t,e,0)}getBrowserCommands(){let e=process.platform;if(e==="darwin")return[{cmd:"open",fixedArgs:[]}];if(e==="win32")return[{cmd:"cmd",fixedArgs:["/c","start",""]}];let t=[];return this.isRunningInWSL()&&(t.push({cmd:"wslview",fixedArgs:[]}),t.push({cmd:"cmd.exe",fixedArgs:["/c","start",""]}),t.push({cmd:"powershell.exe",fixedArgs:["-NoProfile","-Command","Start-Process"]})),t.push({cmd:"xdg-open",fixedArgs:[]}),t}isRunningInWSL(){if(process.platform!=="linux")return!1;try{let e=ct.readFileSync("/proc/sys/kernel/osrelease","utf8");return/microsoft|wsl/i.test(e)}catch{return!1}}tryBrowserCommand(e,t,r){if(r>=e.length){a.debug("[AuthService] No browser-opening command succeeded. User must open the sign-in URL manually (printed to stdout above)."),console.log(""),console.log("\u26A0\uFE0F Could not open browser automatically."),this.isRunningInWSL()?console.log(" WSL detected \u2014 paste this URL in your Windows browser:"):console.log(" Please copy and paste this URL into your browser:"),console.log(` ${t}`),console.log("");return}let i=e[r],s=[...i.fixedArgs,t],o=!1,c=u=>{o||(o=!0,a.debug(`[AuthService] Browser command '${i.cmd}' ${u}; trying next fallback`),this.tryBrowserCommand(e,t,r+1))},d=u=>{o||(o=!0,a.debug(`[AuthService] Browser command '${i.cmd}' ${u}`))},p;try{p=(0,dt.spawn)(i.cmd,s,{detached:!0,stdio:"ignore"})}catch(u){c(`threw synchronously: ${u?.message||u}`);return}p.on("error",u=>{c(`failed to spawn: ${u?.message||u}`)}),p.on("exit",(u,S)=>{u===0?d("exited successfully"):c(S?`terminated by signal ${S}`:`exited with code ${u}`)}),setTimeout(()=>{d("still running after 3s, assuming success")},3e3).unref(),p.unref()}generateState(){return at.randomBytes(32).toString("hex")}buildAuthUrl(e){let t=f(),r=new URLSearchParams({client_id:t.aws.cognitoClientId,response_type:"code",scope:"email openid profile",redirect_uri:xe,state:e});return`https://${t.aws.cognitoDomain}/oauth2/authorize?${r.toString()}`}async exchangeCodeForTokens(e){let t=f(),r=`https://${t.aws.cognitoDomain}/oauth2/token`,i=new URLSearchParams({grant_type:"authorization_code",client_id:t.aws.cognitoClientId,code:e,redirect_uri:xe}),s;try{s=await j(r,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:i.toString()},"Token exchange")}catch(c){throw await v("token_exchange_network_error"),E(c,"token_exchange_network_error"),c}if(!s.ok){let c=await s.text(),d=new Error(`Token exchange failed: ${s.status} ${c}`);throw await v("token_exchange_failed",{httpStatus:s.status}),E(d,"token_exchange_failed"),d}let o=await s.json();return{accessToken:o.access_token,idToken:o.id_token,refreshToken:o.refresh_token,expiresIn:o.expires_in}}decodeJwt(e){let t=e.split(".");if(t.length!==3)throw new Error("Invalid JWT");return JSON.parse(Buffer.from(t[1],"base64").toString("utf-8"))}async refreshTokens(e){let t=f(),r=`https://${t.aws.cognitoDomain}/oauth2/token`,i=new URLSearchParams({grant_type:"refresh_token",client_id:t.aws.cognitoClientId,refresh_token:e}),s=await j(r,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:i.toString()},"Token refresh");if(!s.ok)throw new Error(`Token refresh failed: ${s.status}`);let o=await s.json();return{accessToken:o.access_token,idToken:o.id_token,expiresIn:o.expires_in}}async login(){let e=await g.getTokens(b());if(e&&!g.isTokenExpired(e))return e;let t=this.generateState(),r=this.buildAuthUrl(t);return new Promise((i,s)=>{let o=De.createServer(async(c,d)=>{if(!c.url?.startsWith(lt)){d.writeHead(404),d.end("Not found");return}try{let p=new URL(c.url,`http://localhost:${ne}`),y=p.searchParams.get("code"),u=p.searchParams.get("state"),S=p.searchParams.get("error");if(S){let A=new Error(`OAuth error: ${S}`);throw await v("cognito_rejected"),E(A,"cognito_rejected"),A}if(u!==t){let A=new Error("State mismatch");throw await v("state_mismatch"),E(A,"state_mismatch"),A}if(!y){let A=new Error("No authorization code");throw await v("no_authorization_code"),E(A,"no_authorization_code"),A}let w=await this.exchangeCodeForTokens(y),z=this.decodeJwt(w.idToken),h={accessToken:w.accessToken,idToken:w.idToken,refreshToken:w.refreshToken,expiresAt:Date.now()+w.expiresIn*1e3,userId:z.sub,email:z.email||"unknown"};try{await g.setTokens(h,b())}catch(A){throw await v("keychain_write_failed"),E(A,"keychain_write_failed"),A}d.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),d.end(`
197
+ `};var Qe=(a=>(a.USER_PROMPT="USER_PROMPT",a.ASSISTANT_RESPONSE="ASSISTANT_RESPONSE",a.TOOL_USE="TOOL_USE",a.NOTIFICATION="NOTIFICATION",a.INTERACTIVE_PROMPT="INTERACTIVE_PROMPT",a.PROMPT_RESPONSE="PROMPT_RESPONSE",a.REASONING="REASONING",a))(Qe||{}),Ee=(t=>(t.DESKTOP="DESKTOP",t.MOBILE="MOBILE",t))(Ee||{}),Ze=(r=>(r.SENT="SENT",r.DELIVERED="DELIVERED",r.EXECUTED="EXECUTED",r))(Ze||{});var le=(r=>(r.ACTIVE="ACTIVE",r.INACTIVE="INACTIVE",r.PAUSED="PAUSED",r))(le||{}),et=(r=>(r.CLAUDE="CLAUDE",r.GEMINI="GEMINI",r.CODEX="CODEX",r))(et||{});var x={urgentMaxAttempts:10,baseDelayMs:1e3,maxDelayMs:6e4,backoffMultiplier:2,persistentDelayMs:300*1e3},ee=class n{constructor(){this.authenticated=!1;this.currentUserId=null;this.currentEmail=null;this.tokens=null;this.activeSubscriptions=new Map;this.pendingRefresh=null;this.lastRefreshFailureAt=null;this.deviceKeyWatcher=null;this.heartbeatTimers=new Map;this.environment=b(),c.info("[AppSyncClient] Initialized",{environment:this.environment})}static{this.REFRESH_BACKOFF_MS=3e4}getCurrentUserId(){if(!this.currentUserId)throw new Error("Not authenticated. Call authenticateWithStoredTokens() first.");return this.currentUserId}getCurrentUserEmail(){return this.currentEmail}async authenticateWithStoredTokens(){try{let e=await g.getTokens(this.environment);if(!e)return c.debug("[AppSyncClient] No stored tokens found"),!1;if(c.info("[AppSyncClient] Found stored OAuth tokens",{userId:e.userId,email:e.email,expired:g.isTokenExpired(e)}),g.isTokenExpired(e)){if(c.info("[AppSyncClient] Tokens expired, attempting refresh..."),!await this.refreshTokens(e))return c.warn("[AppSyncClient] Token refresh failed"),!1}else this.tokens=e;return this.currentUserId=this.tokens.userId,this.currentEmail=this.tokens.email,this.authenticated=!0,c.info("[AppSyncClient] Authenticated successfully",{userId:this.currentUserId,email:this.currentEmail}),!0}catch(e){return c.error("[AppSyncClient] Authentication failed:",e),!1}}async refreshTokens(e){if(this.pendingRefresh)return this.pendingRefresh;if(this.lastRefreshFailureAt!==null&&Date.now()-this.lastRefreshFailureAt<n.REFRESH_BACKOFF_MS)return!1;this.pendingRefresh=this.performRefresh(e);try{return await this.pendingRefresh}finally{this.pendingRefresh=null}}async performRefresh(e){let t=await this.callCognitoRefresh(e.refreshToken);if(t!==null)return this.applyRefreshedTokens(e,t);let r=null;try{r=await g.getTokens(this.environment)}catch(i){c.warn("[AppSyncClient] Failed to re-read tokens from storage during refresh recovery",{error:i instanceof Error?i.message:String(i)})}if(r&&r.refreshToken&&r.refreshToken!==e.refreshToken){c.info("[AppSyncClient] In-memory refresh token rejected; retrying with storage-backed token (likely out-of-band re-auth)");let i=await this.callCognitoRefresh(r.refreshToken);if(i!==null)return this.applyRefreshedTokens(r,i)}return this.lastRefreshFailureAt=Date.now(),!1}async callCognitoRefresh(e){try{let t=f(),r=`https://${t.aws.cognitoDomain}/oauth2/token`,i=new URLSearchParams({grant_type:"refresh_token",client_id:t.aws.cognitoClientId,refresh_token:e}),s=await j(r,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:i.toString()},"Token refresh");return s.ok?await s.json():(c.error("[AppSyncClient] Token refresh failed",{status:s.status}),null)}catch(t){return c.error("[AppSyncClient] Token refresh error:",t),null}}async applyRefreshedTokens(e,t){let r={...e,accessToken:t.access_token,idToken:t.id_token,expiresAt:Date.now()+t.expires_in*1e3};this.tokens=r,this.lastRefreshFailureAt=null;try{await g.setTokens(r,this.environment),c.info("[AppSyncClient] Tokens refreshed",{expiresAt:new Date(r.expiresAt).toISOString()})}catch(i){c.warn("[AppSyncClient] Tokens refreshed but persistence failed; daemon keeps using fresh tokens in memory. A restart while persistence is still broken would lose them.",{error:i instanceof Error?i.message:String(i),expiresAt:new Date(r.expiresAt).toISOString()})}return!0}isAuthenticated(){return this.authenticated}signOut(){this.authenticated=!1,this.tokens=null,this.currentUserId=null,this.currentEmail=null,this.cleanupSubscriptions(),c.info("[AppSyncClient] Signed out")}async graphqlRequest(e,t,r=!1){let i=f();if(!this.tokens?.idToken)throw new Error('Not authenticated. Run "codevibe login" first.');let s={"Content-Type":"application/json",Authorization:this.tokens.idToken},o=await j(i.aws.appsyncUrl,{method:"POST",headers:s,body:JSON.stringify({query:e,variables:t})},"AppSync GraphQL request"),a=await o.json();if(o.status===401&&!r&&this.tokens){if(c.info("[AppSyncClient] 401 Unauthorized, refreshing token..."),await this.refreshTokens(this.tokens))return this.graphqlRequest(e,t,!0);throw new Error("Token expired and refresh failed")}if(!o.ok)throw new Error(`GraphQL request failed: ${o.status}`);if(a.errors?.length)throw new Error(`GraphQL error: ${a.errors[0].message}`);return a}async createSession(e){let t={...e,metadata:e.metadata?JSON.stringify(e.metadata):void 0},r=await this.graphqlRequest(T.createSession,{input:t});return c.info("[AppSyncClient] Session created",{sessionId:r.data.createSession.sessionId}),r.data.createSession}async updateSession(e){let t={...e,metadata:e.metadata?JSON.stringify(e.metadata):void 0},r=await this.graphqlRequest(T.updateSession,{input:t});return c.debug("[AppSyncClient] Session updated",{sessionId:r.data.updateSession.sessionId}),r.data.updateSession}async getSession(e){return(await this.graphqlRequest(O.getSession,{sessionId:e})).data.getSession}async createEvent(e){let t={...e,metadata:e.metadata?JSON.stringify(e.metadata):void 0},r=await this.graphqlRequest(T.createEvent,{input:t});return c.debug("[AppSyncClient] Event created",{eventId:r.data.createEvent.eventId,type:r.data.createEvent.type}),r.data.createEvent}async updateEventStatus(e){return(await this.graphqlRequest(T.updateEventStatus,{input:e})).data.updateEventStatus}async listEvents(e,t,r){return(await this.graphqlRequest(O.listEvents,{sessionId:e,source:t,limit:r})).data.listEvents.items}async listSessions(e=100){if(!this.currentUserId)throw new Error("Not authenticated");let t=[],r=null;do{let s=(await this.graphqlRequest(O.listSessions,{userId:this.currentUserId,limit:e,nextToken:r})).data?.listSessions;s?.items&&t.push(...s.items),r=s?.nextToken??null}while(r);return t}async sweepOrphanSessions(e){let t=e.staleThresholdMs??9e5,r=new Set(e.excludeSessionIds??[]),i=Date.now(),s;try{s=await this.listSessions()}catch(a){return c.warn("[AppSyncClient] OrphanSweep: listSessions failed, skipping sweep",{agentType:e.agentType,error:a instanceof Error?a.message:String(a)}),0}let o=0;for(let a of s){if(a.agentType!==e.agentType||a.status!=="ACTIVE"||r.has(a.sessionId)||!a.lastHeartbeatAt)continue;let d=i-new Date(a.lastHeartbeatAt).getTime();if(!(d<t)){c.warn("[AppSyncClient] OrphanSweep: marking stale session INACTIVE",{sessionId:a.sessionId,agentType:a.agentType,lastHeartbeatAt:a.lastHeartbeatAt,heartbeatAgeMinutes:Math.round(d/6e4)});try{await this.updateSession({sessionId:a.sessionId,status:"INACTIVE"}),o++}catch(l){c.warn("[AppSyncClient] OrphanSweep: updateSession failed, leaving row as-is",{sessionId:a.sessionId,error:l instanceof Error?l.message:String(l)})}}}return o>0&&c.info("[AppSyncClient] OrphanSweep complete",{agentType:e.agentType,swept:o}),o}async listUserDeviceKeys(){return(await this.graphqlRequest(O.listUserDeviceKeys,{})).data.listUserDeviceKeys||[]}async registerDeviceKey(e,t,r,i){let s={deviceId:e,publicKey:t,platform:r,deviceName:i};await this.graphqlRequest(T.registerDeviceKey,{input:s}),c.info("[AppSyncClient] Device key registered",{deviceId:e,platform:r})}async grantSessionKey(e){await this.graphqlRequest(T.grantSessionKey,{input:e}),c.info("[AppSyncClient] Session key granted",{sessionId:e.sessionId,deviceId:e.deviceId})}async getAttachmentDownloadUrl(e){return(await this.graphqlRequest(T.getAttachmentDownloadUrl,{s3Key:e})).data.getAttachmentDownloadUrl}subscribeToEvents(e,t,r){c.info("[AppSyncClient] Subscribing to events",{sessionId:e});let i=this.activeSubscriptions.get(e);i&&(this.cleanupSubscriptionState(i),this.activeSubscriptions.delete(e));let s={ws:null,subscriptionId:(0,Z.v4)(),sessionId:e,onEvent:t,onError:r,reconnectAttempts:0,isReconnecting:!1,destroyed:!1};return this.activeSubscriptions.set(e,s),this.createSubscription(s),()=>{this.cleanupSubscriptionState(s),this.activeSubscriptions.delete(e)}}buildRealtimeUrl(){let e=f(),t=e.aws.appsyncUrl.replace("https://","wss://").replace("appsync-api","appsync-realtime-api"),r={host:new URL(e.aws.appsyncUrl).host};this.tokens?.idToken&&(r.Authorization=this.tokens.idToken);let i=Buffer.from(JSON.stringify(r)).toString("base64"),s=Buffer.from(JSON.stringify({})).toString("base64");return`${t}?header=${i}&payload=${s}`}createSubscription(e){let{sessionId:t,subscriptionId:r,onEvent:i,onError:s}=e;try{let o=this.buildRealtimeUrl(),a=new Q.default(o,["graphql-ws"]);a.on("open",()=>{c.info("[AppSyncClient] WebSocket connected",{sessionId:t}),a.send(JSON.stringify({type:"connection_init"}))}),a.on("message",d=>{try{let l=JSON.parse(d.toString());switch(l.type){case"connection_ack":this.sendSubscriptionStart(a,e);break;case"start_ack":c.info("[AppSyncClient] Subscription started",{sessionId:t}),e.isReconnecting=!1,e.reconnectAttempts=0,this.startHeartbeat(t);break;case"data":this.resetKeepAliveTimer(e);let y=l.payload?.data?.onEventCreated;y&&y.source==="MOBILE"&&i(y);break;case"ka":this.resetKeepAliveTimer(e);break;case"error":let u=l.payload?.errors?.[0]?.message||"Unknown error";this.handleSubscriptionError(e,new Error(u));break}}catch(l){c.error("[AppSyncClient] Failed to parse message",{error:l})}}),a.on("error",d=>{c.error("[AppSyncClient] WebSocket error",{sessionId:t,error:d.message}),this.handleSubscriptionError(e,d)}),a.on("close",(d,l)=>{c.info("[AppSyncClient] WebSocket closed",{sessionId:t,code:d}),e.keepAliveTimer&&clearTimeout(e.keepAliveTimer),!e.destroyed&&this.activeSubscriptions.get(t)===e&&this.handleSubscriptionError(e,new Error(`WebSocket closed: ${d}`))}),e.ws=a,this.resetKeepAliveTimer(e)}catch(o){this.handleSubscriptionError(e,o)}}sendSubscriptionStart(e,t){let r=f(),{sessionId:i,subscriptionId:s}=t,o={host:new URL(r.aws.appsyncUrl).host};this.tokens?.idToken&&(o.Authorization=this.tokens.idToken),e.send(JSON.stringify({id:s,type:"start",payload:{data:JSON.stringify({query:V.onEventCreated,variables:{sessionId:i}}),extensions:{authorization:o}}}))}resetKeepAliveTimer(e){e.keepAliveTimer&&clearTimeout(e.keepAliveTimer),e.keepAliveTimer=setTimeout(()=>{this.handleSubscriptionError(e,new Error("Keep-alive timeout"))},300*1e3)}handleSubscriptionError(e,t){let{sessionId:r,onError:i}=e;if(e.isReconnecting||!this.activeSubscriptions.has(r))return;e.isReconnecting=!0,e.reconnectAttempts++,this.stopHeartbeat(r);let s=e.reconnectAttempts<=x.urgentMaxAttempts,o;if(s?o=Math.min(x.baseDelayMs*Math.pow(x.backoffMultiplier,e.reconnectAttempts-1),x.maxDelayMs):(o=x.persistentDelayMs,e.reconnectAttempts===x.urgentMaxAttempts+1&&c.info("[AppSyncClient] Switching to persistent reconnect (every 5min)",{sessionId:r})),c.info("[AppSyncClient] Scheduling reconnect",{sessionId:r,attempt:e.reconnectAttempts,phase:s?"urgent":"persistent",delayMs:o}),e.ws){try{e.ws.close(1e3)}catch{}e.ws=null}e.keepAliveTimer&&clearTimeout(e.keepAliveTimer),e.reconnectTimer=setTimeout(async()=>{if(e.isReconnecting=!1,e.destroyed||this.activeSubscriptions.get(r)!==e){c.info("[AppSyncClient] Reconnect skipped \u2014 state is no longer canonical",{sessionId:r});return}try{let a=await g.getTokens(this.environment);a&&(g.isTokenExpired(a)?await this.refreshTokens(a)&&c.info("[AppSyncClient] Tokens refreshed before reconnect",{sessionId:r}):this.tokens=a)}catch{c.warn("[AppSyncClient] Token refresh failed before reconnect, using existing tokens",{sessionId:r})}if(e.destroyed||this.activeSubscriptions.get(r)!==e){c.info("[AppSyncClient] Reconnect skipped after token refresh \u2014 state no longer canonical",{sessionId:r});return}e.subscriptionId=(0,Z.v4)(),this.createSubscription(e)},o)}cleanupSubscriptionState(e){if(e.destroyed=!0,e.reconnectTimer&&(clearTimeout(e.reconnectTimer),e.reconnectTimer=void 0),e.keepAliveTimer&&(clearTimeout(e.keepAliveTimer),e.keepAliveTimer=void 0),e.ws){try{e.ws.readyState===Q.default.OPEN&&e.ws.send(JSON.stringify({id:e.subscriptionId,type:"stop"}))}catch{}try{e.ws.close(1e3)}catch{}try{e.ws.removeAllListeners()}catch{}e.ws=null}}subscribeToDeviceKeyRegistered(e,t,r,i){c.info("[AppSyncClient] Subscribing to device key registrations",{userId:e}),this.deviceKeyWatcher&&this.stopDeviceKeyWatcherInternal();let s={userId:e,subscriptionId:(0,Z.v4)(),ws:null,onNewDevice:t,onReconnect:r,onError:i,reconnectAttempts:0,isReconnecting:!1,destroyed:!1};return this.deviceKeyWatcher=s,this.createDeviceKeyWatcherConnection(s),()=>{this.stopDeviceKeyWatcherInternal()}}stopDeviceKeyWatcher(){this.stopDeviceKeyWatcherInternal()}stopDeviceKeyWatcherInternal(){let e=this.deviceKeyWatcher;if(e){if(e.destroyed=!0,e.reconnectTimer&&(clearTimeout(e.reconnectTimer),e.reconnectTimer=void 0),e.keepAliveTimer&&(clearTimeout(e.keepAliveTimer),e.keepAliveTimer=void 0),e.ws){try{e.ws.readyState===Q.default.OPEN&&e.ws.send(JSON.stringify({id:e.subscriptionId,type:"stop"}))}catch{}try{e.ws.close(1e3)}catch{}try{e.ws.removeAllListeners()}catch{}e.ws=null}this.deviceKeyWatcher=null,c.info("[AppSyncClient] Device key watcher stopped")}}createDeviceKeyWatcherConnection(e){try{let t=this.buildRealtimeUrl(),r=new Q.default(t,["graphql-ws"]);r.on("open",()=>{c.info("[AppSyncClient] Device key watcher WebSocket connected",{userId:e.userId}),r.send(JSON.stringify({type:"connection_init"}))}),r.on("message",i=>{try{let s=JSON.parse(i.toString());switch(s.type){case"connection_ack":this.sendDeviceKeyWatcherStart(r,e);break;case"start_ack":c.info("[AppSyncClient] Device key watcher subscription started",{userId:e.userId});let o=e.isReconnecting;if(e.isReconnecting=!1,e.reconnectAttempts=0,o&&e.onReconnect)try{e.onReconnect()}catch(l){c.warn("[AppSyncClient] Device key watcher onReconnect handler threw",{error:l})}break;case"data":this.resetDeviceKeyWatcherKeepAlive(e);let a=s.payload?.data?.onDeviceKeyRegistered;if(a){c.info("[AppSyncClient] Device key registration observed",{userId:e.userId,newDeviceId:a.deviceId,platform:a.platform});try{e.onNewDevice(a)}catch(l){c.warn("[AppSyncClient] Device key watcher onNewDevice handler threw",{error:l})}}break;case"ka":this.resetDeviceKeyWatcherKeepAlive(e);break;case"error":let d=s.payload?.errors?.[0]?.message||"Unknown error";this.handleDeviceKeyWatcherError(e,new Error(d));break}}catch(s){c.error("[AppSyncClient] Failed to parse device key watcher message",{error:s})}}),r.on("error",i=>{c.error("[AppSyncClient] Device key watcher WebSocket error",{userId:e.userId,error:i.message}),this.handleDeviceKeyWatcherError(e,i)}),r.on("close",i=>{c.info("[AppSyncClient] Device key watcher WebSocket closed",{userId:e.userId,code:i}),e.keepAliveTimer&&clearTimeout(e.keepAliveTimer),!e.destroyed&&this.deviceKeyWatcher===e&&this.handleDeviceKeyWatcherError(e,new Error(`WebSocket closed: ${i}`))}),e.ws=r,this.resetDeviceKeyWatcherKeepAlive(e)}catch(t){this.handleDeviceKeyWatcherError(e,t)}}sendDeviceKeyWatcherStart(e,t){let r=f(),{userId:i,subscriptionId:s}=t,o={host:new URL(r.aws.appsyncUrl).host};this.tokens?.idToken&&(o.Authorization=this.tokens.idToken),e.send(JSON.stringify({id:s,type:"start",payload:{data:JSON.stringify({query:V.onDeviceKeyRegistered,variables:{userId:i}}),extensions:{authorization:o}}}))}resetDeviceKeyWatcherKeepAlive(e){e.keepAliveTimer&&clearTimeout(e.keepAliveTimer),e.keepAliveTimer=setTimeout(()=>{this.handleDeviceKeyWatcherError(e,new Error("Device key watcher keep-alive timeout"))},300*1e3)}handleDeviceKeyWatcherError(e,t){if(e.isReconnecting||e.destroyed||this.deviceKeyWatcher!==e)return;if(e.isReconnecting=!0,e.reconnectAttempts++,e.onError)try{e.onError(t)}catch{}if(e.ws){try{e.ws.removeAllListeners()}catch{}try{e.ws.close(1e3)}catch{}e.ws=null}e.keepAliveTimer&&(clearTimeout(e.keepAliveTimer),e.keepAliveTimer=void 0);let i=e.reconnectAttempts<=x.urgentMaxAttempts?Math.min(x.baseDelayMs*Math.pow(x.backoffMultiplier,e.reconnectAttempts-1),x.maxDelayMs):x.persistentDelayMs;c.warn("[AppSyncClient] Device key watcher reconnect scheduled",{userId:e.userId,attempts:e.reconnectAttempts,delayMs:i,error:t.message}),e.reconnectTimer=setTimeout(async()=>{if(e.isReconnecting=!1,e.destroyed||this.deviceKeyWatcher!==e){c.info("[AppSyncClient] Device key watcher reconnect skipped \u2014 state no longer canonical",{userId:e.userId});return}try{let s=await g.getTokens(this.environment);s&&(g.isTokenExpired(s)?await this.refreshTokens(s)&&c.info("[AppSyncClient] Tokens refreshed before device key watcher reconnect",{userId:e.userId}):this.tokens=s)}catch{c.warn("[AppSyncClient] Token refresh failed before device key watcher reconnect, using existing tokens",{userId:e.userId})}e.destroyed||this.deviceKeyWatcher!==e||(e.subscriptionId=(0,Z.v4)(),this.createDeviceKeyWatcherConnection(e))},i)}startHeartbeat(e,t=120*1e3){this.stopHeartbeat(e),this.sendHeartbeat(e);let r=setInterval(()=>{this.sendHeartbeat(e)},t);this.heartbeatTimers.set(e,r),c.info("[AppSyncClient] Heartbeat started",{sessionId:e,intervalMs:t})}stopHeartbeat(e){let t=this.heartbeatTimers.get(e);t&&(clearInterval(t),this.heartbeatTimers.delete(e),c.info("[AppSyncClient] Heartbeat stopped",{sessionId:e}))}async sendHeartbeat(e){try{await this.updateSession({sessionId:e,lastHeartbeatAt:new Date().toISOString()}),c.debug("[AppSyncClient] Heartbeat sent",{sessionId:e})}catch(t){c.warn("[AppSyncClient] Heartbeat failed",{sessionId:e,error:t})}}cleanupSubscriptions(){this.activeSubscriptions.forEach(e=>{this.cleanupSubscriptionState(e)}),this.activeSubscriptions.clear(),this.stopDeviceKeyWatcherInternal(),this.heartbeatTimers.forEach(e=>clearInterval(e)),this.heartbeatTimers.clear()}};var at=m(require("crypto")),ct=m(require("fs")),De=m(require("http")),dt=require("child_process");q();P();$();var tt=m(require("crypto")),rt=m(require("https")),nt=m(require("os")),Rt="G-GS74YEQTB8",_t="lAfOF6OxRzSQ-NsLBRjhAg",Pt="www.google-analytics.com",Ot=`/mp/collect?measurement_id=${Rt}&api_secret=${_t}`,Nt={port_in_use:"server_start",server_listen_failed:"server_start",browser_open_failed:"browser_open",login_timeout:"awaiting_callback",cognito_rejected:"awaiting_callback",state_mismatch:"awaiting_callback",no_authorization_code:"awaiting_callback",token_exchange_failed:"exchanging_code",token_exchange_network_error:"exchanging_code",keychain_write_failed:"storing_tokens",user_aborted:"unknown",unknown:"unknown"};function $t(){let n=typeof process.getuid=="function"?process.getuid():0;return tt.createHash("sha256").update(`${nt.hostname()}-${n}`).digest("hex").substring(0,36)}function it(){return{platform:process.platform,source:process.env.CODEVIBE_TELEMETRY_SOURCE||"production"}}async function st(n,e){try{let t=JSON.stringify({client_id:$t(),events:[{name:n,params:e}]});await new Promise(r=>{let i=rt.request({hostname:Pt,path:Ot,method:"POST",headers:{"Content-Type":"application/json"}},()=>r());i.on("error",()=>r()),i.write(t),i.end(),setTimeout(r,2e3)})}catch{}}async function te(n){await st("auth_completed",{...it(),user_id:n})}async function v(n,e){let t={...it(),reason:n,stage:e?.stage??Nt[n]};typeof e?.httpStatus=="number"&&(t.http_status=e.httpStatus),await st("auth_failed",t)}var Ae=Symbol.for("codevibe.auth.beaconed"),ot=Symbol.for("codevibe.auth.failureReason");function I(n,e){try{Object.defineProperty(n,Ae,{value:!0,enumerable:!1,configurable:!0,writable:!1}),Object.defineProperty(n,ot,{value:e,enumerable:!1,configurable:!0,writable:!1})}catch{}return n}function re(n){return!!(n&&typeof n=="object"&&n[Ae])}function Te(n){if(n&&typeof n=="object"&&n[Ae]){let e=n[ot];if(typeof e=="string")return e}}var ne=8080,lt="/callback",xe=`http://localhost:${ne}${lt}`,J=class n{constructor(){}static getInstance(){return n.instance||(n.instance=new n),n.instance}openBrowser(e){console.log(""),console.log("Opening your browser for sign-in..."),this.isRunningInWSL()?console.log("If your browser does not open, paste this URL in your Windows browser:"):console.log("If your browser does not open automatically, visit this URL:"),console.log(` ${e}`),console.log("");let t=this.getBrowserCommands();this.tryBrowserCommand(t,e,0)}getBrowserCommands(){let e=process.platform;if(e==="darwin")return[{cmd:"open",fixedArgs:[]}];if(e==="win32")return[{cmd:"cmd",fixedArgs:["/c","start",""]}];let t=[];return this.isRunningInWSL()&&(t.push({cmd:"wslview",fixedArgs:[]}),t.push({cmd:"cmd.exe",fixedArgs:["/c","start",""]}),t.push({cmd:"powershell.exe",fixedArgs:["-NoProfile","-Command","Start-Process"]})),t.push({cmd:"xdg-open",fixedArgs:[]}),t}isRunningInWSL(){if(process.platform!=="linux")return!1;try{let e=ct.readFileSync("/proc/sys/kernel/osrelease","utf8");return/microsoft|wsl/i.test(e)}catch{return!1}}tryBrowserCommand(e,t,r){if(r>=e.length){c.debug("[AuthService] No browser-opening command succeeded. User must open the sign-in URL manually (printed to stdout above)."),console.log(""),console.log("\u26A0\uFE0F Could not open browser automatically."),this.isRunningInWSL()?console.log(" WSL detected \u2014 paste this URL in your Windows browser:"):console.log(" Please copy and paste this URL into your browser:"),console.log(` ${t}`),console.log("");return}let i=e[r],s=[...i.fixedArgs,t],o=!1,a=u=>{o||(o=!0,c.debug(`[AuthService] Browser command '${i.cmd}' ${u}; trying next fallback`),this.tryBrowserCommand(e,t,r+1))},d=u=>{o||(o=!0,c.debug(`[AuthService] Browser command '${i.cmd}' ${u}`))},l;try{l=(0,dt.spawn)(i.cmd,s,{detached:!0,stdio:"ignore"})}catch(u){a(`threw synchronously: ${u?.message||u}`);return}l.on("error",u=>{a(`failed to spawn: ${u?.message||u}`)}),l.on("exit",(u,S)=>{u===0?d("exited successfully"):a(S?`terminated by signal ${S}`:`exited with code ${u}`)}),setTimeout(()=>{d("still running after 3s, assuming success")},3e3).unref(),l.unref()}generateState(){return at.randomBytes(32).toString("hex")}buildAuthUrl(e){let t=f(),r=new URLSearchParams({client_id:t.aws.cognitoClientId,response_type:"code",scope:"email openid profile",redirect_uri:xe,state:e});return`https://${t.aws.cognitoDomain}/oauth2/authorize?${r.toString()}`}async exchangeCodeForTokens(e){let t=f(),r=`https://${t.aws.cognitoDomain}/oauth2/token`,i=new URLSearchParams({grant_type:"authorization_code",client_id:t.aws.cognitoClientId,code:e,redirect_uri:xe}),s;try{s=await j(r,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:i.toString()},"Token exchange")}catch(a){throw await v("token_exchange_network_error"),I(a,"token_exchange_network_error"),a}if(!s.ok){let a=await s.text(),d=new Error(`Token exchange failed: ${s.status} ${a}`);throw await v("token_exchange_failed",{httpStatus:s.status}),I(d,"token_exchange_failed"),d}let o=await s.json();return{accessToken:o.access_token,idToken:o.id_token,refreshToken:o.refresh_token,expiresIn:o.expires_in}}decodeJwt(e){let t=e.split(".");if(t.length!==3)throw new Error("Invalid JWT");return JSON.parse(Buffer.from(t[1],"base64").toString("utf-8"))}async refreshTokens(e){let t=f(),r=`https://${t.aws.cognitoDomain}/oauth2/token`,i=new URLSearchParams({grant_type:"refresh_token",client_id:t.aws.cognitoClientId,refresh_token:e}),s=await j(r,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:i.toString()},"Token refresh");if(!s.ok)throw new Error(`Token refresh failed: ${s.status}`);let o=await s.json();return{accessToken:o.access_token,idToken:o.id_token,expiresIn:o.expires_in}}async login(){let e=await g.getTokens(b());if(e&&!g.isTokenExpired(e))return e;let t=this.generateState(),r=this.buildAuthUrl(t);return new Promise((i,s)=>{let o=De.createServer(async(a,d)=>{if(!a.url?.startsWith(lt)){d.writeHead(404),d.end("Not found");return}try{let l=new URL(a.url,`http://localhost:${ne}`),y=l.searchParams.get("code"),u=l.searchParams.get("state"),S=l.searchParams.get("error");if(S){let A=new Error(`OAuth error: ${S}`);throw await v("cognito_rejected"),I(A,"cognito_rejected"),A}if(u!==t){let A=new Error("State mismatch");throw await v("state_mismatch"),I(A,"state_mismatch"),A}if(!y){let A=new Error("No authorization code");throw await v("no_authorization_code"),I(A,"no_authorization_code"),A}let w=await this.exchangeCodeForTokens(y),z=this.decodeJwt(w.idToken),h={accessToken:w.accessToken,idToken:w.idToken,refreshToken:w.refreshToken,expiresAt:Date.now()+w.expiresIn*1e3,userId:z.sub,email:z.email||"unknown"};try{await g.setTokens(h,b())}catch(A){throw await v("keychain_write_failed"),I(A,"keychain_write_failed"),A}d.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),d.end(`
186
198
  <!DOCTYPE html>
187
199
  <html>
188
200
  <head><title>Success</title></head>
@@ -191,7 +203,7 @@ ${r.stack}`)):typeof r=="object"?o+=` ${JSON.stringify(r,kt)}`:o+=` ${r}`),o}log
191
203
  <p>You can close this window.</p>
192
204
  </body>
193
205
  </html>
194
- `),setTimeout(()=>{o.close(()=>i(h))},500)}catch(p){let y=String(p?.message||p).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");d.writeHead(400,{"Content-Type":"text/html; charset=utf-8"}),d.end(`
206
+ `),setTimeout(()=>{o.close(()=>i(h))},500)}catch(l){let y=String(l?.message||l).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");d.writeHead(400,{"Content-Type":"text/html; charset=utf-8"}),d.end(`
195
207
  <!DOCTYPE html>
196
208
  <html>
197
209
  <head><title>Error</title></head>
@@ -201,7 +213,7 @@ ${r.stack}`)):typeof r=="object"?o+=` ${JSON.stringify(r,kt)}`:o+=` ${r}`),o}log
201
213
  <p style="text-align: center; color: #71717a; margin-top: 24px;">You can close this window and try again in your terminal.</p>
202
214
  </body>
203
215
  </html>
204
- `),setTimeout(()=>{o.close(()=>s(p))},500)}});o.on("error",async c=>{if(c.code==="EADDRINUSE"){let d=new Error(`Port ${ne} is in use`);await v("port_in_use"),E(d,"port_in_use"),s(d)}else await v("server_listen_failed"),E(c,"server_listen_failed"),s(c)}),o.listen(ne,"localhost",()=>{a.info("[AuthService] Callback server started"),this.openBrowser(r)}),setTimeout(async()=>{let c=new Error("Login timeout");await v("login_timeout"),E(c,"login_timeout"),o.close(()=>s(c))},120*1e3)})}async logout(){let e=f(),t=await g.deleteTokens(b());return t&&new Promise(r=>{let i=De.createServer((s,o)=>{s.url?.startsWith("/signout")?(o.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),o.end(`
216
+ `),setTimeout(()=>{o.close(()=>s(l))},500)}});o.on("error",async a=>{if(a.code==="EADDRINUSE"){let d=new Error(`Port ${ne} is in use`);await v("port_in_use"),I(d,"port_in_use"),s(d)}else await v("server_listen_failed"),I(a,"server_listen_failed"),s(a)}),o.listen(ne,"localhost",()=>{c.info("[AuthService] Callback server started"),this.openBrowser(r)}),setTimeout(async()=>{let a=new Error("Login timeout");await v("login_timeout"),I(a,"login_timeout"),o.close(()=>s(a))},120*1e3)})}async logout(){let e=f(),t=await g.deleteTokens(b());return t&&new Promise(r=>{let i=De.createServer((s,o)=>{s.url?.startsWith("/signout")?(o.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),o.end(`
205
217
  <!DOCTYPE html>
206
218
  <html>
207
219
  <head><title>Signed Out</title></head>
@@ -210,27 +222,27 @@ ${r.stack}`)):typeof r=="object"?o+=` ${JSON.stringify(r,kt)}`:o+=` ${r}`),o}log
210
222
  <p>You can close this window.</p>
211
223
  </body>
212
224
  </html>
213
- `),setTimeout(()=>{i.close(()=>r(!0))},500)):(o.writeHead(404),o.end("Not found"))});i.on("error",()=>{r(!0)}),i.listen(ne,"localhost",()=>{let s=`https://${e.aws.cognitoDomain}/logout?client_id=${e.aws.cognitoClientId}&logout_uri=${encodeURIComponent(xe.replace("/callback","/signout"))}`;this.openBrowser(s)}),setTimeout(()=>{i.close(()=>r(!0))},30*1e3)})}async getStatus(){let e=await g.getTokens(b());return e?{authenticated:!g.isTokenExpired(e),tokens:e}:{authenticated:!1}}},R=J.getInstance();H();var l={reset:"\x1B[0m",green:"\x1B[32m",red:"\x1B[31m",yellow:"\x1B[33m",cyan:"\x1B[36m",dim:"\x1B[2m"};async function Lt(){console.log(`${l.cyan}CodeVibe Login${l.reset}
214
- `);try{let n=await R.getStatus();if(n.authenticated&&n.tokens){console.log(`${l.yellow}Already logged in as: ${n.tokens.email}${l.reset}`),console.log(`Token expires: ${new Date(n.tokens.expiresAt).toLocaleString()}`),console.log(`
215
- Run '${l.dim}codevibe logout${l.reset}' to sign out first.`),process.exit(0);return}console.log("Opening browser for authentication..."),console.log(`${l.dim}Waiting for callback...${l.reset}
225
+ `),setTimeout(()=>{i.close(()=>r(!0))},500)):(o.writeHead(404),o.end("Not found"))});i.on("error",()=>{r(!0)}),i.listen(ne,"localhost",()=>{let s=`https://${e.aws.cognitoDomain}/logout?client_id=${e.aws.cognitoClientId}&logout_uri=${encodeURIComponent(xe.replace("/callback","/signout"))}`;this.openBrowser(s)}),setTimeout(()=>{i.close(()=>r(!0))},30*1e3)})}async getStatus(){let e=await g.getTokens(b());return e?{authenticated:!g.isTokenExpired(e),tokens:e}:{authenticated:!1}}},R=J.getInstance();q();var p={reset:"\x1B[0m",green:"\x1B[32m",red:"\x1B[31m",yellow:"\x1B[33m",cyan:"\x1B[36m",dim:"\x1B[2m"};async function Lt(){console.log(`${p.cyan}CodeVibe Login${p.reset}
226
+ `);try{let n=await R.getStatus();if(n.authenticated&&n.tokens){console.log(`${p.yellow}Already logged in as: ${n.tokens.email}${p.reset}`),console.log(`Token expires: ${new Date(n.tokens.expiresAt).toLocaleString()}`),console.log(`
227
+ Run '${p.dim}codevibe logout${p.reset}' to sign out first.`),process.exit(0);return}console.log("Opening browser for authentication..."),console.log(`${p.dim}Waiting for callback...${p.reset}
216
228
  `);let e=await R.login();e&&(console.log(`
217
- ${l.green}\u2713 Authentication successful!${l.reset}`),console.log(` User: ${e.email}`),console.log(` User ID: ${e.userId}`),console.log(` Expires: ${new Date(e.expiresAt).toLocaleString()}`),await te(e.userId)),process.exit(0)}catch(n){console.log(`
218
- ${l.red}\u2717 Authentication failed${l.reset}`),console.log(` Error: ${n.message}`),re(n)||await v("unknown"),process.exit(1)}}async function Ut(){console.log(`${l.cyan}CodeVibe Logout${l.reset}
219
- `);try{let n=await R.getStatus();if(!n.authenticated){console.log(`${l.yellow}Not logged in.${l.reset}`),process.exit(0);return}let e=n.tokens?.email;await R.logout()?(console.log(`${l.green}\u2713 Logged out successfully.${l.reset}`),console.log(` Previous user: ${e}`),console.log(`
220
- ${l.dim}Clearing browser session...${l.reset}`)):console.log(`${l.red}\u2717 Failed to log out.${l.reset}`),process.exit(0)}catch(n){console.log(`${l.red}\u2717 Logout failed: ${n.message}${l.reset}`),process.exit(1)}}async function Bt(){console.log(`${l.cyan}CodeVibe Auth Status${l.reset}
221
- `);try{let n=await R.getStatus();if(!n.tokens){console.log(`${l.yellow}Not authenticated.${l.reset}`),console.log(`
222
- Run '${l.dim}codevibe login${l.reset}' to sign in.`),process.exit(0);return}let e=!n.authenticated;console.log(e?`${l.yellow}\u26A0 Token expired${l.reset}`:`${l.green}\u2713 Authenticated${l.reset}`),console.log(` User: ${n.tokens.email}`),console.log(` User ID: ${n.tokens.userId}`),console.log(` Expires: ${new Date(n.tokens.expiresAt).toLocaleString()}`),e&&console.log(`
223
- ${l.dim}Token will be refreshed automatically.${l.reset}`),process.exit(0)}catch(n){console.log(`${l.red}\u2717 Status check failed: ${n.message}${l.reset}`),process.exit(1)}}async function Mt(){console.log(`${l.cyan}CodeVibe Reset Device${l.reset}
224
- `),console.log(`${l.red}\u26A0 WARNING: This will delete your device identity.${l.reset}`),console.log(`${l.red} Old encrypted sessions will become inaccessible.${l.reset}
225
- `);let{keychainManager:n}=await Promise.resolve().then(()=>(P(),Ge));try{await n.clearAllData(),console.log(`${l.green}\u2713 Device reset complete.${l.reset}`),console.log(` Run '${l.dim}codevibe login${l.reset}' to set up again.`),process.exit(0)}catch(e){console.log(`${l.red}\u2717 Reset failed: ${e.message}${l.reset}`),process.exit(1)}}function Wt(){console.log(`CodeVibe Authentication
229
+ ${p.green}\u2713 Authentication successful!${p.reset}`),console.log(` User: ${e.email}`),console.log(` User ID: ${e.userId}`),console.log(` Expires: ${new Date(e.expiresAt).toLocaleString()}`),await te(e.userId)),process.exit(0)}catch(n){console.log(`
230
+ ${p.red}\u2717 Authentication failed${p.reset}`),console.log(` Error: ${n.message}`),re(n)||await v("unknown"),process.exit(1)}}async function Ut(){console.log(`${p.cyan}CodeVibe Logout${p.reset}
231
+ `);try{let n=await R.getStatus();if(!n.authenticated){console.log(`${p.yellow}Not logged in.${p.reset}`),process.exit(0);return}let e=n.tokens?.email;await R.logout()?(console.log(`${p.green}\u2713 Logged out successfully.${p.reset}`),console.log(` Previous user: ${e}`),console.log(`
232
+ ${p.dim}Clearing browser session...${p.reset}`)):console.log(`${p.red}\u2717 Failed to log out.${p.reset}`),process.exit(0)}catch(n){console.log(`${p.red}\u2717 Logout failed: ${n.message}${p.reset}`),process.exit(1)}}async function Mt(){console.log(`${p.cyan}CodeVibe Auth Status${p.reset}
233
+ `);try{let n=await R.getStatus();if(!n.tokens){console.log(`${p.yellow}Not authenticated.${p.reset}`),console.log(`
234
+ Run '${p.dim}codevibe login${p.reset}' to sign in.`),process.exit(0);return}let e=!n.authenticated;console.log(e?`${p.yellow}\u26A0 Token expired${p.reset}`:`${p.green}\u2713 Authenticated${p.reset}`),console.log(` User: ${n.tokens.email}`),console.log(` User ID: ${n.tokens.userId}`),console.log(` Expires: ${new Date(n.tokens.expiresAt).toLocaleString()}`),e&&console.log(`
235
+ ${p.dim}Token will be refreshed automatically.${p.reset}`),process.exit(0)}catch(n){console.log(`${p.red}\u2717 Status check failed: ${n.message}${p.reset}`),process.exit(1)}}async function Bt(){console.log(`${p.cyan}CodeVibe Reset Device${p.reset}
236
+ `),console.log(`${p.red}\u26A0 WARNING: This will delete your device identity.${p.reset}`),console.log(`${p.red} Old encrypted sessions will become inaccessible.${p.reset}
237
+ `);let{keychainManager:n}=await Promise.resolve().then(()=>(P(),Ge));try{await n.clearAllData(),console.log(`${p.green}\u2713 Device reset complete.${p.reset}`),console.log(` Run '${p.dim}codevibe login${p.reset}' to set up again.`),process.exit(0)}catch(e){console.log(`${p.red}\u2717 Reset failed: ${e.message}${p.reset}`),process.exit(1)}}function Wt(){console.log(`CodeVibe Authentication
226
238
  `),console.log("Usage:"),console.log(" codevibe login - Sign in via browser"),console.log(" codevibe logout - Sign out"),console.log(" codevibe status - Show auth status"),console.log(" codevibe reset-device - Reset device identity (destructive)"),console.log(`
227
- Environment:`),console.log(' Set ENVIRONMENT env var to "development" or "production" (default)'),console.log(" Example: ENVIRONMENT=development codevibe login")}async function le(n){let e=b();console.log(`${l.dim}Environment: ${e}${l.reset}
228
- `);let r=n.slice(2).filter(i=>!i.startsWith("--"))[0];switch(r){case"login":await Lt();break;case"logout":await Ut();break;case"status":await Bt();break;case"reset-device":await Mt();break;default:Wt(),process.exit(r?1:0)}}require.main===module&&le(process.argv).catch(n=>{console.error("Error:",n),process.exit(1)});H();N();var Ft=/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g;function pt(n){let e=Re(n);if(!e)return null;let t=qt(e);if(t)return t;let r=Ht(e);return r||null}function Re(n){return n.replace(/\r/g,`
239
+ Environment:`),console.log(' Set ENVIRONMENT env var to "development" or "production" (default)'),console.log(" Example: ENVIRONMENT=development codevibe login")}async function pe(n){let e=b();console.log(`${p.dim}Environment: ${e}${p.reset}
240
+ `);let r=n.slice(2).filter(i=>!i.startsWith("--"))[0];switch(r){case"login":await Lt();break;case"logout":await Ut();break;case"status":await Mt();break;case"reset-device":await Bt();break;default:Wt(),process.exit(r?1:0)}}require.main===module&&pe(process.argv).catch(n=>{console.error("Error:",n),process.exit(1)});q();$();var Ft=/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g;function pt(n){let e=Re(n);if(!e)return null;let t=Ht(e);if(t)return t;let r=qt(e);return r||null}function Re(n){return n.replace(/\r/g,`
229
241
  `).replace(Ft,"").replace(/[│┌┐└┘─├┤┬┴┼╌╎╭╮╯╰║═╔╗╚╝╠╣╦╩╬]/g," ").replace(/[ \t]+\n/g,`
230
242
  `).replace(/\n{3,}/g,`
231
243
 
232
- `).trim()}function qt(n){let e=n.split(`
244
+ `).trim()}function Ht(n){let e=n.split(`
233
245
  `).map(y=>y.trim()),t=jt(e,y=>/\[(?:y\/n|Y\/n|y\/N)\]/.test(y)),r=t>=0?e[t]:null;if(!r)return null;let i=gt(e,t),s=i.length>0?i.join(`
234
- `):r,o=s.toLowerCase(),c=o.includes("what to change")||o.includes("what should")||o.includes("provide")||o.includes("instructions");return{kind:"yes_no",promptText:s,options:c?[{number:"1",text:"Yes"},{number:"2",text:"No, provide instructions"}]:[{number:"1",text:"Yes"},{number:"2",text:"No"}],submitMap:{1:"y",2:"n"},requiresFollowUpText:c}}function Ht(n){let e=n.split(`
246
+ `):r,o=s.toLowerCase(),a=o.includes("what to change")||o.includes("what should")||o.includes("provide")||o.includes("instructions");return{kind:"yes_no",promptText:s,options:a?[{number:"1",text:"Yes"},{number:"2",text:"No, provide instructions"}]:[{number:"1",text:"Yes"},{number:"2",text:"No"}],submitMap:{1:"y",2:"n"},requiresFollowUpText:a}}function qt(n){let e=n.split(`
235
247
  `).map(d=>d.trim()),t=Vt(e);if(t.length<2)return null;let r=t.map(({line:d})=>ut(d)).filter(d=>!!d),i={};for(let d of r)i[d.number]=d.number;let s=t[0]?.index??-1,o=gt(e,s-1);return{kind:"numbered",promptText:o.length>0?o.join(`
236
- `):"Select an option",options:r,submitMap:i}}function jt(n,e){for(let t=n.length-1;t>=0;t-=1)if(e(n[t]))return t;return-1}function ut(n){let e=n.match(/^(?:[>›❯▸▶➜➤*●]\s*)?(\d+)\.\s+(.*)$/);return e?{number:e[1],text:e[2]}:null}function Vt(n){let e=n.map((r,i)=>({index:i,line:r,parsed:ut(r)})).filter(r=>!!r.parsed);if(e.length===0)return[];let t=[e[e.length-1]];for(let r=e.length-2;r>=0;r-=1){let i=e[r],s=t[0];if(i.index!==s.index-1)break;t.unshift(i)}return t.map(({index:r,line:i})=>({index:r,line:i}))}function gt(n,e){if(e<0)return[];let t=Ce(n,e);if(t<0)return[];let{start:r,end:i}=Ke(n,t),s=n.slice(r,i+1).filter(Boolean);if(zt(s)){let u=Jt(n,r-1);return u.length>0?u:s}if(r<=1)return s;let o=r-1;if(o=Ce(n,o),o<0||o===r-1)return s;let{start:c,end:d}=Ke(n,o),p=n.slice(c,d+1).filter(Boolean);return p.some(yt)?[...p,...s]:s}function yt(n){return/^(?:would you like to|do you want to|the model would like to|action required|confirm)\b/i.test(n)}function Ce(n,e){let t=e;for(;t>=0&&!n[t];)t-=1;return t}function Ke(n,e){let t=e;for(;t>=0&&n[t];)t-=1;return{start:t+1,end:e}}function Jt(n,e){let t=[],r=e;for(;r>=0&&t.length<2&&(r=Ce(n,r),!(r<0));){let{start:s,end:o}=Ke(n,r),c=n.slice(s,o+1).filter(Boolean);c.length>0&&t.unshift(c),r=s-1}if(t.length===0)return[];let i=t.findIndex(s=>s.some(yt));return i>=0?t.slice(i).flat():t[t.length-1]}function zt(n){return n.length===0?!1:n.filter(Gt).length>=Math.max(2,Math.ceil(n.length/2))}function Gt(n){return/^\d+\s/.test(n)}G();P();G();N();async function W(n,e,t,r={}){let i;try{i=await t.getSession(n)}catch(u){return a.warn("[SessionRekey] Failed to fetch session state for re-key",{sessionId:n,error:u instanceof Error?u.message:String(u)}),0}if(!i)return a.warn("[SessionRekey] Session not found, skipping re-key",{sessionId:n}),0;if(!i.isEncrypted)return 0;let s=i.encryptedKeys||[],o=new Set(s.map(u=>u.deviceId)),c=r.forceDeviceIds??new Set,d;try{d=await t.listUserDeviceKeys()}catch(u){return a.warn("[SessionRekey] Failed to fetch user device keys",{sessionId:n,error:u instanceof Error?u.message:String(u)}),0}let p=d.filter(u=>!o.has(u.deviceId)||c.has(u.deviceId));if(p.length===0)return 0;a.info("[SessionRekey] Granting session key to devices",{sessionId:n,existingDeviceCount:s.length,grantCount:p.length,grantDeviceIds:p.map(u=>u.deviceId),forceCount:c.size});let y=0;for(let u of p)try{let S=k.encryptSessionKey(e,u.publicKey);await t.grantSessionKey({sessionId:n,deviceId:u.deviceId,encryptedKey:S.encryptedKey,ephemeralPublicKey:S.ephemeralPublicKey}),y++,a.info("[SessionRekey] Granted session key to device",{sessionId:n,deviceId:u.deviceId,platform:u.platform})}catch(S){a.warn("[SessionRekey] Failed to grant session key to device",{sessionId:n,deviceId:u.deviceId,error:S instanceof Error?S.message:String(S)})}return y>0&&a.info("[SessionRekey] Re-key complete",{sessionId:n,grantedCount:y,requestedCount:p.length}),y}async function pe(n,e,t){try{let r=await e.listUserDeviceKeys();if(r.length===0)return t.info("No device keys found, session will not be encrypted"),null;t.info("Preparing session encryption",{sessionId:n,deviceCount:r.length});let{sessionKey:i,encryptedKeys:s}=g.createSessionKey(r);return t.info("Session encryption prepared",{sessionId:n,deviceCount:s.length}),{sessionKey:i,encryptedKeys:s}}catch(r){return t.warn("Failed to prepare session encryption:",r),null}}async function _e(n,e,t){let{sessionId:r,userId:i,agentType:s,projectPath:o,metadata:c}=n,d=null;try{d=await e.getSession(r)}catch(w){t.warn("Failed to get session (will attempt to create new)",{sessionId:r,error:w})}if(d){t.info("Session exists in backend - reactivating",{sessionId:r,previousStatus:d.status});try{await e.updateSession({sessionId:r,status:"ACTIVE"})}catch(h){t.warn("Failed to reactivate existing session, will continue",{sessionId:r,error:h})}let w=null,z=d.encryptedKeys;if(d.isEncrypted&&z?.length)try{let h=await g.getSessionKey(r,z);h?(w=h,g.cacheSessionKey(r,h),t.info("Session key retrieved for resumed session",{sessionId:r})):t.warn("No encrypted key for this device; proceeding without decryption",{sessionId:r})}catch(h){t.warn("Failed to retrieve session key for resumed session",{sessionId:r,error:h})}if(w)try{let h=await W(r,w,e);h>0&&t.info("Session re-keyed for newly registered devices on resume",{sessionId:r,newDeviceCount:h})}catch(h){t.warn("Session re-key on resume failed (non-fatal)",{sessionId:r,error:h instanceof Error?h.message:String(h)})}return{resumed:!0,sessionKey:w}}let p=await pe(r,e,t),y=o,u=c;p&&(y=k.encryptContent(o,p.sessionKey),u&&Object.keys(u).length>0&&(u={encrypted:k.encryptMetadata(u,p.sessionKey)}),t.info("Session data encrypted",{sessionId:r})),t.info("Creating new session in backend",{sessionId:r,userId:i,agentType:s,isEncrypted:!!p}),await e.createSession({sessionId:r,userId:i,agentType:s,projectPath:y,status:"ACTIVE",metadata:u,isEncrypted:p?!0:void 0,creatorDeviceId:p?await g.getDeviceId():void 0,encryptionVersion:p?1:void 0,encryptedKeys:p?.encryptedKeys});let S=p?.sessionKey||null;return p&&g.cacheSessionKey(r,p.sessionKey),t.info("Session created",{sessionId:r,userId:i,isEncrypted:!!p}),{resumed:!1,sessionKey:S}}P();function Pe(n,e){let t=n.getCurrentUserId(),r=async(s,o)=>{let c=g.getCachedSessionIds();if(c.length===0){e.info("[DeviceKeyWatcher] No active sessions to re-key",{reason:s});return}e.info("[DeviceKeyWatcher] Running re-key pass",{reason:s,activeSessionCount:c.length,forceDeviceCount:o?.size??0});for(let d of c){let p=g.getCachedSessionKey(d);if(p)try{let y=await W(d,p,n,o?{forceDeviceIds:o}:void 0);y>0&&e.info("[DeviceKeyWatcher] Session re-keyed",{sessionId:d,newDeviceCount:y,reason:s})}catch(y){e.warn("[DeviceKeyWatcher] Re-key failed for session (non-fatal)",{sessionId:d,reason:s,error:y instanceof Error?y.message:String(y)})}}},i=n.subscribeToDeviceKeyRegistered(t,s=>{e.info("[DeviceKeyWatcher] New device observed, triggering re-key",{userId:t,newDeviceId:s.deviceId,platform:s.platform,deviceName:s.deviceName}),r(`new-device:${s.deviceId}`,new Set([s.deviceId]))},()=>{r("watcher-reconnect")},s=>{e.warn("[DeviceKeyWatcher] Subscription error (will retry)",{error:s instanceof Error?s.message:String(s)})});return e.info("[DeviceKeyWatcher] Started",{userId:t}),i}P();async function Oe(n,e){try{let t=await g.getDeviceId(),r=await g.getDevicePublicKey(),i=g.getDevicePlatform(),s=g.getDeviceName();e.info("Registering device encryption key",{deviceId:t,platform:i,deviceName:s}),await n.registerDeviceKey(t,r,i,s),g.setIsRegistered(!0),e.info("Device encryption key registered successfully",{deviceId:t})}catch(t){e.warn("Failed to register device encryption key (E2E encryption may not work):",t)}}0&&(module.exports={AgentType,AppSyncClient,AuthService,CryptoError,CryptoService,DeliveryStatus,ENCRYPTION_VERSION,EventSource,EventType,KeychainError,KeychainManager,Logger,SessionStatus,authService,createLogger,cryptoService,errorWasBeaconed,fireAuthCompletedBeacon,fireAuthFailedBeacon,getConfig,getEnvironment,getErrorReason,keychainManager,loadConfig,logger,markErrorBeaconed,mutations,normalizeSnapshot,parseInteractivePrompt,prepareSessionEncryption,queries,registerDeviceEncryptionKey,rekeySessionForNewDevices,resumeOrCreateSession,runAuthCli,startDeviceKeyWatcher,subscriptions});
248
+ `):"Select an option",options:r,submitMap:i}}function jt(n,e){for(let t=n.length-1;t>=0;t-=1)if(e(n[t]))return t;return-1}function ut(n){let e=n.match(/^(?:[>›❯▸▶➜➤*●]\s*)?(\d+)\.\s+(.*)$/);return e?{number:e[1],text:e[2]}:null}function Vt(n){let e=n.map((r,i)=>({index:i,line:r,parsed:ut(r)})).filter(r=>!!r.parsed);if(e.length===0)return[];let t=[e[e.length-1]];for(let r=e.length-2;r>=0;r-=1){let i=e[r],s=t[0];if(i.index!==s.index-1)break;t.unshift(i)}return t.map(({index:r,line:i})=>({index:r,line:i}))}function gt(n,e){if(e<0)return[];let t=Ce(n,e);if(t<0)return[];let{start:r,end:i}=Ke(n,t),s=n.slice(r,i+1).filter(Boolean);if(zt(s)){let u=Jt(n,r-1);return u.length>0?u:s}if(r<=1)return s;let o=r-1;if(o=Ce(n,o),o<0||o===r-1)return s;let{start:a,end:d}=Ke(n,o),l=n.slice(a,d+1).filter(Boolean);return l.some(yt)?[...l,...s]:s}function yt(n){return/^(?:would you like to|do you want to|the model would like to|action required|confirm)\b/i.test(n)}function Ce(n,e){let t=e;for(;t>=0&&!n[t];)t-=1;return t}function Ke(n,e){let t=e;for(;t>=0&&n[t];)t-=1;return{start:t+1,end:e}}function Jt(n,e){let t=[],r=e;for(;r>=0&&t.length<2&&(r=Ce(n,r),!(r<0));){let{start:s,end:o}=Ke(n,r),a=n.slice(s,o+1).filter(Boolean);a.length>0&&t.unshift(a),r=s-1}if(t.length===0)return[];let i=t.findIndex(s=>s.some(yt));return i>=0?t.slice(i).flat():t[t.length-1]}function zt(n){return n.length===0?!1:n.filter(Gt).length>=Math.max(2,Math.ceil(n.length/2))}function Gt(n){return/^\d+\s/.test(n)}G();P();G();$();async function W(n,e,t,r={}){let i;try{i=await t.getSession(n)}catch(u){return c.warn("[SessionRekey] Failed to fetch session state for re-key",{sessionId:n,error:u instanceof Error?u.message:String(u)}),0}if(!i)return c.warn("[SessionRekey] Session not found, skipping re-key",{sessionId:n}),0;if(!i.isEncrypted)return 0;let s=i.encryptedKeys||[],o=new Set(s.map(u=>u.deviceId)),a=r.forceDeviceIds??new Set,d;try{d=await t.listUserDeviceKeys()}catch(u){return c.warn("[SessionRekey] Failed to fetch user device keys",{sessionId:n,error:u instanceof Error?u.message:String(u)}),0}let l=d.filter(u=>!o.has(u.deviceId)||a.has(u.deviceId));if(l.length===0)return 0;c.info("[SessionRekey] Granting session key to devices",{sessionId:n,existingDeviceCount:s.length,grantCount:l.length,grantDeviceIds:l.map(u=>u.deviceId),forceCount:a.size});let y=0;for(let u of l)try{let S=k.encryptSessionKey(e,u.publicKey);await t.grantSessionKey({sessionId:n,deviceId:u.deviceId,encryptedKey:S.encryptedKey,ephemeralPublicKey:S.ephemeralPublicKey}),y++,c.info("[SessionRekey] Granted session key to device",{sessionId:n,deviceId:u.deviceId,platform:u.platform})}catch(S){c.warn("[SessionRekey] Failed to grant session key to device",{sessionId:n,deviceId:u.deviceId,error:S instanceof Error?S.message:String(S)})}return y>0&&c.info("[SessionRekey] Re-key complete",{sessionId:n,grantedCount:y,requestedCount:l.length}),y}async function ue(n,e,t){try{let r=await e.listUserDeviceKeys();if(r.length===0)return t.info("No device keys found, session will not be encrypted"),null;t.info("Preparing session encryption",{sessionId:n,deviceCount:r.length});let{sessionKey:i,encryptedKeys:s}=g.createSessionKey(r);return t.info("Session encryption prepared",{sessionId:n,deviceCount:s.length}),{sessionKey:i,encryptedKeys:s}}catch(r){return t.warn("Failed to prepare session encryption:",r),null}}async function _e(n,e,t){let{sessionId:r,userId:i,agentType:s,projectPath:o,metadata:a}=n,d=null;try{d=await e.getSession(r)}catch(w){t.warn("Failed to get session (will attempt to create new)",{sessionId:r,error:w})}if(d){t.info("Session exists in backend - reactivating",{sessionId:r,previousStatus:d.status});try{await e.updateSession({sessionId:r,status:"ACTIVE"})}catch(h){t.warn("Failed to reactivate existing session, will continue",{sessionId:r,error:h})}let w=null,z=d.encryptedKeys;if(d.isEncrypted&&z?.length)try{let h=await g.getSessionKey(r,z);h?(w=h,g.cacheSessionKey(r,h),t.info("Session key retrieved for resumed session",{sessionId:r})):t.warn("No encrypted key for this device; proceeding without decryption",{sessionId:r})}catch(h){t.warn("Failed to retrieve session key for resumed session",{sessionId:r,error:h})}if(w)try{let h=await W(r,w,e);h>0&&t.info("Session re-keyed for newly registered devices on resume",{sessionId:r,newDeviceCount:h})}catch(h){t.warn("Session re-key on resume failed (non-fatal)",{sessionId:r,error:h instanceof Error?h.message:String(h)})}return{resumed:!0,sessionKey:w}}let l=await ue(r,e,t),y=o,u=a;l&&(y=k.encryptContent(o,l.sessionKey),u&&Object.keys(u).length>0&&(u={encrypted:k.encryptMetadata(u,l.sessionKey)}),t.info("Session data encrypted",{sessionId:r})),t.info("Creating new session in backend",{sessionId:r,userId:i,agentType:s,isEncrypted:!!l}),await e.createSession({sessionId:r,userId:i,agentType:s,projectPath:y,status:"ACTIVE",metadata:u,isEncrypted:l?!0:void 0,creatorDeviceId:l?await g.getDeviceId():void 0,encryptionVersion:l?1:void 0,encryptedKeys:l?.encryptedKeys});let S=l?.sessionKey||null;return l&&g.cacheSessionKey(r,l.sessionKey),t.info("Session created",{sessionId:r,userId:i,isEncrypted:!!l}),{resumed:!1,sessionKey:S}}P();function Pe(n,e){let t=n.getCurrentUserId(),r=async(s,o)=>{let a=g.getCachedSessionIds();if(a.length===0){e.info("[DeviceKeyWatcher] No active sessions to re-key",{reason:s});return}e.info("[DeviceKeyWatcher] Running re-key pass",{reason:s,activeSessionCount:a.length,forceDeviceCount:o?.size??0});for(let d of a){let l=g.getCachedSessionKey(d);if(l)try{let y=await W(d,l,n,o?{forceDeviceIds:o}:void 0);y>0&&e.info("[DeviceKeyWatcher] Session re-keyed",{sessionId:d,newDeviceCount:y,reason:s})}catch(y){e.warn("[DeviceKeyWatcher] Re-key failed for session (non-fatal)",{sessionId:d,reason:s,error:y instanceof Error?y.message:String(y)})}}},i=n.subscribeToDeviceKeyRegistered(t,s=>{e.info("[DeviceKeyWatcher] New device observed, triggering re-key",{userId:t,newDeviceId:s.deviceId,platform:s.platform,deviceName:s.deviceName}),r(`new-device:${s.deviceId}`,new Set([s.deviceId]))},()=>{r("watcher-reconnect")},s=>{e.warn("[DeviceKeyWatcher] Subscription error (will retry)",{error:s instanceof Error?s.message:String(s)})});return e.info("[DeviceKeyWatcher] Started",{userId:t}),i}P();async function Oe(n,e){try{let t=await g.getDeviceId(),r=await g.getDevicePublicKey(),i=g.getDevicePlatform(),s=g.getDeviceName();e.info("Registering device encryption key",{deviceId:t,platform:i,deviceName:s}),await n.registerDeviceKey(t,r,i,s),g.setIsRegistered(!0),e.info("Device encryption key registered successfully",{deviceId:t})}catch(t){e.warn("Failed to register device encryption key (E2E encryption may not work):",t)}}0&&(module.exports={AgentType,AppSyncClient,AuthService,CryptoError,CryptoService,DeliveryStatus,ENCRYPTION_VERSION,EventSource,EventType,KeychainError,KeychainManager,Logger,SessionStatus,authService,createLogger,cryptoService,errorWasBeaconed,fireAuthCompletedBeacon,fireAuthFailedBeacon,getConfig,getEnvironment,getErrorReason,keychainManager,loadConfig,logger,markErrorBeaconed,mutations,normalizeSnapshot,parseInteractivePrompt,prepareSessionEncryption,queries,registerDeviceEncryptionKey,rekeySessionForNewDevices,resumeOrCreateSession,runAuthCli,startDeviceKeyWatcher,subscriptions});
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quantiya/codevibe-core",
3
- "version": "1.0.15",
3
+ "version": "1.0.16",
4
4
  "description": "Core library for CodeVibe plugins - shared keychain, crypto, AppSync, and auth functionality",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quantiya/codevibe-claude-plugin",
3
- "version": "1.0.24",
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": {
@@ -47,7 +47,7 @@
47
47
  "node": ">=18.0.0"
48
48
  },
49
49
  "dependencies": {
50
- "@quantiya/codevibe-core": "^1.0.15",
50
+ "@quantiya/codevibe-core": "^1.0.16",
51
51
  "dotenv": "^16.6.1",
52
52
  "express": "^5.1.0",
53
53
  "graphql": "^16.12.0",