@diggerhq/catty 0.4.3 → 0.4.5
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 +31 -28
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,33 +1,33 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{program as
|
|
2
|
+
import{program as b}from"commander";import{Command as Po}from"commander";var se="https://api.catty.dev",A=".catty",ye="credentials.json",we="secrets.json";function h(e){return e||(process.env.CATTY_API_ADDR?process.env.CATTY_API_ADDR:se)}function $(e){return new Promise(t=>setTimeout(t,e))}import{homedir as Lt}from"os";import{join as be}from"path";import{readFileSync as Bt,writeFileSync as Ft,mkdirSync as Gt,unlinkSync as Wt,existsSync as zt}from"fs";function Se(){return be(Lt(),A)}function ne(){return be(Se(),ye)}function k(){let e=ne();try{let t=Bt(e,"utf-8");return JSON.parse(t)}catch{return null}}function H(e){let t=Se(),o=ne();Gt(t,{recursive:!0,mode:448}),Ft(o,JSON.stringify(e,null,2),{mode:384})}function xe(){let e=ne();zt(e)&&Wt(e)}function _(){let e=k();return!(!e||!e.access_token||e.expires_at&&!e.refresh_token&&new Date(e.expires_at)<=new Date)}function re(){return k()?.access_token||null}function _e(){return k()?.refresh_token||null}function Ce(){let e=re();if(!e)return null;try{let t=e.split(".");if(t.length!==3)return null;let s=t[1].replace(/-/g,"+").replace(/_/g,"/"),r=Buffer.from(s,"base64").toString("utf-8");return JSON.parse(r).sid||null}catch(t){return console.error("Failed to extract session ID from token:",t),null}}var N=class extends Error{constructor(o,s,r,i){super(r);this.statusCode=o;this.errorCode=s;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||se,this.authToken=re()}async doRequest(t,o,s){let r=new AbortController,i=setTimeout(()=>r.abort(),12e4);try{let n={"Content-Type":"application/json"};return this.authToken&&(n.Authorization=`Bearer ${this.authToken}`),await fetch(`${this.baseURL}${o}`,{method:t,headers:n,body:s?JSON.stringify(s):void 0,signal:r.signal})}finally{clearTimeout(i)}}async doRequestWithRefresh(t,o,s){let r=await this.doRequest(t,o,s);return r.status===401&&await this.refreshAuthToken()&&(r=await this.doRequest(t,o,s)),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 s=await o.json();if(!s.access_token)return!1;let r=k();return r&&(r.access_token=s.access_token,s.refresh_token&&(r.refresh_token=s.refresh_token),s.expires_in&&(r.expires_at=new Date(Date.now()+(s.expires_in-30)*1e3).toISOString()),H(r),this.authToken=s.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.error||"",o.message||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 s=o?`/v1/sessions/${t}?live=true`:`/v1/sessions/${t}`,r=await this.doRequestWithRefresh("GET",s);return this.handleResponse(r)}async stopSession(t,o){let s=o?`/v1/sessions/${t}/stop?delete=true`:`/v1/sessions/${t}/stop`,r=await this.doRequestWithRefresh("POST",s);if(!r.ok){let i;try{i=await r.json()}catch{i={error:r.statusText}}throw new N(r.status,i.error||"",i.message||i.error||r.statusText,i.upgrade_url)}}async createCheckoutSession(){let t=await this.doRequestWithRefresh("POST","/v1/billing/checkout");return(await this.handleResponse(t)).checkout_url}async getSessionDownload(t){let o=await this.doRequestWithRefresh("GET",`/v1/sessions/${t}/download`);return this.handleResponse(o)}async getSessionLogs(t){let o=await this.doRequestWithRefresh("GET",`/v1/sessions/${t}/logs`);if(o.status===404||!o.ok)return null;let s=await o.arrayBuffer();return Buffer.from(s)}async logout(){let t=Ce(),o=t?{session_id:t}:{},s=await this.doRequestWithRefresh("POST","/v1/auth/logout",o);return this.handleResponse(s)}};import J from"ws";import{appendFileSync as po}from"fs";import{homedir as uo}from"os";var ve=!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,!ve)){ve=!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
|
|
6
6
|
`),process.stderr.write(`\x1B[90mReconnect with: catty connect <session-label>\x1B[0m\r
|
|
7
|
-
`),process.exit(1)}),process.on("unhandledRejection",o=>{t();let
|
|
8
|
-
\x1B[31m\u2717 Unexpected error: ${
|
|
7
|
+
`),process.exit(1)}),process.on("unhandledRejection",o=>{t();let s=o instanceof Error?o.message:String(o);process.stderr.write(`\r
|
|
8
|
+
\x1B[31m\u2717 Unexpected error: ${s}\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,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
|
|
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 ke(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 ie(e,t){return JSON.stringify({type:w.RESIZE,cols:e,rows:t})}function Re(){return JSON.stringify({type:w.PONG})}function Ee(e){return JSON.stringify({type:w.SYNC_BACK,enabled:e})}function Te(e,t,o,s){return JSON.stringify({type:w.FILE_UPLOAD,filename:e,remote_path:t,content:o.toString("base64"),mime_type:s})}function $e(e,t,o,s,r,i,n){return JSON.stringify({type:w.FILE_UPLOAD_CHUNK,upload_id:e,filename:t,remote_path:o,chunk_index:s,total_chunks:r,content:i,mime_type:n})}import{mkdirSync as Vt,writeFileSync as Yt,unlinkSync as Ht,existsSync as qt,chmodSync as Jt}from"fs";import{dirname as Kt,join as Ie}from"path";import{homedir as Zt}from"os";import{appendFileSync as Xt}from"fs";function D(e){if(process.env.CATTY_DEBUG==="1"){let t=`${Zt()}/.catty-debug.log`;Xt(t,`${new Date().toISOString()} [syncback] ${e}
|
|
11
|
+
`)}}function Ae(e){try{let t=e.path.replace(/^\/workspace\/?/,"");if(!t){D("ignoring change to workspace root");return}let o=Ie(process.cwd(),t),s=process.cwd(),r=Ie(s,t);if(!r.startsWith(s)){D(`SECURITY: attempted write outside cwd: ${r}`);return}if(e.action==="delete")qt(o)&&(Ht(o),D(`deleted: ${t}`));else if(e.action==="write"){if(!e.content){D(`write without content: ${t}`);return}let i=Kt(o);Vt(i,{recursive:!0});let n=Buffer.from(e.content,"base64");if(Yt(o,n),e.mode!==void 0)try{Jt(o,e.mode)}catch{}D(`wrote: ${t} (${n.length} bytes)`)}}catch(t){D(`ERROR applying change: ${t}`)}}import{readFileSync as Qt,statSync as Pe,appendFileSync as eo}from"fs";import{basename as Me,extname as Ne}from"path";import{homedir as to}from"os";function O(e){if(process.env.CATTY_DEBUG==="1"){let t=`${to()}/.catty-debug.log`;eo(t,`${new Date().toISOString()} ${e}
|
|
12
|
+
`)}}var oo=[".png",".jpg",".jpeg",".gif",".webp",".bmp",".svg"],so=[".pdf",".txt",".md",".json",".xml",".csv"],no=[...oo,...so],ro=10*1024*1024,ae=10*1024;function De(e){let t=e.trim();O(`detectFilePaths input: ${JSON.stringify(t)}`);let o=io(t);O(`split paths: ${JSON.stringify(o)}`);let s=[];for(let r of o){let i=r.replace(/\\ /g," ");if(!i)continue;let n=i.startsWith("~")?i.replace(/^~/,process.env.HOME||"~"):i;if(!n.startsWith("/")&&!n.match(/^[A-Za-z]:\\/)){O(`skipping non-absolute: ${n}`);continue}try{Pe(n),O(`found file: ${n}`),s.push(n)}catch(c){O(`file not found: ${n} - ${c}`);continue}}return O(`found ${s.length} valid files`),s}function io(e){let t=[],o="",s=0;for(;s<e.length;)e[s]==="\\"&&s+1<e.length&&e[s+1]===" "?(o+="\\ ",s+=2):e[s]===" "?(o&&(t.push(o),o=""),s++):(o+=e[s],s++);return o&&t.push(o),t}function Oe(e){try{let t=Pe(e);if(!t.isFile())return{shouldUpload:!1};if(t.size>ro)return{shouldUpload:!1};let o=Ne(e).toLowerCase();if(!no.includes(o))return{shouldUpload:!1};let s=Qt(e),r=Me(e),i=`/workspace/.catty-uploads/${r}`,n=ao(o);return{shouldUpload:!0,localPath:e,remotePath:i,content:s,filename:r,mimeType:n}}catch{return{shouldUpload:!1}}}function ao(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 Ue(e){let t=Date.now(),o=Ne(e);return`${Me(e,o).replace(/\s+/g,"_").replace(/[^a-zA-Z0-9_-]/g,"")}-${t}${o}`}function x(e){if(process.env.CATTY_DEBUG==="1"){let t=`${uo()}/.catty-debug.log`;po(t,`${new Date().toISOString()} [ws] ${e}
|
|
13
|
+
`)}}var Le="\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((s,r)=>{let i=0,n=!1,c=!1,a=!1,u=!1,g=d=>{u||(u=!0,s(d))},S=!1,m="",G="",j=0,R=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{}g({type:"interrupted"})};process.once("SIGINT",V);let Y=()=>{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"),E();try{o.close()}catch{}
|
|
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{}g({type:"interrupted"})};process.once("SIGQUIT",Y);let de=setTimeout(()=>{if(!c&&!n){n=!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{}g({type:"disconnected",reason:"Connection timeout"})}},3e4),pe=Date.now(),At=75e3,ue=setInterval(()=>{if(n)return;let d=Date.now()-pe;if(d>At){x(`Client-side timeout: no data for ${d}ms`),clearInterval(ue);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{}E(),
|
|
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{}
|
|
19
|
+
`);try{o.terminate()}catch{}E(),g({type:"disconnected",reason:"Connection timed out (no data received)"})}},5e3),me=()=>{let{cols:d,rows:l}=t.getSize();o.readyState===J.OPEN&&o.send(ie(d,l))},E=()=>{n=!0,clearTimeout(de),clearInterval(ue),t.disableBracketedPaste(),t.restore(),t.offResize(me),process.stdin.off("data",ge),process.off("SIGINT",V),process.off("SIGQUIT",Y)},ge=d=>{if(o.readyState!==J.OPEN)return;if(d.indexOf(3)!==-1){let p=Date.now();if(p-j<R){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{}g({type:"interrupted"});return}j=p}try{let p=d.toString("utf-8");if(S){let f=p.indexOf(K);if(f===-1)m+=p;else{m+=p.slice(0,f),S=!1,fe(m),m="";let C=p.slice(f+K.length);C&&o.send(Buffer.from(C,"utf-8"))}}else{let f=p.indexOf(Le);if(f===-1){o.send(d);return}f>0&&o.send(Buffer.from(p.slice(0,f),"utf-8"));let C=p.slice(f+Le.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 S=!0,m=C}}catch{try{o.send(d)}catch{}}},Pt=async d=>{let l=Oe(d);if(x(`shouldAutoUpload: ${l.shouldUpload}, size: ${l.content?.length||0}`),!l.shouldUpload||!l.content||!l.filename||!l.remotePath||!l.mimeType)return null;let p=Ue(l.filename),f=`/workspace/.catty-uploads/${p}`;if(x(`uploading to: ${f}`),l.content.length>ae){let T=`${Date.now()}-${Math.random().toString(36).slice(2)}`,P=l.content.toString("base64"),v=Math.ceil(P.length/(ae*1.34)),oe=Math.ceil(P.length/v);x(`chunked upload: ${v} chunks, ~${oe} bytes each`);for(let M=0;M<v;M++){let he=M*oe,Nt=Math.min(he+oe,P.length),Dt=P.slice(he,Nt),Ot=$e(T,p,f,M,v,Dt,l.mimeType);o.send(Ot),x(`sent chunk ${M+1}/${v}`),M<v-1&&await new Promise(Ut=>setTimeout(Ut,1))}}else{let T=Te(p,f,l.content,l.mimeType);x(`single message size: ${T.length} bytes`),o.send(T)}return f},fe=async d=>{try{if(!d||!d.trim())return;let l=De(d);if(l.length>0){x(`found ${l.length} files to upload`);let p=[];for(let f of l){let C=await Pt(f);C&&p.push(C)}if(p.length>0){x(`uploaded ${p.length} files, sending paths`);let f=p.join(" ");o.send(Buffer.from(f,"utf-8")),x(`paths sent: ${f}`);return}}o.send(Buffer.from(d,"utf-8"))}catch(l){x(`ERROR in handlePastedContent: ${l}`);try{o.send(Buffer.from(d,"utf-8"))}catch{}}};o.on("open",()=>{c=!0,clearTimeout(de),t.makeRaw(),t.enableBracketedPaste();let{cols:d,rows:l}=t.getSize();o.send(ie(d,l)),e.syncBack&&(x("requesting sync-back"),o.send(Ee(!0))),t.onResize(me),process.stdin.on("data",ge)}),o.on("message",(d,l)=>{if(pe=Date.now(),l)process.stdout.write(d);else try{let p=ke(d.toString());Mt(p)}catch{}});function Mt(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
|
-
`),E(),o.close(),
|
|
22
|
+
`),E(),o.close(),g({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;x(`sync-back ack: enabled=${l.enabled}, dir=${l.workspace_dir}`);break}case"file_change":{let l=d;x(`file change: ${l.action} ${l.path}`),Ae(l);break}}}o.on("close",(d,l)=>{if(!n&&(E(),!a))if(d===1008)process.stderr.write(`\r
|
|
25
25
|
\x1B[33m\u26A0 Connection replaced by another client\x1B[0m\r
|
|
26
|
-
`),
|
|
26
|
+
`),g({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
|
+
`),g({type:"disconnected",reason:p})}}),o.on("error",d=>{n||(E(),!a&&(process.stderr.write(`\r
|
|
29
29
|
\x1B[31m\u2717 Connection error: ${d.message}\x1B[0m\r
|
|
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 Be,relative as yo}from"path";var wo=["node_modules","node_modules/**","__pycache__","__pycache__/**",".venv",".venv/**","venv","venv/**",".env","*.pyc",".DS_Store","*.log"];async function xo(e,t={}){let o=mo().add(wo);t.excludeGit&&o.add([".git",".git/**"]);try{let n=fo(Be(e,".gitignore"),"utf-8");o.add(n)}catch{}return new Promise((n,r)=>{let i=[],s=uo("zip",{zlib:{level:9}});s.on("data",c=>i.push(c)),s.on("end",()=>n(Buffer.concat(i))),s.on("error",r),Fe(e,e,o,s),s.finalize()})}function Fe(e,t,o,n){let r;try{r=go(t)}catch{return}for(let i of r){let s=Be(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;Fe(e,s,o,n)}else a.isFile()&&n.file(s,{name:c})}}async function Ge(e,t,o,n={}){let r=process.cwd(),i=await xo(r,n);if(i.length>1073741824)throw new Error(`Workspace too large (${i.length} bytes, max ${1073741824})`);let s=await fetch(e,{method:"POST",headers:{Authorization:`Bearer ${t}`,"Content-Type":"application/zip","fly-force-instance-id":o},body:i});if(!s.ok){let c=await s.text();throw new Error(`Upload failed: ${s.status} - ${c}`)}}function ze(e){return e.replace("wss://","https://").replace("ws://","http://").replace("/connect","/upload")}import{homedir as We,hostname as bo}from"os";import{join as je}from"path";import{readFileSync as So,writeFileSync as _o,mkdirSync as Co,existsSync as cs,unlinkSync as ls}from"fs";import{createCipheriv as vo,createDecipheriv as ko,randomBytes as Ro,scryptSync as Eo,createHash as To}from"crypto";var Ve="aes-256-gcm",$o="catty-secrets-v1";function He(){let e=To("sha256").update(`${bo()}:${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=So(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=Ro(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=ko(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-git","Don't upload .git 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"],{excludeGit:e.git===!1}),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 b=r*L;return i.slice(b,b+L)},a=()=>{let b=c(),m=L+2;process.stdout.write(`\x1B[${m}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 R=0;R<L;R++)if(R<b.length){let V=R===n?"\x1B[36m\u276F ":" ",H=R===n?"\x1B[0m":"";console.log(`${V}${Do(b[R])}${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 b=0;b<L+2;b++)console.log("");a(),process.stdin.isTTY&&(et.emitKeypressEvents(process.stdin),process.stdin.setRawMode(!0));let u=()=>{process.stdin.isTTY&&process.stdin.setRawMode(!1),process.stdin.removeListener("keypress",f)},f=(b,m)=>{if(!m)return;let G=c();m.name==="up"||m.name==="k"?(n=Math.max(0,n-1),a()):m.name==="down"||m.name==="j"?(n=Math.min(G.length-1,n+1),a()):m.name==="left"||m.name==="h"?r>0&&(r--,n=0,a()):m.name==="right"||m.name==="l"?r<s-1&&(r++,n=0,a()):m.name==="return"?(u(),o(G[n])):(m.name==="escape"||m.name==="q"||m.ctrl&&m.name==="c")&&(u(),m.ctrl&&m.name==="c"&&(console.log(""),process.exit(130)),o(null))};process.stdin.on("keypress",f),process.stdin.resume()})}var B=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==="timed_out"){console.log(`
|
|
30
|
+
`),g({type:"disconnected",reason:d.message})))}),process.on("exit",()=>{E(),o.readyState===J.OPEN&&o.close()})})}import mo from"archiver";import go from"ignore";import{readFileSync as fo,readdirSync as ho,statSync as yo}from"fs";import{join as Fe,relative as wo}from"path";var bo=["node_modules","node_modules/**","__pycache__","__pycache__/**",".venv",".venv/**","venv","venv/**",".env","*.pyc",".DS_Store","*.log"];async function So(e,t={}){let o=go().add(bo);t.excludeGit&&o.add([".git",".git/**"]);try{let s=fo(Fe(e,".gitignore"),"utf-8");o.add(s)}catch{}return new Promise((s,r)=>{let i=[],n=mo("zip",{zlib:{level:9}});n.on("data",c=>i.push(c)),n.on("end",()=>s(Buffer.concat(i))),n.on("error",r),Ge(e,e,o,n),n.finalize()})}function Ge(e,t,o,s){let r;try{r=ho(t)}catch{return}for(let i of r){let n=Fe(t,i),c=wo(e,n);if(o.ignores(c))continue;let a;try{a=yo(n)}catch{continue}if(a.isDirectory()){if(o.ignores(c+"/"))continue;Ge(e,n,o,s)}else a.isFile()&&s.file(n,{name:c})}}async function We(e,t,o,s={}){let r=process.cwd(),i=await So(r,s);if(i.length>1073741824)throw new Error(`Workspace too large (${i.length} bytes, max ${1073741824})`);let n=await fetch(e,{method:"POST",headers:{Authorization:`Bearer ${t}`,"Content-Type":"application/zip","fly-force-instance-id":o},body:i});if(!n.ok){let c=await n.text();throw new Error(`Upload failed: ${n.status} - ${c}`)}}function ze(e){return e.replace("wss://","https://").replace("ws://","http://").replace("/connect","/upload")}import{homedir as je,hostname as xo}from"os";import{join as Ve}from"path";import{readFileSync as _o,writeFileSync as Co,mkdirSync as vo,existsSync as cn,unlinkSync as ln}from"fs";import{createCipheriv as ko,createDecipheriv as Ro,randomBytes as Eo,scryptSync as To,createHash as $o}from"crypto";var Ye="aes-256-gcm",Io="catty-secrets-v1";function He(){let e=$o("sha256").update(`${xo()}:${je()}:catty-machine-key`).digest("hex");return To(e,Io,32)}function qe(){return Ve(je(),A)}function Je(){return Ve(qe(),we)}function W(){let e=Je();try{let t=_o(e,"utf-8");return JSON.parse(t)}catch{return{version:1,secrets:{}}}}function Ke(e){let t=qe(),o=Je();vo(t,{recursive:!0,mode:448}),Co(o,JSON.stringify(e,null,2),{mode:384})}function Ao(e){let t=He(),o=Eo(16),s=ko(Ye,t,o),r=s.update(e,"utf8","base64");r+=s.final("base64");let i=s.getAuthTag().toString("base64");return`v1:${o.toString("base64")}:${i}:${r}`}function Ze(e){try{let[t,o,s,r]=e.split(":");if(t!=="v1")return null;let i=He(),n=Ro(Ye,i,Buffer.from(o,"base64"));n.setAuthTag(Buffer.from(s,"base64"));let c=n.update(r,"base64","utf8");return c+=n.final("utf8"),c}catch{return null}}function z(e,t){let o=W();o.secrets[e]=Ao(t),Ke(o)}function ce(e){let o=W().secrets[e];return o?Ze(o):null}function X(e){let t=W();return e in t.secrets?(delete t.secrets[e],Ke(t),!0):!1}function Q(){let e=W();return Object.keys(e.secrets)}function Xe(){let e=W(),t={};for(let o of Object.keys(e.secrets)){let s=Ze(e.secrets[o]);s!==null&&(t[o]=s)}return t}async function le(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,Qe=2e3,et=new Po("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-git","Don't upload .git 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 s=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=Xe();let a=Q();a.length>0&&console.log(`Secrets: ${a.join(", ")}`)}let n;try{n=await s.createSession({agent:e.agent,cmd:r,region:"iad",ttl_sec:7200,secrets:i})}catch(a){if(a instanceof N&&a.isQuotaExceeded()){await Mo(a,s);return}throw a}if(console.log(`Session created: ${n.label}`),console.log(` Reconnect with: catty connect ${n.label}`),e.syncBack&&console.log(" Sync-back: enabled (remote changes will sync to local)"),e.upload!==!1){console.log("Uploading workspace...");let a=ze(n.connect_url);await We(a,n.connect_token,n.headers["fly-force-instance-id"],{excludeGit:e.git===!1}),console.log("Workspace uploaded.")}console.log(`Connecting to ${n.connect_url}...`);let c=0;for(;;)try{let a=await Z({connectURL:n.connect_url,connectToken:n.connect_token,headers:n.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 ${n.label}' to try again manually.`),process.exit(1)),console.log(`\x1B[33m\u27F3 Reconnecting (${c}/${U})...\x1B[0m`),await $(Qe);try{n=await s.getSession(n.label,!0),n.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 $(Qe);else throw a}});async function Mo(e,t){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(` ${e.message}`),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 o=await t.createCheckoutSession();console.error(` Upgrade: ${o}`)}catch(o){console.error(` Failed to get upgrade URL: ${o instanceof Error?o.message:String(o)}`),console.error(" Please visit https://catty.dev to upgrade")}console.error("")}import{Command as Oo}from"commander";import*as tt 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 s=Math.floor(o/60);return s<24?`${s}h ago`:`${Math.floor(s/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 ot(e){if(e.length===0)return console.log("No sessions found."),null;let t=[...e].sort((o,s)=>new Date(s.created_at).getTime()-new Date(o.created_at).getTime());return t.length===0?(console.log("No sessions available."),null):new Promise(o=>{let s=0,r=0,i=t,n=Math.ceil(i.length/L),c=()=>{let S=r*L;return i.slice(S,S+L)},a=()=>{let S=c(),m=L+2;process.stdout.write(`\x1B[${m}A`);let G=n>1?` \x1B[2m(page ${r+1}/${n})\x1B[0m`:"";console.log(`\x1B[1mSelect a session to connect:\x1B[0m${G} `);for(let R=0;R<L;R++)if(R<S.length){let V=R===s?"\x1B[36m\u276F ":" ",Y=R===s?"\x1B[0m":"";console.log(`${V}${Do(S[R])}${Y} `)}else console.log(" ");let j=n>1?"\u2190/\u2192 pages, ":"";console.log(`\x1B[2m${j}\u2191/\u2193 navigate, Enter select, q/Esc cancel\x1B[0m `)};for(let S=0;S<L+2;S++)console.log("");a(),process.stdin.isTTY&&(tt.emitKeypressEvents(process.stdin),process.stdin.setRawMode(!0));let u=()=>{process.stdin.isTTY&&process.stdin.setRawMode(!1),process.stdin.removeListener("keypress",g)},g=(S,m)=>{if(!m)return;let G=c();m.name==="up"||m.name==="k"?(s=Math.max(0,s-1),a()):m.name==="down"||m.name==="j"?(s=Math.min(G.length-1,s+1),a()):m.name==="left"||m.name==="h"?r>0&&(r--,s=0,a()):m.name==="right"||m.name==="l"?r<n-1&&(r++,s=0,a()):m.name==="return"?(u(),o(G[s])):(m.name==="escape"||m.name==="q"||m.ctrl&&m.name==="c")&&(u(),m.ctrl&&m.name==="c"&&(console.log(""),process.exit(130)),o(null))};process.stdin.on("keypress",g),process.stdin.resume()})}var B=5,st=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),s=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 ot(c);a||process.exit(0),i=a.label,console.log("")}let n=0;for(;;)try{console.log(`Looking up session ${i}...`);let c=await r.getSession(i,!0);if(c.status==="timed_out"){console.log(`
|
|
31
31
|
\x1B[33m\u23F0 Session ${c.label} has timed out due to inactivity.\x1B[0m
|
|
32
32
|
`);let u=await r.getSessionLogs(i);u&&u.length>0?(console.log(`\x1B[36m--- Last session output ---\x1B[0m
|
|
33
33
|
`),process.stdout.write(u),console.log(`
|
|
@@ -35,20 +35,23 @@ Error: ${l.message}\r
|
|
|
35
35
|
`)):console.log(`No saved logs available for this session.
|
|
36
36
|
`),console.log("To download the workspace files, run:"),console.log(` \x1B[1mcatty sync ${c.label}\x1B[0m
|
|
37
37
|
`),console.log("To start a new session, run:"),console.log(` \x1B[1mcatty new\x1B[0m
|
|
38
|
-
`),process.exit(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})`);
|
|
38
|
+
`),process.exit(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})`);n>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"&&(s||(console.error(`Disconnected: ${a.reason}`),process.exit(1)),n++,n>B&&(console.error(`\x1B[31m\u2717 Failed to reconnect after ${B} attempts\x1B[0m`),console.error(`Run 'catty connect ${i}' to try again manually.`),process.exit(1)),console.log(`\x1B[33m\u27F3 Reconnecting (${n}/${B})...\x1B[0m`),await $(st))}catch(c){if(n>0&&s)n++,n>B&&(console.error(`\x1B[31m\u2717 Failed to reconnect after ${B} attempts\x1B[0m`),process.exit(1)),console.error(`\x1B[33m\u27F3 Reconnect failed, retrying (${n}/${B})...\x1B[0m`),await $(st);else throw c}});import{Command as Uo}from"commander";var rt=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)),n=[r.label.padEnd(22),r.status.padEnd(9),r.region.padEnd(7),i].join(" ");console.log(n)}});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 s=Math.floor(o/60);return s<24?`${s}h ago`:`${Math.floor(s/24)}d ago`}import{Command as Bo}from"commander";var it=new Bo("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 Fo}from"commander";var at=new Fo("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),s=await o.listSessions();if(s.length===0){console.log("No sessions to stop");return}console.log(`Stopping ${s.length} sessions...`);for(let r of s){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 Wo from"open";var ct=new Go("login").description("Log in to Catty").action(async function(){let e=h(this.optsWithGlobals().api);if(_()){let i=k();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(`
|
|
39
39
|
Your confirmation code:
|
|
40
40
|
`),console.log(` ${o.user_code}
|
|
41
41
|
`),console.log(`Opening ${o.verification_uri_complete}
|
|
42
|
-
`),await
|
|
43
|
-
Logged in as ${
|
|
42
|
+
`),await Wo(o.verification_uri_complete),console.log("Waiting for authentication...");let s=(o.interval||5)*1e3,r=Date.now()+o.expires_in*1e3;for(;Date.now()<r;){await $(s);let n=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(!n.pending){if(n.error)throw new Error(n.error);if(n.access_token){H({access_token:n.access_token,refresh_token:n.refresh_token,user_id:n.user?.id||"",email:n.user?.email||"",expires_at:n.expires_in?new Date(Date.now()+(n.expires_in-30)*1e3).toISOString():void 0}),console.log(`
|
|
43
|
+
Logged in as ${n.user?.email}`),console.log("You can now run 'catty new' to start a session");return}}}throw new Error("Authentication timed out")});import{Command as zo}from"commander";import jo from"open";var lt=new zo("logout").description("Log out of Catty").action(async function(){if(!_()){console.log("Not logged in");return}let t=k()?.email||"",o;try{let s=h(this.optsWithGlobals().api);o=(await new y(s).logout()).logout_url}catch(s){console.error("Warning: Failed to get logout URL from server:",s instanceof Error?s.message:String(s))}if(xe(),console.log(t?`Logged out from ${t}`:"Logged out"),o){console.log(`
|
|
44
|
+
Clearing WorkOS session...`);try{await jo(o),console.log("Browser opened to complete logout")}catch(s){console.error("Warning: Failed to open browser:",s instanceof Error?s.message:String(s)),console.log(`
|
|
45
|
+
To complete logout and clear your browser session, visit:`),console.log(o)}}else console.log(`
|
|
46
|
+
Note: Could not retrieve WorkOS logout URL.`),console.log("Your local session has been cleared, but browser session may persist.")});import{Command as Vo}from"commander";var dt=new Vo("version").description("Print the version number").action(()=>{console.log("0.4.5")});import{Command as ss}from"commander";import{readFileSync as Yo,writeFileSync as Ho,existsSync as ut,mkdirSync as qo}from"fs";import{join as mt}from"path";import{homedir as gt}from"os";import{createInterface as Jo}from"readline";import{spawn as Ko}from"child_process";function pt(e){(process.env.DEBUG||process.env.CATTY_DEBUG)&&console.log(`\x1B[2m[DEBUG] ${e}\x1B[0m`)}var Zo=900*1e3,Xo=2880*60*1e3,Qo="https://registry.npmjs.org/@diggerhq/catty/latest";function ft(){return mt(gt(),A,"version-cache.json")}function ht(){try{let e=ft();if(!ut(e))return null;let t=Yo(e,"utf-8");return JSON.parse(t)}catch{return null}}function yt(e,t){try{let o=ft(),s=mt(gt(),A);ut(s)||qo(s,{recursive:!0});let r=ht(),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 es(){try{let e=await fetch(Qo,{signal:AbortSignal.timeout(5e3)});return e.ok&&(await e.json()).version||null}catch{return null}}function ts(e,t){let o=e.split(".").map(Number),s=t.split(".").map(Number);for(let r=0;r<3;r++){let i=o[r]||0,n=s[r]||0;if(n>i)return!0;if(n<i)return!1}return!1}async function ee(e){let t="0.4.5";if(t==="dev")return{updateAvailable:!1,currentVersion:t,latestVersion:null,shouldPrompt:!1};let o=ht(),s=Date.now(),r=null;if(!e?.bypassCache&&o&&s-o.lastChecked<Zo?r=o.latestVersion:(r=await es(),r&&yt(r)),!r)return{updateAvailable:!1,currentVersion:t,latestVersion:null,shouldPrompt:!1};let i=ts(t,r),n=i;return i&&o?.declinedVersion===r&&o.declinedAt&&(n=s-o.declinedAt>=Xo),{updateAvailable:i,currentVersion:t,latestVersion:r,shouldPrompt:n}}function wt(e,t){console.log(""),console.log(`\x1B[33mUpdate available:\x1B[0m \x1B[2m${e}\x1B[0m \u2192 \x1B[32m${t}\x1B[0m`)}async function bt(){let e=Jo({input:process.stdin,output:process.stdout});return new Promise(t=>{e.question("Would you like to update? (Y/n): ",o=>{e.close();let s=o.trim().toLowerCase();t(s===""||s==="y"||s==="yes")})})}function St(e){yt(e,!0)}function os(){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(`
|
|
44
47
|
Updating from ${e} to ${t}...
|
|
45
|
-
`);let o=
|
|
46
|
-
\x1B[32m\u2713\x1B[0m Successfully updated to version ${t}`),
|
|
48
|
+
`);let o=os(),s=`@diggerhq/catty@${t}`,r,i;switch(o){case"yarn":r="yarn",i=["global","add",s];break;case"pnpm":r="pnpm",i=["add","-g",s];break;case"bun":r="bun",i=["install","-g",s];break;default:r="npm",i=["install","-g",s]}return pt(`Package manager: ${o}`),pt(`Command: ${r} ${i.join(" ")}`),new Promise((n,c)=>{let a=Ko(r,i,{stdio:"inherit",shell:process.platform==="win32"});a.on("close",u=>{u===0?(console.log(`
|
|
49
|
+
\x1B[32m\u2713\x1B[0m Successfully updated to version ${t}`),n()):(console.error(`
|
|
47
50
|
\x1B[31m\u2717\x1B[0m Update failed with exit code ${u}`),c(new Error(`Update process exited with code ${u}`)))}),a.on("error",u=>{console.error(`
|
|
48
|
-
\x1B[31m\u2717\x1B[0m Failed to run update command: ${u.message}`),c(u)})})}var
|
|
49
|
-
`||a==="\r"?(
|
|
51
|
+
\x1B[31m\u2717\x1B[0m Failed to run update command: ${u.message}`),c(u)})})}var xt=new ss("update").description("Update catty to the latest version").action(async()=>{try{let e="0.4.5";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 F}from"commander";import{createInterface as _t}from"readline";import ns from"open";async function Ct(e){return new Promise(t=>{let o=_t({input:process.stdin,output:process.stdout});process.stdout.write(e);let s=process.stdin,r=s.isRaw;s.isTTY&&s.setRawMode(!0);let i="",n=c=>{let a=c.toString();a===`
|
|
52
|
+
`||a==="\r"?(s.removeListener("data",n),s.isTTY&&s.setRawMode(r??!1),process.stdout.write(`
|
|
50
53
|
`),o.close(),t(i)):a===""?(process.stdout.write(`
|
|
51
|
-
`),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"))};
|
|
54
|
+
`),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"))};s.on("data",n)})}async function rs(e){return new Promise(t=>{let o=_t({input:process.stdin,output:process.stdout});o.question(e,s=>{o.close(),t(s)})})}async function is(){console.log(`
|
|
52
55
|
\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
|
|
53
56
|
\u2502 GitHub Personal Access Token Setup \u2502
|
|
54
57
|
\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
|
|
@@ -61,8 +64,8 @@ Updating from ${e} to ${t}...
|
|
|
61
64
|
\u2502 3. Generate and copy the token \u2502
|
|
62
65
|
\u2502 \u2502
|
|
63
66
|
\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
|
|
64
|
-
`),(await
|
|
65
|
-
`).filter(c=>c.trim()).filter(c=>!c.endsWith("/"))):o(new Error(`tar list failed: ${i||`exit code ${
|
|
66
|
-
Would sync ${a.length} files:`);let u=20;for(let
|
|
67
|
-
Run without --dry-run to apply.`)}else await
|
|
67
|
+
`),(await rs("Open GitHub in browser? [Y/n] ")).toLowerCase()!=="n"&&(await ns("https://github.com/settings/tokens/new?scopes=repo&description=Catty%20CLI"),console.log(""));let t=await Ct("Paste your token: ");(!t||t.trim()==="")&&(console.error("\u2717 No token provided"),process.exit(1)),console.log("Verifying token...");let o=await le(t.trim());o.valid||(console.error(`\u2717 ${o.error}`),process.exit(1)),z("GH_TOKEN",t.trim()),z("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 vt=new F("secrets").description("Manage secrets for remote sessions").addCommand(new F("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 is();return}let t=await Ct(`Enter value for ${e}: `);(!t||t.trim()==="")&&(console.error("\u2717 No value provided"),process.exit(1)),z(e,t.trim()),console.log(`\u2713 Secret "${e}" saved`)})).addCommand(new F("set").description("Set a secret (non-interactive)").argument("<name>","Secret name").argument("<value>","Secret value").action((e,t)=>{z(e,t),console.log(`\u2713 Secret "${e}" saved`)})).addCommand(new F("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 F("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 F("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=ce("GH_TOKEN")||ce("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 le(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 as}from"commander";import{createWriteStream as kt,mkdirSync as cs,existsSync as ls,unlinkSync as ds}from"fs";import{spawn as ps}from"child_process";import{pipeline as Rt}from"stream/promises";import{Readable as Et}from"stream";async function us(e,t){return new Promise((o,s)=>{let r=ps("tar",["-xzf",e,"-C",t],{stdio:["ignore","pipe","pipe"]}),i="";r.stderr.on("data",n=>{i+=n.toString()}),r.on("close",n=>{n===0?o():s(new Error(`tar extraction failed: ${i||`exit code ${n}`}`))}),r.on("error",s)})}var Tt=new as("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(),s=h(this.optsWithGlobals().api);_()||(console.error("Not logged in. Please run 'catty login' first."),process.exit(1));let r=new y(s);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 n=t||`./${e}`;if(o.format==="tar.gz"){let c=n.endsWith(".tar.gz")?n:`${n}.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 u=kt(c);await Rt(Et.fromWeb(a.body),u);let g=i.size_bytes?Math.round(i.size_bytes/1024):"unknown";console.log(`\u2713 Downloaded ${c} (${g} KB)`)}else{console.log(`Downloading and extracting to ${n}/...`),ls(n)&&(console.error(`\u2717 Destination already exists: ${n}`),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 u=kt(c);await Rt(Et.fromWeb(a.body),u),cs(n,{recursive:!0});try{await us(c,n)}finally{try{ds(c)}catch{}}console.log(`\u2713 Downloaded to ${n}/`)}});import{Command as ms}from"commander";import{createWriteStream as gs,unlinkSync as fs}from"fs";import{spawn as $t}from"child_process";import{pipeline as hs}from"stream/promises";import{Readable as ys}from"stream";async function ws(e,t){return new Promise((o,s)=>{let r=$t("tar",["-xzf",e,"-C",t],{stdio:["ignore","pipe","pipe"]}),i="";r.stderr.on("data",n=>{i+=n.toString()}),r.on("close",n=>{n===0?o():s(new Error(`tar extraction failed: ${i||`exit code ${n}`}`))}),r.on("error",s)})}async function bs(e){return new Promise((t,o)=>{let s=$t("tar",["-tzf",e],{stdio:["ignore","pipe","pipe"]}),r="",i="";s.stdout.on("data",n=>{r+=n.toString()}),s.stderr.on("data",n=>{i+=n.toString()}),s.on("close",n=>{n===0?t(r.split(`
|
|
68
|
+
`).filter(c=>c.trim()).filter(c=>!c.endsWith("/"))):o(new Error(`tar list failed: ${i||`exit code ${n}`}`))}),s.on("error",o)})}var It=new ms("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 s=new y(o);console.log(`Fetching workspace for ${e}...`);let r;try{r=await s.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`,n=await fetch(r.download_url);(!n.ok||!n.body)&&(n.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: ${n.statusText}`),process.exit(1));let c=gs(i);await hs(ys.fromWeb(n.body),c);try{if(t.dryRun){let a=await bs(i);console.log(`
|
|
69
|
+
Would sync ${a.length} files:`);let u=20;for(let g=0;g<Math.min(a.length,u);g++)console.log(` ${a[g]}`);a.length>u&&console.log(` ... and ${a.length-u} more`),console.log(`
|
|
70
|
+
Run without --dry-run to apply.`)}else await ws(i,"."),console.log(`\u2713 Synced workspace from ${e} to current directory`)}finally{try{fs(i)}catch{}}});var Ss="0.4.5";b.name("catty").description("Catty - Remote AI agent sessions").option("--api <url>","API server address").version(Ss);b.addCommand(et);b.addCommand(nt);b.addCommand(rt);b.addCommand(it);b.addCommand(at,{hidden:!0});b.addCommand(ct);b.addCommand(lt);b.addCommand(dt);b.addCommand(xt);b.addCommand(vt);b.addCommand(Tt);b.addCommand(It);b.exitOverride();async function xs(){try{await b.parseAsync(process.argv);let e=process.argv[2];if(e&&!["version","update","-v","--version","-h","--help","help"].includes(e)){let{updateAvailable:o,currentVersion:s,latestVersion:r,shouldPrompt:i}=await ee();if(o&&r&&i)if(wt(s,r),await bt())try{await te(s,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)}}xs();
|
|
68
71
|
//# sourceMappingURL=index.js.map
|