@typhons/sandbox-tools 0.4.9 → 0.5.0

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.
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import{execSync as s}from"child_process";import f from"fs";import{setTimeout as x}from"timers/promises";var u=30,g=1,D=100,S=!1,v=!1,d=process.argv.slice(2);for(let e=0;e<d.length;e++)d[e]==="--json"?S=!0:d[e]==="--threshold"?u=parseInt(d[++e],10):d[e]==="--daemon"&&(v=!0);function _(e){try{if(e==="claude")return s("pgrep -x claude 2>/dev/null | head -1",{encoding:"utf8"}).trim()||null;if(e==="codex"){let t=s("pgrep -f 'node.*codex' 2>/dev/null",{encoding:"utf8"}).trim().split(`
3
- `);for(let n of t)if(n)try{let o=f.readFileSync(`/proc/${n}/cmdline`,"utf8").replace(/\0/g," ");if(/codex\.js|codex\/bin|@openai\/codex/.test(o))return n}catch{}}}catch{}return null}function T(){try{let e=s('find /home -name "*.jsonl" -path "*/.claude/projects/*" ! -path "*/subagents/*" -printf "%T@ %p\\n" 2>/dev/null | sort -rn | head -1',{encoding:"utf8"}).trim();if(e){let t=parseInt(e.split(" ")[0],10);return Math.floor(Date.now()/1e3)-t}}catch{}return-1}function E(){let e=-1,t=["/home/*/.codex/log/codex-tui.log","/root/.codex/log/codex-tui.log"];for(let n of t)try{let o=s(`ls ${n} 2>/dev/null`,{encoding:"utf8"}).trim().split(`
4
- `);for(let c of o)if(c)try{let a=f.statSync(c),l=Math.floor(Date.now()/1e3)-Math.floor(a.mtimeMs/1e3);(e===-1||l<e)&&(e=l)}catch{}}catch{}return e}function $(e){if(!e)return 0;let t=0;try{let n=s(`pgrep -P ${e} 2>/dev/null`,{encoding:"utf8"}).trim().split(`
5
- `);for(let o of n)if(o)try{if(f.readFileSync(`/proc/${o}/cmdline`,"utf8").replace(/\0/g," ").includes("check-claude-active"))continue;t++}catch{t++}}catch{}return t}function r(e){try{let n=f.readFileSync(`/proc/${e}/io`,"utf8").match(/^rchar:\s*(\d+)/m);return n?parseInt(n[1],10):0}catch{return 0}}async function A(e){if(!e)return 0;let t=r(e);return await x(g*1e3),r(e)-t}async function I(){let e=_("claude"),t=_("codex"),n=T(),o=E(),c=n>=0&&n<u||o>=0&&o<u,a=$(e),l=$(t),y=a>0||l>0,i=0;if(e&&t){let w=r(e),O=r(t);await x(g*1e3);let b=r(e),N=r(t);i=Math.max(b-w,N-O)}else e?i=await A(e):t&&(i=await A(t));let h=i>D,p=c||y||h,m=e&&t?"both":e?"claude":t?"codex":"none";return S||v?console.log(JSON.stringify({active:p,agent:m,claude_pid:e?parseInt(e):null,codex_pid:t?parseInt(t):null,claude_jsonl_age:n,codex_log_age:o,log_active:c,children:a+l,io_delta:i,io_active:h})):!e&&!t?console.log("Agents: not running"):p?(console.log(`Agent: ACTIVE (${m})`),c&&(n>=0&&n<u&&console.log(` Claude JSONL written ${n}s ago`),o>=0&&o<u&&console.log(` Codex log written ${o}s ago`)),y&&console.log(` ${a} claude + ${l} codex child process(es)`),h&&console.log(` I/O: ${i} bytes read in ${g}s`)):(console.log(`Agent: idle (${m} running, waiting for user)`),e&&console.log(` Claude JSONL last written ${n}s ago`),t&&console.log(` Codex log last written ${o}s ago`),console.log(" No child processes"),console.log(` I/O: ${i} bytes in ${g}s`)),p}function C(){try{return f.readFileSync("/run/e2b/.DEVBOX_NAME","utf8").trim()}catch{}return""}if(v){let e=process.env.E2B_HTTP_DOMAIN||"d.typhons.dev";for(console.log(`[agent-keepalive] domain=${e} interval=30s`);;){if(await I()){let n=C();if(n){let o=`https://${n}.${e}:49999/`;try{s(`curl -sf -o /dev/null -w '%{http_code}' --max-time 5 "${o}" 2>/dev/null`,{encoding:"utf8"})}catch{}}else console.log("[agent-keepalive] no devbox name available yet, skipping ping")}await x(29e3)}}else{let e=await I();process.exit(e?0:1)}
2
+ import{execSync as r}from"child_process";import f from"fs";import{setTimeout as x}from"timers/promises";var u=30,g=1,b=100,w=!1,v=!1,d=process.argv.slice(2);for(let e=0;e<d.length;e++)d[e]==="--json"?w=!0:d[e]==="--threshold"?u=parseInt(d[++e],10):d[e]==="--daemon"&&(v=!0);function _(e){try{if(e==="claude")return r("pgrep -x claude 2>/dev/null | head -1",{encoding:"utf8"}).trim()||null;if(e==="codex"){let t=r("pgrep -f 'node.*codex' 2>/dev/null",{encoding:"utf8"}).trim().split(`
3
+ `);for(let n of t)if(n)try{let o=f.readFileSync(`/proc/${n}/cmdline`,"utf8").replace(/\0/g," ");if(/codex\.js|codex\/bin|@openai\/codex/.test(o))return n}catch{}}}catch{}return null}function T(){try{let e=r('find /home -name "*.jsonl" -path "*/.claude/projects/*" ! -path "*/subagents/*" -printf "%T@ %p\\n" 2>/dev/null | sort -rn | head -1',{encoding:"utf8"}).trim();if(e){let t=parseInt(e.split(" ")[0],10);return Math.floor(Date.now()/1e3)-t}}catch{}return-1}function E(){let e=-1,t=["/home/*/.codex/log/codex-tui.log","/root/.codex/log/codex-tui.log"];for(let n of t)try{let o=r(`ls ${n} 2>/dev/null`,{encoding:"utf8"}).trim().split(`
4
+ `);for(let c of o)if(c)try{let a=f.statSync(c),l=Math.floor(Date.now()/1e3)-Math.floor(a.mtimeMs/1e3);(e===-1||l<e)&&(e=l)}catch{}}catch{}return e}function $(e){if(!e)return 0;let t=0;try{let n=r(`pgrep -P ${e} 2>/dev/null`,{encoding:"utf8"}).trim().split(`
5
+ `);for(let o of n)if(o)try{if(f.readFileSync(`/proc/${o}/cmdline`,"utf8").replace(/\0/g," ").includes("check-claude-active"))continue;t++}catch{t++}}catch{}return t}function s(e){try{let n=f.readFileSync(`/proc/${e}/io`,"utf8").match(/^rchar:\s*(\d+)/m);return n?parseInt(n[1],10):0}catch{return 0}}async function A(e){if(!e)return 0;let t=s(e);return await x(g*1e3),s(e)-t}async function I(){let e=_("claude"),t=_("codex"),n=T(),o=E(),c=n>=0&&n<u||o>=0&&o<u,a=$(e),l=$(t),y=a>0||l>0,i=0;if(e&&t){let O=s(e),S=s(t);await x(g*1e3);let N=s(e),D=s(t);i=Math.max(N-O,D-S)}else e?i=await A(e):t&&(i=await A(t));let h=i>b,p=c||y||h,m=e&&t?"both":e?"claude":t?"codex":"none";return w||v?console.log(JSON.stringify({active:p,agent:m,claude_pid:e?parseInt(e):null,codex_pid:t?parseInt(t):null,claude_jsonl_age:n,codex_log_age:o,log_active:c,children:a+l,io_delta:i,io_active:h})):!e&&!t?console.log("Agents: not running"):p?(console.log(`Agent: ACTIVE (${m})`),c&&(n>=0&&n<u&&console.log(` Claude JSONL written ${n}s ago`),o>=0&&o<u&&console.log(` Codex log written ${o}s ago`)),y&&console.log(` ${a} claude + ${l} codex child process(es)`),h&&console.log(` I/O: ${i} bytes read in ${g}s`)):(console.log(`Agent: idle (${m} running, waiting for user)`),e&&console.log(` Claude JSONL last written ${n}s ago`),t&&console.log(` Codex log last written ${o}s ago`),console.log(" No child processes"),console.log(` I/O: ${i} bytes in ${g}s`)),p}function C(){try{return f.readFileSync("/run/e2b/.DEVBOX_NAME","utf8").trim()}catch{}return""}if(v){let e=process.env.E2B_HTTP_DOMAIN||"d.typhons.dev";for(console.log(`[agent-keepalive] domain=${e} interval=30s`);;){if(await I()){let n=C();if(n){let o=`https://${n}.${e}:49999/`;try{r(`curl -sf -o /dev/null -w '%{http_code}' --max-time 5 "${o}" 2>/dev/null`,{encoding:"utf8"})}catch{}}else console.log("[agent-keepalive] no devbox name available yet, skipping ping")}await x(29e3)}}else{let e=await I();process.exit(e?0:1)}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@typhons/sandbox-tools",
3
- "version": "0.4.9",
3
+ "version": "0.5.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "sandbox-relay-client": "./relay-client.js",
package/relay-client.js CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import b from"ws";import d from"fs";import{spawn as P,execSync as x,execFileSync as U}from"child_process";import{dirname as F}from"path";import{fileURLToPath as L}from"url";import k from"net";var B="/tmp/session-proxy.sock",j=2*1024*1024;var T=1,I=2;function _(e,n){if(!e||e.destroyed)return!1;let t=Buffer.alloc(5);return t[0]=T,t.writeUInt32LE(n.length,1),e.write(t),e.write(n),!0}function w(e,n){if(!e||e.destroyed)return!1;let t=Buffer.from(JSON.stringify(n)),i=Buffer.alloc(5);return i[0]=I,i.writeUInt32LE(t.length,1),e.write(i),e.write(t),!0}var E=class{constructor(n,t){this.onBinary=n,this.onJSON=t,this.buf=Buffer.alloc(0)}feed(n){for(this.buf=this.buf.length?Buffer.concat([this.buf,n]):n;this.buf.length>=5;){let t=this.buf[0],i=this.buf.readUInt32LE(1);if(this.buf.length<5+i)break;let c=this.buf.slice(5,5+i);if(this.buf=this.buf.slice(5+i),t===T)this.onBinary(c);else if(t===I)try{this.onJSON(JSON.parse(c.toString()))}catch(r){console.error(`[ipc] bad JSON: ${r.message}`)}}}};function v(e,{onConnect:n,onClose:t,onError:i,retryMs:c=500}={}){let r=null,f=!1,l=null;function g(){f||(r=k.createConnection(e),r.on("connect",()=>{n&&n(r)}),r.on("close",()=>{r=null,t&&t(),f||(l=setTimeout(g,c))}),r.on("error",a=>{i&&i(a)}))}return g(),{getSocket(){return r},close(){f=!0,l&&clearTimeout(l),r&&r.destroy()}}}var ce=F(L(import.meta.url));process.on("uncaughtException",e=>{console.error(`[relay-client] UNCAUGHT: ${e.message}`),console.error(e.stack)});process.on("unhandledRejection",e=>{console.error(`[relay-client] UNHANDLED REJECTION: ${e.message||e}`)});var D=process.env.RELAY_HOST||"ssh.typhons.dev",J="/run/e2b/.RELAY_TOKEN",Y=500,H=3e3,G=6e4,C="@typhons/sandbox-tools";function K(){try{return d.readFileSync(J,"utf8").trim()}catch{}return""}function R(){try{return d.readFileSync("/run/e2b/.E2B_SANDBOX_ID","utf8").trim()}catch{}return"unknown"}function o(e){console.log(`[relay-client ${new Date().toISOString().slice(11,19)}] ${e}`)}var m="unknown";function W(){if(process.env.RELAY_URL)return process.env.RELAY_URL;m=R();try{let e=d.readFileSync("/opt/sandbox/sandbox-id","utf8").trim();if(e&&e!==m){o(`Clone detected (${e} -> ${m}), interrupting agents...`);try{x("/opt/sandbox/bin/stop-agents",{timeout:1e4,stdio:"inherit"})}catch{}try{d.writeFileSync("/opt/sandbox/sandbox-id",m)}catch{}}}catch{}return`wss://${D}/tunnel/${m}`}var h=null,N=null;function z(){return v(B,{onConnect(n){o("Connected to session-proxy via IPC"),h=n,N=new E(t=>{s&&s.readyState===b.OPEN&&s.send(t)},t=>{s&&s.readyState===b.OPEN&&s.send(JSON.stringify(t))}),n.on("data",t=>N.feed(t))},onClose(){o("IPC connection to session-proxy lost, will retry..."),h=null,N=null},onError(n){n.code!=="ENOENT"&&n.code!=="ECONNREFUSED"&&o(`IPC error: ${n.message}`)}})}var s=null,$=null,u=0,y=null;function M(){if(s){try{s.terminate()}catch{}s=null}y&&(clearTimeout(y),y=null),y=setTimeout(()=>{y=null,A()},Y)}function A(){let e=++u,n=W();o(`Connecting to ${n}... (gen=${e}, sandbox=${m})`);let t={},i=K();if(i&&(t.Authorization=`Bearer ${i}`),s){try{s.terminate()}catch{}s=null}let c=new b(n,{headers:t});s=c;let r=null,f=!0;function l(){r&&(clearInterval(r),r=null),e===u&&M()}function g(){f=!0,r=setInterval(()=>{if(e!==u){clearInterval(r);return}if(!f){o("No pong received \u2014 connection dead, reconnecting"),l();return}f=!1;try{c.ping()}catch{l()}},H)}c.on("open",()=>{if(e!==u){c.terminate();return}o(`Connected${$?` (reconnect, port ${$})`:""} (gen=${e})`),g()}),c.on("pong",()=>{e===u&&(f=!0)}),c.on("message",(a,p)=>{if(e===u){if(!p){try{V(JSON.parse(a.toString()))}catch(S){o(`Bad control msg: ${S.message}`)}return}h&&!h.destroyed&&_(h,Buffer.from(a))}}),c.on("close",(a,p)=>{if(e!==u)return;let S=p?p.toString():"";a===4100&&(o(`Replaced by newer connection (code 4100, reason=${S}) \u2014 exiting`),process.exit(42)),o(`Data WS closed: code=${a}, reason=${S}`),l()}),c.on("error",a=>{e===u&&(o(`Data WS error: ${a.message} (code=${a.code||"none"})`),l())}),c.on("unexpected-response",(a,p)=>{e===u&&(o(`Data WS unexpected response: status=${p.statusCode}`),l())})}function V(e){if(e.type==="port"){$=e.port,o(`Assigned port ${e.port}`);return}if(e.type==="name"){if(o(`Name assigned: ${e.name}`),e.name&&/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/.test(e.name)){try{U("hostname",[e.name],{timeout:5e3,stdio:"ignore"}),d.writeFileSync("/etc/hostname",e.name+`
3
- `)}catch{}try{d.writeFileSync("/run/e2b/.DEVBOX_NAME",e.name)}catch{}}return}h&&!h.destroyed&&w(h,e)}function O(){try{let e=x(`npm view ${C} version 2>/dev/null`,{timeout:15e3}).toString().trim();if(!e)return;let n="";try{n=JSON.parse(d.readFileSync("/opt/sandbox/lib/node_modules/@typhons/sandbox-tools/package.json","utf8")).version||""}catch{return}if(e!==n){o(`Update available: ${n} \u2192 ${e}, installing...`),x(`npm install -g --prefix /opt/sandbox ${C}@latest`,{timeout:12e4,stdio:"inherit"});try{x('pkill -f "check-claude-active.*--daemon" 2>/dev/null',{shell:!0,timeout:5e3})}catch{}try{let t=d.openSync("/tmp/claude-keepalive.log","a");P("/opt/sandbox/bin/check-claude-active",["--daemon"],{detached:!0,stdio:["ignore",t,t]}).unref(),d.closeSync(t)}catch{}o("Update installed, exiting for restart..."),process.exit(0)}}catch{}}var se=z();A();setTimeout(O,1e4);setInterval(O,G);o(`Relay client started (sandbox=${R()})`);
2
+ import b from"ws";import f from"fs";import{spawn as D,execSync as N,execFileSync as P}from"child_process";import{dirname as U}from"path";import{fileURLToPath as F}from"url";import k from"net";var $="/tmp/session-proxy.sock",j=2*1024*1024;var I=1,T=2;function _(e,n){if(!e||e.destroyed)return!1;let t=Buffer.alloc(5);return t[0]=I,t.writeUInt32LE(n.length,1),e.write(t),e.write(n),!0}function A(e,n){if(!e||e.destroyed)return!1;let t=Buffer.from(JSON.stringify(n)),i=Buffer.alloc(5);return i[0]=T,i.writeUInt32LE(t.length,1),e.write(i),e.write(t),!0}var E=class{constructor(n,t){this.onBinary=n,this.onJSON=t,this.buf=Buffer.alloc(0)}feed(n){for(this.buf=this.buf.length?Buffer.concat([this.buf,n]):n;this.buf.length>=5;){let t=this.buf[0],i=this.buf.readUInt32LE(1);if(this.buf.length<5+i)break;let c=this.buf.slice(5,5+i);if(this.buf=this.buf.slice(5+i),t===I)this.onBinary(c);else if(t===T)try{this.onJSON(JSON.parse(c.toString()))}catch(r){console.error(`[ipc] bad JSON: ${r.message}`)}}}};function w(e,{onConnect:n,onClose:t,onError:i,retryMs:c=500}={}){let r=null,d=!1,l=null;function S(){d||(r=k.createConnection(e),r.on("connect",()=>{n&&n(r)}),r.on("close",()=>{r=null,t&&t(),d||(l=setTimeout(S,c))}),r.on("error",a=>{i&&i(a)}))}return S(),{getSocket(){return r},close(){d=!0,l&&clearTimeout(l),r&&r.destroy()}}}var ce=U(F(import.meta.url));process.on("uncaughtException",e=>{console.error(`[relay-client] UNCAUGHT: ${e.message}`),console.error(e.stack)});process.on("unhandledRejection",e=>{console.error(`[relay-client] UNHANDLED REJECTION: ${e.message||e}`)});var L=process.env.RELAY_HOST||"ssh.typhons.dev",J="/run/e2b/.RELAY_TOKEN",Y=500,H=3e3,G=6e4,O="@typhons/sandbox-tools";function K(){try{return f.readFileSync(J,"utf8").trim()}catch{}return""}function v(){try{return f.readFileSync("/run/e2b/.E2B_SANDBOX_ID","utf8").trim()}catch{}return"unknown"}function o(e){console.log(`[relay-client ${new Date().toISOString().slice(11,19)}] ${e}`)}var m="unknown";function V(){if(process.env.RELAY_URL)return process.env.RELAY_URL;m=v();try{let e=f.readFileSync("/run/e2b/.SAVED_SANDBOX_ID","utf8").trim();if(e&&e!==m){o(`Clone detected (${e} -> ${m}), interrupting agents...`);try{N("/opt/sandbox/bin/stop-agents",{timeout:1e4,stdio:"inherit"})}catch{}try{f.writeFileSync("/run/e2b/.SAVED_SANDBOX_ID",m)}catch{}}}catch{}return`wss://${L}/tunnel/${m}`}var h=null,x=null;function W(){return w($,{onConnect(n){o("Connected to session-proxy via IPC"),h=n,x=new E(t=>{s&&s.readyState===b.OPEN&&s.send(t)},t=>{s&&s.readyState===b.OPEN&&s.send(JSON.stringify(t))}),n.on("data",t=>x.feed(t))},onClose(){o("IPC connection to session-proxy lost, will retry..."),h=null,x=null},onError(n){n.code!=="ENOENT"&&n.code!=="ECONNREFUSED"&&o(`IPC error: ${n.message}`)}})}var s=null,B=null,u=0,y=null;function X(){if(s){try{s.terminate()}catch{}s=null}y&&(clearTimeout(y),y=null),y=setTimeout(()=>{y=null,C()},Y)}function C(){let e=++u,n=V();o(`Connecting to ${n}... (gen=${e}, sandbox=${m})`);let t={},i=K();if(i&&(t.Authorization=`Bearer ${i}`),s){try{s.terminate()}catch{}s=null}let c=new b(n,{headers:t});s=c;let r=null,d=!0;function l(){r&&(clearInterval(r),r=null),e===u&&X()}function S(){d=!0,r=setInterval(()=>{if(e!==u){clearInterval(r);return}if(!d){o("No pong received \u2014 connection dead, reconnecting"),l();return}d=!1;try{c.ping()}catch{l()}},H)}c.on("open",()=>{if(e!==u){c.terminate();return}o(`Connected${B?` (reconnect, port ${B})`:""} (gen=${e})`),S()}),c.on("pong",()=>{e===u&&(d=!0)}),c.on("message",(a,p)=>{if(e===u){if(!p){try{z(JSON.parse(a.toString()))}catch(g){o(`Bad control msg: ${g.message}`)}return}h&&!h.destroyed&&_(h,Buffer.from(a))}}),c.on("close",(a,p)=>{if(e!==u)return;let g=p?p.toString():"";a===4100&&(o(`Replaced by newer connection (code 4100, reason=${g}) \u2014 exiting`),process.exit(42)),o(`Data WS closed: code=${a}, reason=${g}`),l()}),c.on("error",a=>{e===u&&(o(`Data WS error: ${a.message} (code=${a.code||"none"})`),l())}),c.on("unexpected-response",(a,p)=>{e===u&&(o(`Data WS unexpected response: status=${p.statusCode}`),l())})}function z(e){if(e.type==="port"){B=e.port,o(`Assigned port ${e.port}`);return}if(e.type==="name"){if(o(`Name assigned: ${e.name}`),e.name&&/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/.test(e.name)){try{P("hostname",[e.name],{timeout:5e3,stdio:"ignore"}),f.writeFileSync("/etc/hostname",e.name+`
3
+ `)}catch{}try{f.writeFileSync("/run/e2b/.DEVBOX_NAME",e.name)}catch{}}return}h&&!h.destroyed&&A(h,e)}function R(){try{let e=N(`npm view ${O} version 2>/dev/null`,{timeout:15e3}).toString().trim();if(!e)return;let n="";try{n=JSON.parse(f.readFileSync("/opt/sandbox/lib/node_modules/@typhons/sandbox-tools/package.json","utf8")).version||""}catch{return}if(e!==n){o(`Update available: ${n} \u2192 ${e}, installing...`),N(`npm install -g --prefix /opt/sandbox ${O}@latest`,{timeout:12e4,stdio:"inherit"});try{N('pkill -f "check-claude-active.*--daemon" 2>/dev/null',{shell:!0,timeout:5e3})}catch{}try{let t=f.openSync("/tmp/claude-keepalive.log","a");D("/opt/sandbox/bin/check-claude-active",["--daemon"],{detached:!0,stdio:["ignore",t,t]}).unref(),f.closeSync(t)}catch{}o("Update installed, exiting for restart..."),process.exit(0)}}catch{}}var se=W();C();setTimeout(R,1e4);setInterval(R,G);o(`Relay client started (sandbox=${v()})`);