@quantiya/codevibe-claude-plugin 1.0.27 → 1.0.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/bin/codevibe-claude +24 -0
- package/node_modules/@quantiya/codevibe-core/dist/auth/auth-telemetry.d.ts +9 -0
- package/node_modules/@quantiya/codevibe-core/dist/index.js +3 -3
- package/node_modules/@quantiya/codevibe-core/package.json +1 -1
- package/package.json +2 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codevibe-claude",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.29",
|
|
4
4
|
"description": "Sync Claude Code sessions with iOS mobile app via AWS backend. Control Claude Code from your phone with real-time bidirectional synchronization.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "CodeVibe Team"
|
package/bin/codevibe-claude
CHANGED
|
@@ -43,6 +43,30 @@ done
|
|
|
43
43
|
SCRIPT_DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
|
|
44
44
|
PLUGIN_DIR="$(dirname "$SCRIPT_DIR")"
|
|
45
45
|
|
|
46
|
+
# ─── PATH augmentation ───────────────────────────────────────────────
|
|
47
|
+
# When the install one-liner runs in a fresh terminal, Homebrew's
|
|
48
|
+
# installer writes the shellenv eval into ~/.zprofile (and similar)
|
|
49
|
+
# but the user's current shell hasn't sourced it yet. Subsequent
|
|
50
|
+
# codevibe-* runs in that same terminal then fail tmux discovery
|
|
51
|
+
# because /opt/homebrew/bin isn't on PATH. Prepend common locations
|
|
52
|
+
# so the wrapper recovers without forcing the user to open a new
|
|
53
|
+
# terminal. Prepend (not append) is deliberate: the Homebrew binary
|
|
54
|
+
# install.sh just laid down should win over any older system binary
|
|
55
|
+
# (e.g. a stale /usr/bin/node on Linux) at the same name. To preserve
|
|
56
|
+
# the relative ordering of augmented dirs, build a single prefix
|
|
57
|
+
# string and prepend it once — iterating prepend-per-dir would
|
|
58
|
+
# reverse intended order. ${PATH:+:$PATH} keeps an empty starting
|
|
59
|
+
# PATH from producing a trailing colon (which makes cwd searchable).
|
|
60
|
+
_CV_NEW_PATHS=""
|
|
61
|
+
for _CV_DIR in /opt/homebrew/bin /opt/homebrew/sbin /usr/local/bin /usr/local/sbin /opt/local/bin /usr/bin /bin; do
|
|
62
|
+
case ":$PATH:" in
|
|
63
|
+
*":$_CV_DIR:"*) ;;
|
|
64
|
+
*) [ -d "$_CV_DIR" ] && _CV_NEW_PATHS="$_CV_NEW_PATHS:$_CV_DIR" ;;
|
|
65
|
+
esac
|
|
66
|
+
done
|
|
67
|
+
[ -n "$_CV_NEW_PATHS" ] && export PATH="${_CV_NEW_PATHS#:}${PATH:+:$PATH}"
|
|
68
|
+
unset _CV_DIR _CV_NEW_PATHS
|
|
69
|
+
|
|
46
70
|
# ─── Wrapper telemetry (GA4 Measurement Protocol) ─────────────────────
|
|
47
71
|
# Diagnoses agent CLI failures: pre-flight bailouts, fast-die patterns,
|
|
48
72
|
# whether SessionStart hook fired, exit code. Background curl, fail
|
|
@@ -40,10 +40,19 @@ export declare function fireAuthCompletedBeacon(userId: string): Promise<void>;
|
|
|
40
40
|
* `reason` is constrained to the `AuthFailureReason` union — this
|
|
41
41
|
* is the ONLY input path; passing a raw error message is a compile
|
|
42
42
|
* error.
|
|
43
|
+
*
|
|
44
|
+
* Optional `errorFragment` is a diagnostic dimension reserved for
|
|
45
|
+
* `reason: 'unknown'`. The outer `auth-cli` catch passes the first
|
|
46
|
+
* portion of `error.message` here so the next analytics pass can
|
|
47
|
+
* see what's hiding in `unknown` and we can ship a typed reason in
|
|
48
|
+
* a follow-up. Sanitized inside (newlines/tabs/quotes/backslashes
|
|
49
|
+
* stripped, non-ASCII dropped, capped at 100 chars to match GA4's
|
|
50
|
+
* default per-param limit).
|
|
43
51
|
*/
|
|
44
52
|
export declare function fireAuthFailedBeacon(reason: AuthFailureReason, extra?: {
|
|
45
53
|
httpStatus?: number;
|
|
46
54
|
stage?: AuthStage;
|
|
55
|
+
errorFragment?: string;
|
|
47
56
|
}): Promise<void>;
|
|
48
57
|
/**
|
|
49
58
|
* Attach the reason + beaconed marker to an Error. Non-enumerable so
|
|
@@ -194,7 +194,7 @@ ${r.stack}`)):typeof r=="object"?o+=` ${JSON.stringify(r,kt)}`:o+=` ${r}`),o}log
|
|
|
194
194
|
lastUsedAt
|
|
195
195
|
}
|
|
196
196
|
}
|
|
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(`
|
|
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]};if(typeof e?.httpStatus=="number"&&(t.http_status=e.httpStatus),e?.errorFragment){let r=e.errorFragment.replace(/[\n\r\t"\\]/g," ").replace(/[^\x20-\x7E]/g,"").trim().substring(0,100);r&&(t.error_fragment=r)}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(`
|
|
198
198
|
<!DOCTYPE html>
|
|
199
199
|
<html>
|
|
200
200
|
<head><title>Success</title></head>
|
|
@@ -226,8 +226,8 @@ ${r.stack}`)):typeof r=="object"?o+=` ${JSON.stringify(r,kt)}`:o+=` ${r}`),o}log
|
|
|
226
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
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}
|
|
228
228
|
`);let e=await R.login();e&&(console.log(`
|
|
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}`)
|
|
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){if(console.log(`
|
|
230
|
+
${p.red}\u2717 Authentication failed${p.reset}`),console.log(` Error: ${n.message}`),!re(n)){let e=typeof n?.message=="string"?n.message:void 0;await v("unknown",{errorFragment:e})}process.exit(1)}}async function Ut(){console.log(`${p.cyan}CodeVibe Logout${p.reset}
|
|
231
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
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
233
|
`);try{let n=await R.getStatus();if(!n.tokens){console.log(`${p.yellow}Not authenticated.${p.reset}`),console.log(`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quantiya/codevibe-claude-plugin",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.29",
|
|
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.
|
|
50
|
+
"@quantiya/codevibe-core": "^1.0.17",
|
|
51
51
|
"dotenv": "^16.6.1",
|
|
52
52
|
"express": "^5.1.0",
|
|
53
53
|
"graphql": "^16.12.0",
|