@diggerhq/catty 0.3.11 → 0.3.13

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/dist/index.js CHANGED
@@ -1,19 +1,42 @@
1
1
  #!/usr/bin/env node
2
- import{program as u}from"commander";import{Command as Le}from"commander";import Ve from"open";var A="https://api.catty.dev",w=".catty",P="credentials.json";function p(e){return e||(process.env.CATTY_API_ADDR?process.env.CATTY_API_ADDR:A)}function T(e){return new Promise(t=>setTimeout(t,e))}import{homedir as he}from"os";import{join as M}from"path";import{readFileSync as ye,writeFileSync as _e,mkdirSync as we,unlinkSync as Se,existsSync as Ce}from"fs";function N(){return M(he(),w)}function E(){return M(N(),P)}function h(){let e=E();try{let t=ye(e,"utf-8");return JSON.parse(t)}catch{return null}}function S(e){let t=N(),s=E();we(t,{recursive:!0,mode:448}),_e(s,JSON.stringify(e,null,2),{mode:384})}function D(){let e=E();Ce(e)&&Se(e)}function y(){let e=h();return!(!e||!e.access_token||e.expires_at&&!e.refresh_token&&new Date(e.expires_at)<=new Date)}function O(){return h()?.access_token||null}function $(){return h()?.refresh_token||null}var _=class extends Error{constructor(s,n,o,r){super(o);this.statusCode=s;this.errorCode=n;this.upgradeURL=r;this.name="APIError"}isQuotaExceeded(){return this.statusCode===402&&this.errorCode==="quota_exceeded"}},d=class{baseURL;authToken;constructor(t){this.baseURL=t||process.env.CATTY_API_ADDR||A,this.authToken=O()}async doRequest(t,s,n){let o=new AbortController,r=setTimeout(()=>o.abort(),12e4);try{let i={"Content-Type":"application/json"};return this.authToken&&(i.Authorization=`Bearer ${this.authToken}`),await fetch(`${this.baseURL}${s}`,{method:t,headers:i,body:n?JSON.stringify(n):void 0,signal:o.signal})}finally{clearTimeout(r)}}async doRequestWithRefresh(t,s,n){let o=await this.doRequest(t,s,n);return o.status===401&&await this.refreshAuthToken()&&(o=await this.doRequest(t,s,n)),o}async refreshAuthToken(){let t=$();if(!t)return!1;try{let s=await this.doRequest("POST","/v1/auth/refresh",{refresh_token:t});if(!s.ok)return!1;let n=await s.json();if(!n.access_token)return!1;let o=h();return o&&(o.access_token=n.access_token,n.refresh_token&&(o.refresh_token=n.refresh_token),n.expires_in&&(o.expires_at=new Date(Date.now()+(n.expires_in-30)*1e3).toISOString()),S(o),this.authToken=n.access_token),!0}catch{return!1}}async handleResponse(t){if(!t.ok){let s;try{s=await t.json()}catch{s={error:t.statusText}}throw new _(t.status,s.code||"",s.error||t.statusText,s.upgrade_url)}return t.json()}async createSession(t){let s=await this.doRequestWithRefresh("POST","/v1/sessions",t);return this.handleResponse(s)}async listSessions(){let t=await this.doRequestWithRefresh("GET","/v1/sessions");return this.handleResponse(t)}async getSession(t,s){let n=s?`/v1/sessions/${t}?live=true`:`/v1/sessions/${t}`,o=await this.doRequestWithRefresh("GET",n);return this.handleResponse(o)}async stopSession(t,s){let n=s?`/v1/sessions/${t}?delete=true`:`/v1/sessions/${t}`,o=await this.doRequestWithRefresh("DELETE",n);if(!o.ok){let r;try{r=await o.json()}catch{r={error:o.statusText}}throw new _(o.status,r.code||"",r.error||o.statusText)}}async createCheckoutSession(){let t=await this.doRequestWithRefresh("POST","/v1/checkout");return(await this.handleResponse(t)).url}};import k from"ws";var C=class{wasRaw=!1;cleanupDone=!1;isTerminal(){return process.stdin.isTTY===!0}makeRaw(){if(!this.isTerminal()||this.wasRaw)return;process.stdin.setRawMode(!0),process.stdin.resume(),this.wasRaw=!0;let t=()=>this.restore();process.on("exit",t),process.on("SIGINT",()=>{t(),process.exit(130)}),process.on("SIGTERM",()=>{t(),process.exit(143)})}restore(){if(!this.cleanupDone){if(this.wasRaw&&process.stdin.isTTY){try{process.stdin.setRawMode(!1)}catch{}this.wasRaw=!1}this.cleanupDone=!0}}getSize(){return{cols:process.stdout.columns||80,rows:process.stdout.rows||24}}onResize(t){process.stdout.on("resize",t)}offResize(t){process.stdout.off("resize",t)}};var l={RESIZE:"resize",SIGNAL:"signal",PING:"ping",PONG:"pong",READY:"ready",EXIT:"exit",ERROR:"error",SYNC_BACK:"sync_back",SYNC_BACK_ACK:"sync_back_ack",FILE_CHANGE:"file_change"};function U(e){let t=JSON.parse(e);switch(t.type){case l.RESIZE:return JSON.parse(e);case l.SIGNAL:return JSON.parse(e);case l.PING:return{type:"ping"};case l.PONG:return{type:"pong"};case l.READY:return{type:"ready"};case l.EXIT:return JSON.parse(e);case l.ERROR:return JSON.parse(e);case l.SYNC_BACK:return JSON.parse(e);case l.SYNC_BACK_ACK:return JSON.parse(e);case l.FILE_CHANGE:return JSON.parse(e);default:return t}}function v(e,t){return JSON.stringify({type:l.RESIZE,cols:e,rows:t})}function L(){return JSON.stringify({type:l.PONG})}function V(e){return JSON.stringify({type:l.SYNC_BACK,enabled:e})}import{writeFileSync as Re,unlinkSync as be,mkdirSync as xe,renameSync as Ae}from"fs";import{join as B,dirname as F,normalize as I,isAbsolute as Ee,sep as W}from"path";function z(e){let t=I(e.path.replace(/^\.\//,""));if(!t||t===".")return;if(Ee(t)){console.error(`sync-back rejected absolute path: ${t}`);return}if(t===".."||t.startsWith(".."+W)){console.error(`sync-back rejected traversal path: ${t}`);return}let s=process.cwd(),n=B(s,t),o=I(s),r=I(n);if(!r.startsWith(o+W)&&r!==o){console.error(`sync-back rejected path outside base: ${n}`);return}try{if(e.action==="delete")be(n);else if(e.action==="write"){let i=Buffer.from(e.content||"","base64");xe(F(n),{recursive:!0});let c=B(F(n),`.catty-sync-${Date.now()}`);Re(c,i,{mode:e.mode||420}),Ae(c,n)}}catch{}}async function R(e){let t=new C;if(!t.isTerminal())throw new Error("stdin is not a terminal");let s=new k(e.connectURL,{headers:{...e.headers,Authorization:`Bearer ${e.connectToken}`}});return new Promise((n,o)=>{let r=!1,i=0,c=()=>{let{cols:a,rows:g}=t.getSize();s.readyState===k.OPEN&&s.send(v(a,g))},m=()=>{t.restore(),t.offResize(c),process.stdin.off("data",f)},f=a=>{s.readyState===k.OPEN&&s.send(a)};s.on("open",()=>{e.syncBack&&(s.send(V(!0)),setTimeout(()=>{r||process.stderr.write(`\r
2
+ import{program as h}from"commander";import{Command as zt}from"commander";import Wt from"open";var W="https://api.catty.dev",D=".catty",ae="credentials.json";function f(e){return e||(process.env.CATTY_API_ADDR?process.env.CATTY_API_ADDR:W)}function k(e){return new Promise(t=>setTimeout(t,e))}import{homedir as pt}from"os";import{join as ce}from"path";import{readFileSync as dt,writeFileSync as ut,mkdirSync as mt,unlinkSync as ft,existsSync as gt}from"fs";function le(){return ce(pt(),D)}function Y(){return ce(le(),ae)}function b(){let e=Y();try{let t=dt(e,"utf-8");return JSON.parse(t)}catch{return null}}function O(e){let t=le(),n=Y();mt(t,{recursive:!0,mode:448}),ut(n,JSON.stringify(e,null,2),{mode:384})}function pe(){let e=Y();gt(e)&&ft(e)}function E(){let e=b();return!(!e||!e.access_token||e.expires_at&&!e.refresh_token&&new Date(e.expires_at)<=new Date)}function de(){return b()?.access_token||null}function ue(){return b()?.refresh_token||null}var I=class extends Error{constructor(n,o,s,r){super(s);this.statusCode=n;this.errorCode=o;this.upgradeURL=r;this.name="APIError"}isQuotaExceeded(){return this.statusCode===402&&this.errorCode==="quota_exceeded"}},g=class{baseURL;authToken;constructor(t){this.baseURL=t||process.env.CATTY_API_ADDR||W,this.authToken=de()}async doRequest(t,n,o){let s=new AbortController,r=setTimeout(()=>s.abort(),12e4);try{let i={"Content-Type":"application/json"};return this.authToken&&(i.Authorization=`Bearer ${this.authToken}`),await fetch(`${this.baseURL}${n}`,{method:t,headers:i,body:o?JSON.stringify(o):void 0,signal:s.signal})}finally{clearTimeout(r)}}async doRequestWithRefresh(t,n,o){let s=await this.doRequest(t,n,o);return s.status===401&&await this.refreshAuthToken()&&(s=await this.doRequest(t,n,o)),s}async refreshAuthToken(){let t=ue();if(!t)return!1;try{let n=await this.doRequest("POST","/v1/auth/refresh",{refresh_token:t});if(!n.ok)return!1;let o=await n.json();if(!o.access_token)return!1;let s=b();return s&&(s.access_token=o.access_token,o.refresh_token&&(s.refresh_token=o.refresh_token),o.expires_in&&(s.expires_at=new Date(Date.now()+(o.expires_in-30)*1e3).toISOString()),O(s),this.authToken=o.access_token),!0}catch{return!1}}async handleResponse(t){if(!t.ok){let n;try{n=await t.json()}catch{n={error:t.statusText}}throw new I(t.status,n.code||"",n.error||t.statusText,n.upgrade_url)}return t.json()}async createSession(t){let n=await this.doRequestWithRefresh("POST","/v1/sessions",t);return this.handleResponse(n)}async listSessions(){let t=await this.doRequestWithRefresh("GET","/v1/sessions");return this.handleResponse(t)}async getSession(t,n){let o=n?`/v1/sessions/${t}?live=true`:`/v1/sessions/${t}`,s=await this.doRequestWithRefresh("GET",o);return this.handleResponse(s)}async stopSession(t,n){let o=n?`/v1/sessions/${t}/stop?delete=true`:`/v1/sessions/${t}/stop`,s=await this.doRequestWithRefresh("POST",o);if(!s.ok){let r;try{r=await s.json()}catch{r={error:s.statusText}}throw new I(s.status,r.code||"",r.error||s.statusText)}}async createCheckoutSession(){let t=await this.doRequestWithRefresh("POST","/v1/checkout");return(await this.handleResponse(t)).url}};import L from"ws";import{appendFileSync as Nt}from"fs";import{homedir as Dt}from"os";var me=!1,A=null,U=class{wasRaw=!1;cleanupDone=!1;isTerminal(){return process.stdin.isTTY===!0}makeRaw(){if(this.isTerminal()&&!this.wasRaw&&(process.stdin.setRawMode(!0),process.stdin.resume(),this.wasRaw=!0,A=this,!me)){me=!0;let t=()=>{A&&A.restore()};process.on("exit",t),process.on("SIGINT",()=>{t(),process.exit(130)}),process.on("SIGTERM",()=>{t(),process.exit(143)}),process.on("SIGHUP",()=>{t(),process.exit(129)}),process.on("SIGTSTP",()=>{t(),process.kill(process.pid,"SIGTSTP")}),process.on("SIGCONT",()=>{A&&A.wasRaw===!1&&!A.cleanupDone&&process.stderr.write(`\r
3
+ \x1B[33m\u26A0 Session suspended. Run "fg" or reconnect with "catty connect <label>"\x1B[0m\r
4
+ `)}),process.on("uncaughtException",n=>{t(),process.stderr.write(`\r
5
+ \x1B[31m\u2717 Unexpected error: ${n.message}\x1B[0m\r
6
+ `),process.stderr.write(`\x1B[90mReconnect with: catty connect <session-label>\x1B[0m\r
7
+ `),process.exit(1)}),process.on("unhandledRejection",n=>{t();let o=n instanceof Error?n.message:String(n);process.stderr.write(`\r
8
+ \x1B[31m\u2717 Unexpected error: ${o}\x1B[0m\r
9
+ `),process.stderr.write(`\x1B[90mReconnect with: catty connect <session-label>\x1B[0m\r
10
+ `),process.exit(1)})}}restore(){if(!this.cleanupDone){if(this.wasRaw&&process.stdin.isTTY){try{process.stdin.setRawMode(!1),process.stdout.write("\x1B[?2004l"),process.stdout.write("\x1B[?25h")}catch{}this.wasRaw=!1}this.cleanupDone=!0,A===this&&(A=null)}}static forceReset(){try{process.stdin.isTTY&&process.stdin.setRawMode(!1),process.stdout.write("\x1B[?2004l"),process.stdout.write("\x1B[?25h"),process.stdout.write("\x1Bc")}catch{}}getSize(){return{cols:process.stdout.columns||80,rows:process.stdout.rows||24}}onResize(t){process.stdout.on("resize",t)}offResize(t){process.stdout.off("resize",t)}enableBracketedPaste(){process.stdout.write("\x1B[?2004h")}disableBracketedPaste(){process.stdout.write("\x1B[?2004l")}};var u={RESIZE:"resize",SIGNAL:"signal",PING:"ping",PONG:"pong",READY:"ready",EXIT:"exit",ERROR:"error",SYNC_BACK:"sync_back",SYNC_BACK_ACK:"sync_back_ack",FILE_CHANGE:"file_change",FILE_UPLOAD:"file_upload",FILE_UPLOAD_CHUNK:"file_upload_chunk"};function fe(e){let t=JSON.parse(e);switch(t.type){case u.RESIZE:return JSON.parse(e);case u.SIGNAL:return JSON.parse(e);case u.PING:return{type:"ping"};case u.PONG:return{type:"pong"};case u.READY:return{type:"ready"};case u.EXIT:return JSON.parse(e);case u.ERROR:return JSON.parse(e);case u.SYNC_BACK:return JSON.parse(e);case u.SYNC_BACK_ACK:return JSON.parse(e);case u.FILE_CHANGE:return JSON.parse(e);case u.FILE_UPLOAD:return JSON.parse(e);default:return t}}function J(e,t){return JSON.stringify({type:u.RESIZE,cols:e,rows:t})}function ge(){return JSON.stringify({type:u.PONG})}function he(e){return JSON.stringify({type:u.SYNC_BACK,enabled:e})}function ye(e,t,n,o){return JSON.stringify({type:u.FILE_UPLOAD,filename:e,remote_path:t,content:n.toString("base64"),mime_type:o})}function xe(e,t,n,o,s,r,i){return JSON.stringify({type:u.FILE_UPLOAD_CHUNK,upload_id:e,filename:t,remote_path:n,chunk_index:o,total_chunks:s,content:r,mime_type:i})}import{writeFileSync as yt,unlinkSync as xt,mkdirSync as _t,renameSync as wt}from"fs";import{join as _e,dirname as we,normalize as q,isAbsolute as St,sep as Se}from"path";function be(e){let t=q(e.path.replace(/^\.\//,""));if(!t||t===".")return;if(St(t)){console.error(`sync-back rejected absolute path: ${t}`);return}if(t===".."||t.startsWith(".."+Se)){console.error(`sync-back rejected traversal path: ${t}`);return}let n=process.cwd(),o=_e(n,t),s=q(n),r=q(o);if(!r.startsWith(s+Se)&&r!==s){console.error(`sync-back rejected path outside base: ${o}`);return}try{if(e.action==="delete")xt(o);else if(e.action==="write"){let i=Buffer.from(e.content||"","base64");_t(we(o),{recursive:!0});let a=_e(we(o),`.catty-sync-${Date.now()}`);yt(a,i,{mode:e.mode||420}),wt(a,o)}}catch{}}import{readFileSync as bt,statSync as Ce,appendFileSync as Ct}from"fs";import{basename as Re,extname as ke}from"path";import{homedir as Rt}from"os";function P(e){if(process.env.CATTY_DEBUG==="1"){let t=`${Rt()}/.catty-debug.log`;Ct(t,`${new Date().toISOString()} ${e}
11
+ `)}}var kt=[".png",".jpg",".jpeg",".gif",".webp",".bmp",".svg"],Et=[".pdf",".txt",".md",".json",".xml",".csv"],At=[...kt,...Et],Tt=10*1024*1024,K=10*1024;function Ee(e){let t=e.trim();P(`detectFilePaths input: ${JSON.stringify(t)}`);let n=vt(t);P(`split paths: ${JSON.stringify(n)}`);let o=[];for(let s of n){let r=s.replace(/\\ /g," ");if(!r)continue;let i=r.startsWith("~")?r.replace(/^~/,process.env.HOME||"~"):r;if(!i.startsWith("/")&&!i.match(/^[A-Za-z]:\\/)){P(`skipping non-absolute: ${i}`);continue}try{Ce(i),P(`found file: ${i}`),o.push(i)}catch(a){P(`file not found: ${i} - ${a}`);continue}}return P(`found ${o.length} valid files`),o}function vt(e){let t=[],n="",o=0;for(;o<e.length;)e[o]==="\\"&&o+1<e.length&&e[o+1]===" "?(n+="\\ ",o+=2):e[o]===" "?(n&&(t.push(n),n=""),o++):(n+=e[o],o++);return n&&t.push(n),t}function Ae(e){try{let t=Ce(e);if(!t.isFile())return{shouldUpload:!1};if(t.size>Tt)return{shouldUpload:!1};let n=ke(e).toLowerCase();if(!At.includes(n))return{shouldUpload:!1};let o=bt(e),s=Re(e),r=`/workspace/.catty-uploads/${s}`,i=It(n);return{shouldUpload:!0,localPath:e,remotePath:r,content:o,filename:s,mimeType:i}}catch{return{shouldUpload:!1}}}function It(e){return{".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".gif":"image/gif",".webp":"image/webp",".bmp":"image/bmp",".svg":"image/svg+xml",".pdf":"application/pdf",".txt":"text/plain",".md":"text/markdown",".json":"application/json",".xml":"application/xml",".csv":"text/csv"}[e.toLowerCase()]||"application/octet-stream"}function Te(e){let t=Date.now(),n=ke(e);return`${Re(e,n).replace(/\s+/g,"_").replace(/[^a-zA-Z0-9_-]/g,"")}-${t}${n}`}function x(e){if(process.env.CATTY_DEBUG==="1"){let t=`${Dt()}/.catty-debug.log`;Nt(t,`${new Date().toISOString()} [ws] ${e}
12
+ `)}}var ve="\x1B[200~",F="\x1B[201~";async function B(e){let t=new U;if(!t.isTerminal())throw new Error("stdin is not a terminal");let n=new L(e.connectURL,{headers:{...e.headers,Authorization:`Bearer ${e.connectToken}`},handshakeTimeout:3e4});return new Promise((o,s)=>{let r=!1,i=0,a=!1,_=!1,m=!1,Z=!1,w=l=>{Z||(Z=!0,o(l))},j=!1,N="",yn="",H=0,nt=1e3,X=()=>{m=!0,C();try{n.close()}catch{}w({type:"interrupted"})};process.once("SIGINT",X);let Q=()=>{m=!0,process.stderr.write(`\r
13
+ \x1B[33mForce quit (Ctrl+\\)\x1B[0m\r
14
+ `),C();try{n.close()}catch{}w({type:"interrupted"})};process.once("SIGQUIT",Q);let ee=setTimeout(()=>{if(!_&&!a){a=!0,process.stderr.write(`\r
15
+ \x1B[31m\u2717 Connection timeout: server not responding\x1B[0m\r
16
+ `);try{n.terminate()}catch{}w({type:"disconnected",reason:"Connection timeout"})}},3e4),te=Date.now(),ot=75e3,ne=setInterval(()=>{if(a)return;let l=Date.now()-te;if(l>ot){x(`Client-side timeout: no data for ${l}ms`),clearInterval(ne);let c=Math.round(l/1e3);process.stderr.write(`\r
17
+ \x1B[31m\u2717 Connection timed out (no data for ${c}s)\x1B[0m\r
18
+ `);try{n.terminate()}catch{}C(),w({type:"disconnected",reason:"Connection timed out (no data received)"})}},5e3),oe=()=>{let{cols:l,rows:c}=t.getSize();n.readyState===L.OPEN&&n.send(J(l,c))},C=()=>{a=!0,clearTimeout(ee),clearInterval(ne),t.disableBracketedPaste(),t.restore(),t.offResize(oe),process.stdin.off("data",se),process.off("SIGINT",X),process.off("SIGQUIT",Q)},se=l=>{if(n.readyState!==L.OPEN)return;if(l.indexOf(3)!==-1){let p=Date.now();if(p-H<nt){m=!0,process.stderr.write(`\r
19
+ `),C();try{n.close()}catch{}w({type:"interrupted"});return}H=p}try{let p=l.toString("utf-8");if(j){let d=p.indexOf(F);if(d===-1)N+=p;else{N+=p.slice(0,d),j=!1,re(N),N="";let y=p.slice(d+F.length);y&&n.send(Buffer.from(y,"utf-8"))}}else{let d=p.indexOf(ve);if(d===-1){n.send(l);return}d>0&&n.send(Buffer.from(p.slice(0,d),"utf-8"));let y=p.slice(d+ve.length),R=y.indexOf(F);if(R!==-1){let T=y.slice(0,R);re(T);let S=y.slice(R+F.length);S&&n.send(Buffer.from(S,"utf-8"))}else j=!0,N=y}}catch{try{n.send(l)}catch{}}},st=async l=>{let c=Ae(l);if(x(`shouldAutoUpload: ${c.shouldUpload}, size: ${c.content?.length||0}`),!c.shouldUpload||!c.content||!c.filename||!c.remotePath||!c.mimeType)return null;let p=Te(c.filename),d=`/workspace/.catty-uploads/${p}`;if(x(`uploading to: ${d}`),c.content.length>K){let R=`${Date.now()}-${Math.random().toString(36).slice(2)}`,T=c.content.toString("base64"),S=Math.ceil(T.length/(K*1.34)),z=Math.ceil(T.length/S);x(`chunked upload: ${S} chunks, ~${z} bytes each`);for(let v=0;v<S;v++){let ie=v*z,it=Math.min(ie+z,T.length),at=T.slice(ie,it),ct=xe(R,p,d,v,S,at,c.mimeType);n.send(ct),x(`sent chunk ${v+1}/${S}`),v<S-1&&await new Promise(lt=>setTimeout(lt,1))}}else{let R=ye(p,d,c.content,c.mimeType);x(`single message size: ${R.length} bytes`),n.send(R)}return d},re=async l=>{try{if(!l||!l.trim())return;let c=Ee(l);if(c.length>0){x(`found ${c.length} files to upload`);let p=[];for(let d of c){let y=await st(d);y&&p.push(y)}if(p.length>0){x(`uploaded ${p.length} files, sending paths`);let d=p.join(" ");n.send(Buffer.from(d,"utf-8")),x(`paths sent: ${d}`);return}}n.send(Buffer.from(l,"utf-8"))}catch(c){x(`ERROR in handlePastedContent: ${c}`);try{n.send(Buffer.from(l,"utf-8"))}catch{}}};n.on("open",()=>{_=!0,clearTimeout(ee),e.syncBack&&(n.send(he(!0)),setTimeout(()=>{r||process.stderr.write(`\r
3
20
  (sync-back) No ack from executor yet \u2014 this machine may be running an older catty-exec image without sync-back.\r
4
- `)},2e3)),t.makeRaw();let{cols:a,rows:g}=t.getSize();s.send(v(a,g)),t.onResize(c),process.stdin.on("data",f)}),s.on("message",(a,g)=>{if(g)process.stdout.write(a);else try{let fe=U(a.toString());ge(fe)}catch{}});function ge(a){switch(a.type){case"exit":{let g=a;i=g.code,e.onExit?.(g.code),process.stderr.write(`\r
5
- Process exited with code ${g.code}\r
6
- `),m(),s.close(),n();break}case"error":{let g=a;process.stderr.write(`\r
7
- Error: ${g.message}\r
8
- `);break}case"ping":s.send(L());break;case"file_change":z(a);break;case"sync_back_ack":r=!0;break}}s.on("close",a=>{m(),n()}),s.on("error",a=>{m(),o(a)}),process.on("exit",()=>{m(),s.readyState===k.OPEN&&s.close()})})}import Pe from"archiver";import Te from"ignore";import{readFileSync as Me,readdirSync as Ne,statSync as De}from"fs";import{join as j,relative as Oe}from"path";var $e=[".git",".git/**","node_modules","node_modules/**","__pycache__","__pycache__/**",".venv",".venv/**","venv","venv/**",".env","*.pyc",".DS_Store","*.log"];async function Ue(e){let t=Te().add($e);try{let s=Me(j(e,".gitignore"),"utf-8");t.add(s)}catch{}return new Promise((s,n)=>{let o=[],r=Pe("zip",{zlib:{level:9}});r.on("data",i=>o.push(i)),r.on("end",()=>s(Buffer.concat(o))),r.on("error",n),Y(e,e,t,r),r.finalize()})}function Y(e,t,s,n){let o;try{o=Ne(t)}catch{return}for(let r of o){let i=j(t,r),c=Oe(e,i);if(s.ignores(c))continue;let m;try{m=De(i)}catch{continue}if(m.isDirectory()){if(s.ignores(c+"/"))continue;Y(e,i,s,n)}else m.isFile()&&n.file(i,{name:c})}}async function J(e,t,s){let n=process.cwd(),o=await Ue(n);if(o.length>104857600)throw new Error(`Workspace too large (${o.length} bytes, max ${104857600})`);let r=await fetch(e,{method:"POST",headers:{Authorization:`Bearer ${t}`,"Content-Type":"application/zip","fly-force-instance-id":s},body:o});if(!r.ok){let i=await r.text();throw new Error(`Upload failed: ${r.status} - ${i}`)}}function q(e){return e.replace("wss://","https://").replace("ws://","http://").replace("/connect","/upload")}var K=new Le("new").description("Start a new remote agent session").option("--agent <name>","Agent to use: claude or codex","claude").option("--no-upload","Don't upload current directory").option("--no-sync-back","Disable sync-back").option("--enable-prompts","Enable permission prompts (by default, all permissions are auto-approved)",!1).action(async function(){let e=this.opts(),t=p(this.optsWithGlobals().api);y()||(console.error("Not logged in. Please run 'catty login' first."),process.exit(1));let s=new d(t);console.log("Creating session...");let n;switch(e.agent){case"claude":e.enablePrompts?n=["claude-wrapper"]:n=["claude-wrapper","--dangerously-skip-permissions"];break;case"codex":n=["codex"];break;default:console.error(`Unknown agent: ${e.agent} (must be 'claude' or 'codex')`),process.exit(1)}let o;try{o=await s.createSession({agent:e.agent,cmd:n,region:"iad",ttl_sec:7200})}catch(r){if(r instanceof _&&r.isQuotaExceeded()){await Be(s);return}throw r}if(console.log(`Session created: ${o.label}`),console.log(` Reconnect with: catty connect ${o.label}`),e.upload!==!1){console.log("Uploading workspace...");let r=q(o.connect_url);await J(r,o.connect_token,o.headers["fly-force-instance-id"]),console.log("Workspace uploaded.")}console.log(`Connecting to ${o.connect_url}...`),await R({connectURL:o.connect_url,connectToken:o.connect_token,headers:o.headers,syncBack:e.syncBack!==!1})});async function Be(e){console.error(""),console.error("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"),console.error(" Free tier quota exceeded (1M tokens/month)"),console.error(" Upgrade to Pro for unlimited usage."),console.error("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"),console.error("");try{let t=await e.createCheckoutSession();console.error("Opening upgrade page in your browser..."),await Ve(t)}catch(t){console.error(`Failed to create checkout session: ${t}`),console.error("Please visit https://catty.dev to upgrade.")}}import{Command as Fe}from"commander";var Z=new Fe("connect").description("Reconnect to an existing session").argument("<label>","Session label (e.g., brave-tiger-1234)").option("--sync-back","Sync remote file changes back to local",!0).option("--no-sync-back","Disable sync-back").action(async function(e){let t=this.opts(),s=p(this.optsWithGlobals().api);y()||(console.error("Not logged in. Please run 'catty login' first."),process.exit(1));let n=new d(s);console.log(`Looking up session ${e}...`);let o=await n.getSession(e,!0);if(o.status==="stopped")throw new Error(`Session ${o.label} is stopped`);if(o.machine_state&&o.machine_state!=="started")throw new Error(`Machine is not running (state: ${o.machine_state})`);console.log(`Reconnecting to ${o.label}...`),await R({connectURL:o.connect_url,connectToken:o.connect_token,headers:{"fly-force-instance-id":o.machine_id},syncBack:t.syncBack})});import{Command as We}from"commander";var X=new We("list").aliases(["ls"]).description("List all sessions").action(async function(){let e=p(this.optsWithGlobals().api),s=await new d(e).listSessions();if(s.length===0){console.log("No sessions found");return}console.log("LABEL STATUS REGION CREATED");for(let o of s){let r=ze(new Date(o.created_at)),i=[o.label.padEnd(22),o.status.padEnd(9),o.region.padEnd(7),r].join(" ");console.log(i)}});function ze(e){let t=Math.floor((Date.now()-e.getTime())/1e3);if(t<60)return`${t}s ago`;let s=Math.floor(t/60);if(s<60)return`${s}m ago`;let n=Math.floor(s/60);return n<24?`${n}h ago`:`${Math.floor(n/24)}d ago`}import{Command as Ge}from"commander";var H=new Ge("stop").description("Stop a session").argument("<label>","Session ID or label").option("--delete","Delete the machine after stopping",!1).action(async function(e){let t=this.opts(),s=p(this.optsWithGlobals().api);await new d(s).stopSession(e,t.delete),t.delete?console.log(`Session ${e} stopped and deleted`):console.log(`Session ${e} stopped`)});import{Command as je}from"commander";var Q=new je("stop-all-sessions-dangerously").description("Stop and delete ALL sessions").option("--yes-i-mean-it","Confirm you want to stop all sessions",!1).action(async function(){let e=this.opts(),t=p(this.optsWithGlobals().api);if(!e.yesIMeanIt)throw new Error("Must pass --yes-i-mean-it to confirm");let s=new d(t),n=await s.listSessions();if(n.length===0){console.log("No sessions to stop");return}console.log(`Stopping ${n.length} sessions...`);for(let o of n){process.stdout.write(` Stopping ${o.session_id}... `);try{await s.stopSession(o.session_id,!0),console.log("done")}catch(r){console.log(`ERROR: ${r}`)}}});import{Command as Ye}from"commander";import Je from"open";var ee=new Ye("login").description("Log in to Catty").action(async function(){let e=p(this.optsWithGlobals().api);if(y()){let r=h();console.log(`Already logged in as ${r?.email}`),console.log("Run 'catty logout' to log out first");return}console.log("Starting login...");let t=await fetch(`${e}/v1/auth/device`,{method:"POST",headers:{"Content-Type":"application/json"},body:"{}"});if(!t.ok)throw new Error(`Failed to start auth: ${t.statusText}`);let s=await t.json();console.log(`
21
+ `)},2e3)),t.makeRaw(),t.enableBracketedPaste();let{cols:l,rows:c}=t.getSize();n.send(J(l,c)),t.onResize(oe),process.stdin.on("data",se)}),n.on("message",(l,c)=>{if(te=Date.now(),c)process.stdout.write(l);else try{let p=fe(l.toString());rt(p)}catch{}});function rt(l){switch(l.type){case"exit":{let c=l;i=c.code,e.onExit?.(c.code),process.stderr.write(`\r
22
+ Process exited with code ${c.code}\r
23
+ `),C(),n.close(),w({type:"exit",code:c.code});break}case"error":{let c=l;process.stderr.write(`\r
24
+ Error: ${c.message}\r
25
+ `);break}case"ping":n.send(ge());break;case"file_change":be(l);break;case"sync_back_ack":r=!0;break}}n.on("close",(l,c)=>{if(!a&&(C(),!m))if(l===1008)process.stderr.write(`\r
26
+ \x1B[33m\u26A0 Connection replaced by another client\x1B[0m\r
27
+ `),w({type:"replaced"});else{let p=c?.toString()||`code ${l}`;process.stderr.write(`\r
28
+ \x1B[31m\u2717 Connection lost: ${p}\x1B[0m\r
29
+ `),w({type:"disconnected",reason:p})}}),n.on("error",l=>{a||(C(),!m&&(process.stderr.write(`\r
30
+ \x1B[31m\u2717 Connection error: ${l.message}\x1B[0m\r
31
+ `),w({type:"disconnected",reason:l.message})))}),process.on("exit",()=>{C(),n.readyState===L.OPEN&&n.close()})})}import Ot from"archiver";import Ut from"ignore";import{readFileSync as Lt,readdirSync as Ft,statSync as Bt}from"fs";import{join as Pe,relative as Vt}from"path";var Gt=[".git",".git/**","node_modules","node_modules/**","__pycache__","__pycache__/**",".venv",".venv/**","venv","venv/**",".env","*.pyc",".DS_Store","*.log"];async function jt(e){let t=Ut().add(Gt);try{let n=Lt(Pe(e,".gitignore"),"utf-8");t.add(n)}catch{}return new Promise((n,o)=>{let s=[],r=Ot("zip",{zlib:{level:9}});r.on("data",i=>s.push(i)),r.on("end",()=>n(Buffer.concat(s))),r.on("error",o),Me(e,e,t,r),r.finalize()})}function Me(e,t,n,o){let s;try{s=Ft(t)}catch{return}for(let r of s){let i=Pe(t,r),a=Vt(e,i);if(n.ignores(a))continue;let _;try{_=Bt(i)}catch{continue}if(_.isDirectory()){if(n.ignores(a+"/"))continue;Me(e,i,n,o)}else _.isFile()&&o.file(i,{name:a})}}async function $e(e,t,n){let o=process.cwd(),s=await jt(o);if(s.length>104857600)throw new Error(`Workspace too large (${s.length} bytes, max ${104857600})`);let r=await fetch(e,{method:"POST",headers:{Authorization:`Bearer ${t}`,"Content-Type":"application/zip","fly-force-instance-id":n},body:s});if(!r.ok){let i=await r.text();throw new Error(`Upload failed: ${r.status} - ${i}`)}}function Ne(e){return e.replace("wss://","https://").replace("ws://","http://").replace("/connect","/upload")}var M=5,De=2e3,Oe=new zt("new").description("Start a new remote agent session").option("--agent <name>","Agent to use: claude or codex","claude").option("--no-upload","Don't upload current directory").option("--no-sync-back","Disable sync-back").option("--no-auto-reconnect","Disable automatic reconnection on disconnect").option("--enable-prompts","Enable permission prompts (by default, all permissions are auto-approved)",!1).action(async function(){let e=this.opts(),t=f(this.optsWithGlobals().api),n=e.autoReconnect!==!1;E()||(console.error("Not logged in. Please run 'catty login' first."),process.exit(1));let o=new g(t);console.log("Creating session...");let s;switch(e.agent){case"claude":e.enablePrompts?s=["claude-wrapper"]:s=["claude-wrapper","--dangerously-skip-permissions"];break;case"codex":s=["codex"];break;default:console.error(`Unknown agent: ${e.agent} (must be 'claude' or 'codex')`),process.exit(1)}let r;try{r=await o.createSession({agent:e.agent,cmd:s,region:"iad",ttl_sec:7200})}catch(a){if(a instanceof I&&a.isQuotaExceeded()){await Yt(o);return}throw a}if(console.log(`Session created: ${r.label}`),console.log(` Reconnect with: catty connect ${r.label}`),e.upload!==!1){console.log("Uploading workspace...");let a=Ne(r.connect_url);await $e(a,r.connect_token,r.headers["fly-force-instance-id"]),console.log("Workspace uploaded.")}console.log(`Connecting to ${r.connect_url}...`);let i=0;for(;;)try{let a=await B({connectURL:r.connect_url,connectToken:r.connect_token,headers:r.headers,syncBack:e.syncBack!==!1});if(a.type==="exit")process.exit(a.code);else if(a.type==="interrupted")process.exit(130);else if(a.type==="replaced")console.log("Session taken over by another client."),process.exit(0);else if(a.type==="disconnected"){n||(console.error(`Disconnected: ${a.reason}`),process.exit(1)),i++,i>M&&(console.error(`\x1B[31m\u2717 Failed to reconnect after ${M} attempts\x1B[0m`),console.error(`Run 'catty connect ${r.label}' to try again manually.`),process.exit(1)),console.log(`\x1B[33m\u27F3 Reconnecting (${i}/${M})...\x1B[0m`),await k(De);try{r=await o.getSession(r.label,!0),r.status==="stopped"&&(console.error("\x1B[31m\u2717 Session has stopped\x1B[0m"),process.exit(1))}catch{}}}catch(a){if(i>0&&n)i++,i>M&&(console.error(`\x1B[31m\u2717 Failed to reconnect after ${M} attempts\x1B[0m`),process.exit(1)),console.error(`\x1B[33m\u27F3 Reconnect failed, retrying (${i}/${M})...\x1B[0m`),await k(De);else throw a}});async function Yt(e){console.error(""),console.error("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"),console.error(" Free tier quota exceeded (1M tokens/month)"),console.error(" Upgrade to Pro for unlimited usage."),console.error("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"),console.error("");try{let t=await e.createCheckoutSession();console.error("Opening upgrade page in your browser..."),await Wt(t)}catch(t){console.error(`Failed to create checkout session: ${t}`),console.error("Please visit https://catty.dev to upgrade.")}}import{Command as Jt}from"commander";var $=5,Ue=2e3,Le=new Jt("connect").description("Reconnect to an existing session").argument("<label>","Session label (e.g., brave-tiger-1234)").option("--sync-back","Sync remote file changes back to local",!0).option("--no-sync-back","Disable sync-back").option("--no-auto-reconnect","Disable automatic reconnection on disconnect").action(async function(e){let t=this.opts(),n=f(this.optsWithGlobals().api),o=t.autoReconnect!==!1;E()||(console.error("Not logged in. Please run 'catty login' first."),process.exit(1));let s=new g(n),r=0;for(;;)try{console.log(`Looking up session ${e}...`);let i=await s.getSession(e,!0);if(i.status==="stopped")throw new Error(`Session ${i.label} is stopped`);if(i.machine_state&&i.machine_state!=="started")throw new Error(`Machine is not running (state: ${i.machine_state})`);r>0?console.log(`\x1B[32m\u2713 Reconnected to ${i.label}\x1B[0m`):console.log(`Connecting to ${i.label}...`);let a=await B({connectURL:i.connect_url,connectToken:i.connect_token,headers:{"fly-force-instance-id":i.machine_id},syncBack:t.syncBack});a.type==="exit"?process.exit(a.code):a.type==="interrupted"?process.exit(130):a.type==="replaced"?(console.log("Session taken over by another client."),process.exit(0)):a.type==="disconnected"&&(o||(console.error(`Disconnected: ${a.reason}`),process.exit(1)),r++,r>$&&(console.error(`\x1B[31m\u2717 Failed to reconnect after ${$} attempts\x1B[0m`),console.error(`Run 'catty connect ${e}' to try again manually.`),process.exit(1)),console.log(`\x1B[33m\u27F3 Reconnecting (${r}/${$})...\x1B[0m`),await k(Ue))}catch(i){if(r>0&&o)r++,r>$&&(console.error(`\x1B[31m\u2717 Failed to reconnect after ${$} attempts\x1B[0m`),process.exit(1)),console.error(`\x1B[33m\u27F3 Reconnect failed, retrying (${r}/${$})...\x1B[0m`),await k(Ue);else throw i}});import{Command as qt}from"commander";var Fe=new qt("list").aliases(["ls"]).description("List all sessions").action(async function(){let e=f(this.optsWithGlobals().api),n=await new g(e).listSessions();if(n.length===0){console.log("No sessions found");return}console.log("LABEL STATUS REGION CREATED");for(let s of n){let r=Kt(new Date(s.created_at)),i=[s.label.padEnd(22),s.status.padEnd(9),s.region.padEnd(7),r].join(" ");console.log(i)}});function Kt(e){let t=Math.floor((Date.now()-e.getTime())/1e3);if(t<60)return`${t}s ago`;let n=Math.floor(t/60);if(n<60)return`${n}m ago`;let o=Math.floor(n/60);return o<24?`${o}h ago`:`${Math.floor(o/24)}d ago`}import{Command as Zt}from"commander";var Be=new Zt("stop").description("Stop a session").argument("<label>","Session ID or label").option("--delete","Delete the machine after stopping",!1).action(async function(e){let t=this.opts(),n=f(this.optsWithGlobals().api);await new g(n).stopSession(e,t.delete),t.delete?console.log(`Session ${e} stopped and deleted`):console.log(`Session ${e} stopped`)});import{Command as Ht}from"commander";var Ve=new Ht("stop-all-sessions-dangerously").description("Stop and delete ALL sessions").option("--yes-i-mean-it","Confirm you want to stop all sessions",!1).action(async function(){let e=this.opts(),t=f(this.optsWithGlobals().api);if(!e.yesIMeanIt)throw new Error("Must pass --yes-i-mean-it to confirm");let n=new g(t),o=await n.listSessions();if(o.length===0){console.log("No sessions to stop");return}console.log(`Stopping ${o.length} sessions...`);for(let s of o){process.stdout.write(` Stopping ${s.session_id}... `);try{await n.stopSession(s.session_id,!0),console.log("done")}catch(r){console.log(`ERROR: ${r}`)}}});import{Command as Xt}from"commander";import Qt from"open";var Ge=new Xt("login").description("Log in to Catty").action(async function(){let e=f(this.optsWithGlobals().api);if(E()){let r=b();console.log(`Already logged in as ${r?.email}`),console.log("Run 'catty logout' to log out first");return}console.log("Starting login...");let t=await fetch(`${e}/v1/auth/device`,{method:"POST",headers:{"Content-Type":"application/json"},body:"{}"});if(!t.ok)throw new Error(`Failed to start auth: ${t.statusText}`);let n=await t.json();console.log(`
9
32
  Your confirmation code:
10
- `),console.log(` ${s.user_code}
11
- `),console.log(`Opening ${s.verification_uri_complete}
12
- `),await Je(s.verification_uri_complete),console.log("Waiting for authentication...");let n=(s.interval||5)*1e3,o=Date.now()+s.expires_in*1e3;for(;Date.now()<o;){await T(n);let i=await(await fetch(`${e}/v1/auth/device/token`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({device_code:s.device_code})})).json();if(!i.pending){if(i.error)throw new Error(i.error);if(i.access_token){S({access_token:i.access_token,refresh_token:i.refresh_token,user_id:i.user?.id||"",email:i.user?.email||"",expires_at:i.expires_in?new Date(Date.now()+(i.expires_in-30)*1e3).toISOString():void 0}),console.log(`
13
- Logged in as ${i.user?.email}`),console.log("You can now run 'catty new' to start a session");return}}}throw new Error("Authentication timed out")});import{Command as qe}from"commander";var te=new qe("logout").description("Log out of Catty").action(async()=>{if(!y()){console.log("Not logged in");return}let t=h()?.email||"";D(),console.log(t?`Logged out from ${t}`:"Logged out")});import{Command as Ke}from"commander";var oe=new Ke("version").description("Print the version number").action(()=>{console.log("0.3.11")});import{Command as at}from"commander";import{readFileSync as Ze,writeFileSync as Xe,existsSync as ne,mkdirSync as He}from"fs";import{join as re}from"path";import{homedir as ie}from"os";import{createInterface as Qe}from"readline";import{spawn as et}from"child_process";function se(e){(process.env.DEBUG||process.env.CATTY_DEBUG)&&console.log(`\x1B[2m[DEBUG] ${e}\x1B[0m`)}var tt=900*1e3,ot=2880*60*1e3,st="https://registry.npmjs.org/@diggerhq/catty/latest";function ae(){return re(ie(),w,"version-cache.json")}function ce(){try{let e=ae();if(!ne(e))return null;let t=Ze(e,"utf-8");return JSON.parse(t)}catch{return null}}function le(e,t){try{let s=ae(),n=re(ie(),w);ne(n)||He(n,{recursive:!0});let o=ce(),r={latestVersion:e,lastChecked:Date.now(),declinedVersion:t?e:o?.declinedVersion,declinedAt:t?Date.now():o?.declinedAt};Xe(s,JSON.stringify(r,null,2))}catch{}}async function nt(){try{let e=await fetch(st,{signal:AbortSignal.timeout(5e3)});return e.ok&&(await e.json()).version||null}catch{return null}}function rt(e,t){let s=e.split(".").map(Number),n=t.split(".").map(Number);for(let o=0;o<3;o++){let r=s[o]||0,i=n[o]||0;if(i>r)return!0;if(i<r)return!1}return!1}async function b(e){let t="0.3.11";if(t==="dev")return{updateAvailable:!1,currentVersion:t,latestVersion:null,shouldPrompt:!1};let s=ce(),n=Date.now(),o=null;if(!e?.bypassCache&&s&&n-s.lastChecked<tt?o=s.latestVersion:(o=await nt(),o&&le(o)),!o)return{updateAvailable:!1,currentVersion:t,latestVersion:null,shouldPrompt:!1};let r=rt(t,o),i=r;return r&&s?.declinedVersion===o&&s.declinedAt&&(i=n-s.declinedAt>=ot),{updateAvailable:r,currentVersion:t,latestVersion:o,shouldPrompt:i}}function pe(e,t){console.log(""),console.log(`\x1B[33mUpdate available:\x1B[0m \x1B[2m${e}\x1B[0m \u2192 \x1B[32m${t}\x1B[0m`)}async function de(){let e=Qe({input:process.stdin,output:process.stdout});return new Promise(t=>{e.question("Would you like to update? (Y/n): ",s=>{e.close();let n=s.trim().toLowerCase();t(n===""||n==="y"||n==="yes")})})}function ue(e){le(e,!0)}function it(){let e=process.env.npm_config_user_agent||"";return e.includes("yarn")?"yarn":e.includes("pnpm")?"pnpm":e.includes("bun")?"bun":"npm"}async function x(e,t){console.log(`
33
+ `),console.log(` ${n.user_code}
34
+ `),console.log(`Opening ${n.verification_uri_complete}
35
+ `),await Qt(n.verification_uri_complete),console.log("Waiting for authentication...");let o=(n.interval||5)*1e3,s=Date.now()+n.expires_in*1e3;for(;Date.now()<s;){await k(o);let i=await(await fetch(`${e}/v1/auth/device/token`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({device_code:n.device_code})})).json();if(!i.pending){if(i.error)throw new Error(i.error);if(i.access_token){O({access_token:i.access_token,refresh_token:i.refresh_token,user_id:i.user?.id||"",email:i.user?.email||"",expires_at:i.expires_in?new Date(Date.now()+(i.expires_in-30)*1e3).toISOString():void 0}),console.log(`
36
+ Logged in as ${i.user?.email}`),console.log("You can now run 'catty new' to start a session");return}}}throw new Error("Authentication timed out")});import{Command as en}from"commander";var je=new en("logout").description("Log out of Catty").action(async()=>{if(!E()){console.log("Not logged in");return}let t=b()?.email||"";pe(),console.log(t?`Logged out from ${t}`:"Logged out")});import{Command as tn}from"commander";var ze=new tn("version").description("Print the version number").action(()=>{console.log("0.3.13")});import{Command as fn}from"commander";import{readFileSync as nn,writeFileSync as on,existsSync as Ye,mkdirSync as sn}from"fs";import{join as Je}from"path";import{homedir as qe}from"os";import{createInterface as rn}from"readline";import{spawn as an}from"child_process";function We(e){(process.env.DEBUG||process.env.CATTY_DEBUG)&&console.log(`\x1B[2m[DEBUG] ${e}\x1B[0m`)}var cn=900*1e3,ln=2880*60*1e3,pn="https://registry.npmjs.org/@diggerhq/catty/latest";function Ke(){return Je(qe(),D,"version-cache.json")}function Ze(){try{let e=Ke();if(!Ye(e))return null;let t=nn(e,"utf-8");return JSON.parse(t)}catch{return null}}function He(e,t){try{let n=Ke(),o=Je(qe(),D);Ye(o)||sn(o,{recursive:!0});let s=Ze(),r={latestVersion:e,lastChecked:Date.now(),declinedVersion:t?e:s?.declinedVersion,declinedAt:t?Date.now():s?.declinedAt};on(n,JSON.stringify(r,null,2))}catch{}}async function dn(){try{let e=await fetch(pn,{signal:AbortSignal.timeout(5e3)});return e.ok&&(await e.json()).version||null}catch{return null}}function un(e,t){let n=e.split(".").map(Number),o=t.split(".").map(Number);for(let s=0;s<3;s++){let r=n[s]||0,i=o[s]||0;if(i>r)return!0;if(i<r)return!1}return!1}async function V(e){let t="0.3.13";if(t==="dev")return{updateAvailable:!1,currentVersion:t,latestVersion:null,shouldPrompt:!1};let n=Ze(),o=Date.now(),s=null;if(!e?.bypassCache&&n&&o-n.lastChecked<cn?s=n.latestVersion:(s=await dn(),s&&He(s)),!s)return{updateAvailable:!1,currentVersion:t,latestVersion:null,shouldPrompt:!1};let r=un(t,s),i=r;return r&&n?.declinedVersion===s&&n.declinedAt&&(i=o-n.declinedAt>=ln),{updateAvailable:r,currentVersion:t,latestVersion:s,shouldPrompt:i}}function Xe(e,t){console.log(""),console.log(`\x1B[33mUpdate available:\x1B[0m \x1B[2m${e}\x1B[0m \u2192 \x1B[32m${t}\x1B[0m`)}async function Qe(){let e=rn({input:process.stdin,output:process.stdout});return new Promise(t=>{e.question("Would you like to update? (Y/n): ",n=>{e.close();let o=n.trim().toLowerCase();t(o===""||o==="y"||o==="yes")})})}function et(e){He(e,!0)}function mn(){let e=process.env.npm_config_user_agent||"";return e.includes("yarn")?"yarn":e.includes("pnpm")?"pnpm":e.includes("bun")?"bun":"npm"}async function G(e,t){console.log(`
14
37
  Updating from ${e} to ${t}...
15
- `);let s=it(),n=`@diggerhq/catty@${t}`,o,r;switch(s){case"yarn":o="yarn",r=["global","add",n];break;case"pnpm":o="pnpm",r=["add","-g",n];break;case"bun":o="bun",r=["install","-g",n];break;default:o="npm",r=["install","-g",n]}return se(`Package manager: ${s}`),se(`Command: ${o} ${r.join(" ")}`),new Promise((i,c)=>{let m=et(o,r,{stdio:"inherit",shell:process.platform==="win32"});m.on("close",f=>{f===0?(console.log(`
38
+ `);let n=mn(),o=`@diggerhq/catty@${t}`,s,r;switch(n){case"yarn":s="yarn",r=["global","add",o];break;case"pnpm":s="pnpm",r=["add","-g",o];break;case"bun":s="bun",r=["install","-g",o];break;default:s="npm",r=["install","-g",o]}return We(`Package manager: ${n}`),We(`Command: ${s} ${r.join(" ")}`),new Promise((i,a)=>{let _=an(s,r,{stdio:"inherit",shell:process.platform==="win32"});_.on("close",m=>{m===0?(console.log(`
16
39
  \x1B[32m\u2713\x1B[0m Successfully updated to version ${t}`),i()):(console.error(`
17
- \x1B[31m\u2717\x1B[0m Update failed with exit code ${f}`),c(new Error(`Update process exited with code ${f}`)))}),m.on("error",f=>{console.error(`
18
- \x1B[31m\u2717\x1B[0m Failed to run update command: ${f.message}`),c(f)})})}var me=new at("update").description("Update catty to the latest version").action(async()=>{try{let e="0.3.11";if(e==="dev"){console.log("Cannot update in development mode.");return}console.log("Checking for updates...");let{updateAvailable:t,latestVersion:s}=await b({bypassCache:!0});if(!t||!s){console.log(`You are already using the latest version (${e}).`);return}await x(e,s)}catch(e){e instanceof Error&&console.error(`Error: ${e.message}`),process.exit(1)}});var ct="0.3.11";u.name("catty").description("Catty - Remote AI agent sessions").option("--api <url>","API server address").version(ct);u.addCommand(K);u.addCommand(Z);u.addCommand(X);u.addCommand(H);u.addCommand(Q,{hidden:!0});u.addCommand(ee);u.addCommand(te);u.addCommand(oe);u.addCommand(me);u.exitOverride();async function lt(){try{await u.parseAsync(process.argv);let e=process.argv[2];if(e&&!["version","update","-v","--version","-h","--help","help"].includes(e)){let{updateAvailable:s,currentVersion:n,latestVersion:o,shouldPrompt:r}=await b();if(s&&o&&r)if(pe(n,o),await de())try{await x(n,o)}catch{}else ue(o)}}catch(e){e instanceof Error?(e.name==="CommanderError"&&["commander.helpDisplayed","commander.version"].includes(e.code||"")&&process.exit(0),console.error(`Error: ${e.message}`)):console.error(`Error: ${e}`),process.exit(1)}}lt();
40
+ \x1B[31m\u2717\x1B[0m Update failed with exit code ${m}`),a(new Error(`Update process exited with code ${m}`)))}),_.on("error",m=>{console.error(`
41
+ \x1B[31m\u2717\x1B[0m Failed to run update command: ${m.message}`),a(m)})})}var tt=new fn("update").description("Update catty to the latest version").action(async()=>{try{let e="0.3.13";if(e==="dev"){console.log("Cannot update in development mode.");return}console.log("Checking for updates...");let{updateAvailable:t,latestVersion:n}=await V({bypassCache:!0});if(!t||!n){console.log(`You are already using the latest version (${e}).`);return}await G(e,n)}catch(e){e instanceof Error&&console.error(`Error: ${e.message}`),process.exit(1)}});var gn="0.3.13";h.name("catty").description("Catty - Remote AI agent sessions").option("--api <url>","API server address").version(gn);h.addCommand(Oe);h.addCommand(Le);h.addCommand(Fe);h.addCommand(Be);h.addCommand(Ve,{hidden:!0});h.addCommand(Ge);h.addCommand(je);h.addCommand(ze);h.addCommand(tt);h.exitOverride();async function hn(){try{await h.parseAsync(process.argv);let e=process.argv[2];if(e&&!["version","update","-v","--version","-h","--help","help"].includes(e)){let{updateAvailable:n,currentVersion:o,latestVersion:s,shouldPrompt:r}=await V();if(n&&s&&r)if(Xe(o,s),await Qe())try{await G(o,s)}catch{}else et(s)}}catch(e){e instanceof Error?(e.name==="CommanderError"&&["commander.helpDisplayed","commander.version"].includes(e.code||"")&&process.exit(0),console.error(`Error: ${e.message}`)):console.error(`Error: ${e}`),process.exit(1)}}hn();
19
42
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/commands/new.ts","../src/lib/config.ts","../src/lib/auth.ts","../src/lib/api-client.ts","../src/lib/websocket.ts","../src/lib/terminal.ts","../src/protocol/messages.ts","../src/lib/syncback.ts","../src/lib/workspace.ts","../src/commands/connect.ts","../src/commands/list.ts","../src/commands/stop.ts","../src/commands/stopall.ts","../src/commands/login.ts","../src/commands/logout.ts","../src/commands/version.ts","../src/commands/update.ts","../src/lib/version-checker.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { program } from 'commander';\nimport { newCommand } from './commands/new.js';\nimport { connectCommand } from './commands/connect.js';\nimport { listCommand } from './commands/list.js';\nimport { stopCommand } from './commands/stop.js';\nimport { stopAllCommand } from './commands/stopall.js';\nimport { loginCommand } from './commands/login.js';\nimport { logoutCommand } from './commands/logout.js';\nimport { versionCommand } from './commands/version.js';\nimport { updateCommand } from './commands/update.js';\nimport {\n checkForUpdate,\n printUpdateAvailable,\n promptForUpdate,\n runUpdate,\n recordDeclinedUpdate,\n} from './lib/version-checker.js';\n\n// VERSION is replaced at build time by tsup\ndeclare const __VERSION__: string;\nconst version = typeof __VERSION__ !== 'undefined' ? __VERSION__ : 'dev';\n\nprogram\n .name('catty')\n .description('Catty - Remote AI agent sessions')\n .option('--api <url>', 'API server address')\n .version(version);\n\nprogram.addCommand(newCommand);\nprogram.addCommand(connectCommand);\nprogram.addCommand(listCommand);\nprogram.addCommand(stopCommand);\nprogram.addCommand(stopAllCommand, { hidden: true });\nprogram.addCommand(loginCommand);\nprogram.addCommand(logoutCommand);\nprogram.addCommand(versionCommand);\nprogram.addCommand(updateCommand);\n\n// Handle errors gracefully\nprogram.exitOverride();\n\nasync function main() {\n try {\n await program.parseAsync(process.argv);\n\n // Check for updates after command execution\n // Skip if running version, update, or help commands\n const command = process.argv[2];\n const skipUpdateCheck = [\n 'version',\n 'update',\n '-v',\n '--version',\n '-h',\n '--help',\n 'help',\n ];\n if (command && !skipUpdateCheck.includes(command)) {\n const { updateAvailable, currentVersion, latestVersion, shouldPrompt } =\n await checkForUpdate();\n if (updateAvailable && latestVersion && shouldPrompt) {\n printUpdateAvailable(currentVersion, latestVersion);\n const shouldUpdate = await promptForUpdate();\n if (shouldUpdate) {\n try {\n await runUpdate(currentVersion, latestVersion);\n } catch (err) {\n // Update failed, but don't exit the process\n // Error message already printed by runUpdate\n }\n } else {\n // User declined - record it so we don't ask again for 2 days\n recordDeclinedUpdate(latestVersion);\n }\n }\n }\n } catch (err: unknown) {\n if (err instanceof Error) {\n // Commander throws for help/version, ignore those\n if (\n err.name === 'CommanderError' &&\n ['commander.helpDisplayed', 'commander.version'].includes(\n (err as { code?: string }).code || ''\n )\n ) {\n process.exit(0);\n }\n\n console.error(`Error: ${err.message}`);\n } else {\n console.error(`Error: ${err}`);\n }\n process.exit(1);\n }\n}\n\nmain();\n","import { Command } from 'commander';\nimport open from 'open';\nimport { getAPIAddr } from '../lib/config.js';\nimport { isLoggedIn } from '../lib/auth.js';\nimport { APIClient, APIError } from '../lib/api-client.js';\nimport { connectToSession } from '../lib/websocket.js';\nimport { uploadWorkspace, buildUploadURL } from '../lib/workspace.js';\n\nexport const newCommand = new Command('new')\n .description('Start a new remote agent session')\n .option('--agent <name>', 'Agent to use: claude or codex', 'claude')\n .option('--no-upload', \"Don't upload current directory\")\n .option('--no-sync-back', 'Disable sync-back')\n .option(\n '--enable-prompts',\n 'Enable permission prompts (by default, all permissions are auto-approved)',\n false\n )\n .action(async function (this: Command) {\n const opts = this.opts();\n const apiAddr = getAPIAddr(this.optsWithGlobals().api);\n\n if (!isLoggedIn()) {\n console.error(\"Not logged in. Please run 'catty login' first.\");\n process.exit(1);\n }\n\n const client = new APIClient(apiAddr);\n\n console.log('Creating session...');\n\n // Determine command arguments based on agent and prompts setting\n let cmdArgs: string[];\n switch (opts.agent) {\n case 'claude':\n if (opts.enablePrompts) {\n // User wants prompts - don't skip permissions\n cmdArgs = ['claude-wrapper'];\n } else {\n // Default: auto-approve all permissions\n cmdArgs = ['claude-wrapper', '--dangerously-skip-permissions'];\n }\n break;\n case 'codex':\n cmdArgs = ['codex'];\n break;\n default:\n console.error(\n `Unknown agent: ${opts.agent} (must be 'claude' or 'codex')`\n );\n process.exit(1);\n }\n\n let session;\n try {\n session = await client.createSession({\n agent: opts.agent,\n cmd: cmdArgs,\n region: 'iad',\n ttl_sec: 7200,\n });\n } catch (err) {\n if (err instanceof APIError && err.isQuotaExceeded()) {\n await handleQuotaExceeded(client);\n return;\n }\n throw err;\n }\n\n console.log(`Session created: ${session.label}`);\n console.log(` Reconnect with: catty connect ${session.label}`);\n\n // Upload workspace\n if (opts.upload !== false) {\n console.log('Uploading workspace...');\n const uploadURL = buildUploadURL(session.connect_url);\n\n await uploadWorkspace(\n uploadURL,\n session.connect_token,\n session.headers['fly-force-instance-id']\n );\n console.log('Workspace uploaded.');\n }\n\n console.log(`Connecting to ${session.connect_url}...`);\n\n await connectToSession({\n connectURL: session.connect_url,\n connectToken: session.connect_token,\n headers: session.headers,\n syncBack: opts.syncBack !== false,\n });\n });\n\nasync function handleQuotaExceeded(client: APIClient): Promise<void> {\n console.error('');\n console.error('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');\n console.error(' Free tier quota exceeded (1M tokens/month)');\n console.error(' Upgrade to Pro for unlimited usage.');\n console.error('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');\n console.error('');\n\n try {\n const checkoutURL = await client.createCheckoutSession();\n console.error('Opening upgrade page in your browser...');\n await open(checkoutURL);\n } catch (err) {\n console.error(`Failed to create checkout session: ${err}`);\n console.error('Please visit https://catty.dev to upgrade.');\n }\n}\n","export const DEFAULT_API_ADDR = 'https://api.catty.dev';\nexport const CREDENTIALS_DIR = '.catty';\nexport const CREDENTIALS_FILE = 'credentials.json';\nexport const MAX_UPLOAD_SIZE = 100 * 1024 * 1024; // 100MB\n\n// Timeouts\nexport const API_TIMEOUT_MS = 120_000; // 120 seconds for API requests (machine creation can be slow)\nexport const WS_WRITE_TIMEOUT_MS = 10_000; // 10 seconds for WebSocket writes\nexport const WS_READ_TIMEOUT_MS = 60_000; // 60 seconds (must be > 25s ping interval)\nexport const SYNC_BACK_ACK_TIMEOUT_MS = 2_000; // Warn if no sync-back ack after 2s\n\n// WebSocket close codes\nexport const WS_POLICY_VIOLATION = 1008; // Connection replaced by new one\n\n// Helper to get API address (checks flag, env var, or default)\nexport function getAPIAddr(cliOption?: string): string {\n if (cliOption) return cliOption;\n if (process.env.CATTY_API_ADDR) return process.env.CATTY_API_ADDR;\n return DEFAULT_API_ADDR;\n}\n\n// Sleep helper for polling\nexport function sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","import { homedir } from 'os';\nimport { join } from 'path';\nimport {\n readFileSync,\n writeFileSync,\n mkdirSync,\n unlinkSync,\n existsSync,\n} from 'fs';\nimport { CREDENTIALS_DIR, CREDENTIALS_FILE } from './config.js';\nimport type { Credentials } from '../types/index.js';\n\nexport function getCredentialsDir(): string {\n return join(homedir(), CREDENTIALS_DIR);\n}\n\nexport function getCredentialsPath(): string {\n return join(getCredentialsDir(), CREDENTIALS_FILE);\n}\n\nexport function loadCredentials(): Credentials | null {\n const path = getCredentialsPath();\n try {\n const content = readFileSync(path, 'utf-8');\n return JSON.parse(content) as Credentials;\n } catch {\n return null;\n }\n}\n\nexport function saveCredentials(creds: Credentials): void {\n const dir = getCredentialsDir();\n const path = getCredentialsPath();\n\n // Create directory with 0700 permissions\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n\n // Write file with 0600 permissions\n writeFileSync(path, JSON.stringify(creds, null, 2), { mode: 0o600 });\n}\n\nexport function deleteCredentials(): void {\n const path = getCredentialsPath();\n if (existsSync(path)) {\n unlinkSync(path);\n }\n}\n\nexport function isLoggedIn(): boolean {\n const creds = loadCredentials();\n if (!creds) return false;\n\n // Check if token exists\n if (!creds.access_token) return false;\n\n // If there's an expiry and no refresh token, check if expired\n if (creds.expires_at && !creds.refresh_token) {\n const expiresAt = new Date(creds.expires_at);\n if (expiresAt <= new Date()) {\n return false;\n }\n }\n\n // If we have a refresh token, we can refresh even if access token expired\n return true;\n}\n\nexport function getAccessToken(): string | null {\n const creds = loadCredentials();\n return creds?.access_token || null;\n}\n\nexport function getRefreshToken(): string | null {\n const creds = loadCredentials();\n return creds?.refresh_token || null;\n}\n","import {\n DEFAULT_API_ADDR,\n API_TIMEOUT_MS,\n} from './config.js';\nimport {\n getAccessToken,\n getRefreshToken,\n loadCredentials,\n saveCredentials,\n} from './auth.js';\nimport type {\n CreateSessionRequest,\n CreateSessionResponse,\n SessionInfo,\n APIErrorResponse,\n} from '../types/index.js';\n\nexport class APIError extends Error {\n constructor(\n public statusCode: number,\n public errorCode: string,\n message: string,\n public upgradeURL?: string\n ) {\n super(message);\n this.name = 'APIError';\n }\n\n isQuotaExceeded(): boolean {\n return this.statusCode === 402 && this.errorCode === 'quota_exceeded';\n }\n}\n\nexport class APIClient {\n private baseURL: string;\n private authToken: string | null;\n\n constructor(baseURL?: string) {\n this.baseURL = baseURL || process.env.CATTY_API_ADDR || DEFAULT_API_ADDR;\n this.authToken = getAccessToken();\n }\n\n private async doRequest(\n method: string,\n path: string,\n body?: unknown\n ): Promise<Response> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), API_TIMEOUT_MS);\n\n try {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n\n if (this.authToken) {\n headers['Authorization'] = `Bearer ${this.authToken}`;\n }\n\n const response = await fetch(`${this.baseURL}${path}`, {\n method,\n headers,\n body: body ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n });\n\n return response;\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n private async doRequestWithRefresh(\n method: string,\n path: string,\n body?: unknown\n ): Promise<Response> {\n let response = await this.doRequest(method, path, body);\n\n if (response.status === 401) {\n const refreshed = await this.refreshAuthToken();\n if (refreshed) {\n response = await this.doRequest(method, path, body);\n }\n }\n\n return response;\n }\n\n private async refreshAuthToken(): Promise<boolean> {\n const refreshToken = getRefreshToken();\n if (!refreshToken) return false;\n\n try {\n const response = await this.doRequest('POST', '/v1/auth/refresh', {\n refresh_token: refreshToken,\n });\n\n if (!response.ok) return false;\n\n const data = await response.json();\n if (!data.access_token) return false;\n\n // Update stored credentials\n const creds = loadCredentials();\n if (creds) {\n creds.access_token = data.access_token;\n if (data.refresh_token) {\n creds.refresh_token = data.refresh_token;\n }\n if (data.expires_in) {\n creds.expires_at = new Date(\n Date.now() + (data.expires_in - 30) * 1000\n ).toISOString();\n }\n saveCredentials(creds);\n this.authToken = data.access_token;\n }\n\n return true;\n } catch {\n return false;\n }\n }\n\n private async handleResponse<T>(response: Response): Promise<T> {\n if (!response.ok) {\n let errorData: APIErrorResponse;\n try {\n errorData = await response.json();\n } catch {\n errorData = { error: response.statusText };\n }\n\n throw new APIError(\n response.status,\n errorData.code || '',\n errorData.error || response.statusText,\n errorData.upgrade_url\n );\n }\n\n return response.json() as Promise<T>;\n }\n\n async createSession(req: CreateSessionRequest): Promise<CreateSessionResponse> {\n const response = await this.doRequestWithRefresh('POST', '/v1/sessions', req);\n return this.handleResponse<CreateSessionResponse>(response);\n }\n\n async listSessions(): Promise<SessionInfo[]> {\n const response = await this.doRequestWithRefresh('GET', '/v1/sessions');\n return this.handleResponse<SessionInfo[]>(response);\n }\n\n async getSession(idOrLabel: string, live?: boolean): Promise<SessionInfo> {\n const path = live\n ? `/v1/sessions/${idOrLabel}?live=true`\n : `/v1/sessions/${idOrLabel}`;\n const response = await this.doRequestWithRefresh('GET', path);\n return this.handleResponse<SessionInfo>(response);\n }\n\n async stopSession(idOrLabel: string, del?: boolean): Promise<void> {\n const path = del\n ? `/v1/sessions/${idOrLabel}?delete=true`\n : `/v1/sessions/${idOrLabel}`;\n const response = await this.doRequestWithRefresh('DELETE', path);\n\n if (!response.ok) {\n let errorData: APIErrorResponse;\n try {\n errorData = await response.json();\n } catch {\n errorData = { error: response.statusText };\n }\n throw new APIError(\n response.status,\n errorData.code || '',\n errorData.error || response.statusText\n );\n }\n }\n\n async createCheckoutSession(): Promise<string> {\n const response = await this.doRequestWithRefresh('POST', '/v1/checkout');\n const data = await this.handleResponse<{ url: string }>(response);\n return data.url;\n }\n}\n","import WebSocket from 'ws';\nimport { Terminal } from './terminal.js';\nimport {\n SYNC_BACK_ACK_TIMEOUT_MS,\n WS_POLICY_VIOLATION,\n} from './config.js';\nimport {\n parseMessage,\n createResizeMessage,\n createPongMessage,\n createSyncBackMessage,\n type Message,\n type ExitMessage,\n type ErrorMessage,\n type FileChangeMessage,\n} from '../protocol/messages.js';\nimport { applyRemoteFileChange } from './syncback.js';\n\nexport interface WebSocketConnectOptions {\n connectURL: string;\n connectToken: string;\n headers: Record<string, string>;\n syncBack: boolean;\n onExit?: (code: number) => void;\n}\n\nexport async function connectToSession(\n opts: WebSocketConnectOptions\n): Promise<void> {\n const terminal = new Terminal();\n\n if (!terminal.isTerminal()) {\n throw new Error('stdin is not a terminal');\n }\n\n const ws = new WebSocket(opts.connectURL, {\n headers: {\n ...opts.headers,\n Authorization: `Bearer ${opts.connectToken}`,\n },\n });\n\n return new Promise((resolve, reject) => {\n let syncBackAcked = false;\n let exitCode = 0;\n\n const handleResize = () => {\n const { cols, rows } = terminal.getSize();\n if (ws.readyState === WebSocket.OPEN) {\n ws.send(createResizeMessage(cols, rows));\n }\n };\n\n const cleanup = () => {\n terminal.restore();\n terminal.offResize(handleResize);\n process.stdin.off('data', handleStdinData);\n };\n\n const handleStdinData = (data: Buffer) => {\n if (ws.readyState === WebSocket.OPEN) {\n ws.send(data); // Binary\n }\n };\n\n ws.on('open', () => {\n // Enable sync-back if requested\n if (opts.syncBack) {\n ws.send(createSyncBackMessage(true));\n\n // Warn if no ack after timeout\n setTimeout(() => {\n if (!syncBackAcked) {\n process.stderr.write(\n '\\r\\n(sync-back) No ack from executor yet — this machine may be running an older catty-exec image without sync-back.\\r\\n'\n );\n }\n }, SYNC_BACK_ACK_TIMEOUT_MS);\n }\n\n // Enter raw mode\n terminal.makeRaw();\n\n // Send initial size\n const { cols, rows } = terminal.getSize();\n ws.send(createResizeMessage(cols, rows));\n\n // Handle resize\n terminal.onResize(handleResize);\n\n // Relay stdin -> WebSocket\n process.stdin.on('data', handleStdinData);\n });\n\n // Relay WebSocket -> stdout\n ws.on('message', (data: WebSocket.RawData, isBinary: boolean) => {\n if (isBinary) {\n process.stdout.write(data as Buffer);\n } else {\n try {\n const msg = parseMessage(data.toString());\n handleControlMessage(msg);\n } catch {\n // Ignore parse errors\n }\n }\n });\n\n function handleControlMessage(msg: Message) {\n switch (msg.type) {\n case 'exit': {\n const exitMsg = msg as ExitMessage;\n exitCode = exitMsg.code;\n opts.onExit?.(exitMsg.code);\n process.stderr.write(`\\r\\nProcess exited with code ${exitMsg.code}\\r\\n`);\n cleanup();\n ws.close();\n resolve();\n break;\n }\n case 'error': {\n const errorMsg = msg as ErrorMessage;\n process.stderr.write(`\\r\\nError: ${errorMsg.message}\\r\\n`);\n break;\n }\n case 'ping':\n ws.send(createPongMessage());\n break;\n case 'file_change':\n applyRemoteFileChange(msg as FileChangeMessage);\n break;\n case 'sync_back_ack':\n syncBackAcked = true;\n break;\n }\n }\n\n ws.on('close', (code: number) => {\n cleanup();\n // Code 1008 (WS_POLICY_VIOLATION) = connection replaced by new one\n // This is a clean termination, not an error\n if (code === WS_POLICY_VIOLATION) {\n resolve();\n } else {\n resolve();\n }\n });\n\n ws.on('error', (err: Error) => {\n cleanup();\n reject(err);\n });\n\n // Handle process exit\n process.on('exit', () => {\n cleanup();\n if (ws.readyState === WebSocket.OPEN) {\n ws.close();\n }\n });\n });\n}\n","export class Terminal {\n private wasRaw = false;\n private cleanupDone = false;\n\n isTerminal(): boolean {\n return process.stdin.isTTY === true;\n }\n\n makeRaw(): void {\n if (!this.isTerminal()) return;\n if (this.wasRaw) return;\n\n process.stdin.setRawMode(true);\n process.stdin.resume();\n this.wasRaw = true;\n\n // Ensure terminal is restored on process exit\n const cleanup = () => this.restore();\n\n process.on('exit', cleanup);\n\n process.on('SIGINT', () => {\n cleanup();\n process.exit(130); // 128 + SIGINT(2)\n });\n\n process.on('SIGTERM', () => {\n cleanup();\n process.exit(143); // 128 + SIGTERM(15)\n });\n }\n\n restore(): void {\n if (this.cleanupDone) return;\n if (this.wasRaw && process.stdin.isTTY) {\n try {\n process.stdin.setRawMode(false);\n } catch {\n // Ignore errors during cleanup\n }\n this.wasRaw = false;\n }\n this.cleanupDone = true;\n }\n\n getSize(): { cols: number; rows: number } {\n return {\n cols: process.stdout.columns || 80,\n rows: process.stdout.rows || 24,\n };\n }\n\n onResize(callback: () => void): void {\n process.stdout.on('resize', callback);\n }\n\n offResize(callback: () => void): void {\n process.stdout.off('resize', callback);\n }\n}\n","export const MessageType = {\n RESIZE: 'resize',\n SIGNAL: 'signal',\n PING: 'ping',\n PONG: 'pong',\n READY: 'ready',\n EXIT: 'exit',\n ERROR: 'error',\n SYNC_BACK: 'sync_back',\n SYNC_BACK_ACK: 'sync_back_ack',\n FILE_CHANGE: 'file_change',\n} as const;\n\nexport interface BaseMessage {\n type: string;\n}\n\nexport interface ResizeMessage {\n type: 'resize';\n cols: number;\n rows: number;\n}\n\nexport interface SignalMessage {\n type: 'signal';\n name: string;\n}\n\nexport interface PingMessage {\n type: 'ping';\n}\n\nexport interface PongMessage {\n type: 'pong';\n}\n\nexport interface ReadyMessage {\n type: 'ready';\n}\n\nexport interface ExitMessage {\n type: 'exit';\n code: number;\n signal: string | null;\n}\n\nexport interface ErrorMessage {\n type: 'error';\n message: string;\n}\n\nexport interface SyncBackMessage {\n type: 'sync_back';\n enabled: boolean;\n}\n\nexport interface SyncBackAckMessage {\n type: 'sync_back_ack';\n enabled: boolean;\n workspace_dir?: string;\n interval_ms?: number;\n}\n\nexport interface FileChangeMessage {\n type: 'file_change';\n action: 'write' | 'delete';\n path: string;\n content?: string; // base64 encoded\n mode?: number;\n}\n\nexport type Message =\n | ResizeMessage\n | SignalMessage\n | PingMessage\n | PongMessage\n | ReadyMessage\n | ExitMessage\n | ErrorMessage\n | SyncBackMessage\n | SyncBackAckMessage\n | FileChangeMessage\n | BaseMessage;\n\nexport function parseMessage(data: string): Message {\n const base = JSON.parse(data) as BaseMessage;\n\n switch (base.type) {\n case MessageType.RESIZE:\n return JSON.parse(data) as ResizeMessage;\n case MessageType.SIGNAL:\n return JSON.parse(data) as SignalMessage;\n case MessageType.PING:\n return { type: 'ping' } as PingMessage;\n case MessageType.PONG:\n return { type: 'pong' } as PongMessage;\n case MessageType.READY:\n return { type: 'ready' } as ReadyMessage;\n case MessageType.EXIT:\n return JSON.parse(data) as ExitMessage;\n case MessageType.ERROR:\n return JSON.parse(data) as ErrorMessage;\n case MessageType.SYNC_BACK:\n return JSON.parse(data) as SyncBackMessage;\n case MessageType.SYNC_BACK_ACK:\n return JSON.parse(data) as SyncBackAckMessage;\n case MessageType.FILE_CHANGE:\n return JSON.parse(data) as FileChangeMessage;\n default:\n return base;\n }\n}\n\nexport function createResizeMessage(cols: number, rows: number): string {\n return JSON.stringify({ type: MessageType.RESIZE, cols, rows });\n}\n\nexport function createSignalMessage(name: string): string {\n return JSON.stringify({ type: MessageType.SIGNAL, name });\n}\n\nexport function createPingMessage(): string {\n return JSON.stringify({ type: MessageType.PING });\n}\n\nexport function createPongMessage(): string {\n return JSON.stringify({ type: MessageType.PONG });\n}\n\nexport function createSyncBackMessage(enabled: boolean): string {\n return JSON.stringify({ type: MessageType.SYNC_BACK, enabled });\n}\n","import { writeFileSync, unlinkSync, mkdirSync, renameSync } from 'fs';\nimport { join, dirname, normalize, isAbsolute, sep } from 'path';\nimport type { FileChangeMessage } from '../protocol/messages.js';\n\n/**\n * Apply a remote file change to the local filesystem.\n * Best-effort: errors are logged but don't break the terminal.\n */\nexport function applyRemoteFileChange(msg: FileChangeMessage): void {\n // Validate path (no absolute, no traversal)\n const rel = normalize(msg.path.replace(/^\\.\\//, ''));\n\n if (!rel || rel === '.') return;\n\n if (isAbsolute(rel)) {\n console.error(`sync-back rejected absolute path: ${rel}`);\n return;\n }\n\n if (rel === '..' || rel.startsWith('..' + sep)) {\n console.error(`sync-back rejected traversal path: ${rel}`);\n return;\n }\n\n const cwd = process.cwd();\n const destPath = join(cwd, rel);\n\n // Ensure destPath is within cwd (resolve any remaining symlinks/tricks)\n const resolvedCwd = normalize(cwd);\n const resolvedDest = normalize(destPath);\n\n if (!resolvedDest.startsWith(resolvedCwd + sep) && resolvedDest !== resolvedCwd) {\n console.error(`sync-back rejected path outside base: ${destPath}`);\n return;\n }\n\n try {\n if (msg.action === 'delete') {\n unlinkSync(destPath);\n } else if (msg.action === 'write') {\n const content = Buffer.from(msg.content || '', 'base64');\n mkdirSync(dirname(destPath), { recursive: true });\n\n // Atomic write via temp file\n const tmpPath = join(dirname(destPath), `.catty-sync-${Date.now()}`);\n writeFileSync(tmpPath, content, { mode: msg.mode || 0o644 });\n renameSync(tmpPath, destPath);\n }\n } catch {\n // Best-effort, don't break terminal\n }\n}\n","import archiver from 'archiver';\nimport ignore, { type Ignore } from 'ignore';\nimport { createReadStream, readFileSync, readdirSync, statSync } from 'fs';\nimport { join, relative } from 'path';\nimport { MAX_UPLOAD_SIZE } from './config.js';\n\nconst DEFAULT_IGNORES = [\n '.git',\n '.git/**',\n 'node_modules',\n 'node_modules/**',\n '__pycache__',\n '__pycache__/**',\n '.venv',\n '.venv/**',\n 'venv',\n 'venv/**',\n '.env',\n '*.pyc',\n '.DS_Store',\n '*.log',\n];\n\nexport async function createWorkspaceZip(dir: string): Promise<Buffer> {\n const ig = ignore().add(DEFAULT_IGNORES);\n\n // Load .gitignore if exists\n try {\n const gitignore = readFileSync(join(dir, '.gitignore'), 'utf-8');\n ig.add(gitignore);\n } catch {\n // No .gitignore, use defaults only\n }\n\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n const archive = archiver('zip', { zlib: { level: 9 } });\n\n archive.on('data', (chunk: Buffer) => chunks.push(chunk));\n archive.on('end', () => resolve(Buffer.concat(chunks)));\n archive.on('error', reject);\n\n // Walk directory and add files\n walkDir(dir, dir, ig, archive);\n archive.finalize();\n });\n}\n\nfunction walkDir(\n baseDir: string,\n currentDir: string,\n ig: Ignore,\n archive: archiver.Archiver\n): void {\n let entries: string[];\n try {\n entries = readdirSync(currentDir);\n } catch {\n return;\n }\n\n for (const entry of entries) {\n const fullPath = join(currentDir, entry);\n const relativePath = relative(baseDir, fullPath);\n\n // Check if ignored\n if (ig.ignores(relativePath)) {\n continue;\n }\n\n let stat;\n try {\n stat = statSync(fullPath);\n } catch {\n continue;\n }\n\n if (stat.isDirectory()) {\n // Also check directory with trailing slash\n if (ig.ignores(relativePath + '/')) {\n continue;\n }\n walkDir(baseDir, fullPath, ig, archive);\n } else if (stat.isFile()) {\n archive.file(fullPath, { name: relativePath });\n }\n }\n}\n\nexport async function uploadWorkspace(\n uploadURL: string,\n token: string,\n machineID: string\n): Promise<void> {\n const cwd = process.cwd();\n const zipData = await createWorkspaceZip(cwd);\n\n if (zipData.length > MAX_UPLOAD_SIZE) {\n throw new Error(\n `Workspace too large (${zipData.length} bytes, max ${MAX_UPLOAD_SIZE})`\n );\n }\n\n const response = await fetch(uploadURL, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/zip',\n 'fly-force-instance-id': machineID,\n },\n body: zipData,\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`Upload failed: ${response.status} - ${text}`);\n }\n}\n\n/**\n * Build upload URL from connect URL.\n * Converts wss://app.fly.dev/connect to https://app.fly.dev/upload\n */\nexport function buildUploadURL(connectURL: string): string {\n return connectURL\n .replace('wss://', 'https://')\n .replace('ws://', 'http://')\n .replace('/connect', '/upload');\n}\n","import { Command } from 'commander';\nimport { getAPIAddr } from '../lib/config.js';\nimport { isLoggedIn } from '../lib/auth.js';\nimport { APIClient } from '../lib/api-client.js';\nimport { connectToSession } from '../lib/websocket.js';\n\nexport const connectCommand = new Command('connect')\n .description('Reconnect to an existing session')\n .argument('<label>', 'Session label (e.g., brave-tiger-1234)')\n .option('--sync-back', 'Sync remote file changes back to local', true)\n .option('--no-sync-back', 'Disable sync-back')\n .action(async function (this: Command, label: string) {\n const opts = this.opts();\n const apiAddr = getAPIAddr(this.optsWithGlobals().api);\n\n if (!isLoggedIn()) {\n console.error(\"Not logged in. Please run 'catty login' first.\");\n process.exit(1);\n }\n\n const client = new APIClient(apiAddr);\n\n console.log(`Looking up session ${label}...`);\n const session = await client.getSession(label, true);\n\n if (session.status === 'stopped') {\n throw new Error(`Session ${session.label} is stopped`);\n }\n if (session.machine_state && session.machine_state !== 'started') {\n throw new Error(`Machine is not running (state: ${session.machine_state})`);\n }\n\n console.log(`Reconnecting to ${session.label}...`);\n\n await connectToSession({\n connectURL: session.connect_url,\n connectToken: session.connect_token!,\n headers: { 'fly-force-instance-id': session.machine_id },\n syncBack: opts.syncBack,\n });\n });\n","import { Command } from 'commander';\nimport { getAPIAddr } from '../lib/config.js';\nimport { APIClient } from '../lib/api-client.js';\n\nexport const listCommand = new Command('list')\n .aliases(['ls'])\n .description('List all sessions')\n .action(async function (this: Command) {\n const apiAddr = getAPIAddr(this.optsWithGlobals().api);\n const client = new APIClient(apiAddr);\n\n const sessions = await client.listSessions();\n\n if (sessions.length === 0) {\n console.log('No sessions found');\n return;\n }\n\n // Simple table output\n const header = 'LABEL STATUS REGION CREATED';\n console.log(header);\n\n for (const s of sessions) {\n const age = formatAge(new Date(s.created_at));\n const row = [\n s.label.padEnd(22),\n s.status.padEnd(9),\n s.region.padEnd(7),\n age,\n ].join(' ');\n console.log(row);\n }\n });\n\n/**\n * Human-readable time ago formatting\n */\nfunction formatAge(date: Date): string {\n const seconds = Math.floor((Date.now() - date.getTime()) / 1000);\n\n if (seconds < 60) return `${seconds}s ago`;\n const minutes = Math.floor(seconds / 60);\n if (minutes < 60) return `${minutes}m ago`;\n const hours = Math.floor(minutes / 60);\n if (hours < 24) return `${hours}h ago`;\n const days = Math.floor(hours / 24);\n return `${days}d ago`;\n}\n","import { Command } from 'commander';\nimport { getAPIAddr } from '../lib/config.js';\nimport { APIClient } from '../lib/api-client.js';\n\nexport const stopCommand = new Command('stop')\n .description('Stop a session')\n .argument('<label>', 'Session ID or label')\n .option('--delete', 'Delete the machine after stopping', false)\n .action(async function (this: Command, label: string) {\n const opts = this.opts();\n const apiAddr = getAPIAddr(this.optsWithGlobals().api);\n const client = new APIClient(apiAddr);\n\n await client.stopSession(label, opts.delete);\n\n if (opts.delete) {\n console.log(`Session ${label} stopped and deleted`);\n } else {\n console.log(`Session ${label} stopped`);\n }\n });\n","import { Command } from 'commander';\nimport { getAPIAddr } from '../lib/config.js';\nimport { APIClient } from '../lib/api-client.js';\n\nexport const stopAllCommand = new Command('stop-all-sessions-dangerously')\n .description('Stop and delete ALL sessions')\n .option('--yes-i-mean-it', 'Confirm you want to stop all sessions', false)\n .action(async function (this: Command) {\n const opts = this.opts();\n const apiAddr = getAPIAddr(this.optsWithGlobals().api);\n\n if (!opts.yesIMeanIt) {\n throw new Error('Must pass --yes-i-mean-it to confirm');\n }\n\n const client = new APIClient(apiAddr);\n const sessions = await client.listSessions();\n\n if (sessions.length === 0) {\n console.log('No sessions to stop');\n return;\n }\n\n console.log(`Stopping ${sessions.length} sessions...`);\n\n for (const s of sessions) {\n process.stdout.write(` Stopping ${s.session_id}... `);\n try {\n await client.stopSession(s.session_id, true);\n console.log('done');\n } catch (err) {\n console.log(`ERROR: ${err}`);\n }\n }\n });\n","import { Command } from 'commander';\nimport open from 'open';\nimport { getAPIAddr, sleep } from '../lib/config.js';\nimport { isLoggedIn, loadCredentials, saveCredentials } from '../lib/auth.js';\nimport type { DeviceAuthResponse, TokenResponse } from '../types/index.js';\n\nexport const loginCommand = new Command('login')\n .description('Log in to Catty')\n .action(async function (this: Command) {\n const apiAddr = getAPIAddr(this.optsWithGlobals().api);\n\n if (isLoggedIn()) {\n const creds = loadCredentials();\n console.log(`Already logged in as ${creds?.email}`);\n console.log(\"Run 'catty logout' to log out first\");\n return;\n }\n\n console.log('Starting login...');\n\n // Step 1: Start device auth flow\n const authResp = await fetch(`${apiAddr}/v1/auth/device`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: '{}',\n });\n\n if (!authResp.ok) {\n throw new Error(`Failed to start auth: ${authResp.statusText}`);\n }\n\n const auth: DeviceAuthResponse = await authResp.json();\n\n // Step 2: Show code and open browser\n console.log('\\nYour confirmation code:\\n');\n console.log(` ${auth.user_code}\\n`);\n console.log(`Opening ${auth.verification_uri_complete}\\n`);\n\n await open(auth.verification_uri_complete);\n console.log('Waiting for authentication...');\n\n // Step 3: Poll for token\n const interval = (auth.interval || 5) * 1000;\n const deadline = Date.now() + auth.expires_in * 1000;\n\n while (Date.now() < deadline) {\n await sleep(interval);\n\n const tokenResp = await fetch(`${apiAddr}/v1/auth/device/token`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ device_code: auth.device_code }),\n });\n\n const token: TokenResponse = await tokenResp.json();\n\n if (token.pending) continue;\n if (token.error) throw new Error(token.error);\n\n if (token.access_token) {\n saveCredentials({\n access_token: token.access_token,\n refresh_token: token.refresh_token,\n user_id: token.user?.id || '',\n email: token.user?.email || '',\n expires_at: token.expires_in\n ? new Date(Date.now() + (token.expires_in - 30) * 1000).toISOString()\n : undefined,\n });\n console.log(`\\nLogged in as ${token.user?.email}`);\n console.log(\"You can now run 'catty new' to start a session\");\n return;\n }\n }\n\n throw new Error('Authentication timed out');\n });\n","import { Command } from 'commander';\nimport { isLoggedIn, loadCredentials, deleteCredentials } from '../lib/auth.js';\n\nexport const logoutCommand = new Command('logout')\n .description('Log out of Catty')\n .action(async () => {\n if (!isLoggedIn()) {\n console.log('Not logged in');\n return;\n }\n\n const creds = loadCredentials();\n const email = creds?.email || '';\n\n deleteCredentials();\n\n if (email) {\n console.log(`Logged out from ${email}`);\n } else {\n console.log('Logged out');\n }\n });\n","import { Command } from 'commander';\n\n// VERSION is replaced at build time by tsup\ndeclare const __VERSION__: string;\n\nexport const versionCommand = new Command('version')\n .description('Print the version number')\n .action(() => {\n console.log(typeof __VERSION__ !== 'undefined' ? __VERSION__ : 'dev');\n });\n","import { Command } from 'commander';\nimport { checkForUpdate, runUpdate } from '../lib/version-checker.js';\n\ndeclare const __VERSION__: string;\n\nexport const updateCommand = new Command('update')\n .description('Update catty to the latest version')\n .action(async () => {\n try {\n const currentVersion = typeof __VERSION__ !== 'undefined' ? __VERSION__ : 'dev';\n\n if (currentVersion === 'dev') {\n console.log('Cannot update in development mode.');\n return;\n }\n\n console.log('Checking for updates...');\n const { updateAvailable, latestVersion } = await checkForUpdate({\n bypassCache: true,\n });\n\n if (!updateAvailable || !latestVersion) {\n console.log(`You are already using the latest version (${currentVersion}).`);\n return;\n }\n\n // When manually running update command, always update regardless of declined status\n await runUpdate(currentVersion, latestVersion);\n } catch (err) {\n if (err instanceof Error) {\n console.error(`Error: ${err.message}`);\n }\n process.exit(1);\n }\n });\n","import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport { createInterface } from 'readline';\nimport { spawn } from 'child_process';\nimport { CREDENTIALS_DIR } from './config.js';\n\ndeclare const __VERSION__: string;\n\nfunction logDebug(message: string): void {\n if (process.env.DEBUG || process.env.CATTY_DEBUG) {\n console.log(`\\x1b[2m[DEBUG] ${message}\\x1b[0m`);\n }\n}\n\ninterface VersionCache {\n latestVersion: string;\n lastChecked: number;\n declinedVersion?: string;\n declinedAt?: number;\n}\n\nconst VERSION_CHECK_INTERVAL = 15 * 60 * 1000; // 15 minutes\nconst DECLINED_VERSION_REMINDER_INTERVAL = 2 * 24 * 60 * 60 * 1000; // 2 days\nconst NPM_REGISTRY_URL = 'https://registry.npmjs.org/@diggerhq/catty/latest';\n\nfunction getVersionCachePath(): string {\n return join(homedir(), CREDENTIALS_DIR, 'version-cache.json');\n}\n\nfunction getCachedVersion(): VersionCache | null {\n try {\n const cachePath = getVersionCachePath();\n if (!existsSync(cachePath)) {\n return null;\n }\n const data = readFileSync(cachePath, 'utf-8');\n return JSON.parse(data);\n } catch {\n return null;\n }\n}\n\nfunction setCachedVersion(version: string, declined?: boolean): void {\n try {\n const cachePath = getVersionCachePath();\n const cacheDir = join(homedir(), CREDENTIALS_DIR);\n if (!existsSync(cacheDir)) {\n mkdirSync(cacheDir, { recursive: true });\n }\n\n // Preserve existing declined info if not updating it\n const existing = getCachedVersion();\n const cache: VersionCache = {\n latestVersion: version,\n lastChecked: Date.now(),\n declinedVersion: declined ? version : existing?.declinedVersion,\n declinedAt: declined ? Date.now() : existing?.declinedAt,\n };\n\n writeFileSync(cachePath, JSON.stringify(cache, null, 2));\n } catch {\n // Silently fail if we can't write cache\n }\n}\n\nasync function fetchLatestVersion(): Promise<string | null> {\n try {\n const response = await fetch(NPM_REGISTRY_URL, {\n signal: AbortSignal.timeout(5000), // 5 second timeout\n });\n if (!response.ok) {\n return null;\n }\n const data = await response.json();\n return data.version || null;\n } catch {\n return null;\n }\n}\n\nfunction compareVersions(current: string, latest: string): boolean {\n const currentParts = current.split('.').map(Number);\n const latestParts = latest.split('.').map(Number);\n\n for (let i = 0; i < 3; i++) {\n const curr = currentParts[i] || 0;\n const lat = latestParts[i] || 0;\n if (lat > curr) return true;\n if (lat < curr) return false;\n }\n return false;\n}\n\nexport async function checkForUpdate(options?: {\n bypassCache?: boolean;\n}): Promise<{\n updateAvailable: boolean;\n currentVersion: string;\n latestVersion: string | null;\n shouldPrompt: boolean;\n}> {\n const currentVersion =\n typeof __VERSION__ !== 'undefined' ? __VERSION__ : 'dev';\n\n // Don't check in dev mode\n if (currentVersion === 'dev') {\n return {\n updateAvailable: false,\n currentVersion,\n latestVersion: null,\n shouldPrompt: false,\n };\n }\n\n // Check cache first (unless bypassing)\n const cached = getCachedVersion();\n const now = Date.now();\n\n let latestVersion: string | null = null;\n\n if (!options?.bypassCache && cached && now - cached.lastChecked < VERSION_CHECK_INTERVAL) {\n // Use cached version\n latestVersion = cached.latestVersion;\n } else {\n // Fetch latest version\n latestVersion = await fetchLatestVersion();\n if (latestVersion) {\n setCachedVersion(latestVersion);\n }\n }\n\n if (!latestVersion) {\n return {\n updateAvailable: false,\n currentVersion,\n latestVersion: null,\n shouldPrompt: false,\n };\n }\n\n const updateAvailable = compareVersions(currentVersion, latestVersion);\n\n // Check if user previously declined this version\n let shouldPrompt = updateAvailable;\n if (updateAvailable && cached?.declinedVersion === latestVersion && cached.declinedAt) {\n // User declined this version - only prompt again after the reminder interval\n const timeSinceDeclined = now - cached.declinedAt;\n shouldPrompt = timeSinceDeclined >= DECLINED_VERSION_REMINDER_INTERVAL;\n }\n\n return {\n updateAvailable,\n currentVersion,\n latestVersion,\n shouldPrompt,\n };\n}\n\nexport function printUpdateAvailable(\n currentVersion: string,\n latestVersion: string\n): void {\n console.log('');\n console.log(`\\x1b[33mUpdate available:\\x1b[0m \\x1b[2m${currentVersion}\\x1b[0m → \\x1b[32m${latestVersion}\\x1b[0m`);\n}\n\nexport async function promptForUpdate(): Promise<boolean> {\n const rl = createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n return new Promise((resolve) => {\n rl.question('Would you like to update? (Y/n): ', (answer) => {\n rl.close();\n const normalized = answer.trim().toLowerCase();\n // Default to yes if empty or starts with 'y'\n resolve(normalized === '' || normalized === 'y' || normalized === 'yes');\n });\n });\n}\n\nexport function recordDeclinedUpdate(version: string): void {\n setCachedVersion(version, true);\n}\n\nfunction detectPackageManager(): string {\n // Check if npm is available\n const userAgent = process.env.npm_config_user_agent || '';\n\n if (userAgent.includes('yarn')) {\n return 'yarn';\n } else if (userAgent.includes('pnpm')) {\n return 'pnpm';\n } else if (userAgent.includes('bun')) {\n return 'bun';\n }\n\n return 'npm';\n}\n\nexport async function runUpdate(\n currentVersion: string,\n latestVersion: string\n): Promise<void> {\n console.log(`\\nUpdating from ${currentVersion} to ${latestVersion}...\\n`);\n\n const packageManager = detectPackageManager();\n const packageName = `@diggerhq/catty@${latestVersion}`;\n let command: string;\n let args: string[];\n\n switch (packageManager) {\n case 'yarn':\n command = 'yarn';\n args = ['global', 'add', packageName];\n break;\n case 'pnpm':\n command = 'pnpm';\n args = ['add', '-g', packageName];\n break;\n case 'bun':\n command = 'bun';\n args = ['install', '-g', packageName];\n break;\n default:\n command = 'npm';\n args = ['install', '-g', packageName];\n }\n\n logDebug(`Package manager: ${packageManager}`);\n logDebug(`Command: ${command} ${args.join(' ')}`);\n\n return new Promise((resolve, reject) => {\n const child = spawn(command, args, {\n stdio: 'inherit',\n shell: process.platform === 'win32',\n });\n\n child.on('close', (code) => {\n if (code === 0) {\n console.log(\n `\\n\\x1b[32m✓\\x1b[0m Successfully updated to version ${latestVersion}`\n );\n resolve();\n } else {\n console.error(\n `\\n\\x1b[31m✗\\x1b[0m Update failed with exit code ${code}`\n );\n reject(new Error(`Update process exited with code ${code}`));\n }\n });\n\n child.on('error', (err) => {\n console.error(\n `\\n\\x1b[31m✗\\x1b[0m Failed to run update command: ${err.message}`\n );\n reject(err);\n });\n });\n}\n"],"mappings":"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","names":["program","Command","open","DEFAULT_API_ADDR","CREDENTIALS_DIR","CREDENTIALS_FILE","getAPIAddr","cliOption","DEFAULT_API_ADDR","sleep","ms","resolve","homedir","join","readFileSync","writeFileSync","mkdirSync","unlinkSync","existsSync","getCredentialsDir","join","homedir","CREDENTIALS_DIR","getCredentialsPath","CREDENTIALS_FILE","loadCredentials","path","content","readFileSync","saveCredentials","creds","dir","mkdirSync","writeFileSync","deleteCredentials","existsSync","unlinkSync","isLoggedIn","getAccessToken","getRefreshToken","APIError","statusCode","errorCode","message","upgradeURL","APIClient","baseURL","DEFAULT_API_ADDR","getAccessToken","method","path","body","controller","timeoutId","headers","response","refreshToken","getRefreshToken","data","creds","loadCredentials","saveCredentials","errorData","req","idOrLabel","live","del","WebSocket","Terminal","cleanup","callback","MessageType","parseMessage","data","base","createResizeMessage","cols","rows","createPongMessage","MessageType","createSyncBackMessage","enabled","writeFileSync","unlinkSync","mkdirSync","renameSync","join","dirname","normalize","isAbsolute","sep","applyRemoteFileChange","msg","rel","cwd","destPath","resolvedCwd","resolvedDest","content","tmpPath","connectToSession","opts","terminal","Terminal","ws","WebSocket","resolve","reject","syncBackAcked","exitCode","handleResize","cols","rows","createResizeMessage","cleanup","handleStdinData","data","createSyncBackMessage","isBinary","msg","parseMessage","handleControlMessage","exitMsg","errorMsg","createPongMessage","applyRemoteFileChange","code","err","archiver","ignore","readFileSync","readdirSync","statSync","join","relative","DEFAULT_IGNORES","createWorkspaceZip","dir","ig","ignore","gitignore","readFileSync","join","resolve","reject","chunks","archive","archiver","chunk","walkDir","baseDir","currentDir","entries","readdirSync","entry","fullPath","relativePath","relative","stat","statSync","uploadWorkspace","uploadURL","token","machineID","cwd","zipData","response","text","buildUploadURL","connectURL","newCommand","Command","opts","apiAddr","getAPIAddr","isLoggedIn","client","APIClient","cmdArgs","session","err","APIError","handleQuotaExceeded","uploadURL","buildUploadURL","uploadWorkspace","connectToSession","checkoutURL","open","Command","connectCommand","Command","label","opts","apiAddr","getAPIAddr","isLoggedIn","client","APIClient","session","connectToSession","Command","listCommand","Command","apiAddr","getAPIAddr","sessions","APIClient","s","age","formatAge","row","date","seconds","minutes","hours","Command","stopCommand","Command","label","opts","apiAddr","getAPIAddr","APIClient","Command","stopAllCommand","Command","opts","apiAddr","getAPIAddr","client","APIClient","sessions","s","err","Command","open","loginCommand","Command","apiAddr","getAPIAddr","isLoggedIn","creds","loadCredentials","authResp","auth","open","interval","deadline","sleep","token","saveCredentials","Command","logoutCommand","Command","isLoggedIn","email","loadCredentials","deleteCredentials","Command","versionCommand","Command","readFileSync","writeFileSync","existsSync","mkdirSync","join","homedir","createInterface","spawn","logDebug","message","VERSION_CHECK_INTERVAL","DECLINED_VERSION_REMINDER_INTERVAL","NPM_REGISTRY_URL","getVersionCachePath","join","homedir","CREDENTIALS_DIR","getCachedVersion","cachePath","existsSync","data","readFileSync","setCachedVersion","version","declined","cacheDir","mkdirSync","existing","cache","writeFileSync","fetchLatestVersion","response","compareVersions","current","latest","currentParts","latestParts","i","curr","lat","checkForUpdate","options","currentVersion","cached","now","latestVersion","updateAvailable","shouldPrompt","printUpdateAvailable","promptForUpdate","rl","createInterface","resolve","answer","normalized","recordDeclinedUpdate","detectPackageManager","userAgent","runUpdate","packageManager","packageName","command","args","reject","child","spawn","code","err","updateCommand","Command","currentVersion","updateAvailable","latestVersion","checkForUpdate","runUpdate","err","version","program","newCommand","connectCommand","listCommand","stopCommand","stopAllCommand","loginCommand","logoutCommand","versionCommand","updateCommand","main","command","updateAvailable","currentVersion","latestVersion","shouldPrompt","checkForUpdate","printUpdateAvailable","promptForUpdate","runUpdate","recordDeclinedUpdate","err"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/commands/new.ts","../src/lib/config.ts","../src/lib/auth.ts","../src/lib/api-client.ts","../src/lib/websocket.ts","../src/lib/terminal.ts","../src/protocol/messages.ts","../src/lib/syncback.ts","../src/lib/file-upload.ts","../src/lib/workspace.ts","../src/commands/connect.ts","../src/commands/list.ts","../src/commands/stop.ts","../src/commands/stopall.ts","../src/commands/login.ts","../src/commands/logout.ts","../src/commands/version.ts","../src/commands/update.ts","../src/lib/version-checker.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { program } from 'commander';\nimport { newCommand } from './commands/new.js';\nimport { connectCommand } from './commands/connect.js';\nimport { listCommand } from './commands/list.js';\nimport { stopCommand } from './commands/stop.js';\nimport { stopAllCommand } from './commands/stopall.js';\nimport { loginCommand } from './commands/login.js';\nimport { logoutCommand } from './commands/logout.js';\nimport { versionCommand } from './commands/version.js';\nimport { updateCommand } from './commands/update.js';\nimport {\n checkForUpdate,\n printUpdateAvailable,\n promptForUpdate,\n runUpdate,\n recordDeclinedUpdate,\n} from './lib/version-checker.js';\n\n// VERSION is replaced at build time by tsup\ndeclare const __VERSION__: string;\nconst version = typeof __VERSION__ !== 'undefined' ? __VERSION__ : 'dev';\n\nprogram\n .name('catty')\n .description('Catty - Remote AI agent sessions')\n .option('--api <url>', 'API server address')\n .version(version);\n\nprogram.addCommand(newCommand);\nprogram.addCommand(connectCommand);\nprogram.addCommand(listCommand);\nprogram.addCommand(stopCommand);\nprogram.addCommand(stopAllCommand, { hidden: true });\nprogram.addCommand(loginCommand);\nprogram.addCommand(logoutCommand);\nprogram.addCommand(versionCommand);\nprogram.addCommand(updateCommand);\n\n// Handle errors gracefully\nprogram.exitOverride();\n\nasync function main() {\n try {\n await program.parseAsync(process.argv);\n\n // Check for updates after command execution\n // Skip if running version, update, or help commands\n const command = process.argv[2];\n const skipUpdateCheck = [\n 'version',\n 'update',\n '-v',\n '--version',\n '-h',\n '--help',\n 'help',\n ];\n if (command && !skipUpdateCheck.includes(command)) {\n const { updateAvailable, currentVersion, latestVersion, shouldPrompt } =\n await checkForUpdate();\n if (updateAvailable && latestVersion && shouldPrompt) {\n printUpdateAvailable(currentVersion, latestVersion);\n const shouldUpdate = await promptForUpdate();\n if (shouldUpdate) {\n try {\n await runUpdate(currentVersion, latestVersion);\n } catch (err) {\n // Update failed, but don't exit the process\n // Error message already printed by runUpdate\n }\n } else {\n // User declined - record it so we don't ask again for 2 days\n recordDeclinedUpdate(latestVersion);\n }\n }\n }\n } catch (err: unknown) {\n if (err instanceof Error) {\n // Commander throws for help/version, ignore those\n if (\n err.name === 'CommanderError' &&\n ['commander.helpDisplayed', 'commander.version'].includes(\n (err as { code?: string }).code || ''\n )\n ) {\n process.exit(0);\n }\n\n console.error(`Error: ${err.message}`);\n } else {\n console.error(`Error: ${err}`);\n }\n process.exit(1);\n }\n}\n\nmain();\n","import { Command } from 'commander';\nimport open from 'open';\nimport { getAPIAddr, sleep } from '../lib/config.js';\nimport { isLoggedIn } from '../lib/auth.js';\nimport { APIClient, APIError } from '../lib/api-client.js';\nimport { connectToSession, type ConnectionResult } from '../lib/websocket.js';\nimport { uploadWorkspace, buildUploadURL } from '../lib/workspace.js';\n\nconst MAX_RECONNECT_ATTEMPTS = 5;\nconst RECONNECT_DELAY_MS = 2000;\n\nexport const newCommand = new Command('new')\n .description('Start a new remote agent session')\n .option('--agent <name>', 'Agent to use: claude or codex', 'claude')\n .option('--no-upload', \"Don't upload current directory\")\n .option('--no-sync-back', 'Disable sync-back')\n .option('--no-auto-reconnect', 'Disable automatic reconnection on disconnect')\n .option(\n '--enable-prompts',\n 'Enable permission prompts (by default, all permissions are auto-approved)',\n false\n )\n .action(async function (this: Command) {\n const opts = this.opts();\n const apiAddr = getAPIAddr(this.optsWithGlobals().api);\n const autoReconnect = opts.autoReconnect !== false;\n\n if (!isLoggedIn()) {\n console.error(\"Not logged in. Please run 'catty login' first.\");\n process.exit(1);\n }\n\n const client = new APIClient(apiAddr);\n\n console.log('Creating session...');\n\n // Determine command arguments based on agent and prompts setting\n let cmdArgs: string[];\n switch (opts.agent) {\n case 'claude':\n if (opts.enablePrompts) {\n // User wants prompts - don't skip permissions\n cmdArgs = ['claude-wrapper'];\n } else {\n // Default: auto-approve all permissions\n cmdArgs = ['claude-wrapper', '--dangerously-skip-permissions'];\n }\n break;\n case 'codex':\n cmdArgs = ['codex'];\n break;\n default:\n console.error(\n `Unknown agent: ${opts.agent} (must be 'claude' or 'codex')`\n );\n process.exit(1);\n }\n\n let session;\n try {\n session = await client.createSession({\n agent: opts.agent,\n cmd: cmdArgs,\n region: 'iad',\n ttl_sec: 7200,\n });\n } catch (err) {\n if (err instanceof APIError && err.isQuotaExceeded()) {\n await handleQuotaExceeded(client);\n return;\n }\n throw err;\n }\n\n console.log(`Session created: ${session.label}`);\n console.log(` Reconnect with: catty connect ${session.label}`);\n\n // Upload workspace\n if (opts.upload !== false) {\n console.log('Uploading workspace...');\n const uploadURL = buildUploadURL(session.connect_url);\n\n await uploadWorkspace(\n uploadURL,\n session.connect_token,\n session.headers['fly-force-instance-id']\n );\n console.log('Workspace uploaded.');\n }\n\n console.log(`Connecting to ${session.connect_url}...`);\n\n // Connection loop with auto-reconnect\n let reconnectAttempts = 0;\n\n while (true) {\n try {\n const result: ConnectionResult = await connectToSession({\n connectURL: session.connect_url,\n connectToken: session.connect_token,\n headers: session.headers,\n syncBack: opts.syncBack !== false,\n });\n\n // Handle the connection result\n if (result.type === 'exit') {\n // Clean exit - process ended normally\n process.exit(result.code);\n } else if (result.type === 'interrupted') {\n // User pressed Ctrl+C - exit cleanly, don't reconnect\n process.exit(130);\n } else if (result.type === 'replaced') {\n // Connection was replaced by another client - don't reconnect\n console.log('Session taken over by another client.');\n process.exit(0);\n } else if (result.type === 'disconnected') {\n // Connection lost - try to reconnect\n if (!autoReconnect) {\n console.error(`Disconnected: ${result.reason}`);\n process.exit(1);\n }\n\n reconnectAttempts++;\n if (reconnectAttempts > MAX_RECONNECT_ATTEMPTS) {\n console.error(`\\x1b[31m✗ Failed to reconnect after ${MAX_RECONNECT_ATTEMPTS} attempts\\x1b[0m`);\n console.error(`Run 'catty connect ${session.label}' to try again manually.`);\n process.exit(1);\n }\n\n console.log(`\\x1b[33m⟳ Reconnecting (${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})...\\x1b[0m`);\n await sleep(RECONNECT_DELAY_MS);\n\n // Refresh session info before reconnecting\n try {\n session = await client.getSession(session.label, true);\n if (session.status === 'stopped') {\n console.error(`\\x1b[31m✗ Session has stopped\\x1b[0m`);\n process.exit(1);\n }\n } catch {\n // Session lookup failed, try with existing info\n }\n }\n } catch (err) {\n if (reconnectAttempts > 0 && autoReconnect) {\n reconnectAttempts++;\n if (reconnectAttempts > MAX_RECONNECT_ATTEMPTS) {\n console.error(`\\x1b[31m✗ Failed to reconnect after ${MAX_RECONNECT_ATTEMPTS} attempts\\x1b[0m`);\n process.exit(1);\n }\n console.error(`\\x1b[33m⟳ Reconnect failed, retrying (${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})...\\x1b[0m`);\n await sleep(RECONNECT_DELAY_MS);\n } else {\n throw err;\n }\n }\n }\n });\n\nasync function handleQuotaExceeded(client: APIClient): Promise<void> {\n console.error('');\n console.error('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');\n console.error(' Free tier quota exceeded (1M tokens/month)');\n console.error(' Upgrade to Pro for unlimited usage.');\n console.error('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');\n console.error('');\n\n try {\n const checkoutURL = await client.createCheckoutSession();\n console.error('Opening upgrade page in your browser...');\n await open(checkoutURL);\n } catch (err) {\n console.error(`Failed to create checkout session: ${err}`);\n console.error('Please visit https://catty.dev to upgrade.');\n }\n}\n","export const DEFAULT_API_ADDR = 'https://api.catty.dev';\nexport const CREDENTIALS_DIR = '.catty';\nexport const CREDENTIALS_FILE = 'credentials.json';\nexport const MAX_UPLOAD_SIZE = 100 * 1024 * 1024; // 100MB\n\n// Timeouts\nexport const API_TIMEOUT_MS = 120_000; // 120 seconds for API requests (machine creation can be slow)\nexport const WS_WRITE_TIMEOUT_MS = 10_000; // 10 seconds for WebSocket writes\nexport const WS_READ_TIMEOUT_MS = 60_000; // 60 seconds (must be > 25s ping interval)\nexport const SYNC_BACK_ACK_TIMEOUT_MS = 2_000; // Warn if no sync-back ack after 2s\n\n// WebSocket close codes\nexport const WS_POLICY_VIOLATION = 1008; // Connection replaced by new one\n\n// Helper to get API address (checks flag, env var, or default)\nexport function getAPIAddr(cliOption?: string): string {\n if (cliOption) return cliOption;\n if (process.env.CATTY_API_ADDR) return process.env.CATTY_API_ADDR;\n return DEFAULT_API_ADDR;\n}\n\n// Sleep helper for polling\nexport function sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","import { homedir } from 'os';\nimport { join } from 'path';\nimport {\n readFileSync,\n writeFileSync,\n mkdirSync,\n unlinkSync,\n existsSync,\n} from 'fs';\nimport { CREDENTIALS_DIR, CREDENTIALS_FILE } from './config.js';\nimport type { Credentials } from '../types/index.js';\n\nexport function getCredentialsDir(): string {\n return join(homedir(), CREDENTIALS_DIR);\n}\n\nexport function getCredentialsPath(): string {\n return join(getCredentialsDir(), CREDENTIALS_FILE);\n}\n\nexport function loadCredentials(): Credentials | null {\n const path = getCredentialsPath();\n try {\n const content = readFileSync(path, 'utf-8');\n return JSON.parse(content) as Credentials;\n } catch {\n return null;\n }\n}\n\nexport function saveCredentials(creds: Credentials): void {\n const dir = getCredentialsDir();\n const path = getCredentialsPath();\n\n // Create directory with 0700 permissions\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n\n // Write file with 0600 permissions\n writeFileSync(path, JSON.stringify(creds, null, 2), { mode: 0o600 });\n}\n\nexport function deleteCredentials(): void {\n const path = getCredentialsPath();\n if (existsSync(path)) {\n unlinkSync(path);\n }\n}\n\nexport function isLoggedIn(): boolean {\n const creds = loadCredentials();\n if (!creds) return false;\n\n // Check if token exists\n if (!creds.access_token) return false;\n\n // If there's an expiry and no refresh token, check if expired\n if (creds.expires_at && !creds.refresh_token) {\n const expiresAt = new Date(creds.expires_at);\n if (expiresAt <= new Date()) {\n return false;\n }\n }\n\n // If we have a refresh token, we can refresh even if access token expired\n return true;\n}\n\nexport function getAccessToken(): string | null {\n const creds = loadCredentials();\n return creds?.access_token || null;\n}\n\nexport function getRefreshToken(): string | null {\n const creds = loadCredentials();\n return creds?.refresh_token || null;\n}\n","import {\n DEFAULT_API_ADDR,\n API_TIMEOUT_MS,\n} from './config.js';\nimport {\n getAccessToken,\n getRefreshToken,\n loadCredentials,\n saveCredentials,\n} from './auth.js';\nimport type {\n CreateSessionRequest,\n CreateSessionResponse,\n SessionInfo,\n APIErrorResponse,\n} from '../types/index.js';\n\nexport class APIError extends Error {\n constructor(\n public statusCode: number,\n public errorCode: string,\n message: string,\n public upgradeURL?: string\n ) {\n super(message);\n this.name = 'APIError';\n }\n\n isQuotaExceeded(): boolean {\n return this.statusCode === 402 && this.errorCode === 'quota_exceeded';\n }\n}\n\nexport class APIClient {\n private baseURL: string;\n private authToken: string | null;\n\n constructor(baseURL?: string) {\n this.baseURL = baseURL || process.env.CATTY_API_ADDR || DEFAULT_API_ADDR;\n this.authToken = getAccessToken();\n }\n\n private async doRequest(\n method: string,\n path: string,\n body?: unknown\n ): Promise<Response> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), API_TIMEOUT_MS);\n\n try {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n\n if (this.authToken) {\n headers['Authorization'] = `Bearer ${this.authToken}`;\n }\n\n const response = await fetch(`${this.baseURL}${path}`, {\n method,\n headers,\n body: body ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n });\n\n return response;\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n private async doRequestWithRefresh(\n method: string,\n path: string,\n body?: unknown\n ): Promise<Response> {\n let response = await this.doRequest(method, path, body);\n\n if (response.status === 401) {\n const refreshed = await this.refreshAuthToken();\n if (refreshed) {\n response = await this.doRequest(method, path, body);\n }\n }\n\n return response;\n }\n\n private async refreshAuthToken(): Promise<boolean> {\n const refreshToken = getRefreshToken();\n if (!refreshToken) return false;\n\n try {\n const response = await this.doRequest('POST', '/v1/auth/refresh', {\n refresh_token: refreshToken,\n });\n\n if (!response.ok) return false;\n\n const data = await response.json();\n if (!data.access_token) return false;\n\n // Update stored credentials\n const creds = loadCredentials();\n if (creds) {\n creds.access_token = data.access_token;\n if (data.refresh_token) {\n creds.refresh_token = data.refresh_token;\n }\n if (data.expires_in) {\n creds.expires_at = new Date(\n Date.now() + (data.expires_in - 30) * 1000\n ).toISOString();\n }\n saveCredentials(creds);\n this.authToken = data.access_token;\n }\n\n return true;\n } catch {\n return false;\n }\n }\n\n private async handleResponse<T>(response: Response): Promise<T> {\n if (!response.ok) {\n let errorData: APIErrorResponse;\n try {\n errorData = await response.json();\n } catch {\n errorData = { error: response.statusText };\n }\n\n throw new APIError(\n response.status,\n errorData.code || '',\n errorData.error || response.statusText,\n errorData.upgrade_url\n );\n }\n\n return response.json() as Promise<T>;\n }\n\n async createSession(req: CreateSessionRequest): Promise<CreateSessionResponse> {\n const response = await this.doRequestWithRefresh('POST', '/v1/sessions', req);\n return this.handleResponse<CreateSessionResponse>(response);\n }\n\n async listSessions(): Promise<SessionInfo[]> {\n const response = await this.doRequestWithRefresh('GET', '/v1/sessions');\n return this.handleResponse<SessionInfo[]>(response);\n }\n\n async getSession(idOrLabel: string, live?: boolean): Promise<SessionInfo> {\n const path = live\n ? `/v1/sessions/${idOrLabel}?live=true`\n : `/v1/sessions/${idOrLabel}`;\n const response = await this.doRequestWithRefresh('GET', path);\n return this.handleResponse<SessionInfo>(response);\n }\n\n async stopSession(idOrLabel: string, del?: boolean): Promise<void> {\n const path = del\n ? `/v1/sessions/${idOrLabel}/stop?delete=true`\n : `/v1/sessions/${idOrLabel}/stop`;\n const response = await this.doRequestWithRefresh('POST', path);\n\n if (!response.ok) {\n let errorData: APIErrorResponse;\n try {\n errorData = await response.json();\n } catch {\n errorData = { error: response.statusText };\n }\n throw new APIError(\n response.status,\n errorData.code || '',\n errorData.error || response.statusText\n );\n }\n }\n\n async createCheckoutSession(): Promise<string> {\n const response = await this.doRequestWithRefresh('POST', '/v1/checkout');\n const data = await this.handleResponse<{ url: string }>(response);\n return data.url;\n }\n}\n","import WebSocket from 'ws';\nimport { appendFileSync } from 'fs';\nimport { homedir } from 'os';\nimport { Terminal } from './terminal.js';\nimport {\n SYNC_BACK_ACK_TIMEOUT_MS,\n WS_POLICY_VIOLATION,\n WS_READ_TIMEOUT_MS,\n} from './config.js';\nimport {\n parseMessage,\n createResizeMessage,\n createPongMessage,\n createSyncBackMessage,\n createFileUploadMessage,\n createFileUploadChunkMessage,\n type Message,\n type ExitMessage,\n type ErrorMessage,\n type FileChangeMessage,\n} from '../protocol/messages.js';\nimport { applyRemoteFileChange } from './syncback.js';\nimport {\n detectFilePaths,\n shouldAutoUpload,\n generateUniqueFilename,\n CHUNK_SIZE,\n} from './file-upload.js';\n\n// Debug logging to file (avoids terminal corruption)\nfunction debugLog(msg: string): void {\n if (process.env.CATTY_DEBUG === '1') {\n const logFile = `${homedir()}/.catty-debug.log`;\n appendFileSync(logFile, `${new Date().toISOString()} [ws] ${msg}\\n`);\n }\n}\n\n// Connection result types\nexport type ConnectionResult = \n | { type: 'exit'; code: number }\n | { type: 'disconnected'; reason: string }\n | { type: 'replaced' }\n | { type: 'interrupted' }; // User pressed Ctrl+C\n\n// Bracketed paste escape sequences\nconst PASTE_START = '\\x1b[200~';\nconst PASTE_END = '\\x1b[201~';\n\nexport interface WebSocketConnectOptions {\n connectURL: string;\n connectToken: string;\n headers: Record<string, string>;\n syncBack: boolean;\n onExit?: (code: number) => void;\n}\n\nexport async function connectToSession(\n opts: WebSocketConnectOptions\n): Promise<ConnectionResult> {\n const terminal = new Terminal();\n\n if (!terminal.isTerminal()) {\n throw new Error('stdin is not a terminal');\n }\n\n const ws = new WebSocket(opts.connectURL, {\n headers: {\n ...opts.headers,\n Authorization: `Bearer ${opts.connectToken}`,\n },\n handshakeTimeout: 30_000, // 30s timeout for initial connection\n });\n\n return new Promise((resolve, reject) => {\n let syncBackAcked = false;\n let exitCode = 0;\n let connectionClosed = false;\n let connectionOpened = false;\n let userInterrupted = false; // Track if user pressed Ctrl+C\n let resolved = false; // Prevent double resolution\n\n // Safe resolve that only runs once\n const safeResolve = (result: ConnectionResult) => {\n if (resolved) return;\n resolved = true;\n resolve(result);\n };\n\n // Paste detection state\n let inPaste = false;\n let pasteBuffer = '';\n let inputBuffer = '';\n\n // Double Ctrl+C detection - if user presses Ctrl+C twice within 1 second, exit catty\n let lastCtrlC = 0;\n const DOUBLE_CTRLC_MS = 1000;\n\n // Handle Ctrl+C from signal (works when NOT in raw mode)\n const handleSigint = () => {\n userInterrupted = true;\n cleanup();\n try {\n ws.close();\n } catch {\n // Ignore\n }\n safeResolve({ type: 'interrupted' });\n };\n process.once('SIGINT', handleSigint);\n\n // Force quit with Ctrl+\\ (SIGQUIT) - works even in raw mode\n const handleSigquit = () => {\n userInterrupted = true;\n process.stderr.write('\\r\\n\\x1b[33mForce quit (Ctrl+\\\\)\\x1b[0m\\r\\n');\n cleanup();\n try {\n ws.close();\n } catch {\n // Ignore\n }\n safeResolve({ type: 'interrupted' });\n };\n process.once('SIGQUIT', handleSigquit);\n\n // Connection timeout - if we don't connect within 30s, give up\n const connectionTimeout = setTimeout(() => {\n if (!connectionOpened && !connectionClosed) {\n connectionClosed = true;\n process.stderr.write(`\\r\\n\\x1b[31m✗ Connection timeout: server not responding\\x1b[0m\\r\\n`);\n try {\n ws.terminate();\n } catch {\n // Ignore\n }\n safeResolve({ type: 'disconnected', reason: 'Connection timeout' });\n }\n }, 30_000);\n\n // Client-side connection health monitoring\n let lastDataReceived = Date.now();\n const CLIENT_TIMEOUT_MS = WS_READ_TIMEOUT_MS + 15_000; // 75s (server is 60s, give buffer)\n \n const healthCheckInterval = setInterval(() => {\n if (connectionClosed) return;\n \n const timeSinceData = Date.now() - lastDataReceived;\n if (timeSinceData > CLIENT_TIMEOUT_MS) {\n debugLog(`Client-side timeout: no data for ${timeSinceData}ms`);\n clearInterval(healthCheckInterval);\n \n // Show clear message to user\n const timeoutSecs = Math.round(timeSinceData / 1000);\n process.stderr.write(`\\r\\n\\x1b[31m✗ Connection timed out (no data for ${timeoutSecs}s)\\x1b[0m\\r\\n`);\n \n // Force close the connection\n try {\n ws.terminate();\n } catch {\n // Ignore\n }\n \n cleanup();\n safeResolve({ type: 'disconnected', reason: 'Connection timed out (no data received)' });\n }\n }, 5000);\n\n const handleResize = () => {\n const { cols, rows } = terminal.getSize();\n if (ws.readyState === WebSocket.OPEN) {\n ws.send(createResizeMessage(cols, rows));\n }\n };\n\n const cleanup = () => {\n connectionClosed = true;\n clearTimeout(connectionTimeout);\n clearInterval(healthCheckInterval);\n terminal.disableBracketedPaste();\n terminal.restore();\n terminal.offResize(handleResize);\n process.stdin.off('data', handleStdinData);\n process.off('SIGINT', handleSigint);\n process.off('SIGQUIT', handleSigquit);\n };\n\n const handleStdinData = (data: Buffer) => {\n if (ws.readyState !== WebSocket.OPEN) {\n return;\n }\n\n // Detect Ctrl+C (0x03) for double-tap exit\n // Check if buffer contains Ctrl+C (could be alone or with other bytes)\n const ctrlCIndex = data.indexOf(0x03);\n if (ctrlCIndex !== -1) {\n const now = Date.now();\n if (now - lastCtrlC < DOUBLE_CTRLC_MS) {\n // Double Ctrl+C detected - exit catty immediately\n userInterrupted = true;\n process.stderr.write('\\r\\n');\n cleanup();\n try {\n ws.close();\n } catch {\n // Ignore\n }\n safeResolve({ type: 'interrupted' });\n return;\n }\n lastCtrlC = now;\n // First Ctrl+C - just send to remote, no hint (cleaner UX)\n }\n\n try {\n const str = data.toString('utf-8');\n \n // Simple approach: check if this chunk contains paste markers\n if (!inPaste) {\n const pasteStartIdx = str.indexOf(PASTE_START);\n \n if (pasteStartIdx === -1) {\n // No paste sequence - send data through immediately\n ws.send(data);\n return;\n }\n \n // Found paste start\n if (pasteStartIdx > 0) {\n // Send content before paste\n ws.send(Buffer.from(str.slice(0, pasteStartIdx), 'utf-8'));\n }\n \n // Check if paste end is also in this chunk\n const afterStart = str.slice(pasteStartIdx + PASTE_START.length);\n const pasteEndIdx = afterStart.indexOf(PASTE_END);\n \n if (pasteEndIdx !== -1) {\n // Complete paste in one chunk\n const pastedContent = afterStart.slice(0, pasteEndIdx);\n handlePastedContent(pastedContent);\n \n // Send any content after paste end\n const afterEnd = afterStart.slice(pasteEndIdx + PASTE_END.length);\n if (afterEnd) {\n ws.send(Buffer.from(afterEnd, 'utf-8'));\n }\n } else {\n // Paste spans multiple chunks\n inPaste = true;\n pasteBuffer = afterStart;\n }\n } else {\n // We're in a paste, look for end\n const pasteEndIdx = str.indexOf(PASTE_END);\n \n if (pasteEndIdx === -1) {\n // Still no end - keep buffering\n pasteBuffer += str;\n } else {\n // Found end\n pasteBuffer += str.slice(0, pasteEndIdx);\n inPaste = false;\n handlePastedContent(pasteBuffer);\n pasteBuffer = '';\n \n // Send any content after paste end\n const afterEnd = str.slice(pasteEndIdx + PASTE_END.length);\n if (afterEnd) {\n ws.send(Buffer.from(afterEnd, 'utf-8'));\n }\n }\n }\n } catch (err) {\n // On error, try to forward data as-is to avoid breaking terminal\n try {\n ws.send(data);\n } catch {\n // Ignore\n }\n }\n };\n\n const uploadFile = async (filePath: string): Promise<string | null> => {\n const uploadInfo = shouldAutoUpload(filePath);\n\n debugLog(`shouldAutoUpload: ${uploadInfo.shouldUpload}, size: ${uploadInfo.content?.length || 0}`);\n\n if (\n !uploadInfo.shouldUpload ||\n !uploadInfo.content ||\n !uploadInfo.filename ||\n !uploadInfo.remotePath ||\n !uploadInfo.mimeType\n ) {\n return null;\n }\n\n // Generate unique filename to avoid collisions\n const uniqueFilename = generateUniqueFilename(uploadInfo.filename);\n const uniqueRemotePath = `/workspace/.catty-uploads/${uniqueFilename}`;\n\n debugLog(`uploading to: ${uniqueRemotePath}`);\n\n const fileSize = uploadInfo.content.length;\n \n // Use chunked upload for files larger than chunk size\n if (fileSize > CHUNK_SIZE) {\n const uploadId = `${Date.now()}-${Math.random().toString(36).slice(2)}`;\n const base64Content = uploadInfo.content.toString('base64');\n const totalChunks = Math.ceil(base64Content.length / (CHUNK_SIZE * 1.34)); // base64 is ~1.34x larger\n const chunkSize = Math.ceil(base64Content.length / totalChunks);\n \n debugLog(`chunked upload: ${totalChunks} chunks, ~${chunkSize} bytes each`);\n \n for (let i = 0; i < totalChunks; i++) {\n const start = i * chunkSize;\n const end = Math.min(start + chunkSize, base64Content.length);\n const chunk = base64Content.slice(start, end);\n \n const chunkMsg = createFileUploadChunkMessage(\n uploadId,\n uniqueFilename,\n uniqueRemotePath,\n i,\n totalChunks,\n chunk,\n uploadInfo.mimeType\n );\n \n ws.send(chunkMsg);\n debugLog(`sent chunk ${i + 1}/${totalChunks}`);\n \n // Small delay between chunks to avoid overwhelming the connection\n if (i < totalChunks - 1) {\n await new Promise(resolve => setTimeout(resolve, 1));\n }\n }\n } else {\n // Small file - send in one message\n const uploadMsg = createFileUploadMessage(\n uniqueFilename,\n uniqueRemotePath,\n uploadInfo.content,\n uploadInfo.mimeType\n );\n \n debugLog(`single message size: ${uploadMsg.length} bytes`);\n ws.send(uploadMsg);\n }\n\n return uniqueRemotePath;\n };\n\n const handlePastedContent = async (content: string) => {\n try {\n // Skip empty pastes\n if (!content || !content.trim()) {\n return;\n }\n\n // Check if the pasted content contains file paths\n const filePaths = detectFilePaths(content);\n\n if (filePaths.length > 0) {\n debugLog(`found ${filePaths.length} files to upload`);\n \n const uploadedPaths: string[] = [];\n \n // Upload all files\n for (const filePath of filePaths) {\n const remotePath = await uploadFile(filePath);\n if (remotePath) {\n uploadedPaths.push(remotePath);\n }\n }\n \n if (uploadedPaths.length > 0) {\n debugLog(`uploaded ${uploadedPaths.length} files, sending paths`);\n \n // Send all remote paths separated by spaces\n const pathsStr = uploadedPaths.join(' ');\n ws.send(Buffer.from(pathsStr, 'utf-8'));\n \n debugLog(`paths sent: ${pathsStr}`);\n return;\n }\n }\n\n // Not a file or upload not needed, send pasted content as-is\n ws.send(Buffer.from(content, 'utf-8'));\n } catch (err) {\n debugLog(`ERROR in handlePastedContent: ${err}`);\n // On any error, try to send original content to avoid breaking terminal\n try {\n ws.send(Buffer.from(content, 'utf-8'));\n } catch {\n // Ignore - can't do anything\n }\n }\n };\n\n ws.on('open', () => {\n // Connection established - clear the timeout\n connectionOpened = true;\n clearTimeout(connectionTimeout);\n\n // Enable sync-back if requested\n if (opts.syncBack) {\n ws.send(createSyncBackMessage(true));\n\n // Warn if no ack after timeout\n setTimeout(() => {\n if (!syncBackAcked) {\n process.stderr.write(\n '\\r\\n(sync-back) No ack from executor yet — this machine may be running an older catty-exec image without sync-back.\\r\\n'\n );\n }\n }, SYNC_BACK_ACK_TIMEOUT_MS);\n }\n\n // Enter raw mode\n terminal.makeRaw();\n\n // Enable bracketed paste mode for drag-and-drop file detection\n terminal.enableBracketedPaste();\n\n // Send initial size\n const { cols, rows } = terminal.getSize();\n ws.send(createResizeMessage(cols, rows));\n\n // Handle resize\n terminal.onResize(handleResize);\n\n // Relay stdin -> WebSocket\n process.stdin.on('data', handleStdinData);\n });\n\n // Relay WebSocket -> stdout\n ws.on('message', (data: WebSocket.RawData, isBinary: boolean) => {\n // Update last data received time for health monitoring\n lastDataReceived = Date.now();\n \n if (isBinary) {\n process.stdout.write(data as Buffer);\n } else {\n try {\n const msg = parseMessage(data.toString());\n handleControlMessage(msg);\n } catch {\n // Ignore parse errors\n }\n }\n });\n\n function handleControlMessage(msg: Message) {\n switch (msg.type) {\n case 'exit': {\n const exitMsg = msg as ExitMessage;\n exitCode = exitMsg.code;\n opts.onExit?.(exitMsg.code);\n process.stderr.write(`\\r\\nProcess exited with code ${exitMsg.code}\\r\\n`);\n cleanup();\n ws.close();\n safeResolve({ type: 'exit', code: exitMsg.code });\n break;\n }\n case 'error': {\n const errorMsg = msg as ErrorMessage;\n process.stderr.write(`\\r\\nError: ${errorMsg.message}\\r\\n`);\n break;\n }\n case 'ping':\n ws.send(createPongMessage());\n break;\n case 'file_change':\n applyRemoteFileChange(msg as FileChangeMessage);\n break;\n case 'sync_back_ack':\n syncBackAcked = true;\n break;\n }\n }\n\n ws.on('close', (code: number, reason: Buffer) => {\n if (connectionClosed) return; // Already handled\n \n cleanup();\n \n // If user pressed Ctrl+C, don't show disconnect message\n if (userInterrupted) {\n return; // Already resolved in handleSigint\n }\n \n // Code 1008 (WS_POLICY_VIOLATION) = connection replaced by new one\n if (code === WS_POLICY_VIOLATION) {\n process.stderr.write('\\r\\n\\x1b[33m⚠ Connection replaced by another client\\x1b[0m\\r\\n');\n safeResolve({ type: 'replaced' });\n } else {\n const reasonStr = reason?.toString() || `code ${code}`;\n process.stderr.write(`\\r\\n\\x1b[31m✗ Connection lost: ${reasonStr}\\x1b[0m\\r\\n`);\n safeResolve({ type: 'disconnected', reason: reasonStr });\n }\n });\n\n ws.on('error', (err: Error) => {\n if (connectionClosed) return; // Already handled\n \n cleanup();\n \n // If user pressed Ctrl+C, don't show error message\n if (userInterrupted) {\n return; // Already resolved in handleSigint\n }\n \n // Show user-friendly error message\n process.stderr.write(`\\r\\n\\x1b[31m✗ Connection error: ${err.message}\\x1b[0m\\r\\n`);\n safeResolve({ type: 'disconnected', reason: err.message });\n });\n\n // Handle process exit\n process.on('exit', () => {\n cleanup();\n if (ws.readyState === WebSocket.OPEN) {\n ws.close();\n }\n });\n });\n}\n","// Track if global handlers are registered (only register once)\nlet globalHandlersRegistered = false;\nlet activeTerminal: Terminal | null = null;\n\nexport class Terminal {\n private wasRaw = false;\n private cleanupDone = false;\n\n isTerminal(): boolean {\n return process.stdin.isTTY === true;\n }\n\n makeRaw(): void {\n if (!this.isTerminal()) return;\n if (this.wasRaw) return;\n\n process.stdin.setRawMode(true);\n process.stdin.resume();\n this.wasRaw = true;\n activeTerminal = this;\n\n // Register global handlers once\n if (!globalHandlersRegistered) {\n globalHandlersRegistered = true;\n \n const cleanup = () => {\n if (activeTerminal) {\n activeTerminal.restore();\n }\n };\n\n // Normal exit\n process.on('exit', cleanup);\n\n // Ctrl+C\n process.on('SIGINT', () => {\n cleanup();\n process.exit(130); // 128 + SIGINT(2)\n });\n\n // Kill signal\n process.on('SIGTERM', () => {\n cleanup();\n process.exit(143); // 128 + SIGTERM(15)\n });\n\n // Terminal window closed (SSH disconnect, etc.)\n process.on('SIGHUP', () => {\n cleanup();\n process.exit(129); // 128 + SIGHUP(1)\n });\n\n // Ctrl+Z - suspend (IMPORTANT: restore terminal before suspending)\n process.on('SIGTSTP', () => {\n cleanup();\n // Re-emit SIGTSTP with default handler to actually suspend\n process.kill(process.pid, 'SIGTSTP');\n });\n\n // When resumed after Ctrl+Z, re-enter raw mode\n process.on('SIGCONT', () => {\n if (activeTerminal && activeTerminal.wasRaw === false && !activeTerminal.cleanupDone) {\n // Terminal was suspended, but we've already cleaned up\n // Show a message to help the user\n process.stderr.write('\\r\\n\\x1b[33m⚠ Session suspended. Run \"fg\" or reconnect with \"catty connect <label>\"\\x1b[0m\\r\\n');\n }\n });\n\n // Uncaught exceptions - restore terminal before crashing\n process.on('uncaughtException', (err) => {\n cleanup();\n process.stderr.write(`\\r\\n\\x1b[31m✗ Unexpected error: ${err.message}\\x1b[0m\\r\\n`);\n process.stderr.write(`\\x1b[90mReconnect with: catty connect <session-label>\\x1b[0m\\r\\n`);\n process.exit(1);\n });\n\n // Unhandled promise rejections\n process.on('unhandledRejection', (reason) => {\n cleanup();\n const message = reason instanceof Error ? reason.message : String(reason);\n process.stderr.write(`\\r\\n\\x1b[31m✗ Unexpected error: ${message}\\x1b[0m\\r\\n`);\n process.stderr.write(`\\x1b[90mReconnect with: catty connect <session-label>\\x1b[0m\\r\\n`);\n process.exit(1);\n });\n }\n }\n\n restore(): void {\n if (this.cleanupDone) return;\n if (this.wasRaw && process.stdin.isTTY) {\n try {\n // Reset terminal state\n process.stdin.setRawMode(false);\n // Send terminal reset sequences\n process.stdout.write('\\x1b[?2004l'); // Disable bracketed paste\n process.stdout.write('\\x1b[?25h'); // Show cursor (in case it was hidden)\n } catch {\n // Ignore errors during cleanup\n }\n this.wasRaw = false;\n }\n this.cleanupDone = true;\n if (activeTerminal === this) {\n activeTerminal = null;\n }\n }\n\n /**\n * Force reset terminal to a known good state.\n * Call this if you suspect the terminal is corrupted.\n */\n static forceReset(): void {\n try {\n if (process.stdin.isTTY) {\n process.stdin.setRawMode(false);\n }\n // Reset all terminal modes\n process.stdout.write('\\x1b[?2004l'); // Disable bracketed paste\n process.stdout.write('\\x1b[?25h'); // Show cursor\n process.stdout.write('\\x1bc'); // Full terminal reset (RIS)\n } catch {\n // Best effort\n }\n }\n\n getSize(): { cols: number; rows: number } {\n return {\n cols: process.stdout.columns || 80,\n rows: process.stdout.rows || 24,\n };\n }\n\n onResize(callback: () => void): void {\n process.stdout.on('resize', callback);\n }\n\n offResize(callback: () => void): void {\n process.stdout.off('resize', callback);\n }\n\n /**\n * Enable bracketed paste mode.\n * When enabled, pasted text is wrapped in escape sequences:\n * - Start: \\x1b[200~\n * - End: \\x1b[201~\n * This allows detecting drag-and-drop file paths.\n */\n enableBracketedPaste(): void {\n process.stdout.write('\\x1b[?2004h');\n }\n\n /**\n * Disable bracketed paste mode.\n */\n disableBracketedPaste(): void {\n process.stdout.write('\\x1b[?2004l');\n }\n}\n","export const MessageType = {\n RESIZE: 'resize',\n SIGNAL: 'signal',\n PING: 'ping',\n PONG: 'pong',\n READY: 'ready',\n EXIT: 'exit',\n ERROR: 'error',\n SYNC_BACK: 'sync_back',\n SYNC_BACK_ACK: 'sync_back_ack',\n FILE_CHANGE: 'file_change',\n FILE_UPLOAD: 'file_upload',\n FILE_UPLOAD_CHUNK: 'file_upload_chunk',\n} as const;\n\nexport interface BaseMessage {\n type: string;\n}\n\nexport interface ResizeMessage {\n type: 'resize';\n cols: number;\n rows: number;\n}\n\nexport interface SignalMessage {\n type: 'signal';\n name: string;\n}\n\nexport interface PingMessage {\n type: 'ping';\n}\n\nexport interface PongMessage {\n type: 'pong';\n}\n\nexport interface ReadyMessage {\n type: 'ready';\n}\n\nexport interface ExitMessage {\n type: 'exit';\n code: number;\n signal: string | null;\n}\n\nexport interface ErrorMessage {\n type: 'error';\n message: string;\n}\n\nexport interface SyncBackMessage {\n type: 'sync_back';\n enabled: boolean;\n}\n\nexport interface SyncBackAckMessage {\n type: 'sync_back_ack';\n enabled: boolean;\n workspace_dir?: string;\n interval_ms?: number;\n}\n\nexport interface FileChangeMessage {\n type: 'file_change';\n action: 'write' | 'delete';\n path: string;\n content?: string; // base64 encoded\n mode?: number;\n}\n\nexport interface FileUploadMessage {\n type: 'file_upload';\n filename: string;\n remote_path: string;\n content: string; // base64 encoded\n mime_type: string;\n}\n\nexport interface FileUploadChunkMessage {\n type: 'file_upload_chunk';\n upload_id: string;\n filename: string;\n remote_path: string;\n chunk_index: number;\n total_chunks: number;\n content: string; // base64 encoded chunk\n mime_type: string;\n}\n\nexport type Message =\n | ResizeMessage\n | SignalMessage\n | PingMessage\n | PongMessage\n | ReadyMessage\n | ExitMessage\n | ErrorMessage\n | SyncBackMessage\n | SyncBackAckMessage\n | FileChangeMessage\n | FileUploadMessage\n | FileUploadChunkMessage\n | BaseMessage;\n\nexport function parseMessage(data: string): Message {\n const base = JSON.parse(data) as BaseMessage;\n\n switch (base.type) {\n case MessageType.RESIZE:\n return JSON.parse(data) as ResizeMessage;\n case MessageType.SIGNAL:\n return JSON.parse(data) as SignalMessage;\n case MessageType.PING:\n return { type: 'ping' } as PingMessage;\n case MessageType.PONG:\n return { type: 'pong' } as PongMessage;\n case MessageType.READY:\n return { type: 'ready' } as ReadyMessage;\n case MessageType.EXIT:\n return JSON.parse(data) as ExitMessage;\n case MessageType.ERROR:\n return JSON.parse(data) as ErrorMessage;\n case MessageType.SYNC_BACK:\n return JSON.parse(data) as SyncBackMessage;\n case MessageType.SYNC_BACK_ACK:\n return JSON.parse(data) as SyncBackAckMessage;\n case MessageType.FILE_CHANGE:\n return JSON.parse(data) as FileChangeMessage;\n case MessageType.FILE_UPLOAD:\n return JSON.parse(data) as FileUploadMessage;\n default:\n return base;\n }\n}\n\nexport function createResizeMessage(cols: number, rows: number): string {\n return JSON.stringify({ type: MessageType.RESIZE, cols, rows });\n}\n\nexport function createSignalMessage(name: string): string {\n return JSON.stringify({ type: MessageType.SIGNAL, name });\n}\n\nexport function createPingMessage(): string {\n return JSON.stringify({ type: MessageType.PING });\n}\n\nexport function createPongMessage(): string {\n return JSON.stringify({ type: MessageType.PONG });\n}\n\nexport function createSyncBackMessage(enabled: boolean): string {\n return JSON.stringify({ type: MessageType.SYNC_BACK, enabled });\n}\n\nexport function createFileUploadMessage(\n filename: string,\n remotePath: string,\n content: Buffer,\n mimeType: string\n): string {\n return JSON.stringify({\n type: MessageType.FILE_UPLOAD,\n filename,\n remote_path: remotePath,\n content: content.toString('base64'),\n mime_type: mimeType,\n });\n}\n\nexport function createFileUploadChunkMessage(\n uploadId: string,\n filename: string,\n remotePath: string,\n chunkIndex: number,\n totalChunks: number,\n content: string, // already base64 encoded\n mimeType: string\n): string {\n return JSON.stringify({\n type: MessageType.FILE_UPLOAD_CHUNK,\n upload_id: uploadId,\n filename,\n remote_path: remotePath,\n chunk_index: chunkIndex,\n total_chunks: totalChunks,\n content,\n mime_type: mimeType,\n });\n}\n","import { writeFileSync, unlinkSync, mkdirSync, renameSync } from 'fs';\nimport { join, dirname, normalize, isAbsolute, sep } from 'path';\nimport type { FileChangeMessage } from '../protocol/messages.js';\n\n/**\n * Apply a remote file change to the local filesystem.\n * Best-effort: errors are logged but don't break the terminal.\n */\nexport function applyRemoteFileChange(msg: FileChangeMessage): void {\n // Validate path (no absolute, no traversal)\n const rel = normalize(msg.path.replace(/^\\.\\//, ''));\n\n if (!rel || rel === '.') return;\n\n if (isAbsolute(rel)) {\n console.error(`sync-back rejected absolute path: ${rel}`);\n return;\n }\n\n if (rel === '..' || rel.startsWith('..' + sep)) {\n console.error(`sync-back rejected traversal path: ${rel}`);\n return;\n }\n\n const cwd = process.cwd();\n const destPath = join(cwd, rel);\n\n // Ensure destPath is within cwd (resolve any remaining symlinks/tricks)\n const resolvedCwd = normalize(cwd);\n const resolvedDest = normalize(destPath);\n\n if (!resolvedDest.startsWith(resolvedCwd + sep) && resolvedDest !== resolvedCwd) {\n console.error(`sync-back rejected path outside base: ${destPath}`);\n return;\n }\n\n try {\n if (msg.action === 'delete') {\n unlinkSync(destPath);\n } else if (msg.action === 'write') {\n const content = Buffer.from(msg.content || '', 'base64');\n mkdirSync(dirname(destPath), { recursive: true });\n\n // Atomic write via temp file\n const tmpPath = join(dirname(destPath), `.catty-sync-${Date.now()}`);\n writeFileSync(tmpPath, content, { mode: msg.mode || 0o644 });\n renameSync(tmpPath, destPath);\n }\n } catch {\n // Best-effort, don't break terminal\n }\n}\n","import { readFileSync, statSync, appendFileSync } from 'fs';\nimport { basename, extname } from 'path';\nimport { homedir } from 'os';\n\n// Debug logging to file (avoids terminal corruption)\nfunction debugLog(msg: string): void {\n if (process.env.CATTY_DEBUG === '1') {\n const logFile = `${homedir()}/.catty-debug.log`;\n appendFileSync(logFile, `${new Date().toISOString()} ${msg}\\n`);\n }\n}\n\n// Supported file types for auto-upload\nconst IMAGE_EXTENSIONS = ['.png', '.jpg', '.jpeg', '.gif', '.webp', '.bmp', '.svg'];\nconst DOCUMENT_EXTENSIONS = ['.pdf', '.txt', '.md', '.json', '.xml', '.csv'];\nconst SUPPORTED_EXTENSIONS = [...IMAGE_EXTENSIONS, ...DOCUMENT_EXTENSIONS];\n\n// Max file size for auto-upload (10MB)\nconst MAX_FILE_SIZE = 10 * 1024 * 1024;\n\n// Chunk size for large file uploads (10KB raw = ~13KB base64)\nexport const CHUNK_SIZE = 10 * 1024;\n\nexport interface FileUploadResult {\n shouldUpload: boolean;\n localPath?: string;\n remotePath?: string;\n content?: Buffer;\n filename?: string;\n mimeType?: string;\n}\n\n/**\n * Detects if input contains a file path that should be uploaded\n * Returns the first valid file path found\n */\nexport function detectFilePath(input: string): string | null {\n const paths = detectFilePaths(input);\n return paths.length > 0 ? paths[0] : null;\n}\n\n/**\n * Detects all file paths in input that could be uploaded\n * Returns array of valid file paths\n */\nexport function detectFilePaths(input: string): string[] {\n const trimmed = input.trim();\n \n debugLog(`detectFilePaths input: ${JSON.stringify(trimmed)}`);\n \n // Handle escaped paths (e.g., /path/to/Screenshot\\ 2024-12-17\\ at\\ 10.30.png)\n // Split on unescaped spaces to handle multiple files\n // An unescaped space is a space NOT preceded by a backslash\n const rawPaths = splitOnUnescapedSpaces(trimmed);\n \n debugLog(`split paths: ${JSON.stringify(rawPaths)}`);\n \n const validPaths: string[] = [];\n \n for (const rawPath of rawPaths) {\n // Unescape the path (convert \"\\ \" to \" \")\n const unescaped = rawPath.replace(/\\\\ /g, ' ');\n \n // Skip empty\n if (!unescaped) continue;\n \n // Handle tilde expansion\n const expanded = unescaped.startsWith('~') \n ? unescaped.replace(/^~/, process.env.HOME || '~')\n : unescaped;\n \n // Check if it's an absolute path\n if (!expanded.startsWith('/') && !expanded.match(/^[A-Za-z]:\\\\/)) {\n debugLog(`skipping non-absolute: ${expanded}`);\n continue;\n }\n \n // Check if file exists\n try {\n statSync(expanded);\n debugLog(`found file: ${expanded}`);\n validPaths.push(expanded);\n } catch (err) {\n debugLog(`file not found: ${expanded} - ${err}`);\n // File doesn't exist, try next\n continue;\n }\n }\n\n debugLog(`found ${validPaths.length} valid files`);\n return validPaths;\n}\n\n/**\n * Split string on unescaped spaces (spaces not preceded by backslash)\n */\nfunction splitOnUnescapedSpaces(input: string): string[] {\n const results: string[] = [];\n let current = '';\n let i = 0;\n \n while (i < input.length) {\n if (input[i] === '\\\\' && i + 1 < input.length && input[i + 1] === ' ') {\n // Escaped space - keep it (including the backslash for now)\n current += '\\\\ ';\n i += 2;\n } else if (input[i] === ' ') {\n // Unescaped space - split here\n if (current) {\n results.push(current);\n current = '';\n }\n i++;\n } else {\n current += input[i];\n i++;\n }\n }\n \n if (current) {\n results.push(current);\n }\n \n return results;\n}\n\n/**\n * Checks if a file should be auto-uploaded based on extension and size\n */\nexport function shouldAutoUpload(filePath: string): FileUploadResult {\n try {\n // Check if file exists\n const stats = statSync(filePath);\n\n // Check if it's a file (not a directory)\n if (!stats.isFile()) {\n return { shouldUpload: false };\n }\n\n // Check file size\n if (stats.size > MAX_FILE_SIZE) {\n return { shouldUpload: false };\n }\n\n // Check extension\n const ext = extname(filePath).toLowerCase();\n if (!SUPPORTED_EXTENSIONS.includes(ext)) {\n return { shouldUpload: false };\n }\n\n // Read file content\n const content = readFileSync(filePath);\n const filename = basename(filePath);\n const remotePath = `/workspace/.catty-uploads/${filename}`;\n const mimeType = getMimeType(ext);\n\n return {\n shouldUpload: true,\n localPath: filePath,\n remotePath,\n content,\n filename,\n mimeType,\n };\n } catch {\n // File doesn't exist or can't be read\n return { shouldUpload: false };\n }\n}\n\n/**\n * Get MIME type from file extension\n */\nfunction getMimeType(ext: string): string {\n const mimeTypes: Record<string, string> = {\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.gif': 'image/gif',\n '.webp': 'image/webp',\n '.bmp': 'image/bmp',\n '.svg': 'image/svg+xml',\n '.pdf': 'application/pdf',\n '.txt': 'text/plain',\n '.md': 'text/markdown',\n '.json': 'application/json',\n '.xml': 'application/xml',\n '.csv': 'text/csv',\n };\n\n return mimeTypes[ext.toLowerCase()] || 'application/octet-stream';\n}\n\n/**\n * Generate a unique filename to avoid collisions\n * Also sanitizes the filename to remove spaces and special characters\n */\nexport function generateUniqueFilename(originalFilename: string): string {\n const timestamp = Date.now();\n const ext = extname(originalFilename);\n const nameWithoutExt = basename(originalFilename, ext);\n // Sanitize: replace spaces with underscores, remove other problematic chars\n const sanitized = nameWithoutExt\n .replace(/\\s+/g, '_') // spaces -> underscores\n .replace(/[^a-zA-Z0-9_-]/g, ''); // remove other special chars\n return `${sanitized}-${timestamp}${ext}`;\n}\n\n","import archiver from 'archiver';\nimport ignore, { type Ignore } from 'ignore';\nimport { createReadStream, readFileSync, readdirSync, statSync } from 'fs';\nimport { join, relative } from 'path';\nimport { MAX_UPLOAD_SIZE } from './config.js';\n\nconst DEFAULT_IGNORES = [\n '.git',\n '.git/**',\n 'node_modules',\n 'node_modules/**',\n '__pycache__',\n '__pycache__/**',\n '.venv',\n '.venv/**',\n 'venv',\n 'venv/**',\n '.env',\n '*.pyc',\n '.DS_Store',\n '*.log',\n];\n\nexport async function createWorkspaceZip(dir: string): Promise<Buffer> {\n const ig = ignore().add(DEFAULT_IGNORES);\n\n // Load .gitignore if exists\n try {\n const gitignore = readFileSync(join(dir, '.gitignore'), 'utf-8');\n ig.add(gitignore);\n } catch {\n // No .gitignore, use defaults only\n }\n\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n const archive = archiver('zip', { zlib: { level: 9 } });\n\n archive.on('data', (chunk: Buffer) => chunks.push(chunk));\n archive.on('end', () => resolve(Buffer.concat(chunks)));\n archive.on('error', reject);\n\n // Walk directory and add files\n walkDir(dir, dir, ig, archive);\n archive.finalize();\n });\n}\n\nfunction walkDir(\n baseDir: string,\n currentDir: string,\n ig: Ignore,\n archive: archiver.Archiver\n): void {\n let entries: string[];\n try {\n entries = readdirSync(currentDir);\n } catch {\n return;\n }\n\n for (const entry of entries) {\n const fullPath = join(currentDir, entry);\n const relativePath = relative(baseDir, fullPath);\n\n // Check if ignored\n if (ig.ignores(relativePath)) {\n continue;\n }\n\n let stat;\n try {\n stat = statSync(fullPath);\n } catch {\n continue;\n }\n\n if (stat.isDirectory()) {\n // Also check directory with trailing slash\n if (ig.ignores(relativePath + '/')) {\n continue;\n }\n walkDir(baseDir, fullPath, ig, archive);\n } else if (stat.isFile()) {\n archive.file(fullPath, { name: relativePath });\n }\n }\n}\n\nexport async function uploadWorkspace(\n uploadURL: string,\n token: string,\n machineID: string\n): Promise<void> {\n const cwd = process.cwd();\n const zipData = await createWorkspaceZip(cwd);\n\n if (zipData.length > MAX_UPLOAD_SIZE) {\n throw new Error(\n `Workspace too large (${zipData.length} bytes, max ${MAX_UPLOAD_SIZE})`\n );\n }\n\n const response = await fetch(uploadURL, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/zip',\n 'fly-force-instance-id': machineID,\n },\n body: zipData,\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`Upload failed: ${response.status} - ${text}`);\n }\n}\n\n/**\n * Build upload URL from connect URL.\n * Converts wss://app.fly.dev/connect to https://app.fly.dev/upload\n */\nexport function buildUploadURL(connectURL: string): string {\n return connectURL\n .replace('wss://', 'https://')\n .replace('ws://', 'http://')\n .replace('/connect', '/upload');\n}\n","import { Command } from 'commander';\nimport { getAPIAddr, sleep } from '../lib/config.js';\nimport { isLoggedIn } from '../lib/auth.js';\nimport { APIClient } from '../lib/api-client.js';\nimport { connectToSession, type ConnectionResult } from '../lib/websocket.js';\n\nconst MAX_RECONNECT_ATTEMPTS = 5;\nconst RECONNECT_DELAY_MS = 2000;\n\nexport const connectCommand = new Command('connect')\n .description('Reconnect to an existing session')\n .argument('<label>', 'Session label (e.g., brave-tiger-1234)')\n .option('--sync-back', 'Sync remote file changes back to local', true)\n .option('--no-sync-back', 'Disable sync-back')\n .option('--no-auto-reconnect', 'Disable automatic reconnection on disconnect')\n .action(async function (this: Command, label: string) {\n const opts = this.opts();\n const apiAddr = getAPIAddr(this.optsWithGlobals().api);\n const autoReconnect = opts.autoReconnect !== false;\n\n if (!isLoggedIn()) {\n console.error(\"Not logged in. Please run 'catty login' first.\");\n process.exit(1);\n }\n\n const client = new APIClient(apiAddr);\n let reconnectAttempts = 0;\n\n while (true) {\n try {\n console.log(`Looking up session ${label}...`);\n const session = await client.getSession(label, true);\n\n if (session.status === 'stopped') {\n throw new Error(`Session ${session.label} is stopped`);\n }\n if (session.machine_state && session.machine_state !== 'started') {\n throw new Error(`Machine is not running (state: ${session.machine_state})`);\n }\n\n if (reconnectAttempts > 0) {\n console.log(`\\x1b[32m✓ Reconnected to ${session.label}\\x1b[0m`);\n } else {\n console.log(`Connecting to ${session.label}...`);\n }\n\n const result: ConnectionResult = await connectToSession({\n connectURL: session.connect_url,\n connectToken: session.connect_token!,\n headers: { 'fly-force-instance-id': session.machine_id },\n syncBack: opts.syncBack,\n });\n\n // Handle the connection result\n if (result.type === 'exit') {\n // Clean exit - process ended normally\n process.exit(result.code);\n } else if (result.type === 'interrupted') {\n // User pressed Ctrl+C - exit cleanly, don't reconnect\n process.exit(130);\n } else if (result.type === 'replaced') {\n // Connection was replaced by another client - don't reconnect\n console.log('Session taken over by another client.');\n process.exit(0);\n } else if (result.type === 'disconnected') {\n // Connection lost - try to reconnect\n if (!autoReconnect) {\n console.error(`Disconnected: ${result.reason}`);\n process.exit(1);\n }\n\n reconnectAttempts++;\n if (reconnectAttempts > MAX_RECONNECT_ATTEMPTS) {\n console.error(`\\x1b[31m✗ Failed to reconnect after ${MAX_RECONNECT_ATTEMPTS} attempts\\x1b[0m`);\n console.error(`Run 'catty connect ${label}' to try again manually.`);\n process.exit(1);\n }\n\n console.log(`\\x1b[33m⟳ Reconnecting (${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})...\\x1b[0m`);\n await sleep(RECONNECT_DELAY_MS);\n // Loop continues to reconnect\n }\n } catch (err) {\n if (reconnectAttempts > 0 && autoReconnect) {\n reconnectAttempts++;\n if (reconnectAttempts > MAX_RECONNECT_ATTEMPTS) {\n console.error(`\\x1b[31m✗ Failed to reconnect after ${MAX_RECONNECT_ATTEMPTS} attempts\\x1b[0m`);\n process.exit(1);\n }\n console.error(`\\x1b[33m⟳ Reconnect failed, retrying (${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})...\\x1b[0m`);\n await sleep(RECONNECT_DELAY_MS);\n } else {\n throw err;\n }\n }\n }\n });\n","import { Command } from 'commander';\nimport { getAPIAddr } from '../lib/config.js';\nimport { APIClient } from '../lib/api-client.js';\n\nexport const listCommand = new Command('list')\n .aliases(['ls'])\n .description('List all sessions')\n .action(async function (this: Command) {\n const apiAddr = getAPIAddr(this.optsWithGlobals().api);\n const client = new APIClient(apiAddr);\n\n const sessions = await client.listSessions();\n\n if (sessions.length === 0) {\n console.log('No sessions found');\n return;\n }\n\n // Simple table output\n const header = 'LABEL STATUS REGION CREATED';\n console.log(header);\n\n for (const s of sessions) {\n const age = formatAge(new Date(s.created_at));\n const row = [\n s.label.padEnd(22),\n s.status.padEnd(9),\n s.region.padEnd(7),\n age,\n ].join(' ');\n console.log(row);\n }\n });\n\n/**\n * Human-readable time ago formatting\n */\nfunction formatAge(date: Date): string {\n const seconds = Math.floor((Date.now() - date.getTime()) / 1000);\n\n if (seconds < 60) return `${seconds}s ago`;\n const minutes = Math.floor(seconds / 60);\n if (minutes < 60) return `${minutes}m ago`;\n const hours = Math.floor(minutes / 60);\n if (hours < 24) return `${hours}h ago`;\n const days = Math.floor(hours / 24);\n return `${days}d ago`;\n}\n","import { Command } from 'commander';\nimport { getAPIAddr } from '../lib/config.js';\nimport { APIClient } from '../lib/api-client.js';\n\nexport const stopCommand = new Command('stop')\n .description('Stop a session')\n .argument('<label>', 'Session ID or label')\n .option('--delete', 'Delete the machine after stopping', false)\n .action(async function (this: Command, label: string) {\n const opts = this.opts();\n const apiAddr = getAPIAddr(this.optsWithGlobals().api);\n const client = new APIClient(apiAddr);\n\n await client.stopSession(label, opts.delete);\n\n if (opts.delete) {\n console.log(`Session ${label} stopped and deleted`);\n } else {\n console.log(`Session ${label} stopped`);\n }\n });\n","import { Command } from 'commander';\nimport { getAPIAddr } from '../lib/config.js';\nimport { APIClient } from '../lib/api-client.js';\n\nexport const stopAllCommand = new Command('stop-all-sessions-dangerously')\n .description('Stop and delete ALL sessions')\n .option('--yes-i-mean-it', 'Confirm you want to stop all sessions', false)\n .action(async function (this: Command) {\n const opts = this.opts();\n const apiAddr = getAPIAddr(this.optsWithGlobals().api);\n\n if (!opts.yesIMeanIt) {\n throw new Error('Must pass --yes-i-mean-it to confirm');\n }\n\n const client = new APIClient(apiAddr);\n const sessions = await client.listSessions();\n\n if (sessions.length === 0) {\n console.log('No sessions to stop');\n return;\n }\n\n console.log(`Stopping ${sessions.length} sessions...`);\n\n for (const s of sessions) {\n process.stdout.write(` Stopping ${s.session_id}... `);\n try {\n await client.stopSession(s.session_id, true);\n console.log('done');\n } catch (err) {\n console.log(`ERROR: ${err}`);\n }\n }\n });\n","import { Command } from 'commander';\nimport open from 'open';\nimport { getAPIAddr, sleep } from '../lib/config.js';\nimport { isLoggedIn, loadCredentials, saveCredentials } from '../lib/auth.js';\nimport type { DeviceAuthResponse, TokenResponse } from '../types/index.js';\n\nexport const loginCommand = new Command('login')\n .description('Log in to Catty')\n .action(async function (this: Command) {\n const apiAddr = getAPIAddr(this.optsWithGlobals().api);\n\n if (isLoggedIn()) {\n const creds = loadCredentials();\n console.log(`Already logged in as ${creds?.email}`);\n console.log(\"Run 'catty logout' to log out first\");\n return;\n }\n\n console.log('Starting login...');\n\n // Step 1: Start device auth flow\n const authResp = await fetch(`${apiAddr}/v1/auth/device`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: '{}',\n });\n\n if (!authResp.ok) {\n throw new Error(`Failed to start auth: ${authResp.statusText}`);\n }\n\n const auth: DeviceAuthResponse = await authResp.json();\n\n // Step 2: Show code and open browser\n console.log('\\nYour confirmation code:\\n');\n console.log(` ${auth.user_code}\\n`);\n console.log(`Opening ${auth.verification_uri_complete}\\n`);\n\n await open(auth.verification_uri_complete);\n console.log('Waiting for authentication...');\n\n // Step 3: Poll for token\n const interval = (auth.interval || 5) * 1000;\n const deadline = Date.now() + auth.expires_in * 1000;\n\n while (Date.now() < deadline) {\n await sleep(interval);\n\n const tokenResp = await fetch(`${apiAddr}/v1/auth/device/token`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ device_code: auth.device_code }),\n });\n\n const token: TokenResponse = await tokenResp.json();\n\n if (token.pending) continue;\n if (token.error) throw new Error(token.error);\n\n if (token.access_token) {\n saveCredentials({\n access_token: token.access_token,\n refresh_token: token.refresh_token,\n user_id: token.user?.id || '',\n email: token.user?.email || '',\n expires_at: token.expires_in\n ? new Date(Date.now() + (token.expires_in - 30) * 1000).toISOString()\n : undefined,\n });\n console.log(`\\nLogged in as ${token.user?.email}`);\n console.log(\"You can now run 'catty new' to start a session\");\n return;\n }\n }\n\n throw new Error('Authentication timed out');\n });\n","import { Command } from 'commander';\nimport { isLoggedIn, loadCredentials, deleteCredentials } from '../lib/auth.js';\n\nexport const logoutCommand = new Command('logout')\n .description('Log out of Catty')\n .action(async () => {\n if (!isLoggedIn()) {\n console.log('Not logged in');\n return;\n }\n\n const creds = loadCredentials();\n const email = creds?.email || '';\n\n deleteCredentials();\n\n if (email) {\n console.log(`Logged out from ${email}`);\n } else {\n console.log('Logged out');\n }\n });\n","import { Command } from 'commander';\n\n// VERSION is replaced at build time by tsup\ndeclare const __VERSION__: string;\n\nexport const versionCommand = new Command('version')\n .description('Print the version number')\n .action(() => {\n console.log(typeof __VERSION__ !== 'undefined' ? __VERSION__ : 'dev');\n });\n","import { Command } from 'commander';\nimport { checkForUpdate, runUpdate } from '../lib/version-checker.js';\n\ndeclare const __VERSION__: string;\n\nexport const updateCommand = new Command('update')\n .description('Update catty to the latest version')\n .action(async () => {\n try {\n const currentVersion = typeof __VERSION__ !== 'undefined' ? __VERSION__ : 'dev';\n\n if (currentVersion === 'dev') {\n console.log('Cannot update in development mode.');\n return;\n }\n\n console.log('Checking for updates...');\n const { updateAvailable, latestVersion } = await checkForUpdate({\n bypassCache: true,\n });\n\n if (!updateAvailable || !latestVersion) {\n console.log(`You are already using the latest version (${currentVersion}).`);\n return;\n }\n\n // When manually running update command, always update regardless of declined status\n await runUpdate(currentVersion, latestVersion);\n } catch (err) {\n if (err instanceof Error) {\n console.error(`Error: ${err.message}`);\n }\n process.exit(1);\n }\n });\n","import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport { createInterface } from 'readline';\nimport { spawn } from 'child_process';\nimport { CREDENTIALS_DIR } from './config.js';\n\ndeclare const __VERSION__: string;\n\nfunction logDebug(message: string): void {\n if (process.env.DEBUG || process.env.CATTY_DEBUG) {\n console.log(`\\x1b[2m[DEBUG] ${message}\\x1b[0m`);\n }\n}\n\ninterface VersionCache {\n latestVersion: string;\n lastChecked: number;\n declinedVersion?: string;\n declinedAt?: number;\n}\n\nconst VERSION_CHECK_INTERVAL = 15 * 60 * 1000; // 15 minutes\nconst DECLINED_VERSION_REMINDER_INTERVAL = 2 * 24 * 60 * 60 * 1000; // 2 days\nconst NPM_REGISTRY_URL = 'https://registry.npmjs.org/@diggerhq/catty/latest';\n\nfunction getVersionCachePath(): string {\n return join(homedir(), CREDENTIALS_DIR, 'version-cache.json');\n}\n\nfunction getCachedVersion(): VersionCache | null {\n try {\n const cachePath = getVersionCachePath();\n if (!existsSync(cachePath)) {\n return null;\n }\n const data = readFileSync(cachePath, 'utf-8');\n return JSON.parse(data);\n } catch {\n return null;\n }\n}\n\nfunction setCachedVersion(version: string, declined?: boolean): void {\n try {\n const cachePath = getVersionCachePath();\n const cacheDir = join(homedir(), CREDENTIALS_DIR);\n if (!existsSync(cacheDir)) {\n mkdirSync(cacheDir, { recursive: true });\n }\n\n // Preserve existing declined info if not updating it\n const existing = getCachedVersion();\n const cache: VersionCache = {\n latestVersion: version,\n lastChecked: Date.now(),\n declinedVersion: declined ? version : existing?.declinedVersion,\n declinedAt: declined ? Date.now() : existing?.declinedAt,\n };\n\n writeFileSync(cachePath, JSON.stringify(cache, null, 2));\n } catch {\n // Silently fail if we can't write cache\n }\n}\n\nasync function fetchLatestVersion(): Promise<string | null> {\n try {\n const response = await fetch(NPM_REGISTRY_URL, {\n signal: AbortSignal.timeout(5000), // 5 second timeout\n });\n if (!response.ok) {\n return null;\n }\n const data = await response.json();\n return data.version || null;\n } catch {\n return null;\n }\n}\n\nfunction compareVersions(current: string, latest: string): boolean {\n const currentParts = current.split('.').map(Number);\n const latestParts = latest.split('.').map(Number);\n\n for (let i = 0; i < 3; i++) {\n const curr = currentParts[i] || 0;\n const lat = latestParts[i] || 0;\n if (lat > curr) return true;\n if (lat < curr) return false;\n }\n return false;\n}\n\nexport async function checkForUpdate(options?: {\n bypassCache?: boolean;\n}): Promise<{\n updateAvailable: boolean;\n currentVersion: string;\n latestVersion: string | null;\n shouldPrompt: boolean;\n}> {\n const currentVersion =\n typeof __VERSION__ !== 'undefined' ? __VERSION__ : 'dev';\n\n // Don't check in dev mode\n if (currentVersion === 'dev') {\n return {\n updateAvailable: false,\n currentVersion,\n latestVersion: null,\n shouldPrompt: false,\n };\n }\n\n // Check cache first (unless bypassing)\n const cached = getCachedVersion();\n const now = Date.now();\n\n let latestVersion: string | null = null;\n\n if (!options?.bypassCache && cached && now - cached.lastChecked < VERSION_CHECK_INTERVAL) {\n // Use cached version\n latestVersion = cached.latestVersion;\n } else {\n // Fetch latest version\n latestVersion = await fetchLatestVersion();\n if (latestVersion) {\n setCachedVersion(latestVersion);\n }\n }\n\n if (!latestVersion) {\n return {\n updateAvailable: false,\n currentVersion,\n latestVersion: null,\n shouldPrompt: false,\n };\n }\n\n const updateAvailable = compareVersions(currentVersion, latestVersion);\n\n // Check if user previously declined this version\n let shouldPrompt = updateAvailable;\n if (updateAvailable && cached?.declinedVersion === latestVersion && cached.declinedAt) {\n // User declined this version - only prompt again after the reminder interval\n const timeSinceDeclined = now - cached.declinedAt;\n shouldPrompt = timeSinceDeclined >= DECLINED_VERSION_REMINDER_INTERVAL;\n }\n\n return {\n updateAvailable,\n currentVersion,\n latestVersion,\n shouldPrompt,\n };\n}\n\nexport function printUpdateAvailable(\n currentVersion: string,\n latestVersion: string\n): void {\n console.log('');\n console.log(`\\x1b[33mUpdate available:\\x1b[0m \\x1b[2m${currentVersion}\\x1b[0m → \\x1b[32m${latestVersion}\\x1b[0m`);\n}\n\nexport async function promptForUpdate(): Promise<boolean> {\n const rl = createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n return new Promise((resolve) => {\n rl.question('Would you like to update? (Y/n): ', (answer) => {\n rl.close();\n const normalized = answer.trim().toLowerCase();\n // Default to yes if empty or starts with 'y'\n resolve(normalized === '' || normalized === 'y' || normalized === 'yes');\n });\n });\n}\n\nexport function recordDeclinedUpdate(version: string): void {\n setCachedVersion(version, true);\n}\n\nfunction detectPackageManager(): string {\n // Check if npm is available\n const userAgent = process.env.npm_config_user_agent || '';\n\n if (userAgent.includes('yarn')) {\n return 'yarn';\n } else if (userAgent.includes('pnpm')) {\n return 'pnpm';\n } else if (userAgent.includes('bun')) {\n return 'bun';\n }\n\n return 'npm';\n}\n\nexport async function runUpdate(\n currentVersion: string,\n latestVersion: string\n): Promise<void> {\n console.log(`\\nUpdating from ${currentVersion} to ${latestVersion}...\\n`);\n\n const packageManager = detectPackageManager();\n const packageName = `@diggerhq/catty@${latestVersion}`;\n let command: string;\n let args: string[];\n\n switch (packageManager) {\n case 'yarn':\n command = 'yarn';\n args = ['global', 'add', packageName];\n break;\n case 'pnpm':\n command = 'pnpm';\n args = ['add', '-g', packageName];\n break;\n case 'bun':\n command = 'bun';\n args = ['install', '-g', packageName];\n break;\n default:\n command = 'npm';\n args = ['install', '-g', packageName];\n }\n\n logDebug(`Package manager: ${packageManager}`);\n logDebug(`Command: ${command} ${args.join(' ')}`);\n\n return new Promise((resolve, reject) => {\n const child = spawn(command, args, {\n stdio: 'inherit',\n shell: process.platform === 'win32',\n });\n\n child.on('close', (code) => {\n if (code === 0) {\n console.log(\n `\\n\\x1b[32m✓\\x1b[0m Successfully updated to version ${latestVersion}`\n );\n resolve();\n } else {\n console.error(\n `\\n\\x1b[31m✗\\x1b[0m Update failed with exit code ${code}`\n );\n reject(new Error(`Update process exited with code ${code}`));\n }\n });\n\n child.on('error', (err) => {\n console.error(\n `\\n\\x1b[31m✗\\x1b[0m Failed to run update command: ${err.message}`\n );\n reject(err);\n });\n });\n}\n"],"mappings":"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","names":["program","Command","open","DEFAULT_API_ADDR","CREDENTIALS_DIR","CREDENTIALS_FILE","getAPIAddr","cliOption","DEFAULT_API_ADDR","sleep","ms","resolve","homedir","join","readFileSync","writeFileSync","mkdirSync","unlinkSync","existsSync","getCredentialsDir","join","homedir","CREDENTIALS_DIR","getCredentialsPath","CREDENTIALS_FILE","loadCredentials","path","content","readFileSync","saveCredentials","creds","dir","mkdirSync","writeFileSync","deleteCredentials","existsSync","unlinkSync","isLoggedIn","getAccessToken","getRefreshToken","APIError","statusCode","errorCode","message","upgradeURL","APIClient","baseURL","DEFAULT_API_ADDR","getAccessToken","method","path","body","controller","timeoutId","headers","response","refreshToken","getRefreshToken","data","creds","loadCredentials","saveCredentials","errorData","req","idOrLabel","live","del","WebSocket","appendFileSync","homedir","globalHandlersRegistered","activeTerminal","Terminal","cleanup","err","reason","message","callback","MessageType","parseMessage","data","base","createResizeMessage","cols","rows","createPongMessage","MessageType","createSyncBackMessage","enabled","createFileUploadMessage","filename","remotePath","content","mimeType","createFileUploadChunkMessage","uploadId","chunkIndex","totalChunks","writeFileSync","unlinkSync","mkdirSync","renameSync","join","dirname","normalize","isAbsolute","sep","applyRemoteFileChange","msg","rel","cwd","destPath","resolvedCwd","resolvedDest","content","tmpPath","readFileSync","statSync","appendFileSync","basename","extname","homedir","debugLog","msg","logFile","IMAGE_EXTENSIONS","DOCUMENT_EXTENSIONS","SUPPORTED_EXTENSIONS","MAX_FILE_SIZE","CHUNK_SIZE","detectFilePaths","input","trimmed","debugLog","rawPaths","splitOnUnescapedSpaces","validPaths","rawPath","unescaped","expanded","statSync","err","results","current","i","shouldAutoUpload","filePath","stats","MAX_FILE_SIZE","ext","extname","SUPPORTED_EXTENSIONS","content","readFileSync","filename","basename","remotePath","mimeType","getMimeType","generateUniqueFilename","originalFilename","timestamp","debugLog","msg","logFile","homedir","appendFileSync","PASTE_START","PASTE_END","connectToSession","opts","terminal","Terminal","ws","WebSocket","resolve","reject","syncBackAcked","exitCode","connectionClosed","connectionOpened","userInterrupted","resolved","safeResolve","result","inPaste","pasteBuffer","inputBuffer","lastCtrlC","DOUBLE_CTRLC_MS","handleSigint","cleanup","handleSigquit","connectionTimeout","lastDataReceived","CLIENT_TIMEOUT_MS","healthCheckInterval","timeSinceData","timeoutSecs","handleResize","cols","rows","createResizeMessage","handleStdinData","data","now","str","pasteEndIdx","handlePastedContent","afterEnd","pasteStartIdx","afterStart","pastedContent","uploadFile","filePath","uploadInfo","shouldAutoUpload","uniqueFilename","generateUniqueFilename","uniqueRemotePath","CHUNK_SIZE","uploadId","base64Content","totalChunks","chunkSize","i","start","end","chunk","chunkMsg","createFileUploadChunkMessage","uploadMsg","createFileUploadMessage","content","filePaths","detectFilePaths","uploadedPaths","remotePath","pathsStr","err","createSyncBackMessage","isBinary","parseMessage","handleControlMessage","exitMsg","errorMsg","createPongMessage","applyRemoteFileChange","code","reason","reasonStr","archiver","ignore","readFileSync","readdirSync","statSync","join","relative","DEFAULT_IGNORES","createWorkspaceZip","dir","ig","ignore","gitignore","readFileSync","join","resolve","reject","chunks","archive","archiver","chunk","walkDir","baseDir","currentDir","entries","readdirSync","entry","fullPath","relativePath","relative","stat","statSync","uploadWorkspace","uploadURL","token","machineID","cwd","zipData","response","text","buildUploadURL","connectURL","MAX_RECONNECT_ATTEMPTS","RECONNECT_DELAY_MS","newCommand","Command","opts","apiAddr","getAPIAddr","autoReconnect","isLoggedIn","client","APIClient","cmdArgs","session","err","APIError","handleQuotaExceeded","uploadURL","buildUploadURL","uploadWorkspace","reconnectAttempts","result","connectToSession","sleep","checkoutURL","open","Command","MAX_RECONNECT_ATTEMPTS","RECONNECT_DELAY_MS","connectCommand","Command","label","opts","apiAddr","getAPIAddr","autoReconnect","isLoggedIn","client","APIClient","reconnectAttempts","session","result","connectToSession","sleep","err","Command","listCommand","Command","apiAddr","getAPIAddr","sessions","APIClient","age","formatAge","row","date","seconds","minutes","hours","Command","stopCommand","Command","label","opts","apiAddr","getAPIAddr","APIClient","Command","stopAllCommand","Command","opts","apiAddr","getAPIAddr","client","APIClient","sessions","err","Command","open","loginCommand","Command","apiAddr","getAPIAddr","isLoggedIn","creds","loadCredentials","authResp","auth","open","interval","deadline","sleep","token","saveCredentials","Command","logoutCommand","Command","isLoggedIn","email","loadCredentials","deleteCredentials","Command","versionCommand","Command","readFileSync","writeFileSync","existsSync","mkdirSync","join","homedir","createInterface","spawn","logDebug","message","VERSION_CHECK_INTERVAL","DECLINED_VERSION_REMINDER_INTERVAL","NPM_REGISTRY_URL","getVersionCachePath","join","homedir","CREDENTIALS_DIR","getCachedVersion","cachePath","existsSync","data","readFileSync","setCachedVersion","version","declined","cacheDir","mkdirSync","existing","cache","writeFileSync","fetchLatestVersion","response","compareVersions","current","latest","currentParts","latestParts","i","curr","lat","checkForUpdate","options","currentVersion","cached","now","latestVersion","updateAvailable","shouldPrompt","printUpdateAvailable","promptForUpdate","rl","createInterface","resolve","answer","normalized","recordDeclinedUpdate","detectPackageManager","userAgent","runUpdate","packageManager","packageName","command","args","reject","child","spawn","code","err","updateCommand","Command","currentVersion","updateAvailable","latestVersion","checkForUpdate","runUpdate","err","version","program","newCommand","connectCommand","listCommand","stopCommand","stopAllCommand","loginCommand","logoutCommand","versionCommand","updateCommand","main","command","updateAvailable","currentVersion","latestVersion","shouldPrompt","checkForUpdate","printUpdateAvailable","promptForUpdate","runUpdate","recordDeclinedUpdate","err"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diggerhq/catty",
3
- "version": "0.3.11",
3
+ "version": "0.3.13",
4
4
  "description": "Catty - Remote AI agent sessions",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",