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