@diggerhq/catty 0.4.0 → 0.4.1
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 +23 -23
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{program as
|
|
2
|
+
import{program as S}from"commander";import{Command as Ao}from"commander";import Po from"open";var ne="https://api.catty.dev",A=".catty",he="credentials.json",ye="secrets.json";function h(e){return e||(process.env.CATTY_API_ADDR?process.env.CATTY_API_ADDR:ne)}function $(e){return new Promise(t=>setTimeout(t,e))}import{homedir as Ut}from"os";import{join as we}from"path";import{readFileSync as Lt,writeFileSync as Ft,mkdirSync as Bt,unlinkSync as Gt,existsSync as zt}from"fs";function Se(){return we(Ut(),A)}function se(){return we(Se(),he)}function R(){let e=se();try{let t=Lt(e,"utf-8");return JSON.parse(t)}catch{return null}}function Y(e){let t=Se(),o=se();Bt(t,{recursive:!0,mode:448}),Ft(o,JSON.stringify(e,null,2),{mode:384})}function xe(){let e=se();zt(e)&&Gt(e)}function _(){let e=R();return!(!e||!e.access_token||e.expires_at&&!e.refresh_token&&new Date(e.expires_at)<=new Date)}function be(){return R()?.access_token||null}function _e(){return R()?.refresh_token||null}var N=class extends Error{constructor(o,n,r,i){super(r);this.statusCode=o;this.errorCode=n;this.upgradeURL=i;this.name="APIError"}isQuotaExceeded(){return this.statusCode===402&&this.errorCode==="quota_exceeded"}},y=class{baseURL;authToken;constructor(t){this.baseURL=t||process.env.CATTY_API_ADDR||ne,this.authToken=be()}async doRequest(t,o,n){let r=new AbortController,i=setTimeout(()=>r.abort(),12e4);try{let s={"Content-Type":"application/json"};return this.authToken&&(s.Authorization=`Bearer ${this.authToken}`),await fetch(`${this.baseURL}${o}`,{method:t,headers:s,body:n?JSON.stringify(n):void 0,signal:r.signal})}finally{clearTimeout(i)}}async doRequestWithRefresh(t,o,n){let r=await this.doRequest(t,o,n);return r.status===401&&await this.refreshAuthToken()&&(r=await this.doRequest(t,o,n)),r}async refreshAuthToken(){let t=_e();if(!t)return!1;try{let o=await this.doRequest("POST","/v1/auth/refresh",{refresh_token:t});if(!o.ok)return!1;let n=await o.json();if(!n.access_token)return!1;let r=R();return r&&(r.access_token=n.access_token,n.refresh_token&&(r.refresh_token=n.refresh_token),n.expires_in&&(r.expires_at=new Date(Date.now()+(n.expires_in-30)*1e3).toISOString()),Y(r),this.authToken=n.access_token),!0}catch{return!1}}async handleResponse(t){if(!t.ok){let o;try{o=await t.json()}catch{o={error:t.statusText}}throw new N(t.status,o.code||"",o.error||t.statusText,o.upgrade_url)}return t.json()}async createSession(t){let o=await this.doRequestWithRefresh("POST","/v1/sessions",t);return this.handleResponse(o)}async listSessions(){let t=await this.doRequestWithRefresh("GET","/v1/sessions");return this.handleResponse(t)}async getSession(t,o){let n=o?`/v1/sessions/${t}?live=true`:`/v1/sessions/${t}`,r=await this.doRequestWithRefresh("GET",n);return this.handleResponse(r)}async stopSession(t,o){let n=o?`/v1/sessions/${t}/stop?delete=true`:`/v1/sessions/${t}/stop`,r=await this.doRequestWithRefresh("POST",n);if(!r.ok){let i;try{i=await r.json()}catch{i={error:r.statusText}}throw new N(r.status,i.code||"",i.error||r.statusText)}}async createCheckoutSession(){let t=await this.doRequestWithRefresh("POST","/v1/checkout");return(await this.handleResponse(t)).url}async getSessionDownload(t){let o=await this.doRequestWithRefresh("GET",`/v1/sessions/${t}/download`);return this.handleResponse(o)}};import J from"ws";import{appendFileSync as lo}from"fs";import{homedir as po}from"os";var Ce=!1,I=null,q=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,I=this,!Ce)){Ce=!0;let t=()=>{I&&I.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",()=>{I&&I.wasRaw===!1&&!I.cleanupDone&&process.stderr.write(`\r
|
|
3
3
|
\x1B[33m\u26A0 Session suspended. Run "fg" or reconnect with "catty connect <label>"\x1B[0m\r
|
|
4
4
|
`)}),process.on("uncaughtException",o=>{t(),process.stderr.write(`\r
|
|
5
5
|
\x1B[31m\u2717 Unexpected error: ${o.message}\x1B[0m\r
|
|
@@ -7,40 +7,40 @@ import{program as w}from"commander";import{Command as Eo}from"commander";import
|
|
|
7
7
|
`),process.exit(1)}),process.on("unhandledRejection",o=>{t();let n=o instanceof Error?o.message:String(o);process.stderr.write(`\r
|
|
8
8
|
\x1B[31m\u2717 Unexpected error: ${n}\x1B[0m\r
|
|
9
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,
|
|
11
|
-
`)}}function
|
|
12
|
-
`)}}var
|
|
13
|
-
`)}}var
|
|
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,I===this&&(I=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 w={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 ve(e){let t=JSON.parse(e);switch(t.type){case w.RESIZE:return JSON.parse(e);case w.SIGNAL:return JSON.parse(e);case w.PING:return{type:"ping"};case w.PONG:return{type:"pong"};case w.READY:return{type:"ready"};case w.EXIT:return JSON.parse(e);case w.ERROR:return JSON.parse(e);case w.SYNC_BACK:return JSON.parse(e);case w.SYNC_BACK_ACK:return JSON.parse(e);case w.FILE_CHANGE:return JSON.parse(e);case w.FILE_UPLOAD:return JSON.parse(e);default:return t}}function re(e,t){return JSON.stringify({type:w.RESIZE,cols:e,rows:t})}function Re(){return JSON.stringify({type:w.PONG})}function ke(e){return JSON.stringify({type:w.SYNC_BACK,enabled:e})}function Ee(e,t,o,n){return JSON.stringify({type:w.FILE_UPLOAD,filename:e,remote_path:t,content:o.toString("base64"),mime_type:n})}function Te(e,t,o,n,r,i,s){return JSON.stringify({type:w.FILE_UPLOAD_CHUNK,upload_id:e,filename:t,remote_path:o,chunk_index:n,total_chunks:r,content:i,mime_type:s})}import{mkdirSync as jt,writeFileSync as Vt,unlinkSync as Ht,existsSync as Yt,chmodSync as qt}from"fs";import{dirname as Jt,join as $e}from"path";import{homedir as Kt}from"os";import{appendFileSync as Zt}from"fs";function D(e){if(process.env.CATTY_DEBUG==="1"){let t=`${Kt()}/.catty-debug.log`;Zt(t,`${new Date().toISOString()} [syncback] ${e}
|
|
11
|
+
`)}}function Ie(e){try{let t=e.path.replace(/^\/workspace\/?/,"");if(!t){D("ignoring change to workspace root");return}let o=$e(process.cwd(),t),n=process.cwd(),r=$e(n,t);if(!r.startsWith(n)){D(`SECURITY: attempted write outside cwd: ${r}`);return}if(e.action==="delete")Yt(o)&&(Ht(o),D(`deleted: ${t}`));else if(e.action==="write"){if(!e.content){D(`write without content: ${t}`);return}let i=Jt(o);jt(i,{recursive:!0});let s=Buffer.from(e.content,"base64");if(Vt(o,s),e.mode!==void 0)try{qt(o,e.mode)}catch{}D(`wrote: ${t} (${s.length} bytes)`)}}catch(t){D(`ERROR applying change: ${t}`)}}import{readFileSync as Xt,statSync as Ae,appendFileSync as Qt}from"fs";import{basename as Pe,extname as Me}from"path";import{homedir as eo}from"os";function O(e){if(process.env.CATTY_DEBUG==="1"){let t=`${eo()}/.catty-debug.log`;Qt(t,`${new Date().toISOString()} ${e}
|
|
12
|
+
`)}}var to=[".png",".jpg",".jpeg",".gif",".webp",".bmp",".svg"],oo=[".pdf",".txt",".md",".json",".xml",".csv"],no=[...to,...oo],so=10*1024*1024,ie=10*1024;function Ne(e){let t=e.trim();O(`detectFilePaths input: ${JSON.stringify(t)}`);let o=ro(t);O(`split paths: ${JSON.stringify(o)}`);let n=[];for(let r of o){let i=r.replace(/\\ /g," ");if(!i)continue;let s=i.startsWith("~")?i.replace(/^~/,process.env.HOME||"~"):i;if(!s.startsWith("/")&&!s.match(/^[A-Za-z]:\\/)){O(`skipping non-absolute: ${s}`);continue}try{Ae(s),O(`found file: ${s}`),n.push(s)}catch(c){O(`file not found: ${s} - ${c}`);continue}}return O(`found ${n.length} valid files`),n}function ro(e){let t=[],o="",n=0;for(;n<e.length;)e[n]==="\\"&&n+1<e.length&&e[n+1]===" "?(o+="\\ ",n+=2):e[n]===" "?(o&&(t.push(o),o=""),n++):(o+=e[n],n++);return o&&t.push(o),t}function De(e){try{let t=Ae(e);if(!t.isFile())return{shouldUpload:!1};if(t.size>so)return{shouldUpload:!1};let o=Me(e).toLowerCase();if(!no.includes(o))return{shouldUpload:!1};let n=Xt(e),r=Pe(e),i=`/workspace/.catty-uploads/${r}`,s=io(o);return{shouldUpload:!0,localPath:e,remotePath:i,content:n,filename:r,mimeType:s}}catch{return{shouldUpload:!1}}}function io(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 Oe(e){let t=Date.now(),o=Me(e);return`${Pe(e,o).replace(/\s+/g,"_").replace(/[^a-zA-Z0-9_-]/g,"")}-${t}${o}`}function b(e){if(process.env.CATTY_DEBUG==="1"){let t=`${po()}/.catty-debug.log`;lo(t,`${new Date().toISOString()} [ws] ${e}
|
|
13
|
+
`)}}var Ue="\x1B[200~",K="\x1B[201~";async function Z(e){let t=new q;if(!t.isTerminal())throw new Error("stdin is not a terminal");let o=new J(e.connectURL,{headers:{...e.headers,Authorization:`Bearer ${e.connectToken}`},handshakeTimeout:3e4});return new Promise((n,r)=>{let i=0,s=!1,c=!1,a=!1,m=!1,f=d=>{m||(m=!0,n(d))},x=!1,u="",G="",j=0,k=1e3,V=()=>{a=!0,e.syncBack&&process.stderr.write("\r\n\x1B[33mSync paused. Run `catty sync <label>` to pull latest changes.\x1B[0m\r\n"),E();try{o.close()}catch{}f({type:"interrupted"})};process.once("SIGINT",V);let H=()=>{a=!0,process.stderr.write(`\r
|
|
14
14
|
\x1B[33mForce quit (Ctrl+\\)\x1B[0m\r
|
|
15
|
-
`),e.syncBack&&process.stderr.write("\x1B[33mSync paused. Run `catty sync <label>` to pull latest changes.\x1B[0m\r\n"),
|
|
15
|
+
`),e.syncBack&&process.stderr.write("\x1B[33mSync paused. Run `catty sync <label>` to pull latest changes.\x1B[0m\r\n"),E();try{o.close()}catch{}f({type:"interrupted"})};process.once("SIGQUIT",H);let le=setTimeout(()=>{if(!c&&!s){s=!0,process.stderr.write(`\r
|
|
16
16
|
\x1B[31m\u2717 Connection timeout: server not responding\x1B[0m\r
|
|
17
|
-
`);try{o.terminate()}catch{}
|
|
17
|
+
`);try{o.terminate()}catch{}f({type:"disconnected",reason:"Connection timeout"})}},3e4),de=Date.now(),It=75e3,pe=setInterval(()=>{if(s)return;let d=Date.now()-de;if(d>It){b(`Client-side timeout: no data for ${d}ms`),clearInterval(pe);let l=Math.round(d/1e3);process.stderr.write(`\r
|
|
18
18
|
\x1B[31m\u2717 Connection timed out (no data for ${l}s)\x1B[0m\r
|
|
19
|
-
`);try{o.terminate()}catch{}
|
|
20
|
-
`),e.syncBack&&process.stderr.write("\x1B[33mSync paused. Run `catty sync <label>` to pull latest changes.\x1B[0m\r\n"),
|
|
19
|
+
`);try{o.terminate()}catch{}E(),f({type:"disconnected",reason:"Connection timed out (no data received)"})}},5e3),ue=()=>{let{cols:d,rows:l}=t.getSize();o.readyState===J.OPEN&&o.send(re(d,l))},E=()=>{s=!0,clearTimeout(le),clearInterval(pe),t.disableBracketedPaste(),t.restore(),t.offResize(ue),process.stdin.off("data",me),process.off("SIGINT",V),process.off("SIGQUIT",H)},me=d=>{if(o.readyState!==J.OPEN)return;if(d.indexOf(3)!==-1){let p=Date.now();if(p-j<k){a=!0,process.stderr.write(`\r
|
|
20
|
+
`),e.syncBack&&process.stderr.write("\x1B[33mSync paused. Run `catty sync <label>` to pull latest changes.\x1B[0m\r\n"),E();try{o.close()}catch{}f({type:"interrupted"});return}j=p}try{let p=d.toString("utf-8");if(x){let g=p.indexOf(K);if(g===-1)u+=p;else{u+=p.slice(0,g),x=!1,fe(u),u="";let C=p.slice(g+K.length);C&&o.send(Buffer.from(C,"utf-8"))}}else{let g=p.indexOf(Ue);if(g===-1){o.send(d);return}g>0&&o.send(Buffer.from(p.slice(0,g),"utf-8"));let C=p.slice(g+Ue.length),T=C.indexOf(K);if(T!==-1){let P=C.slice(0,T);fe(P);let v=C.slice(T+K.length);v&&o.send(Buffer.from(v,"utf-8"))}else x=!0,u=C}}catch{try{o.send(d)}catch{}}},At=async d=>{let l=De(d);if(b(`shouldAutoUpload: ${l.shouldUpload}, size: ${l.content?.length||0}`),!l.shouldUpload||!l.content||!l.filename||!l.remotePath||!l.mimeType)return null;let p=Oe(l.filename),g=`/workspace/.catty-uploads/${p}`;if(b(`uploading to: ${g}`),l.content.length>ie){let T=`${Date.now()}-${Math.random().toString(36).slice(2)}`,P=l.content.toString("base64"),v=Math.ceil(P.length/(ie*1.34)),oe=Math.ceil(P.length/v);b(`chunked upload: ${v} chunks, ~${oe} bytes each`);for(let M=0;M<v;M++){let ge=M*oe,Mt=Math.min(ge+oe,P.length),Nt=P.slice(ge,Mt),Dt=Te(T,p,g,M,v,Nt,l.mimeType);o.send(Dt),b(`sent chunk ${M+1}/${v}`),M<v-1&&await new Promise(Ot=>setTimeout(Ot,1))}}else{let T=Ee(p,g,l.content,l.mimeType);b(`single message size: ${T.length} bytes`),o.send(T)}return g},fe=async d=>{try{if(!d||!d.trim())return;let l=Ne(d);if(l.length>0){b(`found ${l.length} files to upload`);let p=[];for(let g of l){let C=await At(g);C&&p.push(C)}if(p.length>0){b(`uploaded ${p.length} files, sending paths`);let g=p.join(" ");o.send(Buffer.from(g,"utf-8")),b(`paths sent: ${g}`);return}}o.send(Buffer.from(d,"utf-8"))}catch(l){b(`ERROR in handlePastedContent: ${l}`);try{o.send(Buffer.from(d,"utf-8"))}catch{}}};o.on("open",()=>{c=!0,clearTimeout(le),t.makeRaw(),t.enableBracketedPaste();let{cols:d,rows:l}=t.getSize();o.send(re(d,l)),e.syncBack&&(b("requesting sync-back"),o.send(ke(!0))),t.onResize(ue),process.stdin.on("data",me)}),o.on("message",(d,l)=>{if(de=Date.now(),l)process.stdout.write(d);else try{let p=ve(d.toString());Pt(p)}catch{}});function Pt(d){switch(d.type){case"exit":{let l=d;i=l.code,e.onExit?.(l.code),process.stderr.write(`\r
|
|
21
21
|
Process exited with code ${l.code}\r
|
|
22
|
-
`),
|
|
22
|
+
`),E(),o.close(),f({type:"exit",code:l.code});break}case"error":{let l=d;process.stderr.write(`\r
|
|
23
23
|
Error: ${l.message}\r
|
|
24
|
-
`);break}case"ping":o.send(
|
|
24
|
+
`);break}case"ping":o.send(Re());break;case"sync_back_ack":{let l=d;b(`sync-back ack: enabled=${l.enabled}, dir=${l.workspace_dir}`);break}case"file_change":{let l=d;b(`file change: ${l.action} ${l.path}`),Ie(l);break}}}o.on("close",(d,l)=>{if(!s&&(E(),!a))if(d===1008)process.stderr.write(`\r
|
|
25
25
|
\x1B[33m\u26A0 Connection replaced by another client\x1B[0m\r
|
|
26
|
-
`),
|
|
26
|
+
`),f({type:"replaced"});else{let p=l?.toString()||`code ${d}`;process.stderr.write(`\r
|
|
27
27
|
\x1B[31m\u2717 Connection lost: ${p}\x1B[0m\r
|
|
28
|
-
`),
|
|
28
|
+
`),f({type:"disconnected",reason:p})}}),o.on("error",d=>{s||(E(),!a&&(process.stderr.write(`\r
|
|
29
29
|
\x1B[31m\u2717 Connection error: ${d.message}\x1B[0m\r
|
|
30
|
-
`),y({type:"disconnected",reason:d.message})))}),process.on("exit",()=>{k(),o.readyState===z.OPEN&&o.close()})})}import ao from"archiver";import co from"ignore";import{readFileSync as lo,readdirSync as po,statSync as uo}from"fs";import{join as Oe,relative as mo}from"path";var fo=[".git",".git/**","node_modules","node_modules/**","__pycache__","__pycache__/**",".venv",".venv/**","venv","venv/**",".env","*.pyc",".DS_Store","*.log"];async function go(e){let t=co().add(fo);try{let o=lo(Oe(e,".gitignore"),"utf-8");t.add(o)}catch{}return new Promise((o,n)=>{let r=[],i=ao("zip",{zlib:{level:9}});i.on("data",s=>r.push(s)),i.on("end",()=>o(Buffer.concat(r))),i.on("error",n),Ue(e,e,t,i),i.finalize()})}function Ue(e,t,o,n){let r;try{r=po(t)}catch{return}for(let i of r){let s=Oe(t,i),c=mo(e,s);if(o.ignores(c))continue;let a;try{a=uo(s)}catch{continue}if(a.isDirectory()){if(o.ignores(c+"/"))continue;Ue(e,s,o,n)}else a.isFile()&&n.file(s,{name:c})}}async function Le(e,t,o){let n=process.cwd(),r=await go(n);if(r.length>1073741824)throw new Error(`Workspace too large (${r.length} bytes, max ${1073741824})`);let i=await fetch(e,{method:"POST",headers:{Authorization:`Bearer ${t}`,"Content-Type":"application/zip","fly-force-instance-id":o},body:r});if(!i.ok){let s=await i.text();throw new Error(`Upload failed: ${i.status} - ${s}`)}}function Fe(e){return e.replace("wss://","https://").replace("ws://","http://").replace("/connect","/upload")}import{homedir as Be,hostname as ho}from"os";import{join as Ge}from"path";import{readFileSync as yo,writeFileSync as wo,mkdirSync as So,existsSync as os,unlinkSync as ns}from"fs";import{createCipheriv as xo,createDecipheriv as bo,randomBytes as _o,scryptSync as Co,createHash as ko}from"crypto";var ze="aes-256-gcm",vo="catty-secrets-v1";function We(){let e=ko("sha256").update(`${ho()}:${Be()}:catty-machine-key`).digest("hex");return Co(e,vo,32)}function Ve(){return Ge(Be(),T)}function je(){return Ge(Ve(),fe)}function L(){let e=je();try{let t=yo(e,"utf-8");return JSON.parse(t)}catch{return{version:1,secrets:{}}}}function He(e){let t=Ve(),o=je();So(t,{recursive:!0,mode:448}),wo(o,JSON.stringify(e,null,2),{mode:384})}function Ro(e){let t=We(),o=_o(16),n=xo(ze,t,o),r=n.update(e,"utf8","base64");r+=n.final("base64");let i=n.getAuthTag().toString("base64");return`v1:${o.toString("base64")}:${i}:${r}`}function Ye(e){try{let[t,o,n,r]=e.split(":");if(t!=="v1")return null;let i=We(),s=bo(ze,i,Buffer.from(o,"base64"));s.setAuthTag(Buffer.from(n,"base64"));let c=s.update(r,"base64","utf8");return c+=s.final("utf8"),c}catch{return null}}function F(e,t){let o=L();o.secrets[e]=Ro(t),He(o)}function te(e){let o=L().secrets[e];return o?Ye(o):null}function j(e){let t=L();return e in t.secrets?(delete t.secrets[e],He(t),!0):!1}function H(){let e=L();return Object.keys(e.secrets)}function qe(){let e=L(),t={};for(let o of Object.keys(e.secrets)){let n=Ye(e.secrets[o]);n!==null&&(t[o]=n)}return t}async function oe(e){try{let t=await fetch("https://api.github.com/user",{headers:{Authorization:`Bearer ${e}`,Accept:"application/vnd.github.v3+json","User-Agent":"catty-cli"}});return t.status===401?{valid:!1,error:"Invalid token"}:t.ok?{valid:!0,username:(await t.json()).login}:{valid:!1,error:`GitHub API error: ${t.status}`}}catch(t){return{valid:!1,error:t instanceof Error?t.message:"Unknown error"}}}var M=5,Je=2e3,Ke=new Eo("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-auto-reconnect","Disable automatic reconnection on disconnect").option("--no-secrets","Don't pass stored secrets to session").option("--no-sync-back","Don't sync remote file changes back to local").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),o=e.autoReconnect!==!1;x()||(console.error("Not logged in. Please run 'catty login' first."),process.exit(1));let n=new g(t);console.log("Creating session...");let r;switch(e.agent){case"claude":e.enablePrompts?r=["claude-wrapper"]:r=["claude-wrapper","--dangerously-skip-permissions"];break;case"codex":r=["codex"];break;default:console.error(`Unknown agent: ${e.agent} (must be 'claude' or 'codex')`),process.exit(1)}let i;if(e.secrets!==!1){i=qe();let a=H();a.length>0&&console.log(`Secrets: ${a.join(", ")}`)}let s;try{s=await n.createSession({agent:e.agent,cmd:r,region:"iad",ttl_sec:7200,secrets:i})}catch(a){if(a instanceof I&&a.isQuotaExceeded()){await $o(n);return}throw a}if(console.log(`Session created: ${s.label}`),console.log(` Reconnect with: catty connect ${s.label}`),e.syncBack&&console.log(" Sync-back: enabled (remote changes will sync to local)"),e.upload!==!1){console.log("Uploading workspace...");let a=Fe(s.connect_url);await Le(a,s.connect_token,s.headers["fly-force-instance-id"]),console.log("Workspace uploaded.")}console.log(`Connecting to ${s.connect_url}...`);let c=0;for(;;)try{let a=await V({connectURL:s.connect_url,connectToken:s.connect_token,headers:s.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"){o||(console.error(`Disconnected: ${a.reason}`),process.exit(1)),c++,c>M&&(console.error(`\x1B[31m\u2717 Failed to reconnect after ${M} attempts\x1B[0m`),console.error(`Run 'catty connect ${s.label}' to try again manually.`),process.exit(1)),console.log(`\x1B[33m\u27F3 Reconnecting (${c}/${M})...\x1B[0m`),await R(Je);try{s=await n.getSession(s.label,!0),s.status==="stopped"&&(console.error("\x1B[31m\u2717 Session has stopped\x1B[0m"),process.exit(1))}catch{}}}catch(a){if(c>0&&o)c++,c>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 (${c}/${M})...\x1B[0m`),await R(Je);else throw a}});async function $o(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 To(t)}catch(t){console.error(`Failed to create checkout session: ${t}`),console.error("Please visit https://catty.dev to upgrade.")}}import{Command as Ao}from"commander";var D=5,Ze=2e3,Xe=new Ao("connect").description("Reconnect to an existing session").argument("<label>","Session label (e.g., brave-tiger-1234)").option("--no-auto-reconnect","Disable automatic reconnection on disconnect").option("--no-sync-back","Don't sync remote file changes back to local").action(async function(e){let t=this.opts(),o=f(this.optsWithGlobals().api),n=t.autoReconnect!==!1;x()||(console.error("Not logged in. Please run 'catty login' first."),process.exit(1));let r=new g(o),i=0;for(;;)try{console.log(`Looking up session ${e}...`);let s=await r.getSession(e,!0);if(s.status==="stopped")throw new Error(`Session ${s.label} is stopped`);if(s.machine_state&&s.machine_state!=="started")throw new Error(`Machine is not running (state: ${s.machine_state})`);i>0?console.log(`\x1B[32m\u2713 Reconnected to ${s.label}\x1B[0m`):(console.log(`Connecting to ${s.label}...`),t.syncBack&&console.log(" Sync-back: enabled (remote changes will sync to local)"));let c=await V({connectURL:s.connect_url,connectToken:s.connect_token,headers:{"fly-force-instance-id":s.machine_id},syncBack:t.syncBack!==!1});c.type==="exit"?process.exit(c.code):c.type==="interrupted"?process.exit(130):c.type==="replaced"?(console.log("Session taken over by another client."),process.exit(0)):c.type==="disconnected"&&(n||(console.error(`Disconnected: ${c.reason}`),process.exit(1)),i++,i>D&&(console.error(`\x1B[31m\u2717 Failed to reconnect after ${D} attempts\x1B[0m`),console.error(`Run 'catty connect ${e}' to try again manually.`),process.exit(1)),console.log(`\x1B[33m\u27F3 Reconnecting (${i}/${D})...\x1B[0m`),await R(Ze))}catch(s){if(i>0&&n)i++,i>D&&(console.error(`\x1B[31m\u2717 Failed to reconnect after ${D} attempts\x1B[0m`),process.exit(1)),console.error(`\x1B[33m\u27F3 Reconnect failed, retrying (${i}/${D})...\x1B[0m`),await R(Ze);else throw s}});import{Command as Io}from"commander";var Qe=new Io("list").aliases(["ls"]).description("List all sessions").action(async function(){let e=f(this.optsWithGlobals().api),o=await new g(e).listSessions();if(o.length===0){console.log("No sessions found");return}console.log("LABEL STATUS REGION CREATED");for(let r of o){let i=Po(new Date(r.created_at)),s=[r.label.padEnd(22),r.status.padEnd(9),r.region.padEnd(7),i].join(" ");console.log(s)}});function Po(e){let t=Math.floor((Date.now()-e.getTime())/1e3);if(t<60)return`${t}s ago`;let o=Math.floor(t/60);if(o<60)return`${o}m ago`;let n=Math.floor(o/60);return n<24?`${n}h ago`:`${Math.floor(n/24)}d ago`}import{Command as No}from"commander";var et=new No("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(),o=f(this.optsWithGlobals().api);await new g(o).stopSession(e,t.delete),t.delete?console.log(`Session ${e} stopped and deleted`):console.log(`Session ${e} stopped`)});import{Command as Mo}from"commander";var tt=new Mo("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 o=new g(t),n=await o.listSessions();if(n.length===0){console.log("No sessions to stop");return}console.log(`Stopping ${n.length} sessions...`);for(let r of n){process.stdout.write(` Stopping ${r.session_id}... `);try{await o.stopSession(r.session_id,!0),console.log("done")}catch(i){console.log(`ERROR: ${i}`)}}});import{Command as Do}from"commander";import Oo from"open";var ot=new Do("login").description("Log in to Catty").action(async function(){let e=f(this.optsWithGlobals().api);if(x()){let i=C();console.log(`Already logged in as ${i?.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 o=await t.json();console.log(`
|
|
30
|
+
`),f({type:"disconnected",reason:d.message})))}),process.on("exit",()=>{E(),o.readyState===J.OPEN&&o.close()})})}import uo from"archiver";import mo from"ignore";import{readFileSync as fo,readdirSync as go,statSync as ho}from"fs";import{join as Fe,relative as yo}from"path";var wo=[".git",".git/**","node_modules","node_modules/**","__pycache__","__pycache__/**",".venv",".venv/**","venv","venv/**",".env","*.pyc",".DS_Store","*.log"];async function So(e){let t=mo().add(wo);try{let o=fo(Fe(e,".gitignore"),"utf-8");t.add(o)}catch{}return new Promise((o,n)=>{let r=[],i=uo("zip",{zlib:{level:9}});i.on("data",s=>r.push(s)),i.on("end",()=>o(Buffer.concat(r))),i.on("error",n),Be(e,e,t,i),i.finalize()})}function Be(e,t,o,n){let r;try{r=go(t)}catch{return}for(let i of r){let s=Fe(t,i),c=yo(e,s);if(o.ignores(c))continue;let a;try{a=ho(s)}catch{continue}if(a.isDirectory()){if(o.ignores(c+"/"))continue;Be(e,s,o,n)}else a.isFile()&&n.file(s,{name:c})}}async function Ge(e,t,o){let n=process.cwd(),r=await So(n);if(r.length>1073741824)throw new Error(`Workspace too large (${r.length} bytes, max ${1073741824})`);let i=await fetch(e,{method:"POST",headers:{Authorization:`Bearer ${t}`,"Content-Type":"application/zip","fly-force-instance-id":o},body:r});if(!i.ok){let s=await i.text();throw new Error(`Upload failed: ${i.status} - ${s}`)}}function ze(e){return e.replace("wss://","https://").replace("ws://","http://").replace("/connect","/upload")}import{homedir as We,hostname as xo}from"os";import{join as je}from"path";import{readFileSync as bo,writeFileSync as _o,mkdirSync as Co,existsSync as as,unlinkSync as cs}from"fs";import{createCipheriv as vo,createDecipheriv as Ro,randomBytes as ko,scryptSync as Eo,createHash as To}from"crypto";var Ve="aes-256-gcm",$o="catty-secrets-v1";function He(){let e=To("sha256").update(`${xo()}:${We()}:catty-machine-key`).digest("hex");return Eo(e,$o,32)}function Ye(){return je(We(),A)}function qe(){return je(Ye(),ye)}function z(){let e=qe();try{let t=bo(e,"utf-8");return JSON.parse(t)}catch{return{version:1,secrets:{}}}}function Je(e){let t=Ye(),o=qe();Co(t,{recursive:!0,mode:448}),_o(o,JSON.stringify(e,null,2),{mode:384})}function Io(e){let t=He(),o=ko(16),n=vo(Ve,t,o),r=n.update(e,"utf8","base64");r+=n.final("base64");let i=n.getAuthTag().toString("base64");return`v1:${o.toString("base64")}:${i}:${r}`}function Ke(e){try{let[t,o,n,r]=e.split(":");if(t!=="v1")return null;let i=He(),s=Ro(Ve,i,Buffer.from(o,"base64"));s.setAuthTag(Buffer.from(n,"base64"));let c=s.update(r,"base64","utf8");return c+=s.final("utf8"),c}catch{return null}}function W(e,t){let o=z();o.secrets[e]=Io(t),Je(o)}function ae(e){let o=z().secrets[e];return o?Ke(o):null}function X(e){let t=z();return e in t.secrets?(delete t.secrets[e],Je(t),!0):!1}function Q(){let e=z();return Object.keys(e.secrets)}function Ze(){let e=z(),t={};for(let o of Object.keys(e.secrets)){let n=Ke(e.secrets[o]);n!==null&&(t[o]=n)}return t}async function ce(e){try{let t=await fetch("https://api.github.com/user",{headers:{Authorization:`Bearer ${e}`,Accept:"application/vnd.github.v3+json","User-Agent":"catty-cli"}});return t.status===401?{valid:!1,error:"Invalid token"}:t.ok?{valid:!0,username:(await t.json()).login}:{valid:!1,error:`GitHub API error: ${t.status}`}}catch(t){return{valid:!1,error:t instanceof Error?t.message:"Unknown error"}}}var U=5,Xe=2e3,Qe=new Ao("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-auto-reconnect","Disable automatic reconnection on disconnect").option("--no-secrets","Don't pass stored secrets to session").option("--no-sync-back","Don't sync remote file changes back to local").option("--enable-prompts","Enable permission prompts (by default, all permissions are auto-approved)",!1).action(async function(){let e=this.opts(),t=h(this.optsWithGlobals().api),o=e.autoReconnect!==!1;_()||(console.error("Not logged in. Please run 'catty login' first."),process.exit(1));let n=new y(t);console.log("Creating session...");let r;switch(e.agent){case"claude":e.enablePrompts?r=["claude-wrapper"]:r=["claude-wrapper","--dangerously-skip-permissions"];break;case"codex":r=["codex"];break;default:console.error(`Unknown agent: ${e.agent} (must be 'claude' or 'codex')`),process.exit(1)}let i;if(e.secrets!==!1){i=Ze();let a=Q();a.length>0&&console.log(`Secrets: ${a.join(", ")}`)}let s;try{s=await n.createSession({agent:e.agent,cmd:r,region:"iad",ttl_sec:7200,secrets:i})}catch(a){if(a instanceof N&&a.isQuotaExceeded()){await Mo(n);return}throw a}if(console.log(`Session created: ${s.label}`),console.log(` Reconnect with: catty connect ${s.label}`),e.syncBack&&console.log(" Sync-back: enabled (remote changes will sync to local)"),e.upload!==!1){console.log("Uploading workspace...");let a=ze(s.connect_url);await Ge(a,s.connect_token,s.headers["fly-force-instance-id"]),console.log("Workspace uploaded.")}console.log(`Connecting to ${s.connect_url}...`);let c=0;for(;;)try{let a=await Z({connectURL:s.connect_url,connectToken:s.connect_token,headers:s.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"){o||(console.error(`Disconnected: ${a.reason}`),process.exit(1)),c++,c>U&&(console.error(`\x1B[31m\u2717 Failed to reconnect after ${U} attempts\x1B[0m`),console.error(`Run 'catty connect ${s.label}' to try again manually.`),process.exit(1)),console.log(`\x1B[33m\u27F3 Reconnecting (${c}/${U})...\x1B[0m`),await $(Xe);try{s=await n.getSession(s.label,!0),s.status==="stopped"&&(console.error("\x1B[31m\u2717 Session has stopped\x1B[0m"),process.exit(1))}catch{}}}catch(a){if(c>0&&o)c++,c>U&&(console.error(`\x1B[31m\u2717 Failed to reconnect after ${U} attempts\x1B[0m`),process.exit(1)),console.error(`\x1B[33m\u27F3 Reconnect failed, retrying (${c}/${U})...\x1B[0m`),await $(Xe);else throw a}});async function Mo(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 Po(t)}catch(t){console.error(`Failed to create checkout session: ${t}`),console.error("Please visit https://catty.dev to upgrade.")}}import{Command as Oo}from"commander";import*as et from"readline";var L=10;function No(e){let t=Math.floor((Date.now()-e.getTime())/1e3);if(t<60)return`${t}s ago`;let o=Math.floor(t/60);if(o<60)return`${o}m ago`;let n=Math.floor(o/60);return n<24?`${n}h ago`:`${Math.floor(n/24)}d ago`}function Do(e){let t=No(new Date(e.created_at)),o=e.status==="running"?"\x1B[32m":e.status==="stopped"?"\x1B[31m":"\x1B[33m";return`${e.label.padEnd(24)} ${o}${e.status.padEnd(10)}\x1B[0m ${e.region.padEnd(8)} ${t}`}async function tt(e){if(e.length===0)return console.log("No sessions found."),null;let t=[...e].sort((o,n)=>new Date(n.created_at).getTime()-new Date(o.created_at).getTime());return t.length===0?(console.log("No sessions available."),null):new Promise(o=>{let n=0,r=0,i=t,s=Math.ceil(i.length/L),c=()=>{let x=r*L;return i.slice(x,x+L)},a=()=>{let x=c(),u=L+2;process.stdout.write(`\x1B[${u}A`);let G=s>1?` \x1B[2m(page ${r+1}/${s})\x1B[0m`:"";console.log(`\x1B[1mSelect a session to connect:\x1B[0m${G} `);for(let k=0;k<L;k++)if(k<x.length){let V=k===n?"\x1B[36m\u276F ":" ",H=k===n?"\x1B[0m":"";console.log(`${V}${Do(x[k])}${H} `)}else console.log(" ");let j=s>1?"\u2190/\u2192 pages, ":"";console.log(`\x1B[2m${j}\u2191/\u2193 navigate, Enter select, q/Esc cancel\x1B[0m `)};for(let x=0;x<L+2;x++)console.log("");a(),process.stdin.isTTY&&(et.emitKeypressEvents(process.stdin),process.stdin.setRawMode(!0));let m=()=>{process.stdin.isTTY&&process.stdin.setRawMode(!1),process.stdin.removeListener("keypress",f)},f=(x,u)=>{if(!u)return;let G=c();u.name==="up"||u.name==="k"?(n=Math.max(0,n-1),a()):u.name==="down"||u.name==="j"?(n=Math.min(G.length-1,n+1),a()):u.name==="left"||u.name==="h"?r>0&&(r--,n=0,a()):u.name==="right"||u.name==="l"?r<s-1&&(r++,n=0,a()):u.name==="return"?(m(),o(G[n])):(u.name==="escape"||u.name==="q"||u.ctrl&&u.name==="c")&&(m(),u.ctrl&&u.name==="c"&&(console.log(""),process.exit(130)),o(null))};process.stdin.on("keypress",f),process.stdin.resume()})}var F=5,ot=2e3,nt=new Oo("connect").description("Reconnect to an existing session").argument("[label]","Session label (e.g., brave-tiger-1234)").option("--no-auto-reconnect","Disable automatic reconnection on disconnect").option("--no-sync-back","Don't sync remote file changes back to local").action(async function(e){let t=this.opts(),o=h(this.optsWithGlobals().api),n=t.autoReconnect!==!1;_()||(console.error("Not logged in. Please run 'catty login' first."),process.exit(1));let r=new y(o),i=e;if(!i){let c=await r.listSessions(),a=await tt(c);a||process.exit(0),i=a.label,console.log("")}let s=0;for(;;)try{console.log(`Looking up session ${i}...`);let c=await r.getSession(i,!0);if(c.status==="stopped")throw new Error(`Session ${c.label} is stopped`);if(c.machine_state&&c.machine_state!=="started")throw new Error(`Machine is not running (state: ${c.machine_state})`);s>0?console.log(`\x1B[32m\u2713 Reconnected to ${c.label}\x1B[0m`):(console.log(`Connecting to ${c.label}...`),t.syncBack&&console.log(" Sync-back: enabled (remote changes will sync to local)"));let a=await Z({connectURL:c.connect_url,connectToken:c.connect_token,headers:{"fly-force-instance-id":c.machine_id},syncBack:t.syncBack!==!1});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"&&(n||(console.error(`Disconnected: ${a.reason}`),process.exit(1)),s++,s>F&&(console.error(`\x1B[31m\u2717 Failed to reconnect after ${F} attempts\x1B[0m`),console.error(`Run 'catty connect ${i}' to try again manually.`),process.exit(1)),console.log(`\x1B[33m\u27F3 Reconnecting (${s}/${F})...\x1B[0m`),await $(ot))}catch(c){if(s>0&&n)s++,s>F&&(console.error(`\x1B[31m\u2717 Failed to reconnect after ${F} attempts\x1B[0m`),process.exit(1)),console.error(`\x1B[33m\u27F3 Reconnect failed, retrying (${s}/${F})...\x1B[0m`),await $(ot);else throw c}});import{Command as Uo}from"commander";var st=new Uo("list").aliases(["ls"]).description("List all sessions").action(async function(){let e=h(this.optsWithGlobals().api),o=await new y(e).listSessions();if(o.length===0){console.log("No sessions found");return}console.log("LABEL STATUS REGION CREATED");for(let r of o){let i=Lo(new Date(r.created_at)),s=[r.label.padEnd(22),r.status.padEnd(9),r.region.padEnd(7),i].join(" ");console.log(s)}});function Lo(e){let t=Math.floor((Date.now()-e.getTime())/1e3);if(t<60)return`${t}s ago`;let o=Math.floor(t/60);if(o<60)return`${o}m ago`;let n=Math.floor(o/60);return n<24?`${n}h ago`:`${Math.floor(n/24)}d ago`}import{Command as Fo}from"commander";var rt=new Fo("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(),o=h(this.optsWithGlobals().api);await new y(o).stopSession(e,t.delete),t.delete?console.log(`Session ${e} stopped and deleted`):console.log(`Session ${e} stopped`)});import{Command as Bo}from"commander";var it=new Bo("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=h(this.optsWithGlobals().api);if(!e.yesIMeanIt)throw new Error("Must pass --yes-i-mean-it to confirm");let o=new y(t),n=await o.listSessions();if(n.length===0){console.log("No sessions to stop");return}console.log(`Stopping ${n.length} sessions...`);for(let r of n){process.stdout.write(` Stopping ${r.session_id}... `);try{await o.stopSession(r.session_id,!0),console.log("done")}catch(i){console.log(`ERROR: ${i}`)}}});import{Command as Go}from"commander";import zo from"open";var at=new Go("login").description("Log in to Catty").action(async function(){let e=h(this.optsWithGlobals().api);if(_()){let i=R();console.log(`Already logged in as ${i?.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 o=await t.json();console.log(`
|
|
31
31
|
Your confirmation code:
|
|
32
32
|
`),console.log(` ${o.user_code}
|
|
33
33
|
`),console.log(`Opening ${o.verification_uri_complete}
|
|
34
|
-
`),await
|
|
35
|
-
Logged in as ${s.user?.email}`),console.log("You can now run 'catty new' to start a session");return}}}throw new Error("Authentication timed out")});import{Command as
|
|
34
|
+
`),await zo(o.verification_uri_complete),console.log("Waiting for authentication...");let n=(o.interval||5)*1e3,r=Date.now()+o.expires_in*1e3;for(;Date.now()<r;){await $(n);let s=await(await fetch(`${e}/v1/auth/device/token`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({device_code:o.device_code})})).json();if(!s.pending){if(s.error)throw new Error(s.error);if(s.access_token){Y({access_token:s.access_token,refresh_token:s.refresh_token,user_id:s.user?.id||"",email:s.user?.email||"",expires_at:s.expires_in?new Date(Date.now()+(s.expires_in-30)*1e3).toISOString():void 0}),console.log(`
|
|
35
|
+
Logged in as ${s.user?.email}`),console.log("You can now run 'catty new' to start a session");return}}}throw new Error("Authentication timed out")});import{Command as Wo}from"commander";var ct=new Wo("logout").description("Log out of Catty").action(async()=>{if(!_()){console.log("Not logged in");return}let t=R()?.email||"";xe(),console.log(t?`Logged out from ${t}`:"Logged out")});import{Command as jo}from"commander";var lt=new jo("version").description("Print the version number").action(()=>{console.log("0.4.1")});import{Command as on}from"commander";import{readFileSync as Vo,writeFileSync as Ho,existsSync as pt,mkdirSync as Yo}from"fs";import{join as ut}from"path";import{homedir as mt}from"os";import{createInterface as qo}from"readline";import{spawn as Jo}from"child_process";function dt(e){(process.env.DEBUG||process.env.CATTY_DEBUG)&&console.log(`\x1B[2m[DEBUG] ${e}\x1B[0m`)}var Ko=900*1e3,Zo=2880*60*1e3,Xo="https://registry.npmjs.org/@diggerhq/catty/latest";function ft(){return ut(mt(),A,"version-cache.json")}function gt(){try{let e=ft();if(!pt(e))return null;let t=Vo(e,"utf-8");return JSON.parse(t)}catch{return null}}function ht(e,t){try{let o=ft(),n=ut(mt(),A);pt(n)||Yo(n,{recursive:!0});let r=gt(),i={latestVersion:e,lastChecked:Date.now(),declinedVersion:t?e:r?.declinedVersion,declinedAt:t?Date.now():r?.declinedAt};Ho(o,JSON.stringify(i,null,2))}catch{}}async function Qo(){try{let e=await fetch(Xo,{signal:AbortSignal.timeout(5e3)});return e.ok&&(await e.json()).version||null}catch{return null}}function en(e,t){let o=e.split(".").map(Number),n=t.split(".").map(Number);for(let r=0;r<3;r++){let i=o[r]||0,s=n[r]||0;if(s>i)return!0;if(s<i)return!1}return!1}async function ee(e){let t="0.4.1";if(t==="dev")return{updateAvailable:!1,currentVersion:t,latestVersion:null,shouldPrompt:!1};let o=gt(),n=Date.now(),r=null;if(!e?.bypassCache&&o&&n-o.lastChecked<Ko?r=o.latestVersion:(r=await Qo(),r&&ht(r)),!r)return{updateAvailable:!1,currentVersion:t,latestVersion:null,shouldPrompt:!1};let i=en(t,r),s=i;return i&&o?.declinedVersion===r&&o.declinedAt&&(s=n-o.declinedAt>=Zo),{updateAvailable:i,currentVersion:t,latestVersion:r,shouldPrompt:s}}function yt(e,t){console.log(""),console.log(`\x1B[33mUpdate available:\x1B[0m \x1B[2m${e}\x1B[0m \u2192 \x1B[32m${t}\x1B[0m`)}async function wt(){let e=qo({input:process.stdin,output:process.stdout});return new Promise(t=>{e.question("Would you like to update? (Y/n): ",o=>{e.close();let n=o.trim().toLowerCase();t(n===""||n==="y"||n==="yes")})})}function St(e){ht(e,!0)}function tn(){let e=process.env.npm_config_user_agent||"";return e.includes("yarn")?"yarn":e.includes("pnpm")?"pnpm":e.includes("bun")?"bun":"npm"}async function te(e,t){console.log(`
|
|
36
36
|
Updating from ${e} to ${t}...
|
|
37
|
-
`);let o=
|
|
37
|
+
`);let o=tn(),n=`@diggerhq/catty@${t}`,r,i;switch(o){case"yarn":r="yarn",i=["global","add",n];break;case"pnpm":r="pnpm",i=["add","-g",n];break;case"bun":r="bun",i=["install","-g",n];break;default:r="npm",i=["install","-g",n]}return dt(`Package manager: ${o}`),dt(`Command: ${r} ${i.join(" ")}`),new Promise((s,c)=>{let a=Jo(r,i,{stdio:"inherit",shell:process.platform==="win32"});a.on("close",m=>{m===0?(console.log(`
|
|
38
38
|
\x1B[32m\u2713\x1B[0m Successfully updated to version ${t}`),s()):(console.error(`
|
|
39
39
|
\x1B[31m\u2717\x1B[0m Update failed with exit code ${m}`),c(new Error(`Update process exited with code ${m}`)))}),a.on("error",m=>{console.error(`
|
|
40
|
-
\x1B[31m\u2717\x1B[0m Failed to run update command: ${m.message}`),c(m)})})}var
|
|
40
|
+
\x1B[31m\u2717\x1B[0m Failed to run update command: ${m.message}`),c(m)})})}var xt=new on("update").description("Update catty to the latest version").action(async()=>{try{let e="0.4.1";if(e==="dev"){console.log("Cannot update in development mode.");return}console.log("Checking for updates...");let{updateAvailable:t,latestVersion:o}=await ee({bypassCache:!0});if(!t||!o){console.log(`You are already using the latest version (${e}).`);return}await te(e,o)}catch(e){e instanceof Error&&console.error(`Error: ${e.message}`),process.exit(1)}});import{Command as B}from"commander";import{createInterface as bt}from"readline";import nn from"open";async function _t(e){return new Promise(t=>{let o=bt({input:process.stdin,output:process.stdout});process.stdout.write(e);let n=process.stdin,r=n.isRaw;n.isTTY&&n.setRawMode(!0);let i="",s=c=>{let a=c.toString();a===`
|
|
41
41
|
`||a==="\r"?(n.removeListener("data",s),n.isTTY&&n.setRawMode(r??!1),process.stdout.write(`
|
|
42
42
|
`),o.close(),t(i)):a===""?(process.stdout.write(`
|
|
43
|
-
`),process.exit(130)):a==="\x7F"||a==="\b"?i.length>0&&(i=i.slice(0,-1),process.stdout.write("\b \b")):a.charCodeAt(0)>=32&&(i+=a,process.stdout.write("\u2022"))};n.on("data",s)})}async function
|
|
43
|
+
`),process.exit(130)):a==="\x7F"||a==="\b"?i.length>0&&(i=i.slice(0,-1),process.stdout.write("\b \b")):a.charCodeAt(0)>=32&&(i+=a,process.stdout.write("\u2022"))};n.on("data",s)})}async function sn(e){return new Promise(t=>{let o=bt({input:process.stdin,output:process.stdout});o.question(e,n=>{o.close(),t(n)})})}async function rn(){console.log(`
|
|
44
44
|
\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
|
|
45
45
|
\u2502 GitHub Personal Access Token Setup \u2502
|
|
46
46
|
\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524
|
|
@@ -53,8 +53,8 @@ Updating from ${e} to ${t}...
|
|
|
53
53
|
\u2502 3. Generate and copy the token \u2502
|
|
54
54
|
\u2502 \u2502
|
|
55
55
|
\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
|
|
56
|
-
`),(await
|
|
57
|
-
`).filter(c=>c.trim()).filter(c=>!c.endsWith("/"))):o(new Error(`tar list failed: ${i||`exit code ${s}`}`))}),n.on("error",o)})}var
|
|
58
|
-
Would sync ${a.length} files:`);let m=20;for(let
|
|
59
|
-
Run without --dry-run to apply.`)}else await
|
|
56
|
+
`),(await sn("Open GitHub in browser? [Y/n] ")).toLowerCase()!=="n"&&(await nn("https://github.com/settings/tokens/new?scopes=repo&description=Catty%20CLI"),console.log(""));let t=await _t("Paste your token: ");(!t||t.trim()==="")&&(console.error("\u2717 No token provided"),process.exit(1)),console.log("Verifying token...");let o=await ce(t.trim());o.valid||(console.error(`\u2717 ${o.error}`),process.exit(1)),W("GH_TOKEN",t.trim()),W("GITHUB_TOKEN",t.trim()),console.log(`\u2713 Token verified (user: ${o.username})`),console.log("\u2713 Saved securely"),console.log(""),console.log("Your sessions will now have GitHub access."),console.log("Claude can clone repos, push commits, and more.")}var Ct=new B("secrets").description("Manage secrets for remote sessions").addCommand(new B("add").description("Add a secret").argument("[name]",'Secret name (or "github" for guided setup)').action(async e=>{if(e||(console.error("Usage: catty secrets add <name>"),console.error(" catty secrets add github (guided setup)"),process.exit(1)),e.toLowerCase()==="github"){await rn();return}let t=await _t(`Enter value for ${e}: `);(!t||t.trim()==="")&&(console.error("\u2717 No value provided"),process.exit(1)),W(e,t.trim()),console.log(`\u2713 Secret "${e}" saved`)})).addCommand(new B("set").description("Set a secret (non-interactive)").argument("<name>","Secret name").argument("<value>","Secret value").action((e,t)=>{W(e,t),console.log(`\u2713 Secret "${e}" saved`)})).addCommand(new B("list").description("List configured secrets").action(()=>{let e=Q();if(e.length===0){console.log("No secrets configured."),console.log(""),console.log("Add secrets with:"),console.log(" catty secrets add github # GitHub token (guided)"),console.log(" catty secrets add <NAME> # Any secret");return}console.log("Configured secrets:");for(let t of e)console.log(` \u2022 ${t}`);console.log(""),console.log("Secrets are passed to sessions as environment variables.")})).addCommand(new B("remove").description("Remove a secret").argument("<name>","Secret name").action(e=>{X(e)?(e==="GH_TOKEN"&&X("GITHUB_TOKEN"),e==="GITHUB_TOKEN"&&X("GH_TOKEN"),console.log(`\u2713 Secret "${e}" removed`)):(console.error(`\u2717 Secret "${e}" not found`),process.exit(1))})).addCommand(new B("test").description("Test a secret (e.g., verify GitHub token)").argument("<name>",'Secret name (currently only "github" supported)').action(async e=>{if(e.toLowerCase()==="github"){let t=ae("GH_TOKEN")||ae("GITHUB_TOKEN");t||(console.error("\u2717 No GitHub token configured"),console.error(" Run: catty secrets add github"),process.exit(1)),console.log("Testing GitHub token...");let o=await ce(t);o.valid?console.log(`\u2713 Token valid (user: ${o.username})`):(console.error(`\u2717 ${o.error}`),console.error(" Run: catty secrets add github"),process.exit(1))}else console.error(`\u2717 Testing "${e}" is not supported. Only "github" can be tested.`),process.exit(1)}));import{Command as an}from"commander";import{createWriteStream as vt,mkdirSync as cn,existsSync as ln,unlinkSync as dn}from"fs";import{spawn as pn}from"child_process";import{pipeline as Rt}from"stream/promises";import{Readable as kt}from"stream";async function un(e,t){return new Promise((o,n)=>{let r=pn("tar",["-xzf",e,"-C",t],{stdio:["ignore","pipe","pipe"]}),i="";r.stderr.on("data",s=>{i+=s.toString()}),r.on("close",s=>{s===0?o():n(new Error(`tar extraction failed: ${i||`exit code ${s}`}`))}),r.on("error",n)})}var Et=new an("download").description("Download workspace from a session").argument("<label>","Session label (e.g., brave-tiger-1234)").argument("[path]","Destination path (default: ./<label>)").option("--format <type>","Output format: dir or tar.gz","dir").action(async function(e,t){let o=this.opts(),n=h(this.optsWithGlobals().api);_()||(console.error("Not logged in. Please run 'catty login' first."),process.exit(1));let r=new y(n);console.log(`Fetching download URL for ${e}...`);let i;try{i=await r.getSessionDownload(e)}catch(c){c instanceof Error?console.error(`\u2717 ${c.message}`):console.error("\u2717 Failed to get download URL"),process.exit(1)}let s=t||`./${e}`;if(o.format==="tar.gz"){let c=s.endsWith(".tar.gz")?s:`${s}.tar.gz`;console.log(`Downloading to ${c}...`);let a=await fetch(i.download_url);(!a.ok||!a.body)&&(a.status===404?(console.error(`\u2717 No workspace snapshot found for ${e}`),console.error(" The session may not have saved yet or was just created.")):console.error(`\u2717 Download failed: ${a.statusText}`),process.exit(1));let m=vt(c);await Rt(kt.fromWeb(a.body),m);let f=i.size_bytes?Math.round(i.size_bytes/1024):"unknown";console.log(`\u2713 Downloaded ${c} (${f} KB)`)}else{console.log(`Downloading and extracting to ${s}/...`),ln(s)&&(console.error(`\u2717 Destination already exists: ${s}`),console.error(" Remove it first or specify a different path."),process.exit(1));let c=`/tmp/catty-download-${Date.now()}.tar.gz`,a=await fetch(i.download_url);(!a.ok||!a.body)&&(a.status===404?(console.error(`\u2717 No workspace snapshot found for ${e}`),console.error(" The session may not have saved yet (saves every 30s) or was just created.")):console.error(`\u2717 Download failed: ${a.statusText}`),process.exit(1));let m=vt(c);await Rt(kt.fromWeb(a.body),m),cn(s,{recursive:!0});try{await un(c,s)}finally{try{dn(c)}catch{}}console.log(`\u2713 Downloaded to ${s}/`)}});import{Command as mn}from"commander";import{createWriteStream as fn,unlinkSync as gn}from"fs";import{spawn as Tt}from"child_process";import{pipeline as hn}from"stream/promises";import{Readable as yn}from"stream";async function wn(e,t){return new Promise((o,n)=>{let r=Tt("tar",["-xzf",e,"-C",t],{stdio:["ignore","pipe","pipe"]}),i="";r.stderr.on("data",s=>{i+=s.toString()}),r.on("close",s=>{s===0?o():n(new Error(`tar extraction failed: ${i||`exit code ${s}`}`))}),r.on("error",n)})}async function Sn(e){return new Promise((t,o)=>{let n=Tt("tar",["-tzf",e],{stdio:["ignore","pipe","pipe"]}),r="",i="";n.stdout.on("data",s=>{r+=s.toString()}),n.stderr.on("data",s=>{i+=s.toString()}),n.on("close",s=>{s===0?t(r.split(`
|
|
57
|
+
`).filter(c=>c.trim()).filter(c=>!c.endsWith("/"))):o(new Error(`tar list failed: ${i||`exit code ${s}`}`))}),n.on("error",o)})}var $t=new mn("sync").description("Sync remote workspace to current directory").argument("<label>","Session label (e.g., brave-tiger-1234)").option("--dry-run","Show what would be synced without making changes").action(async function(e){let t=this.opts(),o=h(this.optsWithGlobals().api);_()||(console.error("Not logged in. Please run 'catty login' first."),process.exit(1));let n=new y(o);console.log(`Fetching workspace for ${e}...`);let r;try{r=await n.getSessionDownload(e)}catch(a){a instanceof Error?console.error(`\u2717 ${a.message}`):console.error("\u2717 Failed to get download URL"),process.exit(1)}let i=`/tmp/catty-sync-${Date.now()}.tar.gz`,s=await fetch(r.download_url);(!s.ok||!s.body)&&(s.status===404?(console.error(`\u2717 No workspace snapshot found for ${e}`),console.error(" The session may not have saved yet (saves every 30s).")):console.error(`\u2717 Download failed: ${s.statusText}`),process.exit(1));let c=fn(i);await hn(yn.fromWeb(s.body),c);try{if(t.dryRun){let a=await Sn(i);console.log(`
|
|
58
|
+
Would sync ${a.length} files:`);let m=20;for(let f=0;f<Math.min(a.length,m);f++)console.log(` ${a[f]}`);a.length>m&&console.log(` ... and ${a.length-m} more`),console.log(`
|
|
59
|
+
Run without --dry-run to apply.`)}else await wn(i,"."),console.log(`\u2713 Synced workspace from ${e} to current directory`)}finally{try{gn(i)}catch{}}});var xn="0.4.1";S.name("catty").description("Catty - Remote AI agent sessions").option("--api <url>","API server address").version(xn);S.addCommand(Qe);S.addCommand(nt);S.addCommand(st);S.addCommand(rt);S.addCommand(it,{hidden:!0});S.addCommand(at);S.addCommand(ct);S.addCommand(lt);S.addCommand(xt);S.addCommand(Ct);S.addCommand(Et);S.addCommand($t);S.exitOverride();async function bn(){try{await S.parseAsync(process.argv);let e=process.argv[2];if(e&&!["version","update","-v","--version","-h","--help","help"].includes(e)){let{updateAvailable:o,currentVersion:n,latestVersion:r,shouldPrompt:i}=await ee();if(o&&r&&i)if(yt(n,r),await wt())try{await te(n,r)}catch{}else St(r)}}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)}}bn();
|
|
60
60
|
//# 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/file-upload.ts","../src/lib/workspace.ts","../src/lib/secrets.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","../src/commands/secrets.ts","../src/commands/download.ts","../src/commands/sync.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 { secretsCommand } from './commands/secrets.js';\nimport { downloadCommand } from './commands/download.js';\nimport { syncCommand } from './commands/sync.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);\nprogram.addCommand(secretsCommand);\nprogram.addCommand(downloadCommand);\nprogram.addCommand(syncCommand);\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';\nimport { getAllSecrets, listSecretNames } from '../lib/secrets.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-auto-reconnect', 'Disable automatic reconnection on disconnect')\n .option('--no-secrets', \"Don't pass stored secrets to session\")\n .option('--no-sync-back', \"Don't sync remote file changes back to local\")\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 // Gather secrets to pass to session\n let secrets: Record<string, string> | undefined;\n if (opts.secrets !== false) {\n secrets = getAllSecrets();\n const secretNames = listSecretNames();\n if (secretNames.length > 0) {\n console.log(`Secrets: ${secretNames.join(', ')}`);\n }\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 secrets,\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 if (opts.syncBack) {\n console.log(` Sync-back: enabled (remote changes will sync to local)`);\n }\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';\n\nexport const SECRETS_FILE = 'secrets.json';\nexport const MAX_UPLOAD_SIZE = 1024 * 1024 * 1024; // 1GB\n\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)\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 async getSessionDownload(\n idOrLabel: string\n ): Promise<{ download_url: string; size_bytes?: number }> {\n const response = await this.doRequestWithRefresh(\n 'GET',\n `/v1/sessions/${idOrLabel}/download`\n );\n return this.handleResponse<{ download_url: string; size_bytes?: number }>(\n response\n );\n }\n}\n","import WebSocket from 'ws';\nimport { appendFileSync } from 'fs';\nimport { homedir } from 'os';\nimport { Terminal } from './terminal.js';\nimport {\n WS_POLICY_VIOLATION,\n WS_READ_TIMEOUT_MS,\n} from './config.js';\nimport {\n parseMessage,\n createResizeMessage,\n createPongMessage,\n createFileUploadMessage,\n createFileUploadChunkMessage,\n createSyncBackMessage,\n type Message,\n type ExitMessage,\n type ErrorMessage,\n type FileChangeMessage,\n type SyncBackAckMessage,\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; // Enable sync-back of remote file changes to local\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 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 if (opts.syncBack) {\n process.stderr.write('\\r\\n\\x1b[33mSync paused. Run `catty sync <label>` to pull latest changes.\\x1b[0m\\r\\n');\n }\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 if (opts.syncBack) {\n process.stderr.write('\\x1b[33mSync paused. Run `catty sync <label>` to pull latest changes.\\x1b[0m\\r\\n');\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 if (opts.syncBack) {\n process.stderr.write('\\x1b[33mSync paused. Run `catty sync <label>` to pull latest changes.\\x1b[0m\\r\\n');\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 // 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 // Request sync-back if enabled\n if (opts.syncBack) {\n debugLog('requesting sync-back');\n ws.send(createSyncBackMessage(true));\n }\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 'sync_back_ack': {\n const ackMsg = msg as SyncBackAckMessage;\n debugLog(`sync-back ack: enabled=${ackMsg.enabled}, dir=${ackMsg.workspace_dir}`);\n break;\n }\n case 'file_change': {\n const changeMsg = msg as FileChangeMessage;\n debugLog(`file change: ${changeMsg.action} ${changeMsg.path}`);\n applyRemoteFileChange(changeMsg);\n break;\n }\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 { mkdirSync, writeFileSync, unlinkSync, existsSync, chmodSync } from 'fs';\nimport { dirname, join } from 'path';\nimport { homedir } from 'os';\nimport { appendFileSync } from 'fs';\nimport type { FileChangeMessage } from '../protocol/messages.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()} [syncback] ${msg}\\n`);\n }\n}\n\n/**\n * Apply a remote file change to the local filesystem.\n * Called when the executor sends a file_change message.\n */\nexport function applyRemoteFileChange(msg: FileChangeMessage): void {\n try {\n // Get the local path - remove /workspace prefix and use cwd\n const relativePath = msg.path.replace(/^\\/workspace\\/?/, '');\n if (!relativePath) {\n debugLog('ignoring change to workspace root');\n return;\n }\n\n const localPath = join(process.cwd(), relativePath);\n\n // Security check: ensure we're not writing outside cwd\n const cwd = process.cwd();\n const resolved = join(cwd, relativePath);\n if (!resolved.startsWith(cwd)) {\n debugLog(`SECURITY: attempted write outside cwd: ${resolved}`);\n return;\n }\n\n if (msg.action === 'delete') {\n if (existsSync(localPath)) {\n unlinkSync(localPath);\n debugLog(`deleted: ${relativePath}`);\n }\n } else if (msg.action === 'write') {\n if (!msg.content) {\n debugLog(`write without content: ${relativePath}`);\n return;\n }\n\n // Ensure directory exists\n const dir = dirname(localPath);\n mkdirSync(dir, { recursive: true });\n\n // Decode base64 content and write\n const content = Buffer.from(msg.content, 'base64');\n writeFileSync(localPath, content);\n\n // Apply mode if provided\n if (msg.mode !== undefined) {\n try {\n chmodSync(localPath, msg.mode);\n } catch {\n // Ignore chmod errors (may not be supported on all platforms)\n }\n }\n\n debugLog(`wrote: ${relativePath} (${content.length} bytes)`);\n }\n } catch (err) {\n debugLog(`ERROR applying change: ${err}`);\n }\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 { homedir, hostname } from 'os';\nimport { join } from 'path';\nimport {\n readFileSync,\n writeFileSync,\n mkdirSync,\n existsSync,\n unlinkSync,\n} from 'fs';\nimport {\n createCipheriv,\n createDecipheriv,\n randomBytes,\n scryptSync,\n createHash,\n} from 'crypto';\nimport { CREDENTIALS_DIR, SECRETS_FILE } from './config.js';\n\nconst ALGORITHM = 'aes-256-gcm';\nconst SALT = 'catty-secrets-v1';\n\ninterface SecretsStore {\n version: number;\n secrets: Record<string, string>; // name -> encrypted value\n}\n\n/**\n * Get machine-specific encryption key.\n * Uses hostname + homedir as entropy (unique per machine).\n */\nfunction getEncryptionKey(): Buffer {\n const machineId = createHash('sha256')\n .update(`${hostname()}:${homedir()}:catty-machine-key`)\n .digest('hex');\n return scryptSync(machineId, SALT, 32);\n}\n\nfunction getSecretsDir(): string {\n return join(homedir(), CREDENTIALS_DIR);\n}\n\nfunction getSecretsPath(): string {\n return join(getSecretsDir(), SECRETS_FILE);\n}\n\nfunction loadStore(): SecretsStore {\n const path = getSecretsPath();\n try {\n const content = readFileSync(path, 'utf-8');\n return JSON.parse(content) as SecretsStore;\n } catch {\n return { version: 1, secrets: {} };\n }\n}\n\nfunction saveStore(store: SecretsStore): void {\n const dir = getSecretsDir();\n const path = getSecretsPath();\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(store, null, 2), { mode: 0o600 });\n}\n\n/**\n * Encrypt a secret value.\n */\nfunction encrypt(value: string): string {\n const key = getEncryptionKey();\n const iv = randomBytes(16);\n const cipher = createCipheriv(ALGORITHM, key, iv);\n\n let encrypted = cipher.update(value, 'utf8', 'base64');\n encrypted += cipher.final('base64');\n const authTag = cipher.getAuthTag().toString('base64');\n\n // Format: version:iv:authTag:encrypted\n return `v1:${iv.toString('base64')}:${authTag}:${encrypted}`;\n}\n\n/**\n * Decrypt a secret value.\n */\nfunction decrypt(encrypted: string): string | null {\n try {\n const [version, ivB64, authTagB64, data] = encrypted.split(':');\n if (version !== 'v1') return null;\n\n const key = getEncryptionKey();\n const decipher = createDecipheriv(\n ALGORITHM,\n key,\n Buffer.from(ivB64, 'base64')\n );\n decipher.setAuthTag(Buffer.from(authTagB64, 'base64'));\n\n let decrypted = decipher.update(data, 'base64', 'utf8');\n decrypted += decipher.final('utf8');\n return decrypted;\n } catch {\n return null;\n }\n}\n\n/**\n * Set a secret value.\n */\nexport function setSecret(name: string, value: string): void {\n const store = loadStore();\n store.secrets[name] = encrypt(value);\n saveStore(store);\n}\n\n/**\n * Get a secret value.\n */\nexport function getSecret(name: string): string | null {\n const store = loadStore();\n const encrypted = store.secrets[name];\n if (!encrypted) return null;\n return decrypt(encrypted);\n}\n\n/**\n * Delete a secret.\n */\nexport function deleteSecret(name: string): boolean {\n const store = loadStore();\n if (!(name in store.secrets)) return false;\n delete store.secrets[name];\n saveStore(store);\n return true;\n}\n\n/**\n * List all secret names (not values).\n */\nexport function listSecretNames(): string[] {\n const store = loadStore();\n return Object.keys(store.secrets);\n}\n\n/**\n * Get all secrets as key-value pairs (decrypted).\n * Used when passing to API for session creation.\n */\nexport function getAllSecrets(): Record<string, string> {\n const store = loadStore();\n const result: Record<string, string> = {};\n\n for (const name of Object.keys(store.secrets)) {\n const value = decrypt(store.secrets[name]);\n if (value !== null) {\n result[name] = value;\n }\n }\n\n return result;\n}\n\n/**\n * Check if secrets file exists.\n */\nexport function hasSecrets(): boolean {\n return existsSync(getSecretsPath());\n}\n\n/**\n * Clear all secrets.\n */\nexport function clearAllSecrets(): void {\n const path = getSecretsPath();\n if (existsSync(path)) {\n unlinkSync(path);\n }\n}\n\n/**\n * Verify a GitHub token is valid and return user info.\n */\nexport async function verifyGitHubToken(\n token: string\n): Promise<{ valid: boolean; username?: string; error?: string }> {\n try {\n const res = await fetch('https://api.github.com/user', {\n headers: {\n Authorization: `Bearer ${token}`,\n Accept: 'application/vnd.github.v3+json',\n 'User-Agent': 'catty-cli',\n },\n });\n\n if (res.status === 401) {\n return { valid: false, error: 'Invalid token' };\n }\n\n if (!res.ok) {\n return { valid: false, error: `GitHub API error: ${res.status}` };\n }\n\n const user = (await res.json()) as { login: string };\n return { valid: true, username: user.login };\n } catch (err) {\n return {\n valid: false,\n error: err instanceof Error ? err.message : 'Unknown error',\n };\n }\n}\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('--no-auto-reconnect', 'Disable automatic reconnection on disconnect')\n .option('--no-sync-back', \"Don't sync remote file changes back to local\")\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 if (opts.syncBack) {\n console.log(` Sync-back: enabled (remote changes will sync to local)`);\n }\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 !== 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 ${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","import { Command } from 'commander';\nimport { createInterface } from 'readline';\nimport open from 'open';\nimport {\n setSecret,\n getSecret,\n deleteSecret,\n listSecretNames,\n verifyGitHubToken,\n} from '../lib/secrets.js';\n\n/**\n * Read a line from stdin with hidden input (for secrets).\n */\nasync function readHiddenInput(prompt: string): Promise<string> {\n return new Promise((resolve) => {\n const rl = createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n // Hide input by writing asterisks\n process.stdout.write(prompt);\n\n const stdin = process.stdin;\n const wasRaw = stdin.isRaw;\n\n if (stdin.isTTY) {\n stdin.setRawMode(true);\n }\n\n let input = '';\n\n const onData = (char: Buffer) => {\n const c = char.toString();\n\n if (c === '\\n' || c === '\\r') {\n // Enter pressed\n stdin.removeListener('data', onData);\n if (stdin.isTTY) {\n stdin.setRawMode(wasRaw ?? false);\n }\n process.stdout.write('\\n');\n rl.close();\n resolve(input);\n } else if (c === '\\u0003') {\n // Ctrl+C\n process.stdout.write('\\n');\n process.exit(130);\n } else if (c === '\\u007F' || c === '\\b') {\n // Backspace\n if (input.length > 0) {\n input = input.slice(0, -1);\n process.stdout.write('\\b \\b');\n }\n } else if (c.charCodeAt(0) >= 32) {\n // Printable character\n input += c;\n process.stdout.write('•');\n }\n };\n\n stdin.on('data', onData);\n });\n}\n\n/**\n * Read a line from stdin (visible input).\n */\nasync function readInput(prompt: string): Promise<string> {\n return new Promise((resolve) => {\n const rl = createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n rl.question(prompt, (answer) => {\n rl.close();\n resolve(answer);\n });\n });\n}\n\n/**\n * Interactive GitHub token setup.\n */\nasync function setupGitHub(): Promise<void> {\n console.log(`\n┌──────────────────────────────────────────────────────────────┐\n│ GitHub Personal Access Token Setup │\n├──────────────────────────────────────────────────────────────┤\n│ │\n│ 1. Go to: https://github.com/settings/tokens/new │\n│ │\n│ 2. Create a token with these scopes: │\n│ ✓ repo (Full control of private repositories) │\n│ │\n│ 3. Generate and copy the token │\n│ │\n└──────────────────────────────────────────────────────────────┘\n`);\n\n const openBrowser = await readInput('Open GitHub in browser? [Y/n] ');\n if (openBrowser.toLowerCase() !== 'n') {\n await open(\n 'https://github.com/settings/tokens/new?scopes=repo&description=Catty%20CLI'\n );\n console.log('');\n }\n\n const token = await readHiddenInput('Paste your token: ');\n\n if (!token || token.trim() === '') {\n console.error('✗ No token provided');\n process.exit(1);\n }\n\n console.log('Verifying token...');\n const result = await verifyGitHubToken(token.trim());\n\n if (!result.valid) {\n console.error(`✗ ${result.error}`);\n process.exit(1);\n }\n\n // Save both common names for the token\n setSecret('GH_TOKEN', token.trim());\n setSecret('GITHUB_TOKEN', token.trim());\n\n console.log(`✓ Token verified (user: ${result.username})`);\n console.log('✓ Saved securely');\n console.log('');\n console.log('Your sessions will now have GitHub access.');\n console.log('Claude can clone repos, push commits, and more.');\n}\n\nexport const secretsCommand = new Command('secrets')\n .description('Manage secrets for remote sessions')\n .addCommand(\n new Command('add')\n .description('Add a secret')\n .argument('[name]', 'Secret name (or \"github\" for guided setup)')\n .action(async (name?: string) => {\n if (!name) {\n console.error('Usage: catty secrets add <name>');\n console.error(' catty secrets add github (guided setup)');\n process.exit(1);\n }\n\n if (name.toLowerCase() === 'github') {\n await setupGitHub();\n return;\n }\n\n // Generic secret\n const value = await readHiddenInput(`Enter value for ${name}: `);\n if (!value || value.trim() === '') {\n console.error('✗ No value provided');\n process.exit(1);\n }\n\n setSecret(name, value.trim());\n console.log(`✓ Secret \"${name}\" saved`);\n })\n )\n .addCommand(\n new Command('set')\n .description('Set a secret (non-interactive)')\n .argument('<name>', 'Secret name')\n .argument('<value>', 'Secret value')\n .action((name: string, value: string) => {\n setSecret(name, value);\n console.log(`✓ Secret \"${name}\" saved`);\n })\n )\n .addCommand(\n new Command('list')\n .description('List configured secrets')\n .action(() => {\n const names = listSecretNames();\n if (names.length === 0) {\n console.log('No secrets configured.');\n console.log('');\n console.log('Add secrets with:');\n console.log(' catty secrets add github # GitHub token (guided)');\n console.log(' catty secrets add <NAME> # Any secret');\n return;\n }\n\n console.log('Configured secrets:');\n for (const name of names) {\n console.log(` • ${name}`);\n }\n console.log('');\n console.log('Secrets are passed to sessions as environment variables.');\n })\n )\n .addCommand(\n new Command('remove')\n .description('Remove a secret')\n .argument('<name>', 'Secret name')\n .action((name: string) => {\n const deleted = deleteSecret(name);\n if (deleted) {\n // Also delete paired token if removing GitHub\n if (name === 'GH_TOKEN') deleteSecret('GITHUB_TOKEN');\n if (name === 'GITHUB_TOKEN') deleteSecret('GH_TOKEN');\n\n console.log(`✓ Secret \"${name}\" removed`);\n } else {\n console.error(`✗ Secret \"${name}\" not found`);\n process.exit(1);\n }\n })\n )\n .addCommand(\n new Command('test')\n .description('Test a secret (e.g., verify GitHub token)')\n .argument('<name>', 'Secret name (currently only \"github\" supported)')\n .action(async (name: string) => {\n if (name.toLowerCase() === 'github') {\n const token = getSecret('GH_TOKEN') || getSecret('GITHUB_TOKEN');\n if (!token) {\n console.error('✗ No GitHub token configured');\n console.error(' Run: catty secrets add github');\n process.exit(1);\n }\n\n console.log('Testing GitHub token...');\n const result = await verifyGitHubToken(token);\n\n if (result.valid) {\n console.log(`✓ Token valid (user: ${result.username})`);\n } else {\n console.error(`✗ ${result.error}`);\n console.error(' Run: catty secrets add github');\n process.exit(1);\n }\n } else {\n console.error(\n `✗ Testing \"${name}\" is not supported. Only \"github\" can be tested.`\n );\n process.exit(1);\n }\n })\n );\n\n","import { Command } from 'commander';\nimport { createWriteStream, mkdirSync, existsSync, unlinkSync } from 'fs';\nimport { spawn } from 'child_process';\nimport { pipeline } from 'stream/promises';\nimport { Readable } from 'stream';\nimport { getAPIAddr } from '../lib/config.js';\nimport { isLoggedIn } from '../lib/auth.js';\nimport { APIClient } from '../lib/api-client.js';\n\nasync function extractTarGz(tarPath: string, destDir: string): Promise<void> {\n return new Promise((resolve, reject) => {\n const tar = spawn('tar', ['-xzf', tarPath, '-C', destDir], {\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n let stderr = '';\n tar.stderr.on('data', (data) => {\n stderr += data.toString();\n });\n\n tar.on('close', (code) => {\n if (code === 0) {\n resolve();\n } else {\n reject(new Error(`tar extraction failed: ${stderr || `exit code ${code}`}`));\n }\n });\n\n tar.on('error', reject);\n });\n}\n\nexport const downloadCommand = new Command('download')\n .description('Download workspace from a session')\n .argument('<label>', 'Session label (e.g., brave-tiger-1234)')\n .argument('[path]', 'Destination path (default: ./<label>)')\n .option('--format <type>', 'Output format: dir or tar.gz', 'dir')\n .action(async function (this: Command, label: string, destPath?: 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(`Fetching download URL for ${label}...`);\n\n let downloadInfo;\n try {\n downloadInfo = await client.getSessionDownload(label);\n } catch (err) {\n if (err instanceof Error) {\n console.error(`✗ ${err.message}`);\n } else {\n console.error('✗ Failed to get download URL');\n }\n process.exit(1);\n }\n\n const dest = destPath || `./${label}`;\n\n if (opts.format === 'tar.gz') {\n // Download as tarball\n const tarPath = dest.endsWith('.tar.gz') ? dest : `${dest}.tar.gz`;\n console.log(`Downloading to ${tarPath}...`);\n\n const response = await fetch(downloadInfo.download_url);\n if (!response.ok || !response.body) {\n if (response.status === 404) {\n console.error(`✗ No workspace snapshot found for ${label}`);\n console.error(' The session may not have saved yet or was just created.');\n } else {\n console.error(`✗ Download failed: ${response.statusText}`);\n }\n process.exit(1);\n }\n\n const fileStream = createWriteStream(tarPath);\n await pipeline(Readable.fromWeb(response.body as never), fileStream);\n\n const sizeKB = downloadInfo.size_bytes\n ? Math.round(downloadInfo.size_bytes / 1024)\n : 'unknown';\n console.log(`✓ Downloaded ${tarPath} (${sizeKB} KB)`);\n } else {\n // Download and extract to directory\n console.log(`Downloading and extracting to ${dest}/...`);\n\n if (existsSync(dest)) {\n console.error(`✗ Destination already exists: ${dest}`);\n console.error(' Remove it first or specify a different path.');\n process.exit(1);\n }\n\n // Download to temp file first\n const tempPath = `/tmp/catty-download-${Date.now()}.tar.gz`;\n const response = await fetch(downloadInfo.download_url);\n if (!response.ok || !response.body) {\n if (response.status === 404) {\n console.error(`✗ No workspace snapshot found for ${label}`);\n console.error(' The session may not have saved yet (saves every 30s) or was just created.');\n } else {\n console.error(`✗ Download failed: ${response.statusText}`);\n }\n process.exit(1);\n }\n\n const fileStream = createWriteStream(tempPath);\n await pipeline(Readable.fromWeb(response.body as never), fileStream);\n\n // Extract\n mkdirSync(dest, { recursive: true });\n try {\n await extractTarGz(tempPath, dest);\n } finally {\n // Clean up temp file\n try {\n unlinkSync(tempPath);\n } catch {\n // Ignore cleanup errors\n }\n }\n\n console.log(`✓ Downloaded to ${dest}/`);\n }\n });\n\n","import { Command } from 'commander';\nimport { createWriteStream, unlinkSync } from 'fs';\nimport { spawn } from 'child_process';\nimport { pipeline } from 'stream/promises';\nimport { Readable } from 'stream';\nimport { getAPIAddr } from '../lib/config.js';\nimport { isLoggedIn } from '../lib/auth.js';\nimport { APIClient } from '../lib/api-client.js';\n\nasync function extractTarGz(tarPath: string, destDir: string): Promise<void> {\n return new Promise((resolve, reject) => {\n // BSD tar (macOS) overwrites by default, GNU tar needs explicit flag\n // Use basic flags that work on both\n const tar = spawn('tar', ['-xzf', tarPath, '-C', destDir], {\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n let stderr = '';\n tar.stderr.on('data', (data) => {\n stderr += data.toString();\n });\n\n tar.on('close', (code) => {\n if (code === 0) {\n resolve();\n } else {\n reject(\n new Error(`tar extraction failed: ${stderr || `exit code ${code}`}`)\n );\n }\n });\n\n tar.on('error', reject);\n });\n}\n\nasync function listTarContents(tarPath: string): Promise<string[]> {\n return new Promise((resolve, reject) => {\n const tar = spawn('tar', ['-tzf', tarPath], {\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n let stdout = '';\n let stderr = '';\n\n tar.stdout.on('data', (data) => {\n stdout += data.toString();\n });\n tar.stderr.on('data', (data) => {\n stderr += data.toString();\n });\n\n tar.on('close', (code) => {\n if (code === 0) {\n resolve(\n stdout\n .split('\\n')\n .filter((f) => f.trim())\n .filter((f) => !f.endsWith('/'))\n );\n } else {\n reject(new Error(`tar list failed: ${stderr || `exit code ${code}`}`));\n }\n });\n\n tar.on('error', reject);\n });\n}\n\nexport const syncCommand = new Command('sync')\n .description('Sync remote workspace to current directory')\n .argument('<label>', 'Session label (e.g., brave-tiger-1234)')\n .option('--dry-run', 'Show what would be synced without making changes')\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(`Fetching workspace for ${label}...`);\n\n let downloadInfo;\n try {\n downloadInfo = await client.getSessionDownload(label);\n } catch (err) {\n if (err instanceof Error) {\n console.error(`✗ ${err.message}`);\n } else {\n console.error('✗ Failed to get download URL');\n }\n process.exit(1);\n }\n\n // Download to temp file\n const tempPath = `/tmp/catty-sync-${Date.now()}.tar.gz`;\n const response = await fetch(downloadInfo.download_url);\n if (!response.ok || !response.body) {\n if (response.status === 404) {\n console.error(`✗ No workspace snapshot found for ${label}`);\n console.error(\n ' The session may not have saved yet (saves every 30s).'\n );\n } else {\n console.error(`✗ Download failed: ${response.statusText}`);\n }\n process.exit(1);\n }\n\n const fileStream = createWriteStream(tempPath);\n await pipeline(Readable.fromWeb(response.body as never), fileStream);\n\n try {\n if (opts.dryRun) {\n // List what would be synced\n const files = await listTarContents(tempPath);\n console.log(`\\nWould sync ${files.length} files:`);\n const maxShow = 20;\n for (let i = 0; i < Math.min(files.length, maxShow); i++) {\n console.log(` ${files[i]}`);\n }\n if (files.length > maxShow) {\n console.log(` ... and ${files.length - maxShow} more`);\n }\n console.log('\\nRun without --dry-run to apply.');\n } else {\n // Extract to current directory\n await extractTarGz(tempPath, '.');\n console.log(`✓ Synced workspace from ${label} to current directory`);\n }\n } finally {\n // Clean up temp file\n try {\n unlinkSync(tempPath);\n } catch {\n // Ignore cleanup errors\n }\n }\n });\n\n"],"mappings":";AACA,OAAS,WAAAA,MAAe,YCDxB,OAAS,WAAAC,OAAe,YACxB,OAAOC,OAAU,OCDV,IAAMC,EAAmB,wBACnBC,EAAkB,SAClBC,GAAmB,mBAEnBC,GAAe,eAarB,SAASC,EAAWC,EAA4B,CACrD,OAAIA,IACA,QAAQ,IAAI,eAAuB,QAAQ,IAAI,eAC5CC,EACT,CAGO,SAASC,EAAMC,EAA2B,CAC/C,OAAO,IAAI,QAASC,GAAY,WAAWA,EAASD,CAAE,CAAC,CACzD,CC1BA,OAAS,WAAAE,OAAe,KACxB,OAAS,QAAAC,OAAY,OACrB,OACE,gBAAAC,GACA,iBAAAC,GACA,aAAAC,GACA,cAAAC,GACA,cAAAC,OACK,KAIA,SAASC,IAA4B,CAC1C,OAAOC,GAAKC,GAAQ,EAAGC,CAAe,CACxC,CAEO,SAASC,GAA6B,CAC3C,OAAOH,GAAKD,GAAkB,EAAGK,EAAgB,CACnD,CAEO,SAASC,GAAsC,CACpD,IAAMC,EAAOH,EAAmB,EAChC,GAAI,CACF,IAAMI,EAAUC,GAAaF,EAAM,OAAO,EAC1C,OAAO,KAAK,MAAMC,CAAO,CAC3B,MAAQ,CACN,OAAO,IACT,CACF,CAEO,SAASE,EAAgBC,EAA0B,CACxD,IAAMC,EAAMZ,GAAkB,EACxBO,EAAOH,EAAmB,EAGhCS,GAAUD,EAAK,CAAE,UAAW,GAAM,KAAM,GAAM,CAAC,EAG/CE,GAAcP,EAAM,KAAK,UAAUI,EAAO,KAAM,CAAC,EAAG,CAAE,KAAM,GAAM,CAAC,CACrE,CAEO,SAASI,IAA0B,CACxC,IAAMR,EAAOH,EAAmB,EAC5BY,GAAWT,CAAI,GACjBU,GAAWV,CAAI,CAEnB,CAEO,SAASW,GAAsB,CACpC,IAAMP,EAAQL,EAAgB,EAO9B,MANI,GAACK,GAGD,CAACA,EAAM,cAGPA,EAAM,YAAc,CAACA,EAAM,eACX,IAAI,KAAKA,EAAM,UAAU,GAC1B,IAAI,KAOzB,CAEO,SAASQ,IAAgC,CAE9C,OADcb,EAAgB,GAChB,cAAgB,IAChC,CAEO,SAASc,IAAiC,CAE/C,OADcd,EAAgB,GAChB,eAAiB,IACjC,CC1DO,IAAMe,EAAN,cAAuB,KAAM,CAClC,YACSC,EACAC,EACPC,EACOC,EACP,CACA,MAAMD,CAAO,EALN,gBAAAF,EACA,eAAAC,EAEA,gBAAAE,EAGP,KAAK,KAAO,UACd,CAEA,iBAA2B,CACzB,OAAO,KAAK,aAAe,KAAO,KAAK,YAAc,gBACvD,CACF,EAEaC,EAAN,KAAgB,CACb,QACA,UAER,YAAYC,EAAkB,CAC5B,KAAK,QAAUA,GAAW,QAAQ,IAAI,gBAAkBC,EACxD,KAAK,UAAYC,GAAe,CAClC,CAEA,MAAc,UACZC,EACAC,EACAC,EACmB,CACnB,IAAMC,EAAa,IAAI,gBACjBC,EAAY,WAAW,IAAMD,EAAW,MAAM,EAAG,IAAc,EAErE,GAAI,CACF,IAAME,EAAkC,CACtC,eAAgB,kBAClB,EAEA,OAAI,KAAK,YACPA,EAAQ,cAAmB,UAAU,KAAK,SAAS,IAGpC,MAAM,MAAM,GAAG,KAAK,OAAO,GAAGJ,CAAI,GAAI,CACrD,OAAAD,EACA,QAAAK,EACA,KAAMH,EAAO,KAAK,UAAUA,CAAI,EAAI,OACpC,OAAQC,EAAW,MACrB,CAAC,CAGH,QAAE,CACA,aAAaC,CAAS,CACxB,CACF,CAEA,MAAc,qBACZJ,EACAC,EACAC,EACmB,CACnB,IAAII,EAAW,MAAM,KAAK,UAAUN,EAAQC,EAAMC,CAAI,EAEtD,OAAII,EAAS,SAAW,KACJ,MAAM,KAAK,iBAAiB,IAE5CA,EAAW,MAAM,KAAK,UAAUN,EAAQC,EAAMC,CAAI,GAI/CI,CACT,CAEA,MAAc,kBAAqC,CACjD,IAAMC,EAAeC,GAAgB,EACrC,GAAI,CAACD,EAAc,MAAO,GAE1B,GAAI,CACF,IAAMD,EAAW,MAAM,KAAK,UAAU,OAAQ,mBAAoB,CAChE,cAAeC,CACjB,CAAC,EAED,GAAI,CAACD,EAAS,GAAI,MAAO,GAEzB,IAAMG,EAAO,MAAMH,EAAS,KAAK,EACjC,GAAI,CAACG,EAAK,aAAc,MAAO,GAG/B,IAAMC,EAAQC,EAAgB,EAC9B,OAAID,IACFA,EAAM,aAAeD,EAAK,aACtBA,EAAK,gBACPC,EAAM,cAAgBD,EAAK,eAEzBA,EAAK,aACPC,EAAM,WAAa,IAAI,KACrB,KAAK,IAAI,GAAKD,EAAK,WAAa,IAAM,GACxC,EAAE,YAAY,GAEhBG,EAAgBF,CAAK,EACrB,KAAK,UAAYD,EAAK,cAGjB,EACT,MAAQ,CACN,MAAO,EACT,CACF,CAEA,MAAc,eAAkBH,EAAgC,CAC9D,GAAI,CAACA,EAAS,GAAI,CAChB,IAAIO,EACJ,GAAI,CACFA,EAAY,MAAMP,EAAS,KAAK,CAClC,MAAQ,CACNO,EAAY,CAAE,MAAOP,EAAS,UAAW,CAC3C,CAEA,MAAM,IAAIf,EACRe,EAAS,OACTO,EAAU,MAAQ,GAClBA,EAAU,OAASP,EAAS,WAC5BO,EAAU,WACZ,CACF,CAEA,OAAOP,EAAS,KAAK,CACvB,CAEA,MAAM,cAAcQ,EAA2D,CAC7E,IAAMR,EAAW,MAAM,KAAK,qBAAqB,OAAQ,eAAgBQ,CAAG,EAC5E,OAAO,KAAK,eAAsCR,CAAQ,CAC5D,CAEA,MAAM,cAAuC,CAC3C,IAAMA,EAAW,MAAM,KAAK,qBAAqB,MAAO,cAAc,EACtE,OAAO,KAAK,eAA8BA,CAAQ,CACpD,CAEA,MAAM,WAAWS,EAAmBC,EAAsC,CACxE,IAAMf,EAAOe,EACT,gBAAgBD,CAAS,aACzB,gBAAgBA,CAAS,GACvBT,EAAW,MAAM,KAAK,qBAAqB,MAAOL,CAAI,EAC5D,OAAO,KAAK,eAA4BK,CAAQ,CAClD,CAEA,MAAM,YAAYS,EAAmBE,EAA8B,CACjE,IAAMhB,EAAOgB,EACT,gBAAgBF,CAAS,oBACzB,gBAAgBA,CAAS,QACvBT,EAAW,MAAM,KAAK,qBAAqB,OAAQL,CAAI,EAE7D,GAAI,CAACK,EAAS,GAAI,CAChB,IAAIO,EACJ,GAAI,CACFA,EAAY,MAAMP,EAAS,KAAK,CAClC,MAAQ,CACNO,EAAY,CAAE,MAAOP,EAAS,UAAW,CAC3C,CACA,MAAM,IAAIf,EACRe,EAAS,OACTO,EAAU,MAAQ,GAClBA,EAAU,OAASP,EAAS,UAC9B,CACF,CACF,CAEA,MAAM,uBAAyC,CAC7C,IAAMA,EAAW,MAAM,KAAK,qBAAqB,OAAQ,cAAc,EAEvE,OADa,MAAM,KAAK,eAAgCA,CAAQ,GACpD,GACd,CAEA,MAAM,mBACJS,EACwD,CACxD,IAAMT,EAAW,MAAM,KAAK,qBAC1B,MACA,gBAAgBS,CAAS,WAC3B,EACA,OAAO,KAAK,eACVT,CACF,CACF,CACF,ECzMA,OAAOY,MAAe,KACtB,OAAS,kBAAAC,OAAsB,KAC/B,OAAS,WAAAC,OAAe,KCDxB,IAAIC,GAA2B,GAC3BC,EAAkC,KAEzBC,EAAN,KAAe,CACZ,OAAS,GACT,YAAc,GAEtB,YAAsB,CACpB,OAAO,QAAQ,MAAM,QAAU,EACjC,CAEA,SAAgB,CACd,GAAK,KAAK,WAAW,GACjB,MAAK,SAET,QAAQ,MAAM,WAAW,EAAI,EAC7B,QAAQ,MAAM,OAAO,EACrB,KAAK,OAAS,GACdD,EAAiB,KAGb,CAACD,IAA0B,CAC7BA,GAA2B,GAE3B,IAAMG,EAAU,IAAM,CAChBF,GACFA,EAAe,QAAQ,CAE3B,EAGA,QAAQ,GAAG,OAAQE,CAAO,EAG1B,QAAQ,GAAG,SAAU,IAAM,CACzBA,EAAQ,EACR,QAAQ,KAAK,GAAG,CAClB,CAAC,EAGD,QAAQ,GAAG,UAAW,IAAM,CAC1BA,EAAQ,EACR,QAAQ,KAAK,GAAG,CAClB,CAAC,EAGD,QAAQ,GAAG,SAAU,IAAM,CACzBA,EAAQ,EACR,QAAQ,KAAK,GAAG,CAClB,CAAC,EAGD,QAAQ,GAAG,UAAW,IAAM,CAC1BA,EAAQ,EAER,QAAQ,KAAK,QAAQ,IAAK,SAAS,CACrC,CAAC,EAGD,QAAQ,GAAG,UAAW,IAAM,CACtBF,GAAkBA,EAAe,SAAW,IAAS,CAACA,EAAe,aAGvE,QAAQ,OAAO,MAAM;AAAA;AAAA,CAAgG,CAEzH,CAAC,EAGD,QAAQ,GAAG,oBAAsBG,GAAQ,CACvCD,EAAQ,EACR,QAAQ,OAAO,MAAM;AAAA,mCAAmCC,EAAI,OAAO;AAAA,CAAa,EAChF,QAAQ,OAAO,MAAM;AAAA,CAAkE,EACvF,QAAQ,KAAK,CAAC,CAChB,CAAC,EAGD,QAAQ,GAAG,qBAAuBC,GAAW,CAC3CF,EAAQ,EACR,IAAMG,EAAUD,aAAkB,MAAQA,EAAO,QAAU,OAAOA,CAAM,EACxE,QAAQ,OAAO,MAAM;AAAA,mCAAmCC,CAAO;AAAA,CAAa,EAC5E,QAAQ,OAAO,MAAM;AAAA,CAAkE,EACvF,QAAQ,KAAK,CAAC,CAChB,CAAC,CACH,CACF,CAEA,SAAgB,CACd,GAAI,MAAK,YACT,IAAI,KAAK,QAAU,QAAQ,MAAM,MAAO,CACtC,GAAI,CAEF,QAAQ,MAAM,WAAW,EAAK,EAE9B,QAAQ,OAAO,MAAM,aAAa,EAClC,QAAQ,OAAO,MAAM,WAAW,CAClC,MAAQ,CAER,CACA,KAAK,OAAS,EAChB,CACA,KAAK,YAAc,GACfL,IAAmB,OACrBA,EAAiB,MAErB,CAMA,OAAO,YAAmB,CACxB,GAAI,CACE,QAAQ,MAAM,OAChB,QAAQ,MAAM,WAAW,EAAK,EAGhC,QAAQ,OAAO,MAAM,aAAa,EAClC,QAAQ,OAAO,MAAM,WAAW,EAChC,QAAQ,OAAO,MAAM,OAAO,CAC9B,MAAQ,CAER,CACF,CAEA,SAA0C,CACxC,MAAO,CACL,KAAM,QAAQ,OAAO,SAAW,GAChC,KAAM,QAAQ,OAAO,MAAQ,EAC/B,CACF,CAEA,SAASM,EAA4B,CACnC,QAAQ,OAAO,GAAG,SAAUA,CAAQ,CACtC,CAEA,UAAUA,EAA4B,CACpC,QAAQ,OAAO,IAAI,SAAUA,CAAQ,CACvC,CASA,sBAA6B,CAC3B,QAAQ,OAAO,MAAM,aAAa,CACpC,CAKA,uBAA8B,CAC5B,QAAQ,OAAO,MAAM,aAAa,CACpC,CACF,EC7JO,IAAMC,EAAc,CACzB,OAAQ,SACR,OAAQ,SACR,KAAM,OACN,KAAM,OACN,MAAO,QACP,KAAM,OACN,MAAO,QACP,UAAW,YACX,cAAe,gBACf,YAAa,cACb,YAAa,cACb,kBAAmB,mBACrB,EA8FO,SAASC,GAAaC,EAAuB,CAClD,IAAMC,EAAO,KAAK,MAAMD,CAAI,EAE5B,OAAQC,EAAK,KAAM,CACjB,KAAKH,EAAY,OACf,OAAO,KAAK,MAAME,CAAI,EACxB,KAAKF,EAAY,OACf,OAAO,KAAK,MAAME,CAAI,EACxB,KAAKF,EAAY,KACf,MAAO,CAAE,KAAM,MAAO,EACxB,KAAKA,EAAY,KACf,MAAO,CAAE,KAAM,MAAO,EACxB,KAAKA,EAAY,MACf,MAAO,CAAE,KAAM,OAAQ,EACzB,KAAKA,EAAY,KACf,OAAO,KAAK,MAAME,CAAI,EACxB,KAAKF,EAAY,MACf,OAAO,KAAK,MAAME,CAAI,EACxB,KAAKF,EAAY,UACf,OAAO,KAAK,MAAME,CAAI,EACxB,KAAKF,EAAY,cACf,OAAO,KAAK,MAAME,CAAI,EACxB,KAAKF,EAAY,YACf,OAAO,KAAK,MAAME,CAAI,EACxB,KAAKF,EAAY,YACf,OAAO,KAAK,MAAME,CAAI,EACxB,QACE,OAAOC,CACX,CACF,CAEO,SAASC,EAAoBC,EAAcC,EAAsB,CACtE,OAAO,KAAK,UAAU,CAAE,KAAMN,EAAY,OAAQ,KAAAK,EAAM,KAAAC,CAAK,CAAC,CAChE,CAUO,SAASC,IAA4B,CAC1C,OAAO,KAAK,UAAU,CAAE,KAAMC,EAAY,IAAK,CAAC,CAClD,CAEO,SAASC,GAAsBC,EAA0B,CAC9D,OAAO,KAAK,UAAU,CAAE,KAAMF,EAAY,UAAW,QAAAE,CAAQ,CAAC,CAChE,CAEO,SAASC,GACdC,EACAC,EACAC,EACAC,EACQ,CACR,OAAO,KAAK,UAAU,CACpB,KAAMP,EAAY,YAClB,SAAAI,EACA,YAAaC,EACb,QAASC,EAAQ,SAAS,QAAQ,EAClC,UAAWC,CACb,CAAC,CACH,CAEO,SAASC,GACdC,EACAL,EACAC,EACAK,EACAC,EACAL,EACAC,EACQ,CACR,OAAO,KAAK,UAAU,CACpB,KAAMP,EAAY,kBAClB,UAAWS,EACX,SAAAL,EACA,YAAaC,EACb,YAAaK,EACb,aAAcC,EACd,QAAAL,EACA,UAAWC,CACb,CAAC,CACH,CChMA,OAAS,aAAAK,GAAW,iBAAAC,GAAe,cAAAC,GAAY,cAAAC,GAAY,aAAAC,OAAiB,KAC5E,OAAS,WAAAC,GAAS,QAAAC,OAAY,OAC9B,OAAS,WAAAC,OAAe,KACxB,OAAS,kBAAAC,OAAsB,KAI/B,SAASC,EAASC,EAAmB,CACnC,GAAI,QAAQ,IAAI,cAAgB,IAAK,CACnC,IAAMC,EAAU,GAAGJ,GAAQ,CAAC,oBAC5BC,GAAeG,EAAS,GAAG,IAAI,KAAK,EAAE,YAAY,CAAC,eAAeD,CAAG;AAAA,CAAI,CAC3E,CACF,CAMO,SAASE,GAAsBF,EAA8B,CAClE,GAAI,CAEF,IAAMG,EAAeH,EAAI,KAAK,QAAQ,kBAAmB,EAAE,EAC3D,GAAI,CAACG,EAAc,CACjBJ,EAAS,mCAAmC,EAC5C,MACF,CAEA,IAAMK,EAAYR,GAAK,QAAQ,IAAI,EAAGO,CAAY,EAG5CE,EAAM,QAAQ,IAAI,EAClBC,EAAWV,GAAKS,EAAKF,CAAY,EACvC,GAAI,CAACG,EAAS,WAAWD,CAAG,EAAG,CAC7BN,EAAS,0CAA0CO,CAAQ,EAAE,EAC7D,MACF,CAEA,GAAIN,EAAI,SAAW,SACbP,GAAWW,CAAS,IACtBZ,GAAWY,CAAS,EACpBL,EAAS,YAAYI,CAAY,EAAE,WAE5BH,EAAI,SAAW,QAAS,CACjC,GAAI,CAACA,EAAI,QAAS,CAChBD,EAAS,0BAA0BI,CAAY,EAAE,EACjD,MACF,CAGA,IAAMI,EAAMZ,GAAQS,CAAS,EAC7Bd,GAAUiB,EAAK,CAAE,UAAW,EAAK,CAAC,EAGlC,IAAMC,EAAU,OAAO,KAAKR,EAAI,QAAS,QAAQ,EAIjD,GAHAT,GAAca,EAAWI,CAAO,EAG5BR,EAAI,OAAS,OACf,GAAI,CACFN,GAAUU,EAAWJ,EAAI,IAAI,CAC/B,MAAQ,CAER,CAGFD,EAAS,UAAUI,CAAY,KAAKK,EAAQ,MAAM,SAAS,CAC7D,CACF,OAASC,EAAK,CACZV,EAAS,0BAA0BU,CAAG,EAAE,CAC1C,CACF,CCtEA,OAAS,gBAAAC,GAAc,YAAAC,GAAU,kBAAAC,OAAsB,KACvD,OAAS,YAAAC,GAAU,WAAAC,OAAe,OAClC,OAAS,WAAAC,OAAe,KAGxB,SAASC,EAASC,EAAmB,CACnC,GAAI,QAAQ,IAAI,cAAgB,IAAK,CACnC,IAAMC,EAAU,GAAGH,GAAQ,CAAC,oBAC5BH,GAAeM,EAAS,GAAG,IAAI,KAAK,EAAE,YAAY,CAAC,IAAID,CAAG;AAAA,CAAI,CAChE,CACF,CAGA,IAAME,GAAmB,CAAC,OAAQ,OAAQ,QAAS,OAAQ,QAAS,OAAQ,MAAM,EAC5EC,GAAsB,CAAC,OAAQ,OAAQ,MAAO,QAAS,OAAQ,MAAM,EACrEC,GAAuB,CAAC,GAAGF,GAAkB,GAAGC,EAAmB,EAGnEE,GAAgB,GAAK,KAAO,KAGrBC,GAAa,GAAK,KAwBxB,SAASC,GAAgBC,EAAyB,CACvD,IAAMC,EAAUD,EAAM,KAAK,EAE3BE,EAAS,0BAA0B,KAAK,UAAUD,CAAO,CAAC,EAAE,EAK5D,IAAME,EAAWC,GAAuBH,CAAO,EAE/CC,EAAS,gBAAgB,KAAK,UAAUC,CAAQ,CAAC,EAAE,EAEnD,IAAME,EAAuB,CAAC,EAE9B,QAAWC,KAAWH,EAAU,CAE9B,IAAMI,EAAYD,EAAQ,QAAQ,OAAQ,GAAG,EAG7C,GAAI,CAACC,EAAW,SAGhB,IAAMC,EAAWD,EAAU,WAAW,GAAG,EACrCA,EAAU,QAAQ,KAAM,QAAQ,IAAI,MAAQ,GAAG,EAC/CA,EAGJ,GAAI,CAACC,EAAS,WAAW,GAAG,GAAK,CAACA,EAAS,MAAM,cAAc,EAAG,CAChEN,EAAS,0BAA0BM,CAAQ,EAAE,EAC7C,QACF,CAGA,GAAI,CACFC,GAASD,CAAQ,EACjBN,EAAS,eAAeM,CAAQ,EAAE,EAClCH,EAAW,KAAKG,CAAQ,CAC1B,OAASE,EAAK,CACZR,EAAS,mBAAmBM,CAAQ,MAAME,CAAG,EAAE,EAE/C,QACF,CACF,CAEA,OAAAR,EAAS,SAASG,EAAW,MAAM,cAAc,EAC1CA,CACT,CAKA,SAASD,GAAuBJ,EAAyB,CACvD,IAAMW,EAAoB,CAAC,EACvBC,EAAU,GACVC,EAAI,EAER,KAAOA,EAAIb,EAAM,QACXA,EAAMa,CAAC,IAAM,MAAQA,EAAI,EAAIb,EAAM,QAAUA,EAAMa,EAAI,CAAC,IAAM,KAEhED,GAAW,MACXC,GAAK,GACIb,EAAMa,CAAC,IAAM,KAElBD,IACFD,EAAQ,KAAKC,CAAO,EACpBA,EAAU,IAEZC,MAEAD,GAAWZ,EAAMa,CAAC,EAClBA,KAIJ,OAAID,GACFD,EAAQ,KAAKC,CAAO,EAGfD,CACT,CAKO,SAASG,GAAiBC,EAAoC,CACnE,GAAI,CAEF,IAAMC,EAAQP,GAASM,CAAQ,EAG/B,GAAI,CAACC,EAAM,OAAO,EAChB,MAAO,CAAE,aAAc,EAAM,EAI/B,GAAIA,EAAM,KAAOC,GACf,MAAO,CAAE,aAAc,EAAM,EAI/B,IAAMC,EAAMC,GAAQJ,CAAQ,EAAE,YAAY,EAC1C,GAAI,CAACK,GAAqB,SAASF,CAAG,EACpC,MAAO,CAAE,aAAc,EAAM,EAI/B,IAAMG,EAAUC,GAAaP,CAAQ,EAC/BQ,EAAWC,GAAST,CAAQ,EAC5BU,EAAa,6BAA6BF,CAAQ,GAClDG,EAAWC,GAAYT,CAAG,EAEhC,MAAO,CACL,aAAc,GACd,UAAWH,EACX,WAAAU,EACA,QAAAJ,EACA,SAAAE,EACA,SAAAG,CACF,CACF,MAAQ,CAEN,MAAO,CAAE,aAAc,EAAM,CAC/B,CACF,CAKA,SAASC,GAAYT,EAAqB,CAiBxC,MAhB0C,CACxC,OAAQ,YACR,OAAQ,aACR,QAAS,aACT,OAAQ,YACR,QAAS,aACT,OAAQ,YACR,OAAQ,gBACR,OAAQ,kBACR,OAAQ,aACR,MAAO,gBACP,QAAS,mBACT,OAAQ,kBACR,OAAQ,UACV,EAEiBA,EAAI,YAAY,CAAC,GAAK,0BACzC,CAMO,SAASU,GAAuBC,EAAkC,CACvE,IAAMC,EAAY,KAAK,IAAI,EACrBZ,EAAMC,GAAQU,CAAgB,EAMpC,MAAO,GALgBL,GAASK,EAAkBX,CAAG,EAGlD,QAAQ,OAAQ,GAAG,EACnB,QAAQ,kBAAmB,EAAE,CACb,IAAIY,CAAS,GAAGZ,CAAG,EACxC,CJhLA,SAASa,EAASC,EAAmB,CACnC,GAAI,QAAQ,IAAI,cAAgB,IAAK,CACnC,IAAMC,EAAU,GAAGC,GAAQ,CAAC,oBAC5BC,GAAeF,EAAS,GAAG,IAAI,KAAK,EAAE,YAAY,CAAC,SAASD,CAAG;AAAA,CAAI,CACrE,CACF,CAUA,IAAMI,GAAc,YACdC,EAAY,YAUlB,eAAsBC,EACpBC,EAC2B,CAC3B,IAAMC,EAAW,IAAIC,EAErB,GAAI,CAACD,EAAS,WAAW,EACvB,MAAM,IAAI,MAAM,yBAAyB,EAG3C,IAAME,EAAK,IAAIC,EAAUJ,EAAK,WAAY,CACxC,QAAS,CACP,GAAGA,EAAK,QACR,cAAe,UAAUA,EAAK,YAAY,EAC5C,EACA,iBAAkB,GACpB,CAAC,EAED,OAAO,IAAI,QAAQ,CAACK,EAASC,IAAW,CACtC,IAAIC,EAAW,EACXC,EAAmB,GACnBC,EAAmB,GACnBC,EAAkB,GAClBC,EAAW,GAGTC,EAAeC,GAA6B,CAC5CF,IACJA,EAAW,GACXN,EAAQQ,CAAM,EAChB,EAGIC,EAAU,GACVC,EAAc,GACdC,GAAc,GAGdC,GAAY,EACVC,GAAkB,IAGlBC,GAAe,IAAM,CACzBT,EAAkB,GACdV,EAAK,UACP,QAAQ,OAAO,MAAM,sFAAsF,EAE7GoB,EAAQ,EACR,GAAI,CACFjB,EAAG,MAAM,CACX,MAAQ,CAER,CACAS,EAAY,CAAE,KAAM,aAAc,CAAC,CACrC,EACA,QAAQ,KAAK,SAAUO,EAAY,EAGnC,IAAME,GAAgB,IAAM,CAC1BX,EAAkB,GAClB,QAAQ,OAAO,MAAM;AAAA;AAAA,CAA6C,EAC9DV,EAAK,UACP,QAAQ,OAAO,MAAM,kFAAkF,EAEzGoB,EAAQ,EACR,GAAI,CACFjB,EAAG,MAAM,CACX,MAAQ,CAER,CACAS,EAAY,CAAE,KAAM,aAAc,CAAC,CACrC,EACA,QAAQ,KAAK,UAAWS,EAAa,EAGrC,IAAMC,GAAoB,WAAW,IAAM,CACzC,GAAI,CAACb,GAAoB,CAACD,EAAkB,CAC1CA,EAAmB,GACnB,QAAQ,OAAO,MAAM;AAAA;AAAA,CAAoE,EACzF,GAAI,CACFL,EAAG,UAAU,CACf,MAAQ,CAER,CACAS,EAAY,CAAE,KAAM,eAAgB,OAAQ,oBAAqB,CAAC,CACpE,CACF,EAAG,GAAM,EAGLW,GAAmB,KAAK,IAAI,EAC1BC,GAAoB,KAEpBC,GAAsB,YAAY,IAAM,CAC5C,GAAIjB,EAAkB,OAEtB,IAAMkB,EAAgB,KAAK,IAAI,EAAIH,GACnC,GAAIG,EAAgBF,GAAmB,CACrChC,EAAS,oCAAoCkC,CAAa,IAAI,EAC9D,cAAcD,EAAmB,EAGjC,IAAME,EAAc,KAAK,MAAMD,EAAgB,GAAI,EACnD,QAAQ,OAAO,MAAM;AAAA,mDAAmDC,CAAW;AAAA,CAAe,EAGlG,GAAI,CACFxB,EAAG,UAAU,CACf,MAAQ,CAER,CAEAiB,EAAQ,EACRR,EAAY,CAAE,KAAM,eAAgB,OAAQ,yCAA0C,CAAC,CACzF,CACF,EAAG,GAAI,EAEDgB,GAAe,IAAM,CACzB,GAAM,CAAE,KAAAC,EAAM,KAAAC,CAAK,EAAI7B,EAAS,QAAQ,EACpCE,EAAG,aAAeC,EAAU,MAC9BD,EAAG,KAAK4B,EAAoBF,EAAMC,CAAI,CAAC,CAE3C,EAEMV,EAAU,IAAM,CACpBZ,EAAmB,GACnB,aAAac,EAAiB,EAC9B,cAAcG,EAAmB,EACjCxB,EAAS,sBAAsB,EAC/BA,EAAS,QAAQ,EACjBA,EAAS,UAAU2B,EAAY,EAC/B,QAAQ,MAAM,IAAI,OAAQI,EAAe,EACzC,QAAQ,IAAI,SAAUb,EAAY,EAClC,QAAQ,IAAI,UAAWE,EAAa,CACtC,EAEMW,GAAmBC,GAAiB,CACxC,GAAI9B,EAAG,aAAeC,EAAU,KAC9B,OAMF,GADmB6B,EAAK,QAAQ,CAAI,IACjB,GAAI,CACrB,IAAMC,EAAM,KAAK,IAAI,EACrB,GAAIA,EAAMjB,GAAYC,GAAiB,CAErCR,EAAkB,GAClB,QAAQ,OAAO,MAAM;AAAA,CAAM,EACvBV,EAAK,UACP,QAAQ,OAAO,MAAM,kFAAkF,EAEzGoB,EAAQ,EACR,GAAI,CACFjB,EAAG,MAAM,CACX,MAAQ,CAER,CACAS,EAAY,CAAE,KAAM,aAAc,CAAC,EACnC,MACF,CACAK,GAAYiB,CAEd,CAEA,GAAI,CACF,IAAMC,EAAMF,EAAK,SAAS,OAAO,EAGjC,GAAKnB,EAkCE,CAEL,IAAMsB,EAAcD,EAAI,QAAQrC,CAAS,EAEzC,GAAIsC,IAAgB,GAElBrB,GAAeoB,MACV,CAELpB,GAAeoB,EAAI,MAAM,EAAGC,CAAW,EACvCtB,EAAU,GACVuB,GAAoBtB,CAAW,EAC/BA,EAAc,GAGd,IAAMuB,EAAWH,EAAI,MAAMC,EAActC,EAAU,MAAM,EACrDwC,GACFnC,EAAG,KAAK,OAAO,KAAKmC,EAAU,OAAO,CAAC,CAE1C,CACF,KAtDc,CACZ,IAAMC,EAAgBJ,EAAI,QAAQtC,EAAW,EAE7C,GAAI0C,IAAkB,GAAI,CAExBpC,EAAG,KAAK8B,CAAI,EACZ,MACF,CAGIM,EAAgB,GAElBpC,EAAG,KAAK,OAAO,KAAKgC,EAAI,MAAM,EAAGI,CAAa,EAAG,OAAO,CAAC,EAI3D,IAAMC,EAAaL,EAAI,MAAMI,EAAgB1C,GAAY,MAAM,EACzDuC,EAAcI,EAAW,QAAQ1C,CAAS,EAEhD,GAAIsC,IAAgB,GAAI,CAEtB,IAAMK,EAAgBD,EAAW,MAAM,EAAGJ,CAAW,EACrDC,GAAoBI,CAAa,EAGjC,IAAMH,EAAWE,EAAW,MAAMJ,EAActC,EAAU,MAAM,EAC5DwC,GACFnC,EAAG,KAAK,OAAO,KAAKmC,EAAU,OAAO,CAAC,CAE1C,MAEExB,EAAU,GACVC,EAAcyB,CAElB,CAqBF,MAAc,CAEZ,GAAI,CACFrC,EAAG,KAAK8B,CAAI,CACd,MAAQ,CAER,CACF,CACF,EAEMS,GAAa,MAAOC,GAA6C,CACrE,IAAMC,EAAaC,GAAiBF,CAAQ,EAI5C,GAFAnD,EAAS,qBAAqBoD,EAAW,YAAY,WAAWA,EAAW,SAAS,QAAU,CAAC,EAAE,EAG/F,CAACA,EAAW,cACZ,CAACA,EAAW,SACZ,CAACA,EAAW,UACZ,CAACA,EAAW,YACZ,CAACA,EAAW,SAEZ,OAAO,KAIT,IAAME,EAAiBC,GAAuBH,EAAW,QAAQ,EAC3DI,EAAmB,6BAA6BF,CAAc,GAOpE,GALAtD,EAAS,iBAAiBwD,CAAgB,EAAE,EAE3BJ,EAAW,QAAQ,OAGrBK,GAAY,CACzB,IAAMC,EAAW,GAAG,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,CAAC,CAAC,GAC/DC,EAAgBP,EAAW,QAAQ,SAAS,QAAQ,EACpDQ,EAAc,KAAK,KAAKD,EAAc,QAAUF,GAAa,KAAK,EAClEI,EAAY,KAAK,KAAKF,EAAc,OAASC,CAAW,EAE9D5D,EAAS,mBAAmB4D,CAAW,aAAaC,CAAS,aAAa,EAE1E,QAASC,EAAI,EAAGA,EAAIF,EAAaE,IAAK,CACpC,IAAMC,GAAQD,EAAID,EACZG,GAAM,KAAK,IAAID,GAAQF,EAAWF,EAAc,MAAM,EACtDM,GAAQN,EAAc,MAAMI,GAAOC,EAAG,EAEtCE,GAAWC,GACfT,EACAJ,EACAE,EACAM,EACAF,EACAK,GACAb,EAAW,QACb,EAEAzC,EAAG,KAAKuD,EAAQ,EAChBlE,EAAS,cAAc8D,EAAI,CAAC,IAAIF,CAAW,EAAE,EAGzCE,EAAIF,EAAc,GACpB,MAAM,IAAI,QAAQ/C,IAAW,WAAWA,GAAS,CAAC,CAAC,CAEvD,CACF,KAAO,CAEL,IAAMuD,EAAYC,GAChBf,EACAE,EACAJ,EAAW,QACXA,EAAW,QACb,EAEApD,EAAS,wBAAwBoE,EAAU,MAAM,QAAQ,EACzDzD,EAAG,KAAKyD,CAAS,CACnB,CAEA,OAAOZ,CACT,EAEMX,GAAsB,MAAOyB,GAAoB,CACrD,GAAI,CAEF,GAAI,CAACA,GAAW,CAACA,EAAQ,KAAK,EAC5B,OAIF,IAAMC,EAAYC,GAAgBF,CAAO,EAEzC,GAAIC,EAAU,OAAS,EAAG,CACxBvE,EAAS,SAASuE,EAAU,MAAM,kBAAkB,EAEpD,IAAME,EAA0B,CAAC,EAGjC,QAAWtB,KAAYoB,EAAW,CAChC,IAAMG,EAAa,MAAMxB,GAAWC,CAAQ,EACxCuB,GACFD,EAAc,KAAKC,CAAU,CAEjC,CAEA,GAAID,EAAc,OAAS,EAAG,CAC5BzE,EAAS,YAAYyE,EAAc,MAAM,uBAAuB,EAGhE,IAAME,EAAWF,EAAc,KAAK,GAAG,EACvC9D,EAAG,KAAK,OAAO,KAAKgE,EAAU,OAAO,CAAC,EAEtC3E,EAAS,eAAe2E,CAAQ,EAAE,EAClC,MACF,CACF,CAGAhE,EAAG,KAAK,OAAO,KAAK2D,EAAS,OAAO,CAAC,CACvC,OAASM,EAAK,CACZ5E,EAAS,iCAAiC4E,CAAG,EAAE,EAE/C,GAAI,CACFjE,EAAG,KAAK,OAAO,KAAK2D,EAAS,OAAO,CAAC,CACvC,MAAQ,CAER,CACF,CACF,EAEA3D,EAAG,GAAG,OAAQ,IAAM,CAElBM,EAAmB,GACnB,aAAaa,EAAiB,EAG9BrB,EAAS,QAAQ,EAGjBA,EAAS,qBAAqB,EAG9B,GAAM,CAAE,KAAA4B,EAAM,KAAAC,CAAK,EAAI7B,EAAS,QAAQ,EACxCE,EAAG,KAAK4B,EAAoBF,EAAMC,CAAI,CAAC,EAGnC9B,EAAK,WACPR,EAAS,sBAAsB,EAC/BW,EAAG,KAAKkE,GAAsB,EAAI,CAAC,GAIrCpE,EAAS,SAAS2B,EAAY,EAG9B,QAAQ,MAAM,GAAG,OAAQI,EAAe,CAC1C,CAAC,EAGD7B,EAAG,GAAG,UAAW,CAAC8B,EAAyBqC,IAAsB,CAI/D,GAFA/C,GAAmB,KAAK,IAAI,EAExB+C,EACF,QAAQ,OAAO,MAAMrC,CAAc,MAEnC,IAAI,CACF,IAAMxC,EAAM8E,GAAatC,EAAK,SAAS,CAAC,EACxCuC,GAAqB/E,CAAG,CAC1B,MAAQ,CAER,CAEJ,CAAC,EAED,SAAS+E,GAAqB/E,EAAc,CAC1C,OAAQA,EAAI,KAAM,CAChB,IAAK,OAAQ,CACX,IAAMgF,EAAUhF,EAChBc,EAAWkE,EAAQ,KACnBzE,EAAK,SAASyE,EAAQ,IAAI,EAC1B,QAAQ,OAAO,MAAM;AAAA,2BAAgCA,EAAQ,IAAI;AAAA,CAAM,EACvErD,EAAQ,EACRjB,EAAG,MAAM,EACTS,EAAY,CAAE,KAAM,OAAQ,KAAM6D,EAAQ,IAAK,CAAC,EAChD,KACF,CACA,IAAK,QAAS,CACZ,IAAMC,EAAWjF,EACjB,QAAQ,OAAO,MAAM;AAAA,SAAciF,EAAS,OAAO;AAAA,CAAM,EACzD,KACF,CACA,IAAK,OACHvE,EAAG,KAAKwE,GAAkB,CAAC,EAC3B,MACF,IAAK,gBAAiB,CACpB,IAAMC,EAASnF,EACfD,EAAS,0BAA0BoF,EAAO,OAAO,SAASA,EAAO,aAAa,EAAE,EAChF,KACF,CACA,IAAK,cAAe,CAClB,IAAMC,EAAYpF,EAClBD,EAAS,gBAAgBqF,EAAU,MAAM,IAAIA,EAAU,IAAI,EAAE,EAC7DC,GAAsBD,CAAS,EAC/B,KACF,CACF,CACF,CAEA1E,EAAG,GAAG,QAAS,CAAC4E,EAAcC,IAAmB,CAC/C,GAAI,CAAAxE,IAEJY,EAAQ,EAGJ,CAAAV,GAKJ,GAAIqE,IAAS,KACX,QAAQ,OAAO,MAAM;AAAA;AAAA,CAAgE,EACrFnE,EAAY,CAAE,KAAM,UAAW,CAAC,MAC3B,CACL,IAAMqE,EAAYD,GAAQ,SAAS,GAAK,QAAQD,CAAI,GACpD,QAAQ,OAAO,MAAM;AAAA,kCAAkCE,CAAS;AAAA,CAAa,EAC7ErE,EAAY,CAAE,KAAM,eAAgB,OAAQqE,CAAU,CAAC,CACzD,CACF,CAAC,EAED9E,EAAG,GAAG,QAAUiE,GAAe,CACzB5D,IAEJY,EAAQ,EAGJ,CAAAV,IAKJ,QAAQ,OAAO,MAAM;AAAA,mCAAmC0D,EAAI,OAAO;AAAA,CAAa,EAChFxD,EAAY,CAAE,KAAM,eAAgB,OAAQwD,EAAI,OAAQ,CAAC,GAC3D,CAAC,EAGD,QAAQ,GAAG,OAAQ,IAAM,CACvBhD,EAAQ,EACJjB,EAAG,aAAeC,EAAU,MAC9BD,EAAG,MAAM,CAEb,CAAC,CACH,CAAC,CACH,CKnhBA,OAAO+E,OAAc,WACrB,OAAOC,OAA6B,SACpC,OAA2B,gBAAAC,GAAc,eAAAC,GAAa,YAAAC,OAAgB,KACtE,OAAS,QAAAC,GAAM,YAAAC,OAAgB,OAG/B,IAAMC,GAAkB,CACtB,OACA,UACA,eACA,kBACA,cACA,iBACA,QACA,WACA,OACA,UACA,OACA,QACA,YACA,OACF,EAEA,eAAsBC,GAAmBC,EAA8B,CACrE,IAAMC,EAAKC,GAAO,EAAE,IAAIJ,EAAe,EAGvC,GAAI,CACF,IAAMK,EAAYC,GAAaC,GAAKL,EAAK,YAAY,EAAG,OAAO,EAC/DC,EAAG,IAAIE,CAAS,CAClB,MAAQ,CAER,CAEA,OAAO,IAAI,QAAQ,CAACG,EAASC,IAAW,CACtC,IAAMC,EAAmB,CAAC,EACpBC,EAAUC,GAAS,MAAO,CAAE,KAAM,CAAE,MAAO,CAAE,CAAE,CAAC,EAEtDD,EAAQ,GAAG,OAASE,GAAkBH,EAAO,KAAKG,CAAK,CAAC,EACxDF,EAAQ,GAAG,MAAO,IAAMH,EAAQ,OAAO,OAAOE,CAAM,CAAC,CAAC,EACtDC,EAAQ,GAAG,QAASF,CAAM,EAG1BK,GAAQZ,EAAKA,EAAKC,EAAIQ,CAAO,EAC7BA,EAAQ,SAAS,CACnB,CAAC,CACH,CAEA,SAASG,GACPC,EACAC,EACAb,EACAQ,EACM,CACN,IAAIM,EACJ,GAAI,CACFA,EAAUC,GAAYF,CAAU,CAClC,MAAQ,CACN,MACF,CAEA,QAAWG,KAASF,EAAS,CAC3B,IAAMG,EAAWb,GAAKS,EAAYG,CAAK,EACjCE,EAAeC,GAASP,EAASK,CAAQ,EAG/C,GAAIjB,EAAG,QAAQkB,CAAY,EACzB,SAGF,IAAIE,EACJ,GAAI,CACFA,EAAOC,GAASJ,CAAQ,CAC1B,MAAQ,CACN,QACF,CAEA,GAAIG,EAAK,YAAY,EAAG,CAEtB,GAAIpB,EAAG,QAAQkB,EAAe,GAAG,EAC/B,SAEFP,GAAQC,EAASK,EAAUjB,EAAIQ,CAAO,CACxC,MAAWY,EAAK,OAAO,GACrBZ,EAAQ,KAAKS,EAAU,CAAE,KAAMC,CAAa,CAAC,CAEjD,CACF,CAEA,eAAsBI,GACpBC,EACAC,EACAC,EACe,CACf,IAAMC,EAAM,QAAQ,IAAI,EAClBC,EAAU,MAAM7B,GAAmB4B,CAAG,EAE5C,GAAIC,EAAQ,OAAS,WACnB,MAAM,IAAI,MACR,wBAAwBA,EAAQ,MAAM,eAAe,UAAe,GACtE,EAGF,IAAMC,EAAW,MAAM,MAAML,EAAW,CACtC,OAAQ,OACR,QAAS,CACP,cAAe,UAAUC,CAAK,GAC9B,eAAgB,kBAChB,wBAAyBC,CAC3B,EACA,KAAME,CACR,CAAC,EAED,GAAI,CAACC,EAAS,GAAI,CAChB,IAAMC,EAAO,MAAMD,EAAS,KAAK,EACjC,MAAM,IAAI,MAAM,kBAAkBA,EAAS,MAAM,MAAMC,CAAI,EAAE,CAC/D,CACF,CAMO,SAASC,GAAeC,EAA4B,CACzD,OAAOA,EACJ,QAAQ,SAAU,UAAU,EAC5B,QAAQ,QAAS,SAAS,EAC1B,QAAQ,WAAY,SAAS,CAClC,CChIA,OAAS,WAAAC,GAAS,YAAAC,OAAgB,KAClC,OAAS,QAAAC,OAAY,OACrB,OACE,gBAAAC,GACA,iBAAAC,GACA,aAAAC,GACA,cAAAC,GACA,cAAAC,OACK,KACP,OACE,kBAAAC,GACA,oBAAAC,GACA,eAAAC,GACA,cAAAC,GACA,cAAAC,OACK,SAGP,IAAMC,GAAY,cACZC,GAAO,mBAWb,SAASC,IAA2B,CAClC,IAAMC,EAAYC,GAAW,QAAQ,EAClC,OAAO,GAAGC,GAAS,CAAC,IAAIC,GAAQ,CAAC,oBAAoB,EACrD,OAAO,KAAK,EACf,OAAOC,GAAWJ,EAAWF,GAAM,EAAE,CACvC,CAEA,SAASO,IAAwB,CAC/B,OAAOC,GAAKH,GAAQ,EAAGI,CAAe,CACxC,CAEA,SAASC,IAAyB,CAChC,OAAOF,GAAKD,GAAc,EAAGI,EAAY,CAC3C,CAEA,SAASC,GAA0B,CACjC,IAAMC,EAAOH,GAAe,EAC5B,GAAI,CACF,IAAMI,EAAUC,GAAaF,EAAM,OAAO,EAC1C,OAAO,KAAK,MAAMC,CAAO,CAC3B,MAAQ,CACN,MAAO,CAAE,QAAS,EAAG,QAAS,CAAC,CAAE,CACnC,CACF,CAEA,SAASE,GAAUC,EAA2B,CAC5C,IAAMC,EAAMX,GAAc,EACpBM,EAAOH,GAAe,EAG5BS,GAAUD,EAAK,CAAE,UAAW,GAAM,KAAM,GAAM,CAAC,EAG/CE,GAAcP,EAAM,KAAK,UAAUI,EAAO,KAAM,CAAC,EAAG,CAAE,KAAM,GAAM,CAAC,CACrE,CAKA,SAASI,GAAQC,EAAuB,CACtC,IAAMC,EAAMtB,GAAiB,EACvBuB,EAAKC,GAAY,EAAE,EACnBC,EAASC,GAAe5B,GAAWwB,EAAKC,CAAE,EAE5CI,EAAYF,EAAO,OAAOJ,EAAO,OAAQ,QAAQ,EACrDM,GAAaF,EAAO,MAAM,QAAQ,EAClC,IAAMG,EAAUH,EAAO,WAAW,EAAE,SAAS,QAAQ,EAGrD,MAAO,MAAMF,EAAG,SAAS,QAAQ,CAAC,IAAIK,CAAO,IAAID,CAAS,EAC5D,CAKA,SAASE,GAAQF,EAAkC,CACjD,GAAI,CACF,GAAM,CAACG,EAASC,EAAOC,EAAYC,CAAI,EAAIN,EAAU,MAAM,GAAG,EAC9D,GAAIG,IAAY,KAAM,OAAO,KAE7B,IAAMR,EAAMtB,GAAiB,EACvBkC,EAAWC,GACfrC,GACAwB,EACA,OAAO,KAAKS,EAAO,QAAQ,CAC7B,EACAG,EAAS,WAAW,OAAO,KAAKF,EAAY,QAAQ,CAAC,EAErD,IAAII,EAAYF,EAAS,OAAOD,EAAM,SAAU,MAAM,EACtD,OAAAG,GAAaF,EAAS,MAAM,MAAM,EAC3BE,CACT,MAAQ,CACN,OAAO,IACT,CACF,CAKO,SAASC,EAAUC,EAAcjB,EAAqB,CAC3D,IAAML,EAAQL,EAAU,EACxBK,EAAM,QAAQsB,CAAI,EAAIlB,GAAQC,CAAK,EACnCN,GAAUC,CAAK,CACjB,CAKO,SAASuB,GAAUD,EAA6B,CAErD,IAAMX,EADQhB,EAAU,EACA,QAAQ2B,CAAI,EACpC,OAAKX,EACEE,GAAQF,CAAS,EADD,IAEzB,CAKO,SAASa,EAAaF,EAAuB,CAClD,IAAMtB,EAAQL,EAAU,EACxB,OAAM2B,KAAQtB,EAAM,SACpB,OAAOA,EAAM,QAAQsB,CAAI,EACzBvB,GAAUC,CAAK,EACR,IAH8B,EAIvC,CAKO,SAASyB,GAA4B,CAC1C,IAAMzB,EAAQL,EAAU,EACxB,OAAO,OAAO,KAAKK,EAAM,OAAO,CAClC,CAMO,SAAS0B,IAAwC,CACtD,IAAM1B,EAAQL,EAAU,EAClBgC,EAAiC,CAAC,EAExC,QAAWL,KAAQ,OAAO,KAAKtB,EAAM,OAAO,EAAG,CAC7C,IAAMK,EAAQQ,GAAQb,EAAM,QAAQsB,CAAI,CAAC,EACrCjB,IAAU,OACZsB,EAAOL,CAAI,EAAIjB,EAEnB,CAEA,OAAOsB,CACT,CAsBA,eAAsBC,GACpBC,EACgE,CAChE,GAAI,CACF,IAAMC,EAAM,MAAM,MAAM,8BAA+B,CACrD,QAAS,CACP,cAAe,UAAUD,CAAK,GAC9B,OAAQ,iCACR,aAAc,WAChB,CACF,CAAC,EAED,OAAIC,EAAI,SAAW,IACV,CAAE,MAAO,GAAO,MAAO,eAAgB,EAG3CA,EAAI,GAKF,CAAE,MAAO,GAAM,UADR,MAAMA,EAAI,KAAK,GACQ,KAAM,EAJlC,CAAE,MAAO,GAAO,MAAO,qBAAqBA,EAAI,MAAM,EAAG,CAKpE,OAASC,EAAK,CACZ,MAAO,CACL,MAAO,GACP,MAAOA,aAAe,MAAQA,EAAI,QAAU,eAC9C,CACF,CACF,CVzMA,IAAMC,EAAyB,EACzBC,GAAqB,IAEdC,GAAa,IAAIC,GAAQ,KAAK,EACxC,YAAY,kCAAkC,EAC9C,OAAO,iBAAkB,gCAAiC,QAAQ,EAClE,OAAO,cAAe,gCAAgC,EACtD,OAAO,sBAAuB,8CAA8C,EAC5E,OAAO,eAAgB,sCAAsC,EAC7D,OAAO,iBAAkB,8CAA8C,EACvE,OACC,mBACA,4EACA,EACF,EACC,OAAO,gBAA+B,CACrC,IAAMC,EAAO,KAAK,KAAK,EACjBC,EAAUC,EAAW,KAAK,gBAAgB,EAAE,GAAG,EAC/CC,EAAgBH,EAAK,gBAAkB,GAExCI,EAAW,IACd,QAAQ,MAAM,gDAAgD,EAC9D,QAAQ,KAAK,CAAC,GAGhB,IAAMC,EAAS,IAAIC,EAAUL,CAAO,EAEpC,QAAQ,IAAI,qBAAqB,EAGjC,IAAIM,EACJ,OAAQP,EAAK,MAAO,CAClB,IAAK,SACCA,EAAK,cAEPO,EAAU,CAAC,gBAAgB,EAG3BA,EAAU,CAAC,iBAAkB,gCAAgC,EAE/D,MACF,IAAK,QACHA,EAAU,CAAC,OAAO,EAClB,MACF,QACE,QAAQ,MACN,kBAAkBP,EAAK,KAAK,gCAC9B,EACA,QAAQ,KAAK,CAAC,CAClB,CAGA,IAAIQ,EACJ,GAAIR,EAAK,UAAY,GAAO,CAC1BQ,EAAUC,GAAc,EACxB,IAAMC,EAAcC,EAAgB,EAChCD,EAAY,OAAS,GACvB,QAAQ,IAAI,YAAYA,EAAY,KAAK,IAAI,CAAC,EAAE,CAEpD,CAEA,IAAIE,EACJ,GAAI,CACFA,EAAU,MAAMP,EAAO,cAAc,CACnC,MAAOL,EAAK,MACZ,IAAKO,EACL,OAAQ,MACR,QAAS,KACT,QAAAC,CACF,CAAC,CACH,OAASK,EAAK,CACZ,GAAIA,aAAeC,GAAYD,EAAI,gBAAgB,EAAG,CACpD,MAAME,GAAoBV,CAAM,EAChC,MACF,CACA,MAAMQ,CACR,CASA,GAPA,QAAQ,IAAI,oBAAoBD,EAAQ,KAAK,EAAE,EAC/C,QAAQ,IAAI,mCAAmCA,EAAQ,KAAK,EAAE,EAC1DZ,EAAK,UACP,QAAQ,IAAI,0DAA0D,EAIpEA,EAAK,SAAW,GAAO,CACzB,QAAQ,IAAI,wBAAwB,EACpC,IAAMgB,EAAYC,GAAeL,EAAQ,WAAW,EAEpD,MAAMM,GACJF,EACAJ,EAAQ,cACRA,EAAQ,QAAQ,uBAAuB,CACzC,EACA,QAAQ,IAAI,qBAAqB,CACnC,CAEA,QAAQ,IAAI,iBAAiBA,EAAQ,WAAW,KAAK,EAGrD,IAAIO,EAAoB,EAExB,OACE,GAAI,CACF,IAAMC,EAA2B,MAAMC,EAAiB,CACtD,WAAYT,EAAQ,YACpB,aAAcA,EAAQ,cACtB,QAASA,EAAQ,QACjB,SAAUZ,EAAK,WAAa,EAC9B,CAAC,EAGD,GAAIoB,EAAO,OAAS,OAElB,QAAQ,KAAKA,EAAO,IAAI,UACfA,EAAO,OAAS,cAEzB,QAAQ,KAAK,GAAG,UACPA,EAAO,OAAS,WAEzB,QAAQ,IAAI,uCAAuC,EACnD,QAAQ,KAAK,CAAC,UACLA,EAAO,OAAS,eAAgB,CAEpCjB,IACH,QAAQ,MAAM,iBAAiBiB,EAAO,MAAM,EAAE,EAC9C,QAAQ,KAAK,CAAC,GAGhBD,IACIA,EAAoBvB,IACtB,QAAQ,MAAM,4CAAuCA,CAAsB,kBAAkB,EAC7F,QAAQ,MAAM,sBAAsBgB,EAAQ,KAAK,0BAA0B,EAC3E,QAAQ,KAAK,CAAC,GAGhB,QAAQ,IAAI,gCAA2BO,CAAiB,IAAIvB,CAAsB,aAAa,EAC/F,MAAM0B,EAAMzB,EAAkB,EAG9B,GAAI,CACFe,EAAU,MAAMP,EAAO,WAAWO,EAAQ,MAAO,EAAI,EACjDA,EAAQ,SAAW,YACrB,QAAQ,MAAM,2CAAsC,EACpD,QAAQ,KAAK,CAAC,EAElB,MAAQ,CAER,CACF,CACF,OAASC,EAAK,CACZ,GAAIM,EAAoB,GAAKhB,EAC3BgB,IACIA,EAAoBvB,IACtB,QAAQ,MAAM,4CAAuCA,CAAsB,kBAAkB,EAC7F,QAAQ,KAAK,CAAC,GAEhB,QAAQ,MAAM,8CAAyCuB,CAAiB,IAAIvB,CAAsB,aAAa,EAC/G,MAAM0B,EAAMzB,EAAkB,MAE9B,OAAMgB,CAEV,CAEJ,CAAC,EAEH,eAAeE,GAAoBV,EAAkC,CACnE,QAAQ,MAAM,EAAE,EAChB,QAAQ,MAAM,8SAAoD,EAClE,QAAQ,MAAM,8CAA8C,EAC5D,QAAQ,MAAM,uCAAuC,EACrD,QAAQ,MAAM,8SAAoD,EAClE,QAAQ,MAAM,EAAE,EAEhB,GAAI,CACF,IAAMkB,EAAc,MAAMlB,EAAO,sBAAsB,EACvD,QAAQ,MAAM,yCAAyC,EACvD,MAAMmB,GAAKD,CAAW,CACxB,OAASV,EAAK,CACZ,QAAQ,MAAM,sCAAsCA,CAAG,EAAE,EACzD,QAAQ,MAAM,4CAA4C,CAC5D,CACF,CW/LA,OAAS,WAAAY,OAAe,YAMxB,IAAMC,EAAyB,EACzBC,GAAqB,IAEdC,GAAiB,IAAIC,GAAQ,SAAS,EAChD,YAAY,kCAAkC,EAC9C,SAAS,UAAW,wCAAwC,EAC5D,OAAO,sBAAuB,8CAA8C,EAC5E,OAAO,iBAAkB,8CAA8C,EACvE,OAAO,eAA+BC,EAAe,CACpD,IAAMC,EAAO,KAAK,KAAK,EACjBC,EAAUC,EAAW,KAAK,gBAAgB,EAAE,GAAG,EAC/CC,EAAgBH,EAAK,gBAAkB,GAExCI,EAAW,IACd,QAAQ,MAAM,gDAAgD,EAC9D,QAAQ,KAAK,CAAC,GAGhB,IAAMC,EAAS,IAAIC,EAAUL,CAAO,EAChCM,EAAoB,EAExB,OACE,GAAI,CACF,QAAQ,IAAI,sBAAsBR,CAAK,KAAK,EAC5C,IAAMS,EAAU,MAAMH,EAAO,WAAWN,EAAO,EAAI,EAEnD,GAAIS,EAAQ,SAAW,UACrB,MAAM,IAAI,MAAM,WAAWA,EAAQ,KAAK,aAAa,EAEvD,GAAIA,EAAQ,eAAiBA,EAAQ,gBAAkB,UACrD,MAAM,IAAI,MAAM,kCAAkCA,EAAQ,aAAa,GAAG,EAGxED,EAAoB,EACtB,QAAQ,IAAI,iCAA4BC,EAAQ,KAAK,SAAS,GAE9D,QAAQ,IAAI,iBAAiBA,EAAQ,KAAK,KAAK,EAC3CR,EAAK,UACP,QAAQ,IAAI,0DAA0D,GAI1E,IAAMS,EAA2B,MAAMC,EAAiB,CACtD,WAAYF,EAAQ,YACpB,aAAcA,EAAQ,cACtB,QAAS,CAAE,wBAAyBA,EAAQ,UAAW,EACvD,SAAUR,EAAK,WAAa,EAC9B,CAAC,EAGGS,EAAO,OAAS,OAElB,QAAQ,KAAKA,EAAO,IAAI,EACfA,EAAO,OAAS,cAEzB,QAAQ,KAAK,GAAG,EACPA,EAAO,OAAS,YAEzB,QAAQ,IAAI,uCAAuC,EACnD,QAAQ,KAAK,CAAC,GACLA,EAAO,OAAS,iBAEpBN,IACH,QAAQ,MAAM,iBAAiBM,EAAO,MAAM,EAAE,EAC9C,QAAQ,KAAK,CAAC,GAGhBF,IACIA,EAAoBZ,IACtB,QAAQ,MAAM,4CAAuCA,CAAsB,kBAAkB,EAC7F,QAAQ,MAAM,sBAAsBI,CAAK,0BAA0B,EACnE,QAAQ,KAAK,CAAC,GAGhB,QAAQ,IAAI,gCAA2BQ,CAAiB,IAAIZ,CAAsB,aAAa,EAC/F,MAAMgB,EAAMf,EAAkB,EAGlC,OAASgB,EAAK,CACZ,GAAIL,EAAoB,GAAKJ,EAC3BI,IACIA,EAAoBZ,IACtB,QAAQ,MAAM,4CAAuCA,CAAsB,kBAAkB,EAC7F,QAAQ,KAAK,CAAC,GAEhB,QAAQ,MAAM,8CAAyCY,CAAiB,IAAIZ,CAAsB,aAAa,EAC/G,MAAMgB,EAAMf,EAAkB,MAE9B,OAAMgB,CAEV,CAEJ,CAAC,EClGH,OAAS,WAAAC,OAAe,YAIjB,IAAMC,GAAc,IAAIC,GAAQ,MAAM,EAC1C,QAAQ,CAAC,IAAI,CAAC,EACd,YAAY,mBAAmB,EAC/B,OAAO,gBAA+B,CACrC,IAAMC,EAAUC,EAAW,KAAK,gBAAgB,EAAE,GAAG,EAG/CC,EAAW,MAFF,IAAIC,EAAUH,CAAO,EAEN,aAAa,EAE3C,GAAIE,EAAS,SAAW,EAAG,CACzB,QAAQ,IAAI,mBAAmB,EAC/B,MACF,CAIA,QAAQ,IADO,kDACG,EAElB,QAAWE,KAAKF,EAAU,CACxB,IAAMG,EAAMC,GAAU,IAAI,KAAKF,EAAE,UAAU,CAAC,EACtCG,EAAM,CACVH,EAAE,MAAM,OAAO,EAAE,EACjBA,EAAE,OAAO,OAAO,CAAC,EACjBA,EAAE,OAAO,OAAO,CAAC,EACjBC,CACF,EAAE,KAAK,GAAG,EACV,QAAQ,IAAIE,CAAG,CACjB,CACF,CAAC,EAKH,SAASD,GAAUE,EAAoB,CACrC,IAAMC,EAAU,KAAK,OAAO,KAAK,IAAI,EAAID,EAAK,QAAQ,GAAK,GAAI,EAE/D,GAAIC,EAAU,GAAI,MAAO,GAAGA,CAAO,QACnC,IAAMC,EAAU,KAAK,MAAMD,EAAU,EAAE,EACvC,GAAIC,EAAU,GAAI,MAAO,GAAGA,CAAO,QACnC,IAAMC,EAAQ,KAAK,MAAMD,EAAU,EAAE,EACrC,OAAIC,EAAQ,GAAW,GAAGA,CAAK,QAExB,GADM,KAAK,MAAMA,EAAQ,EAAE,CACpB,OAChB,CC/CA,OAAS,WAAAC,OAAe,YAIjB,IAAMC,GAAc,IAAIC,GAAQ,MAAM,EAC1C,YAAY,gBAAgB,EAC5B,SAAS,UAAW,qBAAqB,EACzC,OAAO,WAAY,oCAAqC,EAAK,EAC7D,OAAO,eAA+BC,EAAe,CACpD,IAAMC,EAAO,KAAK,KAAK,EACjBC,EAAUC,EAAW,KAAK,gBAAgB,EAAE,GAAG,EAGrD,MAFe,IAAIC,EAAUF,CAAO,EAEvB,YAAYF,EAAOC,EAAK,MAAM,EAEvCA,EAAK,OACP,QAAQ,IAAI,WAAWD,CAAK,sBAAsB,EAElD,QAAQ,IAAI,WAAWA,CAAK,UAAU,CAE1C,CAAC,ECpBH,OAAS,WAAAK,OAAe,YAIjB,IAAMC,GAAiB,IAAIC,GAAQ,+BAA+B,EACtE,YAAY,8BAA8B,EAC1C,OAAO,kBAAmB,wCAAyC,EAAK,EACxE,OAAO,gBAA+B,CACrC,IAAMC,EAAO,KAAK,KAAK,EACjBC,EAAUC,EAAW,KAAK,gBAAgB,EAAE,GAAG,EAErD,GAAI,CAACF,EAAK,WACR,MAAM,IAAI,MAAM,sCAAsC,EAGxD,IAAMG,EAAS,IAAIC,EAAUH,CAAO,EAC9BI,EAAW,MAAMF,EAAO,aAAa,EAE3C,GAAIE,EAAS,SAAW,EAAG,CACzB,QAAQ,IAAI,qBAAqB,EACjC,MACF,CAEA,QAAQ,IAAI,YAAYA,EAAS,MAAM,cAAc,EAErD,QAAWC,KAAKD,EAAU,CACxB,QAAQ,OAAO,MAAM,cAAcC,EAAE,UAAU,MAAM,EACrD,GAAI,CACF,MAAMH,EAAO,YAAYG,EAAE,WAAY,EAAI,EAC3C,QAAQ,IAAI,MAAM,CACpB,OAASC,EAAK,CACZ,QAAQ,IAAI,UAAUA,CAAG,EAAE,CAC7B,CACF,CACF,CAAC,EClCH,OAAS,WAAAC,OAAe,YACxB,OAAOC,OAAU,OAKV,IAAMC,GAAe,IAAIC,GAAQ,OAAO,EAC5C,YAAY,iBAAiB,EAC7B,OAAO,gBAA+B,CACrC,IAAMC,EAAUC,EAAW,KAAK,gBAAgB,EAAE,GAAG,EAErD,GAAIC,EAAW,EAAG,CAChB,IAAMC,EAAQC,EAAgB,EAC9B,QAAQ,IAAI,wBAAwBD,GAAO,KAAK,EAAE,EAClD,QAAQ,IAAI,qCAAqC,EACjD,MACF,CAEA,QAAQ,IAAI,mBAAmB,EAG/B,IAAME,EAAW,MAAM,MAAM,GAAGL,CAAO,kBAAmB,CACxD,OAAQ,OACR,QAAS,CAAE,eAAgB,kBAAmB,EAC9C,KAAM,IACR,CAAC,EAED,GAAI,CAACK,EAAS,GACZ,MAAM,IAAI,MAAM,yBAAyBA,EAAS,UAAU,EAAE,EAGhE,IAAMC,EAA2B,MAAMD,EAAS,KAAK,EAGrD,QAAQ,IAAI;AAAA;AAAA,CAA6B,EACzC,QAAQ,IAAI,OAAOC,EAAK,SAAS;AAAA,CAAI,EACrC,QAAQ,IAAI,WAAWA,EAAK,yBAAyB;AAAA,CAAI,EAEzD,MAAMC,GAAKD,EAAK,yBAAyB,EACzC,QAAQ,IAAI,+BAA+B,EAG3C,IAAME,GAAYF,EAAK,UAAY,GAAK,IAClCG,EAAW,KAAK,IAAI,EAAIH,EAAK,WAAa,IAEhD,KAAO,KAAK,IAAI,EAAIG,GAAU,CAC5B,MAAMC,EAAMF,CAAQ,EAQpB,IAAMG,EAAuB,MANX,MAAM,MAAM,GAAGX,CAAO,wBAAyB,CAC/D,OAAQ,OACR,QAAS,CAAE,eAAgB,kBAAmB,EAC9C,KAAM,KAAK,UAAU,CAAE,YAAaM,EAAK,WAAY,CAAC,CACxD,CAAC,GAE4C,KAAK,EAElD,GAAI,CAAAK,EAAM,QACV,IAAIA,EAAM,MAAO,MAAM,IAAI,MAAMA,EAAM,KAAK,EAE5C,GAAIA,EAAM,aAAc,CACtBC,EAAgB,CACd,aAAcD,EAAM,aACpB,cAAeA,EAAM,cACrB,QAASA,EAAM,MAAM,IAAM,GAC3B,MAAOA,EAAM,MAAM,OAAS,GAC5B,WAAYA,EAAM,WACd,IAAI,KAAK,KAAK,IAAI,GAAKA,EAAM,WAAa,IAAM,GAAI,EAAE,YAAY,EAClE,MACN,CAAC,EACD,QAAQ,IAAI;AAAA,eAAkBA,EAAM,MAAM,KAAK,EAAE,EACjD,QAAQ,IAAI,gDAAgD,EAC5D,MACF,EACF,CAEA,MAAM,IAAI,MAAM,0BAA0B,CAC5C,CAAC,EC5EH,OAAS,WAAAE,OAAe,YAGjB,IAAMC,GAAgB,IAAIC,GAAQ,QAAQ,EAC9C,YAAY,kBAAkB,EAC9B,OAAO,SAAY,CAClB,GAAI,CAACC,EAAW,EAAG,CACjB,QAAQ,IAAI,eAAe,EAC3B,MACF,CAGA,IAAMC,EADQC,EAAgB,GACT,OAAS,GAE9BC,GAAkB,EAGhB,QAAQ,IADNF,EACU,mBAAmBA,CAAK,GAExB,YAF0B,CAI1C,CAAC,ECrBH,OAAS,WAAAG,OAAe,YAKjB,IAAMC,GAAiB,IAAID,GAAQ,SAAS,EAChD,YAAY,0BAA0B,EACtC,OAAO,IAAM,CACZ,QAAQ,IAAyC,OAAmB,CACtE,CAAC,ECTH,OAAS,WAAAE,OAAe,YCAxB,OAAS,gBAAAC,GAAc,iBAAAC,GAAe,cAAAC,GAAY,aAAAC,OAAiB,KACnE,OAAS,QAAAC,OAAY,OACrB,OAAS,WAAAC,OAAe,KACxB,OAAS,mBAAAC,OAAuB,WAChC,OAAS,SAAAC,OAAa,gBAKtB,SAASC,GAASC,EAAuB,EACnC,QAAQ,IAAI,OAAS,QAAQ,IAAI,cACnC,QAAQ,IAAI,kBAAkBA,CAAO,SAAS,CAElD,CASA,IAAMC,GAAyB,IAAU,IACnCC,GAAqC,KAAc,GAAK,IACxDC,GAAmB,oDAEzB,SAASC,IAA8B,CACrC,OAAOC,GAAKC,GAAQ,EAAGC,EAAiB,oBAAoB,CAC9D,CAEA,SAASC,IAAwC,CAC/C,GAAI,CACF,IAAMC,EAAYL,GAAoB,EACtC,GAAI,CAACM,GAAWD,CAAS,EACvB,OAAO,KAET,IAAME,EAAOC,GAAaH,EAAW,OAAO,EAC5C,OAAO,KAAK,MAAME,CAAI,CACxB,MAAQ,CACN,OAAO,IACT,CACF,CAEA,SAASE,GAAiBC,EAAiBC,EAA0B,CACnE,GAAI,CACF,IAAMN,EAAYL,GAAoB,EAChCY,EAAWX,GAAKC,GAAQ,EAAGC,CAAe,EAC3CG,GAAWM,CAAQ,GACtBC,GAAUD,EAAU,CAAE,UAAW,EAAK,CAAC,EAIzC,IAAME,EAAWV,GAAiB,EAC5BW,EAAsB,CAC1B,cAAeL,EACf,YAAa,KAAK,IAAI,EACtB,gBAAiBC,EAAWD,EAAUI,GAAU,gBAChD,WAAYH,EAAW,KAAK,IAAI,EAAIG,GAAU,UAChD,EAEAE,GAAcX,EAAW,KAAK,UAAUU,EAAO,KAAM,CAAC,CAAC,CACzD,MAAQ,CAER,CACF,CAEA,eAAeE,IAA6C,CAC1D,GAAI,CACF,IAAMC,EAAW,MAAM,MAAMnB,GAAkB,CAC7C,OAAQ,YAAY,QAAQ,GAAI,CAClC,CAAC,EACD,OAAKmB,EAAS,KAGD,MAAMA,EAAS,KAAK,GACrB,SAAW,IACzB,MAAQ,CACN,OAAO,IACT,CACF,CAEA,SAASC,GAAgBC,EAAiBC,EAAyB,CACjE,IAAMC,EAAeF,EAAQ,MAAM,GAAG,EAAE,IAAI,MAAM,EAC5CG,EAAcF,EAAO,MAAM,GAAG,EAAE,IAAI,MAAM,EAEhD,QAASG,EAAI,EAAGA,EAAI,EAAGA,IAAK,CAC1B,IAAMC,EAAOH,EAAaE,CAAC,GAAK,EAC1BE,EAAMH,EAAYC,CAAC,GAAK,EAC9B,GAAIE,EAAMD,EAAM,MAAO,GACvB,GAAIC,EAAMD,EAAM,MAAO,EACzB,CACA,MAAO,EACT,CAEA,eAAsBE,EAAeC,EAOlC,CACD,IAAMC,EACiC,QAGvC,GAAIA,IAAmB,MACrB,MAAO,CACL,gBAAiB,GACjB,eAAAA,EACA,cAAe,KACf,aAAc,EAChB,EAIF,IAAMC,EAAS1B,GAAiB,EAC1B2B,EAAM,KAAK,IAAI,EAEjBC,EAA+B,KAanC,GAXI,CAACJ,GAAS,aAAeE,GAAUC,EAAMD,EAAO,YAAcjC,GAEhEmC,EAAgBF,EAAO,eAGvBE,EAAgB,MAAMf,GAAmB,EACrCe,GACFvB,GAAiBuB,CAAa,GAI9B,CAACA,EACH,MAAO,CACL,gBAAiB,GACjB,eAAAH,EACA,cAAe,KACf,aAAc,EAChB,EAGF,IAAMI,EAAkBd,GAAgBU,EAAgBG,CAAa,EAGjEE,EAAeD,EACnB,OAAIA,GAAmBH,GAAQ,kBAAoBE,GAAiBF,EAAO,aAGzEI,EAD0BH,EAAMD,EAAO,YACHhC,IAG/B,CACL,gBAAAmC,EACA,eAAAJ,EACA,cAAAG,EACA,aAAAE,CACF,CACF,CAEO,SAASC,GACdN,EACAG,EACM,CACN,QAAQ,IAAI,EAAE,EACd,QAAQ,IAAI,2CAA2CH,CAAc,0BAAqBG,CAAa,SAAS,CAClH,CAEA,eAAsBI,IAAoC,CACxD,IAAMC,EAAKC,GAAgB,CACzB,MAAO,QAAQ,MACf,OAAQ,QAAQ,MAClB,CAAC,EAED,OAAO,IAAI,QAASC,GAAY,CAC9BF,EAAG,SAAS,oCAAsCG,GAAW,CAC3DH,EAAG,MAAM,EACT,IAAMI,EAAaD,EAAO,KAAK,EAAE,YAAY,EAE7CD,EAAQE,IAAe,IAAMA,IAAe,KAAOA,IAAe,KAAK,CACzE,CAAC,CACH,CAAC,CACH,CAEO,SAASC,GAAqBhC,EAAuB,CAC1DD,GAAiBC,EAAS,EAAI,CAChC,CAEA,SAASiC,IAA+B,CAEtC,IAAMC,EAAY,QAAQ,IAAI,uBAAyB,GAEvD,OAAIA,EAAU,SAAS,MAAM,EACpB,OACEA,EAAU,SAAS,MAAM,EAC3B,OACEA,EAAU,SAAS,KAAK,EAC1B,MAGF,KACT,CAEA,eAAsBC,EACpBhB,EACAG,EACe,CACf,QAAQ,IAAI;AAAA,gBAAmBH,CAAc,OAAOG,CAAa;AAAA,CAAO,EAExE,IAAMc,EAAiBH,GAAqB,EACtCI,EAAc,mBAAmBf,CAAa,GAChDgB,EACAC,EAEJ,OAAQH,EAAgB,CACtB,IAAK,OACHE,EAAU,OACVC,EAAO,CAAC,SAAU,MAAOF,CAAW,EACpC,MACF,IAAK,OACHC,EAAU,OACVC,EAAO,CAAC,MAAO,KAAMF,CAAW,EAChC,MACF,IAAK,MACHC,EAAU,MACVC,EAAO,CAAC,UAAW,KAAMF,CAAW,EACpC,MACF,QACEC,EAAU,MACVC,EAAO,CAAC,UAAW,KAAMF,CAAW,CACxC,CAEA,OAAApD,GAAS,oBAAoBmD,CAAc,EAAE,EAC7CnD,GAAS,YAAYqD,CAAO,IAAIC,EAAK,KAAK,GAAG,CAAC,EAAE,EAEzC,IAAI,QAAQ,CAACV,EAASW,IAAW,CACtC,IAAMC,EAAQC,GAAMJ,EAASC,EAAM,CACjC,MAAO,UACP,MAAO,QAAQ,WAAa,OAC9B,CAAC,EAEDE,EAAM,GAAG,QAAUE,GAAS,CACtBA,IAAS,GACX,QAAQ,IACN;AAAA,wDAAsDrB,CAAa,EACrE,EACAO,EAAQ,IAER,QAAQ,MACN;AAAA,qDAAmDc,CAAI,EACzD,EACAH,EAAO,IAAI,MAAM,mCAAmCG,CAAI,EAAE,CAAC,EAE/D,CAAC,EAEDF,EAAM,GAAG,QAAUG,GAAQ,CACzB,QAAQ,MACN;AAAA,sDAAoDA,EAAI,OAAO,EACjE,EACAJ,EAAOI,CAAG,CACZ,CAAC,CACH,CAAC,CACH,CDhQO,IAAMC,GAAgB,IAAIC,GAAQ,QAAQ,EAC9C,YAAY,oCAAoC,EAChD,OAAO,SAAY,CAClB,GAAI,CACF,IAAMC,EAAsD,QAE5D,GAAIA,IAAmB,MAAO,CAC5B,QAAQ,IAAI,oCAAoC,EAChD,MACF,CAEA,QAAQ,IAAI,yBAAyB,EACrC,GAAM,CAAE,gBAAAC,EAAiB,cAAAC,CAAc,EAAI,MAAMC,EAAe,CAC9D,YAAa,EACf,CAAC,EAED,GAAI,CAACF,GAAmB,CAACC,EAAe,CACtC,QAAQ,IAAI,6CAA6CF,CAAc,IAAI,EAC3E,MACF,CAGA,MAAMI,EAAUJ,EAAgBE,CAAa,CAC/C,OAASG,EAAK,CACRA,aAAe,OACjB,QAAQ,MAAM,UAAUA,EAAI,OAAO,EAAE,EAEvC,QAAQ,KAAK,CAAC,CAChB,CACF,CAAC,EElCH,OAAS,WAAAC,MAAe,YACxB,OAAS,mBAAAC,OAAuB,WAChC,OAAOC,OAAU,OAYjB,eAAeC,GAAgBC,EAAiC,CAC9D,OAAO,IAAI,QAASC,GAAY,CAC9B,IAAMC,EAAKC,GAAgB,CACzB,MAAO,QAAQ,MACf,OAAQ,QAAQ,MAClB,CAAC,EAGD,QAAQ,OAAO,MAAMH,CAAM,EAE3B,IAAMI,EAAQ,QAAQ,MAChBC,EAASD,EAAM,MAEjBA,EAAM,OACRA,EAAM,WAAW,EAAI,EAGvB,IAAIE,EAAQ,GAENC,EAAUC,GAAiB,CAC/B,IAAMC,EAAID,EAAK,SAAS,EAEpBC,IAAM;AAAA,GAAQA,IAAM,MAEtBL,EAAM,eAAe,OAAQG,CAAM,EAC/BH,EAAM,OACRA,EAAM,WAAWC,GAAU,EAAK,EAElC,QAAQ,OAAO,MAAM;AAAA,CAAI,EACzBH,EAAG,MAAM,EACTD,EAAQK,CAAK,GACJG,IAAM,KAEf,QAAQ,OAAO,MAAM;AAAA,CAAI,EACzB,QAAQ,KAAK,GAAG,GACPA,IAAM,QAAYA,IAAM,KAE7BH,EAAM,OAAS,IACjBA,EAAQA,EAAM,MAAM,EAAG,EAAE,EACzB,QAAQ,OAAO,MAAM,OAAO,GAErBG,EAAE,WAAW,CAAC,GAAK,KAE5BH,GAASG,EACT,QAAQ,OAAO,MAAM,QAAG,EAE5B,EAEAL,EAAM,GAAG,OAAQG,CAAM,CACzB,CAAC,CACH,CAKA,eAAeG,GAAUV,EAAiC,CACxD,OAAO,IAAI,QAASC,GAAY,CAC9B,IAAMC,EAAKC,GAAgB,CACzB,MAAO,QAAQ,MACf,OAAQ,QAAQ,MAClB,CAAC,EAEDD,EAAG,SAASF,EAASW,GAAW,CAC9BT,EAAG,MAAM,EACTD,EAAQU,CAAM,CAChB,CAAC,CACH,CAAC,CACH,CAKA,eAAeC,IAA6B,CAC1C,QAAQ,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAab,GAEqB,MAAMF,GAAU,gCAAgC,GACpD,YAAY,IAAM,MAChC,MAAMG,GACJ,4EACF,EACA,QAAQ,IAAI,EAAE,GAGhB,IAAMC,EAAQ,MAAMf,GAAgB,oBAAoB,GAEpD,CAACe,GAASA,EAAM,KAAK,IAAM,MAC7B,QAAQ,MAAM,0BAAqB,EACnC,QAAQ,KAAK,CAAC,GAGhB,QAAQ,IAAI,oBAAoB,EAChC,IAAMC,EAAS,MAAMC,GAAkBF,EAAM,KAAK,CAAC,EAE9CC,EAAO,QACV,QAAQ,MAAM,UAAKA,EAAO,KAAK,EAAE,EACjC,QAAQ,KAAK,CAAC,GAIhBE,EAAU,WAAYH,EAAM,KAAK,CAAC,EAClCG,EAAU,eAAgBH,EAAM,KAAK,CAAC,EAEtC,QAAQ,IAAI,gCAA2BC,EAAO,QAAQ,GAAG,EACzD,QAAQ,IAAI,uBAAkB,EAC9B,QAAQ,IAAI,EAAE,EACd,QAAQ,IAAI,4CAA4C,EACxD,QAAQ,IAAI,iDAAiD,CAC/D,CAEO,IAAMG,GAAiB,IAAIC,EAAQ,SAAS,EAChD,YAAY,oCAAoC,EAChD,WACC,IAAIA,EAAQ,KAAK,EACd,YAAY,cAAc,EAC1B,SAAS,SAAU,4CAA4C,EAC/D,OAAO,MAAOC,GAAkB,CAO/B,GANKA,IACH,QAAQ,MAAM,iCAAiC,EAC/C,QAAQ,MAAM,kDAAkD,EAChE,QAAQ,KAAK,CAAC,GAGZA,EAAK,YAAY,IAAM,SAAU,CACnC,MAAMR,GAAY,EAClB,MACF,CAGA,IAAMS,EAAQ,MAAMtB,GAAgB,mBAAmBqB,CAAI,IAAI,GAC3D,CAACC,GAASA,EAAM,KAAK,IAAM,MAC7B,QAAQ,MAAM,0BAAqB,EACnC,QAAQ,KAAK,CAAC,GAGhBJ,EAAUG,EAAMC,EAAM,KAAK,CAAC,EAC5B,QAAQ,IAAI,kBAAaD,CAAI,SAAS,CACxC,CAAC,CACL,EACC,WACC,IAAID,EAAQ,KAAK,EACd,YAAY,gCAAgC,EAC5C,SAAS,SAAU,aAAa,EAChC,SAAS,UAAW,cAAc,EAClC,OAAO,CAACC,EAAcC,IAAkB,CACvCJ,EAAUG,EAAMC,CAAK,EACrB,QAAQ,IAAI,kBAAaD,CAAI,SAAS,CACxC,CAAC,CACL,EACC,WACC,IAAID,EAAQ,MAAM,EACf,YAAY,yBAAyB,EACrC,OAAO,IAAM,CACZ,IAAMG,EAAQC,EAAgB,EAC9B,GAAID,EAAM,SAAW,EAAG,CACtB,QAAQ,IAAI,wBAAwB,EACpC,QAAQ,IAAI,EAAE,EACd,QAAQ,IAAI,mBAAmB,EAC/B,QAAQ,IAAI,sDAAsD,EAClE,QAAQ,IAAI,2CAA2C,EACvD,MACF,CAEA,QAAQ,IAAI,qBAAqB,EACjC,QAAWF,KAAQE,EACjB,QAAQ,IAAI,YAAOF,CAAI,EAAE,EAE3B,QAAQ,IAAI,EAAE,EACd,QAAQ,IAAI,0DAA0D,CACxE,CAAC,CACL,EACC,WACC,IAAID,EAAQ,QAAQ,EACjB,YAAY,iBAAiB,EAC7B,SAAS,SAAU,aAAa,EAChC,OAAQC,GAAiB,CACRI,EAAaJ,CAAI,GAG3BA,IAAS,YAAYI,EAAa,cAAc,EAChDJ,IAAS,gBAAgBI,EAAa,UAAU,EAEpD,QAAQ,IAAI,kBAAaJ,CAAI,WAAW,IAExC,QAAQ,MAAM,kBAAaA,CAAI,aAAa,EAC5C,QAAQ,KAAK,CAAC,EAElB,CAAC,CACL,EACC,WACC,IAAID,EAAQ,MAAM,EACf,YAAY,2CAA2C,EACvD,SAAS,SAAU,iDAAiD,EACpE,OAAO,MAAOC,GAAiB,CAC9B,GAAIA,EAAK,YAAY,IAAM,SAAU,CACnC,IAAMN,EAAQW,GAAU,UAAU,GAAKA,GAAU,cAAc,EAC1DX,IACH,QAAQ,MAAM,mCAA8B,EAC5C,QAAQ,MAAM,iCAAiC,EAC/C,QAAQ,KAAK,CAAC,GAGhB,QAAQ,IAAI,yBAAyB,EACrC,IAAMC,EAAS,MAAMC,GAAkBF,CAAK,EAExCC,EAAO,MACT,QAAQ,IAAI,6BAAwBA,EAAO,QAAQ,GAAG,GAEtD,QAAQ,MAAM,UAAKA,EAAO,KAAK,EAAE,EACjC,QAAQ,MAAM,iCAAiC,EAC/C,QAAQ,KAAK,CAAC,EAElB,MACE,QAAQ,MACN,mBAAcK,CAAI,kDACpB,EACA,QAAQ,KAAK,CAAC,CAElB,CAAC,CACL,ECrPF,OAAS,WAAAM,OAAe,YACxB,OAAS,qBAAAC,GAAmB,aAAAC,GAAW,cAAAC,GAAY,cAAAC,OAAkB,KACrE,OAAS,SAAAC,OAAa,gBACtB,OAAS,YAAAC,OAAgB,kBACzB,OAAS,YAAAC,OAAgB,SAKzB,eAAeC,GAAaC,EAAiBC,EAAgC,CAC3E,OAAO,IAAI,QAAQ,CAACC,EAASC,IAAW,CACtC,IAAMC,EAAMC,GAAM,MAAO,CAAC,OAAQL,EAAS,KAAMC,CAAO,EAAG,CACzD,MAAO,CAAC,SAAU,OAAQ,MAAM,CAClC,CAAC,EAEGK,EAAS,GACbF,EAAI,OAAO,GAAG,OAASG,GAAS,CAC9BD,GAAUC,EAAK,SAAS,CAC1B,CAAC,EAEDH,EAAI,GAAG,QAAUI,GAAS,CACpBA,IAAS,EACXN,EAAQ,EAERC,EAAO,IAAI,MAAM,0BAA0BG,GAAU,aAAaE,CAAI,EAAE,EAAE,CAAC,CAE/E,CAAC,EAEDJ,EAAI,GAAG,QAASD,CAAM,CACxB,CAAC,CACH,CAEO,IAAMM,GAAkB,IAAIC,GAAQ,UAAU,EAClD,YAAY,mCAAmC,EAC/C,SAAS,UAAW,wCAAwC,EAC5D,SAAS,SAAU,uCAAuC,EAC1D,OAAO,kBAAmB,+BAAgC,KAAK,EAC/D,OAAO,eAA+BC,EAAeC,EAAmB,CACvE,IAAMC,EAAO,KAAK,KAAK,EACjBC,EAAUC,EAAW,KAAK,gBAAgB,EAAE,GAAG,EAEhDC,EAAW,IACd,QAAQ,MAAM,gDAAgD,EAC9D,QAAQ,KAAK,CAAC,GAGhB,IAAMC,EAAS,IAAIC,EAAUJ,CAAO,EAEpC,QAAQ,IAAI,6BAA6BH,CAAK,KAAK,EAEnD,IAAIQ,EACJ,GAAI,CACFA,EAAe,MAAMF,EAAO,mBAAmBN,CAAK,CACtD,OAASS,EAAK,CACRA,aAAe,MACjB,QAAQ,MAAM,UAAKA,EAAI,OAAO,EAAE,EAEhC,QAAQ,MAAM,mCAA8B,EAE9C,QAAQ,KAAK,CAAC,CAChB,CAEA,IAAMC,EAAOT,GAAY,KAAKD,CAAK,GAEnC,GAAIE,EAAK,SAAW,SAAU,CAE5B,IAAMb,EAAUqB,EAAK,SAAS,SAAS,EAAIA,EAAO,GAAGA,CAAI,UACzD,QAAQ,IAAI,kBAAkBrB,CAAO,KAAK,EAE1C,IAAMsB,EAAW,MAAM,MAAMH,EAAa,YAAY,GAClD,CAACG,EAAS,IAAM,CAACA,EAAS,QACxBA,EAAS,SAAW,KACtB,QAAQ,MAAM,0CAAqCX,CAAK,EAAE,EAC1D,QAAQ,MAAM,2DAA2D,GAEzE,QAAQ,MAAM,2BAAsBW,EAAS,UAAU,EAAE,EAE3D,QAAQ,KAAK,CAAC,GAGhB,IAAMC,EAAaC,GAAkBxB,CAAO,EAC5C,MAAMyB,GAASC,GAAS,QAAQJ,EAAS,IAAa,EAAGC,CAAU,EAEnE,IAAMI,EAASR,EAAa,WACxB,KAAK,MAAMA,EAAa,WAAa,IAAI,EACzC,UACJ,QAAQ,IAAI,qBAAgBnB,CAAO,KAAK2B,CAAM,MAAM,CACtD,KAAO,CAEL,QAAQ,IAAI,iCAAiCN,CAAI,MAAM,EAEnDO,GAAWP,CAAI,IACjB,QAAQ,MAAM,sCAAiCA,CAAI,EAAE,EACrD,QAAQ,MAAM,gDAAgD,EAC9D,QAAQ,KAAK,CAAC,GAIhB,IAAMQ,EAAW,uBAAuB,KAAK,IAAI,CAAC,UAC5CP,EAAW,MAAM,MAAMH,EAAa,YAAY,GAClD,CAACG,EAAS,IAAM,CAACA,EAAS,QACxBA,EAAS,SAAW,KACtB,QAAQ,MAAM,0CAAqCX,CAAK,EAAE,EAC1D,QAAQ,MAAM,6EAA6E,GAE3F,QAAQ,MAAM,2BAAsBW,EAAS,UAAU,EAAE,EAE3D,QAAQ,KAAK,CAAC,GAGhB,IAAMC,EAAaC,GAAkBK,CAAQ,EAC7C,MAAMJ,GAASC,GAAS,QAAQJ,EAAS,IAAa,EAAGC,CAAU,EAGnEO,GAAUT,EAAM,CAAE,UAAW,EAAK,CAAC,EACnC,GAAI,CACF,MAAMtB,GAAa8B,EAAUR,CAAI,CACnC,QAAE,CAEA,GAAI,CACFU,GAAWF,CAAQ,CACrB,MAAQ,CAER,CACF,CAEA,QAAQ,IAAI,wBAAmBR,CAAI,GAAG,CACxC,CACF,CAAC,EChIH,OAAS,WAAAW,OAAe,YACxB,OAAS,qBAAAC,GAAmB,cAAAC,OAAkB,KAC9C,OAAS,SAAAC,OAAa,gBACtB,OAAS,YAAAC,OAAgB,kBACzB,OAAS,YAAAC,OAAgB,SAKzB,eAAeC,GAAaC,EAAiBC,EAAgC,CAC3E,OAAO,IAAI,QAAQ,CAACC,EAASC,IAAW,CAGtC,IAAMC,EAAMC,GAAM,MAAO,CAAC,OAAQL,EAAS,KAAMC,CAAO,EAAG,CACzD,MAAO,CAAC,SAAU,OAAQ,MAAM,CAClC,CAAC,EAEGK,EAAS,GACbF,EAAI,OAAO,GAAG,OAASG,GAAS,CAC9BD,GAAUC,EAAK,SAAS,CAC1B,CAAC,EAEDH,EAAI,GAAG,QAAUI,GAAS,CACpBA,IAAS,EACXN,EAAQ,EAERC,EACE,IAAI,MAAM,0BAA0BG,GAAU,aAAaE,CAAI,EAAE,EAAE,CACrE,CAEJ,CAAC,EAEDJ,EAAI,GAAG,QAASD,CAAM,CACxB,CAAC,CACH,CAEA,eAAeM,GAAgBT,EAAoC,CACjE,OAAO,IAAI,QAAQ,CAACE,EAASC,IAAW,CACtC,IAAMC,EAAMC,GAAM,MAAO,CAAC,OAAQL,CAAO,EAAG,CAC1C,MAAO,CAAC,SAAU,OAAQ,MAAM,CAClC,CAAC,EAEGU,EAAS,GACTJ,EAAS,GAEbF,EAAI,OAAO,GAAG,OAASG,GAAS,CAC9BG,GAAUH,EAAK,SAAS,CAC1B,CAAC,EACDH,EAAI,OAAO,GAAG,OAASG,GAAS,CAC9BD,GAAUC,EAAK,SAAS,CAC1B,CAAC,EAEDH,EAAI,GAAG,QAAUI,GAAS,CACpBA,IAAS,EACXN,EACEQ,EACG,MAAM;AAAA,CAAI,EACV,OAAQC,GAAMA,EAAE,KAAK,CAAC,EACtB,OAAQA,GAAM,CAACA,EAAE,SAAS,GAAG,CAAC,CACnC,EAEAR,EAAO,IAAI,MAAM,oBAAoBG,GAAU,aAAaE,CAAI,EAAE,EAAE,CAAC,CAEzE,CAAC,EAEDJ,EAAI,GAAG,QAASD,CAAM,CACxB,CAAC,CACH,CAEO,IAAMS,GAAc,IAAIC,GAAQ,MAAM,EAC1C,YAAY,4CAA4C,EACxD,SAAS,UAAW,wCAAwC,EAC5D,OAAO,YAAa,kDAAkD,EACtE,OAAO,eAA+BC,EAAe,CACpD,IAAMC,EAAO,KAAK,KAAK,EACjBC,EAAUC,EAAW,KAAK,gBAAgB,EAAE,GAAG,EAEhDC,EAAW,IACd,QAAQ,MAAM,gDAAgD,EAC9D,QAAQ,KAAK,CAAC,GAGhB,IAAMC,EAAS,IAAIC,EAAUJ,CAAO,EAEpC,QAAQ,IAAI,0BAA0BF,CAAK,KAAK,EAEhD,IAAIO,EACJ,GAAI,CACFA,EAAe,MAAMF,EAAO,mBAAmBL,CAAK,CACtD,OAASQ,EAAK,CACRA,aAAe,MACjB,QAAQ,MAAM,UAAKA,EAAI,OAAO,EAAE,EAEhC,QAAQ,MAAM,mCAA8B,EAE9C,QAAQ,KAAK,CAAC,CAChB,CAGA,IAAMC,EAAW,mBAAmB,KAAK,IAAI,CAAC,UACxCC,EAAW,MAAM,MAAMH,EAAa,YAAY,GAClD,CAACG,EAAS,IAAM,CAACA,EAAS,QACxBA,EAAS,SAAW,KACtB,QAAQ,MAAM,0CAAqCV,CAAK,EAAE,EAC1D,QAAQ,MACN,yDACF,GAEA,QAAQ,MAAM,2BAAsBU,EAAS,UAAU,EAAE,EAE3D,QAAQ,KAAK,CAAC,GAGhB,IAAMC,EAAaC,GAAkBH,CAAQ,EAC7C,MAAMI,GAASC,GAAS,QAAQJ,EAAS,IAAa,EAAGC,CAAU,EAEnE,GAAI,CACF,GAAIV,EAAK,OAAQ,CAEf,IAAMc,EAAQ,MAAMpB,GAAgBc,CAAQ,EAC5C,QAAQ,IAAI;AAAA,aAAgBM,EAAM,MAAM,SAAS,EACjD,IAAMC,EAAU,GAChB,QAASC,EAAI,EAAGA,EAAI,KAAK,IAAIF,EAAM,OAAQC,CAAO,EAAGC,IACnD,QAAQ,IAAI,KAAKF,EAAME,CAAC,CAAC,EAAE,EAEzBF,EAAM,OAASC,GACjB,QAAQ,IAAI,aAAaD,EAAM,OAASC,CAAO,OAAO,EAExD,QAAQ,IAAI;AAAA,gCAAmC,CACjD,MAEE,MAAM/B,GAAawB,EAAU,GAAG,EAChC,QAAQ,IAAI,gCAA2BT,CAAK,uBAAuB,CAEvE,QAAE,CAEA,GAAI,CACFkB,GAAWT,CAAQ,CACrB,MAAQ,CAER,CACF,CACF,CAAC,EvBtHH,IAAMU,GAA+C,QAErDC,EACG,KAAK,OAAO,EACZ,YAAY,kCAAkC,EAC9C,OAAO,cAAe,oBAAoB,EAC1C,QAAQD,EAAO,EAElBC,EAAQ,WAAWC,EAAU,EAC7BD,EAAQ,WAAWE,EAAc,EACjCF,EAAQ,WAAWG,EAAW,EAC9BH,EAAQ,WAAWI,EAAW,EAC9BJ,EAAQ,WAAWK,GAAgB,CAAE,OAAQ,EAAK,CAAC,EACnDL,EAAQ,WAAWM,EAAY,EAC/BN,EAAQ,WAAWO,EAAa,EAChCP,EAAQ,WAAWQ,EAAc,EACjCR,EAAQ,WAAWS,EAAa,EAChCT,EAAQ,WAAWU,EAAc,EACjCV,EAAQ,WAAWW,EAAe,EAClCX,EAAQ,WAAWY,EAAW,EAG9BZ,EAAQ,aAAa,EAErB,eAAea,IAAO,CACpB,GAAI,CACF,MAAMb,EAAQ,WAAW,QAAQ,IAAI,EAIrC,IAAMc,EAAU,QAAQ,KAAK,CAAC,EAU9B,GAAIA,GAAW,CATS,CACtB,UACA,SACA,KACA,YACA,KACA,SACA,MACF,EACgC,SAASA,CAAO,EAAG,CACjD,GAAM,CAAE,gBAAAC,EAAiB,eAAAC,EAAgB,cAAAC,EAAe,aAAAC,CAAa,EACnE,MAAMC,EAAe,EACvB,GAAIJ,GAAmBE,GAAiBC,EAGtC,GAFAE,GAAqBJ,EAAgBC,CAAa,EAC7B,MAAMI,GAAgB,EAEzC,GAAI,CACF,MAAMC,EAAUN,EAAgBC,CAAa,CAC/C,MAAc,CAGd,MAGAM,GAAqBN,CAAa,CAGxC,CACF,OAASO,EAAc,CACjBA,aAAe,OAGfA,EAAI,OAAS,kBACb,CAAC,0BAA2B,mBAAmB,EAAE,SAC9CA,EAA0B,MAAQ,EACrC,GAEA,QAAQ,KAAK,CAAC,EAGhB,QAAQ,MAAM,UAAUA,EAAI,OAAO,EAAE,GAErC,QAAQ,MAAM,UAAUA,CAAG,EAAE,EAE/B,QAAQ,KAAK,CAAC,CAChB,CACF,CAEAX,GAAK","names":["program","Command","open","DEFAULT_API_ADDR","CREDENTIALS_DIR","CREDENTIALS_FILE","SECRETS_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","mkdirSync","writeFileSync","unlinkSync","existsSync","chmodSync","dirname","join","homedir","appendFileSync","debugLog","msg","logFile","applyRemoteFileChange","relativePath","localPath","cwd","resolved","dir","content","err","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","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","ackMsg","changeMsg","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","homedir","hostname","join","readFileSync","writeFileSync","mkdirSync","existsSync","unlinkSync","createCipheriv","createDecipheriv","randomBytes","scryptSync","createHash","ALGORITHM","SALT","getEncryptionKey","machineId","createHash","hostname","homedir","scryptSync","getSecretsDir","join","CREDENTIALS_DIR","getSecretsPath","SECRETS_FILE","loadStore","path","content","readFileSync","saveStore","store","dir","mkdirSync","writeFileSync","encrypt","value","key","iv","randomBytes","cipher","createCipheriv","encrypted","authTag","decrypt","version","ivB64","authTagB64","data","decipher","createDecipheriv","decrypted","setSecret","name","getSecret","deleteSecret","listSecretNames","getAllSecrets","result","verifyGitHubToken","token","res","err","MAX_RECONNECT_ATTEMPTS","RECONNECT_DELAY_MS","newCommand","Command","opts","apiAddr","getAPIAddr","autoReconnect","isLoggedIn","client","APIClient","cmdArgs","secrets","getAllSecrets","secretNames","listSecretNames","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","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","Command","createInterface","open","readHiddenInput","prompt","resolve","rl","createInterface","stdin","wasRaw","input","onData","char","c","readInput","answer","setupGitHub","open","token","result","verifyGitHubToken","setSecret","secretsCommand","Command","name","value","names","listSecretNames","deleteSecret","getSecret","Command","createWriteStream","mkdirSync","existsSync","unlinkSync","spawn","pipeline","Readable","extractTarGz","tarPath","destDir","resolve","reject","tar","spawn","stderr","data","code","downloadCommand","Command","label","destPath","opts","apiAddr","getAPIAddr","isLoggedIn","client","APIClient","downloadInfo","err","dest","response","fileStream","createWriteStream","pipeline","Readable","sizeKB","existsSync","tempPath","mkdirSync","unlinkSync","Command","createWriteStream","unlinkSync","spawn","pipeline","Readable","extractTarGz","tarPath","destDir","resolve","reject","tar","spawn","stderr","data","code","listTarContents","stdout","f","syncCommand","Command","label","opts","apiAddr","getAPIAddr","isLoggedIn","client","APIClient","downloadInfo","err","tempPath","response","fileStream","createWriteStream","pipeline","Readable","files","maxShow","i","unlinkSync","version","program","newCommand","connectCommand","listCommand","stopCommand","stopAllCommand","loginCommand","logoutCommand","versionCommand","updateCommand","secretsCommand","downloadCommand","syncCommand","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/lib/secrets.ts","../src/commands/connect.ts","../src/lib/session-picker.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","../src/commands/secrets.ts","../src/commands/download.ts","../src/commands/sync.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 { secretsCommand } from './commands/secrets.js';\nimport { downloadCommand } from './commands/download.js';\nimport { syncCommand } from './commands/sync.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);\nprogram.addCommand(secretsCommand);\nprogram.addCommand(downloadCommand);\nprogram.addCommand(syncCommand);\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';\nimport { getAllSecrets, listSecretNames } from '../lib/secrets.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-auto-reconnect', 'Disable automatic reconnection on disconnect')\n .option('--no-secrets', \"Don't pass stored secrets to session\")\n .option('--no-sync-back', \"Don't sync remote file changes back to local\")\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 // Gather secrets to pass to session\n let secrets: Record<string, string> | undefined;\n if (opts.secrets !== false) {\n secrets = getAllSecrets();\n const secretNames = listSecretNames();\n if (secretNames.length > 0) {\n console.log(`Secrets: ${secretNames.join(', ')}`);\n }\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 secrets,\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 if (opts.syncBack) {\n console.log(` Sync-back: enabled (remote changes will sync to local)`);\n }\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';\n\nexport const SECRETS_FILE = 'secrets.json';\nexport const MAX_UPLOAD_SIZE = 1024 * 1024 * 1024; // 1GB\n\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)\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 async getSessionDownload(\n idOrLabel: string\n ): Promise<{ download_url: string; size_bytes?: number }> {\n const response = await this.doRequestWithRefresh(\n 'GET',\n `/v1/sessions/${idOrLabel}/download`\n );\n return this.handleResponse<{ download_url: string; size_bytes?: number }>(\n response\n );\n }\n}\n","import WebSocket from 'ws';\nimport { appendFileSync } from 'fs';\nimport { homedir } from 'os';\nimport { Terminal } from './terminal.js';\nimport {\n WS_POLICY_VIOLATION,\n WS_READ_TIMEOUT_MS,\n} from './config.js';\nimport {\n parseMessage,\n createResizeMessage,\n createPongMessage,\n createFileUploadMessage,\n createFileUploadChunkMessage,\n createSyncBackMessage,\n type Message,\n type ExitMessage,\n type ErrorMessage,\n type FileChangeMessage,\n type SyncBackAckMessage,\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; // Enable sync-back of remote file changes to local\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 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 if (opts.syncBack) {\n process.stderr.write('\\r\\n\\x1b[33mSync paused. Run `catty sync <label>` to pull latest changes.\\x1b[0m\\r\\n');\n }\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 if (opts.syncBack) {\n process.stderr.write('\\x1b[33mSync paused. Run `catty sync <label>` to pull latest changes.\\x1b[0m\\r\\n');\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 if (opts.syncBack) {\n process.stderr.write('\\x1b[33mSync paused. Run `catty sync <label>` to pull latest changes.\\x1b[0m\\r\\n');\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 // 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 // Request sync-back if enabled\n if (opts.syncBack) {\n debugLog('requesting sync-back');\n ws.send(createSyncBackMessage(true));\n }\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 'sync_back_ack': {\n const ackMsg = msg as SyncBackAckMessage;\n debugLog(`sync-back ack: enabled=${ackMsg.enabled}, dir=${ackMsg.workspace_dir}`);\n break;\n }\n case 'file_change': {\n const changeMsg = msg as FileChangeMessage;\n debugLog(`file change: ${changeMsg.action} ${changeMsg.path}`);\n applyRemoteFileChange(changeMsg);\n break;\n }\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 { mkdirSync, writeFileSync, unlinkSync, existsSync, chmodSync } from 'fs';\nimport { dirname, join } from 'path';\nimport { homedir } from 'os';\nimport { appendFileSync } from 'fs';\nimport type { FileChangeMessage } from '../protocol/messages.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()} [syncback] ${msg}\\n`);\n }\n}\n\n/**\n * Apply a remote file change to the local filesystem.\n * Called when the executor sends a file_change message.\n */\nexport function applyRemoteFileChange(msg: FileChangeMessage): void {\n try {\n // Get the local path - remove /workspace prefix and use cwd\n const relativePath = msg.path.replace(/^\\/workspace\\/?/, '');\n if (!relativePath) {\n debugLog('ignoring change to workspace root');\n return;\n }\n\n const localPath = join(process.cwd(), relativePath);\n\n // Security check: ensure we're not writing outside cwd\n const cwd = process.cwd();\n const resolved = join(cwd, relativePath);\n if (!resolved.startsWith(cwd)) {\n debugLog(`SECURITY: attempted write outside cwd: ${resolved}`);\n return;\n }\n\n if (msg.action === 'delete') {\n if (existsSync(localPath)) {\n unlinkSync(localPath);\n debugLog(`deleted: ${relativePath}`);\n }\n } else if (msg.action === 'write') {\n if (!msg.content) {\n debugLog(`write without content: ${relativePath}`);\n return;\n }\n\n // Ensure directory exists\n const dir = dirname(localPath);\n mkdirSync(dir, { recursive: true });\n\n // Decode base64 content and write\n const content = Buffer.from(msg.content, 'base64');\n writeFileSync(localPath, content);\n\n // Apply mode if provided\n if (msg.mode !== undefined) {\n try {\n chmodSync(localPath, msg.mode);\n } catch {\n // Ignore chmod errors (may not be supported on all platforms)\n }\n }\n\n debugLog(`wrote: ${relativePath} (${content.length} bytes)`);\n }\n } catch (err) {\n debugLog(`ERROR applying change: ${err}`);\n }\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 { homedir, hostname } from 'os';\nimport { join } from 'path';\nimport {\n readFileSync,\n writeFileSync,\n mkdirSync,\n existsSync,\n unlinkSync,\n} from 'fs';\nimport {\n createCipheriv,\n createDecipheriv,\n randomBytes,\n scryptSync,\n createHash,\n} from 'crypto';\nimport { CREDENTIALS_DIR, SECRETS_FILE } from './config.js';\n\nconst ALGORITHM = 'aes-256-gcm';\nconst SALT = 'catty-secrets-v1';\n\ninterface SecretsStore {\n version: number;\n secrets: Record<string, string>; // name -> encrypted value\n}\n\n/**\n * Get machine-specific encryption key.\n * Uses hostname + homedir as entropy (unique per machine).\n */\nfunction getEncryptionKey(): Buffer {\n const machineId = createHash('sha256')\n .update(`${hostname()}:${homedir()}:catty-machine-key`)\n .digest('hex');\n return scryptSync(machineId, SALT, 32);\n}\n\nfunction getSecretsDir(): string {\n return join(homedir(), CREDENTIALS_DIR);\n}\n\nfunction getSecretsPath(): string {\n return join(getSecretsDir(), SECRETS_FILE);\n}\n\nfunction loadStore(): SecretsStore {\n const path = getSecretsPath();\n try {\n const content = readFileSync(path, 'utf-8');\n return JSON.parse(content) as SecretsStore;\n } catch {\n return { version: 1, secrets: {} };\n }\n}\n\nfunction saveStore(store: SecretsStore): void {\n const dir = getSecretsDir();\n const path = getSecretsPath();\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(store, null, 2), { mode: 0o600 });\n}\n\n/**\n * Encrypt a secret value.\n */\nfunction encrypt(value: string): string {\n const key = getEncryptionKey();\n const iv = randomBytes(16);\n const cipher = createCipheriv(ALGORITHM, key, iv);\n\n let encrypted = cipher.update(value, 'utf8', 'base64');\n encrypted += cipher.final('base64');\n const authTag = cipher.getAuthTag().toString('base64');\n\n // Format: version:iv:authTag:encrypted\n return `v1:${iv.toString('base64')}:${authTag}:${encrypted}`;\n}\n\n/**\n * Decrypt a secret value.\n */\nfunction decrypt(encrypted: string): string | null {\n try {\n const [version, ivB64, authTagB64, data] = encrypted.split(':');\n if (version !== 'v1') return null;\n\n const key = getEncryptionKey();\n const decipher = createDecipheriv(\n ALGORITHM,\n key,\n Buffer.from(ivB64, 'base64')\n );\n decipher.setAuthTag(Buffer.from(authTagB64, 'base64'));\n\n let decrypted = decipher.update(data, 'base64', 'utf8');\n decrypted += decipher.final('utf8');\n return decrypted;\n } catch {\n return null;\n }\n}\n\n/**\n * Set a secret value.\n */\nexport function setSecret(name: string, value: string): void {\n const store = loadStore();\n store.secrets[name] = encrypt(value);\n saveStore(store);\n}\n\n/**\n * Get a secret value.\n */\nexport function getSecret(name: string): string | null {\n const store = loadStore();\n const encrypted = store.secrets[name];\n if (!encrypted) return null;\n return decrypt(encrypted);\n}\n\n/**\n * Delete a secret.\n */\nexport function deleteSecret(name: string): boolean {\n const store = loadStore();\n if (!(name in store.secrets)) return false;\n delete store.secrets[name];\n saveStore(store);\n return true;\n}\n\n/**\n * List all secret names (not values).\n */\nexport function listSecretNames(): string[] {\n const store = loadStore();\n return Object.keys(store.secrets);\n}\n\n/**\n * Get all secrets as key-value pairs (decrypted).\n * Used when passing to API for session creation.\n */\nexport function getAllSecrets(): Record<string, string> {\n const store = loadStore();\n const result: Record<string, string> = {};\n\n for (const name of Object.keys(store.secrets)) {\n const value = decrypt(store.secrets[name]);\n if (value !== null) {\n result[name] = value;\n }\n }\n\n return result;\n}\n\n/**\n * Check if secrets file exists.\n */\nexport function hasSecrets(): boolean {\n return existsSync(getSecretsPath());\n}\n\n/**\n * Clear all secrets.\n */\nexport function clearAllSecrets(): void {\n const path = getSecretsPath();\n if (existsSync(path)) {\n unlinkSync(path);\n }\n}\n\n/**\n * Verify a GitHub token is valid and return user info.\n */\nexport async function verifyGitHubToken(\n token: string\n): Promise<{ valid: boolean; username?: string; error?: string }> {\n try {\n const res = await fetch('https://api.github.com/user', {\n headers: {\n Authorization: `Bearer ${token}`,\n Accept: 'application/vnd.github.v3+json',\n 'User-Agent': 'catty-cli',\n },\n });\n\n if (res.status === 401) {\n return { valid: false, error: 'Invalid token' };\n }\n\n if (!res.ok) {\n return { valid: false, error: `GitHub API error: ${res.status}` };\n }\n\n const user = (await res.json()) as { login: string };\n return { valid: true, username: user.login };\n } catch (err) {\n return {\n valid: false,\n error: err instanceof Error ? err.message : 'Unknown error',\n };\n }\n}\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';\nimport { pickSession } from '../lib/session-picker.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('--no-auto-reconnect', 'Disable automatic reconnection on disconnect')\n .option('--no-sync-back', \"Don't sync remote file changes back to local\")\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 sessionLabel = label;\n\n // If no label provided, show interactive session picker\n if (!sessionLabel) {\n const sessions = await client.listSessions();\n const selected = await pickSession(sessions);\n\n if (!selected) {\n process.exit(0);\n }\n\n sessionLabel = selected.label;\n console.log(''); // Add spacing after picker\n }\n\n let reconnectAttempts = 0;\n\n while (true) {\n try {\n console.log(`Looking up session ${sessionLabel}...`);\n const session = await client.getSession(sessionLabel, 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 if (opts.syncBack) {\n console.log(` Sync-back: enabled (remote changes will sync to local)`);\n }\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 !== 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 ${sessionLabel}' 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 type { SessionInfo } from '../types/index.js';\nimport * as readline from 'readline';\n\nconst PAGE_SIZE = 10;\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\n/**\n * Format a session for display in the picker\n */\nfunction formatSession(session: SessionInfo): string {\n const age = formatAge(new Date(session.created_at));\n const statusColor =\n session.status === 'running'\n ? '\\x1b[32m'\n : session.status === 'stopped'\n ? '\\x1b[31m'\n : '\\x1b[33m';\n const reset = '\\x1b[0m';\n\n return `${session.label.padEnd(24)} ${statusColor}${session.status.padEnd(10)}${reset} ${session.region.padEnd(8)} ${age}`;\n}\n\n/**\n * Interactive session picker using arrow keys with pagination\n * Returns the selected session or null if cancelled\n */\nexport async function pickSession(\n sessions: SessionInfo[]\n): Promise<SessionInfo | null> {\n if (sessions.length === 0) {\n console.log('No sessions found.');\n return null;\n }\n\n // Sort by created_at descending (most recent first)\n const sortedSessions = [...sessions].sort(\n (a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()\n );\n\n if (sortedSessions.length === 0) {\n console.log('No sessions available.');\n return null;\n }\n\n return new Promise((resolve) => {\n let selectedIndex = 0;\n let currentPage = 0;\n const items = sortedSessions;\n const totalPages = Math.ceil(items.length / PAGE_SIZE);\n\n const getPageItems = () => {\n const start = currentPage * PAGE_SIZE;\n return items.slice(start, start + PAGE_SIZE);\n };\n\n const render = () => {\n const pageItems = getPageItems();\n\n // Move cursor up to redraw - always use PAGE_SIZE + 2 for consistent layout\n const linesToClear = PAGE_SIZE + 2;\n process.stdout.write(`\\x1b[${linesToClear}A`);\n\n // Header with page indicator\n const pageInfo =\n totalPages > 1 ? ` \\x1b[2m(page ${currentPage + 1}/${totalPages})\\x1b[0m` : '';\n console.log(\n `\\x1b[1mSelect a session to connect:\\x1b[0m${pageInfo} `\n );\n\n // Items - always render PAGE_SIZE lines for consistent height\n for (let i = 0; i < PAGE_SIZE; i++) {\n if (i < pageItems.length) {\n const prefix = i === selectedIndex ? '\\x1b[36m❯ ' : ' ';\n const suffix = i === selectedIndex ? '\\x1b[0m' : '';\n console.log(\n `${prefix}${formatSession(pageItems[i])}${suffix} `\n );\n } else {\n // Empty line to maintain consistent height\n console.log(' ');\n }\n }\n\n // Instructions\n const navHint = totalPages > 1 ? '←/→ pages, ' : '';\n console.log(\n `\\x1b[2m${navHint}↑/↓ navigate, Enter select, q/Esc cancel\\x1b[0m `\n );\n };\n\n // Initial render - print placeholder lines first\n for (let i = 0; i < PAGE_SIZE + 2; i++) {\n console.log('');\n }\n\n render();\n\n // Set up raw mode for keyboard input\n if (process.stdin.isTTY) {\n readline.emitKeypressEvents(process.stdin);\n process.stdin.setRawMode(true);\n }\n\n const cleanup = () => {\n if (process.stdin.isTTY) {\n process.stdin.setRawMode(false);\n }\n process.stdin.removeListener('keypress', onKeypress);\n };\n\n const onKeypress = (\n _str: string,\n key: { name: string; ctrl: boolean; sequence: string }\n ) => {\n if (!key) return;\n\n const pageItems = getPageItems();\n\n if (key.name === 'up' || key.name === 'k') {\n selectedIndex = Math.max(0, selectedIndex - 1);\n render();\n } else if (key.name === 'down' || key.name === 'j') {\n selectedIndex = Math.min(pageItems.length - 1, selectedIndex + 1);\n render();\n } else if (key.name === 'left' || key.name === 'h') {\n // Previous page\n if (currentPage > 0) {\n currentPage--;\n selectedIndex = 0;\n render();\n }\n } else if (key.name === 'right' || key.name === 'l') {\n // Next page\n if (currentPage < totalPages - 1) {\n currentPage++;\n selectedIndex = 0;\n render();\n }\n } else if (key.name === 'return') {\n cleanup();\n resolve(pageItems[selectedIndex]);\n } else if (\n key.name === 'escape' ||\n key.name === 'q' ||\n (key.ctrl && key.name === 'c')\n ) {\n cleanup();\n if (key.ctrl && key.name === 'c') {\n console.log('');\n process.exit(130);\n }\n resolve(null);\n }\n };\n\n process.stdin.on('keypress', onKeypress);\n process.stdin.resume();\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","import { Command } from 'commander';\nimport { createInterface } from 'readline';\nimport open from 'open';\nimport {\n setSecret,\n getSecret,\n deleteSecret,\n listSecretNames,\n verifyGitHubToken,\n} from '../lib/secrets.js';\n\n/**\n * Read a line from stdin with hidden input (for secrets).\n */\nasync function readHiddenInput(prompt: string): Promise<string> {\n return new Promise((resolve) => {\n const rl = createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n // Hide input by writing asterisks\n process.stdout.write(prompt);\n\n const stdin = process.stdin;\n const wasRaw = stdin.isRaw;\n\n if (stdin.isTTY) {\n stdin.setRawMode(true);\n }\n\n let input = '';\n\n const onData = (char: Buffer) => {\n const c = char.toString();\n\n if (c === '\\n' || c === '\\r') {\n // Enter pressed\n stdin.removeListener('data', onData);\n if (stdin.isTTY) {\n stdin.setRawMode(wasRaw ?? false);\n }\n process.stdout.write('\\n');\n rl.close();\n resolve(input);\n } else if (c === '\\u0003') {\n // Ctrl+C\n process.stdout.write('\\n');\n process.exit(130);\n } else if (c === '\\u007F' || c === '\\b') {\n // Backspace\n if (input.length > 0) {\n input = input.slice(0, -1);\n process.stdout.write('\\b \\b');\n }\n } else if (c.charCodeAt(0) >= 32) {\n // Printable character\n input += c;\n process.stdout.write('•');\n }\n };\n\n stdin.on('data', onData);\n });\n}\n\n/**\n * Read a line from stdin (visible input).\n */\nasync function readInput(prompt: string): Promise<string> {\n return new Promise((resolve) => {\n const rl = createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n rl.question(prompt, (answer) => {\n rl.close();\n resolve(answer);\n });\n });\n}\n\n/**\n * Interactive GitHub token setup.\n */\nasync function setupGitHub(): Promise<void> {\n console.log(`\n┌──────────────────────────────────────────────────────────────┐\n│ GitHub Personal Access Token Setup │\n├──────────────────────────────────────────────────────────────┤\n│ │\n│ 1. Go to: https://github.com/settings/tokens/new │\n│ │\n│ 2. Create a token with these scopes: │\n│ ✓ repo (Full control of private repositories) │\n│ │\n│ 3. Generate and copy the token │\n│ │\n└──────────────────────────────────────────────────────────────┘\n`);\n\n const openBrowser = await readInput('Open GitHub in browser? [Y/n] ');\n if (openBrowser.toLowerCase() !== 'n') {\n await open(\n 'https://github.com/settings/tokens/new?scopes=repo&description=Catty%20CLI'\n );\n console.log('');\n }\n\n const token = await readHiddenInput('Paste your token: ');\n\n if (!token || token.trim() === '') {\n console.error('✗ No token provided');\n process.exit(1);\n }\n\n console.log('Verifying token...');\n const result = await verifyGitHubToken(token.trim());\n\n if (!result.valid) {\n console.error(`✗ ${result.error}`);\n process.exit(1);\n }\n\n // Save both common names for the token\n setSecret('GH_TOKEN', token.trim());\n setSecret('GITHUB_TOKEN', token.trim());\n\n console.log(`✓ Token verified (user: ${result.username})`);\n console.log('✓ Saved securely');\n console.log('');\n console.log('Your sessions will now have GitHub access.');\n console.log('Claude can clone repos, push commits, and more.');\n}\n\nexport const secretsCommand = new Command('secrets')\n .description('Manage secrets for remote sessions')\n .addCommand(\n new Command('add')\n .description('Add a secret')\n .argument('[name]', 'Secret name (or \"github\" for guided setup)')\n .action(async (name?: string) => {\n if (!name) {\n console.error('Usage: catty secrets add <name>');\n console.error(' catty secrets add github (guided setup)');\n process.exit(1);\n }\n\n if (name.toLowerCase() === 'github') {\n await setupGitHub();\n return;\n }\n\n // Generic secret\n const value = await readHiddenInput(`Enter value for ${name}: `);\n if (!value || value.trim() === '') {\n console.error('✗ No value provided');\n process.exit(1);\n }\n\n setSecret(name, value.trim());\n console.log(`✓ Secret \"${name}\" saved`);\n })\n )\n .addCommand(\n new Command('set')\n .description('Set a secret (non-interactive)')\n .argument('<name>', 'Secret name')\n .argument('<value>', 'Secret value')\n .action((name: string, value: string) => {\n setSecret(name, value);\n console.log(`✓ Secret \"${name}\" saved`);\n })\n )\n .addCommand(\n new Command('list')\n .description('List configured secrets')\n .action(() => {\n const names = listSecretNames();\n if (names.length === 0) {\n console.log('No secrets configured.');\n console.log('');\n console.log('Add secrets with:');\n console.log(' catty secrets add github # GitHub token (guided)');\n console.log(' catty secrets add <NAME> # Any secret');\n return;\n }\n\n console.log('Configured secrets:');\n for (const name of names) {\n console.log(` • ${name}`);\n }\n console.log('');\n console.log('Secrets are passed to sessions as environment variables.');\n })\n )\n .addCommand(\n new Command('remove')\n .description('Remove a secret')\n .argument('<name>', 'Secret name')\n .action((name: string) => {\n const deleted = deleteSecret(name);\n if (deleted) {\n // Also delete paired token if removing GitHub\n if (name === 'GH_TOKEN') deleteSecret('GITHUB_TOKEN');\n if (name === 'GITHUB_TOKEN') deleteSecret('GH_TOKEN');\n\n console.log(`✓ Secret \"${name}\" removed`);\n } else {\n console.error(`✗ Secret \"${name}\" not found`);\n process.exit(1);\n }\n })\n )\n .addCommand(\n new Command('test')\n .description('Test a secret (e.g., verify GitHub token)')\n .argument('<name>', 'Secret name (currently only \"github\" supported)')\n .action(async (name: string) => {\n if (name.toLowerCase() === 'github') {\n const token = getSecret('GH_TOKEN') || getSecret('GITHUB_TOKEN');\n if (!token) {\n console.error('✗ No GitHub token configured');\n console.error(' Run: catty secrets add github');\n process.exit(1);\n }\n\n console.log('Testing GitHub token...');\n const result = await verifyGitHubToken(token);\n\n if (result.valid) {\n console.log(`✓ Token valid (user: ${result.username})`);\n } else {\n console.error(`✗ ${result.error}`);\n console.error(' Run: catty secrets add github');\n process.exit(1);\n }\n } else {\n console.error(\n `✗ Testing \"${name}\" is not supported. Only \"github\" can be tested.`\n );\n process.exit(1);\n }\n })\n );\n\n","import { Command } from 'commander';\nimport { createWriteStream, mkdirSync, existsSync, unlinkSync } from 'fs';\nimport { spawn } from 'child_process';\nimport { pipeline } from 'stream/promises';\nimport { Readable } from 'stream';\nimport { getAPIAddr } from '../lib/config.js';\nimport { isLoggedIn } from '../lib/auth.js';\nimport { APIClient } from '../lib/api-client.js';\n\nasync function extractTarGz(tarPath: string, destDir: string): Promise<void> {\n return new Promise((resolve, reject) => {\n const tar = spawn('tar', ['-xzf', tarPath, '-C', destDir], {\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n let stderr = '';\n tar.stderr.on('data', (data) => {\n stderr += data.toString();\n });\n\n tar.on('close', (code) => {\n if (code === 0) {\n resolve();\n } else {\n reject(new Error(`tar extraction failed: ${stderr || `exit code ${code}`}`));\n }\n });\n\n tar.on('error', reject);\n });\n}\n\nexport const downloadCommand = new Command('download')\n .description('Download workspace from a session')\n .argument('<label>', 'Session label (e.g., brave-tiger-1234)')\n .argument('[path]', 'Destination path (default: ./<label>)')\n .option('--format <type>', 'Output format: dir or tar.gz', 'dir')\n .action(async function (this: Command, label: string, destPath?: 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(`Fetching download URL for ${label}...`);\n\n let downloadInfo;\n try {\n downloadInfo = await client.getSessionDownload(label);\n } catch (err) {\n if (err instanceof Error) {\n console.error(`✗ ${err.message}`);\n } else {\n console.error('✗ Failed to get download URL');\n }\n process.exit(1);\n }\n\n const dest = destPath || `./${label}`;\n\n if (opts.format === 'tar.gz') {\n // Download as tarball\n const tarPath = dest.endsWith('.tar.gz') ? dest : `${dest}.tar.gz`;\n console.log(`Downloading to ${tarPath}...`);\n\n const response = await fetch(downloadInfo.download_url);\n if (!response.ok || !response.body) {\n if (response.status === 404) {\n console.error(`✗ No workspace snapshot found for ${label}`);\n console.error(' The session may not have saved yet or was just created.');\n } else {\n console.error(`✗ Download failed: ${response.statusText}`);\n }\n process.exit(1);\n }\n\n const fileStream = createWriteStream(tarPath);\n await pipeline(Readable.fromWeb(response.body as never), fileStream);\n\n const sizeKB = downloadInfo.size_bytes\n ? Math.round(downloadInfo.size_bytes / 1024)\n : 'unknown';\n console.log(`✓ Downloaded ${tarPath} (${sizeKB} KB)`);\n } else {\n // Download and extract to directory\n console.log(`Downloading and extracting to ${dest}/...`);\n\n if (existsSync(dest)) {\n console.error(`✗ Destination already exists: ${dest}`);\n console.error(' Remove it first or specify a different path.');\n process.exit(1);\n }\n\n // Download to temp file first\n const tempPath = `/tmp/catty-download-${Date.now()}.tar.gz`;\n const response = await fetch(downloadInfo.download_url);\n if (!response.ok || !response.body) {\n if (response.status === 404) {\n console.error(`✗ No workspace snapshot found for ${label}`);\n console.error(' The session may not have saved yet (saves every 30s) or was just created.');\n } else {\n console.error(`✗ Download failed: ${response.statusText}`);\n }\n process.exit(1);\n }\n\n const fileStream = createWriteStream(tempPath);\n await pipeline(Readable.fromWeb(response.body as never), fileStream);\n\n // Extract\n mkdirSync(dest, { recursive: true });\n try {\n await extractTarGz(tempPath, dest);\n } finally {\n // Clean up temp file\n try {\n unlinkSync(tempPath);\n } catch {\n // Ignore cleanup errors\n }\n }\n\n console.log(`✓ Downloaded to ${dest}/`);\n }\n });\n\n","import { Command } from 'commander';\nimport { createWriteStream, unlinkSync } from 'fs';\nimport { spawn } from 'child_process';\nimport { pipeline } from 'stream/promises';\nimport { Readable } from 'stream';\nimport { getAPIAddr } from '../lib/config.js';\nimport { isLoggedIn } from '../lib/auth.js';\nimport { APIClient } from '../lib/api-client.js';\n\nasync function extractTarGz(tarPath: string, destDir: string): Promise<void> {\n return new Promise((resolve, reject) => {\n // BSD tar (macOS) overwrites by default, GNU tar needs explicit flag\n // Use basic flags that work on both\n const tar = spawn('tar', ['-xzf', tarPath, '-C', destDir], {\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n let stderr = '';\n tar.stderr.on('data', (data) => {\n stderr += data.toString();\n });\n\n tar.on('close', (code) => {\n if (code === 0) {\n resolve();\n } else {\n reject(\n new Error(`tar extraction failed: ${stderr || `exit code ${code}`}`)\n );\n }\n });\n\n tar.on('error', reject);\n });\n}\n\nasync function listTarContents(tarPath: string): Promise<string[]> {\n return new Promise((resolve, reject) => {\n const tar = spawn('tar', ['-tzf', tarPath], {\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n let stdout = '';\n let stderr = '';\n\n tar.stdout.on('data', (data) => {\n stdout += data.toString();\n });\n tar.stderr.on('data', (data) => {\n stderr += data.toString();\n });\n\n tar.on('close', (code) => {\n if (code === 0) {\n resolve(\n stdout\n .split('\\n')\n .filter((f) => f.trim())\n .filter((f) => !f.endsWith('/'))\n );\n } else {\n reject(new Error(`tar list failed: ${stderr || `exit code ${code}`}`));\n }\n });\n\n tar.on('error', reject);\n });\n}\n\nexport const syncCommand = new Command('sync')\n .description('Sync remote workspace to current directory')\n .argument('<label>', 'Session label (e.g., brave-tiger-1234)')\n .option('--dry-run', 'Show what would be synced without making changes')\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(`Fetching workspace for ${label}...`);\n\n let downloadInfo;\n try {\n downloadInfo = await client.getSessionDownload(label);\n } catch (err) {\n if (err instanceof Error) {\n console.error(`✗ ${err.message}`);\n } else {\n console.error('✗ Failed to get download URL');\n }\n process.exit(1);\n }\n\n // Download to temp file\n const tempPath = `/tmp/catty-sync-${Date.now()}.tar.gz`;\n const response = await fetch(downloadInfo.download_url);\n if (!response.ok || !response.body) {\n if (response.status === 404) {\n console.error(`✗ No workspace snapshot found for ${label}`);\n console.error(\n ' The session may not have saved yet (saves every 30s).'\n );\n } else {\n console.error(`✗ Download failed: ${response.statusText}`);\n }\n process.exit(1);\n }\n\n const fileStream = createWriteStream(tempPath);\n await pipeline(Readable.fromWeb(response.body as never), fileStream);\n\n try {\n if (opts.dryRun) {\n // List what would be synced\n const files = await listTarContents(tempPath);\n console.log(`\\nWould sync ${files.length} files:`);\n const maxShow = 20;\n for (let i = 0; i < Math.min(files.length, maxShow); i++) {\n console.log(` ${files[i]}`);\n }\n if (files.length > maxShow) {\n console.log(` ... and ${files.length - maxShow} more`);\n }\n console.log('\\nRun without --dry-run to apply.');\n } else {\n // Extract to current directory\n await extractTarGz(tempPath, '.');\n console.log(`✓ Synced workspace from ${label} to current directory`);\n }\n } finally {\n // Clean up temp file\n try {\n unlinkSync(tempPath);\n } catch {\n // Ignore cleanup errors\n }\n }\n });\n\n"],"mappings":"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","names":["program","Command","open","DEFAULT_API_ADDR","CREDENTIALS_DIR","CREDENTIALS_FILE","SECRETS_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","mkdirSync","writeFileSync","unlinkSync","existsSync","chmodSync","dirname","join","homedir","appendFileSync","debugLog","msg","logFile","applyRemoteFileChange","relativePath","localPath","cwd","resolved","dir","content","err","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","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","ackMsg","changeMsg","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","homedir","hostname","join","readFileSync","writeFileSync","mkdirSync","existsSync","unlinkSync","createCipheriv","createDecipheriv","randomBytes","scryptSync","createHash","ALGORITHM","SALT","getEncryptionKey","machineId","createHash","hostname","homedir","scryptSync","getSecretsDir","join","CREDENTIALS_DIR","getSecretsPath","SECRETS_FILE","loadStore","path","content","readFileSync","saveStore","store","dir","mkdirSync","writeFileSync","encrypt","value","key","iv","randomBytes","cipher","createCipheriv","encrypted","authTag","decrypt","version","ivB64","authTagB64","data","decipher","createDecipheriv","decrypted","setSecret","name","getSecret","deleteSecret","listSecretNames","getAllSecrets","result","verifyGitHubToken","token","res","err","MAX_RECONNECT_ATTEMPTS","RECONNECT_DELAY_MS","newCommand","Command","opts","apiAddr","getAPIAddr","autoReconnect","isLoggedIn","client","APIClient","cmdArgs","secrets","getAllSecrets","secretNames","listSecretNames","session","err","APIError","handleQuotaExceeded","uploadURL","buildUploadURL","uploadWorkspace","reconnectAttempts","result","connectToSession","sleep","checkoutURL","open","Command","readline","PAGE_SIZE","formatAge","date","seconds","minutes","hours","formatSession","session","age","statusColor","pickSession","sessions","sortedSessions","a","b","resolve","selectedIndex","currentPage","items","totalPages","getPageItems","start","render","pageItems","linesToClear","pageInfo","i","prefix","suffix","navHint","cleanup","onKeypress","_str","key","MAX_RECONNECT_ATTEMPTS","RECONNECT_DELAY_MS","connectCommand","Command","label","opts","apiAddr","getAPIAddr","autoReconnect","isLoggedIn","client","APIClient","sessionLabel","sessions","selected","pickSession","reconnectAttempts","session","result","connectToSession","sleep","err","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","Command","createInterface","open","readHiddenInput","prompt","resolve","rl","createInterface","stdin","wasRaw","input","onData","char","c","readInput","answer","setupGitHub","open","token","result","verifyGitHubToken","setSecret","secretsCommand","Command","name","value","names","listSecretNames","deleteSecret","getSecret","Command","createWriteStream","mkdirSync","existsSync","unlinkSync","spawn","pipeline","Readable","extractTarGz","tarPath","destDir","resolve","reject","tar","spawn","stderr","data","code","downloadCommand","Command","label","destPath","opts","apiAddr","getAPIAddr","isLoggedIn","client","APIClient","downloadInfo","err","dest","response","fileStream","createWriteStream","pipeline","Readable","sizeKB","existsSync","tempPath","mkdirSync","unlinkSync","Command","createWriteStream","unlinkSync","spawn","pipeline","Readable","extractTarGz","tarPath","destDir","resolve","reject","tar","spawn","stderr","data","code","listTarContents","stdout","f","syncCommand","Command","label","opts","apiAddr","getAPIAddr","isLoggedIn","client","APIClient","downloadInfo","err","tempPath","response","fileStream","createWriteStream","pipeline","Readable","files","maxShow","i","unlinkSync","version","program","newCommand","connectCommand","listCommand","stopCommand","stopAllCommand","loginCommand","logoutCommand","versionCommand","updateCommand","secretsCommand","downloadCommand","syncCommand","main","command","updateAvailable","currentVersion","latestVersion","shouldPrompt","checkForUpdate","printUpdateAvailable","promptForUpdate","runUpdate","recordDeclinedUpdate","err"]}
|