@quantiya/codevibe-claude-plugin 1.0.17 → 1.0.18
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/dist/server.js +4 -4
- package/node_modules/@quantiya/codevibe-core/dist/appsync/appsync-client.d.ts +28 -1
- package/node_modules/@quantiya/codevibe-core/dist/appsync/queries.d.ts +2 -0
- package/node_modules/@quantiya/codevibe-core/dist/index.d.ts +1 -1
- package/node_modules/@quantiya/codevibe-core/dist/index.js +47 -31
- package/node_modules/@quantiya/codevibe-core/dist/keychain/keychain-manager.d.ts +17 -0
- package/node_modules/@quantiya/codevibe-core/dist/session/device-key-watcher.d.ts +10 -0
- package/node_modules/@quantiya/codevibe-core/dist/session/index.d.ts +3 -0
- package/node_modules/@quantiya/codevibe-core/dist/session/register-device-key.d.ts +13 -0
- package/node_modules/@quantiya/codevibe-core/dist/session/session-rekey.d.ts +38 -0
- 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.18",
|
|
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/dist/server.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
"use strict";var G=Object.create;var P=Object.defineProperty;var Y=Object.getOwnPropertyDescriptor;var z=Object.getOwnPropertyNames;var W=Object.getPrototypeOf,J=Object.prototype.hasOwnProperty;var Q=(m,e)=>{for(var s in e)P(m,s,{get:e[s],enumerable:!0})},F=(m,e,s,t)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of z(e))!J.call(m,i)&&i!==s&&P(m,i,{get:()=>e[i],enumerable:!(t=Y(e,i))||t.enumerable});return m};var y=(m,e,s)=>(s=m!=null?G(W(m)):{},F(e||!m||!m.__esModule?P(s,"default",{value:m,enumerable:!0}):s,m)),Z=m=>F(P({},"__esModule",{value:!0}),m);var te={};Q(te,{parseInteractivePromptInput:()=>B});module.exports=Z(te);var v=y(require("fs")),E=y(require("path")),T=y(require("os"));var O=y(require("os")),D=y(require("path"))
|
|
2
|
-
`);for(let t=s.length-1;t>=0;t--){let i=s[t].trim();if(this.detectInteractivePrompt(i))return i}return null}};var L=require("child_process"),H=require("util");var j=(0,H.promisify)(L.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 j(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 j(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
|
|
1
|
+
"use strict";var G=Object.create;var P=Object.defineProperty;var Y=Object.getOwnPropertyDescriptor;var z=Object.getOwnPropertyNames;var W=Object.getPrototypeOf,J=Object.prototype.hasOwnProperty;var Q=(m,e)=>{for(var s in e)P(m,s,{get:e[s],enumerable:!0})},F=(m,e,s,t)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of z(e))!J.call(m,i)&&i!==s&&P(m,i,{get:()=>e[i],enumerable:!(t=Y(e,i))||t.enumerable});return m};var y=(m,e,s)=>(s=m!=null?G(W(m)):{},F(e||!m||!m.__esModule?P(s,"default",{value:m,enumerable:!0}):s,m)),Z=m=>F(P({},"__esModule",{value:!0}),m);var te={};Q(te,{parseInteractivePromptInput:()=>B});module.exports=Z(te);var v=y(require("fs")),E=y(require("path")),T=y(require("os"));var O=y(require("os")),D=y(require("path")),$=require("@quantiya/codevibe-core"),n=(0,$.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")),N=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,N.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(){return new Promise((e,s)=>{this.removePortFile(),this.server?this.server.close(t=>{t?(n.error("Error stopping HTTP server:",t),s(t)):(n.info("HTTP API stopped"),e())}):e()})}};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="",u=!1,h=setTimeout(()=>{u=!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&&!u,output:c,error:d,exitCode:g||void 0,timedOut:u};f.success?n.info("Command executed successfully",{sessionId:e,exitCode:g,outputLength:c.length}):n.error("Command execution failed",{sessionId:e,exitCode:g,timedOut:u,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 L=require("child_process"),H=require("util");var j=(0,H.promisify)(L.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 j(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 j(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 _=class m{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>m.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)}}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());n.info(`Marking ${e.length} active session(s) as INACTIVE...`);for(let s of e)try{await this.appSyncClient.updateSession({sessionId:s,status:a.SessionStatus.INACTIVE}),n.info("Session marked as INACTIVE during shutdown",{sessionId:s});let t=this.activeSessions.get(s);t&&this.removePortFile(t.claudeSessionId)}catch(t){n.warn("Failed to mark session as INACTIVE during shutdown",{sessionId:s,error:t})}this.appSyncClient.cleanupSubscriptions(),this.activeSessions.clear(),await this.httpApi.stop(),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 u=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(u){n.warn("Failed to mark previous session as INACTIVE",{prevId:c,error:u})}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(`
|
|
3
3
|
\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.
|
|
4
|
-
`)}}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})}
|
|
4
|
+
`)}}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 u=o.pendingSubmitMap?.[d.option]||d.option;n.info("User selected option",{option:d.option,terminalInput:u}),await this.promptResponder.answerInteractivePrompt(e,u)?(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 u=o.pendingSubmitMap?.[d.option]||d.option;n.info("User selected option with follow-up",{option:d.option,terminalInput:u,followUpText:d.followUpText});let h=await this.promptResponder.answerInteractivePrompt(e,u);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(u=>setTimeout(u,500));let i=process.env.CODEVIBE_TMUX_SESSION,r={...s.metadata||{}};if(i)try{let{exec:u}=await import("child_process"),h=x=>new Promise((V,q)=>{u(x,{timeout:5e3},(k,X)=>{k?q(k):V({stdout:X||""})})}),{stdout:g}=await h(`tmux capture-pane -p -e -S -30 -t '${i}'`),f=g.split(`
|
|
5
5
|
`);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(u){n.warn("Failed to capture tmux pane for options",{error:u}),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(`
|
|
6
6
|
`+"=".repeat(60)),console.log("\u26A0\uFE0F SUBSCRIPTION LIMIT REACHED"),console.log("=".repeat(60)),s){case"session":console.log(`You have reached the maximum number of active sessions${i}.`),console.log(`${o}`),console.log(`
|
|
7
7
|
To continue, please:`),console.log(" \u2022 Close an existing Claude Code session, or"),console.log(" \u2022 Upgrade your subscription in the CodeVibe iOS app");break;case"message":console.log(`You have reached your monthly message limit${i}.`),console.log(`${o}`),console.log(`
|
|
@@ -13,4 +13,4 @@ Note: You can still use Claude Code normally from your desktop.`),console.log("T
|
|
|
13
13
|
|
|
14
14
|
${t}`:t=`${o}
|
|
15
15
|
|
|
16
|
-
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 ee(){let m=process.argv[2]||process.env.CLAUDE_SESSION_ID;m?n.info(`Starting MCP server for session: ${m}`):n.info("Starting MCP server without initial session ID (will be set on SessionStart)");let e=new
|
|
16
|
+
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 ee(){let m=process.argv[2]||process.env.CLAUDE_SESSION_ID;m?n.info(`Starting MCP server for session: ${m}`):n.info("Starting MCP server without initial session ID (will be set on SessionStart)");let e=new _(m);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(m,e=3){let s=m.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"}}ee().catch(m=>{n.error("Unhandled error in main:",m),process.exit(1)});0&&(module.exports={parseInteractivePromptInput});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CreateEventInput, CreateSessionInput, UpdateSessionInput, UpdateEventStatusInput, Event, Session, EventSource, DeviceKey } from '../types';
|
|
1
|
+
import { CreateEventInput, CreateSessionInput, UpdateSessionInput, UpdateEventStatusInput, Event, Session, EventSource, DeviceKey, GrantSessionKeyInput } from '../types';
|
|
2
2
|
/**
|
|
3
3
|
* Download URL response
|
|
4
4
|
*/
|
|
@@ -15,6 +15,7 @@ export declare class AppSyncClient {
|
|
|
15
15
|
private currentEmail;
|
|
16
16
|
private tokens;
|
|
17
17
|
private activeSubscriptions;
|
|
18
|
+
private deviceKeyWatcher;
|
|
18
19
|
private environment;
|
|
19
20
|
constructor();
|
|
20
21
|
/**
|
|
@@ -77,6 +78,18 @@ export declare class AppSyncClient {
|
|
|
77
78
|
* Register device key
|
|
78
79
|
*/
|
|
79
80
|
registerDeviceKey(deviceId: string, publicKey: string, platform: string, deviceName: string): Promise<void>;
|
|
81
|
+
/**
|
|
82
|
+
* Grant a session key to an additional device by appending a new
|
|
83
|
+
* per-device wrapped key blob to the session's encryptedKeys array.
|
|
84
|
+
*
|
|
85
|
+
* This is the client-side entry point for the session re-key flow:
|
|
86
|
+
* when a new device (typically a mobile app) registers for a user who
|
|
87
|
+
* already has active sessions, an already-enrolled device (typically
|
|
88
|
+
* the desktop plugin) wraps the session key for the new device's
|
|
89
|
+
* public key and calls this mutation to append it. The backend VTL
|
|
90
|
+
* resolver enforces that the caller owns the session.
|
|
91
|
+
*/
|
|
92
|
+
grantSessionKey(input: GrantSessionKeyInput): Promise<void>;
|
|
80
93
|
/**
|
|
81
94
|
* Get attachment download URL
|
|
82
95
|
*/
|
|
@@ -111,6 +124,20 @@ export declare class AppSyncClient {
|
|
|
111
124
|
* Cleanup subscription state
|
|
112
125
|
*/
|
|
113
126
|
private cleanupSubscriptionState;
|
|
127
|
+
/**
|
|
128
|
+
* Subscribe to device key registrations for the caller's own userId.
|
|
129
|
+
* Returns a cleanup function that stops the subscription.
|
|
130
|
+
*/
|
|
131
|
+
subscribeToDeviceKeyRegistered(userId: string, onNewDevice: (device: DeviceKey) => void, onReconnect?: () => void, onError?: (error: Error) => void): () => void;
|
|
132
|
+
/**
|
|
133
|
+
* Public method to stop the device key watcher. Used at plugin shutdown.
|
|
134
|
+
*/
|
|
135
|
+
stopDeviceKeyWatcher(): void;
|
|
136
|
+
private stopDeviceKeyWatcherInternal;
|
|
137
|
+
private createDeviceKeyWatcherConnection;
|
|
138
|
+
private sendDeviceKeyWatcherStart;
|
|
139
|
+
private resetDeviceKeyWatcherKeepAlive;
|
|
140
|
+
private handleDeviceKeyWatcherError;
|
|
114
141
|
private heartbeatTimers;
|
|
115
142
|
/**
|
|
116
143
|
* Start periodic heartbeat for a session.
|
|
@@ -9,8 +9,10 @@ export declare const mutations: {
|
|
|
9
9
|
createEvent: string;
|
|
10
10
|
updateEventStatus: string;
|
|
11
11
|
registerDeviceKey: string;
|
|
12
|
+
grantSessionKey: string;
|
|
12
13
|
getAttachmentDownloadUrl: string;
|
|
13
14
|
};
|
|
14
15
|
export declare const subscriptions: {
|
|
15
16
|
onEventCreated: string;
|
|
17
|
+
onDeviceKeyRegistered: string;
|
|
16
18
|
};
|
|
@@ -9,6 +9,6 @@ export type { Config, Environment } from './config';
|
|
|
9
9
|
export { Logger, logger, createLogger } from './logger';
|
|
10
10
|
export { parseInteractivePrompt, normalizeSnapshot, } from './prompt-parser';
|
|
11
11
|
export type { ParsedInteractivePrompt, PromptKind, InteractivePromptOption, } from './prompt-parser';
|
|
12
|
-
export { resumeOrCreateSession, prepareSessionEncryption } from './session';
|
|
12
|
+
export { resumeOrCreateSession, prepareSessionEncryption, rekeySessionForNewDevices, startDeviceKeyWatcher, registerDeviceEncryptionKey, } from './session';
|
|
13
13
|
export type { ResumeOrCreateSessionInput, ResumeOrCreateSessionResult } from './session';
|
|
14
14
|
export * from './types';
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
"use strict";var
|
|
2
|
-
${
|
|
3
|
-
`)}catch{}if(this.enableConsole)switch(e){case"error":console.error(
|
|
4
|
-
`)}catch{}}function
|
|
5
|
-
`))}function dt(r){return r.replace(/[^a-zA-Z0-9._-]/g,"_")}function Ke(r){return ae.join(se,`${dt(r)}.json`)}function ce(r){try{let e=A.readFileSync(Ke(r),"utf-8"),t=JSON.parse(e);return t&&typeof t=="object"?t:{}}catch{return{}}}function Oe(r,e){let t=Ke(r);A.writeFileSync(t,JSON.stringify(e,null,2),{mode:384});try{A.chmodSync(t,384)}catch{}}function de(){if(ct(),P===null)throw oe??new X("Keychain backend not initialized")}async function le(r,e){return de(),P==="keytar"&&I?I.getPassword(r,e):ce(r)[e]??null}async function pe(r,e,t){if(de(),P==="keytar"&&I){await I.setPassword(r,e,t);return}let n=ce(r);n[e]=t,Oe(r,n)}async function ue(r,e){if(de(),P==="keytar"&&I)return I.deletePassword(r,e);let t=ce(r);return e in t?(delete t[e],Oe(r,t),!0):!1}var Pe,ae,A,X,P,I,se,oe,Ne=E(()=>{"use strict";Pe=y(require("os")),ae=y(require("path")),A=y(require("fs"));L();X=class extends Error{constructor(e){super(e),this.name="KeychainBackendUnavailableError"}},P=null,I=null,se="",oe=null});var v,K,ge,pt,U,b,Re=E(()=>{"use strict";v=y(require("crypto")),K=class extends Error{constructor(e){super(e),this.name="CryptoError"}},ge=1,pt="CodeVibe E2E v1",U=class r{constructor(){}static getInstance(){return r.instance||(r.instance=new r),r.instance}generateKeyPair(){let e=v.createECDH("prime256v1");e.generateKeys();let n=e.getPublicKey().subarray(1).toString("base64");return{privateKey:e.getPrivateKey().toString("base64"),publicKey:n}}generateSessionKey(){return v.randomBytes(32).toString("base64")}deriveSharedKey(e,t){try{let n=v.createECDH("prime256v1"),i=Buffer.from(e,"base64");n.setPrivateKey(i);let s=Buffer.concat([Buffer.from([4]),Buffer.from(t,"base64")]),o=n.computeSecret(s),a=v.hkdfSync("sha256",o,Buffer.alloc(0),Buffer.from(pt,"utf8"),32);return Buffer.from(a)}catch(n){throw new K(`Failed to derive shared key: ${n}`)}}encryptSessionKey(e,t){let n=this.generateKeyPair(),i=this.deriveSharedKey(n.privateKey,t),s=Buffer.from(e,"base64");return{encryptedKey:this.encrypt(s,i).toString("base64"),ephemeralPublicKey:n.publicKey}}decryptSessionKey(e,t){let n=this.deriveSharedKey(t,e.ephemeralPublicKey),i=Buffer.from(e.encryptedKey,"base64");return this.decrypt(i,n).toString("base64")}encryptContent(e,t){let n=Buffer.from(t,"base64"),i=Buffer.from(e,"utf8");return this.encrypt(i,n).toString("base64")}decryptContent(e,t){let n=Buffer.from(t,"base64"),i=Buffer.from(e,"base64");return this.decrypt(i,n).toString("utf8")}encryptMetadata(e,t){let n=JSON.stringify(e);return this.encryptContent(n,t)}decryptMetadata(e,t){let n=this.decryptContent(e,t);return JSON.parse(n)}encryptData(e,t){let n=Buffer.from(t,"base64");return this.encrypt(e,n)}decryptData(e,t){let n=Buffer.from(t,"base64");return this.decrypt(e,n)}encrypt(e,t){let n=v.randomBytes(12),i=v.createCipheriv("aes-256-gcm",t,n),s=Buffer.concat([i.update(e),i.final()]),o=i.getAuthTag();return Buffer.concat([n,s,o])}decrypt(e,t){let n=e.subarray(0,12),i=e.subarray(e.length-16),s=e.subarray(12,e.length-16),o=v.createDecipheriv("aes-256-gcm",t,n);o.setAuthTag(i);try{return Buffer.concat([o.update(s),o.final()])}catch{throw new K("Decryption failed: Invalid ciphertext or authentication tag")}}serializePrivateKey(e){return e}deserializePrivateKey(e){return e}},b=U.getInstance()});var Q=E(()=>{"use strict";Re()});function h(){let r=process.env.ENVIRONMENT;return r==="development"||r==="production"?r:"production"}function ee(r){let e=r||h();return Z={...O[e],aws:{...O[e].aws,region:process.env.AWS_REGION||O[e].aws.region,appsyncUrl:process.env.APPSYNC_URL||O[e].aws.appsyncUrl,cognitoUserPoolId:process.env.COGNITO_USER_POOL_ID||O[e].aws.cognitoUserPoolId,cognitoClientId:process.env.COGNITO_CLIENT_ID||O[e].aws.cognitoClientId,cognitoDomain:process.env.COGNITO_DOMAIN||O[e].aws.cognitoDomain}},$e=!0,Z}function f(){return(!$e||!Z)&&ee(),Z}var V,W,O,Z,$e,Le=E(()=>{"use strict";V=y(require("os")),W=y(require("path")),O={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:W.default.join(V.default.homedir(),".codex","sessions"),approvalTimeoutMs:5e3},gemini:{command:"gemini",defaultTimeout:6e4,transcriptDir:W.default.join(V.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:W.default.join(V.default.homedir(),".codex","sessions"),approvalTimeoutMs:5e3},gemini:{command:"gemini",defaultTimeout:6e4,transcriptDir:W.default.join(V.default.homedir(),".gemini","tmp")}}},Z=null,$e=!1});var _=E(()=>{"use strict";Le()});var te,Ue,T,me,ut,N,g,_e=E(()=>{"use strict";te=y(require("os")),Ue=require("uuid");Ne();Q();_();L();T=class extends Error{constructor(e){super(e),this.name="KeychainError"}},me="device-identity",ut="tokens-",N=class r{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 r.instance||(r.instance=new r),r.instance}async getDeviceIdentity(){if(this.deviceIdentity)return this.deviceIdentity;let e=await le(this.serviceName,me);return e?(this.deviceIdentity=JSON.parse(e),c.info(`[KeychainManager] Loaded device identity: ${this.deviceIdentity.deviceId}`),this.deviceIdentity):null}async setDeviceIdentity(e){try{await pe(this.serviceName,me,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 T(`Failed to save device identity: ${t}`)}}async getOrCreateDeviceIdentity(){let e=await this.getDeviceIdentity();if(e)return e;let t=b.generateKeyPair();return e={deviceId:(0,Ue.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 ue(this.serviceName,me),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 T(`Failed to delete device identity: ${e}`)}}getTokenAccount(e){return`${ut}${e}`}async getTokens(e="production"){let t=await le(this.serviceName,this.getTokenAccount(e));if(!t)return null;let n=JSON.parse(t);return c.debug(`[KeychainManager] Loaded tokens for ${e}`),n}async setTokens(e,t="production"){try{await pe(this.serviceName,this.getTokenAccount(t),JSON.stringify(e)),c.info(`[KeychainManager] Saved tokens for ${t}`,{userId:e.userId,email:e.email})}catch(n){throw c.error(`[KeychainManager] Failed to save tokens: ${n}`),new T(`Failed to save tokens: ${n}`)}}async deleteTokens(e="production"){try{let t=await ue(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 n=this.sessionKeyCache.get(e);if(n)return n;if(!t||t.length===0)return null;let i=await this.getDeviceId(),s=t.find(l=>l.deviceId===i);if(!s)return c.warn(`[KeychainManager] Device ${i} not found in encryptedKeys`),null;let o=await this.getDevicePrivateKey(),a=b.decryptSessionKey(s,o);return this.sessionKeyCache.set(e,a),c.info(`[KeychainManager] Decrypted and cached session key for ${e}`),a}createSessionKey(e){let t=b.generateSessionKey(),n=e.map(i=>{let s=b.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:n}}cacheSessionKey(e,t){this.sessionKeyCache.set(e,t)}clearSessionKey(e){this.sessionKeyCache.delete(e)}clearAllSessionKeys(){this.sessionKeyCache.clear()}getIsRegistered(){return this.isRegistered}setIsRegistered(e){this.isRegistered=e}getDeviceName(){return te.hostname()||"CLI Client"}getDevicePlatform(){let e=te.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=N.getInstance()});var Me={};Te(Me,{KeychainError:()=>T,KeychainManager:()=>N,keychainManager:()=>g});var M=E(()=>{"use strict";_e()});var Ct={};Te(Ct,{AgentType:()=>Ve,AppSyncClient:()=>J,AuthService:()=>q,CryptoError:()=>K,CryptoService:()=>U,DeliveryStatus:()=>He,ENCRYPTION_VERSION:()=>ge,EventSource:()=>ye,EventType:()=>qe,KeychainError:()=>T,KeychainManager:()=>N,Logger:()=>D,SessionStatus:()=>fe,authService:()=>x,createLogger:()=>ie,cryptoService:()=>b,getConfig:()=>f,getEnvironment:()=>h,keychainManager:()=>g,loadConfig:()=>ee,logger:()=>c,mutations:()=>k,normalizeSnapshot:()=>Ee,parseInteractivePrompt:()=>Ye,prepareSessionEncryption:()=>re,queries:()=>R,resumeOrCreateSession:()=>Ie,runAuthCli:()=>ne,subscriptions:()=>j});module.exports=it(Ct);M();Q();var he=y(require("ws")),ve=require("uuid");_();L();M();var Be=y(require("dns")),Fe=y(require("fs"));if(gt())try{Be.setDefaultResultOrder("ipv4first")}catch{}function gt(){if(process.platform!=="linux")return!1;try{let r=Fe.readFileSync("/proc/sys/kernel/osrelease","utf8");return/microsoft|wsl/i.test(r)}catch{return!1}}async function B(r,e,t){try{return await fetch(r,e)}catch(n){let i=n?.cause?.code,s=n?.cause?.message,o=i||s||n?.message||"unknown",a=mt(i),l=t?`${t}: `:"",p=`Node ${process.version} on ${process.platform}`,m=[`${l}Cannot reach ${r}`,` Underlying error: ${o}`];a&&m.push(` Suggested fix: ${a}`),m.push(` Platform: ${p}`);let u=new Error(m.join(`
|
|
6
|
-
`));throw u.cause=
|
|
1
|
+
"use strict";var rt=Object.create;var Z=Object.defineProperty;var nt=Object.getOwnPropertyDescriptor;var it=Object.getOwnPropertyNames;var st=Object.getPrototypeOf,ot=Object.prototype.hasOwnProperty;var T=(n,e)=>()=>(n&&(e=n(n=0)),e);var Ke=(n,e)=>{for(var t in e)Z(n,t,{get:e[t],enumerable:!0})},xe=(n,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of it(e))!ot.call(n,s)&&s!==t&&Z(n,s,{get:()=>e[s],enumerable:!(r=nt(e,s))||r.enumerable});return n};var f=(n,e,t)=>(t=n!=null?rt(st(n)):{},xe(e||!n||!n.__esModule?Z(t,"default",{value:n,enumerable:!0}):t,n)),at=n=>xe(Z({},"__esModule",{value:!0}),n);function ct(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 ae(n){return new P(n)}var M,ee,Pe,Ce,P,a,Re=T(()=>{"use strict";M=f(require("fs")),ee=f(require("path")),Pe=f(require("os")),Ce={debug:0,info:1,warn:2,error:3};P=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=ee.dirname(this.logFile);M.existsSync(e)||M.mkdirSync(e,{recursive:!0})}}shouldLog(e){return Ce[e]>=Ce[this.level]}formatMessage(e,t,r){let s=new Date().toISOString(),i=e.toUpperCase().padEnd(5),o=`[${s}] [${i}] [${this.name}] ${t}`;return r!==void 0&&(r instanceof Error?(o+=` ${r.name}: ${r.message}`,r.stack&&(o+=`
|
|
2
|
+
${r.stack}`)):typeof r=="object"?o+=` ${JSON.stringify(r,ct)}`:o+=` ${r}`),o}log(e,t,r){if(!this.shouldLog(e))return;let s=this.formatMessage(e,t,r);if(this.logFile)try{M.appendFileSync(this.logFile,s+`
|
|
3
|
+
`)}catch{}if(this.enableConsole)switch(e){case"error":console.error(s);break;case"warn":console.warn(s);break;default:console.log(s)}}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 P({name:"codevibe-core",logFile:ee.join(Pe.tmpdir(),"codevibe-core.log"),level:"info"})});var R=T(()=>{"use strict";Re()});function dt(n){for(let e of n)try{process.stderr.write(e+`
|
|
4
|
+
`)}catch{}}function lt(){ce=le.join(Ne.homedir(),".codevibe");try{x.mkdirSync(ce,{recursive:!0,mode:448})}catch{}N="file"}function pt(){if(N!==null||de!==null)return;let optedIn=process.env.CODEVIBE_ALLOW_FILE_KEYCHAIN==="1";if(optedIn){dt(["","\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)"),lt();return}let keytarLoadError=null;try{let nodeRequire=eval("require");A=nodeRequire("keytar")}catch(n){keytarLoadError=n instanceof Error?n.message:String(n),A=null}if(A){N="keytar",a.info("[keychain-backend] Using keytar (OS-native keyring)");return}de=new te(["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 ut(n){return n.replace(/[^a-zA-Z0-9._-]/g,"_")}function Oe(n){return le.join(ce,`${ut(n)}.json`)}function pe(n){try{let e=x.readFileSync(Oe(n),"utf-8"),t=JSON.parse(e);return t&&typeof t=="object"?t:{}}catch{return{}}}function $e(n,e){let t=Oe(n);x.writeFileSync(t,JSON.stringify(e,null,2),{mode:384});try{x.chmodSync(t,384)}catch{}}function ue(){if(pt(),N===null)throw de??new te("Keychain backend not initialized")}async function ye(n,e){return ue(),N==="keytar"&&A?A.getPassword(n,e):pe(n)[e]??null}async function ge(n,e,t){if(ue(),N==="keytar"&&A){await A.setPassword(n,e,t);return}let r=pe(n);r[e]=t,$e(n,r)}async function me(n,e){if(ue(),N==="keytar"&&A)return A.deletePassword(n,e);let t=pe(n);return e in t?(delete t[e],$e(n,t),!0):!1}var Ne,le,x,te,N,A,ce,de,Le=T(()=>{"use strict";Ne=f(require("os")),le=f(require("path")),x=f(require("fs"));R();te=class extends Error{constructor(e){super(e),this.name="KeychainBackendUnavailableError"}},N=null,A=null,ce="",de=null});var b,O,fe,gt,W,w,Ue=T(()=>{"use strict";b=f(require("crypto")),O=class extends Error{constructor(e){super(e),this.name="CryptoError"}},fe=1,gt="CodeVibe E2E v1",W=class n{constructor(){}static getInstance(){return n.instance||(n.instance=new n),n.instance}generateKeyPair(){let e=b.createECDH("prime256v1");e.generateKeys();let r=e.getPublicKey().subarray(1).toString("base64");return{privateKey:e.getPrivateKey().toString("base64"),publicKey:r}}generateSessionKey(){return b.randomBytes(32).toString("base64")}deriveSharedKey(e,t){try{let r=b.createECDH("prime256v1"),s=Buffer.from(e,"base64");r.setPrivateKey(s);let i=Buffer.concat([Buffer.from([4]),Buffer.from(t,"base64")]),o=r.computeSecret(i),c=b.hkdfSync("sha256",o,Buffer.alloc(0),Buffer.from(gt,"utf8"),32);return Buffer.from(c)}catch(r){throw new O(`Failed to derive shared key: ${r}`)}}encryptSessionKey(e,t){let r=this.generateKeyPair(),s=this.deriveSharedKey(r.privateKey,t),i=Buffer.from(e,"base64");return{encryptedKey:this.encrypt(i,s).toString("base64"),ephemeralPublicKey:r.publicKey}}decryptSessionKey(e,t){let r=this.deriveSharedKey(t,e.ephemeralPublicKey),s=Buffer.from(e.encryptedKey,"base64");return this.decrypt(s,r).toString("base64")}encryptContent(e,t){let r=Buffer.from(t,"base64"),s=Buffer.from(e,"utf8");return this.encrypt(s,r).toString("base64")}decryptContent(e,t){let r=Buffer.from(t,"base64"),s=Buffer.from(e,"base64");return this.decrypt(s,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=b.randomBytes(12),s=b.createCipheriv("aes-256-gcm",t,r),i=Buffer.concat([s.update(e),s.final()]),o=s.getAuthTag();return Buffer.concat([r,i,o])}decrypt(e,t){let r=e.subarray(0,12),s=e.subarray(e.length-16),i=e.subarray(12,e.length-16),o=b.createDecipheriv("aes-256-gcm",t,r);o.setAuthTag(s);try{return Buffer.concat([o.update(i),o.final()])}catch{throw new O("Decryption failed: Invalid ciphertext or authentication tag")}}serializePrivateKey(e){return e}deserializePrivateKey(e){return e}},w=W.getInstance()});var J=T(()=>{"use strict";Ue()});function k(){let n=process.env.ENVIRONMENT;return n==="development"||n==="production"?n:"production"}function ne(n){let e=n||k();return re={...$[e],aws:{...$[e].aws,region:process.env.AWS_REGION||$[e].aws.region,appsyncUrl:process.env.APPSYNC_URL||$[e].aws.appsyncUrl,cognitoUserPoolId:process.env.COGNITO_USER_POOL_ID||$[e].aws.cognitoUserPoolId,cognitoClientId:process.env.COGNITO_CLIENT_ID||$[e].aws.cognitoClientId,cognitoDomain:process.env.COGNITO_DOMAIN||$[e].aws.cognitoDomain}},_e=!0,re}function h(){return(!_e||!re)&&ne(),re}var j,z,$,re,_e,Me=T(()=>{"use strict";j=f(require("os")),z=f(require("path")),$={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:z.default.join(j.default.homedir(),".codex","sessions"),approvalTimeoutMs:5e3},gemini:{command:"gemini",defaultTimeout:6e4,transcriptDir:z.default.join(j.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:z.default.join(j.default.homedir(),".codex","sessions"),approvalTimeoutMs:5e3},gemini:{command:"gemini",defaultTimeout:6e4,transcriptDir:z.default.join(j.default.homedir(),".gemini","tmp")}}},re=null,_e=!1});var B=T(()=>{"use strict";Me()});var ie,We,D,he,mt,L,y,Be=T(()=>{"use strict";ie=f(require("os")),We=require("uuid");Le();J();B();R();D=class extends Error{constructor(e){super(e),this.name="KeychainError"}},he="device-identity",mt="tokens-",L=class n{constructor(){this.deviceIdentity=null;this.sessionKeyCache=new Map;this.isRegistered=!1;this._serviceName=null}get serviceName(){return this._serviceName||(this._serviceName=h().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 ye(this.serviceName,he);return e?(this.deviceIdentity=JSON.parse(e),a.info(`[KeychainManager] Loaded device identity: ${this.deviceIdentity.deviceId}`),this.deviceIdentity):null}async setDeviceIdentity(e){try{await ge(this.serviceName,he,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 D(`Failed to save device identity: ${t}`)}}async getOrCreateDeviceIdentity(){let e=await this.getDeviceIdentity();if(e)return e;let t=w.generateKeyPair();return e={deviceId:(0,We.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 me(this.serviceName,he),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 D(`Failed to delete device identity: ${e}`)}}getTokenAccount(e){return`${mt}${e}`}async getTokens(e="production"){let t=await ye(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 ge(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 D(`Failed to save tokens: ${r}`)}}async deleteTokens(e="production"){try{let t=await me(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 s=await this.getDeviceId(),i=t.find(l=>l.deviceId===s);if(!i)return a.warn(`[KeychainManager] Device ${s} not found in encryptedKeys`),null;let o=await this.getDevicePrivateKey(),c=w.decryptSessionKey(i,o);return this.sessionKeyCache.set(e,c),a.info(`[KeychainManager] Decrypted and cached session key for ${e}`),c}createSessionKey(e){let t=w.generateSessionKey(),r=e.map(s=>{let i=w.encryptSessionKey(t,s.publicKey);return{deviceId:s.deviceId,encryptedKey:i.encryptedKey,ephemeralPublicKey:i.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 ie.hostname()||"CLI Client"}getDevicePlatform(){let e=ie.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")}},y=L.getInstance()});var Fe={};Ke(Fe,{KeychainError:()=>D,KeychainManager:()=>L,keychainManager:()=>y});var C=T(()=>{"use strict";Be()});var Pt={};Ke(Pt,{AgentType:()=>je,AppSyncClient:()=>X,AuthService:()=>H,CryptoError:()=>O,CryptoService:()=>W,DeliveryStatus:()=>Je,ENCRYPTION_VERSION:()=>fe,EventSource:()=>ve,EventType:()=>Ve,KeychainError:()=>D,KeychainManager:()=>L,Logger:()=>P,SessionStatus:()=>Se,authService:()=>K,createLogger:()=>ae,cryptoService:()=>w,getConfig:()=>h,getEnvironment:()=>k,keychainManager:()=>y,loadConfig:()=>ne,logger:()=>a,mutations:()=>I,normalizeSnapshot:()=>Ee,parseInteractivePrompt:()=>Qe,prepareSessionEncryption:()=>oe,queries:()=>U,registerDeviceEncryptionKey:()=>De,rekeySessionForNewDevices:()=>_,resumeOrCreateSession:()=>Te,runAuthCli:()=>se,startDeviceKeyWatcher:()=>Ae,subscriptions:()=>q});module.exports=at(Pt);C();J();var G=f(require("ws")),Y=require("uuid");B();R();C();var qe=f(require("dns")),He=f(require("fs"));if(ft())try{qe.setDefaultResultOrder("ipv4first")}catch{}function ft(){if(process.platform!=="linux")return!1;try{let n=He.readFileSync("/proc/sys/kernel/osrelease","utf8");return/microsoft|wsl/i.test(n)}catch{return!1}}async function F(n,e,t){try{return await fetch(n,e)}catch(r){let s=r?.cause?.code,i=r?.cause?.message,o=s||i||r?.message||"unknown",c=ht(s),l=t?`${t}: `:"",p=`Node ${process.version} on ${process.platform}`,g=[`${l}Cannot reach ${n}`,` Underlying error: ${o}`];c&&g.push(` Suggested fix: ${c}`),g.push(` Platform: ${p}`);let u=new Error(g.join(`
|
|
6
|
+
`));throw u.cause=r,u}}function ht(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 U={getSession:`
|
|
7
7
|
query GetSession($sessionId: ID!) {
|
|
8
8
|
getSession(sessionId: $sessionId) {
|
|
9
9
|
sessionId
|
|
@@ -66,7 +66,7 @@ ${n.stack}`)):typeof n=="object"?o+=` ${JSON.stringify(n,st)}`:o+=` ${n}`),o}log
|
|
|
66
66
|
lastUsedAt
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
|
-
`},
|
|
69
|
+
`},I={createSession:`
|
|
70
70
|
mutation CreateSession($input: CreateSessionInput!) {
|
|
71
71
|
createSession(input: $input) {
|
|
72
72
|
sessionId
|
|
@@ -132,6 +132,10 @@ ${n.stack}`)):typeof n=="object"?o+=` ${JSON.stringify(n,st)}`:o+=` ${n}`),o}log
|
|
|
132
132
|
lastUsedAt
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
|
+
`,grantSessionKey:`
|
|
136
|
+
mutation GrantSessionKey($input: GrantSessionKeyInput!) {
|
|
137
|
+
grantSessionKey(input: $input)
|
|
138
|
+
}
|
|
135
139
|
`,getAttachmentDownloadUrl:`
|
|
136
140
|
mutation GetAttachmentDownloadUrl($s3Key: String!) {
|
|
137
141
|
getAttachmentDownloadUrl(s3Key: $s3Key) {
|
|
@@ -139,7 +143,7 @@ ${n.stack}`)):typeof n=="object"?o+=` ${JSON.stringify(n,st)}`:o+=` ${n}`),o}log
|
|
|
139
143
|
expiresAt
|
|
140
144
|
}
|
|
141
145
|
}
|
|
142
|
-
`},
|
|
146
|
+
`},q={onEventCreated:`
|
|
143
147
|
subscription OnEventCreated($sessionId: ID!) {
|
|
144
148
|
onEventCreated(sessionId: $sessionId) {
|
|
145
149
|
eventId
|
|
@@ -166,7 +170,19 @@ ${n.stack}`)):typeof n=="object"?o+=` ${JSON.stringify(n,st)}`:o+=` ${n}`),o}log
|
|
|
166
170
|
isEncrypted
|
|
167
171
|
}
|
|
168
172
|
}
|
|
169
|
-
`};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||{}),ye=(t=>(t.DESKTOP="DESKTOP",t.MOBILE="MOBILE",t))(ye||{}),He=(n=>(n.SENT="SENT",n.DELIVERED="DELIVERED",n.EXECUTED="EXECUTED",n))(He||{});var fe=(n=>(n.ACTIVE="ACTIVE",n.INACTIVE="INACTIVE",n.PAUSED="PAUSED",n))(fe||{}),Ve=(n=>(n.CLAUDE="CLAUDE",n.GEMINI="GEMINI",n.CODEX="CODEX",n))(Ve||{});var F={urgentMaxAttempts:10,baseDelayMs:1e3,maxDelayMs:6e4,backoffMultiplier:2,persistentDelayMs:300*1e3},J=class{constructor(){this.authenticated=!1;this.currentUserId=null;this.currentEmail=null;this.tokens=null;this.activeSubscriptions=new Map;this.heartbeatTimers=new Map;this.environment=h(),c.info("[AppSyncClient] Initialized",{environment:this.environment})}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){try{let t=f(),n=`https://${t.aws.cognitoDomain}/oauth2/token`,i=new URLSearchParams({grant_type:"refresh_token",client_id:t.aws.cognitoClientId,refresh_token:e.refreshToken}),s=await B(n,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:i.toString()},"Token refresh");if(!s.ok)return c.error("[AppSyncClient] Token refresh failed",{status:s.status}),!1;let o=await s.json(),a={...e,accessToken:o.access_token,idToken:o.id_token,expiresAt:Date.now()+o.expires_in*1e3};return await g.setTokens(a,this.environment),this.tokens=a,c.info("[AppSyncClient] Tokens refreshed",{expiresAt:new Date(a.expiresAt).toISOString()}),!0}catch(t){return c.error("[AppSyncClient] Token refresh error:",t),!1}}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,n=!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 B(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&&!n&&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},n=await this.graphqlRequest(k.createSession,{input:t});return c.info("[AppSyncClient] Session created",{sessionId:n.data.createSession.sessionId}),n.data.createSession}async updateSession(e){let t={...e,metadata:e.metadata?JSON.stringify(e.metadata):void 0},n=await this.graphqlRequest(k.updateSession,{input:t});return c.debug("[AppSyncClient] Session updated",{sessionId:n.data.updateSession.sessionId}),n.data.updateSession}async getSession(e){return(await this.graphqlRequest(R.getSession,{sessionId:e})).data.getSession}async createEvent(e){let t={...e,metadata:e.metadata?JSON.stringify(e.metadata):void 0},n=await this.graphqlRequest(k.createEvent,{input:t});return c.debug("[AppSyncClient] Event created",{eventId:n.data.createEvent.eventId,type:n.data.createEvent.type}),n.data.createEvent}async updateEventStatus(e){return(await this.graphqlRequest(k.updateEventStatus,{input:e})).data.updateEventStatus}async listEvents(e,t,n){return(await this.graphqlRequest(R.listEvents,{sessionId:e,source:t,limit:n})).data.listEvents.items}async listUserDeviceKeys(){return(await this.graphqlRequest(R.listUserDeviceKeys,{})).data.listUserDeviceKeys||[]}async registerDeviceKey(e,t,n,i){let s={deviceId:e,publicKey:t,platform:n,deviceName:i};await this.graphqlRequest(k.registerDeviceKey,{input:s}),c.info("[AppSyncClient] Device key registered",{deviceId:e,platform:n})}async getAttachmentDownloadUrl(e){return(await this.graphqlRequest(k.getAttachmentDownloadUrl,{s3Key:e})).data.getAttachmentDownloadUrl}subscribeToEvents(e,t,n){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,ve.v4)(),sessionId:e,onEvent:t,onError:n,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"),n={host:new URL(e.aws.appsyncUrl).host};this.tokens?.idToken&&(n.Authorization=this.tokens.idToken);let i=Buffer.from(JSON.stringify(n)).toString("base64"),s=Buffer.from(JSON.stringify({})).toString("base64");return`${t}?header=${i}&payload=${s}`}createSubscription(e){let{sessionId:t,subscriptionId:n,onEvent:i,onError:s}=e;try{let o=this.buildRealtimeUrl(),a=new he.default(o,["graphql-ws"]);a.on("open",()=>{c.info("[AppSyncClient] WebSocket connected",{sessionId:t}),a.send(JSON.stringify({type:"connection_init"}))}),a.on("message",l=>{try{let p=JSON.parse(l.toString());switch(p.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 m=p.payload?.data?.onEventCreated;m&&m.source==="MOBILE"&&i(m);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){c.error("[AppSyncClient] Failed to parse message",{error:p})}}),a.on("error",l=>{c.error("[AppSyncClient] WebSocket error",{sessionId:t,error:l.message}),this.handleSubscriptionError(e,l)}),a.on("close",(l,p)=>{c.info("[AppSyncClient] WebSocket closed",{sessionId:t,code:l}),e.keepAliveTimer&&clearTimeout(e.keepAliveTimer),!e.destroyed&&this.activeSubscriptions.get(t)===e&&this.handleSubscriptionError(e,new Error(`WebSocket closed: ${l}`))}),e.ws=a,this.resetKeepAliveTimer(e)}catch(o){this.handleSubscriptionError(e,o)}}sendSubscriptionStart(e,t){let n=f(),{sessionId:i,subscriptionId:s}=t,o={host:new URL(n.aws.appsyncUrl).host};this.tokens?.idToken&&(o.Authorization=this.tokens.idToken),e.send(JSON.stringify({id:s,type:"start",payload:{data:JSON.stringify({query:j.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:n,onError:i}=e;if(e.isReconnecting||!this.activeSubscriptions.has(n))return;e.isReconnecting=!0,e.reconnectAttempts++,this.stopHeartbeat(n);let s=e.reconnectAttempts<=F.urgentMaxAttempts,o;if(s?o=Math.min(F.baseDelayMs*Math.pow(F.backoffMultiplier,e.reconnectAttempts-1),F.maxDelayMs):(o=F.persistentDelayMs,e.reconnectAttempts===F.urgentMaxAttempts+1&&c.info("[AppSyncClient] Switching to persistent reconnect (every 5min)",{sessionId:n})),c.info("[AppSyncClient] Scheduling reconnect",{sessionId:n,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(n)!==e){c.info("[AppSyncClient] Reconnect skipped \u2014 state is no longer canonical",{sessionId:n});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:n}):this.tokens=a)}catch{c.warn("[AppSyncClient] Token refresh failed before reconnect, using existing tokens",{sessionId:n})}if(e.destroyed||this.activeSubscriptions.get(n)!==e){c.info("[AppSyncClient] Reconnect skipped after token refresh \u2014 state no longer canonical",{sessionId:n});return}e.subscriptionId=(0,ve.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===he.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}}startHeartbeat(e,t=120*1e3){this.stopHeartbeat(e),this.sendHeartbeat(e);let n=setInterval(()=>{this.sendHeartbeat(e)},t);this.heartbeatTimers.set(e,n),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.heartbeatTimers.forEach(e=>clearInterval(e)),this.heartbeatTimers.clear()}};var We=y(require("crypto")),je=y(require("fs")),be=y(require("http")),Je=require("child_process");_();M();L();var z=8080,ze="/callback",Se=`http://localhost:${z}${ze}`,q=class r{constructor(){}static getInstance(){return r.instance||(r.instance=new r),r.instance}openBrowser(e){console.log(""),console.log("Opening your browser for sign-in..."),console.log("If your browser does not open automatically, visit this URL manually:"),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=je.readFileSync("/proc/sys/kernel/osrelease","utf8");return/microsoft|wsl/i.test(e)}catch{return!1}}tryBrowserCommand(e,t,n){if(n>=e.length){c.debug("[AuthService] No browser-opening command succeeded. User must open the sign-in URL manually (printed to stdout above).");return}let i=e[n],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,n+1))},l=u=>{o||(o=!0,c.debug(`[AuthService] Browser command '${i.cmd}' ${u}`))},p;try{p=(0,Je.spawn)(i.cmd,s,{detached:!0,stdio:"ignore"})}catch(u){a(`threw synchronously: ${u?.message||u}`);return}p.on("error",u=>{a(`failed to spawn: ${u?.message||u}`)}),p.on("exit",(u,C)=>{u===0?l("exited successfully"):a(C?`terminated by signal ${C}`:`exited with code ${u}`)}),setTimeout(()=>{l("still running after 3s, assuming success")},3e3).unref(),p.unref()}generateState(){return We.randomBytes(32).toString("hex")}buildAuthUrl(e){let t=f(),n=new URLSearchParams({client_id:t.aws.cognitoClientId,response_type:"code",scope:"email openid profile",redirect_uri:Se,state:e});return`https://${t.aws.cognitoDomain}/oauth2/authorize?${n.toString()}`}async exchangeCodeForTokens(e){let t=f(),n=`https://${t.aws.cognitoDomain}/oauth2/token`,i=new URLSearchParams({grant_type:"authorization_code",client_id:t.aws.cognitoClientId,code:e,redirect_uri:Se}),s=await B(n,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:i.toString()},"Token exchange");if(!s.ok){let a=await s.text();throw new Error(`Token exchange failed: ${s.status} ${a}`)}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(),n=`https://${t.aws.cognitoDomain}/oauth2/token`,i=new URLSearchParams({grant_type:"refresh_token",client_id:t.aws.cognitoClientId,refresh_token:e}),s=await B(n,{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(h());if(e&&!g.isTokenExpired(e))return e;let t=this.generateState(),n=this.buildAuthUrl(t);return new Promise((i,s)=>{let o=be.createServer(async(a,l)=>{if(!a.url?.startsWith(ze)){l.writeHead(404),l.end("Not found");return}try{let p=new URL(a.url,`http://localhost:${z}`),m=p.searchParams.get("code"),u=p.searchParams.get("state"),C=p.searchParams.get("error");if(C)throw new Error(`OAuth error: ${C}`);if(u!==t)throw new Error("State mismatch");if(!m)throw new Error("No authorization code");let S=await this.exchangeCodeForTokens(m),H=this.decodeJwt(S.idToken),w={accessToken:S.accessToken,idToken:S.idToken,refreshToken:S.refreshToken,expiresAt:Date.now()+S.expiresIn*1e3,userId:H.sub,email:H.email||"unknown"};await g.setTokens(w,h()),l.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),l.end(`
|
|
173
|
+
`,onDeviceKeyRegistered:`
|
|
174
|
+
subscription OnDeviceKeyRegistered($userId: ID!) {
|
|
175
|
+
onDeviceKeyRegistered(userId: $userId) {
|
|
176
|
+
userId
|
|
177
|
+
deviceId
|
|
178
|
+
publicKey
|
|
179
|
+
platform
|
|
180
|
+
deviceName
|
|
181
|
+
createdAt
|
|
182
|
+
lastUsedAt
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
`};var Ve=(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))(Ve||{}),ve=(t=>(t.DESKTOP="DESKTOP",t.MOBILE="MOBILE",t))(ve||{}),Je=(r=>(r.SENT="SENT",r.DELIVERED="DELIVERED",r.EXECUTED="EXECUTED",r))(Je||{});var Se=(r=>(r.ACTIVE="ACTIVE",r.INACTIVE="INACTIVE",r.PAUSED="PAUSED",r))(Se||{}),je=(r=>(r.CLAUDE="CLAUDE",r.GEMINI="GEMINI",r.CODEX="CODEX",r))(je||{});var E={urgentMaxAttempts:10,baseDelayMs:1e3,maxDelayMs:6e4,backoffMultiplier:2,persistentDelayMs:300*1e3},X=class{constructor(){this.authenticated=!1;this.currentUserId=null;this.currentEmail=null;this.tokens=null;this.activeSubscriptions=new Map;this.deviceKeyWatcher=null;this.heartbeatTimers=new Map;this.environment=k(),a.info("[AppSyncClient] Initialized",{environment:this.environment})}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 y.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:y.isTokenExpired(e)}),y.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){try{let t=h(),r=`https://${t.aws.cognitoDomain}/oauth2/token`,s=new URLSearchParams({grant_type:"refresh_token",client_id:t.aws.cognitoClientId,refresh_token:e.refreshToken}),i=await F(r,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:s.toString()},"Token refresh");if(!i.ok)return a.error("[AppSyncClient] Token refresh failed",{status:i.status}),!1;let o=await i.json(),c={...e,accessToken:o.access_token,idToken:o.id_token,expiresAt:Date.now()+o.expires_in*1e3};return await y.setTokens(c,this.environment),this.tokens=c,a.info("[AppSyncClient] Tokens refreshed",{expiresAt:new Date(c.expiresAt).toISOString()}),!0}catch(t){return a.error("[AppSyncClient] Token refresh error:",t),!1}}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 s=h();if(!this.tokens?.idToken)throw new Error('Not authenticated. Run "codevibe login" first.');let i={"Content-Type":"application/json",Authorization:this.tokens.idToken},o=await F(s.aws.appsyncUrl,{method:"POST",headers:i,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(I.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(I.updateSession,{input:t});return a.debug("[AppSyncClient] Session updated",{sessionId:r.data.updateSession.sessionId}),r.data.updateSession}async getSession(e){return(await this.graphqlRequest(U.getSession,{sessionId:e})).data.getSession}async createEvent(e){let t={...e,metadata:e.metadata?JSON.stringify(e.metadata):void 0},r=await this.graphqlRequest(I.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(I.updateEventStatus,{input:e})).data.updateEventStatus}async listEvents(e,t,r){return(await this.graphqlRequest(U.listEvents,{sessionId:e,source:t,limit:r})).data.listEvents.items}async listUserDeviceKeys(){return(await this.graphqlRequest(U.listUserDeviceKeys,{})).data.listUserDeviceKeys||[]}async registerDeviceKey(e,t,r,s){let i={deviceId:e,publicKey:t,platform:r,deviceName:s};await this.graphqlRequest(I.registerDeviceKey,{input:i}),a.info("[AppSyncClient] Device key registered",{deviceId:e,platform:r})}async grantSessionKey(e){await this.graphqlRequest(I.grantSessionKey,{input:e}),a.info("[AppSyncClient] Session key granted",{sessionId:e.sessionId,deviceId:e.deviceId})}async getAttachmentDownloadUrl(e){return(await this.graphqlRequest(I.getAttachmentDownloadUrl,{s3Key:e})).data.getAttachmentDownloadUrl}subscribeToEvents(e,t,r){a.info("[AppSyncClient] Subscribing to events",{sessionId:e});let s=this.activeSubscriptions.get(e);s&&(this.cleanupSubscriptionState(s),this.activeSubscriptions.delete(e));let i={ws:null,subscriptionId:(0,Y.v4)(),sessionId:e,onEvent:t,onError:r,reconnectAttempts:0,isReconnecting:!1,destroyed:!1};return this.activeSubscriptions.set(e,i),this.createSubscription(i),()=>{this.cleanupSubscriptionState(i),this.activeSubscriptions.delete(e)}}buildRealtimeUrl(){let e=h(),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 s=Buffer.from(JSON.stringify(r)).toString("base64"),i=Buffer.from(JSON.stringify({})).toString("base64");return`${t}?header=${s}&payload=${i}`}createSubscription(e){let{sessionId:t,subscriptionId:r,onEvent:s,onError:i}=e;try{let o=this.buildRealtimeUrl(),c=new G.default(o,["graphql-ws"]);c.on("open",()=>{a.info("[AppSyncClient] WebSocket connected",{sessionId:t}),c.send(JSON.stringify({type:"connection_init"}))}),c.on("message",l=>{try{let p=JSON.parse(l.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 g=p.payload?.data?.onEventCreated;g&&g.source==="MOBILE"&&s(g);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",l=>{a.error("[AppSyncClient] WebSocket error",{sessionId:t,error:l.message}),this.handleSubscriptionError(e,l)}),c.on("close",(l,p)=>{a.info("[AppSyncClient] WebSocket closed",{sessionId:t,code:l}),e.keepAliveTimer&&clearTimeout(e.keepAliveTimer),!e.destroyed&&this.activeSubscriptions.get(t)===e&&this.handleSubscriptionError(e,new Error(`WebSocket closed: ${l}`))}),e.ws=c,this.resetKeepAliveTimer(e)}catch(o){this.handleSubscriptionError(e,o)}}sendSubscriptionStart(e,t){let r=h(),{sessionId:s,subscriptionId:i}=t,o={host:new URL(r.aws.appsyncUrl).host};this.tokens?.idToken&&(o.Authorization=this.tokens.idToken),e.send(JSON.stringify({id:i,type:"start",payload:{data:JSON.stringify({query:q.onEventCreated,variables:{sessionId:s}}),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:s}=e;if(e.isReconnecting||!this.activeSubscriptions.has(r))return;e.isReconnecting=!0,e.reconnectAttempts++,this.stopHeartbeat(r);let i=e.reconnectAttempts<=E.urgentMaxAttempts,o;if(i?o=Math.min(E.baseDelayMs*Math.pow(E.backoffMultiplier,e.reconnectAttempts-1),E.maxDelayMs):(o=E.persistentDelayMs,e.reconnectAttempts===E.urgentMaxAttempts+1&&a.info("[AppSyncClient] Switching to persistent reconnect (every 5min)",{sessionId:r})),a.info("[AppSyncClient] Scheduling reconnect",{sessionId:r,attempt:e.reconnectAttempts,phase:i?"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 y.getTokens(this.environment);c&&(y.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,Y.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===G.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,s){a.info("[AppSyncClient] Subscribing to device key registrations",{userId:e}),this.deviceKeyWatcher&&this.stopDeviceKeyWatcherInternal();let i={userId:e,subscriptionId:(0,Y.v4)(),ws:null,onNewDevice:t,onReconnect:r,onError:s,reconnectAttempts:0,isReconnecting:!1,destroyed:!1};return this.deviceKeyWatcher=i,this.createDeviceKeyWatcherConnection(i),()=>{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===G.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 G.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",s=>{try{let i=JSON.parse(s.toString());switch(i.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=i.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 l=i.payload?.errors?.[0]?.message||"Unknown error";this.handleDeviceKeyWatcherError(e,new Error(l));break}}catch(i){a.error("[AppSyncClient] Failed to parse device key watcher message",{error:i})}}),r.on("error",s=>{a.error("[AppSyncClient] Device key watcher WebSocket error",{userId:e.userId,error:s.message}),this.handleDeviceKeyWatcherError(e,s)}),r.on("close",s=>{a.info("[AppSyncClient] Device key watcher WebSocket closed",{userId:e.userId,code:s}),e.keepAliveTimer&&clearTimeout(e.keepAliveTimer),!e.destroyed&&this.deviceKeyWatcher===e&&this.handleDeviceKeyWatcherError(e,new Error(`WebSocket closed: ${s}`))}),e.ws=r,this.resetDeviceKeyWatcherKeepAlive(e)}catch(t){this.handleDeviceKeyWatcherError(e,t)}}sendDeviceKeyWatcherStart(e,t){let r=h(),{userId:s,subscriptionId:i}=t,o={host:new URL(r.aws.appsyncUrl).host};this.tokens?.idToken&&(o.Authorization=this.tokens.idToken),e.send(JSON.stringify({id:i,type:"start",payload:{data:JSON.stringify({query:q.onDeviceKeyRegistered,variables:{userId:s}}),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 s=e.reconnectAttempts<=E.urgentMaxAttempts?Math.min(E.baseDelayMs*Math.pow(E.backoffMultiplier,e.reconnectAttempts-1),E.maxDelayMs):E.persistentDelayMs;a.warn("[AppSyncClient] Device key watcher reconnect scheduled",{userId:e.userId,attempts:e.reconnectAttempts,delayMs:s,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 i=await y.getTokens(this.environment);i&&(y.isTokenExpired(i)?await this.refreshTokens(i)&&a.info("[AppSyncClient] Tokens refreshed before device key watcher reconnect",{userId:e.userId}):this.tokens=i)}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,Y.v4)(),this.createDeviceKeyWatcherConnection(e))},s)}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 ze=f(require("crypto")),Ge=f(require("fs")),ke=f(require("http")),Ye=require("child_process");B();C();R();var Q=8080,Xe="/callback",we=`http://localhost:${Q}${Xe}`,H=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..."),console.log("If your browser does not open automatically, visit this URL manually:"),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=Ge.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).");return}let s=e[r],i=[...s.fixedArgs,t],o=!1,c=u=>{o||(o=!0,a.debug(`[AuthService] Browser command '${s.cmd}' ${u}; trying next fallback`),this.tryBrowserCommand(e,t,r+1))},l=u=>{o||(o=!0,a.debug(`[AuthService] Browser command '${s.cmd}' ${u}`))},p;try{p=(0,Ye.spawn)(s.cmd,i,{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,v)=>{u===0?l("exited successfully"):c(v?`terminated by signal ${v}`:`exited with code ${u}`)}),setTimeout(()=>{l("still running after 3s, assuming success")},3e3).unref(),p.unref()}generateState(){return ze.randomBytes(32).toString("hex")}buildAuthUrl(e){let t=h(),r=new URLSearchParams({client_id:t.aws.cognitoClientId,response_type:"code",scope:"email openid profile",redirect_uri:we,state:e});return`https://${t.aws.cognitoDomain}/oauth2/authorize?${r.toString()}`}async exchangeCodeForTokens(e){let t=h(),r=`https://${t.aws.cognitoDomain}/oauth2/token`,s=new URLSearchParams({grant_type:"authorization_code",client_id:t.aws.cognitoClientId,code:e,redirect_uri:we}),i=await F(r,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:s.toString()},"Token exchange");if(!i.ok){let c=await i.text();throw new Error(`Token exchange failed: ${i.status} ${c}`)}let o=await i.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=h(),r=`https://${t.aws.cognitoDomain}/oauth2/token`,s=new URLSearchParams({grant_type:"refresh_token",client_id:t.aws.cognitoClientId,refresh_token:e}),i=await F(r,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:s.toString()},"Token refresh");if(!i.ok)throw new Error(`Token refresh failed: ${i.status}`);let o=await i.json();return{accessToken:o.access_token,idToken:o.id_token,expiresIn:o.expires_in}}async login(){let e=await y.getTokens(k());if(e&&!y.isTokenExpired(e))return e;let t=this.generateState(),r=this.buildAuthUrl(t);return new Promise((s,i)=>{let o=ke.createServer(async(c,l)=>{if(!c.url?.startsWith(Xe)){l.writeHead(404),l.end("Not found");return}try{let p=new URL(c.url,`http://localhost:${Q}`),g=p.searchParams.get("code"),u=p.searchParams.get("state"),v=p.searchParams.get("error");if(v)throw new Error(`OAuth error: ${v}`);if(u!==t)throw new Error("State mismatch");if(!g)throw new Error("No authorization code");let S=await this.exchangeCodeForTokens(g),V=this.decodeJwt(S.idToken),m={accessToken:S.accessToken,idToken:S.idToken,refreshToken:S.refreshToken,expiresAt:Date.now()+S.expiresIn*1e3,userId:V.sub,email:V.email||"unknown"};await y.setTokens(m,k()),l.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),l.end(`
|
|
170
186
|
<!DOCTYPE html>
|
|
171
187
|
<html>
|
|
172
188
|
<head><title>Success</title></head>
|
|
@@ -175,17 +191,17 @@ ${n.stack}`)):typeof n=="object"?o+=` ${JSON.stringify(n,st)}`:o+=` ${n}`),o}log
|
|
|
175
191
|
<p>You can close this window.</p>
|
|
176
192
|
</body>
|
|
177
193
|
</html>
|
|
178
|
-
`),setTimeout(()=>{o.close(()=>
|
|
194
|
+
`),setTimeout(()=>{o.close(()=>s(m))},500)}catch(p){let g=String(p?.message||p).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");l.writeHead(400,{"Content-Type":"text/html; charset=utf-8"}),l.end(`
|
|
179
195
|
<!DOCTYPE html>
|
|
180
196
|
<html>
|
|
181
197
|
<head><title>Error</title></head>
|
|
182
198
|
<body style="font-family: system-ui; max-width: 720px; margin: 50px auto; padding: 0 16px;">
|
|
183
199
|
<h1 style="color: #ef4444; text-align: center;">✗ Authentication Failed</h1>
|
|
184
|
-
<pre style="background: #f4f4f5; padding: 16px; border-radius: 8px; white-space: pre-wrap; word-wrap: break-word; font-size: 13px; line-height: 1.5;">${
|
|
200
|
+
<pre style="background: #f4f4f5; padding: 16px; border-radius: 8px; white-space: pre-wrap; word-wrap: break-word; font-size: 13px; line-height: 1.5;">${g}</pre>
|
|
185
201
|
<p style="text-align: center; color: #71717a; margin-top: 24px;">You can close this window and try again in your terminal.</p>
|
|
186
202
|
</body>
|
|
187
203
|
</html>
|
|
188
|
-
`),setTimeout(()=>{o.close(()=>
|
|
204
|
+
`),setTimeout(()=>{o.close(()=>i(p))},500)}});o.on("error",c=>{c.code==="EADDRINUSE"?i(new Error(`Port ${Q} is in use`)):i(c)}),o.listen(Q,"localhost",()=>{a.info("[AuthService] Callback server started"),this.openBrowser(r)}),setTimeout(()=>{o.close(()=>i(new Error("Login timeout")))},120*1e3)})}async logout(){let e=h(),t=await y.deleteTokens(k());return t&&new Promise(r=>{let s=ke.createServer((i,o)=>{i.url?.startsWith("/signout")?(o.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),o.end(`
|
|
189
205
|
<!DOCTYPE html>
|
|
190
206
|
<html>
|
|
191
207
|
<head><title>Signed Out</title></head>
|
|
@@ -194,27 +210,27 @@ ${n.stack}`)):typeof n=="object"?o+=` ${JSON.stringify(n,st)}`:o+=` ${n}`),o}log
|
|
|
194
210
|
<p>You can close this window.</p>
|
|
195
211
|
</body>
|
|
196
212
|
</html>
|
|
197
|
-
`),setTimeout(()=>{
|
|
198
|
-
`);try{let
|
|
213
|
+
`),setTimeout(()=>{s.close(()=>r(!0))},500)):(o.writeHead(404),o.end("Not found"))});s.on("error",()=>{r(!0)}),s.listen(Q,"localhost",()=>{let i=`https://${e.aws.cognitoDomain}/logout?client_id=${e.aws.cognitoClientId}&logout_uri=${encodeURIComponent(we.replace("/callback","/signout"))}`;this.openBrowser(i)}),setTimeout(()=>{s.close(()=>r(!0))},30*1e3)})}async getStatus(){let e=await y.getTokens(k());return e?{authenticated:!y.isTokenExpired(e),tokens:e}:{authenticated:!1}}},K=H.getInstance();B();var d={reset:"\x1B[0m",green:"\x1B[32m",red:"\x1B[31m",yellow:"\x1B[33m",cyan:"\x1B[36m",dim:"\x1B[2m"};async function vt(){console.log(`${d.cyan}CodeVibe Login${d.reset}
|
|
214
|
+
`);try{let n=await K.getStatus();if(n.authenticated&&n.tokens){console.log(`${d.yellow}Already logged in as: ${n.tokens.email}${d.reset}`),console.log(`Token expires: ${new Date(n.tokens.expiresAt).toLocaleString()}`),console.log(`
|
|
199
215
|
Run '${d.dim}codevibe logout${d.reset}' to sign out first.`),process.exit(0);return}console.log("Opening browser for authentication..."),console.log(`${d.dim}Waiting for callback...${d.reset}
|
|
200
|
-
`);let e=await
|
|
201
|
-
${d.green}\u2713 Authentication successful!${d.reset}`),console.log(` User: ${e.email}`),console.log(` User ID: ${e.userId}`),console.log(` Expires: ${new Date(e.expiresAt).toLocaleString()}`)),process.exit(0)}catch(
|
|
202
|
-
${d.red}\u2717 Authentication failed${d.reset}`),console.log(` Error: ${
|
|
203
|
-
`);try{let
|
|
204
|
-
${d.dim}Clearing browser session...${d.reset}`)):console.log(`${d.red}\u2717 Failed to log out.${d.reset}`),process.exit(0)}catch(
|
|
205
|
-
`);try{let
|
|
206
|
-
Run '${d.dim}codevibe login${d.reset}' to sign in.`),process.exit(0);return}let e=!
|
|
207
|
-
${d.dim}Token will be refreshed automatically.${d.reset}`),process.exit(0)}catch(
|
|
216
|
+
`);let e=await K.login();e&&(console.log(`
|
|
217
|
+
${d.green}\u2713 Authentication successful!${d.reset}`),console.log(` User: ${e.email}`),console.log(` User ID: ${e.userId}`),console.log(` Expires: ${new Date(e.expiresAt).toLocaleString()}`)),process.exit(0)}catch(n){console.log(`
|
|
218
|
+
${d.red}\u2717 Authentication failed${d.reset}`),console.log(` Error: ${n.message}`),process.exit(1)}}async function St(){console.log(`${d.cyan}CodeVibe Logout${d.reset}
|
|
219
|
+
`);try{let n=await K.getStatus();if(!n.authenticated){console.log(`${d.yellow}Not logged in.${d.reset}`),process.exit(0);return}let e=n.tokens?.email;await K.logout()?(console.log(`${d.green}\u2713 Logged out successfully.${d.reset}`),console.log(` Previous user: ${e}`),console.log(`
|
|
220
|
+
${d.dim}Clearing browser session...${d.reset}`)):console.log(`${d.red}\u2717 Failed to log out.${d.reset}`),process.exit(0)}catch(n){console.log(`${d.red}\u2717 Logout failed: ${n.message}${d.reset}`),process.exit(1)}}async function wt(){console.log(`${d.cyan}CodeVibe Auth Status${d.reset}
|
|
221
|
+
`);try{let n=await K.getStatus();if(!n.tokens){console.log(`${d.yellow}Not authenticated.${d.reset}`),console.log(`
|
|
222
|
+
Run '${d.dim}codevibe login${d.reset}' to sign in.`),process.exit(0);return}let e=!n.authenticated;console.log(e?`${d.yellow}\u26A0 Token expired${d.reset}`:`${d.green}\u2713 Authenticated${d.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
|
+
${d.dim}Token will be refreshed automatically.${d.reset}`),process.exit(0)}catch(n){console.log(`${d.red}\u2717 Status check failed: ${n.message}${d.reset}`),process.exit(1)}}async function kt(){console.log(`${d.cyan}CodeVibe Reset Device${d.reset}
|
|
208
224
|
`),console.log(`${d.red}\u26A0 WARNING: This will delete your device identity.${d.reset}`),console.log(`${d.red} Old encrypted sessions will become inaccessible.${d.reset}
|
|
209
|
-
`);let{keychainManager:
|
|
225
|
+
`);let{keychainManager:n}=await Promise.resolve().then(()=>(C(),Fe));try{await n.clearAllData(),console.log(`${d.green}\u2713 Device reset complete.${d.reset}`),console.log(` Run '${d.dim}codevibe login${d.reset}' to set up again.`),process.exit(0)}catch(e){console.log(`${d.red}\u2717 Reset failed: ${e.message}${d.reset}`),process.exit(1)}}function bt(){console.log(`CodeVibe Authentication
|
|
210
226
|
`),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(`
|
|
211
|
-
Environment:`),console.log(' Set ENVIRONMENT env var to "development" or "production" (default)'),console.log(" Example: ENVIRONMENT=development codevibe login")}async function
|
|
212
|
-
`);let n
|
|
213
|
-
`).replace(
|
|
227
|
+
Environment:`),console.log(' Set ENVIRONMENT env var to "development" or "production" (default)'),console.log(" Example: ENVIRONMENT=development codevibe login")}async function se(n){let e=k();console.log(`${d.dim}Environment: ${e}${d.reset}
|
|
228
|
+
`);let r=n.slice(2).filter(s=>!s.startsWith("--"))[0];switch(r){case"login":await vt();break;case"logout":await St();break;case"status":await wt();break;case"reset-device":await kt();break;default:bt(),process.exit(r?1:0)}}require.main===module&&se(process.argv).catch(n=>{console.error("Error:",n),process.exit(1)});B();R();var It=/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g;function Qe(n){let e=Ee(n);if(!e)return null;let t=Et(e);if(t)return t;let r=Tt(e);return r||null}function Ee(n){return n.replace(/\r/g,`
|
|
229
|
+
`).replace(It,"").replace(/[│┌┐└┘─├┤┬┴┼╌╎╭╮╯╰║═╔╗╚╝╠╣╦╩╬]/g," ").replace(/[ \t]+\n/g,`
|
|
214
230
|
`).replace(/\n{3,}/g,`
|
|
215
231
|
|
|
216
|
-
`).trim()}function
|
|
217
|
-
`).map(
|
|
218
|
-
`):
|
|
219
|
-
`).map(l=>l.trim()),t=
|
|
220
|
-
`):"Select an option",options:
|
|
232
|
+
`).trim()}function Et(n){let e=n.split(`
|
|
233
|
+
`).map(g=>g.trim()),t=At(e,g=>/\[(?:y\/n|Y\/n|y\/N)\]/.test(g)),r=t>=0?e[t]:null;if(!r)return null;let s=et(e,t),i=s.length>0?s.join(`
|
|
234
|
+
`):r,o=i.toLowerCase(),c=o.includes("what to change")||o.includes("what should")||o.includes("provide")||o.includes("instructions");return{kind:"yes_no",promptText:i,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 Tt(n){let e=n.split(`
|
|
235
|
+
`).map(l=>l.trim()),t=Dt(e);if(t.length<2)return null;let r=t.map(({line:l})=>Ze(l)).filter(l=>!!l),s={};for(let l of r)s[l.number]=l.number;let i=t[0]?.index??-1,o=et(e,i-1);return{kind:"numbered",promptText:o.length>0?o.join(`
|
|
236
|
+
`):"Select an option",options:r,submitMap:s}}function At(n,e){for(let t=n.length-1;t>=0;t-=1)if(e(n[t]))return t;return-1}function Ze(n){let e=n.match(/^(?:[>›❯▸▶➜➤*●]\s*)?(\d+)\.\s+(.*)$/);return e?{number:e[1],text:e[2]}:null}function Dt(n){let e=n.map((r,s)=>({index:s,line:r,parsed:Ze(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 s=e[r],i=t[0];if(s.index!==i.index-1)break;t.unshift(s)}return t.map(({index:r,line:s})=>({index:r,line:s}))}function et(n,e){if(e<0)return[];let t=be(n,e);if(t<0)return[];let{start:r,end:s}=Ie(n,t),i=n.slice(r,s+1).filter(Boolean);if(xt(i)){let u=Kt(n,r-1);return u.length>0?u:i}if(r<=1)return i;let o=r-1;if(o=be(n,o),o<0||o===r-1)return i;let{start:c,end:l}=Ie(n,o),p=n.slice(c,l+1).filter(Boolean);return p.some(tt)?[...p,...i]:i}function tt(n){return/^(?:would you like to|do you want to|the model would like to|action required|confirm)\b/i.test(n)}function be(n,e){let t=e;for(;t>=0&&!n[t];)t-=1;return t}function Ie(n,e){let t=e;for(;t>=0&&n[t];)t-=1;return{start:t+1,end:e}}function Kt(n,e){let t=[],r=e;for(;r>=0&&t.length<2&&(r=be(n,r),!(r<0));){let{start:i,end:o}=Ie(n,r),c=n.slice(i,o+1).filter(Boolean);c.length>0&&t.unshift(c),r=i-1}if(t.length===0)return[];let s=t.findIndex(i=>i.some(tt));return s>=0?t.slice(s).flat():t[t.length-1]}function xt(n){return n.length===0?!1:n.filter(Ct).length>=Math.max(2,Math.ceil(n.length/2))}function Ct(n){return/^\d+\s/.test(n)}J();C();J();R();async function _(n,e,t,r={}){let s;try{s=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(!s)return a.warn("[SessionRekey] Session not found, skipping re-key",{sessionId:n}),0;if(!s.isEncrypted)return 0;let i=s.encryptedKeys||[],o=new Set(i.map(u=>u.deviceId)),c=r.forceDeviceIds??new Set,l;try{l=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=l.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:i.length,grantCount:p.length,grantDeviceIds:p.map(u=>u.deviceId),forceCount:c.size});let g=0;for(let u of p)try{let v=w.encryptSessionKey(e,u.publicKey);await t.grantSessionKey({sessionId:n,deviceId:u.deviceId,encryptedKey:v.encryptedKey,ephemeralPublicKey:v.ephemeralPublicKey}),g++,a.info("[SessionRekey] Granted session key to device",{sessionId:n,deviceId:u.deviceId,platform:u.platform})}catch(v){a.warn("[SessionRekey] Failed to grant session key to device",{sessionId:n,deviceId:u.deviceId,error:v instanceof Error?v.message:String(v)})}return g>0&&a.info("[SessionRekey] Re-key complete",{sessionId:n,grantedCount:g,requestedCount:p.length}),g}async function oe(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:s,encryptedKeys:i}=y.createSessionKey(r);return t.info("Session encryption prepared",{sessionId:n,deviceCount:i.length}),{sessionKey:s,encryptedKeys:i}}catch(r){return t.warn("Failed to prepare session encryption:",r),null}}async function Te(n,e,t){let{sessionId:r,userId:s,agentType:i,projectPath:o,metadata:c}=n,l=null;try{l=await e.getSession(r)}catch(S){t.warn("Failed to get session (will attempt to create new)",{sessionId:r,error:S})}if(l){t.info("Session exists in backend - reactivating",{sessionId:r,previousStatus:l.status});try{await e.updateSession({sessionId:r,status:"ACTIVE"})}catch(m){t.warn("Failed to reactivate existing session, will continue",{sessionId:r,error:m})}let S=null,V=l.encryptedKeys;if(l.isEncrypted&&V?.length)try{let m=await y.getSessionKey(r,V);m?(S=m,y.cacheSessionKey(r,m),t.info("Session key retrieved for resumed session",{sessionId:r})):t.warn("No encrypted key for this device; proceeding without decryption",{sessionId:r})}catch(m){t.warn("Failed to retrieve session key for resumed session",{sessionId:r,error:m})}if(S)try{let m=await _(r,S,e);m>0&&t.info("Session re-keyed for newly registered devices on resume",{sessionId:r,newDeviceCount:m})}catch(m){t.warn("Session re-key on resume failed (non-fatal)",{sessionId:r,error:m instanceof Error?m.message:String(m)})}return{resumed:!0,sessionKey:S}}let p=await oe(r,e,t),g=o,u=c;p&&(g=w.encryptContent(o,p.sessionKey),u&&Object.keys(u).length>0&&(u={encrypted:w.encryptMetadata(u,p.sessionKey)}),t.info("Session data encrypted",{sessionId:r})),t.info("Creating new session in backend",{sessionId:r,userId:s,agentType:i,isEncrypted:!!p}),await e.createSession({sessionId:r,userId:s,agentType:i,projectPath:g,status:"ACTIVE",metadata:u,isEncrypted:p?!0:void 0,creatorDeviceId:p?await y.getDeviceId():void 0,encryptionVersion:p?1:void 0,encryptedKeys:p?.encryptedKeys});let v=p?.sessionKey||null;return p&&y.cacheSessionKey(r,p.sessionKey),t.info("Session created",{sessionId:r,userId:s,isEncrypted:!!p}),{resumed:!1,sessionKey:v}}C();function Ae(n,e){let t=n.getCurrentUserId(),r=async(i,o)=>{let c=y.getCachedSessionIds();if(c.length===0){e.info("[DeviceKeyWatcher] No active sessions to re-key",{reason:i});return}e.info("[DeviceKeyWatcher] Running re-key pass",{reason:i,activeSessionCount:c.length,forceDeviceCount:o?.size??0});for(let l of c){let p=y.getCachedSessionKey(l);if(p)try{let g=await _(l,p,n,o?{forceDeviceIds:o}:void 0);g>0&&e.info("[DeviceKeyWatcher] Session re-keyed",{sessionId:l,newDeviceCount:g,reason:i})}catch(g){e.warn("[DeviceKeyWatcher] Re-key failed for session (non-fatal)",{sessionId:l,reason:i,error:g instanceof Error?g.message:String(g)})}}},s=n.subscribeToDeviceKeyRegistered(t,i=>{e.info("[DeviceKeyWatcher] New device observed, triggering re-key",{userId:t,newDeviceId:i.deviceId,platform:i.platform,deviceName:i.deviceName}),r(`new-device:${i.deviceId}`,new Set([i.deviceId]))},()=>{r("watcher-reconnect")},i=>{e.warn("[DeviceKeyWatcher] Subscription error (will retry)",{error:i instanceof Error?i.message:String(i)})});return e.info("[DeviceKeyWatcher] Started",{userId:t}),s}C();async function De(n,e){try{let t=await y.getDeviceId(),r=await y.getDevicePublicKey(),s=y.getDevicePlatform(),i=y.getDeviceName();e.info("Registering device encryption key",{deviceId:t,platform:s,deviceName:i}),await n.registerDeviceKey(t,r,s,i),y.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,getConfig,getEnvironment,keychainManager,loadConfig,logger,mutations,normalizeSnapshot,parseInteractivePrompt,prepareSessionEncryption,queries,registerDeviceEncryptionKey,rekeySessionForNewDevices,resumeOrCreateSession,runAuthCli,startDeviceKeyWatcher,subscriptions});
|
|
@@ -104,6 +104,23 @@ export declare class KeychainManager {
|
|
|
104
104
|
* Cache a session key
|
|
105
105
|
*/
|
|
106
106
|
cacheSessionKey(sessionId: string, sessionKey: string): void;
|
|
107
|
+
/**
|
|
108
|
+
* Get a cached session key without touching the backend or attempting
|
|
109
|
+
* to decrypt from encryptedKeys. Returns null if the session key is not
|
|
110
|
+
* currently cached in memory.
|
|
111
|
+
*
|
|
112
|
+
* Used by the session re-key flow: when the plugin observes a new device
|
|
113
|
+
* registration via the onDeviceKeyRegistered subscription, it needs the
|
|
114
|
+
* plaintext session key of each active session to wrap it for the new
|
|
115
|
+
* device. Those keys live in this cache for the lifetime of the session.
|
|
116
|
+
*/
|
|
117
|
+
getCachedSessionKey(sessionId: string): string | null;
|
|
118
|
+
/**
|
|
119
|
+
* List session IDs that currently have a cached session key in this
|
|
120
|
+
* process's memory. Used by the re-key flow to iterate over all sessions
|
|
121
|
+
* the plugin is actively managing.
|
|
122
|
+
*/
|
|
123
|
+
getCachedSessionIds(): string[];
|
|
107
124
|
/**
|
|
108
125
|
* Clear cached session key
|
|
109
126
|
*/
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { AppSyncClient } from '../appsync/appsync-client';
|
|
2
|
+
import { Logger } from '../logger';
|
|
3
|
+
/**
|
|
4
|
+
* Start the device-key registration watcher for the authenticated user.
|
|
5
|
+
*
|
|
6
|
+
* @returns A function that stops the watcher. Most plugins don't need to
|
|
7
|
+
* call it because `appSyncClient.cleanupSubscriptions()` at
|
|
8
|
+
* shutdown already handles the teardown.
|
|
9
|
+
*/
|
|
10
|
+
export declare function startDeviceKeyWatcher(appSyncClient: AppSyncClient, logger: Logger): () => void;
|
|
@@ -1,2 +1,5 @@
|
|
|
1
1
|
export { resumeOrCreateSession, prepareSessionEncryption } from './session-resume';
|
|
2
2
|
export type { ResumeOrCreateSessionInput, ResumeOrCreateSessionResult } from './session-resume';
|
|
3
|
+
export { rekeySessionForNewDevices } from './session-rekey';
|
|
4
|
+
export { startDeviceKeyWatcher } from './device-key-watcher';
|
|
5
|
+
export { registerDeviceEncryptionKey } from './register-device-key';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { AppSyncClient } from '../appsync/appsync-client';
|
|
2
|
+
import { Logger } from '../logger';
|
|
3
|
+
/**
|
|
4
|
+
* Register the current device's ECDH public key with the backend.
|
|
5
|
+
*
|
|
6
|
+
* Fetches the device identity from the local keychain (creating a new
|
|
7
|
+
* ECDH keypair if one doesn't exist), then calls the `registerDeviceKey`
|
|
8
|
+
* mutation to publish the public half.
|
|
9
|
+
*
|
|
10
|
+
* @param appSyncClient Authenticated AppSyncClient.
|
|
11
|
+
* @param logger Plugin-specific logger so log lines are tagged correctly.
|
|
12
|
+
*/
|
|
13
|
+
export declare function registerDeviceEncryptionKey(appSyncClient: AppSyncClient, logger: Logger): Promise<void>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { AppSyncClient } from '../appsync/appsync-client';
|
|
2
|
+
/**
|
|
3
|
+
* Options for re-keying behavior.
|
|
4
|
+
*/
|
|
5
|
+
export interface RekeyOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Set of deviceIds that should be force-wrapped (granted) even if
|
|
8
|
+
* they already have an entry in `session.encryptedKeys`. Use this
|
|
9
|
+
* when you know a specific device just re-registered — for example,
|
|
10
|
+
* when handling an `onDeviceKeyRegistered` subscription event — so
|
|
11
|
+
* a rotated public key gets a fresh wrapped entry. The old entry
|
|
12
|
+
* is left intact; the new one is appended via `grantSessionKey`.
|
|
13
|
+
*
|
|
14
|
+
* Default: empty. The bulk diff path (catch-up after disconnect,
|
|
15
|
+
* catch-up at session resume) does not pass this and relies on the
|
|
16
|
+
* standard "deviceId missing from encryptedKeys" comparison.
|
|
17
|
+
*/
|
|
18
|
+
forceDeviceIds?: ReadonlySet<string>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Compare the current list of registered devices for the authenticated
|
|
22
|
+
* user against the session's encryptedKeys array, and grant the session
|
|
23
|
+
* key to any device that is missing an entry (or that the caller has
|
|
24
|
+
* explicitly marked as needing a fresh wrap via `forceDeviceIds`).
|
|
25
|
+
*
|
|
26
|
+
* Safe to call repeatedly. No-op when every registered device already
|
|
27
|
+
* has an entry and nothing is forced. Does not modify or remove existing
|
|
28
|
+
* entries — only appends new ones via the grantSessionKey mutation.
|
|
29
|
+
*
|
|
30
|
+
* @param sessionId The session to re-key.
|
|
31
|
+
* @param sessionKey The plaintext session key (base64). The caller must
|
|
32
|
+
* already hold this — typically from
|
|
33
|
+
* keychainManager.getCachedSessionKey(sessionId).
|
|
34
|
+
* @param appSyncClient Authenticated AppSyncClient.
|
|
35
|
+
* @param options Optional behavior knobs. See RekeyOptions.
|
|
36
|
+
* @returns The number of new grants that succeeded.
|
|
37
|
+
*/
|
|
38
|
+
export declare function rekeySessionForNewDevices(sessionId: string, sessionKey: string, appSyncClient: AppSyncClient, options?: RekeyOptions): Promise<number>;
|
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.18",
|
|
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.10",
|
|
51
51
|
"dotenv": "^16.6.1",
|
|
52
52
|
"express": "^5.1.0",
|
|
53
53
|
"graphql": "^16.12.0",
|