@shogo-ai/worker 2.2.18 → 2.2.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -1,30 +1,30 @@
1
- import{createRequire as br}from"node:module";var vr=(e,t,r)=>()=>{if(e)try{t=e(e=0)}catch(o){r=[o]}if(r)throw r[0];return t};var Pe=br(import.meta.url);import{spawn as Vr,execFile as Yr}from"node:child_process";import{existsSync as _e}from"node:fs";import{join as Ae}from"node:path";import{promisify as Xr}from"node:util";async function wt(e=!1){if(ee!==null&&!e)return ee;try{await zr("git",["--version"],{timeout:3000}),ee=!0}catch{ee=!1}return ee}function de(e,t){return`${e.replace(/\/+$/,"")}/api/projects/${t}/git`}function P(e,t={}){let{cwd:r,env:o,timeoutMs:n=300000,logger:i}=t;return new Promise((s,a)=>{let c=Vr("git",e,{cwd:r,env:{...process.env,...o},stdio:["ignore","pipe","pipe"]}),l="",u="",p=[],m=[];c.stdout.setEncoding("utf-8"),c.stderr.setEncoding("utf-8"),c.stdout.on("data",(d)=>{if(p.push(d),i)i.log(d.trimEnd())}),c.stderr.on("data",(d)=>{if(m.push(d),i)i.warn(d.trimEnd())});let f=setTimeout(()=>{try{c.kill("SIGKILL")}catch{}a(Error(`git ${e[0]} timed out after ${n}ms`))},n);c.on("error",(d)=>{clearTimeout(f),a(d)}),c.on("close",(d)=>{clearTimeout(f),l=p.join(""),u=m.join("");let w=d??-1;if(w===0)s({stdout:l,stderr:u,exitCode:w});else{let g=Error(`git ${e[0]} exited with code ${w}: ${u.slice(0,500)}`);g.exitCode=w,g.stdout=l,g.stderr=u,a(g)}})})}async function yt(e){let{apiUrl:t,apiKey:r,projectId:o,localDir:n,shallow:i=!0,logger:s,timeoutMs:a}=e;if(_e(Ae(n,".git")))throw Error(`cloneProject: ${n}/.git already exists; use gitFetchAndReset instead`);let c=de(t,o),l=["-c",`http.extraHeader=Authorization: Bearer ${r}`,"clone"];if(i)l.push("--depth=1");return l.push(c,n),await P(l,{logger:s,timeoutMs:a}),{commitSha:(await P(["rev-parse","HEAD"],{cwd:n,logger:s,timeoutMs:1e4})).stdout.trim()}}async function me(e){let{apiUrl:t,apiKey:r,projectId:o,localDir:n,branch:i="HEAD",logger:s,timeoutMs:a}=e,c=de(t,o),l=["-c",`http.extraHeader=Authorization: Bearer ${r}`];return await P([...l,"fetch",c,i],{cwd:n,logger:s,timeoutMs:a}),await P(["reset","--hard","FETCH_HEAD"],{cwd:n,logger:s,timeoutMs:a}),{commitSha:(await P(["rev-parse","HEAD"],{cwd:n,logger:s,timeoutMs:1e4})).stdout.trim()}}async function Oe(e){let{apiUrl:t,apiKey:r,projectId:o,localDir:n,logger:i,timeoutMs:s}=e,a=de(t,o),c=["-c",`http.extraHeader=Authorization: Bearer ${r}`];if(_e(Ae(n,".git","shallow")))await P([...c,"fetch","--unshallow",a],{cwd:n,logger:i,timeoutMs:s})}async function Qr(e){let{apiUrl:t,apiKey:r,projectId:o,localDir:n,message:i,branch:s="HEAD",authorEmail:a,authorName:c,logger:l,timeoutMs:u}=e,p=de(t,o),m={};if(a)m.GIT_AUTHOR_EMAIL=a,m.GIT_COMMITTER_EMAIL=a;if(c)m.GIT_AUTHOR_NAME=c,m.GIT_COMMITTER_NAME=c;await P(["add","-A"],{cwd:n,env:m,logger:l,timeoutMs:u});let f=!1;try{await P(["diff","--cached","--quiet"],{cwd:n,env:m,logger:l,timeoutMs:u})}catch{f=!0}if(!f)return{committed:!1};await P(["commit","-m",i,"--no-verify","--trailer",`Co-authored-by: ${c||process.env.SHOGO_AGENT_GIT_NAME||"Shogo Agent"} <${a||process.env.SHOGO_AGENT_GIT_EMAIL||"agent@shogo.ai"}>`],{cwd:n,env:m,logger:l,timeoutMs:u});let w=(await P(["rev-parse","HEAD"],{cwd:n,env:m,logger:l,timeoutMs:1e4})).stdout.trim(),g=["-c",`http.extraHeader=Authorization: Bearer ${r}`];return await P([...g,"push",p,s],{cwd:n,env:m,logger:l,timeoutMs:u}),{committed:!0,commitSha:w}}function ge(e){return _e(Ae(e,".git"))}var zr,ee=null;var he=vr(()=>{zr=Xr(Yr)});import{Command as Hn}from"commander";import wr from"picocolors";import h from"picocolors";import{existsSync as Ao}from"node:fs";import{readFileSync as kr,writeFileSync as Rr,existsSync as Pr,chmodSync as $r}from"node:fs";import{hostname as Tr}from"node:os";import{homedir as Sr}from"node:os";import{join as E}from"node:path";import{mkdirSync as ie}from"node:fs";var W=E(Sr(),".shogo"),U=E(W,"config.json"),Yn=E(W,"credentials.json"),Y=E(W,"device-id"),X=E(W,"worker.pid"),$e=E(W,"logs"),se=E($e,"worker.log"),ae=E($e,"worker.err.log"),z=E(W,"runtime"),_=E(z,process.platform==="win32"?"agent-runtime.exe":"agent-runtime"),Q=E(z,"version.json"),le=E(W,"projects");function B(e,t=le){return E(t,e)}function N(){ie(W,{recursive:!0,mode:448}),ie($e,{recursive:!0,mode:448})}function ze(){N(),ie(z,{recursive:!0,mode:448})}function Qe(e=le){N(),ie(e,{recursive:!0,mode:448})}var Te={cloudUrl:"https://studio.shogo.ai",port:8002,projectsDir:le};function j(){if(!Pr(U))return{};try{return JSON.parse(kr(U,"utf-8"))}catch(e){throw Error(`Corrupt config at ${U}: ${e.message}`)}}function Z(e){N(),Rr(U,JSON.stringify(e,null,2),{mode:384}),$r(U,384)}function Ze(e,t){return{...e,...Object.fromEntries(Object.entries(t).filter(([,r])=>r!==void 0))}}function A(e={}){let t=j(),r={apiKey:process.env.SHOGO_API_KEY,cloudUrl:process.env.SHOGO_CLOUD_URL,name:process.env.SHOGO_INSTANCE_NAME,workerDir:process.env.SHOGO_WORKER_DIR,port:process.env.PORT?parseInt(process.env.PORT,10):void 0,projectsDir:process.env.SHOGO_PROJECTS_DIR},o=Ze(Ze(t,r),e);if(!o.apiKey)throw Error("No API key. Run `shogo login` or pass --api-key / set SHOGO_API_KEY.");return{apiKey:o.apiKey,cloudUrl:o.cloudUrl??Te.cloudUrl,name:o.name??Tr(),workerDir:o.workerDir??process.cwd(),port:o.port??Te.port,projectsDir:o.projectsDir??Te.projectsDir}}import{spawn as Er}from"node:child_process";import{existsSync as Ir,openSync as et,readFileSync as xr,unlinkSync as Cr,writeFileSync as _r}from"node:fs";function ce(){if(!Ir(X))return null;let e=xr(X,"utf-8").trim(),t=parseInt(e,10);return Number.isFinite(t)?t:null}function ue(e){try{return process.kill(e,0),!0}catch{return!1}}function Ee(){try{Cr(X)}catch{}}function tt(e){N();let t=ce();if(t&&ue(t))throw Error(`Worker already running (pid=${t}). Run \`shogo worker stop\` first.`);if(t)Ee();let r=e.inheritStdio?["ignore","inherit","inherit"]:["ignore",et(se,"a"),et(ae,"a")],o=Er(e.runner,[e.entry],{cwd:e.cwd,env:e.env,detached:!!e.detach,stdio:r});if(!o.pid)throw Error("Failed to spawn worker process.");if(_r(X,String(o.pid),{mode:384}),e.detach)o.unref();return{pid:o.pid,child:o}}function rt(e="SIGTERM"){let t=ce();if(!t)return{killedPid:null};if(!ue(t))return Ee(),{killedPid:null};try{process.kill(t,e)}catch{}return Ee(),{killedPid:t}}import I from"picocolors";import{existsSync as Or}from"node:fs";import{request as Ar}from"node:http";function nt(e,t=process.env){let r=(i)=>i&&i.trim().length>0?i.trim():void 0,o=r(e);if(o)return{url:ot(o),source:"flag"};let n=[["HTTPS_PROXY",t.HTTPS_PROXY],["https_proxy",t.https_proxy],["HTTP_PROXY",t.HTTP_PROXY],["http_proxy",t.http_proxy]];for(let[i,s]of n){let a=r(s);if(a)return{url:ot(a),source:i}}return null}function ot(e){if(/^https?:\/\//i.test(e))return e;return`http://${e}`}function Ie(e,t){if(!t)return e;return{...e,HTTPS_PROXY:e.HTTPS_PROXY??t.url,https_proxy:e.https_proxy??t.url,HTTP_PROXY:e.HTTP_PROXY??t.url,http_proxy:e.http_proxy??t.url}}async function it(e,t="api.shogo.ai",r=5000){return new Promise((o)=>{let n=!1,i=(s)=>{if(n)return;n=!0,o(s)};try{let s=new URL(e.url),a=s.hostname,c=s.port?parseInt(s.port,10):s.protocol==="https:"?443:80,l=Ar({method:"CONNECT",host:a,port:c,path:`${t}:443`,timeout:r});l.on("connect",(u)=>{l.destroy();let p=u.statusCode??0;if(p===200)i({ok:!0,detail:`CONNECT ${t}:443 → 200 (via ${a}:${c})`});else if(p===407)i({ok:!1,detail:`407 Proxy Authentication Required — check credentials in ${e.source}`});else i({ok:!1,detail:`CONNECT → HTTP ${p}`})}),l.on("timeout",()=>{l.destroy(),i({ok:!1,detail:`timeout after ${r}ms`})}),l.on("error",(u)=>{let p=u.code?` (${u.code})`:"";i({ok:!1,detail:`${u.message}${p}`})}),l.end()}catch(s){i({ok:!1,detail:s?.message??"unknown error"})}})}function st(e){let t;try{t=new URL(e)}catch{return[]}let r=t.host,o=t.protocol.replace(":","")||"https",n=t.hostname.split("."),i=n.length>=2?n.slice(-2).join("."):t.hostname;return[{url:`${o}://${r}`,host:r,purpose:"control",criticality:"fatal"},{url:`${o}://api-direct.${i}`,host:`api-direct.${i}`,purpose:"tunnel-direct",criticality:"graceful"},{url:`${o}://artifacts.${i}`,host:`artifacts.${i}`,purpose:"artifacts",criticality:"graceful"}]}var Mr=5000;async function Dr(e,t){try{let r=new AbortController,o=setTimeout(()=>r.abort(),t),n=await fetch(`${e.replace(/\/$/,"")}/health`,{signal:r.signal}).catch(()=>null);if(clearTimeout(o),!n)return{ok:!1,detail:"no response (firewall? DNS?)"};return{ok:!0,detail:`HTTP ${n.status}`}}catch(r){return{ok:!1,detail:r?.message??"unknown error"}}}var at=(e)=>{let t=[{name:"Runtime (node >= 20)",criticality:"fatal",async run(){let[o]=process.versions.node.split(".").map(Number);return(o??0)>=20?{ok:!0,detail:`node v${process.versions.node}`}:{ok:!1,detail:`node v${process.versions.node} — need >=20`}}},{name:"Worker directory exists",criticality:"fatal",async run(){return Or(e.workerDir)?{ok:!0,detail:e.workerDir}:{ok:!1,detail:`${e.workerDir} does not exist`}}}];if(e.proxy)t.push({name:`Proxy reachable (${Wr(e.proxy.url)})`,criticality:"fatal",async run(){return it(e.proxy)}});let r=st(e.cloudUrl);for(let o of r)t.push({name:`Reach ${o.host}${o.purpose!=="control"?I.dim(` (${o.purpose})`):""}`,criticality:o.criticality,run:()=>Dr(o.url,Mr)});return t.push({name:"API key valid",criticality:"fatal",async run(){try{let o=await fetch(`${e.cloudUrl}/api/instances/heartbeat`,{method:"POST",headers:{"content-type":"application/json","x-api-key":e.apiKey},body:JSON.stringify({hostname:"preflight",name:"preflight",os:"preflight",arch:"preflight",metadata:{preflight:!0}})});if(o.status===401||o.status===403)return{ok:!1,detail:`HTTP ${o.status} — key rejected`};return o.ok?{ok:!0,detail:`HTTP ${o.status}`}:{ok:!1,detail:`HTTP ${o.status}`}}catch(o){return{ok:!1,detail:o?.message??"unknown error"}}}}),t};function Wr(e){try{return new URL(e).host}catch{return e}}async function lt(e){console.log(I.bold(`
1
+ import{createRequire as Ar}from"node:module";var ot=(e,t,r)=>()=>{if(e)try{t=e(e=0)}catch(o){r=[o]}if(r)throw r[0];return t};var Ce=Ar(import.meta.url);import*as P from"node:child_process";import{promisify as lt}from"node:util";function ct(e){return!!e&&typeof e==="object"&&!Array.isArray(e)}function ut(e){if(e.windowsHide!==void 0)return e;return{...e,windowsHide:!0}}function pt(e){let t=e.slice(),r=t[1];if(t.length<2||typeof r==="function")return t.splice(1,0,{windowsHide:!0}),t;if(r==null)return t[1]={windowsHide:!0},t;if(ct(r))return t[1]=ut(r),t;return t}function ie(e){let t=e.slice(),r=-1;for(let i=1;i<t.length;i++)if(typeof t[i]==="function"){r=i;break}let o=r===-1?t.length:r;for(let i=1;i<o;i++)if(ct(t[i]))return t[i]=ut(t[i]),t;let n=o>1&&(Array.isArray(t[1])||t[1]==null)?2:1;if(n>=o)t.splice(o,0,{windowsHide:!0});else if(t[n]==null)t[n]={windowsHide:!0};return t}function K(e,t){let r=function(...o){let n=t();return Reflect.apply(n,this,e(o))};return Object.defineProperty(r,at,{enumerable:!1,configurable:!0,writable:!1,value(...o){let n=t(),i=n[at];if(typeof i==="function")return Reflect.apply(i,n,e(o));let s=lt(n);return Reflect.apply(s,this,e(o))}}),r}var at,T,Y,ni,ii,dt,Oe,si;var D=ot(()=>{at=lt.custom;T=K(ie,()=>P.spawn),Y=K(ie,()=>P.spawnSync),ni=K(pt,()=>P.exec),ii=K(pt,()=>P.execSync),dt=K(ie,()=>P.execFile),Oe=K(ie,()=>P.execFileSync),si=K(ie,()=>P.fork)});import{existsSync as Ue}from"node:fs";import{join as Ne}from"node:path";import{promisify as no}from"node:util";async function Ct(e=!1){if(se!==null&&!e)return se;try{await io("git",["--version"],{timeout:3000}),se=!0}catch{se=!1}return se}function ye(e,t){return`${e.replace(/\/+$/,"")}/api/projects/${t}/git`}function E(e,t={}){let{cwd:r,env:o,timeoutMs:n=300000,logger:i}=t;return new Promise((s,a)=>{let c=T("git",e,{cwd:r,env:{...process.env,...o},stdio:["ignore","pipe","pipe"]}),l="",u="",p=[],m=[];c.stdout.setEncoding("utf-8"),c.stderr.setEncoding("utf-8"),c.stdout.on("data",(d)=>{if(p.push(d),i)i.log(d.trimEnd())}),c.stderr.on("data",(d)=>{if(m.push(d),i)i.warn(d.trimEnd())});let f=setTimeout(()=>{try{c.kill("SIGKILL")}catch{}a(Error(`git ${e[0]} timed out after ${n}ms`))},n);c.on("error",(d)=>{clearTimeout(f),a(d)}),c.on("close",(d)=>{clearTimeout(f),l=p.join(""),u=m.join("");let w=d??-1;if(w===0)s({stdout:l,stderr:u,exitCode:w});else{let g=Error(`git ${e[0]} exited with code ${w}: ${u.slice(0,500)}`);g.exitCode=w,g.stdout=l,g.stderr=u,a(g)}})})}async function At(e){let{apiUrl:t,apiKey:r,projectId:o,localDir:n,shallow:i=!0,logger:s,timeoutMs:a}=e;if(Ue(Ne(n,".git")))throw Error(`cloneProject: ${n}/.git already exists; use gitFetchAndReset instead`);let c=ye(t,o),l=["-c",`http.extraHeader=Authorization: Bearer ${r}`,"clone"];if(i)l.push("--depth=1");return l.push(c,n),await E(l,{logger:s,timeoutMs:a}),{commitSha:(await E(["rev-parse","HEAD"],{cwd:n,logger:s,timeoutMs:1e4})).stdout.trim()}}async function Se(e){let{apiUrl:t,apiKey:r,projectId:o,localDir:n,branch:i="HEAD",logger:s,timeoutMs:a}=e,c=ye(t,o),l=["-c",`http.extraHeader=Authorization: Bearer ${r}`];return await E([...l,"fetch",c,i],{cwd:n,logger:s,timeoutMs:a}),await E(["reset","--hard","FETCH_HEAD"],{cwd:n,logger:s,timeoutMs:a}),{commitSha:(await E(["rev-parse","HEAD"],{cwd:n,logger:s,timeoutMs:1e4})).stdout.trim()}}async function je(e){let{apiUrl:t,apiKey:r,projectId:o,localDir:n,logger:i,timeoutMs:s}=e,a=ye(t,o),c=["-c",`http.extraHeader=Authorization: Bearer ${r}`];if(Ue(Ne(n,".git","shallow")))await E([...c,"fetch","--unshallow",a],{cwd:n,logger:i,timeoutMs:s})}async function so(e){let{apiUrl:t,apiKey:r,projectId:o,localDir:n,message:i,branch:s="HEAD",authorEmail:a,authorName:c,logger:l,timeoutMs:u}=e,p=ye(t,o),m={};if(a)m.GIT_AUTHOR_EMAIL=a,m.GIT_COMMITTER_EMAIL=a;if(c)m.GIT_AUTHOR_NAME=c,m.GIT_COMMITTER_NAME=c;await E(["add","-A"],{cwd:n,env:m,logger:l,timeoutMs:u});let f=!1;try{await E(["diff","--cached","--quiet"],{cwd:n,env:m,logger:l,timeoutMs:u})}catch{f=!0}if(!f)return{committed:!1};await E(["commit","-m",i,"--no-verify","--trailer",`Co-authored-by: ${c||process.env.SHOGO_AGENT_GIT_NAME||"Shogo Agent"} <${a||process.env.SHOGO_AGENT_GIT_EMAIL||"agent@shogo.ai"}>`],{cwd:n,env:m,logger:l,timeoutMs:u});let w=(await E(["rev-parse","HEAD"],{cwd:n,env:m,logger:l,timeoutMs:1e4})).stdout.trim(),g=["-c",`http.extraHeader=Authorization: Bearer ${r}`];return await E([...g,"push",p,s],{cwd:n,env:m,logger:l,timeoutMs:u}),{committed:!0,commitSha:w}}function be(e){return Ue(Ne(e,".git"))}var io,se=null;var ve=ot(()=>{D();io=no(dt)});import{Command as Bn}from"commander";import Ir from"picocolors";import h from"picocolors";import{existsSync as No}from"node:fs";import{readFileSync as Or,writeFileSync as Mr,existsSync as Dr,chmodSync as Wr}from"node:fs";import{hostname as Ur}from"node:os";import{homedir as _r}from"node:os";import{join as I}from"node:path";import{mkdirSync as pe}from"node:fs";var j=I(_r(),".shogo"),F=I(j,"config.json"),zn=I(j,"credentials.json"),ee=I(j,"device-id"),te=I(j,"worker.pid"),Ae=I(j,"logs"),de=I(Ae,"worker.log"),me=I(Ae,"worker.err.log"),re=I(j,"runtime"),O=I(re,process.platform==="win32"?"agent-runtime.exe":"agent-runtime"),oe=I(re,"version.json"),ge=I(j,"projects");function V(e,t=ge){return I(t,e)}function H(){pe(j,{recursive:!0,mode:448}),pe(Ae,{recursive:!0,mode:448})}function nt(){H(),pe(re,{recursive:!0,mode:448})}function it(e=ge){H(),pe(e,{recursive:!0,mode:448})}var _e={cloudUrl:"https://studio.shogo.ai",port:8002,projectsDir:ge};function L(){if(!Dr(F))return{};try{return JSON.parse(Or(F,"utf-8"))}catch(e){throw Error(`Corrupt config at ${F}: ${e.message}`)}}function ne(e){H(),Mr(F,JSON.stringify(e,null,2),{mode:384}),Wr(F,384)}function st(e,t){return{...e,...Object.fromEntries(Object.entries(t).filter(([,r])=>r!==void 0))}}function M(e={}){let t=L(),r={apiKey:process.env.SHOGO_API_KEY,cloudUrl:process.env.SHOGO_CLOUD_URL,name:process.env.SHOGO_INSTANCE_NAME,workerDir:process.env.SHOGO_WORKER_DIR,port:process.env.PORT?parseInt(process.env.PORT,10):void 0,projectsDir:process.env.SHOGO_PROJECTS_DIR},o=st(st(t,r),e);if(!o.apiKey)throw Error("No API key. Run `shogo login` or pass --api-key / set SHOGO_API_KEY.");return{apiKey:o.apiKey,cloudUrl:o.cloudUrl??_e.cloudUrl,name:o.name??Ur(),workerDir:o.workerDir??process.cwd(),port:o.port??_e.port,projectsDir:o.projectsDir??_e.projectsDir}}D();import{existsSync as Nr,openSync as mt,readFileSync as jr,unlinkSync as Fr,writeFileSync as Hr}from"node:fs";function he(){if(!Nr(te))return null;let e=jr(te,"utf-8").trim(),t=parseInt(e,10);return Number.isFinite(t)?t:null}function fe(e){try{return process.kill(e,0),!0}catch{return!1}}function Me(){try{Fr(te)}catch{}}function gt(e){H();let t=he();if(t&&fe(t))throw Error(`Worker already running (pid=${t}). Run \`shogo worker stop\` first.`);if(t)Me();let r=e.inheritStdio?["ignore","inherit","inherit"]:["ignore",mt(de,"a"),mt(me,"a")],o=T(e.runner,[e.entry],{cwd:e.cwd,env:e.env,detached:!!e.detach,stdio:r});if(!o.pid)throw Error("Failed to spawn worker process.");if(Hr(te,String(o.pid),{mode:384}),e.detach)o.unref();return{pid:o.pid,child:o}}function ht(e="SIGTERM"){let t=he();if(!t)return{killedPid:null};if(!fe(t))return Me(),{killedPid:null};try{process.kill(t,e)}catch{}return Me(),{killedPid:t}}import C from"picocolors";import{existsSync as Gr}from"node:fs";import{request as Lr}from"node:http";function wt(e,t=process.env){let r=(i)=>i&&i.trim().length>0?i.trim():void 0,o=r(e);if(o)return{url:ft(o),source:"flag"};let n=[["HTTPS_PROXY",t.HTTPS_PROXY],["https_proxy",t.https_proxy],["HTTP_PROXY",t.HTTP_PROXY],["http_proxy",t.http_proxy]];for(let[i,s]of n){let a=r(s);if(a)return{url:ft(a),source:i}}return null}function ft(e){if(/^https?:\/\//i.test(e))return e;return`http://${e}`}function De(e,t){if(!t)return e;return{...e,HTTPS_PROXY:e.HTTPS_PROXY??t.url,https_proxy:e.https_proxy??t.url,HTTP_PROXY:e.HTTP_PROXY??t.url,http_proxy:e.http_proxy??t.url}}async function yt(e,t="api.shogo.ai",r=5000){return new Promise((o)=>{let n=!1,i=(s)=>{if(n)return;n=!0,o(s)};try{let s=new URL(e.url),a=s.hostname,c=s.port?parseInt(s.port,10):s.protocol==="https:"?443:80,l=Lr({method:"CONNECT",host:a,port:c,path:`${t}:443`,timeout:r});l.on("connect",(u)=>{l.destroy();let p=u.statusCode??0;if(p===200)i({ok:!0,detail:`CONNECT ${t}:443 → 200 (via ${a}:${c})`});else if(p===407)i({ok:!1,detail:`407 Proxy Authentication Required — check credentials in ${e.source}`});else i({ok:!1,detail:`CONNECT → HTTP ${p}`})}),l.on("timeout",()=>{l.destroy(),i({ok:!1,detail:`timeout after ${r}ms`})}),l.on("error",(u)=>{let p=u.code?` (${u.code})`:"";i({ok:!1,detail:`${u.message}${p}`})}),l.end()}catch(s){i({ok:!1,detail:s?.message??"unknown error"})}})}function St(e){let t;try{t=new URL(e)}catch{return[]}let r=t.host,o=t.protocol.replace(":","")||"https",n=t.hostname.split("."),i=n.length>=2?n.slice(-2).join("."):t.hostname;return[{url:`${o}://${r}`,host:r,purpose:"control",criticality:"fatal"},{url:`${o}://api-direct.${i}`,host:`api-direct.${i}`,purpose:"tunnel-direct",criticality:"graceful"},{url:`${o}://artifacts.${i}`,host:`artifacts.${i}`,purpose:"artifacts",criticality:"graceful"}]}var Br=5000;async function Kr(e,t){try{let r=new AbortController,o=setTimeout(()=>r.abort(),t),n=await fetch(`${e.replace(/\/$/,"")}/health`,{signal:r.signal}).catch(()=>null);if(clearTimeout(o),!n)return{ok:!1,detail:"no response (firewall? DNS?)"};return{ok:!0,detail:`HTTP ${n.status}`}}catch(r){return{ok:!1,detail:r?.message??"unknown error"}}}var bt=(e)=>{let t=[{name:"Runtime (node >= 20)",criticality:"fatal",async run(){let[o]=process.versions.node.split(".").map(Number);return(o??0)>=20?{ok:!0,detail:`node v${process.versions.node}`}:{ok:!1,detail:`node v${process.versions.node} — need >=20`}}},{name:"Worker directory exists",criticality:"fatal",async run(){return Gr(e.workerDir)?{ok:!0,detail:e.workerDir}:{ok:!1,detail:`${e.workerDir} does not exist`}}}];if(e.proxy)t.push({name:`Proxy reachable (${qr(e.proxy.url)})`,criticality:"fatal",async run(){return yt(e.proxy)}});let r=St(e.cloudUrl);for(let o of r)t.push({name:`Reach ${o.host}${o.purpose!=="control"?C.dim(` (${o.purpose})`):""}`,criticality:o.criticality,run:()=>Kr(o.url,Br)});return t.push({name:"API key valid",criticality:"fatal",async run(){try{let o=await fetch(`${e.cloudUrl}/api/instances/heartbeat`,{method:"POST",headers:{"content-type":"application/json","x-api-key":e.apiKey},body:JSON.stringify({hostname:"preflight",name:"preflight",os:"preflight",arch:"preflight",metadata:{preflight:!0}})});if(o.status===401||o.status===403)return{ok:!1,detail:`HTTP ${o.status} — key rejected`};return o.ok?{ok:!0,detail:`HTTP ${o.status}`}:{ok:!1,detail:`HTTP ${o.status}`}}catch(o){return{ok:!1,detail:o?.message??"unknown error"}}}}),t};function qr(e){try{return new URL(e).host}catch{return e}}async function vt(e){console.log(C.bold(`
2
2
  Shogo Worker — Preflight
3
- `));let t=!1,r=0;for(let o of e){process.stdout.write(` ${I.dim("...")} ${o.name}`);let n=await o.run();if(process.stdout.write("\r"),n.ok)console.log(` ${I.green("✓")} ${o.name}${n.detail?I.dim(` — ${n.detail}`):""}`);else if(o.criticality==="graceful")console.log(` ${I.yellow("◦")} ${o.name}${n.detail?I.dim(` — ${n.detail}`):""}${I.yellow(" (optional — worker will still start)")}`),r++;else console.log(` ${I.red("✗")} ${o.name}${n.detail?I.dim(` — ${n.detail}`):""}`),t=!0}if(!t&&r===0)console.log(I.green(`
3
+ `));let t=!1,r=0;for(let o of e){process.stdout.write(` ${C.dim("...")} ${o.name}`);let n=await o.run();if(process.stdout.write("\r"),n.ok)console.log(` ${C.green("✓")} ${o.name}${n.detail?C.dim(` — ${n.detail}`):""}`);else if(o.criticality==="graceful")console.log(` ${C.yellow("◦")} ${o.name}${n.detail?C.dim(` — ${n.detail}`):""}${C.yellow(" (optional — worker will still start)")}`),r++;else console.log(` ${C.red("✗")} ${o.name}${n.detail?C.dim(` — ${n.detail}`):""}`),t=!0}if(!t&&r===0)console.log(C.green(`
4
4
  All checks passed.
5
- `));else if(!t)console.log(I.yellow(`
5
+ `));else if(!t)console.log(C.yellow(`
6
6
  Starting with ${r} optional host(s) blocked. See docs/my-machines-networking.md.
7
- `));else console.log(I.red(`
7
+ `));else console.log(C.red(`
8
8
  Preflight failed — fix blocking issues before starting.
9
- `));return!t}import{accessSync as Ur,constants as Nr,existsSync as jr}from"node:fs";import{delimiter as Fr,join as Lr}from"node:path";function F(e={}){let t=e.env??process.env,r=Hr(e);for(let i of r)if(ut(i.path))return i;let o=e.systemBinName??ct(),n=Gr(o,t.PATH);if(n)return{path:n,source:"path"};return null}function Hr(e){let t=e.env??process.env,r=[];if(e.flag&&e.flag.trim())r.push({path:e.flag.trim(),source:"flag"});let o=t.SHOGO_AGENT_RUNTIME_BIN?.trim();if(o)r.push({path:o,source:"env"});return r.push({path:_,source:"home"}),r}function ct(){return process.platform==="win32"?"shogo-agent-runtime.exe":"shogo-agent-runtime"}function ut(e){if(!jr(e))return!1;try{if(process.platform==="win32")return!0;return Ur(e,Nr.X_OK),!0}catch{return!1}}function Gr(e,t){if(!t)return null;for(let r of t.split(Fr)){if(!r)continue;let o=Lr(r,e);if(ut(o))return o}return null}function H(e={}){let t=e.env??process.env,r=[];if(r.push("Error: agent-runtime binary not found."),r.push(""),r.push("Looked in (priority order):"),e.flag)r.push(` --runtime-bin ${e.flag}`);if(t.SHOGO_AGENT_RUNTIME_BIN)r.push(` $SHOGO_AGENT_RUNTIME_BIN = ${t.SHOGO_AGENT_RUNTIME_BIN}`);return r.push(` ${_} (default install location)`),r.push(` ${ct()} on $PATH`),r.push(""),r.push("Fix:"),r.push(" - Run `shogo runtime install` to download the latest binary, or"),r.push(" - Pass `--runtime-bin <path>` to point at an existing build."),r.join(`
10
- `)}import{spawn as so,spawnSync as ao}from"node:child_process";import{createHmac as Ct,randomBytes as lo}from"node:crypto";import{existsSync as Me,mkdirSync as bt,readdirSync as _t}from"node:fs";import{createConnection as co}from"node:net";import{tmpdir as uo}from"node:os";import{dirname as po,join as q}from"node:path";import{spawnSync as xe}from"node:child_process";import{existsSync as Br,mkdirSync as Kr,writeFileSync as pe}from"node:fs";import{join as K}from"node:path";var pt=1e5;function dt(e){let t=parseInt(e.RUNTIME_MEMORY_MB||"",10);if(!Number.isFinite(t)||t<=0)return null;let r=parseInt(e.RUNTIME_CPU_PERCENT||"0",10),o=Number.isFinite(r)&&r>0?r:0,n=parseInt(e.RUNTIME_TASKS_MAX||"",10),i=Number.isFinite(n)&&n>0?n:4096;return{memoryMB:t,cpuPercent:o,tasksMax:i}}function mt(e,t){let o=`--max-old-space-size=${Math.max(256,Math.floor(t*0.75))}`,n=e.NODE_OPTIONS?.trim();if(n){if(/--max-old-space-size=/.test(n))return;e.NODE_OPTIONS=`${n} ${o}`}else e.NODE_OPTIONS=o}function qr(){if(process.platform!=="linux")return!1;try{return xe("systemd-run",["--version"],{stdio:"ignore",timeout:3000}).status===0}catch{return!1}}function gt(e){let{command:t,args:r,limits:o,scopeName:n,logger:i}=e;if(process.platform!=="linux"||!qr())return{command:t,args:r,wrapped:!1};let s=["-p",`MemoryMax=${o.memoryMB}M`,"-p",`MemoryHigh=${Math.max(128,Math.floor(o.memoryMB*0.9))}M`,"-p",`TasksMax=${o.tasksMax}`];if(o.cpuPercent>0)s.push("-p",`CPUQuota=${o.cpuPercent}%`);let a=["--user","--scope","--quiet","--collect",`--unit=${n}`,...s,"--",t,...r];return i?.log(`[resource-limits] wrapping spawn in systemd scope ${n} (MemoryMax=${o.memoryMB}M)`),{command:"systemd-run",args:a,wrapped:!0}}function ht(e){let{pid:t,limits:r,logger:o}=e;if(process.platform!=="linux")return!1;let n="/sys/fs/cgroup";if(!Br(K(n,"cgroup.controllers")))return!1;let i=K(n,"shogo",`rt-${t}`);try{if(Kr(i,{recursive:!0}),pe(K(i,"memory.max"),String(r.memoryMB*1024*1024)),r.cpuPercent>0){let s=Math.floor(r.cpuPercent/100*pt);pe(K(i,"cpu.max"),`${s} ${pt}`)}try{pe(K(i,"pids.max"),String(r.tasksMax))}catch{}return pe(K(i,"cgroup.procs"),String(t)),o?.log(`[resource-limits] attached pid ${t} to cgroup ${i} (memory.max=${r.memoryMB}M)`),!0}catch(s){return o?.warn(`[resource-limits] direct cgroup attach failed for pid ${t}: ${s?.message??s}`),!1}}function ft(e){let{pid:t,limits:r,logger:o}=e;if(process.platform!=="win32")return!1;let n=process.env.SHOGO_WINJOB_HELPER||"@shogo/winjob";try{let i=Pe(n);if(typeof i.limitProcess!=="function")return!1;let s=i.limitProcess(t,{memoryBytes:r.memoryMB*1024*1024,killOnClose:!0});if(s)o?.log(`[resource-limits] attached pid ${t} to Job Object (${r.memoryMB}M)`);return s}catch{return!1}}function Jr(e,t=process.platform){try{if(t==="win32"){let i=`$ErrorActionPreference='SilentlyContinue';$root=${e};$all=Get-CimInstance Win32_Process;$seen=@{};$stack=New-Object System.Collections.Stack;$stack.Push($root);$sum=0;while($stack.Count -gt 0){$id=$stack.Pop();if($seen[$id]){continue};$seen[$id]=$true;foreach($p in $all){if($p.ProcessId -eq $id){$sum+=$p.WorkingSetSize};if($p.ParentProcessId -eq $id){$stack.Push($p.ProcessId)}}};[math]::Floor($sum/1024)`,s=xe("powershell.exe",["-NoProfile","-NonInteractive","-Command",i],{encoding:"utf-8",timeout:1e4,windowsHide:!0});if(s.status!==0||!s.stdout)return null;let a=parseInt(s.stdout.trim(),10);return Number.isFinite(a)?a:null}let r=xe("ps",["-o","rss=","-g",String(e)],{encoding:"utf-8",timeout:5000});if(r.status!==0||!r.stdout)return null;let o=0,n=!1;for(let i of r.stdout.split(`
11
- `)){let s=parseInt(i.trim(),10);if(Number.isFinite(s))o+=s,n=!0}return n?o:null}catch{return null}}class Ce{timer=null;consecutive=0;fired=!1;pid;ceilingMB;platform;intervalMs;strikes;logger;onBreach;constructor(e){this.pid=e.pid,this.ceilingMB=e.ceilingMB,this.platform=e.platform??process.platform,this.intervalMs=e.intervalMs??parseInt(process.env.RUNTIME_RSS_WATCHDOG_MS||"15000",10),this.strikes=e.strikes??parseInt(process.env.RUNTIME_RSS_WATCHDOG_STRIKES||"2",10),this.logger=e.logger,this.onBreach=e.onBreach}start(){if(this.timer)return;this.timer=setInterval(()=>this.sample(),Math.max(2000,this.intervalMs));try{this.timer.unref?.()}catch{}}stop(){if(this.timer)clearInterval(this.timer),this.timer=null}sample(){if(this.fired)return;let e=Jr(this.pid,this.platform);if(e==null){this.consecutive=0;return}let t=Math.floor(e/1024);if(t<=this.ceilingMB){this.consecutive=0;return}if(this.consecutive+=1,this.logger?.warn(`[resource-limits] pid ${this.pid} RSS ${t}MB over ceiling ${this.ceilingMB}MB (strike ${this.consecutive}/${this.strikes})`),this.consecutive>=this.strikes)this.fired=!0,this.stop(),this.onBreach(t)}}import{CloudFileTransport as De}from"@shogo-ai/sdk/cloud-file-transport";import{killViteWatchFromPidfile as St}from"@shogo-ai/sdk/vite-watch";import{watch as vt,statSync as Zr}from"node:fs";import{relative as Ei,sep as eo,posix as to}from"node:path";var ro=1500,oo=new Set(["node_modules",".git","dist","build",".vite",".cache"]),no=[".shogo/"];function io(e){if(!e)return!0;return e.split("/").some((t)=>oo.has(t))}class te{rootDir;transport;debounceMs;logger;onFlush;mode;git;commitAndPush;watcher=null;pending=new Set;timer=null;flushing=!1;stopped=!1;constructor(e){if(this.rootDir=e.rootDir,this.transport=e.transport,this.debounceMs=e.debounceMs??ro,this.logger=e.logger??console,this.onFlush=e.onFlush,this.mode=e.mode??"files",this.mode==="git"&&!e.git)throw Error('CloudSyncWatcher: mode: "git" requires the `git` option block');this.git=e.git,this.commitAndPush=e.commitAndPush??(async(t)=>{let r=await Promise.resolve().then(() => (he(),{}));return Qr(t)})}start(){if(this.watcher)return;if(this.stopped)throw Error("CloudSyncWatcher: already stopped, build a fresh instance");try{this.watcher=vt(this.rootDir,{recursive:!0},(e,t)=>{if(!t)return;this.handleEvent(String(t))})}catch(e){this.logger.warn(`[CloudSyncWatcher] recursive watch failed (${e?.message??e}); falling back to root-only watch.`),this.watcher=vt(this.rootDir,(t,r)=>{if(!r)return;this.handleEvent(String(r))})}}async stop(){if(this.stopped)return;if(this.stopped=!0,this.timer)clearTimeout(this.timer),this.timer=null;if(this.watcher){try{this.watcher.close()}catch{}this.watcher=null}if(this.pending.size>0)await this.flush()}handleEvent(e){if(this.stopped)return;let t=e.split(eo).join(to.sep);if(io(t))return;try{let r=`${this.rootDir}/${t}`;if(Zr(r).isDirectory())return}catch{return}this.pending.add(t),this.scheduleFlush()}scheduleFlush(){if(this.timer)return;this.timer=setTimeout(()=>{this.timer=null,this.flush()},this.debounceMs)}async flush(){if(this.flushing){this.scheduleFlush();return}if(this.pending.size===0)return;this.flushing=!0;let e=Array.from(this.pending);this.pending.clear();try{if(this.mode==="git")await this.flushGit(e);else await this.flushFiles(e)}catch(t){this.logger.error(`[CloudSyncWatcher] flush failed: ${t?.message??t}`);for(let r of e)this.pending.add(r);this.scheduleFlush()}finally{this.flushing=!1}}async flushFiles(e){if(e.length===0)return;let t=await this.transport.uploadFiles(e);if(this.onFlush?.({uploaded:e,errors:t.errors.length}),t.errors.length>0)for(let r of t.errors)this.logger.warn(`[CloudSyncWatcher] upload ${r.path}: ${r.message}`)}async flushGit(e){if(!this.git)throw Error("CloudSyncWatcher: missing git options");let t=[],r=[];for(let i of e)if(no.some((s)=>i===s.slice(0,-1)||i.startsWith(s)))t.push(i);else r.push(i);if(t.length>0){let i=await this.transport.uploadFiles(t);if(i.errors.length>0)for(let s of i.errors)this.logger.warn(`[CloudSyncWatcher] upload ${s.path}: ${s.message}`)}let o=!1,n;if(r.length>0){let i=`auto: ${new Date().toISOString()}`;try{let s=await this.commitAndPush({apiUrl:this.git.apiUrl,apiKey:this.git.apiKey,projectId:this.git.projectId,localDir:this.rootDir,message:i,branch:this.git.branch,authorEmail:this.git.authorEmail,authorName:this.git.authorName,logger:this.logger});o=s.committed,n=s.commitSha}catch(s){throw s}}this.onFlush?.({uploaded:e,errors:0,committed:o,commitSha:n})}}he();var We=37100,Ue=37900,kt=1,mo=2,fe=16,go=900000,ho=30000,fo=1000,wo=60000,Rt=8,Pt=300000,$t=3600000,yo=8,vo=120000,bo=900000,So=60000,ko=500,Ro=30000,Tt=25000,Po=5000,$o=500,To=process.env.SHOGO_PERF_LOG==="1";function we(e,t,r,o){if(!To)return;console.log(`[shogo-perf] ${JSON.stringify({perf:"open",source:"worker",phase:e,projectId:t,openAttemptId:r,atMs:Date.now(),...o})}`)}var Eo=(e)=>({command:e,args:[]}),Ne=null;function At(){if(!Ne)Ne=lo(32).toString("hex");return Ne}function Et(e){return Ct("sha256",At()).update(`runtime:${e}`).digest("hex")}function Io(e){return Ct("sha256",At()).update(`webhook:${e}`).digest("hex")}function It(e){let t=e.indexOf("?");if(t===-1)return{pathname:e,search:""};return{pathname:e.slice(0,t),search:e.slice(t)}}function xt(e){try{return _t(e).length===0}catch{return!0}}function je(e,t,r){let o=[];switch(o.push(`Cannot spawn agent-runtime for project ${e}: no workspace directory available.`),o.push(""),t){case"no-auto-pull-config":o.push(" Reason: WorkerRuntimeManager was constructed without an `autoPull` config and no caller-provided `projectDir` was found on disk."),o.push(" This usually means a programmatic embedder forgot to wire up enrichSpawnConfig "+"or autoPull. CLI users should not see this — please file a bug.");break;case"no-projects-dir":o.push(" Reason: auto-pull is configured but `projectsDir` is empty. The worker needs a persistent root directory under which it can store cloned project workspaces.");break;case"auto-pull-disabled":o.push(` Reason: auto-pull was disabled (--no-auto-pull) and the expected pre-pulled workspace at ${r} is missing or empty.`);break}return o.push(""),o.push(" How to fix (pick one):"),o.push(" 1. Re-enable auto-pull (default). Drop the --no-auto-pull flag and restart"),o.push(" the worker. The first inbound request for this project will clone its"),o.push(" workspace from Shogo Cloud into <projectsDir>/<projectId>/."),o.push(""),o.push(" 2. Pre-pull manually with `shogo project pull <projectId>` before starting"),o.push(" the worker. Use this when you want full control over when the clone runs"),o.push(" (slow links, scheduled maintenance windows, etc.)."),o.push(""),o.push(" 3. Point the worker at an existing workspace by setting either:"),o.push(" --projects-dir <path> (per-invocation flag)"),o.push(" SHOGO_PROJECTS_DIR=<path> (env var, persists across restarts)"),o.push(" shogo config set projectsDir <path>"),o.push(" Whichever path you pick must contain a subdirectory named after the "),o.push(` project id (e.g. <path>/${e}/) populated with the project's source.`),o.push(""),o.push(" Docs: https://shogo.ai/docs/self-hosted-worker#workspace-seeding"),o.join(`
12
- `)}class Fe{opts;log;runtimes=new Map;usedPorts=new Set;spawnCommand;resolved=null;stopped=!1;resolvedLimits=void 0;watchers=new Map;pulledProjects=new Set;syncModes=new Map;constructor(e={}){this.opts=e,this.log=e.logger??console,this.spawnCommand=e.spawnCommand??Eo,this.sweepOrphanedViteWatches()}resolveBinary(){if(!this.resolved)this.resolved=this.opts.resolveBin?this.opts.resolveBin():F({flag:this.opts.runtimeBin,env:this.opts.env});return this.resolved}async resolveLocalUrl(e,t){let{pathname:r,search:o}=It(e);if(!(r.startsWith("/agent/")||r==="/agent"))return null;if(!t){let s=this.getActiveProjects();if(s.length!==1)return null;t=s[0]}let n=await this.spawnConfigFor(t);if(!n)return this.log.warn(`[WorkerRuntimeManager] No spawn config for ${t} — set defaultSpawnConfig or enrichSpawnConfig`),null;let i=await this.ensureRunning(t,n);if(!i.agentPort)return null;return this.touch(t),`http://127.0.0.1:${i.agentPort}${r}${o}`}deriveRuntimeToken(e){return Et(e)}describeRejection(e,t){let{pathname:r}=It(e);if(!(r.startsWith("/agent/")||r==="/agent"))return{code:"CLI_WORKER_HAS_NO_DATA_API",message:`cli-worker only serves /agent/* paths; tried: ${r}`};return{code:"CLI_WORKER_NO_PROJECT_FOR_PATH",message:`cli-worker received an /agent path without a single active project; projectId=${t??"none"}, path=${r}`}}async spawnConfigFor(e){let t=this.opts.defaultSpawnConfig;if(!t)return null;if(this.opts.enrichSpawnConfig)try{return await this.opts.enrichSpawnConfig(e,t)}catch(r){this.log.warn(`[WorkerRuntimeManager] enrichSpawnConfig failed for ${e}: ${r?.message??r}`)}return t}async ensureRunning(e,t){if(this.stopped)throw Error("WorkerRuntimeManager is stopped");let r=this.runtimes.get(e);if(r?.status==="failed"){let i=this.breakerCooldownMs(r.breakerTrips),s=Date.now()-r.lastFailureAt;if(s>=i)this.log.log(`[WorkerRuntimeManager] auto-heal: ${e} cooldown elapsed (${Math.round(s/1000)}s since trip #${r.breakerTrips}) — `+"clearing circuit breaker and attempting a fresh spawn"),this.healBreaker(r);else{let a=i-s;throw Error(`[WorkerRuntimeManager] cannot ensureRunning(${e}): ${r.lastError??"runtime is in failed state"} Will auto-retry in ~${Math.ceil(a/1000)}s; call resetFailure(${e}) to retry immediately, or stop(${e}) to give up.`)}}t=await this.maybeAutoPull(e,t);let o=this.runtimes.get(e);if(o?.status==="running")return this.touch(e),this.snapshot(o);if(o?.startPromise){let i=await o.startPromise;return this.snapshot(i)}let n=o??this.makeSlot(e,t);if(!o)this.runtimes.set(e,n);n.spawnConfig=t,n.startPromise=this.doStart(n);try{let i=await n.startPromise;return this.enforceMaxRuntimes(e),this.snapshot(i)}finally{n.startPromise=null}}enforceMaxRuntimes(e){let t=this.opts.maxRuntimes;if(t==null||!Number.isFinite(t)||t<=0)return;let r=Date.now(),o=Array.from(this.runtimes.values()).filter((s)=>s.status==="running");if(o.length<=t)return;let n=o.filter((s)=>s.projectId!==e&&r-s.lastUsedAt>=ho).sort((s,a)=>s.lastUsedAt-a.lastUsedAt),i=o.length-t;for(let s of n){if(i<=0)break;let a=r-s.lastUsedAt;this.log.log(`[WorkerRuntimeManager] maxRuntimes=${t} exceeded (${o.length} running) — `+`LRU-evicting ${s.projectId} (idle ${Math.round(a/1000)}s)`),this.stop(s.projectId,"SIGTERM","lru-evict").catch((c)=>{this.log.warn(`[WorkerRuntimeManager] maxRuntimes eviction of ${s.projectId} failed: ${c?.message??c}`)}),i--}if(i>0)this.log.log(`[WorkerRuntimeManager] maxRuntimes=${t} still exceeded by ${i} after eviction pass — `+"remaining over-cap slots are mid-stream; will retry on next spawn / idle reap")}async ensurePulled(e,t){return this.maybeAutoPull(e,t)}async maybeAutoPull(e,t){let r=this.opts.autoPull;if(t.projectDir&&Me(t.projectDir))return t;if(!r)throw Error(je(e,"no-auto-pull-config",null));if(!r.projectsDir)throw Error(je(e,"no-projects-dir",null));if(!r.enabled){let s=q(r.projectsDir,e);if(Me(s)&&!xt(s))return{...t,projectDir:s};throw Error(je(e,"auto-pull-disabled",s))}if(this.pulledProjects.has(e))return{...t,projectDir:q(r.projectsDir,e)};let o=q(r.projectsDir,e),n=r.logger??this.log,i={cloneProject:r.gitOps?.cloneProject??yt,gitIsAvailable:r.gitOps?.gitIsAvailable??wt,isGitRepo:r.gitOps?.isGitRepo??ge};this.pulledProjects.add(e);try{bt(o,{recursive:!0});let s=xt(o),a=i.isGitRepo(o),u=(r.useGit!==!1?await i.gitIsAvailable():!1)&&(s||a)?"git":"files";if(this.syncModes.set(e,u),u==="git"){if(s){n.log(`[WorkerRuntimeManager] auto-pull: git clone project ${e} into ${o}`);try{let p=await i.cloneProject({apiUrl:t.cloudUrl,apiKey:t.apiKey,projectId:e,localDir:o,shallow:!0,logger:n});n.log(`[WorkerRuntimeManager] auto-pull: ${e} cloned at ${p.commitSha.slice(0,8)}`)}catch(p){n.warn(`[WorkerRuntimeManager] auto-pull: git clone failed for ${e} (${p?.message??p}); falling back to CloudFileTransport.downloadAll`),this.syncModes.set(e,"files"),await this.fileTransportClone(e,o,t,n)}}else if(a)n.log(`[WorkerRuntimeManager] auto-pull: ${e} already has .git/; skipping clone`);if(this.syncModes.get(e)==="git")await this.topUpShogoState(e,o,t,n)}else if(s)await this.fileTransportClone(e,o,t,n);else n.log(`[WorkerRuntimeManager] auto-pull: ${e} workspace already populated; skipping clone`);if(r.watch!==!1&&!this.watchers.has(e))try{let p=new De({apiUrl:t.cloudUrl,apiKey:t.apiKey,projectId:e,localDir:o}),m=this.syncModes.get(e)??"files",f=new te({rootDir:o,transport:p,logger:n,mode:m,git:m==="git"?{apiUrl:t.cloudUrl,apiKey:t.apiKey,projectId:e}:void 0});f.start(),this.watchers.set(e,f)}catch(p){n.warn(`[WorkerRuntimeManager] auto-pull: watcher start failed for ${e}: ${p?.message??p}`)}}catch(s){n.warn(`[WorkerRuntimeManager] auto-pull: failed for ${e} — runtime will fall back to template defaults. `+`(${s?.message??s})`)}return{...t,projectDir:o}}async fileTransportClone(e,t,r,o){o.log(`[WorkerRuntimeManager] auto-pull: file-transport clone of ${e} into ${t}`);let i=await new De({apiUrl:r.cloudUrl,apiKey:r.apiKey,projectId:e,localDir:t}).downloadAll();o.log(`[WorkerRuntimeManager] auto-pull: ${e} downloaded ${i.downloaded} files (${i.errors.length} errors)`)}async topUpShogoState(e,t,r,o){try{let n=new De({apiUrl:r.cloudUrl,apiKey:r.apiKey,projectId:e,localDir:t}),s=(await n.listManifest()).filter((c)=>c.path===".shogo"||c.path.startsWith(".shogo/"));if(s.length===0)return;let a=await n.downloadFiles(s);o.log(`[WorkerRuntimeManager] auto-pull: ${e} .shogo/ top-up downloaded ${a.downloaded} files (${a.errors.length} errors)`)}catch(n){o.warn(`[WorkerRuntimeManager] auto-pull: .shogo top-up failed for ${e}: ${n?.message??n}`)}}shouldDeferToCloudSync(e){return!!this.opts.autoPull?.enabled&&this.pulledProjects.has(e)}status(e){let t=this.runtimes.get(e);return t?this.snapshot(t):null}getActiveProjects(){return Array.from(this.runtimes.keys()).filter((e)=>{let t=this.runtimes.get(e);return t&&(t.status==="running"||t.status==="starting"||t.status==="restarting")})}touch(e){let t=this.runtimes.get(e);if(!t)return;t.lastUsedAt=Date.now(),this.armIdleTimer(t)}async stop(e,t="SIGTERM",r="stop"){let o=this.runtimes.get(e);if(!o)return;if(o.status="stopping",o.restartTimer)clearTimeout(o.restartTimer),o.restartTimer=null;if(o.idleTimer)clearTimeout(o.idleTimer),o.idleTimer=null;if(o.graceTimer)clearTimeout(o.graceTimer),o.graceTimer=null;if(o.limitWatchdog)o.limitWatchdog.stop(),o.limitWatchdog=null;if(o.proc){this.killProcessGroup(o,t),this.killViteWatch(o);try{o.proc.kill(t)}catch{}await this.waitForExit(o.proc,5000),this.killProcessGroup(o,"SIGKILL"),this.killViteWatch(o)}o.pid=null,this.releasePort(o.agentPort),this.runtimes.delete(e),this.notifyGone(e,{reason:r})}notifyGone(e,t){let r=this.opts.onRuntimeGone;if(!r)return;try{r(e,t)}catch(o){this.log.warn(`[WorkerRuntimeManager] onRuntimeGone(${e}) threw: ${o?.message??o}`)}}resetFailure(e){let t=this.runtimes.get(e);if(!t||t.status!=="failed")return!1;if(t.restartTimer)clearTimeout(t.restartTimer),t.restartTimer=null;if(t.idleTimer)clearTimeout(t.idleTimer),t.idleTimer=null;if(t.graceTimer)clearTimeout(t.graceTimer),t.graceTimer=null;if(t.limitWatchdog)t.limitWatchdog.stop(),t.limitWatchdog=null;return this.runtimes.delete(e),this.log.log(`[WorkerRuntimeManager] resetFailure: ${e} cleared, next ensureRunning will respawn`),!0}breakerCooldownMs(e){let t=Math.max(0,e-1),r=vo*Math.pow(2,t);return Math.min(r,bo)}healBreaker(e){e.status="stopped",e.consecutiveFailures=0}async stopAll(e="SIGTERM"){this.stopped=!0;let t=Array.from(this.watchers.keys());await Promise.all(t.map(async(o)=>{let n=this.watchers.get(o);if(this.watchers.delete(o),n)try{await n.stop()}catch(i){this.log.warn(`[WorkerRuntimeManager] watcher stop ${o}: ${i?.message??i}`)}}));let r=Array.from(this.runtimes.keys());await Promise.all(r.map((o)=>this.stop(o,e).catch((n)=>{this.log.error(`[WorkerRuntimeManager] Failed to stop ${o}: ${n?.message??n}`)})))}makeSlot(e,t){return{projectId:e,agentPort:0,apiServerPort:0,status:"starting",proc:null,pid:null,startedAt:0,lastStdoutAt:0,lastUsedAt:Date.now(),restarts:0,consecutiveFailures:0,lastFailureAt:0,breakerTrips:0,restartTimestamps:[],graceTimer:null,restartTimer:null,idleTimer:null,limitWatchdog:null,spawnConfig:t,startPromise:null}}async doStart(e){let t=this.resolveBinary();if(!t)throw e.status="error",e.lastError="agent-runtime binary not found (run `shogo runtime install`)",Error(e.lastError);if(!e.agentPort)e.agentPort=await this.allocatePort(),e.apiServerPort=e.agentPort+kt;let r=this.buildEnv(e,t.path),o=this.resolveCwd(e),n=this.spawnCommand(t.path),i=this.resolveLimits(),{command:s,args:a}=n,c=!1;if(i){let d=gt({command:n.command,args:n.args,limits:i,scopeName:`shogo-rt-${e.projectId.slice(0,8)}-${Date.now().toString(36)}`,logger:this.log});s=d.command,a=d.args,c=d.wrapped}this.log.log(`[WorkerRuntimeManager] Spawning agent-runtime for ${e.projectId} via ${s} ${a.join(" ")} (port=${e.agentPort}, source=${t.source}${i?`, memCeil=${i.memoryMB}M`:""})`);let l=process.platform!=="win32",u=so(s,a,{cwd:o,env:r,detached:l,stdio:["ignore","pipe","pipe"]});if(l)try{u.unref()}catch{}e.proc=u,e.pid=u.pid??null,e.status="starting",e.startedAt=Date.now();let p=e.spawnConfig.extraEnv?.SHOGO_OPEN_ID;if(we("agent-spawned",e.projectId,p,{pid:e.pid,command:s,args:a,source:t.source}),i)try{this.applyPostSpawnLimits(e,i,c)}catch(d){this.log.warn(`[WorkerRuntimeManager] failed to apply resource limits for ${e.projectId}: ${d?.message??d}`)}e.lastStdoutAt=e.startedAt,u.on("error",(d)=>{e.lastError=d?.message??String(d),this.log.error(`[WorkerRuntimeManager] spawn error for ${e.projectId}: ${e.lastError}`)}),u.on("exit",(d,w)=>{this.handleExit(e,d,w)});let m=`[runtime:${e.projectId.slice(0,8)}]`,f=!0;u.stdout?.on("data",(d)=>{if(e.lastStdoutAt=Date.now(),f)f=!1,we("agent-first-output",e.projectId,p,{elapsedMs:Date.now()-(e.startedAt??Date.now())});for(let w of d.toString().trimEnd().split(`
13
- `))if(w)this.log.log(`${m} ${w}`)}),u.stderr?.on("data",(d)=>{if(e.lastStdoutAt=Date.now(),f)f=!1,we("agent-first-output",e.projectId,p,{elapsedMs:Date.now()-(e.startedAt??Date.now()),stream:"stderr"});for(let w of d.toString().trimEnd().split(`
14
- `))if(w)this.log.error(`${m} ${w}`)});try{return await this.waitForHealth(e,Ro),we("agent-health-ready",e.projectId,p,{elapsedMs:Date.now()-(e.startedAt??Date.now()),agentPort:e.agentPort}),e.status="running",e.lastUsedAt=Date.now(),this.armIdleTimer(e),this.armGraceTimer(e),e}catch(d){e.status="error",e.lastError=d?.message??String(d),this.killProcessGroup(e,"SIGTERM"),this.killViteWatch(e);try{u.kill("SIGTERM")}catch{}throw this.releasePort(e.agentPort),e.agentPort=0,e.apiServerPort=0,d}}resolveLimits(){if(this.resolvedLimits===void 0)this.resolvedLimits=dt(this.opts.env??process.env);return this.resolvedLimits}applyPostSpawnLimits(e,t,r){let o=e.pid;if(!o)return;if(process.platform==="linux"){if(r)return;if(ht({pid:o,limits:t,logger:this.log}))return}else if(process.platform==="win32"){if(ft({pid:o,limits:t,logger:this.log}))return}e.limitWatchdog=new Ce({pid:o,ceilingMB:t.memoryMB,logger:this.log,onBreach:(n)=>this.handleLimitBreach(e,n,t.memoryMB)}),e.limitWatchdog.start()}handleLimitBreach(e,t,r){if(this.log.warn(`[WorkerRuntimeManager] runtime ${e.projectId} exceeded memory ceiling (${t}MB > ${r}MB) — restarting`),e.lastError=`memory ceiling exceeded (${t}MB > ${r}MB)`,e.limitWatchdog)e.limitWatchdog.stop(),e.limitWatchdog=null;this.killProcessGroup(e,"SIGTERM"),this.killViteWatch(e);try{e.proc?.kill("SIGTERM")}catch{}let o=setTimeout(()=>{this.killProcessGroup(e,"SIGKILL"),this.killViteWatch(e)},5000);try{o.unref?.()}catch{}}killProcessGroup(e,t){if(!e.pid)return;if(process.platform==="win32"){try{ao("taskkill",["/F","/T","/PID",String(e.pid)],{stdio:"ignore",windowsHide:!0})}catch{}return}try{process.kill(-e.pid,t)}catch{}}armGraceTimer(e){if(e.graceTimer)clearTimeout(e.graceTimer),e.graceTimer=null;e.graceTimer=setTimeout(()=>{if(e.graceTimer=null,e.consecutiveFailures>0)e.consecutiveFailures=0;e.breakerTrips=0,e.restartTimestamps=[]},So);try{e.graceTimer.unref?.()}catch{}}buildEnv(e,t){let r=e.spawnConfig,o={...this.opts.env??process.env,PROJECT_ID:e.projectId,PORT:String(e.agentPort),API_SERVER_PORT:String(e.apiServerPort),SKILL_SERVER_PORT:String(e.apiServerPort),WORKSPACE_API_PORT_BASE:String(e.agentPort+mo),NODE_ENV:"production",STARTUP_TIME:String(Date.now()),SHOGO_CLOUD_URL:r.cloudUrl,SHOGO_API_URL:r.cloudUrl,SHOGO_API_KEY:r.apiKey,RUNTIME_AUTH_SECRET:Et(e.projectId),WEBHOOK_TOKEN:Io(e.projectId)};if(r.projectDir)o.PROJECT_DIR=r.projectDir,o.WORKSPACE_DIR=r.projectDir;if(this.shouldDeferToCloudSync(e.projectId))o.SHOGO_CLOUD_SYNC="1";if(r.aiProxyUrl)o.AI_PROXY_URL=r.aiProxyUrl;if(r.aiProxyToken)o.AI_PROXY_TOKEN=r.aiProxyToken;if(r.techStackId)o.TECH_STACK_ID=r.techStackId;if(r.name)o.AGENT_NAME=r.name;if(r.workspaceId)o.WORKSPACE_ID=r.workspaceId;if(!o.TREE_SITTER_WASM_DIR)o.TREE_SITTER_WASM_DIR=q(po(t),"tree-sitter-wasm");if(r.extraEnv)Object.assign(o,r.extraEnv);let n=this.resolveLimits();if(n)mt(o,n.memoryMB);return o}killViteWatch(e){let t=e.spawnConfig.projectDir;if(!t)return;try{St(t,{logger:this.log})}catch(r){this.log.warn(`[WorkerRuntimeManager] Vite watch cleanup for ${e.projectId} failed: ${r?.message??r}`)}}sweepOrphanedViteWatches(){let e=this.opts.autoPull?.projectsDir;if(!e)return;let t;try{t=_t(e,{withFileTypes:!0})}catch{return}for(let r of t){if(!r.isDirectory()||this.runtimes.has(r.name))continue;let o=q(e,r.name);try{let n=St(o,{logger:this.log});if(n)this.log.log(`[WorkerRuntimeManager] Reaped orphaned Vite watch for ${r.name} (pid=${n.pid})`)}catch(n){this.log.warn(`[WorkerRuntimeManager] Vite watch startup sweep failed for ${r.name}: ${n?.message??n}`)}}}resolveCwd(e){let t=e.spawnConfig;if(t.projectDir&&Me(t.projectDir))return t.projectDir;let r=this.opts.runtimeWorkDir??q(uo(),"shogo-runtime",e.projectId);return bt(r,{recursive:!0}),r}handleExit(e,t,r){let o=r===null&&t===0;if(this.log.log(`[WorkerRuntimeManager] runtime ${e.projectId} exited (code=${t}, signal=${r})`),e.proc=null,e.graceTimer)clearTimeout(e.graceTimer),e.graceTimer=null;if(e.limitWatchdog)e.limitWatchdog.stop(),e.limitWatchdog=null;if(this.killViteWatch(e),e.status==="stopping"||this.stopped){e.status="stopped",e.pid=null,this.releasePort(e.agentPort),e.agentPort=0,e.apiServerPort=0;return}if(o){e.status="stopped",e.pid=null,this.releasePort(e.agentPort),e.agentPort=0,e.apiServerPort=0,this.runtimes.delete(e.projectId),this.notifyGone(e.projectId,{reason:"exited",code:t,signal:r});return}this.killProcessGroup(e,"SIGKILL"),this.killViteWatch(e),e.pid=null;let n=Date.now(),i=n-e.lastFailureAt<=Pt;e.consecutiveFailures=i?e.consecutiveFailures+1:1,e.lastFailureAt=n,e.restarts+=1,e.lastError=`exited code=${t} signal=${r}`,e.restartTimestamps.push(n),e.restartTimestamps=e.restartTimestamps.filter((l)=>n-l<=$t);let s=e.consecutiveFailures>=Rt,a=e.restartTimestamps.length>=yo;if(s||a){e.status="failed",e.breakerTrips+=1;let l=this.breakerCooldownMs(e.breakerTrips),u=s?`${e.consecutiveFailures} consecutive non-clean exits within ${Math.round(Pt/1000)}s`:`${e.restartTimestamps.length} non-clean exits within the last ${Math.round($t/60000)}m (slow-bleed loop — gaps between crashes kept clearing the tight-loop window)`;if(e.lastError=`Circuit breaker tripped (trip #${e.breakerTrips}): ${u} (last: code=${t} signal=${r}). Most recent on macOS is jetsam OOM (signal=SIGKILL with code=null); the previous incarnation's vite/tsserver/preview-manager children were reaped to prevent further RSS growth. Will auto-retry in ~${Math.round(l/1000)}s; call resetFailure(${e.projectId}) to retry immediately, or stop(${e.projectId}) to give up.`,this.releasePort(e.agentPort),e.agentPort=0,e.apiServerPort=0,e.restartTimer)clearTimeout(e.restartTimer),e.restartTimer=null;if(e.idleTimer)clearTimeout(e.idleTimer),e.idleTimer=null;this.log.error(`[WorkerRuntimeManager] ${e.lastError}`),this.notifyGone(e.projectId,{reason:"failed",code:t,signal:r});return}let c=this.restartBackoffMs(e.restarts);if(e.status="restarting",this.log.warn(`[WorkerRuntimeManager] restarting ${e.projectId} in ${Math.round(c/1000)}s (restart #${e.restarts}, consecutive failures ${e.consecutiveFailures}/${Rt})`),e.restartTimer)clearTimeout(e.restartTimer);e.restartTimer=setTimeout(()=>{if(e.restartTimer=null,e.status==="failed"||this.stopped)return;e.startPromise=this.doStart(e).then((l)=>(e.startPromise=null,l)).catch((l)=>(e.startPromise=null,this.log.error(`[WorkerRuntimeManager] restart of ${e.projectId} failed: ${l?.message??l}`),e))},c);try{e.restartTimer.unref?.()}catch{}}restartBackoffMs(e){let t=Math.min(fo*Math.pow(2,Math.max(0,e-1)),wo),r=t*0.2*Math.random();return t+r}armIdleTimer(e){if(e.idleTimer)clearTimeout(e.idleTimer),e.idleTimer=null;let t=this.opts.idleMs??go;if(!Number.isFinite(t)||t<=0)return;e.idleTimer=setTimeout(()=>{let r=Date.now()-e.lastUsedAt;if(r<t){this.armIdleTimer(e);return}this.log.log(`[WorkerRuntimeManager] idle-evicting ${e.projectId} after ${Math.round(r/1000)}s`),this.stop(e.projectId,"SIGTERM","idle-evict").catch((o)=>{this.log.warn(`[WorkerRuntimeManager] idle stop failed: ${o?.message??o}`)})},t)}async allocatePort(){let e=Ue-We,t=Math.min(e,50);for(let r=0;r<t;r++){let o=We+Math.floor(Math.random()*e);if(o+fe-1>Ue)continue;let n=!0;for(let a=0;a<fe;a++)if(this.usedPorts.has(o+a)){n=!1;break}if(!n)continue;let i=await this.isPortListening(o),s=await this.isPortListening(o+kt);if(i||s)continue;for(let a=0;a<fe;a++)this.usedPorts.add(o+a);return o}throw Error(`Cannot allocate port in range ${We}-${Ue} after ${t} attempts`)}releasePort(e){if(!e)return;for(let t=0;t<fe;t++)this.usedPorts.delete(e+t)}async isPortListening(e){let t=new AbortController,r=setTimeout(()=>t.abort(),250);try{return await fetch(`http://127.0.0.1:${e}/`,{method:"HEAD",signal:t.signal}),clearTimeout(r),!0}catch{return clearTimeout(r),!1}}tcpProbe(e){return new Promise((t)=>{let r=!1,o=(i)=>{if(r)return;r=!0;try{n.destroy()}catch{}t(i)},n=co({host:"127.0.0.1",port:e});n.setTimeout($o),n.once("connect",()=>o(!0)),n.once("error",()=>o(!1)),n.once("timeout",()=>o(!1))})}async waitForHealth(e,t){let{agentPort:r,proc:o}=e;if(!o)throw Error(`waitForHealth: slot ${e.projectId} has no spawned process`);let n=Date.now(),i=n+t,s=null,a=!1,c=0,l=0,u=0,p=0,m=n;while(Date.now()<i){if(p++,o.exitCode!==null||o.signalCode!=null||o.killed)throw Error(`agent-runtime exited (code=${o.exitCode}, signal=${o.signalCode}) before becoming healthy on port ${r}`);l++;let f=new AbortController,d=setTimeout(()=>f.abort(),1500);try{let g=await fetch(`http://127.0.0.1:${r}/health`,{method:"GET",signal:f.signal});if(clearTimeout(d),g.ok){this.log.log(`[WorkerRuntimeManager] /health ready for ${e.projectId} on port ${r} (HTTP ${g.status} after ${Date.now()-n}ms, ${p} iter, ${l} http)`);return}s=`HTTP /health returned ${g.status}`}catch(g){clearTimeout(d);let S=g?.name??"Error",V=g?.code??g?.cause?.code;s=`HTTP /health failed: ${S}${V?`(${V})`:""}: ${g?.message??g}`}if(u++,a=await this.tcpProbe(r),a){c=Date.now();let g=Date.now()-e.lastStdoutAt;if(g>=Tt)throw Error(`agent-runtime wedged on port ${r}: TCP listening but stdout silent for ${g}ms (> ${Tt}ms window); ${l} /health attempts, last error: ${s??"n/a"}`)}let w=Date.now();if(w-m>=Po){let g=w-n,S=w-e.lastStdoutAt,V=c>0?w-c:null;this.log.log(`[WorkerRuntimeManager] still waiting for /health on ${e.projectId} port ${r} (${(g/1000).toFixed(1)}s elapsed, tcpListening=${a}${V!=null?`(${V}ms ago)`:""}, lastStdout=${S}ms ago, ${l} http, ${u} tcp, lastError=${s??"n/a"})`),m=w}await new Promise((g)=>setTimeout(g,ko))}if(o.exitCode!==null||o.signalCode!=null||o.killed)throw Error(`agent-runtime exited (code=${o.exitCode}, signal=${o.signalCode}) before becoming healthy on port ${r}`);throw Error(`Timeout waiting for agent-runtime /health on port ${r} after ${p} iter (httpAttempts=${l}, tcpAttempts=${u}, tcpListening=${a}, lastError=${s??"n/a"})`)}async waitForExit(e,t){if(e.exitCode!==null||e.signalCode!=null||e.killed)return;await new Promise((r)=>{let o=setTimeout(()=>{try{e.kill("SIGKILL")}catch{}r()},t);e.once("exit",()=>{clearTimeout(o),r()})})}snapshot(e){return{projectId:e.projectId,status:e.status,agentPort:e.agentPort||void 0,apiServerPort:e.apiServerPort||void 0,pid:e.proc?.pid,startedAt:e.startedAt||void 0,lastUsedAt:e.lastUsedAt,restarts:e.restarts,lastError:e.lastError}}}import{hostname as Le,platform as He,arch as Ge}from"node:os";function G(){if(typeof __SHOGO_WORKER_VERSION__==="string"&&__SHOGO_WORKER_VERSION__.length>0)return`shogo-cli/${__SHOGO_WORKER_VERSION__}`;return"shogo-cli/unknown"}class Wt extends Error{code="TUNNEL_WS_HEADERS_UNSUPPORTED";constructor(){super("Tunnel WebSocket auth requires a runtime with WebSocket header support (Bun or Node >= 21). Current runtime does not support WebSocket constructor headers.");this.name="TunnelWebSocketHeaderSupportError"}}var re=60,ye=300,Be=3,xo=Be,Co=1800000,_o=25000,Ot=1000,Mt=60000,Dt=3;class Ke{opts;log;pollTimer=null;ws=null;wsIdleTimer=null;heartbeatTimer=null;stopped=!1;currentPollInterval=re;wsReconnectAttempt=0;lastHeartbeatError=null;consecutiveAuthFailures=0;consecutiveAuthSuccesses=0;serverPublishedWsUrl=null;activeAbortControllers=new Map;constructor(e){this.opts=e,this.log=e.logger??console}start(){if(!this.opts.apiKey){this.log.log("[WorkerTunnel] No API key set, skipping tunnel");return}this.stopped=!1,this.wsReconnectAttempt=0,this.currentPollInterval=re,this.log.log("[WorkerTunnel] Starting heartbeat polling to Shogo Cloud..."),this.heartbeatLoop()}stop(){if(this.stopped=!0,this.pollTimer)clearTimeout(this.pollTimer),this.pollTimer=null;this.cleanupWs(),this.log.log("[WorkerTunnel] Tunnel stopped")}isConnected(){if(this.ws!==null&&this.ws.readyState===WebSocket.OPEN)return!0;return!this.stopped&&!!this.opts.apiKey&&this.lastHeartbeatError===null&&this.pollTimer!==null}getCloudUrl(){return this.opts.cloudUrl.replace(/\/$/,"")}getWsBaseUrl(){let e=(this.opts.wsUrlOverride||process.env.SHOGO_TUNNEL_WS_URL||"").trim();if(e)return e.replace(/\/$/,"");if(this.serverPublishedWsUrl)return this.serverPublishedWsUrl.replace(/\/$/,"");return this.getCloudUrl().replace(/^http/,"ws")}buildWsUrl(){return`${this.getWsBaseUrl()}/api/instances/ws`}supportsWebSocketConstructorHeaders(e=globalThis){if(typeof e.Bun<"u"||typeof e.process?.versions?.bun==="string")return!0;let t=e.process?.versions?.node;if(t){if(parseInt(t.split(".")[0]??"0",10)>=21)return!0}return!1}createTunnelWebSocket(e,t,r=globalThis){if(!this.supportsWebSocketConstructorHeaders(r))throw new Wt;return new WebSocket(e,t)}getReconnectDelayMs(){let e=Math.min(Ot*Math.pow(2,this.wsReconnectAttempt),Mt),t=e*0.2*Math.random();return e+t}async collectMetadata(){let e={hostname:Le(),os:He(),arch:Ge(),uptime:process.uptime(),protocolVersion:Dt,tunnelStatus:this.ws?.readyState===WebSocket.OPEN?"connected":"polling",kind:this.opts.kind??"cli-worker"};try{let t=this.opts.resolver.getActiveProjects();e.activeProjects=t.length,e.projects=t.map((r)=>{let o=this.opts.resolver.status(r);return{projectId:r,status:o?.status??"unknown",agentPort:o?.agentPort}})}catch{e.activeProjects=0}return e}async sendHeartbeat(){let e=this.getCloudUrl(),t=await this.collectMetadata(),r=Le(),o=this.opts.name??r,n=await fetch(`${e}/api/instances/heartbeat`,{method:"POST",headers:{"Content-Type":"application/json","User-Agent":G(),"x-api-key":this.opts.apiKey,"x-shogo-kind":this.opts.kind??"cli-worker"},body:JSON.stringify({hostname:r,name:o,os:He(),arch:Ge(),kind:this.opts.kind??"cli-worker",metadata:t})});if(!n.ok)throw Error(`Heartbeat failed: HTTP ${n.status}`);let i=await n.json();if(typeof i.wsUrl==="string"&&i.wsUrl.length>0){if(i.wsUrl!==this.serverPublishedWsUrl)this.log.log(`[WorkerTunnel] Cloud advertised tunnel WS URL: ${i.wsUrl}`);this.serverPublishedWsUrl=i.wsUrl}return i}scheduleNextPoll(e){if(this.stopped)return;if(this.pollTimer)clearTimeout(this.pollTimer);let t=(e??this.currentPollInterval)*1000;this.pollTimer=setTimeout(()=>void this.heartbeatLoop(),t)}async heartbeatLoop(){if(this.stopped)return;if(this.ws&&this.ws.readyState===WebSocket.OPEN){this.scheduleNextPoll(this.currentPollInterval);return}try{let e=await this.sendHeartbeat(),t=e.nextPollIn||re;if(this.consecutiveAuthFailures>=Be){if(this.consecutiveAuthSuccesses++,this.consecutiveAuthSuccesses<xo){this.currentPollInterval=ye,this.scheduleNextPoll();return}}if(this.currentPollInterval=t,this.lastHeartbeatError)this.log.log("[WorkerTunnel] Heartbeat recovered"),this.lastHeartbeatError=null;if(this.consecutiveAuthFailures=0,this.consecutiveAuthSuccesses=0,e.wsRequested&&!this.ws){this.log.log("[WorkerTunnel] Cloud requested WebSocket — connecting..."),this.connectWs();return}}catch(e){let t=e?.message??String(e);if(/HTTP 40[13]\b/.test(t))this.consecutiveAuthFailures++,this.consecutiveAuthSuccesses=0;else this.consecutiveAuthFailures=0,this.consecutiveAuthSuccesses=0;if(t!==this.lastHeartbeatError)this.log.error(`[WorkerTunnel] Heartbeat error: ${t}`),this.lastHeartbeatError=t;if(this.consecutiveAuthFailures>=Be){let o=`tunnel saw ${this.consecutiveAuthFailures} consecutive auth failures from Shogo Cloud`;try{this.opts.onAuthRevoked?.(o)}catch(n){this.log.warn(`[WorkerTunnel] onAuthRevoked threw: ${n?.message??n}`)}if(this.currentPollInterval!==ye)this.log.warn(`[WorkerTunnel] ${this.consecutiveAuthFailures} consecutive auth failures — `+`backing off to ${ye}s. Re-authenticate with \`shogo login\`.`);this.currentPollInterval=ye}else this.currentPollInterval=re}this.scheduleNextPoll()}async handleRequest(e){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)return;this.resetWsIdleTimer();let t=new AbortController;this.activeAbortControllers.set(e.requestId,t);try{let r=await this.resolveLocalUrl(e.path,e.projectId);if(!r){let s=this.opts.resolver.describeRejection?this.opts.resolver.describeRejection(e.path,e.projectId):{code:"NO_LOCAL_RUNTIME",message:`no local runtime available for path: ${e.path}`};this.sendFrame({type:"response",requestId:e.requestId,status:502,headers:{"content-type":"application/json"},body:JSON.stringify({code:s.code,message:s.message,path:e.path})});return}let o={...e.headers??{}};if(e.projectId&&(e.path.startsWith("/agent/")||e.path==="/agent")){let s=await this.opts.resolver.deriveRuntimeToken(e.projectId);if(s)o["x-runtime-token"]=s}let n={method:e.method,headers:o,signal:t.signal};if(e.body&&e.method!=="GET"&&e.method!=="HEAD")n.body=e.body;let i=await fetch(r,n);if(e.stream){let s=i.body?.getReader();if(!s){this.sendFrame({type:"stream-error",requestId:e.requestId,error:"No response body for stream"});return}let a=new TextDecoder;try{while(!0){let{done:c,value:l}=await s.read();if(c)break;if(this.ws?.readyState!==WebSocket.OPEN)break;this.sendFrame({type:"stream-chunk",requestId:e.requestId,data:a.decode(l,{stream:!0})})}if(this.ws?.readyState===WebSocket.OPEN)this.sendFrame({type:"stream-end",requestId:e.requestId})}catch(c){if(c?.name!=="AbortError"&&this.ws?.readyState===WebSocket.OPEN)this.sendFrame({type:"stream-error",requestId:e.requestId,error:c?.message??String(c)})}}else{let s=await i.text(),a={};i.headers.forEach((c,l)=>{a[l]=c}),this.sendFrame({type:"response",requestId:e.requestId,status:i.status,headers:a,body:s})}}catch(r){if(r?.name==="AbortError")return;if(this.ws?.readyState!==WebSocket.OPEN)return;let o=e.stream?{type:"stream-error",requestId:e.requestId,error:r?.message??String(r)}:{type:"response",requestId:e.requestId,status:502,body:JSON.stringify({error:r?.message??String(r)})};this.sendFrame(o)}finally{this.activeAbortControllers.delete(e.requestId)}}async resolveLocalUrl(e,t){return this.opts.resolver.resolveLocalUrl(e,t)}sendFrame(e){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)return;try{this.ws.send(JSON.stringify(e))}catch(t){this.log.warn(`[WorkerTunnel] Frame send failed: ${t?.message??t}`)}}resetWsIdleTimer(){if(this.wsIdleTimer)clearTimeout(this.wsIdleTimer);this.wsIdleTimer=setTimeout(()=>{if(this.ws&&this.ws.readyState===WebSocket.OPEN){this.log.log("[WorkerTunnel] WebSocket idle timeout — closing, returning to polling");try{this.ws.close(1000,"Idle timeout")}catch{}}},Co)}startWsHeartbeat(){if(this.heartbeatTimer)clearInterval(this.heartbeatTimer);this.heartbeatTimer=setInterval(async()=>{if(!this.ws||this.ws.readyState!==WebSocket.OPEN)return;try{let e=await this.collectMetadata();this.sendFrame({type:"heartbeat",metadata:e})}catch{}},_o)}connectWs(){if(this.stopped||this.ws)return;let e=this.buildWsUrl(),t=Le(),r=He(),o=Ge(),n=this.opts.name??t;this.log.log(`[WorkerTunnel] Opening WebSocket to ${e} (hostname=${t})`);let i={headers:{Authorization:`Bearer ${this.opts.apiKey}`,"User-Agent":G(),"x-shogo-hostname":t,"x-shogo-name":n,"x-shogo-os":r,"x-shogo-arch":o,"x-shogo-kind":this.opts.kind??"cli-worker"}},s;try{s=this.createTunnelWebSocket(e,i)}catch(a){this.log.error(`[WorkerTunnel] WebSocket creation failed: ${a?.message??a}`),this.ws=null,this.scheduleNextPoll(5);return}this.ws=s,s.onopen=()=>{this.log.log("[WorkerTunnel] WebSocket connected — session active"),this.wsReconnectAttempt=0,this.startWsHeartbeat(),this.resetWsIdleTimer()},s.onmessage=(a)=>{let c;try{let l=typeof a.data==="string"?a.data:a.data.toString();c=JSON.parse(l)}catch{return}if(c.type==="ping"){this.sendFrame({type:"pong"}),this.resetWsIdleTimer();return}if(c.type==="cancel"){let l=this.activeAbortControllers.get(c.requestId);if(l)l.abort();return}if(c.type==="request"){this.handleRequest(c).catch((l)=>{this.log.error(`[WorkerTunnel] Error handling request: ${l?.message??l}`)});return}},s.onclose=(a)=>{if(this.log.log(`[WorkerTunnel] WebSocket closed: code=${a.code} reason=${a.reason||"none"}`),this.cleanupWs(),this.stopped)return;if(a.code===1000||a.code===4000)this.scheduleNextPoll(this.currentPollInterval);else{this.wsReconnectAttempt++;let c=this.getReconnectDelayMs();this.log.log(`[WorkerTunnel] Reconnecting in ${Math.round(c/1000)}s (attempt ${this.wsReconnectAttempt})`),this.scheduleNextPoll(Math.ceil(c/1000))}},s.onerror=(a)=>{this.log.error("[WorkerTunnel] WebSocket error:",a?.message??"unknown")}}cleanupWs(){if(this.heartbeatTimer)clearInterval(this.heartbeatTimer),this.heartbeatTimer=null;if(this.wsIdleTimer)clearTimeout(this.wsIdleTimer),this.wsIdleTimer=null;for(let[,e]of this.activeAbortControllers)try{e.abort()}catch{}this.activeAbortControllers.clear(),this.ws=null}_testing(){let e=this;return{sendHeartbeat:()=>e.sendHeartbeat(),heartbeatLoop:()=>e.heartbeatLoop(),connectWs:()=>e.connectWs(),cleanupWs:()=>e.cleanupWs(),handleRequest:(t)=>e.handleRequest(t),installFakeWs:(t)=>{e.ws=t},getCloudUrl:()=>e.getCloudUrl(),getWsBaseUrl:()=>e.getWsBaseUrl(),buildWsUrl:()=>e.buildWsUrl(),getReconnectDelayMs:()=>e.getReconnectDelayMs(),supportsWebSocketConstructorHeaders:(t)=>e.supportsWebSocketConstructorHeaders(t),createTunnelWebSocket:(t,r,o)=>e.createTunnelWebSocket(t,r,o),DEFAULT_POLL_INTERVAL_S:re,BACKOFF_BASE_MS:Ot,BACKOFF_MAX_MS:Mt,TUNNEL_PROTOCOL_VERSION:Dt,get currentPollInterval(){return e.currentPollInterval},set currentPollInterval(t){e.currentPollInterval=t},get wsReconnectAttempt(){return e.wsReconnectAttempt},set wsReconnectAttempt(t){e.wsReconnectAttempt=t},get ws(){return e.ws},get stopped(){return e.stopped},get serverPublishedWsUrl(){return e.serverPublishedWsUrl},set serverPublishedWsUrl(t){e.serverPublishedWsUrl=t}}}}async function Ut(e){let t=A({name:e.name,workerDir:e.workerDir,apiKey:e.apiKey,cloudUrl:e.cloudUrl,port:e.port?parseInt(e.port,10):void 0,projectsDir:e.projectsDir}),r=nt(e.proxy);if(!e.foreground)return Oo({cfg:t,proxy:r,flags:e});let o=F({flag:e.runtimeBin});if(!o)console.error(h.red(H({flag:e.runtimeBin}))),process.exit(1);if(Ie(process.env,r),e.debug){if(!await lt(at({cloudUrl:t.cloudUrl,apiKey:t.apiKey,workerDir:t.workerDir,proxy:r})))process.exit(1)}let n=!e.noAutoPull,i=!e.noGit;if(console.log(h.bold(`
15
- Shogo Worker — Starting`)),console.log(h.dim(" name ")+t.name),console.log(h.dim(" worker-dir ")+t.workerDir),console.log(h.dim(" cloud ")+t.cloudUrl),console.log(h.dim(" runtime ")+`${o.path} ${h.dim(`(via ${o.source})`)}`),console.log(h.dim(" auto-pull ")+(n?h.green("on")+h.dim(` → ${t.projectsDir}`):h.yellow("off"))),console.log(h.dim(" sync mode ")+(i?h.green("git")+h.dim(" (falls back to file transport if git is missing)"):h.yellow("files-only"))),e.project)console.log(h.dim(" project ")+e.project);if(r)console.log(h.dim(" proxy ")+`${r.url} ${h.dim(`(from ${r.source})`)}`);console.log("");let s=`${t.cloudUrl.replace(/\/+$/,"")}/api/ai/v1`,a={cloudUrl:t.cloudUrl,apiKey:t.apiKey,aiProxyUrl:s,aiProxyToken:t.apiKey,extraEnv:{SHOGO_PERMISSION_SCOPE:"cloud"}},c=new Fe({runtimeBin:e.runtimeBin,defaultSpawnConfig:a,autoPull:{enabled:n,projectsDir:t.projectsDir,watch:!0,useGit:i}});if(!c.resolveBinary())console.error(h.red(H({flag:e.runtimeBin}))),process.exit(1);let l=new Ke({apiKey:t.apiKey,cloudUrl:t.cloudUrl,name:t.name,kind:"cli-worker",resolver:c,onAuthRevoked:(m)=>{console.error(h.red(`✗ Cloud auth revoked: ${m}`)),console.error(h.dim(" Run `shogo login` to re-authenticate; this worker will keep polling at the auth-failure backoff until then."))}}),u=!1,p=async(m)=>{if(u)return;u=!0,console.log(h.dim(`
16
- Received ${m} — shutting down...`));try{l.stop()}catch{}try{await c.stopAll()}catch(f){console.warn(h.yellow(`stopAll: ${f?.message??f}`))}process.exit(0)};process.once("SIGINT",()=>void p("SIGINT")),process.once("SIGTERM",()=>void p("SIGTERM")),process.once("SIGHUP",()=>void p("SIGHUP")),l.start(),console.log(h.green("✓ Worker running. Ctrl-C to stop.")),await new Promise(()=>{})}function Oo({cfg:e,proxy:t,flags:r}){let{entry:o,runner:n}=Do(),i=Ie({...process.env,SHOGO_API_KEY:e.apiKey,SHOGO_CLOUD_URL:e.cloudUrl,SHOGO_INSTANCE_NAME:e.name,SHOGO_WORKER_DIR:e.workerDir,SHOGO_LOCAL_MODE:"true",PORT:String(e.port)},t),s=Mo(r),{pid:a}=tt({entry:o,runner:n,env:{...i,SHOGO_DETACHED_ARGS:s.join(" ")},cwd:e.workerDir,detach:!0,inheritStdio:!1});console.log(h.bold(`
17
- Shogo Worker — Started`)),console.log(h.dim(" pid: ")+a),console.log(h.dim(" name: ")+e.name),console.log(h.dim(" logs: ")+"~/.shogo/logs/worker.log"),console.log(h.dim(" stop: ")+"shogo worker stop")}function Mo(e){let t=["worker","start","--foreground"];if(e.name)t.push("--name",e.name);if(e.workerDir)t.push("--worker-dir",e.workerDir);if(e.apiKey)t.push("--api-key",e.apiKey);if(e.cloudUrl)t.push("--cloud-url",e.cloudUrl);if(e.port)t.push("--port",e.port);if(e.proxy)t.push("--proxy",e.proxy);if(e.project)t.push("--project",e.project);if(e.runtimeBin)t.push("--runtime-bin",e.runtimeBin);if(e.debug)t.push("--debug");if(e.noAutoPull)t.push("--no-auto-pull");if(e.projectsDir)t.push("--projects-dir",e.projectsDir);if(e.noGit)t.push("--no-git");return t}function Do(){let e=process.execPath,t=/\bbun(?:-[^/\\]*)?$/.test(e)||typeof globalThis.Bun<"u",r=process.argv[1];if(r&&Ao(r)){let n=/\.ts$/.test(r);return{entry:r,runner:t?"bun":n?"tsx":"node"}}return{entry:new URL("../../bin/shogo.mjs",import.meta.url).pathname,runner:t?"bun":"node"}}import Nt from"picocolors";async function jt(){let{killedPid:e}=rt("SIGTERM");if(e===null){console.log(Nt.dim("No worker running."));return}console.log(Nt.green(`✓ Worker stopped (pid=${e}).`))}import D from"picocolors";async function Ft(){let e=ce();if(!e){console.log(D.yellow("● stopped")+D.dim(" (no pid file)"));return}if(!ue(e)){console.log(D.red("● dead")+D.dim(` (stale pid ${e})`));return}let t=j();if(console.log(D.green("● running")+D.dim(` (pid ${e})`)),t.name)console.log(D.dim(" name: ")+t.name);if(t.cloudUrl)console.log(D.dim(" cloud: ")+t.cloudUrl);if(t.workerDir)console.log(D.dim(" dir: ")+t.workerDir)}import{spawn as Wo}from"node:child_process";import{existsSync as Uo,statSync as No}from"node:fs";import Lt from"picocolors";async function Ht(e){let t=e.err?ae:se;if(!Uo(t)){console.log(Lt.dim("No logs yet."));return}let r=e.follow?["-F","-n","200",t]:["-n","200",t];if(Wo("tail",r,{stdio:"inherit"}).on("exit",(n)=>process.exit(n??0)),!e.follow){if(No(t).size===0)console.log(Lt.dim("(log file is empty)"))}}import T from"picocolors";import{spawn as jo}from"node:child_process";import{hostname as Fo,platform as Lo,arch as Ho}from"node:os";import L from"picocolors";class x extends Error{kind;constructor(e,t){super(e);this.kind=t;this.name="CloudLoginError"}}async function Gt(e){let t=e.cloudUrl.replace(/\/$/,""),r=e.client??"cli",o=e.deviceName??Fo(),n=e.devicePlatform??`${Lo()}-${Ho()}`,i=e.appVersion??Ko(),s=e.log??((g)=>console.log(g)),a=e.fetchImpl??fetch,c=await a(`${t}/api/cli/login/start`,{method:"POST",headers:{"content-type":"application/json","user-agent":G()},body:JSON.stringify({deviceId:e.deviceId,deviceName:o,devicePlatform:n,deviceAppVersion:i,workspaceId:e.workspaceId,client:r}),signal:AbortSignal.timeout(1e4)}).catch((g)=>{throw new x(`Cannot reach Shogo Cloud at ${t}: ${g?.message??g}`,"transport")});if(!c.ok){let g=await c.text().catch(()=>"");throw new x(`Cloud rejected /api/cli/login/start (HTTP ${c.status}): ${g||"no body"}`,"transport")}let l=await c.json().catch(()=>({}));if(!l?.ok||!l.state||!l.authUrl)throw new x(`Cloud returned a malformed start response: ${l?.error??JSON.stringify(l)}`,"transport");let u=Go(e.pollIntervalMs??l.pollIntervalMs),p=e.timeoutMs??l.expiresInMs,m=Date.now()+p;if(s(""),s(L.bold("Sign in to Shogo Cloud")),s(L.dim(" cloud: ")+t),s(L.dim(" device: ")+`${o} (${n})`),s(L.dim(" user code: ")+L.cyan(l.userCode)),s(""),s(" Open this URL in your browser to approve:"),s(" "+L.cyan(l.authUrl)),s(""),e.openBrowser!==!1)if(typeof e.openBrowser==="function")try{await e.openBrowser(l.authUrl)}catch{}else qo(l.authUrl).catch(()=>{});s(L.dim("Waiting for approval..."));let f=e.installSignalHandlers??!0,d=()=>{w.abort()},w=new AbortController;if(e.abortSignal)if(e.abortSignal.aborted)w.abort();else e.abortSignal.addEventListener("abort",()=>w.abort(),{once:!0});if(f)process.once("SIGINT",d),process.once("SIGTERM",d);try{while(!0){if(w.signal.aborted)throw new x("Aborted by caller.","cancelled");if(Date.now()>=m)throw new x(`Timed out after ${Math.round(p/1000)}s waiting for approval.`,"timeout");let g=await a(`${t}/api/cli/login/poll?state=${encodeURIComponent(l.state)}`,{method:"GET",headers:{accept:"application/json","user-agent":G()},signal:AbortSignal.timeout(1e4)}).catch((S)=>(s(L.dim(` (poll error: ${S?.message??S} — retrying)`)),null));if(g&&g.ok){let S=await g.json().catch(()=>({}));if(S?.status==="approved"&&S.key)return{key:S.key,email:S.email??null,workspace:S.workspace??null,deviceId:S.deviceId??e.deviceId,workspaces:S.workspaces??null};if(S?.status==="denied")throw new x("Sign-in was denied in the browser.","denied");if(S?.status==="expired")throw new x("Sign-in request expired before it was approved.","expired")}await Bo(u,w.signal)}}finally{if(f)process.removeListener("SIGINT",d),process.removeListener("SIGTERM",d)}}function Go(e){if(!Number.isFinite(e)||e<=0)return 2000;return Math.min(Math.max(e,1000),1e4)}function Bo(e,t){return new Promise((r,o)=>{if(t?.aborted)return o(new x("Aborted by caller.","cancelled"));let n=setTimeout(()=>{t?.removeEventListener("abort",i),r()},e),i=()=>{clearTimeout(n),o(new x("Aborted by caller.","cancelled"))};t?.addEventListener("abort",i,{once:!0})})}function Ko(){return G()}function qo(e){return new Promise((t)=>{let r=process.platform==="darwin"?"open":process.platform==="win32"?"cmd":"xdg-open",o=process.platform==="win32"?["/c","start",'""',e]:[e];try{let n=jo(r,o,{stdio:"ignore",detached:!0});n.on("error",()=>t()),n.unref(),t()}catch{t()}})}import{readFileSync as Jo,writeFileSync as Vo,existsSync as Yo,chmodSync as Xo}from"node:fs";import{randomUUID as zo}from"node:crypto";function Bt(){if(Yo(Y)){let t=Jo(Y,"utf-8").trim();if(t)return t}N();let e=zo();return Vo(Y,e,{mode:384}),Xo(Y,384),e}var Qo="https://studio.shogo.ai";async function Kt(e){let t=j(),r=(e.cloudUrl||t.cloudUrl||Qo).replace(/\/$/,""),o=e.apiKey||process.env.SHOGO_API_KEY;if(o){await Zo({key:o,cloudUrl:r,cfg:t,name:e.name});return}let n=Bt(),i;try{i=await Gt({cloudUrl:r,deviceId:n,deviceName:e.name,workspaceId:e.workspace,openBrowser:!e.noBrowser})}catch(s){if(s instanceof x){if(console.error(T.red(`✗ ${s.message}`)),s.kind==="transport")console.error(T.dim(" If your network blocks browsers, run with --api-key <key> instead."));process.exit(1)}throw s}if(t.apiKey=i.key,t.cloudUrl=r,e.name)t.name=e.name;if(Z(t),console.log(""),console.log(T.green("✓ Signed in to Shogo Cloud")),i.workspace)console.log(T.dim(" workspace: ")+i.workspace);if(i.email)console.log(T.dim(" email: ")+i.email);console.log(T.dim(" saved to: ")+"~/.shogo/config.json"),console.log(""),console.log(T.dim(" next: ")+"shogo worker start")}async function Zo({key:e,cloudUrl:t,cfg:r,name:o}){if(!/^shogo_sk_/.test(e))console.error(T.red('✗ API key should start with "shogo_sk_". Copy it verbatim from the API Keys page.')),process.exit(1);let n;try{let i=await fetch(`${t}/api/api-keys/validate`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({key:e}),signal:AbortSignal.timeout(1e4)});if(n=await i.json().catch(()=>({})),!i.ok||!n?.valid)console.error(T.red(`✗ ${n?.error||`Cloud rejected the key (HTTP ${i.status}).`}`)),process.exit(1)}catch(i){console.error(T.red(`✗ Cannot reach Shogo Cloud at ${t}: ${i?.message??i}`)),process.exit(1)}if(r.apiKey=e,r.cloudUrl=t,o)r.name=o;if(Z(r),console.log(T.green("✓ API key saved to ~/.shogo/config.json")),n.workspace?.name)console.log(T.dim(" workspace: ")+n.workspace.name);if(n.user?.email)console.log(T.dim(" email: ")+n.user.email);console.log(T.dim(" next: ")+"shogo worker start")}import oe from"picocolors";async function qt(){let e=j();if(Object.keys(e).length===0){console.log(oe.dim("(no config — run `shogo login` or `shogo config set`)"));return}let t={...e,apiKey:e.apiKey?`***${e.apiKey.slice(-4)}`:void 0};console.log(oe.dim(`file: ${U}`)),console.log(JSON.stringify(t,null,2))}async function Jt(e,t){let r=["apiKey","cloudUrl","name","workerDir","port"];if(!r.includes(e))console.error(oe.red(`Unknown key: ${e}`)),console.error(oe.dim(`Allowed: ${r.join(", ")}`)),process.exit(1);let o=j();o[e]=e==="port"?parseInt(t,10):t,Z(o),console.log(oe.green(`✓ ${e} set`))}import b from"picocolors";import{spawn as en}from"node:child_process";import{createHash as tn}from"node:crypto";import{createWriteStream as rn,existsSync as ne,mkdirSync as qe,readFileSync as Xt,renameSync as Vt,rmSync as Je,writeFileSync as on}from"node:fs";import{tmpdir as nn}from"node:os";import{dirname as sn,join as ve}from"node:path";import{Readable as an}from"node:stream";import{pipeline as ln}from"node:stream/promises";var cn="https://github.com/shogo-labs/shogo-ai/releases/download";function zt(){let e=process.platform,t=process.arch,r;if(e==="darwin")r="darwin";else if(e==="linux")r="linux";else if(e==="win32")r="windows";else throw Error(`Unsupported platform: ${e}`);let o;if(t==="arm64")o="arm64";else if(t==="x64")o="x64";else throw Error(`Unsupported arch: ${t} (need arm64 or x64)`);return`${r}-${o}`}function be(){if(!ne(Q))return null;try{let e=Xt(Q,"utf-8");return JSON.parse(e)}catch{return null}}async function un(e,t){let r=t.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+)\/releases\/download/);if(!r)throw Error(`Cannot auto-resolve latest version from non-GitHub baseUrl '${t}'. Pass --version explicitly.`);let o=r[1],n=r[2];if(e==="stable"){let u=`https://api.github.com/repos/${o}/${n}/releases/latest`,p=await fetch(u,{headers:{Accept:"application/vnd.github+json"}});if(!p.ok)throw Error(`GitHub API ${p.status} for ${u}`);let m=await p.json();if(!m.tag_name)throw Error("GitHub API did not return tag_name");return Yt(m.tag_name)}let i=`https://api.github.com/repos/${o}/${n}/releases?per_page=30`,s=await fetch(i,{headers:{Accept:"application/vnd.github+json"}});if(!s.ok)throw Error(`GitHub API ${s.status} for ${i}`);let a=await s.json(),c=e==="beta"?"-beta":"-nightly",l=a.find((u)=>u.prerelease&&/^v\d+\.\d+\.\d+-/.test(u.tag_name)&&u.tag_name.includes(c));if(!l)throw Error(`No ${e} runtime release found`);return Yt(l.tag_name)}function Yt(e){if(!/^v\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/.test(e))throw Error(`Unexpected app tag '${e}' (expected vX.Y.Z[-prerelease])`);return e.slice(1)}function pn(e,t,r){let o=`shogo-agent-runtime-${t}.tar.gz`,n=`v${e}`,i=`${r.replace(/\/$/,"")}/${n}/${o}`;return{tarball:i,sha256:`${i}.sha256`,assetName:o}}async function dn(e,t){let r=await fetch(e,{redirect:"follow"});if(!r.ok)throw Error(`Download failed: HTTP ${r.status} for ${e}`);if(!r.body)throw Error(`Download failed: empty body for ${e}`);await ln(an.fromWeb(r.body),rn(t))}async function mn(e){let t=await fetch(e,{redirect:"follow"});if(!t.ok)throw Error(`SHA256 sidecar fetch failed: HTTP ${t.status} for ${e}`);let o=(await t.text()).trim().split(/\s+/)[0];if(!/^[0-9a-f]{64}$/i.test(o))throw Error(`SHA256 sidecar at ${e} did not contain a 64-char hex digest`);return o.toLowerCase()}function gn(e){let t=Xt(e);return tn("sha256").update(t).digest("hex")}async function hn(e,t){await new Promise((r,o)=>{let n=en("tar",["-xzf",e,"-C",t],{stdio:["ignore","pipe","pipe"]}),i="";n.stderr.on("data",(s)=>{i+=s.toString()}),n.on("error",o),n.on("exit",(s)=>{if(s===0)r();else o(Error(`tar exited ${s}: ${i.trim()}`))})})}function fn(e,t){ze();let r=sn(t);if(!ne(r))qe(r,{recursive:!0,mode:448});let o=`${t}.next`;if(ne(o))Je(o,{force:!0});Vt(e,o);try{if(process.platform!=="win32"){let{chmodSync:n}=Pe("node:fs");n(o,493)}}catch{}if(ne(t))Je(t,{force:!0});Vt(o,t)}async function Qt(e={}){let t=e.logger??console,r=e.channel??"stable",o=e.baseUrl??process.env.SHOGO_RUNTIME_RELEASES_URL??cn,n=e.target??zt(),i=e.version;if(!i)t.log(`[runtime install] Resolving latest ${r} version...`),i=await un(r,o),t.log(`[runtime install] Latest ${r} = ${i}`);let s=be();if(s&&s.version===i&&s.target===n&&!e.force)return t.log(`[runtime install] ${i} (${n}) already installed at ${_} — pass --force to reinstall`),{version:i,target:n,binPath:_,source:s.source,sha256:s.sha256,channel:r};let a=pn(i,n,o);t.log(`[runtime install] Downloading ${a.tarball}`);let c=ve(nn(),`shogo-runtime-install-${process.pid}-${Date.now()}`);qe(c,{recursive:!0});try{let l=ve(c,a.assetName);await dn(a.tarball,l),t.log("[runtime install] Verifying SHA-256...");let u=await mn(a.sha256),p=gn(l);if(p!==u)throw Error(`SHA-256 mismatch for ${a.assetName}
9
+ `));return!t}import{accessSync as Jr,constants as Vr,existsSync as Yr}from"node:fs";import{delimiter as Xr,join as zr}from"node:path";function G(e={}){let t=e.env??process.env,r=Qr(e);for(let i of r)if(Rt(i.path))return i;let o=e.systemBinName??kt(),n=Zr(o,t.PATH);if(n)return{path:n,source:"path"};return null}function Qr(e){let t=e.env??process.env,r=[];if(e.flag&&e.flag.trim())r.push({path:e.flag.trim(),source:"flag"});let o=t.SHOGO_AGENT_RUNTIME_BIN?.trim();if(o)r.push({path:o,source:"env"});return r.push({path:O,source:"home"}),r}function kt(){return process.platform==="win32"?"shogo-agent-runtime.exe":"shogo-agent-runtime"}function Rt(e){if(!Yr(e))return!1;try{if(process.platform==="win32")return!0;return Jr(e,Vr.X_OK),!0}catch{return!1}}function Zr(e,t){if(!t)return null;for(let r of t.split(Xr)){if(!r)continue;let o=zr(r,e);if(Rt(o))return o}return null}function q(e={}){let t=e.env??process.env,r=[];if(r.push("Error: agent-runtime binary not found."),r.push(""),r.push("Looked in (priority order):"),e.flag)r.push(` --runtime-bin ${e.flag}`);if(t.SHOGO_AGENT_RUNTIME_BIN)r.push(` $SHOGO_AGENT_RUNTIME_BIN = ${t.SHOGO_AGENT_RUNTIME_BIN}`);return r.push(` ${O} (default install location)`),r.push(` ${kt()} on $PATH`),r.push(""),r.push("Fix:"),r.push(" - Run `shogo runtime install` to download the latest binary, or"),r.push(" - Pass `--runtime-bin <path>` to point at an existing build."),r.join(`
10
+ `)}D();import{createHmac as Gt,randomBytes as ho}from"node:crypto";import{existsSync as Fe,mkdirSync as Ot,readdirSync as Bt}from"node:fs";import{createConnection as fo}from"node:net";import{tmpdir as wo}from"node:os";import{dirname as yo,join as z}from"node:path";D();import{existsSync as eo,mkdirSync as to,writeFileSync as we}from"node:fs";import{join as X}from"node:path";var Pt=1e5;function Tt(e){let t=parseInt(e.RUNTIME_MEMORY_MB||"",10);if(!Number.isFinite(t)||t<=0)return null;let r=parseInt(e.RUNTIME_CPU_PERCENT||"0",10),o=Number.isFinite(r)&&r>0?r:0,n=parseInt(e.RUNTIME_TASKS_MAX||"",10),i=Number.isFinite(n)&&n>0?n:4096;return{memoryMB:t,cpuPercent:o,tasksMax:i}}function $t(e,t){let o=`--max-old-space-size=${Math.max(256,Math.floor(t*0.75))}`,n=e.NODE_OPTIONS?.trim();if(n){if(/--max-old-space-size=/.test(n))return;e.NODE_OPTIONS=`${n} ${o}`}else e.NODE_OPTIONS=o}function ro(){if(process.platform!=="linux")return!1;try{return Y("systemd-run",["--version"],{stdio:"ignore",timeout:3000}).status===0}catch{return!1}}function Et(e){let{command:t,args:r,limits:o,scopeName:n,logger:i}=e;if(process.platform!=="linux"||!ro())return{command:t,args:r,wrapped:!1};let s=["-p",`MemoryMax=${o.memoryMB}M`,"-p",`MemoryHigh=${Math.max(128,Math.floor(o.memoryMB*0.9))}M`,"-p",`TasksMax=${o.tasksMax}`];if(o.cpuPercent>0)s.push("-p",`CPUQuota=${o.cpuPercent}%`);let a=["--user","--scope","--quiet","--collect",`--unit=${n}`,...s,"--",t,...r];return i?.log(`[resource-limits] wrapping spawn in systemd scope ${n} (MemoryMax=${o.memoryMB}M)`),{command:"systemd-run",args:a,wrapped:!0}}function xt(e){let{pid:t,limits:r,logger:o}=e;if(process.platform!=="linux")return!1;let n="/sys/fs/cgroup";if(!eo(X(n,"cgroup.controllers")))return!1;let i=X(n,"shogo",`rt-${t}`);try{if(to(i,{recursive:!0}),we(X(i,"memory.max"),String(r.memoryMB*1024*1024)),r.cpuPercent>0){let s=Math.floor(r.cpuPercent/100*Pt);we(X(i,"cpu.max"),`${s} ${Pt}`)}try{we(X(i,"pids.max"),String(r.tasksMax))}catch{}return we(X(i,"cgroup.procs"),String(t)),o?.log(`[resource-limits] attached pid ${t} to cgroup ${i} (memory.max=${r.memoryMB}M)`),!0}catch(s){return o?.warn(`[resource-limits] direct cgroup attach failed for pid ${t}: ${s?.message??s}`),!1}}function It(e){let{pid:t,limits:r,logger:o}=e;if(process.platform!=="win32")return!1;let n=process.env.SHOGO_WINJOB_HELPER||"@shogo/winjob";try{let i=Ce(n);if(typeof i.limitProcess!=="function")return!1;let s=i.limitProcess(t,{memoryBytes:r.memoryMB*1024*1024,killOnClose:!0});if(s)o?.log(`[resource-limits] attached pid ${t} to Job Object (${r.memoryMB}M)`);return s}catch{return!1}}function oo(e,t=process.platform){try{if(t==="win32"){let i=`$ErrorActionPreference='SilentlyContinue';$root=${e};$all=Get-CimInstance Win32_Process;$seen=@{};$stack=New-Object System.Collections.Stack;$stack.Push($root);$sum=0;while($stack.Count -gt 0){$id=$stack.Pop();if($seen[$id]){continue};$seen[$id]=$true;foreach($p in $all){if($p.ProcessId -eq $id){$sum+=$p.WorkingSetSize};if($p.ParentProcessId -eq $id){$stack.Push($p.ProcessId)}}};[math]::Floor($sum/1024)`,s=Y("powershell.exe",["-NoProfile","-NonInteractive","-Command",i],{encoding:"utf-8",timeout:1e4,windowsHide:!0});if(s.status!==0||!s.stdout)return null;let a=parseInt(s.stdout.trim(),10);return Number.isFinite(a)?a:null}let r=Y("ps",["-o","rss=","-g",String(e)],{encoding:"utf-8",timeout:5000});if(r.status!==0||!r.stdout)return null;let o=0,n=!1;for(let i of r.stdout.split(`
11
+ `)){let s=parseInt(i.trim(),10);if(Number.isFinite(s))o+=s,n=!0}return n?o:null}catch{return null}}class We{timer=null;consecutive=0;fired=!1;pid;ceilingMB;platform;intervalMs;strikes;logger;onBreach;constructor(e){this.pid=e.pid,this.ceilingMB=e.ceilingMB,this.platform=e.platform??process.platform,this.intervalMs=e.intervalMs??parseInt(process.env.RUNTIME_RSS_WATCHDOG_MS||"15000",10),this.strikes=e.strikes??parseInt(process.env.RUNTIME_RSS_WATCHDOG_STRIKES||"2",10),this.logger=e.logger,this.onBreach=e.onBreach}start(){if(this.timer)return;this.timer=setInterval(()=>this.sample(),Math.max(2000,this.intervalMs));try{this.timer.unref?.()}catch{}}stop(){if(this.timer)clearInterval(this.timer),this.timer=null}sample(){if(this.fired)return;let e=oo(this.pid,this.platform);if(e==null){this.consecutive=0;return}let t=Math.floor(e/1024);if(t<=this.ceilingMB){this.consecutive=0;return}if(this.consecutive+=1,this.logger?.warn(`[resource-limits] pid ${this.pid} RSS ${t}MB over ceiling ${this.ceilingMB}MB (strike ${this.consecutive}/${this.strikes})`),this.consecutive>=this.strikes)this.fired=!0,this.stop(),this.onBreach(t)}}import{CloudFileTransport as He}from"@shogo-ai/sdk/cloud-file-transport";import{killViteWatchFromPidfile as Mt}from"@shogo-ai/sdk/vite-watch";import{watch as _t,statSync as ao}from"node:fs";import{relative as Mi,sep as lo,posix as co}from"node:path";var uo=1500,po=new Set(["node_modules",".git","dist","build",".vite",".cache"]),mo=[".shogo/"];function go(e){if(!e)return!0;return e.split("/").some((t)=>po.has(t))}class ae{rootDir;transport;debounceMs;logger;onFlush;mode;git;commitAndPush;watcher=null;pending=new Set;timer=null;flushing=!1;stopped=!1;constructor(e){if(this.rootDir=e.rootDir,this.transport=e.transport,this.debounceMs=e.debounceMs??uo,this.logger=e.logger??console,this.onFlush=e.onFlush,this.mode=e.mode??"files",this.mode==="git"&&!e.git)throw Error('CloudSyncWatcher: mode: "git" requires the `git` option block');this.git=e.git,this.commitAndPush=e.commitAndPush??(async(t)=>{let r=await Promise.resolve().then(() => (ve(),{}));return so(t)})}start(){if(this.watcher)return;if(this.stopped)throw Error("CloudSyncWatcher: already stopped, build a fresh instance");try{this.watcher=_t(this.rootDir,{recursive:!0},(e,t)=>{if(!t)return;this.handleEvent(String(t))})}catch(e){this.logger.warn(`[CloudSyncWatcher] recursive watch failed (${e?.message??e}); falling back to root-only watch.`),this.watcher=_t(this.rootDir,(t,r)=>{if(!r)return;this.handleEvent(String(r))})}}async stop(){if(this.stopped)return;if(this.stopped=!0,this.timer)clearTimeout(this.timer),this.timer=null;if(this.watcher){try{this.watcher.close()}catch{}this.watcher=null}if(this.pending.size>0)await this.flush()}handleEvent(e){if(this.stopped)return;let t=e.split(lo).join(co.sep);if(go(t))return;try{let r=`${this.rootDir}/${t}`;if(ao(r).isDirectory())return}catch{return}this.pending.add(t),this.scheduleFlush()}scheduleFlush(){if(this.timer)return;this.timer=setTimeout(()=>{this.timer=null,this.flush()},this.debounceMs)}async flush(){if(this.flushing){this.scheduleFlush();return}if(this.pending.size===0)return;this.flushing=!0;let e=Array.from(this.pending);this.pending.clear();try{if(this.mode==="git")await this.flushGit(e);else await this.flushFiles(e)}catch(t){this.logger.error(`[CloudSyncWatcher] flush failed: ${t?.message??t}`);for(let r of e)this.pending.add(r);this.scheduleFlush()}finally{this.flushing=!1}}async flushFiles(e){if(e.length===0)return;let t=await this.transport.uploadFiles(e);if(this.onFlush?.({uploaded:e,errors:t.errors.length}),t.errors.length>0)for(let r of t.errors)this.logger.warn(`[CloudSyncWatcher] upload ${r.path}: ${r.message}`)}async flushGit(e){if(!this.git)throw Error("CloudSyncWatcher: missing git options");let t=[],r=[];for(let i of e)if(mo.some((s)=>i===s.slice(0,-1)||i.startsWith(s)))t.push(i);else r.push(i);if(t.length>0){let i=await this.transport.uploadFiles(t);if(i.errors.length>0)for(let s of i.errors)this.logger.warn(`[CloudSyncWatcher] upload ${s.path}: ${s.message}`)}let o=!1,n;if(r.length>0){let i=`auto: ${new Date().toISOString()}`;try{let s=await this.commitAndPush({apiUrl:this.git.apiUrl,apiKey:this.git.apiKey,projectId:this.git.projectId,localDir:this.rootDir,message:i,branch:this.git.branch,authorEmail:this.git.authorEmail,authorName:this.git.authorName,logger:this.logger});o=s.committed,n=s.commitSha}catch(s){throw s}}this.onFlush?.({uploaded:e,errors:0,committed:o,commitSha:n})}}ve();var Le=37100,Ge=37900,Dt=1,So=2,ke=16,bo=900000,vo=30000,ko=1000,Ro=60000,Wt=8,Ut=300000,Nt=3600000,Po=8,To=120000,$o=900000,Eo=60000,xo=500,Io=30000,jt=25000,Co=5000,Ao=500,_o=process.env.SHOGO_PERF_LOG==="1";function Re(e,t,r,o){if(!_o)return;console.log(`[shogo-perf] ${JSON.stringify({perf:"open",source:"worker",phase:e,projectId:t,openAttemptId:r,atMs:Date.now(),...o})}`)}var Oo=(e)=>({command:e,args:[]}),Be=null;function Kt(){if(!Be)Be=ho(32).toString("hex");return Be}function Ft(e){return Gt("sha256",Kt()).update(`runtime:${e}`).digest("hex")}function Mo(e){return Gt("sha256",Kt()).update(`webhook:${e}`).digest("hex")}function Ht(e){let t=e.indexOf("?");if(t===-1)return{pathname:e,search:""};return{pathname:e.slice(0,t),search:e.slice(t)}}function Lt(e){try{return Bt(e).length===0}catch{return!0}}function Ke(e,t,r){let o=[];switch(o.push(`Cannot spawn agent-runtime for project ${e}: no workspace directory available.`),o.push(""),t){case"no-auto-pull-config":o.push(" Reason: WorkerRuntimeManager was constructed without an `autoPull` config and no caller-provided `projectDir` was found on disk."),o.push(" This usually means a programmatic embedder forgot to wire up enrichSpawnConfig "+"or autoPull. CLI users should not see this — please file a bug.");break;case"no-projects-dir":o.push(" Reason: auto-pull is configured but `projectsDir` is empty. The worker needs a persistent root directory under which it can store cloned project workspaces.");break;case"auto-pull-disabled":o.push(` Reason: auto-pull was disabled (--no-auto-pull) and the expected pre-pulled workspace at ${r} is missing or empty.`);break}return o.push(""),o.push(" How to fix (pick one):"),o.push(" 1. Re-enable auto-pull (default). Drop the --no-auto-pull flag and restart"),o.push(" the worker. The first inbound request for this project will clone its"),o.push(" workspace from Shogo Cloud into <projectsDir>/<projectId>/."),o.push(""),o.push(" 2. Pre-pull manually with `shogo project pull <projectId>` before starting"),o.push(" the worker. Use this when you want full control over when the clone runs"),o.push(" (slow links, scheduled maintenance windows, etc.)."),o.push(""),o.push(" 3. Point the worker at an existing workspace by setting either:"),o.push(" --projects-dir <path> (per-invocation flag)"),o.push(" SHOGO_PROJECTS_DIR=<path> (env var, persists across restarts)"),o.push(" shogo config set projectsDir <path>"),o.push(" Whichever path you pick must contain a subdirectory named after the "),o.push(` project id (e.g. <path>/${e}/) populated with the project's source.`),o.push(""),o.push(" Docs: https://shogo.ai/docs/self-hosted-worker#workspace-seeding"),o.join(`
12
+ `)}class qe{opts;log;runtimes=new Map;usedPorts=new Set;spawnCommand;resolved=null;stopped=!1;resolvedLimits=void 0;watchers=new Map;pulledProjects=new Set;syncModes=new Map;constructor(e={}){this.opts=e,this.log=e.logger??console,this.spawnCommand=e.spawnCommand??Oo,this.sweepOrphanedViteWatches()}resolveBinary(){if(!this.resolved)this.resolved=this.opts.resolveBin?this.opts.resolveBin():G({flag:this.opts.runtimeBin,env:this.opts.env});return this.resolved}async resolveLocalUrl(e,t){let{pathname:r,search:o}=Ht(e);if(!(r.startsWith("/agent/")||r==="/agent"))return null;if(!t){let s=this.getActiveProjects();if(s.length!==1)return null;t=s[0]}let n=await this.spawnConfigFor(t);if(!n)return this.log.warn(`[WorkerRuntimeManager] No spawn config for ${t} — set defaultSpawnConfig or enrichSpawnConfig`),null;let i=await this.ensureRunning(t,n);if(!i.agentPort)return null;return this.touch(t),`http://127.0.0.1:${i.agentPort}${r}${o}`}deriveRuntimeToken(e){return Ft(e)}describeRejection(e,t){let{pathname:r}=Ht(e);if(!(r.startsWith("/agent/")||r==="/agent"))return{code:"CLI_WORKER_HAS_NO_DATA_API",message:`cli-worker only serves /agent/* paths; tried: ${r}`};return{code:"CLI_WORKER_NO_PROJECT_FOR_PATH",message:`cli-worker received an /agent path without a single active project; projectId=${t??"none"}, path=${r}`}}async spawnConfigFor(e){let t=this.opts.defaultSpawnConfig;if(!t)return null;if(this.opts.enrichSpawnConfig)try{return await this.opts.enrichSpawnConfig(e,t)}catch(r){this.log.warn(`[WorkerRuntimeManager] enrichSpawnConfig failed for ${e}: ${r?.message??r}`)}return t}async ensureRunning(e,t){if(this.stopped)throw Error("WorkerRuntimeManager is stopped");let r=this.runtimes.get(e);if(r?.status==="failed"){let i=this.breakerCooldownMs(r.breakerTrips),s=Date.now()-r.lastFailureAt;if(s>=i)this.log.log(`[WorkerRuntimeManager] auto-heal: ${e} cooldown elapsed (${Math.round(s/1000)}s since trip #${r.breakerTrips}) — `+"clearing circuit breaker and attempting a fresh spawn"),this.healBreaker(r);else{let a=i-s;throw Error(`[WorkerRuntimeManager] cannot ensureRunning(${e}): ${r.lastError??"runtime is in failed state"} Will auto-retry in ~${Math.ceil(a/1000)}s; call resetFailure(${e}) to retry immediately, or stop(${e}) to give up.`)}}t=await this.maybeAutoPull(e,t);let o=this.runtimes.get(e);if(o?.status==="running")return this.touch(e),this.snapshot(o);if(o?.startPromise){let i=await o.startPromise;return this.snapshot(i)}let n=o??this.makeSlot(e,t);if(!o)this.runtimes.set(e,n);n.spawnConfig=t,n.startPromise=this.doStart(n);try{let i=await n.startPromise;return this.enforceMaxRuntimes(e),this.snapshot(i)}finally{n.startPromise=null}}enforceMaxRuntimes(e){let t=this.opts.maxRuntimes;if(t==null||!Number.isFinite(t)||t<=0)return;let r=Date.now(),o=Array.from(this.runtimes.values()).filter((s)=>s.status==="running");if(o.length<=t)return;let n=o.filter((s)=>s.projectId!==e&&r-s.lastUsedAt>=vo).sort((s,a)=>s.lastUsedAt-a.lastUsedAt),i=o.length-t;for(let s of n){if(i<=0)break;let a=r-s.lastUsedAt;this.log.log(`[WorkerRuntimeManager] maxRuntimes=${t} exceeded (${o.length} running) — `+`LRU-evicting ${s.projectId} (idle ${Math.round(a/1000)}s)`),this.stop(s.projectId,"SIGTERM","lru-evict").catch((c)=>{this.log.warn(`[WorkerRuntimeManager] maxRuntimes eviction of ${s.projectId} failed: ${c?.message??c}`)}),i--}if(i>0)this.log.log(`[WorkerRuntimeManager] maxRuntimes=${t} still exceeded by ${i} after eviction pass — `+"remaining over-cap slots are mid-stream; will retry on next spawn / idle reap")}async ensurePulled(e,t){return this.maybeAutoPull(e,t)}async maybeAutoPull(e,t){let r=this.opts.autoPull;if(t.projectDir&&Fe(t.projectDir))return t;if(!r)throw Error(Ke(e,"no-auto-pull-config",null));if(!r.projectsDir)throw Error(Ke(e,"no-projects-dir",null));if(!r.enabled){let s=z(r.projectsDir,e);if(Fe(s)&&!Lt(s))return{...t,projectDir:s};throw Error(Ke(e,"auto-pull-disabled",s))}if(this.pulledProjects.has(e))return{...t,projectDir:z(r.projectsDir,e)};let o=z(r.projectsDir,e),n=r.logger??this.log,i={cloneProject:r.gitOps?.cloneProject??At,gitIsAvailable:r.gitOps?.gitIsAvailable??Ct,isGitRepo:r.gitOps?.isGitRepo??be};this.pulledProjects.add(e);try{Ot(o,{recursive:!0});let s=Lt(o),a=i.isGitRepo(o),u=(r.useGit!==!1?await i.gitIsAvailable():!1)&&(s||a)?"git":"files";if(this.syncModes.set(e,u),u==="git"){if(s){n.log(`[WorkerRuntimeManager] auto-pull: git clone project ${e} into ${o}`);try{let p=await i.cloneProject({apiUrl:t.cloudUrl,apiKey:t.apiKey,projectId:e,localDir:o,shallow:!0,logger:n});n.log(`[WorkerRuntimeManager] auto-pull: ${e} cloned at ${p.commitSha.slice(0,8)}`)}catch(p){n.warn(`[WorkerRuntimeManager] auto-pull: git clone failed for ${e} (${p?.message??p}); falling back to CloudFileTransport.downloadAll`),this.syncModes.set(e,"files"),await this.fileTransportClone(e,o,t,n)}}else if(a)n.log(`[WorkerRuntimeManager] auto-pull: ${e} already has .git/; skipping clone`);if(this.syncModes.get(e)==="git")await this.topUpShogoState(e,o,t,n)}else if(s)await this.fileTransportClone(e,o,t,n);else n.log(`[WorkerRuntimeManager] auto-pull: ${e} workspace already populated; skipping clone`);if(r.watch!==!1&&!this.watchers.has(e))try{let p=new He({apiUrl:t.cloudUrl,apiKey:t.apiKey,projectId:e,localDir:o}),m=this.syncModes.get(e)??"files",f=new ae({rootDir:o,transport:p,logger:n,mode:m,git:m==="git"?{apiUrl:t.cloudUrl,apiKey:t.apiKey,projectId:e}:void 0});f.start(),this.watchers.set(e,f)}catch(p){n.warn(`[WorkerRuntimeManager] auto-pull: watcher start failed for ${e}: ${p?.message??p}`)}}catch(s){n.warn(`[WorkerRuntimeManager] auto-pull: failed for ${e} — runtime will fall back to template defaults. `+`(${s?.message??s})`)}return{...t,projectDir:o}}async fileTransportClone(e,t,r,o){o.log(`[WorkerRuntimeManager] auto-pull: file-transport clone of ${e} into ${t}`);let i=await new He({apiUrl:r.cloudUrl,apiKey:r.apiKey,projectId:e,localDir:t}).downloadAll();o.log(`[WorkerRuntimeManager] auto-pull: ${e} downloaded ${i.downloaded} files (${i.errors.length} errors)`)}async topUpShogoState(e,t,r,o){try{let n=new He({apiUrl:r.cloudUrl,apiKey:r.apiKey,projectId:e,localDir:t}),s=(await n.listManifest()).filter((c)=>c.path===".shogo"||c.path.startsWith(".shogo/"));if(s.length===0)return;let a=await n.downloadFiles(s);o.log(`[WorkerRuntimeManager] auto-pull: ${e} .shogo/ top-up downloaded ${a.downloaded} files (${a.errors.length} errors)`)}catch(n){o.warn(`[WorkerRuntimeManager] auto-pull: .shogo top-up failed for ${e}: ${n?.message??n}`)}}shouldDeferToCloudSync(e){return!!this.opts.autoPull?.enabled&&this.pulledProjects.has(e)}status(e){let t=this.runtimes.get(e);return t?this.snapshot(t):null}getActiveProjects(){return Array.from(this.runtimes.keys()).filter((e)=>{let t=this.runtimes.get(e);return t&&(t.status==="running"||t.status==="starting"||t.status==="restarting")})}touch(e){let t=this.runtimes.get(e);if(!t)return;t.lastUsedAt=Date.now(),this.armIdleTimer(t)}async stop(e,t="SIGTERM",r="stop"){let o=this.runtimes.get(e);if(!o)return;if(o.status="stopping",o.restartTimer)clearTimeout(o.restartTimer),o.restartTimer=null;if(o.idleTimer)clearTimeout(o.idleTimer),o.idleTimer=null;if(o.graceTimer)clearTimeout(o.graceTimer),o.graceTimer=null;if(o.limitWatchdog)o.limitWatchdog.stop(),o.limitWatchdog=null;if(o.proc){this.killProcessGroup(o,t),this.killViteWatch(o);try{o.proc.kill(t)}catch{}await this.waitForExit(o.proc,5000),this.killProcessGroup(o,"SIGKILL"),this.killViteWatch(o)}o.pid=null,this.releasePort(o.agentPort),this.runtimes.delete(e),this.notifyGone(e,{reason:r})}notifyGone(e,t){let r=this.opts.onRuntimeGone;if(!r)return;try{r(e,t)}catch(o){this.log.warn(`[WorkerRuntimeManager] onRuntimeGone(${e}) threw: ${o?.message??o}`)}}resetFailure(e){let t=this.runtimes.get(e);if(!t||t.status!=="failed")return!1;if(t.restartTimer)clearTimeout(t.restartTimer),t.restartTimer=null;if(t.idleTimer)clearTimeout(t.idleTimer),t.idleTimer=null;if(t.graceTimer)clearTimeout(t.graceTimer),t.graceTimer=null;if(t.limitWatchdog)t.limitWatchdog.stop(),t.limitWatchdog=null;return this.runtimes.delete(e),this.log.log(`[WorkerRuntimeManager] resetFailure: ${e} cleared, next ensureRunning will respawn`),!0}breakerCooldownMs(e){let t=Math.max(0,e-1),r=To*Math.pow(2,t);return Math.min(r,$o)}healBreaker(e){e.status="stopped",e.consecutiveFailures=0}async stopAll(e="SIGTERM"){this.stopped=!0;let t=Array.from(this.watchers.keys());await Promise.all(t.map(async(o)=>{let n=this.watchers.get(o);if(this.watchers.delete(o),n)try{await n.stop()}catch(i){this.log.warn(`[WorkerRuntimeManager] watcher stop ${o}: ${i?.message??i}`)}}));let r=Array.from(this.runtimes.keys());await Promise.all(r.map((o)=>this.stop(o,e).catch((n)=>{this.log.error(`[WorkerRuntimeManager] Failed to stop ${o}: ${n?.message??n}`)})))}makeSlot(e,t){return{projectId:e,agentPort:0,apiServerPort:0,status:"starting",proc:null,pid:null,startedAt:0,lastStdoutAt:0,lastUsedAt:Date.now(),restarts:0,consecutiveFailures:0,lastFailureAt:0,breakerTrips:0,restartTimestamps:[],graceTimer:null,restartTimer:null,idleTimer:null,limitWatchdog:null,spawnConfig:t,startPromise:null}}async doStart(e){let t=this.resolveBinary();if(!t)throw e.status="error",e.lastError="agent-runtime binary not found (run `shogo runtime install`)",Error(e.lastError);if(!e.agentPort)e.agentPort=await this.allocatePort(),e.apiServerPort=e.agentPort+Dt;let r=this.buildEnv(e,t.path),o=this.resolveCwd(e),n=this.spawnCommand(t.path),i=this.resolveLimits(),{command:s,args:a}=n,c=!1;if(i){let d=Et({command:n.command,args:n.args,limits:i,scopeName:`shogo-rt-${e.projectId.slice(0,8)}-${Date.now().toString(36)}`,logger:this.log});s=d.command,a=d.args,c=d.wrapped}this.log.log(`[WorkerRuntimeManager] Spawning agent-runtime for ${e.projectId} via ${s} ${a.join(" ")} (port=${e.agentPort}, source=${t.source}${i?`, memCeil=${i.memoryMB}M`:""})`);let l=process.platform!=="win32",u=T(s,a,{cwd:o,env:r,detached:l,stdio:["ignore","pipe","pipe"]});if(l)try{u.unref()}catch{}e.proc=u,e.pid=u.pid??null,e.status="starting",e.startedAt=Date.now();let p=e.spawnConfig.extraEnv?.SHOGO_OPEN_ID;if(Re("agent-spawned",e.projectId,p,{pid:e.pid,command:s,args:a,source:t.source}),i)try{this.applyPostSpawnLimits(e,i,c)}catch(d){this.log.warn(`[WorkerRuntimeManager] failed to apply resource limits for ${e.projectId}: ${d?.message??d}`)}e.lastStdoutAt=e.startedAt,u.on("error",(d)=>{e.lastError=d?.message??String(d),this.log.error(`[WorkerRuntimeManager] spawn error for ${e.projectId}: ${e.lastError}`)}),u.on("exit",(d,w)=>{this.handleExit(e,d,w)});let m=`[runtime:${e.projectId.slice(0,8)}]`,f=!0;u.stdout?.on("data",(d)=>{if(e.lastStdoutAt=Date.now(),f)f=!1,Re("agent-first-output",e.projectId,p,{elapsedMs:Date.now()-(e.startedAt??Date.now())});for(let w of d.toString().trimEnd().split(`
13
+ `))if(w)this.log.log(`${m} ${w}`)}),u.stderr?.on("data",(d)=>{if(e.lastStdoutAt=Date.now(),f)f=!1,Re("agent-first-output",e.projectId,p,{elapsedMs:Date.now()-(e.startedAt??Date.now()),stream:"stderr"});for(let w of d.toString().trimEnd().split(`
14
+ `))if(w)this.log.error(`${m} ${w}`)});try{return await this.waitForHealth(e,Io),Re("agent-health-ready",e.projectId,p,{elapsedMs:Date.now()-(e.startedAt??Date.now()),agentPort:e.agentPort}),e.status="running",e.lastUsedAt=Date.now(),this.armIdleTimer(e),this.armGraceTimer(e),e}catch(d){e.status="error",e.lastError=d?.message??String(d),this.killProcessGroup(e,"SIGTERM"),this.killViteWatch(e);try{u.kill("SIGTERM")}catch{}throw this.releasePort(e.agentPort),e.agentPort=0,e.apiServerPort=0,d}}resolveLimits(){if(this.resolvedLimits===void 0)this.resolvedLimits=Tt(this.opts.env??process.env);return this.resolvedLimits}applyPostSpawnLimits(e,t,r){let o=e.pid;if(!o)return;if(process.platform==="linux"){if(r)return;if(xt({pid:o,limits:t,logger:this.log}))return}else if(process.platform==="win32"){if(It({pid:o,limits:t,logger:this.log}))return}e.limitWatchdog=new We({pid:o,ceilingMB:t.memoryMB,logger:this.log,onBreach:(n)=>this.handleLimitBreach(e,n,t.memoryMB)}),e.limitWatchdog.start()}handleLimitBreach(e,t,r){if(this.log.warn(`[WorkerRuntimeManager] runtime ${e.projectId} exceeded memory ceiling (${t}MB > ${r}MB) — restarting`),e.lastError=`memory ceiling exceeded (${t}MB > ${r}MB)`,e.limitWatchdog)e.limitWatchdog.stop(),e.limitWatchdog=null;this.killProcessGroup(e,"SIGTERM"),this.killViteWatch(e);try{e.proc?.kill("SIGTERM")}catch{}let o=setTimeout(()=>{this.killProcessGroup(e,"SIGKILL"),this.killViteWatch(e)},5000);try{o.unref?.()}catch{}}killProcessGroup(e,t){if(!e.pid)return;if(process.platform==="win32"){try{Y("taskkill",["/F","/T","/PID",String(e.pid)],{stdio:"ignore",windowsHide:!0})}catch{}return}try{process.kill(-e.pid,t)}catch{}}armGraceTimer(e){if(e.graceTimer)clearTimeout(e.graceTimer),e.graceTimer=null;e.graceTimer=setTimeout(()=>{if(e.graceTimer=null,e.consecutiveFailures>0)e.consecutiveFailures=0;e.breakerTrips=0,e.restartTimestamps=[]},Eo);try{e.graceTimer.unref?.()}catch{}}buildEnv(e,t){let r=e.spawnConfig,o={...this.opts.env??process.env,PROJECT_ID:e.projectId,PORT:String(e.agentPort),API_SERVER_PORT:String(e.apiServerPort),SKILL_SERVER_PORT:String(e.apiServerPort),WORKSPACE_API_PORT_BASE:String(e.agentPort+So),NODE_ENV:"production",STARTUP_TIME:String(Date.now()),SHOGO_CLOUD_URL:r.cloudUrl,SHOGO_API_URL:r.cloudUrl,SHOGO_API_KEY:r.apiKey,RUNTIME_AUTH_SECRET:Ft(e.projectId),WEBHOOK_TOKEN:Mo(e.projectId)};if(r.projectDir)o.PROJECT_DIR=r.projectDir,o.WORKSPACE_DIR=r.projectDir;if(this.shouldDeferToCloudSync(e.projectId))o.SHOGO_CLOUD_SYNC="1";if(r.aiProxyUrl)o.AI_PROXY_URL=r.aiProxyUrl;if(r.aiProxyToken)o.AI_PROXY_TOKEN=r.aiProxyToken;if(r.techStackId)o.TECH_STACK_ID=r.techStackId;if(r.name)o.AGENT_NAME=r.name;if(r.workspaceId)o.WORKSPACE_ID=r.workspaceId;if(!o.TREE_SITTER_WASM_DIR)o.TREE_SITTER_WASM_DIR=z(yo(t),"tree-sitter-wasm");if(r.extraEnv)Object.assign(o,r.extraEnv);let n=this.resolveLimits();if(n)$t(o,n.memoryMB);return o}killViteWatch(e){let t=e.spawnConfig.projectDir;if(!t)return;try{Mt(t,{logger:this.log})}catch(r){this.log.warn(`[WorkerRuntimeManager] Vite watch cleanup for ${e.projectId} failed: ${r?.message??r}`)}}sweepOrphanedViteWatches(){let e=this.opts.autoPull?.projectsDir;if(!e)return;let t;try{t=Bt(e,{withFileTypes:!0})}catch{return}for(let r of t){if(!r.isDirectory()||this.runtimes.has(r.name))continue;let o=z(e,r.name);try{let n=Mt(o,{logger:this.log});if(n)this.log.log(`[WorkerRuntimeManager] Reaped orphaned Vite watch for ${r.name} (pid=${n.pid})`)}catch(n){this.log.warn(`[WorkerRuntimeManager] Vite watch startup sweep failed for ${r.name}: ${n?.message??n}`)}}}resolveCwd(e){let t=e.spawnConfig;if(t.projectDir&&Fe(t.projectDir))return t.projectDir;let r=this.opts.runtimeWorkDir??z(wo(),"shogo-runtime",e.projectId);return Ot(r,{recursive:!0}),r}handleExit(e,t,r){let o=r===null&&t===0;if(this.log.log(`[WorkerRuntimeManager] runtime ${e.projectId} exited (code=${t}, signal=${r})`),e.proc=null,e.graceTimer)clearTimeout(e.graceTimer),e.graceTimer=null;if(e.limitWatchdog)e.limitWatchdog.stop(),e.limitWatchdog=null;if(this.killViteWatch(e),e.status==="stopping"||this.stopped){e.status="stopped",e.pid=null,this.releasePort(e.agentPort),e.agentPort=0,e.apiServerPort=0;return}if(o){e.status="stopped",e.pid=null,this.releasePort(e.agentPort),e.agentPort=0,e.apiServerPort=0,this.runtimes.delete(e.projectId),this.notifyGone(e.projectId,{reason:"exited",code:t,signal:r});return}this.killProcessGroup(e,"SIGKILL"),this.killViteWatch(e),e.pid=null;let n=Date.now(),i=n-e.lastFailureAt<=Ut;e.consecutiveFailures=i?e.consecutiveFailures+1:1,e.lastFailureAt=n,e.restarts+=1,e.lastError=`exited code=${t} signal=${r}`,e.restartTimestamps.push(n),e.restartTimestamps=e.restartTimestamps.filter((l)=>n-l<=Nt);let s=e.consecutiveFailures>=Wt,a=e.restartTimestamps.length>=Po;if(s||a){e.status="failed",e.breakerTrips+=1;let l=this.breakerCooldownMs(e.breakerTrips),u=s?`${e.consecutiveFailures} consecutive non-clean exits within ${Math.round(Ut/1000)}s`:`${e.restartTimestamps.length} non-clean exits within the last ${Math.round(Nt/60000)}m (slow-bleed loop — gaps between crashes kept clearing the tight-loop window)`;if(e.lastError=`Circuit breaker tripped (trip #${e.breakerTrips}): ${u} (last: code=${t} signal=${r}). Most recent on macOS is jetsam OOM (signal=SIGKILL with code=null); the previous incarnation's vite/tsserver/preview-manager children were reaped to prevent further RSS growth. Will auto-retry in ~${Math.round(l/1000)}s; call resetFailure(${e.projectId}) to retry immediately, or stop(${e.projectId}) to give up.`,this.releasePort(e.agentPort),e.agentPort=0,e.apiServerPort=0,e.restartTimer)clearTimeout(e.restartTimer),e.restartTimer=null;if(e.idleTimer)clearTimeout(e.idleTimer),e.idleTimer=null;this.log.error(`[WorkerRuntimeManager] ${e.lastError}`),this.notifyGone(e.projectId,{reason:"failed",code:t,signal:r});return}let c=this.restartBackoffMs(e.restarts);if(e.status="restarting",this.log.warn(`[WorkerRuntimeManager] restarting ${e.projectId} in ${Math.round(c/1000)}s (restart #${e.restarts}, consecutive failures ${e.consecutiveFailures}/${Wt})`),e.restartTimer)clearTimeout(e.restartTimer);e.restartTimer=setTimeout(()=>{if(e.restartTimer=null,e.status==="failed"||this.stopped)return;e.startPromise=this.doStart(e).then((l)=>(e.startPromise=null,l)).catch((l)=>(e.startPromise=null,this.log.error(`[WorkerRuntimeManager] restart of ${e.projectId} failed: ${l?.message??l}`),e))},c);try{e.restartTimer.unref?.()}catch{}}restartBackoffMs(e){let t=Math.min(ko*Math.pow(2,Math.max(0,e-1)),Ro),r=t*0.2*Math.random();return t+r}armIdleTimer(e){if(e.idleTimer)clearTimeout(e.idleTimer),e.idleTimer=null;let t=this.opts.idleMs??bo;if(!Number.isFinite(t)||t<=0)return;e.idleTimer=setTimeout(()=>{let r=Date.now()-e.lastUsedAt;if(r<t){this.armIdleTimer(e);return}this.log.log(`[WorkerRuntimeManager] idle-evicting ${e.projectId} after ${Math.round(r/1000)}s`),this.stop(e.projectId,"SIGTERM","idle-evict").catch((o)=>{this.log.warn(`[WorkerRuntimeManager] idle stop failed: ${o?.message??o}`)})},t)}async allocatePort(){let e=Ge-Le,t=Math.min(e,50);for(let r=0;r<t;r++){let o=Le+Math.floor(Math.random()*e);if(o+ke-1>Ge)continue;let n=!0;for(let a=0;a<ke;a++)if(this.usedPorts.has(o+a)){n=!1;break}if(!n)continue;let i=await this.isPortListening(o),s=await this.isPortListening(o+Dt);if(i||s)continue;for(let a=0;a<ke;a++)this.usedPorts.add(o+a);return o}throw Error(`Cannot allocate port in range ${Le}-${Ge} after ${t} attempts`)}releasePort(e){if(!e)return;for(let t=0;t<ke;t++)this.usedPorts.delete(e+t)}async isPortListening(e){let t=new AbortController,r=setTimeout(()=>t.abort(),250);try{return await fetch(`http://127.0.0.1:${e}/`,{method:"HEAD",signal:t.signal}),clearTimeout(r),!0}catch{return clearTimeout(r),!1}}tcpProbe(e){return new Promise((t)=>{let r=!1,o=(i)=>{if(r)return;r=!0;try{n.destroy()}catch{}t(i)},n=fo({host:"127.0.0.1",port:e});n.setTimeout(Ao),n.once("connect",()=>o(!0)),n.once("error",()=>o(!1)),n.once("timeout",()=>o(!1))})}async waitForHealth(e,t){let{agentPort:r,proc:o}=e;if(!o)throw Error(`waitForHealth: slot ${e.projectId} has no spawned process`);let n=Date.now(),i=n+t,s=null,a=!1,c=0,l=0,u=0,p=0,m=n;while(Date.now()<i){if(p++,o.exitCode!==null||o.signalCode!=null||o.killed)throw Error(`agent-runtime exited (code=${o.exitCode}, signal=${o.signalCode}) before becoming healthy on port ${r}`);l++;let f=new AbortController,d=setTimeout(()=>f.abort(),1500);try{let g=await fetch(`http://127.0.0.1:${r}/health`,{method:"GET",signal:f.signal});if(clearTimeout(d),g.ok){this.log.log(`[WorkerRuntimeManager] /health ready for ${e.projectId} on port ${r} (HTTP ${g.status} after ${Date.now()-n}ms, ${p} iter, ${l} http)`);return}s=`HTTP /health returned ${g.status}`}catch(g){clearTimeout(d);let v=g?.name??"Error",Z=g?.code??g?.cause?.code;s=`HTTP /health failed: ${v}${Z?`(${Z})`:""}: ${g?.message??g}`}if(u++,a=await this.tcpProbe(r),a){c=Date.now();let g=Date.now()-e.lastStdoutAt;if(g>=jt)throw Error(`agent-runtime wedged on port ${r}: TCP listening but stdout silent for ${g}ms (> ${jt}ms window); ${l} /health attempts, last error: ${s??"n/a"}`)}let w=Date.now();if(w-m>=Co){let g=w-n,v=w-e.lastStdoutAt,Z=c>0?w-c:null;this.log.log(`[WorkerRuntimeManager] still waiting for /health on ${e.projectId} port ${r} (${(g/1000).toFixed(1)}s elapsed, tcpListening=${a}${Z!=null?`(${Z}ms ago)`:""}, lastStdout=${v}ms ago, ${l} http, ${u} tcp, lastError=${s??"n/a"})`),m=w}await new Promise((g)=>setTimeout(g,xo))}if(o.exitCode!==null||o.signalCode!=null||o.killed)throw Error(`agent-runtime exited (code=${o.exitCode}, signal=${o.signalCode}) before becoming healthy on port ${r}`);throw Error(`Timeout waiting for agent-runtime /health on port ${r} after ${p} iter (httpAttempts=${l}, tcpAttempts=${u}, tcpListening=${a}, lastError=${s??"n/a"})`)}async waitForExit(e,t){if(e.exitCode!==null||e.signalCode!=null||e.killed)return;await new Promise((r)=>{let o=setTimeout(()=>{try{e.kill("SIGKILL")}catch{}r()},t);e.once("exit",()=>{clearTimeout(o),r()})})}snapshot(e){return{projectId:e.projectId,status:e.status,agentPort:e.agentPort||void 0,apiServerPort:e.apiServerPort||void 0,pid:e.proc?.pid,startedAt:e.startedAt||void 0,lastUsedAt:e.lastUsedAt,restarts:e.restarts,lastError:e.lastError}}}import{hostname as Je,platform as Ve,arch as Ye}from"node:os";function J(){if(typeof __SHOGO_WORKER_VERSION__==="string"&&__SHOGO_WORKER_VERSION__.length>0)return`shogo-cli/${__SHOGO_WORKER_VERSION__}`;return"shogo-cli/unknown"}class Yt extends Error{code="TUNNEL_WS_HEADERS_UNSUPPORTED";constructor(){super("Tunnel WebSocket auth requires a runtime with WebSocket header support (Bun or Node >= 21). Current runtime does not support WebSocket constructor headers.");this.name="TunnelWebSocketHeaderSupportError"}}var le=60,Pe=300,Xe=3,Do=Xe,Wo=1800000,Uo=25000,qt=1000,Jt=60000,Vt=3;class ze{opts;log;pollTimer=null;ws=null;wsIdleTimer=null;heartbeatTimer=null;stopped=!1;currentPollInterval=le;wsReconnectAttempt=0;lastHeartbeatError=null;consecutiveAuthFailures=0;consecutiveAuthSuccesses=0;serverPublishedWsUrl=null;activeAbortControllers=new Map;constructor(e){this.opts=e,this.log=e.logger??console}start(){if(!this.opts.apiKey){this.log.log("[WorkerTunnel] No API key set, skipping tunnel");return}this.stopped=!1,this.wsReconnectAttempt=0,this.currentPollInterval=le,this.log.log("[WorkerTunnel] Starting heartbeat polling to Shogo Cloud..."),this.heartbeatLoop()}stop(){if(this.stopped=!0,this.pollTimer)clearTimeout(this.pollTimer),this.pollTimer=null;this.cleanupWs(),this.log.log("[WorkerTunnel] Tunnel stopped")}isConnected(){if(this.ws!==null&&this.ws.readyState===WebSocket.OPEN)return!0;return!this.stopped&&!!this.opts.apiKey&&this.lastHeartbeatError===null&&this.pollTimer!==null}getCloudUrl(){return this.opts.cloudUrl.replace(/\/$/,"")}getWsBaseUrl(){let e=(this.opts.wsUrlOverride||process.env.SHOGO_TUNNEL_WS_URL||"").trim();if(e)return e.replace(/\/$/,"");if(this.serverPublishedWsUrl)return this.serverPublishedWsUrl.replace(/\/$/,"");return this.getCloudUrl().replace(/^http/,"ws")}buildWsUrl(){return`${this.getWsBaseUrl()}/api/instances/ws`}supportsWebSocketConstructorHeaders(e=globalThis){if(typeof e.Bun<"u"||typeof e.process?.versions?.bun==="string")return!0;let t=e.process?.versions?.node;if(t){if(parseInt(t.split(".")[0]??"0",10)>=21)return!0}return!1}createTunnelWebSocket(e,t,r=globalThis){if(!this.supportsWebSocketConstructorHeaders(r))throw new Yt;return new WebSocket(e,t)}getReconnectDelayMs(){let e=Math.min(qt*Math.pow(2,this.wsReconnectAttempt),Jt),t=e*0.2*Math.random();return e+t}async collectMetadata(){let e={hostname:Je(),os:Ve(),arch:Ye(),uptime:process.uptime(),protocolVersion:Vt,tunnelStatus:this.ws?.readyState===WebSocket.OPEN?"connected":"polling",kind:this.opts.kind??"cli-worker"};try{let t=this.opts.resolver.getActiveProjects();e.activeProjects=t.length,e.projects=t.map((r)=>{let o=this.opts.resolver.status(r);return{projectId:r,status:o?.status??"unknown",agentPort:o?.agentPort}})}catch{e.activeProjects=0}return e}async sendHeartbeat(){let e=this.getCloudUrl(),t=await this.collectMetadata(),r=Je(),o=this.opts.name??r,n=await fetch(`${e}/api/instances/heartbeat`,{method:"POST",headers:{"Content-Type":"application/json","User-Agent":J(),"x-api-key":this.opts.apiKey,"x-shogo-kind":this.opts.kind??"cli-worker"},body:JSON.stringify({hostname:r,name:o,os:Ve(),arch:Ye(),kind:this.opts.kind??"cli-worker",metadata:t})});if(!n.ok)throw Error(`Heartbeat failed: HTTP ${n.status}`);let i=await n.json();if(typeof i.wsUrl==="string"&&i.wsUrl.length>0){if(i.wsUrl!==this.serverPublishedWsUrl)this.log.log(`[WorkerTunnel] Cloud advertised tunnel WS URL: ${i.wsUrl}`);this.serverPublishedWsUrl=i.wsUrl}return i}scheduleNextPoll(e){if(this.stopped)return;if(this.pollTimer)clearTimeout(this.pollTimer);let t=(e??this.currentPollInterval)*1000;this.pollTimer=setTimeout(()=>void this.heartbeatLoop(),t)}async heartbeatLoop(){if(this.stopped)return;if(this.ws&&this.ws.readyState===WebSocket.OPEN){this.scheduleNextPoll(this.currentPollInterval);return}try{let e=await this.sendHeartbeat(),t=e.nextPollIn||le;if(this.consecutiveAuthFailures>=Xe){if(this.consecutiveAuthSuccesses++,this.consecutiveAuthSuccesses<Do){this.currentPollInterval=Pe,this.scheduleNextPoll();return}}if(this.currentPollInterval=t,this.lastHeartbeatError)this.log.log("[WorkerTunnel] Heartbeat recovered"),this.lastHeartbeatError=null;if(this.consecutiveAuthFailures=0,this.consecutiveAuthSuccesses=0,e.wsRequested&&!this.ws){this.log.log("[WorkerTunnel] Cloud requested WebSocket — connecting..."),this.connectWs();return}}catch(e){let t=e?.message??String(e);if(/HTTP 40[13]\b/.test(t))this.consecutiveAuthFailures++,this.consecutiveAuthSuccesses=0;else this.consecutiveAuthFailures=0,this.consecutiveAuthSuccesses=0;if(t!==this.lastHeartbeatError)this.log.error(`[WorkerTunnel] Heartbeat error: ${t}`),this.lastHeartbeatError=t;if(this.consecutiveAuthFailures>=Xe){let o=`tunnel saw ${this.consecutiveAuthFailures} consecutive auth failures from Shogo Cloud`;try{this.opts.onAuthRevoked?.(o)}catch(n){this.log.warn(`[WorkerTunnel] onAuthRevoked threw: ${n?.message??n}`)}if(this.currentPollInterval!==Pe)this.log.warn(`[WorkerTunnel] ${this.consecutiveAuthFailures} consecutive auth failures — `+`backing off to ${Pe}s. Re-authenticate with \`shogo login\`.`);this.currentPollInterval=Pe}else this.currentPollInterval=le}this.scheduleNextPoll()}async handleRequest(e){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)return;this.resetWsIdleTimer();let t=new AbortController;this.activeAbortControllers.set(e.requestId,t);try{let r=await this.resolveLocalUrl(e.path,e.projectId);if(!r){let s=this.opts.resolver.describeRejection?this.opts.resolver.describeRejection(e.path,e.projectId):{code:"NO_LOCAL_RUNTIME",message:`no local runtime available for path: ${e.path}`};this.sendFrame({type:"response",requestId:e.requestId,status:502,headers:{"content-type":"application/json"},body:JSON.stringify({code:s.code,message:s.message,path:e.path})});return}let o={...e.headers??{}};if(e.projectId&&(e.path.startsWith("/agent/")||e.path==="/agent")){let s=await this.opts.resolver.deriveRuntimeToken(e.projectId);if(s)o["x-runtime-token"]=s}let n={method:e.method,headers:o,signal:t.signal};if(e.body&&e.method!=="GET"&&e.method!=="HEAD")n.body=e.body;let i=await fetch(r,n);if(e.stream){let s=i.body?.getReader();if(!s){this.sendFrame({type:"stream-error",requestId:e.requestId,error:"No response body for stream"});return}let a=new TextDecoder;try{while(!0){let{done:c,value:l}=await s.read();if(c)break;if(this.ws?.readyState!==WebSocket.OPEN)break;this.sendFrame({type:"stream-chunk",requestId:e.requestId,data:a.decode(l,{stream:!0})})}if(this.ws?.readyState===WebSocket.OPEN)this.sendFrame({type:"stream-end",requestId:e.requestId})}catch(c){if(c?.name!=="AbortError"&&this.ws?.readyState===WebSocket.OPEN)this.sendFrame({type:"stream-error",requestId:e.requestId,error:c?.message??String(c)})}}else{let s=await i.text(),a={};i.headers.forEach((c,l)=>{a[l]=c}),this.sendFrame({type:"response",requestId:e.requestId,status:i.status,headers:a,body:s})}}catch(r){if(r?.name==="AbortError")return;if(this.ws?.readyState!==WebSocket.OPEN)return;let o=e.stream?{type:"stream-error",requestId:e.requestId,error:r?.message??String(r)}:{type:"response",requestId:e.requestId,status:502,body:JSON.stringify({error:r?.message??String(r)})};this.sendFrame(o)}finally{this.activeAbortControllers.delete(e.requestId)}}async resolveLocalUrl(e,t){return this.opts.resolver.resolveLocalUrl(e,t)}sendFrame(e){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)return;try{this.ws.send(JSON.stringify(e))}catch(t){this.log.warn(`[WorkerTunnel] Frame send failed: ${t?.message??t}`)}}resetWsIdleTimer(){if(this.wsIdleTimer)clearTimeout(this.wsIdleTimer);this.wsIdleTimer=setTimeout(()=>{if(this.ws&&this.ws.readyState===WebSocket.OPEN){this.log.log("[WorkerTunnel] WebSocket idle timeout — closing, returning to polling");try{this.ws.close(1000,"Idle timeout")}catch{}}},Wo)}startWsHeartbeat(){if(this.heartbeatTimer)clearInterval(this.heartbeatTimer);this.heartbeatTimer=setInterval(async()=>{if(!this.ws||this.ws.readyState!==WebSocket.OPEN)return;try{let e=await this.collectMetadata();this.sendFrame({type:"heartbeat",metadata:e})}catch{}},Uo)}connectWs(){if(this.stopped||this.ws)return;let e=this.buildWsUrl(),t=Je(),r=Ve(),o=Ye(),n=this.opts.name??t;this.log.log(`[WorkerTunnel] Opening WebSocket to ${e} (hostname=${t})`);let i={headers:{Authorization:`Bearer ${this.opts.apiKey}`,"User-Agent":J(),"x-shogo-hostname":t,"x-shogo-name":n,"x-shogo-os":r,"x-shogo-arch":o,"x-shogo-kind":this.opts.kind??"cli-worker"}},s;try{s=this.createTunnelWebSocket(e,i)}catch(a){this.log.error(`[WorkerTunnel] WebSocket creation failed: ${a?.message??a}`),this.ws=null,this.scheduleNextPoll(5);return}this.ws=s,s.onopen=()=>{this.log.log("[WorkerTunnel] WebSocket connected — session active"),this.wsReconnectAttempt=0,this.startWsHeartbeat(),this.resetWsIdleTimer()},s.onmessage=(a)=>{let c;try{let l=typeof a.data==="string"?a.data:a.data.toString();c=JSON.parse(l)}catch{return}if(c.type==="ping"){this.sendFrame({type:"pong"}),this.resetWsIdleTimer();return}if(c.type==="cancel"){let l=this.activeAbortControllers.get(c.requestId);if(l)l.abort();return}if(c.type==="request"){this.handleRequest(c).catch((l)=>{this.log.error(`[WorkerTunnel] Error handling request: ${l?.message??l}`)});return}},s.onclose=(a)=>{if(this.log.log(`[WorkerTunnel] WebSocket closed: code=${a.code} reason=${a.reason||"none"}`),this.cleanupWs(),this.stopped)return;if(a.code===1000||a.code===4000)this.scheduleNextPoll(this.currentPollInterval);else{this.wsReconnectAttempt++;let c=this.getReconnectDelayMs();this.log.log(`[WorkerTunnel] Reconnecting in ${Math.round(c/1000)}s (attempt ${this.wsReconnectAttempt})`),this.scheduleNextPoll(Math.ceil(c/1000))}},s.onerror=(a)=>{this.log.error("[WorkerTunnel] WebSocket error:",a?.message??"unknown")}}cleanupWs(){if(this.heartbeatTimer)clearInterval(this.heartbeatTimer),this.heartbeatTimer=null;if(this.wsIdleTimer)clearTimeout(this.wsIdleTimer),this.wsIdleTimer=null;for(let[,e]of this.activeAbortControllers)try{e.abort()}catch{}this.activeAbortControllers.clear(),this.ws=null}_testing(){let e=this;return{sendHeartbeat:()=>e.sendHeartbeat(),heartbeatLoop:()=>e.heartbeatLoop(),connectWs:()=>e.connectWs(),cleanupWs:()=>e.cleanupWs(),handleRequest:(t)=>e.handleRequest(t),installFakeWs:(t)=>{e.ws=t},getCloudUrl:()=>e.getCloudUrl(),getWsBaseUrl:()=>e.getWsBaseUrl(),buildWsUrl:()=>e.buildWsUrl(),getReconnectDelayMs:()=>e.getReconnectDelayMs(),supportsWebSocketConstructorHeaders:(t)=>e.supportsWebSocketConstructorHeaders(t),createTunnelWebSocket:(t,r,o)=>e.createTunnelWebSocket(t,r,o),DEFAULT_POLL_INTERVAL_S:le,BACKOFF_BASE_MS:qt,BACKOFF_MAX_MS:Jt,TUNNEL_PROTOCOL_VERSION:Vt,get currentPollInterval(){return e.currentPollInterval},set currentPollInterval(t){e.currentPollInterval=t},get wsReconnectAttempt(){return e.wsReconnectAttempt},set wsReconnectAttempt(t){e.wsReconnectAttempt=t},get ws(){return e.ws},get stopped(){return e.stopped},get serverPublishedWsUrl(){return e.serverPublishedWsUrl},set serverPublishedWsUrl(t){e.serverPublishedWsUrl=t}}}}async function Xt(e){let t=M({name:e.name,workerDir:e.workerDir,apiKey:e.apiKey,cloudUrl:e.cloudUrl,port:e.port?parseInt(e.port,10):void 0,projectsDir:e.projectsDir}),r=wt(e.proxy);if(!e.foreground)return jo({cfg:t,proxy:r,flags:e});let o=G({flag:e.runtimeBin});if(!o)console.error(h.red(q({flag:e.runtimeBin}))),process.exit(1);if(De(process.env,r),e.debug){if(!await vt(bt({cloudUrl:t.cloudUrl,apiKey:t.apiKey,workerDir:t.workerDir,proxy:r})))process.exit(1)}let n=!e.noAutoPull,i=!e.noGit;if(console.log(h.bold(`
15
+ Shogo Worker — Starting`)),console.log(h.dim(" name ")+t.name),console.log(h.dim(" worker-dir ")+t.workerDir),console.log(h.dim(" cloud ")+t.cloudUrl),console.log(h.dim(" runtime ")+`${o.path} ${h.dim(`(via ${o.source})`)}`),console.log(h.dim(" auto-pull ")+(n?h.green("on")+h.dim(` → ${t.projectsDir}`):h.yellow("off"))),console.log(h.dim(" sync mode ")+(i?h.green("git")+h.dim(" (falls back to file transport if git is missing)"):h.yellow("files-only"))),e.project)console.log(h.dim(" project ")+e.project);if(r)console.log(h.dim(" proxy ")+`${r.url} ${h.dim(`(from ${r.source})`)}`);console.log("");let s=`${t.cloudUrl.replace(/\/+$/,"")}/api/ai/v1`,a={cloudUrl:t.cloudUrl,apiKey:t.apiKey,aiProxyUrl:s,aiProxyToken:t.apiKey,extraEnv:{SHOGO_PERMISSION_SCOPE:"cloud"}},c=new qe({runtimeBin:e.runtimeBin,defaultSpawnConfig:a,autoPull:{enabled:n,projectsDir:t.projectsDir,watch:!0,useGit:i}});if(!c.resolveBinary())console.error(h.red(q({flag:e.runtimeBin}))),process.exit(1);let l=new ze({apiKey:t.apiKey,cloudUrl:t.cloudUrl,name:t.name,kind:"cli-worker",resolver:c,onAuthRevoked:(m)=>{console.error(h.red(`✗ Cloud auth revoked: ${m}`)),console.error(h.dim(" Run `shogo login` to re-authenticate; this worker will keep polling at the auth-failure backoff until then."))}}),u=!1,p=async(m)=>{if(u)return;u=!0,console.log(h.dim(`
16
+ Received ${m} — shutting down...`));try{l.stop()}catch{}try{await c.stopAll()}catch(f){console.warn(h.yellow(`stopAll: ${f?.message??f}`))}process.exit(0)};process.once("SIGINT",()=>void p("SIGINT")),process.once("SIGTERM",()=>void p("SIGTERM")),process.once("SIGHUP",()=>void p("SIGHUP")),l.start(),console.log(h.green("✓ Worker running. Ctrl-C to stop.")),await new Promise(()=>{})}function jo({cfg:e,proxy:t,flags:r}){let{entry:o,runner:n}=Ho(),i=De({...process.env,SHOGO_API_KEY:e.apiKey,SHOGO_CLOUD_URL:e.cloudUrl,SHOGO_INSTANCE_NAME:e.name,SHOGO_WORKER_DIR:e.workerDir,SHOGO_LOCAL_MODE:"true",PORT:String(e.port)},t),s=Fo(r),{pid:a}=gt({entry:o,runner:n,env:{...i,SHOGO_DETACHED_ARGS:s.join(" ")},cwd:e.workerDir,detach:!0,inheritStdio:!1});console.log(h.bold(`
17
+ Shogo Worker — Started`)),console.log(h.dim(" pid: ")+a),console.log(h.dim(" name: ")+e.name),console.log(h.dim(" logs: ")+"~/.shogo/logs/worker.log"),console.log(h.dim(" stop: ")+"shogo worker stop")}function Fo(e){let t=["worker","start","--foreground"];if(e.name)t.push("--name",e.name);if(e.workerDir)t.push("--worker-dir",e.workerDir);if(e.apiKey)t.push("--api-key",e.apiKey);if(e.cloudUrl)t.push("--cloud-url",e.cloudUrl);if(e.port)t.push("--port",e.port);if(e.proxy)t.push("--proxy",e.proxy);if(e.project)t.push("--project",e.project);if(e.runtimeBin)t.push("--runtime-bin",e.runtimeBin);if(e.debug)t.push("--debug");if(e.noAutoPull)t.push("--no-auto-pull");if(e.projectsDir)t.push("--projects-dir",e.projectsDir);if(e.noGit)t.push("--no-git");return t}function Ho(){let e=process.execPath,t=/\bbun(?:-[^/\\]*)?$/.test(e)||typeof globalThis.Bun<"u",r=process.argv[1];if(r&&No(r)){let n=/\.ts$/.test(r);return{entry:r,runner:t?"bun":n?"tsx":"node"}}return{entry:new URL("../../bin/shogo.mjs",import.meta.url).pathname,runner:t?"bun":"node"}}import zt from"picocolors";async function Qt(){let{killedPid:e}=ht("SIGTERM");if(e===null){console.log(zt.dim("No worker running."));return}console.log(zt.green(`✓ Worker stopped (pid=${e}).`))}import N from"picocolors";async function Zt(){let e=he();if(!e){console.log(N.yellow("● stopped")+N.dim(" (no pid file)"));return}if(!fe(e)){console.log(N.red("● dead")+N.dim(` (stale pid ${e})`));return}let t=L();if(console.log(N.green("● running")+N.dim(` (pid ${e})`)),t.name)console.log(N.dim(" name: ")+t.name);if(t.cloudUrl)console.log(N.dim(" cloud: ")+t.cloudUrl);if(t.workerDir)console.log(N.dim(" dir: ")+t.workerDir)}D();import{existsSync as Lo,statSync as Go}from"node:fs";import er from"picocolors";async function tr(e){let t=e.err?me:de;if(!Lo(t)){console.log(er.dim("No logs yet."));return}let r=e.follow?["-F","-n","200",t]:["-n","200",t];if(T("tail",r,{stdio:"inherit"}).on("exit",(n)=>process.exit(n??0)),!e.follow){if(Go(t).size===0)console.log(er.dim("(log file is empty)"))}}import x from"picocolors";D();import{hostname as Bo,platform as Ko,arch as qo}from"node:os";import B from"picocolors";class A extends Error{kind;constructor(e,t){super(e);this.kind=t;this.name="CloudLoginError"}}async function rr(e){let t=e.cloudUrl.replace(/\/$/,""),r=e.client??"cli",o=e.deviceName??Bo(),n=e.devicePlatform??`${Ko()}-${qo()}`,i=e.appVersion??Yo(),s=e.log??((g)=>console.log(g)),a=e.fetchImpl??fetch,c=await a(`${t}/api/cli/login/start`,{method:"POST",headers:{"content-type":"application/json","user-agent":J()},body:JSON.stringify({deviceId:e.deviceId,deviceName:o,devicePlatform:n,deviceAppVersion:i,workspaceId:e.workspaceId,client:r}),signal:AbortSignal.timeout(1e4)}).catch((g)=>{throw new A(`Cannot reach Shogo Cloud at ${t}: ${g?.message??g}`,"transport")});if(!c.ok){let g=await c.text().catch(()=>"");throw new A(`Cloud rejected /api/cli/login/start (HTTP ${c.status}): ${g||"no body"}`,"transport")}let l=await c.json().catch(()=>({}));if(!l?.ok||!l.state||!l.authUrl)throw new A(`Cloud returned a malformed start response: ${l?.error??JSON.stringify(l)}`,"transport");let u=Jo(e.pollIntervalMs??l.pollIntervalMs),p=e.timeoutMs??l.expiresInMs,m=Date.now()+p;if(s(""),s(B.bold("Sign in to Shogo Cloud")),s(B.dim(" cloud: ")+t),s(B.dim(" device: ")+`${o} (${n})`),s(B.dim(" user code: ")+B.cyan(l.userCode)),s(""),s(" Open this URL in your browser to approve:"),s(" "+B.cyan(l.authUrl)),s(""),e.openBrowser!==!1)if(typeof e.openBrowser==="function")try{await e.openBrowser(l.authUrl)}catch{}else Xo(l.authUrl).catch(()=>{});s(B.dim("Waiting for approval..."));let f=e.installSignalHandlers??!0,d=()=>{w.abort()},w=new AbortController;if(e.abortSignal)if(e.abortSignal.aborted)w.abort();else e.abortSignal.addEventListener("abort",()=>w.abort(),{once:!0});if(f)process.once("SIGINT",d),process.once("SIGTERM",d);try{while(!0){if(w.signal.aborted)throw new A("Aborted by caller.","cancelled");if(Date.now()>=m)throw new A(`Timed out after ${Math.round(p/1000)}s waiting for approval.`,"timeout");let g=await a(`${t}/api/cli/login/poll?state=${encodeURIComponent(l.state)}`,{method:"GET",headers:{accept:"application/json","user-agent":J()},signal:AbortSignal.timeout(1e4)}).catch((v)=>(s(B.dim(` (poll error: ${v?.message??v} — retrying)`)),null));if(g&&g.ok){let v=await g.json().catch(()=>({}));if(v?.status==="approved"&&v.key)return{key:v.key,email:v.email??null,workspace:v.workspace??null,deviceId:v.deviceId??e.deviceId,workspaces:v.workspaces??null};if(v?.status==="denied")throw new A("Sign-in was denied in the browser.","denied");if(v?.status==="expired")throw new A("Sign-in request expired before it was approved.","expired")}await Vo(u,w.signal)}}finally{if(f)process.removeListener("SIGINT",d),process.removeListener("SIGTERM",d)}}function Jo(e){if(!Number.isFinite(e)||e<=0)return 2000;return Math.min(Math.max(e,1000),1e4)}function Vo(e,t){return new Promise((r,o)=>{if(t?.aborted)return o(new A("Aborted by caller.","cancelled"));let n=setTimeout(()=>{t?.removeEventListener("abort",i),r()},e),i=()=>{clearTimeout(n),o(new A("Aborted by caller.","cancelled"))};t?.addEventListener("abort",i,{once:!0})})}function Yo(){return J()}function Xo(e){return new Promise((t)=>{let r=process.platform==="darwin"?"open":process.platform==="win32"?"cmd":"xdg-open",o=process.platform==="win32"?["/c","start",'""',e]:[e];try{let n=T(r,o,{stdio:"ignore",detached:!0});n.on("error",()=>t()),n.unref(),t()}catch{t()}})}import{readFileSync as zo,writeFileSync as Qo,existsSync as Zo,chmodSync as en}from"node:fs";import{randomUUID as tn}from"node:crypto";function or(){if(Zo(ee)){let t=zo(ee,"utf-8").trim();if(t)return t}H();let e=tn();return Qo(ee,e,{mode:384}),en(ee,384),e}var rn="https://studio.shogo.ai";async function nr(e){let t=L(),r=(e.cloudUrl||t.cloudUrl||rn).replace(/\/$/,""),o=e.apiKey||process.env.SHOGO_API_KEY;if(o){await on({key:o,cloudUrl:r,cfg:t,name:e.name});return}let n=or(),i;try{i=await rr({cloudUrl:r,deviceId:n,deviceName:e.name,workspaceId:e.workspace,openBrowser:!e.noBrowser})}catch(s){if(s instanceof A){if(console.error(x.red(`✗ ${s.message}`)),s.kind==="transport")console.error(x.dim(" If your network blocks browsers, run with --api-key <key> instead."));process.exit(1)}throw s}if(t.apiKey=i.key,t.cloudUrl=r,e.name)t.name=e.name;if(ne(t),console.log(""),console.log(x.green("✓ Signed in to Shogo Cloud")),i.workspace)console.log(x.dim(" workspace: ")+i.workspace);if(i.email)console.log(x.dim(" email: ")+i.email);console.log(x.dim(" saved to: ")+"~/.shogo/config.json"),console.log(""),console.log(x.dim(" next: ")+"shogo worker start")}async function on({key:e,cloudUrl:t,cfg:r,name:o}){if(!/^shogo_sk_/.test(e))console.error(x.red('✗ API key should start with "shogo_sk_". Copy it verbatim from the API Keys page.')),process.exit(1);let n;try{let i=await fetch(`${t}/api/api-keys/validate`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({key:e}),signal:AbortSignal.timeout(1e4)});if(n=await i.json().catch(()=>({})),!i.ok||!n?.valid)console.error(x.red(`✗ ${n?.error||`Cloud rejected the key (HTTP ${i.status}).`}`)),process.exit(1)}catch(i){console.error(x.red(`✗ Cannot reach Shogo Cloud at ${t}: ${i?.message??i}`)),process.exit(1)}if(r.apiKey=e,r.cloudUrl=t,o)r.name=o;if(ne(r),console.log(x.green("✓ API key saved to ~/.shogo/config.json")),n.workspace?.name)console.log(x.dim(" workspace: ")+n.workspace.name);if(n.user?.email)console.log(x.dim(" email: ")+n.user.email);console.log(x.dim(" next: ")+"shogo worker start")}import ce from"picocolors";async function ir(){let e=L();if(Object.keys(e).length===0){console.log(ce.dim("(no config — run `shogo login` or `shogo config set`)"));return}let t={...e,apiKey:e.apiKey?`***${e.apiKey.slice(-4)}`:void 0};console.log(ce.dim(`file: ${F}`)),console.log(JSON.stringify(t,null,2))}async function sr(e,t){let r=["apiKey","cloudUrl","name","workerDir","port"];if(!r.includes(e))console.error(ce.red(`Unknown key: ${e}`)),console.error(ce.dim(`Allowed: ${r.join(", ")}`)),process.exit(1);let o=L();o[e]=e==="port"?parseInt(t,10):t,ne(o),console.log(ce.green(`✓ ${e} set`))}import b from"picocolors";D();import{createHash as nn}from"node:crypto";import{createWriteStream as sn,existsSync as ue,mkdirSync as Qe,readFileSync as cr,renameSync as ar,rmSync as Ze,writeFileSync as an}from"node:fs";import{tmpdir as ln}from"node:os";import{dirname as cn,join as Te}from"node:path";import{Readable as un}from"node:stream";import{pipeline as pn}from"node:stream/promises";var dn="https://github.com/shogo-labs/shogo-ai/releases/download";function ur(){let e=process.platform,t=process.arch,r;if(e==="darwin")r="darwin";else if(e==="linux")r="linux";else if(e==="win32")r="windows";else throw Error(`Unsupported platform: ${e}`);let o;if(t==="arm64")o="arm64";else if(t==="x64")o="x64";else throw Error(`Unsupported arch: ${t} (need arm64 or x64)`);return`${r}-${o}`}function $e(){if(!ue(oe))return null;try{let e=cr(oe,"utf-8");return JSON.parse(e)}catch{return null}}async function mn(e,t){let r=t.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+)\/releases\/download/);if(!r)throw Error(`Cannot auto-resolve latest version from non-GitHub baseUrl '${t}'. Pass --version explicitly.`);let o=r[1],n=r[2];if(e==="stable"){let u=`https://api.github.com/repos/${o}/${n}/releases/latest`,p=await fetch(u,{headers:{Accept:"application/vnd.github+json"}});if(!p.ok)throw Error(`GitHub API ${p.status} for ${u}`);let m=await p.json();if(!m.tag_name)throw Error("GitHub API did not return tag_name");return lr(m.tag_name)}let i=`https://api.github.com/repos/${o}/${n}/releases?per_page=30`,s=await fetch(i,{headers:{Accept:"application/vnd.github+json"}});if(!s.ok)throw Error(`GitHub API ${s.status} for ${i}`);let a=await s.json(),c=e==="beta"?"-beta":"-nightly",l=a.find((u)=>u.prerelease&&/^v\d+\.\d+\.\d+-/.test(u.tag_name)&&u.tag_name.includes(c));if(!l)throw Error(`No ${e} runtime release found`);return lr(l.tag_name)}function lr(e){if(!/^v\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/.test(e))throw Error(`Unexpected app tag '${e}' (expected vX.Y.Z[-prerelease])`);return e.slice(1)}function gn(e,t,r){let o=`shogo-agent-runtime-${t}.tar.gz`,n=`v${e}`,i=`${r.replace(/\/$/,"")}/${n}/${o}`;return{tarball:i,sha256:`${i}.sha256`,assetName:o}}async function hn(e,t){let r=await fetch(e,{redirect:"follow"});if(!r.ok)throw Error(`Download failed: HTTP ${r.status} for ${e}`);if(!r.body)throw Error(`Download failed: empty body for ${e}`);await pn(un.fromWeb(r.body),sn(t))}async function fn(e){let t=await fetch(e,{redirect:"follow"});if(!t.ok)throw Error(`SHA256 sidecar fetch failed: HTTP ${t.status} for ${e}`);let o=(await t.text()).trim().split(/\s+/)[0];if(!/^[0-9a-f]{64}$/i.test(o))throw Error(`SHA256 sidecar at ${e} did not contain a 64-char hex digest`);return o.toLowerCase()}function wn(e){let t=cr(e);return nn("sha256").update(t).digest("hex")}async function yn(e,t){await new Promise((r,o)=>{let n=T("tar",["-xzf",e,"-C",t],{stdio:["ignore","pipe","pipe"]}),i="";n.stderr.on("data",(s)=>{i+=s.toString()}),n.on("error",o),n.on("exit",(s)=>{if(s===0)r();else o(Error(`tar exited ${s}: ${i.trim()}`))})})}function Sn(e,t){nt();let r=cn(t);if(!ue(r))Qe(r,{recursive:!0,mode:448});let o=`${t}.next`;if(ue(o))Ze(o,{force:!0});ar(e,o);try{if(process.platform!=="win32"){let{chmodSync:n}=Ce("node:fs");n(o,493)}}catch{}if(ue(t))Ze(t,{force:!0});ar(o,t)}async function pr(e={}){let t=e.logger??console,r=e.channel??"stable",o=e.baseUrl??process.env.SHOGO_RUNTIME_RELEASES_URL??dn,n=e.target??ur(),i=e.version;if(!i)t.log(`[runtime install] Resolving latest ${r} version...`),i=await mn(r,o),t.log(`[runtime install] Latest ${r} = ${i}`);let s=$e();if(s&&s.version===i&&s.target===n&&!e.force)return t.log(`[runtime install] ${i} (${n}) already installed at ${O} — pass --force to reinstall`),{version:i,target:n,binPath:O,source:s.source,sha256:s.sha256,channel:r};let a=gn(i,n,o);t.log(`[runtime install] Downloading ${a.tarball}`);let c=Te(ln(),`shogo-runtime-install-${process.pid}-${Date.now()}`);Qe(c,{recursive:!0});try{let l=Te(c,a.assetName);await hn(a.tarball,l),t.log("[runtime install] Verifying SHA-256...");let u=await fn(a.sha256),p=wn(l);if(p!==u)throw Error(`SHA-256 mismatch for ${a.assetName}
18
18
  expected: ${u}
19
- actual: ${p}`);let m=ve(c,"extract");qe(m,{recursive:!0}),await hn(l,m);let f=ve(m,"agent-runtime");if(!ne(f))throw Error(`Tarball ${a.assetName} did not contain ./agent-runtime`);fn(f,_);let d={version:i,target:n,installedAt:new Date().toISOString(),channel:r,source:a.tarball,sha256:p};return on(Q,JSON.stringify(d,null,2)+`
20
- `,{mode:384}),t.log(`[runtime install] Installed agent-runtime ${i} (${n}) to ${_}`),{version:i,target:n,binPath:_,source:a.tarball,sha256:p,channel:r}}finally{try{Je(c,{recursive:!0,force:!0})}catch{}}}function Zt(){return{runtimeDir:z,runtimeBin:_,versionFile:Q}}async function Ve(e={}){let t=await Qt({channel:e.channel,version:e.version,baseUrl:e.baseUrl,force:e.force});console.log(),console.log(b.green("✓"),`agent-runtime ${b.bold(t.version)} installed (${t.target})`),console.log(` ${b.dim("path: ")} ${t.binPath}`),console.log(` ${b.dim("source: ")} ${t.source}`),console.log(` ${b.dim("sha256: ")} ${t.sha256}`),console.log(` ${b.dim("channel: ")} ${t.channel}`)}function er(){let e=be();if(!e){console.log(b.yellow("No agent-runtime installed.")),console.log(`Run ${b.cyan("shogo runtime install")} to download the latest.`),process.exitCode=1;return}console.log(`${b.bold("agent-runtime")} ${e.version}`),console.log(` ${b.dim("target: ")} ${e.target}`),console.log(` ${b.dim("channel: ")} ${e.channel}`),console.log(` ${b.dim("installed at:")} ${e.installedAt}`),console.log(` ${b.dim("source: ")} ${e.source}`)}function tr(){let e=F(),t=Zt();if(!e){console.log(b.yellow("agent-runtime binary not found on this machine.")),console.log(),console.log(b.dim("Default install path:"),t.runtimeBin),console.log(),console.log(H()),process.exitCode=1;return}if(console.log(e.path),process.env.SHOGO_DEBUG||process.env.VERBOSE)console.log(b.dim(` (resolved via: ${e.source})`))}async function rr(e={}){let t=be(),r=e.channel??t?.channel??"stable";if(t)console.log(`${b.dim("current:")} ${t.version} (${t.target}, ${t.channel})`);else console.log(b.dim("No existing install — installing fresh..."));await Ve({channel:r,baseUrl:e.baseUrl,force:!0})}import{existsSync as wn,mkdirSync as yn}from"node:fs";import{resolve as vn}from"node:path";import y from"picocolors";import{CloudFileTransport as bn}from"@shogo-ai/sdk/cloud-file-transport";async function or(e,t){if(!e)throw Error("projectId is required");let r=A({apiKey:t.apiKey,cloudUrl:t.cloudUrl});Qe(r.projectsDir);let o=vn(t.into??B(e,r.projectsDir));if(!wn(o))yn(o,{recursive:!0});let n=t.include?.split(",").map((a)=>a.trim()).filter(Boolean);if(console.log(y.bold(`
21
- shogo project pull ${y.cyan(e)}`)),console.log(y.dim(" cloud ")+r.cloudUrl),console.log(y.dim(" into ")+o),n?.length)console.log(y.dim(" include ")+n.join(", "));console.log("");let i=new bn({apiUrl:r.cloudUrl,apiKey:r.apiKey,projectId:e,localDir:o,include:n,onProgress:Sn()}),s=await i.downloadAll();if(kn("Pull",s),t.watch){console.log(y.bold(`
22
- Watching for local changes...`));let a=new te({rootDir:o,transport:i,onFlush:({uploaded:u,errors:p})=>{let m=u.length===1?u[0]:`${u.length} files`,f=p>0?y.red(` (${p} errors)`):"";console.log(y.dim(` ↑ ${m}${f}`))}});a.start();let c=!1,l=async(u)=>{if(c)return;c=!0,console.log(y.dim(`
23
- Received ${u} — flushing pending uploads...`));try{await a.stop()}catch(p){console.warn(y.yellow(`watcher.stop: ${p?.message??p}`))}process.exit(0)};process.once("SIGINT",()=>void l("SIGINT")),process.once("SIGTERM",()=>void l("SIGTERM")),process.once("SIGHUP",()=>void l("SIGHUP")),await new Promise(()=>{})}}function Sn(){return(e)=>{let t=e.kind==="download"?"↓":e.kind==="upload"?"↑":e.kind==="delete"?"✗":"·",r=e.total>0?` ${e.index+1}/${e.total}`:"",o=e.bytes!=null?y.dim(` (${Rn(e.bytes)})`):"";console.log(y.dim(` ${t}${r} ${e.path}${o}`))}}function kn(e,t){let r=t.errors.length===0,o=r?y.green(`✓ ${e} complete`):y.red(`✗ ${e} completed with errors`);if(console.log(`
24
- ${o}`),console.log(y.dim(" downloaded: ")+t.downloaded),t.uploaded)console.log(y.dim(" uploaded: ")+t.uploaded);if(t.deleted)console.log(y.dim(" deleted: ")+t.deleted);if(t.skipped)console.log(y.dim(" skipped: ")+t.skipped);if(!r){console.log(y.dim(" errors: ")+t.errors.length);for(let n of t.errors.slice(0,5))console.log(y.dim(" ")+y.red(`${n.path}: ${n.message}`));if(t.errors.length>5)console.log(y.dim(` ... and ${t.errors.length-5} more`))}}function Rn(e){if(e<1024)return`${e}B`;if(e<1048576)return`${(e/1024).toFixed(1)}KB`;return`${(e/1024/1024).toFixed(2)}MB`}import{existsSync as Pn}from"node:fs";import{resolve as $n}from"node:path";import k from"picocolors";import{CloudFileTransport as Tn}from"@shogo-ai/sdk/cloud-file-transport";async function nr(e,t){if(!e)throw Error("projectId is required");let r=A({apiKey:t.apiKey,cloudUrl:t.cloudUrl}),o=$n(t.from??B(e,r.projectsDir));if(!Pn(o))throw Error(`Source directory does not exist: ${o}`);let n=t.include?.split(",").map((a)=>a.trim()).filter(Boolean);if(console.log(k.bold(`
25
- shogo project push ${k.cyan(e)}`)),console.log(k.dim(" cloud ")+r.cloudUrl),console.log(k.dim(" from ")+o),n?.length)console.log(k.dim(" include ")+n.join(", "));if(t.deleteRemote)console.log(k.yellow(" --delete-remote: remote files not present locally will be DELETED"));console.log("");let s=await new Tn({apiUrl:r.cloudUrl,apiKey:r.apiKey,projectId:e,localDir:o,include:n,onProgress:(a)=>{let c=a.kind==="upload"?"↑":a.kind==="delete"?"✗":"·",l=a.total>0?` ${a.index+1}/${a.total}`:"",u=a.bytes!=null?k.dim(` (${In(a.bytes)})`):"";console.log(k.dim(` ${c}${l} ${a.path}${u}`))}}).uploadAll({deleteRemote:t.deleteRemote});En("Push",s)}function En(e,t){let r=t.errors.length===0,o=r?k.green(`✓ ${e} complete`):k.red(`✗ ${e} completed with errors`);if(console.log(`
26
- ${o}`),console.log(k.dim(" uploaded: ")+t.uploaded),t.deleted)console.log(k.dim(" deleted: ")+t.deleted);if(!r){console.log(k.dim(" errors: ")+t.errors.length);for(let n of t.errors.slice(0,5))console.log(k.dim(" ")+k.red(`${n.path}: ${n.message}`));if(t.errors.length>5)console.log(k.dim(` ... and ${t.errors.length-5} more`))}}function In(e){if(e<1024)return`${e}B`;if(e<1048576)return`${(e/1024).toFixed(1)}KB`;return`${(e/1024/1024).toFixed(2)}MB`}import{existsSync as xn}from"node:fs";import{resolve as Cn}from"node:path";import O from"picocolors";he();async function ir(e,t){if(!e)throw Error("projectId is required");let r=A({apiKey:t.apiKey,cloudUrl:t.cloudUrl}),o=Cn(t.into??B(e,r.projectsDir));if(!xn(o))throw Error(`Local project dir does not exist: ${o} — run \`shogo project pull\` first`);if(!ge(o))throw Error(`${o} is not a git repo. \`shogo project checkout\` requires the git sync path; run \`shogo project pull\` after installing git, or use \`shogo project pull --include\` for file-only restore.`);if(console.log(O.bold(`
27
- shogo project checkout ${O.cyan(e)}`)),console.log(O.dim(" cloud ")+r.cloudUrl),console.log(O.dim(" local ")+o),t.at)console.log(O.dim(" at ")+t.at);if(console.log(""),t.unshallow)console.log(O.dim("Unshallowing repo (this may take a moment)...")),await Oe({apiUrl:r.cloudUrl,apiKey:r.apiKey,projectId:e,localDir:o});if(!t.at){let i=await me({apiUrl:r.cloudUrl,apiKey:r.apiKey,projectId:e,localDir:o});console.log(O.green(`✓ Reset to ${i.commitSha.slice(0,8)} (remote HEAD)`));return}let n;try{n=(await P(["rev-parse","--verify",`${t.at}^{commit}`],{cwd:o})).stdout.trim()}catch{n=await _n(r.cloudUrl,r.apiKey,e,t.at),console.log(O.dim(` resolved checkpoint "${t.at}" → ${n.slice(0,8)}`))}try{await me({apiUrl:r.cloudUrl,apiKey:r.apiKey,projectId:e,localDir:o,branch:n})}catch{if(!t.unshallow)console.log(O.yellow(" fetch failed; unshallowing and retrying...")),await Oe({apiUrl:r.cloudUrl,apiKey:r.apiKey,projectId:e,localDir:o}),await me({apiUrl:r.cloudUrl,apiKey:r.apiKey,projectId:e,localDir:o,branch:n});else throw Error(`Cannot reach commit ${n} in local clone`)}await P(["reset","--hard",n],{cwd:o}),console.log(O.green(`✓ Checked out ${n.slice(0,8)}`))}async function _n(e,t,r,o){let n=`${e.replace(/\/+$/,"")}/api/projects/${encodeURIComponent(r)}/checkpoints?limit=100`,i=await fetch(n,{headers:{Authorization:`Bearer ${t}`}});if(!i.ok)throw Error(`Failed to list checkpoints: HTTP ${i.status}`);let s=await i.json(),a=o.toLowerCase(),c=s.checkpoints.find((l)=>{if(l.name&&l.name.toLowerCase()===a)return!0;if(l.commitSha.startsWith(o))return!0;if(l.commitMessage&&l.commitMessage.toLowerCase().includes(a))return!0;return!1});if(!c)throw Error(`No checkpoint matches "${o}" (searched ${s.checkpoints.length} most recent)`);return c.commitSha}import{createInterface as Wn}from"node:readline";import v from"picocolors";import{execFileSync as ar}from"node:child_process";import{existsSync as J,copyFileSync as sr}from"node:fs";import C from"node:path";import{homedir as lr,platform as cr}from"node:os";function ur(e,t,r){let n=ar(e,["-e",r],{env:{...process.env,DBP:t},encoding:"utf-8",stdio:["ignore","pipe","pipe"],timeout:5000}).trim();if(!n)throw Error(`bun script returned empty output for ${t}`);return JSON.parse(n)}function Se(e,t){if(!J(t))return[];return ur(e,t,`
19
+ actual: ${p}`);let m=Te(c,"extract");Qe(m,{recursive:!0}),await yn(l,m);let f=Te(m,"agent-runtime");if(!ue(f))throw Error(`Tarball ${a.assetName} did not contain ./agent-runtime`);Sn(f,O);let d={version:i,target:n,installedAt:new Date().toISOString(),channel:r,source:a.tarball,sha256:p};return an(oe,JSON.stringify(d,null,2)+`
20
+ `,{mode:384}),t.log(`[runtime install] Installed agent-runtime ${i} (${n}) to ${O}`),{version:i,target:n,binPath:O,source:a.tarball,sha256:p,channel:r}}finally{try{Ze(c,{recursive:!0,force:!0})}catch{}}}function dr(){return{runtimeDir:re,runtimeBin:O,versionFile:oe}}async function et(e={}){let t=await pr({channel:e.channel,version:e.version,baseUrl:e.baseUrl,force:e.force});console.log(),console.log(b.green("✓"),`agent-runtime ${b.bold(t.version)} installed (${t.target})`),console.log(` ${b.dim("path: ")} ${t.binPath}`),console.log(` ${b.dim("source: ")} ${t.source}`),console.log(` ${b.dim("sha256: ")} ${t.sha256}`),console.log(` ${b.dim("channel: ")} ${t.channel}`)}function mr(){let e=$e();if(!e){console.log(b.yellow("No agent-runtime installed.")),console.log(`Run ${b.cyan("shogo runtime install")} to download the latest.`),process.exitCode=1;return}console.log(`${b.bold("agent-runtime")} ${e.version}`),console.log(` ${b.dim("target: ")} ${e.target}`),console.log(` ${b.dim("channel: ")} ${e.channel}`),console.log(` ${b.dim("installed at:")} ${e.installedAt}`),console.log(` ${b.dim("source: ")} ${e.source}`)}function gr(){let e=G(),t=dr();if(!e){console.log(b.yellow("agent-runtime binary not found on this machine.")),console.log(),console.log(b.dim("Default install path:"),t.runtimeBin),console.log(),console.log(q()),process.exitCode=1;return}if(console.log(e.path),process.env.SHOGO_DEBUG||process.env.VERBOSE)console.log(b.dim(` (resolved via: ${e.source})`))}async function hr(e={}){let t=$e(),r=e.channel??t?.channel??"stable";if(t)console.log(`${b.dim("current:")} ${t.version} (${t.target}, ${t.channel})`);else console.log(b.dim("No existing install — installing fresh..."));await et({channel:r,baseUrl:e.baseUrl,force:!0})}import{existsSync as bn,mkdirSync as vn}from"node:fs";import{resolve as kn}from"node:path";import y from"picocolors";import{CloudFileTransport as Rn}from"@shogo-ai/sdk/cloud-file-transport";async function fr(e,t){if(!e)throw Error("projectId is required");let r=M({apiKey:t.apiKey,cloudUrl:t.cloudUrl});it(r.projectsDir);let o=kn(t.into??V(e,r.projectsDir));if(!bn(o))vn(o,{recursive:!0});let n=t.include?.split(",").map((a)=>a.trim()).filter(Boolean);if(console.log(y.bold(`
21
+ shogo project pull ${y.cyan(e)}`)),console.log(y.dim(" cloud ")+r.cloudUrl),console.log(y.dim(" into ")+o),n?.length)console.log(y.dim(" include ")+n.join(", "));console.log("");let i=new Rn({apiUrl:r.cloudUrl,apiKey:r.apiKey,projectId:e,localDir:o,include:n,onProgress:Pn()}),s=await i.downloadAll();if(Tn("Pull",s),t.watch){console.log(y.bold(`
22
+ Watching for local changes...`));let a=new ae({rootDir:o,transport:i,onFlush:({uploaded:u,errors:p})=>{let m=u.length===1?u[0]:`${u.length} files`,f=p>0?y.red(` (${p} errors)`):"";console.log(y.dim(` ↑ ${m}${f}`))}});a.start();let c=!1,l=async(u)=>{if(c)return;c=!0,console.log(y.dim(`
23
+ Received ${u} — flushing pending uploads...`));try{await a.stop()}catch(p){console.warn(y.yellow(`watcher.stop: ${p?.message??p}`))}process.exit(0)};process.once("SIGINT",()=>void l("SIGINT")),process.once("SIGTERM",()=>void l("SIGTERM")),process.once("SIGHUP",()=>void l("SIGHUP")),await new Promise(()=>{})}}function Pn(){return(e)=>{let t=e.kind==="download"?"↓":e.kind==="upload"?"↑":e.kind==="delete"?"✗":"·",r=e.total>0?` ${e.index+1}/${e.total}`:"",o=e.bytes!=null?y.dim(` (${$n(e.bytes)})`):"";console.log(y.dim(` ${t}${r} ${e.path}${o}`))}}function Tn(e,t){let r=t.errors.length===0,o=r?y.green(`✓ ${e} complete`):y.red(`✗ ${e} completed with errors`);if(console.log(`
24
+ ${o}`),console.log(y.dim(" downloaded: ")+t.downloaded),t.uploaded)console.log(y.dim(" uploaded: ")+t.uploaded);if(t.deleted)console.log(y.dim(" deleted: ")+t.deleted);if(t.skipped)console.log(y.dim(" skipped: ")+t.skipped);if(!r){console.log(y.dim(" errors: ")+t.errors.length);for(let n of t.errors.slice(0,5))console.log(y.dim(" ")+y.red(`${n.path}: ${n.message}`));if(t.errors.length>5)console.log(y.dim(` ... and ${t.errors.length-5} more`))}}function $n(e){if(e<1024)return`${e}B`;if(e<1048576)return`${(e/1024).toFixed(1)}KB`;return`${(e/1024/1024).toFixed(2)}MB`}import{existsSync as En}from"node:fs";import{resolve as xn}from"node:path";import k from"picocolors";import{CloudFileTransport as In}from"@shogo-ai/sdk/cloud-file-transport";async function wr(e,t){if(!e)throw Error("projectId is required");let r=M({apiKey:t.apiKey,cloudUrl:t.cloudUrl}),o=xn(t.from??V(e,r.projectsDir));if(!En(o))throw Error(`Source directory does not exist: ${o}`);let n=t.include?.split(",").map((a)=>a.trim()).filter(Boolean);if(console.log(k.bold(`
25
+ shogo project push ${k.cyan(e)}`)),console.log(k.dim(" cloud ")+r.cloudUrl),console.log(k.dim(" from ")+o),n?.length)console.log(k.dim(" include ")+n.join(", "));if(t.deleteRemote)console.log(k.yellow(" --delete-remote: remote files not present locally will be DELETED"));console.log("");let s=await new In({apiUrl:r.cloudUrl,apiKey:r.apiKey,projectId:e,localDir:o,include:n,onProgress:(a)=>{let c=a.kind==="upload"?"↑":a.kind==="delete"?"✗":"·",l=a.total>0?` ${a.index+1}/${a.total}`:"",u=a.bytes!=null?k.dim(` (${An(a.bytes)})`):"";console.log(k.dim(` ${c}${l} ${a.path}${u}`))}}).uploadAll({deleteRemote:t.deleteRemote});Cn("Push",s)}function Cn(e,t){let r=t.errors.length===0,o=r?k.green(`✓ ${e} complete`):k.red(`✗ ${e} completed with errors`);if(console.log(`
26
+ ${o}`),console.log(k.dim(" uploaded: ")+t.uploaded),t.deleted)console.log(k.dim(" deleted: ")+t.deleted);if(!r){console.log(k.dim(" errors: ")+t.errors.length);for(let n of t.errors.slice(0,5))console.log(k.dim(" ")+k.red(`${n.path}: ${n.message}`));if(t.errors.length>5)console.log(k.dim(` ... and ${t.errors.length-5} more`))}}function An(e){if(e<1024)return`${e}B`;if(e<1048576)return`${(e/1024).toFixed(1)}KB`;return`${(e/1024/1024).toFixed(2)}MB`}import{existsSync as _n}from"node:fs";import{resolve as On}from"node:path";import W from"picocolors";ve();async function yr(e,t){if(!e)throw Error("projectId is required");let r=M({apiKey:t.apiKey,cloudUrl:t.cloudUrl}),o=On(t.into??V(e,r.projectsDir));if(!_n(o))throw Error(`Local project dir does not exist: ${o} — run \`shogo project pull\` first`);if(!be(o))throw Error(`${o} is not a git repo. \`shogo project checkout\` requires the git sync path; run \`shogo project pull\` after installing git, or use \`shogo project pull --include\` for file-only restore.`);if(console.log(W.bold(`
27
+ shogo project checkout ${W.cyan(e)}`)),console.log(W.dim(" cloud ")+r.cloudUrl),console.log(W.dim(" local ")+o),t.at)console.log(W.dim(" at ")+t.at);if(console.log(""),t.unshallow)console.log(W.dim("Unshallowing repo (this may take a moment)...")),await je({apiUrl:r.cloudUrl,apiKey:r.apiKey,projectId:e,localDir:o});if(!t.at){let i=await Se({apiUrl:r.cloudUrl,apiKey:r.apiKey,projectId:e,localDir:o});console.log(W.green(`✓ Reset to ${i.commitSha.slice(0,8)} (remote HEAD)`));return}let n;try{n=(await E(["rev-parse","--verify",`${t.at}^{commit}`],{cwd:o})).stdout.trim()}catch{n=await Mn(r.cloudUrl,r.apiKey,e,t.at),console.log(W.dim(` resolved checkpoint "${t.at}" → ${n.slice(0,8)}`))}try{await Se({apiUrl:r.cloudUrl,apiKey:r.apiKey,projectId:e,localDir:o,branch:n})}catch{if(!t.unshallow)console.log(W.yellow(" fetch failed; unshallowing and retrying...")),await je({apiUrl:r.cloudUrl,apiKey:r.apiKey,projectId:e,localDir:o}),await Se({apiUrl:r.cloudUrl,apiKey:r.apiKey,projectId:e,localDir:o,branch:n});else throw Error(`Cannot reach commit ${n} in local clone`)}await E(["reset","--hard",n],{cwd:o}),console.log(W.green(`✓ Checked out ${n.slice(0,8)}`))}async function Mn(e,t,r,o){let n=`${e.replace(/\/+$/,"")}/api/projects/${encodeURIComponent(r)}/checkpoints?limit=100`,i=await fetch(n,{headers:{Authorization:`Bearer ${t}`}});if(!i.ok)throw Error(`Failed to list checkpoints: HTTP ${i.status}`);let s=await i.json(),a=o.toLowerCase(),c=s.checkpoints.find((l)=>{if(l.name&&l.name.toLowerCase()===a)return!0;if(l.commitSha.startsWith(o))return!0;if(l.commitMessage&&l.commitMessage.toLowerCase().includes(a))return!0;return!1});if(!c)throw Error(`No checkpoint matches "${o}" (searched ${s.checkpoints.length} most recent)`);return c.commitSha}import{createInterface as jn}from"node:readline";import S from"picocolors";D();import{existsSync as Q,copyFileSync as Sr}from"node:fs";import _ from"node:path";import{homedir as br,platform as vr}from"node:os";function kr(e,t,r){let n=Oe(e,["-e",r],{env:{...process.env,DBP:t},encoding:"utf-8",stdio:["ignore","pipe","pipe"],timeout:5000}).trim();if(!n)throw Error(`bun script returned empty output for ${t}`);return JSON.parse(n)}function Ee(e,t){if(!Q(t))return[];return kr(e,t,`
28
28
  import { Database } from 'bun:sqlite';
29
29
  try {
30
30
  const db = new Database(process.env.DBP, { readonly: true });
@@ -38,7 +38,7 @@ shogo project checkout ${O.cyan(e)}`)),console.log(O.dim(" cloud ")+r.cloudUrl
38
38
  console.error(String(e?.stack || e));
39
39
  process.exit(2);
40
40
  }
41
- `)}function An(e){if(!J(e))throw Error(`Database does not exist at ${e} — refusing to back up nothing`);let t=C.dirname(e),r=C.basename(e),o=new Date().toISOString().replace(/[:.]/g,"-"),n=C.join(t,`${r}.bak-${o}`);sr(e,n);for(let i of["-wal","-shm"]){let s=`${e}${i}`;if(J(s))sr(s,`${n}${i}`)}return n}function On(e,t,r){if(r.length===0)return 0;if(!J(t))throw Error(`Cannot repair: database does not exist at ${t}`);let n=`
41
+ `)}function Dn(e){if(!Q(e))throw Error(`Database does not exist at ${e} — refusing to back up nothing`);let t=_.dirname(e),r=_.basename(e),o=new Date().toISOString().replace(/[:.]/g,"-"),n=_.join(t,`${r}.bak-${o}`);Sr(e,n);for(let i of["-wal","-shm"]){let s=`${e}${i}`;if(Q(s))Sr(s,`${n}${i}`)}return n}function Wn(e,t,r){if(r.length===0)return 0;if(!Q(t))throw Error(`Cannot repair: database does not exist at ${t}`);let n=`
42
42
  import { Database } from 'bun:sqlite';
43
43
  const names = ${JSON.stringify(r)};
44
44
  if (!Array.isArray(names) || names.some(n => typeof n !== 'string')) {
@@ -57,8 +57,8 @@ shogo project checkout ${O.cyan(e)}`)),console.log(O.dim(" cloud ")+r.cloudUrl
57
57
  console.error(String(e?.stack || e));
58
58
  process.exit(3);
59
59
  }
60
- `;return ur(e,t,n).deleted}function pr(e){let t=e.log??(()=>{}),{bunPath:r,dbPath:o}=e;if(!J(o))return{status:"no-database",detected:[],cleared:[],remaining:[],message:`No database found at ${o}. Nothing to repair — launch Shogo once to create it.`};let n=Se(r,o);if(n.length===0)return{status:"healthy",detected:[],cleared:[],remaining:[],message:"No failed migrations detected — the local database looks healthy."};t(`Found ${n.length} failed migration(s): ${n.map((p)=>p.name).join(", ")}`);let i;if(!e.skipBackup)i=An(o),t(`Backed up database to ${i}`);let s=n.map((p)=>p.name),a=On(r,o,s);t(`Cleared ${a} failed migration row(s).`);let c=Se(r,o),l=c.length===0?"repaired":"failed",u=l==="repaired"?"Cleared the failed migration record. Relaunch Shogo to re-apply migrations cleanly.":`Repair incomplete — ${c.length} migration(s) still failed: ${c.map((p)=>p.name).join(", ")}.`;return{status:l,detected:n,backupPath:i,cleared:s.slice(0,a),remaining:c,message:u}}function Mn(){let e=lr(),t=cr(),r;if(t==="darwin")r=C.join(e,"Library","Application Support");else if(t==="win32")r=process.env.APPDATA??C.join(e,"AppData","Roaming");else r=process.env.XDG_CONFIG_HOME??C.join(e,".config");return C.join(r,"Shogo","data")}function dr(){return C.join(Mn(),"shogo.db")}function Dn(){let e=cr(),t=e==="win32"?"bun.exe":"bun",r=lr(),o=[];if(e==="darwin")o.push(C.join("/Applications","Shogo.app","Contents","Resources","bun",t),C.join(r,"Applications","Shogo.app","Contents","Resources","bun",t));else if(e==="linux")o.push(C.join("/opt","Shogo","resources","bun",t),C.join("/usr","lib","shogo","resources","bun",t));return o}function Ye(e){try{return ar(e,["--version"],{stdio:"ignore",timeout:5000}),!0}catch{return!1}}function mr(e){if(e)return Ye(e)?e:null;if(process.versions.bun&&process.execPath)return process.execPath;for(let t of Dn())if(J(t)&&Ye(t))return t;if(Ye("bun"))return"bun";return null}function Un(e){for(let t of e){let r=Number.isFinite(t.startedAt)?new Date(t.startedAt).toISOString():"unknown time",o=(t.errorExcerpt??"").split(`
61
- `)[0]?.trim();if(console.log(` ${v.yellow("•")} ${v.bold(t.name)} ${v.dim(`(attempted ${r})`)}`),o)console.log(` ${v.dim(o)}`)}}async function Nn(e){if(!process.stdin.isTTY)return console.log(v.dim("(stdin is not a TTY — re-run with --yes to repair non-interactively)")),!1;let t=Wn({input:process.stdin,output:process.stdout});try{let r=await new Promise((o)=>t.question(e,o));return/^y(es)?$/i.test(r.trim())}finally{t.close()}}async function gr(e={}){let t=e.db??dr(),r=mr(e.bun);if(!r)throw Error("Could not find a usable `bun` binary (needed to inspect the SQLite database).\n"+" • Install Bun (https://bun.sh) so `bun` is on your PATH, or\n"+` • pass --bun <path> pointing at the bun shipped inside the Shogo app
62
- `+" (e.g. /Applications/Shogo.app/Contents/Resources/bun/bun on macOS).");console.log(v.dim(`Database: ${t}`)),console.log(v.dim(`Bun: ${r}`)),console.log();let o=Se(r,t);if(o.length===0){console.log(v.green("✓ No failed migrations detected — your local database looks healthy."));return}if(console.log(v.red(`✗ Found ${o.length} failed migration(s):`)),Un(o),console.log(),e.check){console.log(v.dim("Run `shogo doctor` (without --check) to back up the database and clear these records.")),process.exitCode=1;return}if(!e.yes){let i=e.backup===!1?v.red("This will NOT create a backup (--no-backup)."):"Your database will be backed up to a .bak-<timestamp> file first.";if(console.log(i),!await Nn(v.bold("Clear these failed migration records and repair? [y/N] "))){console.log(v.dim("Aborted — no changes made.")),process.exitCode=1;return}console.log()}let n=pr({bunPath:r,dbPath:t,skipBackup:e.backup===!1,log:(i)=>console.log(v.dim(` ${i}`))});if(console.log(),n.status==="repaired"){if(console.log(v.green(`✓ ${n.message}`)),n.backupPath)console.log(v.dim(` Backup: ${n.backupPath}`));console.log(v.bold(`
63
- → Relaunch the Shogo app to finish applying migrations.`))}else{if(console.log(v.red(`✗ ${n.message}`)),n.backupPath)console.log(v.dim(` Backup: ${n.backupPath}`));process.exitCode=1}}import{spawn as jn}from"node:child_process";import{resolve as Fn}from"node:path";import hr from"picocolors";function Ln(e){let{flags:t,config:r,runtime:o}=e,n=t.cwd?Fn(t.cwd):process.cwd(),i=["interactive"];if(t.print!==void 0)i.push("-p",t.print);if(t.noTui)i.push("--no-tui");let s=r.cloudUrl.replace(/\/$/,""),a={...e.baseEnv??process.env,SHOGO_INTERACTIVE:"1",SHOGO_INTERACTIVE_CWD:n,PROJECT_DIR:n,WORKSPACE_DIR:n,SHOGO_API_URL:s,SHOGO_CLOUD_URL:s,SHOGO_API_KEY:r.apiKey,AI_PROXY_URL:`${s}/api/ai/v1`,AI_PROXY_TOKEN:r.apiKey,NODE_ENV:"production"};if(t.model)a.SHOGO_MODEL=t.model;if(t.print!==void 0)a.SHOGO_PRINT_PROMPT=t.print;return{bin:o.path,args:i,env:a,cwd:n}}async function fr(e={}){let t=A({apiKey:e.apiKey,cloudUrl:e.cloudUrl}),r=F({flag:e.runtimeBin});if(!r)console.error(hr.red(H({flag:e.runtimeBin}))),process.exit(1);let o=Ln({flags:e,config:{apiKey:t.apiKey,cloudUrl:t.cloudUrl},runtime:r}),n=await new Promise((i)=>{let s=jn(o.bin,o.args,{cwd:o.cwd,env:o.env,stdio:"inherit"});s.on("error",(a)=>{console.error(hr.red(`✗ Failed to launch agent-runtime: ${a.message}`)),i(1)}),s.on("exit",(a)=>i(a??0))});process.exit(n)}var Gn="0.1.0",M=new Hn;M.name("shogo").description("Shogo Cloud Agent Worker — run Shogo agents on your own machine.").version(Gn);M.command("login").description("Pair this machine with Shogo Cloud (browser device flow, or --api-key for CI)").option("--api-key <key>","CI escape hatch — skip the browser flow and use a key directly").option("--cloud-url <url>","Shogo Cloud URL (default: https://studio.shogo.ai)").option("--name <name>","Device label shown in the dashboard (default: hostname)").option("--workspace <id>","Pre-select a workspace on the bridge picker").option("--no-browser","Do not auto-open the browser; print the URL instead").action((e)=>R(()=>Kt(e)));var ke=M.command("worker").description("Manage the local worker process");ke.command("start").description("Start the worker and pair with Shogo Cloud").option("--name <name>","Instance name shown in the dashboard (default: hostname)").option("--worker-dir <path>","Working directory for the worker (default: $PWD)").option("--api-key <key>","API key (overrides config/env)").option("--cloud-url <url>","Shogo Cloud URL").option("--port <port>","Local HTTP port for the embedded API").option("--proxy <url>","HTTPS proxy (overrides HTTPS_PROXY env)").option("--project <id>","Pin to a single project (default: multi-project on demand)").option("--runtime-bin <path>","Override the agent-runtime binary path").option("--debug","Run preflight checks before starting").option("--foreground","Run in foreground (don't detach)").option("--no-auto-pull","Disable auto-clone of project workspaces on first request").option("--projects-dir <path>","Root directory for cloned project workspaces (default: ~/.shogo/projects)").option("--no-git","Force the file-transport sync path even when git is available").action((e)=>R(()=>Ut(e)));ke.command("stop").description("Stop the running worker").action(()=>R(jt));ke.command("status").description("Show worker status").action(()=>R(Ft));ke.command("logs").description("Tail worker logs from ~/.shogo/logs/worker.log").option("-f, --follow","Follow the log (tail -F)").option("--err","Show stderr log instead").action((e)=>R(()=>Ht(e)));var Re=M.command("runtime").description("Install / inspect the local agent-runtime binary");Re.command("install").description("Download + verify the agent-runtime tarball into ~/.shogo/runtime/").option("--channel <channel>","stable | beta | nightly (default: stable)").option("--version <version>","install a specific version (e.g. 0.1.0)").option("--base-url <url>","override release base URL (default: GitHub Releases)").option("--force","reinstall even if the same version is already on disk").action((e)=>R(()=>Ve(e)));Re.command("version").description("Print the installed agent-runtime version").action(()=>R(()=>er()));Re.command("where").description("Print the resolved agent-runtime binary path").action(()=>R(()=>tr()));Re.command("update").description("Update agent-runtime to the latest in its channel").option("--channel <channel>","override channel (defaults to whatever is installed)").option("--base-url <url>","override release base URL").action((e)=>R(()=>rr(e)));var yr=M.command("config").description("Inspect or modify ~/.shogo/config.json");yr.command("show").description("Print current config (API key masked)").action(()=>R(qt));yr.command("set <key> <value>").description("Set apiKey / cloudUrl / name / workerDir / port / projectsDir").action((e,t)=>R(()=>Jt(e,t)));var Xe=M.command("project").description("Clone/sync project workspaces between Shogo Cloud and this machine");Xe.command("pull <projectId>").description("Clone a project from Shogo Cloud into ~/.shogo/projects/<projectId>/").option("--into <dir>","Destination directory (default: ~/.shogo/projects/<projectId>)").option("--watch","After pull, watch the local dir and push edits back to cloud").option("--include <patterns>",'Comma-separated glob patterns (e.g. "src/**,*.md")').option("--api-key <key>","Override API key for this run").option("--cloud-url <url>","Override Shogo Cloud URL for this run").action((e,t)=>R(()=>or(e,t)));Xe.command("push <projectId>").description("Upload local workspace edits back to Shogo Cloud").option("--from <dir>","Source directory (default: ~/.shogo/projects/<projectId>)").option("--delete-remote","Mirror local deletions to cloud (DESTRUCTIVE)").option("--include <patterns>","Comma-separated glob patterns").option("--api-key <key>","Override API key for this run").option("--cloud-url <url>","Override Shogo Cloud URL for this run").action((e,t)=>R(()=>nr(e,t)));Xe.command("checkout <projectId>").description("Roll the local workspace to a specific git checkpoint (SHA or named checkpoint)").option("--at <ref>","Target SHA or checkpoint name (default: remote HEAD)").option("--unshallow","Fetch full history before checking out (needed for old SHAs)").option("--into <dir>","Local dir override (default: ~/.shogo/projects/<projectId>)").option("--api-key <key>","Override API key for this run").option("--cloud-url <url>","Override Shogo Cloud URL for this run").action((e,t)=>R(()=>ir(e,t)));M.command("doctor").description("Diagnose & repair a wedged local Shogo database (clears failed migrations so the app can reboot)").option("--check","Detect only — never modify the database").option("--yes","Repair without the confirmation prompt").option("--db <path>","Path to shogo.db (default: the desktop app's local database)").option("--bun <path>","Path to a bun binary (default: bundled/PATH bun)").option("--no-backup","Skip the pre-repair database backup (discouraged)").action((e)=>R(()=>gr(e)));M.command("chat",{isDefault:!0}).description("Start the interactive coding agent in the current directory").argument("[prompt...]","Optional one-shot prompt (runs non-interactively, like -p)").option("-p, --print <prompt>","Headless one-shot: run a single turn and print the result").option("--model <model>","Model id to use for new turns").option("--cwd <dir>","Working directory to operate in (default: $PWD)").option("--runtime-bin <path>","Override the agent-runtime binary path").option("--no-tui","Disable the rich TUI; use a plain readline renderer").option("--api-key <key>","Override API key for this run").option("--cloud-url <url>","Override Shogo Cloud URL for this run").action((e,t)=>R(()=>fr({print:t.print??(e.length?e.join(" "):void 0),model:t.model,cwd:t.cwd,runtimeBin:t.runtimeBin,noTui:t.tui===!1,apiKey:t.apiKey,cloudUrl:t.cloudUrl})));M.showHelpAfterError(wr.dim(`
64
- (use --help for usage)`));M.parseAsync(process.argv);async function R(e){try{await e()}catch(t){if(console.error(wr.red(`✗ ${t?.message??t}`)),process.env.SHOGO_DEBUG)console.error(t?.stack);process.exit(1)}}
60
+ `;return kr(e,t,n).deleted}function Rr(e){let t=e.log??(()=>{}),{bunPath:r,dbPath:o}=e;if(!Q(o))return{status:"no-database",detected:[],cleared:[],remaining:[],message:`No database found at ${o}. Nothing to repair — launch Shogo once to create it.`};let n=Ee(r,o);if(n.length===0)return{status:"healthy",detected:[],cleared:[],remaining:[],message:"No failed migrations detected — the local database looks healthy."};t(`Found ${n.length} failed migration(s): ${n.map((p)=>p.name).join(", ")}`);let i;if(!e.skipBackup)i=Dn(o),t(`Backed up database to ${i}`);let s=n.map((p)=>p.name),a=Wn(r,o,s);t(`Cleared ${a} failed migration row(s).`);let c=Ee(r,o),l=c.length===0?"repaired":"failed",u=l==="repaired"?"Cleared the failed migration record. Relaunch Shogo to re-apply migrations cleanly.":`Repair incomplete — ${c.length} migration(s) still failed: ${c.map((p)=>p.name).join(", ")}.`;return{status:l,detected:n,backupPath:i,cleared:s.slice(0,a),remaining:c,message:u}}function Un(){let e=br(),t=vr(),r;if(t==="darwin")r=_.join(e,"Library","Application Support");else if(t==="win32")r=process.env.APPDATA??_.join(e,"AppData","Roaming");else r=process.env.XDG_CONFIG_HOME??_.join(e,".config");return _.join(r,"Shogo","data")}function Pr(){return _.join(Un(),"shogo.db")}function Nn(){let e=vr(),t=e==="win32"?"bun.exe":"bun",r=br(),o=[];if(e==="darwin")o.push(_.join("/Applications","Shogo.app","Contents","Resources","bun",t),_.join(r,"Applications","Shogo.app","Contents","Resources","bun",t));else if(e==="linux")o.push(_.join("/opt","Shogo","resources","bun",t),_.join("/usr","lib","shogo","resources","bun",t));return o}function tt(e){try{return Oe(e,["--version"],{stdio:"ignore",timeout:5000}),!0}catch{return!1}}function Tr(e){if(e)return tt(e)?e:null;if(process.versions.bun&&process.execPath)return process.execPath;for(let t of Nn())if(Q(t)&&tt(t))return t;if(tt("bun"))return"bun";return null}function Fn(e){for(let t of e){let r=Number.isFinite(t.startedAt)?new Date(t.startedAt).toISOString():"unknown time",o=(t.errorExcerpt??"").split(`
61
+ `)[0]?.trim();if(console.log(` ${S.yellow("•")} ${S.bold(t.name)} ${S.dim(`(attempted ${r})`)}`),o)console.log(` ${S.dim(o)}`)}}async function Hn(e){if(!process.stdin.isTTY)return console.log(S.dim("(stdin is not a TTY — re-run with --yes to repair non-interactively)")),!1;let t=jn({input:process.stdin,output:process.stdout});try{let r=await new Promise((o)=>t.question(e,o));return/^y(es)?$/i.test(r.trim())}finally{t.close()}}async function $r(e={}){let t=e.db??Pr(),r=Tr(e.bun);if(!r)throw Error("Could not find a usable `bun` binary (needed to inspect the SQLite database).\n"+" • Install Bun (https://bun.sh) so `bun` is on your PATH, or\n"+` • pass --bun <path> pointing at the bun shipped inside the Shogo app
62
+ `+" (e.g. /Applications/Shogo.app/Contents/Resources/bun/bun on macOS).");console.log(S.dim(`Database: ${t}`)),console.log(S.dim(`Bun: ${r}`)),console.log();let o=Ee(r,t);if(o.length===0){console.log(S.green("✓ No failed migrations detected — your local database looks healthy."));return}if(console.log(S.red(`✗ Found ${o.length} failed migration(s):`)),Fn(o),console.log(),e.check){console.log(S.dim("Run `shogo doctor` (without --check) to back up the database and clear these records.")),process.exitCode=1;return}if(!e.yes){let i=e.backup===!1?S.red("This will NOT create a backup (--no-backup)."):"Your database will be backed up to a .bak-<timestamp> file first.";if(console.log(i),!await Hn(S.bold("Clear these failed migration records and repair? [y/N] "))){console.log(S.dim("Aborted — no changes made.")),process.exitCode=1;return}console.log()}let n=Rr({bunPath:r,dbPath:t,skipBackup:e.backup===!1,log:(i)=>console.log(S.dim(` ${i}`))});if(console.log(),n.status==="repaired"){if(console.log(S.green(`✓ ${n.message}`)),n.backupPath)console.log(S.dim(` Backup: ${n.backupPath}`));console.log(S.bold(`
63
+ → Relaunch the Shogo app to finish applying migrations.`))}else{if(console.log(S.red(`✗ ${n.message}`)),n.backupPath)console.log(S.dim(` Backup: ${n.backupPath}`));process.exitCode=1}}D();import{resolve as Ln}from"node:path";import Er from"picocolors";function Gn(e){let{flags:t,config:r,runtime:o}=e,n=t.cwd?Ln(t.cwd):process.cwd(),i=["interactive"];if(t.print!==void 0)i.push("-p",t.print);if(t.noTui)i.push("--no-tui");let s=r.cloudUrl.replace(/\/$/,""),a={...e.baseEnv??process.env,SHOGO_INTERACTIVE:"1",SHOGO_INTERACTIVE_CWD:n,PROJECT_DIR:n,WORKSPACE_DIR:n,SHOGO_API_URL:s,SHOGO_CLOUD_URL:s,SHOGO_API_KEY:r.apiKey,AI_PROXY_URL:`${s}/api/ai/v1`,AI_PROXY_TOKEN:r.apiKey,NODE_ENV:"production"};if(t.model)a.SHOGO_MODEL=t.model;if(t.print!==void 0)a.SHOGO_PRINT_PROMPT=t.print;return{bin:o.path,args:i,env:a,cwd:n}}async function xr(e={}){let t=M({apiKey:e.apiKey,cloudUrl:e.cloudUrl}),r=G({flag:e.runtimeBin});if(!r)console.error(Er.red(q({flag:e.runtimeBin}))),process.exit(1);let o=Gn({flags:e,config:{apiKey:t.apiKey,cloudUrl:t.cloudUrl},runtime:r}),n=await new Promise((i)=>{let s=T(o.bin,o.args,{cwd:o.cwd,env:o.env,stdio:"inherit"});s.on("error",(a)=>{console.error(Er.red(`✗ Failed to launch agent-runtime: ${a.message}`)),i(1)}),s.on("exit",(a)=>i(a??0))});process.exit(n)}var Kn="0.1.0",U=new Bn;U.name("shogo").description("Shogo Cloud Agent Worker — run Shogo agents on your own machine.").version(Kn);U.command("login").description("Pair this machine with Shogo Cloud (browser device flow, or --api-key for CI)").option("--api-key <key>","CI escape hatch — skip the browser flow and use a key directly").option("--cloud-url <url>","Shogo Cloud URL (default: https://studio.shogo.ai)").option("--name <name>","Device label shown in the dashboard (default: hostname)").option("--workspace <id>","Pre-select a workspace on the bridge picker").option("--no-browser","Do not auto-open the browser; print the URL instead").action((e)=>R(()=>nr(e)));var xe=U.command("worker").description("Manage the local worker process");xe.command("start").description("Start the worker and pair with Shogo Cloud").option("--name <name>","Instance name shown in the dashboard (default: hostname)").option("--worker-dir <path>","Working directory for the worker (default: $PWD)").option("--api-key <key>","API key (overrides config/env)").option("--cloud-url <url>","Shogo Cloud URL").option("--port <port>","Local HTTP port for the embedded API").option("--proxy <url>","HTTPS proxy (overrides HTTPS_PROXY env)").option("--project <id>","Pin to a single project (default: multi-project on demand)").option("--runtime-bin <path>","Override the agent-runtime binary path").option("--debug","Run preflight checks before starting").option("--foreground","Run in foreground (don't detach)").option("--no-auto-pull","Disable auto-clone of project workspaces on first request").option("--projects-dir <path>","Root directory for cloned project workspaces (default: ~/.shogo/projects)").option("--no-git","Force the file-transport sync path even when git is available").action((e)=>R(()=>Xt(e)));xe.command("stop").description("Stop the running worker").action(()=>R(Qt));xe.command("status").description("Show worker status").action(()=>R(Zt));xe.command("logs").description("Tail worker logs from ~/.shogo/logs/worker.log").option("-f, --follow","Follow the log (tail -F)").option("--err","Show stderr log instead").action((e)=>R(()=>tr(e)));var Ie=U.command("runtime").description("Install / inspect the local agent-runtime binary");Ie.command("install").description("Download + verify the agent-runtime tarball into ~/.shogo/runtime/").option("--channel <channel>","stable | beta | nightly (default: stable)").option("--version <version>","install a specific version (e.g. 0.1.0)").option("--base-url <url>","override release base URL (default: GitHub Releases)").option("--force","reinstall even if the same version is already on disk").action((e)=>R(()=>et(e)));Ie.command("version").description("Print the installed agent-runtime version").action(()=>R(()=>mr()));Ie.command("where").description("Print the resolved agent-runtime binary path").action(()=>R(()=>gr()));Ie.command("update").description("Update agent-runtime to the latest in its channel").option("--channel <channel>","override channel (defaults to whatever is installed)").option("--base-url <url>","override release base URL").action((e)=>R(()=>hr(e)));var Cr=U.command("config").description("Inspect or modify ~/.shogo/config.json");Cr.command("show").description("Print current config (API key masked)").action(()=>R(ir));Cr.command("set <key> <value>").description("Set apiKey / cloudUrl / name / workerDir / port / projectsDir").action((e,t)=>R(()=>sr(e,t)));var rt=U.command("project").description("Clone/sync project workspaces between Shogo Cloud and this machine");rt.command("pull <projectId>").description("Clone a project from Shogo Cloud into ~/.shogo/projects/<projectId>/").option("--into <dir>","Destination directory (default: ~/.shogo/projects/<projectId>)").option("--watch","After pull, watch the local dir and push edits back to cloud").option("--include <patterns>",'Comma-separated glob patterns (e.g. "src/**,*.md")').option("--api-key <key>","Override API key for this run").option("--cloud-url <url>","Override Shogo Cloud URL for this run").action((e,t)=>R(()=>fr(e,t)));rt.command("push <projectId>").description("Upload local workspace edits back to Shogo Cloud").option("--from <dir>","Source directory (default: ~/.shogo/projects/<projectId>)").option("--delete-remote","Mirror local deletions to cloud (DESTRUCTIVE)").option("--include <patterns>","Comma-separated glob patterns").option("--api-key <key>","Override API key for this run").option("--cloud-url <url>","Override Shogo Cloud URL for this run").action((e,t)=>R(()=>wr(e,t)));rt.command("checkout <projectId>").description("Roll the local workspace to a specific git checkpoint (SHA or named checkpoint)").option("--at <ref>","Target SHA or checkpoint name (default: remote HEAD)").option("--unshallow","Fetch full history before checking out (needed for old SHAs)").option("--into <dir>","Local dir override (default: ~/.shogo/projects/<projectId>)").option("--api-key <key>","Override API key for this run").option("--cloud-url <url>","Override Shogo Cloud URL for this run").action((e,t)=>R(()=>yr(e,t)));U.command("doctor").description("Diagnose & repair a wedged local Shogo database (clears failed migrations so the app can reboot)").option("--check","Detect only — never modify the database").option("--yes","Repair without the confirmation prompt").option("--db <path>","Path to shogo.db (default: the desktop app's local database)").option("--bun <path>","Path to a bun binary (default: bundled/PATH bun)").option("--no-backup","Skip the pre-repair database backup (discouraged)").action((e)=>R(()=>$r(e)));U.command("chat",{isDefault:!0}).description("Start the interactive coding agent in the current directory").argument("[prompt...]","Optional one-shot prompt (runs non-interactively, like -p)").option("-p, --print <prompt>","Headless one-shot: run a single turn and print the result").option("--model <model>","Model id to use for new turns").option("--cwd <dir>","Working directory to operate in (default: $PWD)").option("--runtime-bin <path>","Override the agent-runtime binary path").option("--no-tui","Disable the rich TUI; use a plain readline renderer").option("--api-key <key>","Override API key for this run").option("--cloud-url <url>","Override Shogo Cloud URL for this run").action((e,t)=>R(()=>xr({print:t.print??(e.length?e.join(" "):void 0),model:t.model,cwd:t.cwd,runtimeBin:t.runtimeBin,noTui:t.tui===!1,apiKey:t.apiKey,cloudUrl:t.cloudUrl})));U.showHelpAfterError(Ir.dim(`
64
+ (use --help for usage)`));U.parseAsync(process.argv);async function R(e){try{await e()}catch(t){if(console.error(Ir.red(`✗ ${t?.message??t}`)),process.env.SHOGO_DEBUG)console.error(t?.stack);process.exit(1)}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shogo-ai/worker",
3
- "version": "2.2.18",
3
+ "version": "2.2.20",
4
4
  "description": "Shogo Cloud Agent Worker — run Shogo agents on your own machine (laptop, devbox, CI).",
5
5
  "license": "MIT",
6
6
  "author": "Shogo Technologies, Inc.",
@@ -14,7 +14,7 @@
14
14
  * workspace key (`AI_PROXY_TOKEN` = `shogo_sk_…`). The proxy accepts the key
15
15
  * directly, so all LLM usage bills to the account with no token minting.
16
16
  */
17
- import { spawn } from 'node:child_process';
17
+ import { spawn } from '../lib/child-process.ts';
18
18
  import { resolve as resolvePath } from 'node:path';
19
19
  import pc from 'picocolors';
20
20
  import { resolveConfig } from '../lib/config.ts';
@@ -1,6 +1,6 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  // Copyright (C) 2026 Shogo Technologies, Inc.
3
- import { spawn } from 'node:child_process';
3
+ import { spawn } from '../lib/child-process.ts';
4
4
  import { existsSync, statSync } from 'node:fs';
5
5
  import pc from 'picocolors';
6
6
  import { WORKER_LOG, WORKER_ERR } from '../lib/paths.ts';
@@ -0,0 +1,114 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (C) 2026 Shogo Technologies, Inc.
3
+ /**
4
+ * `node:child_process` with `windowsHide: true` filled in whenever the caller
5
+ * doesn't set it. Twin of `packages/shared-runtime/src/child-process.ts` —
6
+ * keep the implementation identical. This package can't depend on
7
+ * `@shogo/shared-runtime` (published, and a different license).
8
+ *
9
+ * Shogo's desktop shell is a GUI process. On Windows, spawning a console
10
+ * program (`git.exe`, `cmd.exe`, `npm.cmd`, …) from a GUI process opens a
11
+ * visible console window unless `windowsHide` is set. The flag is a no-op
12
+ * on macOS and Linux. An explicit `windowsHide` (including `false`) is left
13
+ * untouched, and the caller's options object is not mutated.
14
+ */
15
+ import * as cp from 'node:child_process'
16
+ import { promisify } from 'node:util'
17
+
18
+ type Options = Record<string, unknown>
19
+
20
+ function isOptions(value: unknown): value is Options {
21
+ return !!value && typeof value === 'object' && !Array.isArray(value)
22
+ }
23
+
24
+ /** Keep an explicit `windowsHide`. Otherwise return a shallow copy with it set. */
25
+ function mergeHide(opts: Options): Options {
26
+ if (opts.windowsHide !== undefined) return opts
27
+ return { ...opts, windowsHide: true }
28
+ }
29
+
30
+ /** `exec` / `execSync`: `(command, options?, callback?)`. No argv array. */
31
+ function hideExecArgs(args: unknown[]): unknown[] {
32
+ const out = args.slice()
33
+ const slot = out[1]
34
+ if (out.length < 2 || typeof slot === 'function') {
35
+ out.splice(1, 0, { windowsHide: true })
36
+ return out
37
+ }
38
+ if (slot == null) {
39
+ out[1] = { windowsHide: true }
40
+ return out
41
+ }
42
+ if (isOptions(slot)) {
43
+ out[1] = mergeHide(slot)
44
+ return out
45
+ }
46
+ return out
47
+ }
48
+
49
+ /**
50
+ * `spawn`, `spawnSync`, `execFile`, `execFileSync`, `fork`:
51
+ * `(file, args?, options?, callback?)`. A `null` argv is preserved.
52
+ */
53
+ function hideSpawnArgs(args: unknown[]): unknown[] {
54
+ const out = args.slice()
55
+ let callbackAt = -1
56
+ for (let i = 1; i < out.length; i++) {
57
+ if (typeof out[i] === 'function') {
58
+ callbackAt = i
59
+ break
60
+ }
61
+ }
62
+ const end = callbackAt === -1 ? out.length : callbackAt
63
+ for (let i = 1; i < end; i++) {
64
+ if (isOptions(out[i])) {
65
+ out[i] = mergeHide(out[i] as Options)
66
+ return out
67
+ }
68
+ }
69
+ // Options follow argv when argv (array or null) was passed. Fill an empty
70
+ // `undefined` / `null` slot in place: inserting after it would push a
71
+ // trailing callback out of position, or land where Node ignores it.
72
+ const slot = end > 1 && (Array.isArray(out[1]) || out[1] == null) ? 2 : 1
73
+ if (slot >= end) out.splice(end, 0, { windowsHide: true })
74
+ else if (out[slot] == null) out[slot] = { windowsHide: true }
75
+ return out
76
+ }
77
+
78
+ const customPromisify = promisify.custom
79
+
80
+ function wrap<T extends (...args: any[]) => any>(
81
+ hide: (args: unknown[]) => unknown[],
82
+ getFn: () => T,
83
+ ): T {
84
+ const wrapped = function (this: unknown, ...args: unknown[]) {
85
+ const fn = getFn()
86
+ return Reflect.apply(fn, this, hide(args))
87
+ }
88
+ // `util.promisify(exec)` returns this instead of guessing a callback slot.
89
+ // Delegate to the underlying custom so Node's encoding overloads stay intact,
90
+ // and read it at call time so tests that mock `child_process` are honored.
91
+ Object.defineProperty(wrapped, customPromisify, {
92
+ enumerable: false,
93
+ configurable: true,
94
+ writable: false,
95
+ value(this: unknown, ...args: unknown[]) {
96
+ const fn = getFn() as T & { [customPromisify]?: (...a: unknown[]) => unknown }
97
+ const custom = fn[customPromisify]
98
+ if (typeof custom === 'function') return Reflect.apply(custom, fn, hide(args))
99
+ const promisified = promisify(fn as (...a: never[]) => unknown)
100
+ return Reflect.apply(promisified, this, hide(args))
101
+ },
102
+ })
103
+ return wrapped as T
104
+ }
105
+
106
+ export const spawn: typeof cp.spawn = wrap(hideSpawnArgs, () => cp.spawn)
107
+ export const spawnSync: typeof cp.spawnSync = wrap(hideSpawnArgs, () => cp.spawnSync)
108
+ export const exec: typeof cp.exec = wrap(hideExecArgs, () => cp.exec)
109
+ export const execSync: typeof cp.execSync = wrap(hideExecArgs, () => cp.execSync)
110
+ export const execFile: typeof cp.execFile = wrap(hideSpawnArgs, () => cp.execFile)
111
+ export const execFileSync: typeof cp.execFileSync = wrap(hideSpawnArgs, () => cp.execFileSync)
112
+ export const fork: typeof cp.fork = wrap(hideSpawnArgs, () => cp.fork)
113
+
114
+ export type * from 'node:child_process'
@@ -34,7 +34,7 @@
34
34
  * - No localhost listener, no protocol handler, no deep links —
35
35
  * works behind firewalls / over SSH / from a remote tmux session.
36
36
  */
37
- import { spawn } from 'node:child_process';
37
+ import { spawn } from './child-process.ts';
38
38
  import { hostname, platform as osPlatform, arch } from 'node:os';
39
39
  import pc from 'picocolors';
40
40
  import { workerUserAgent } from './user-agent.ts';
@@ -39,7 +39,7 @@
39
39
  // apps/desktop/src/db-recovery.ts (kept as a separate copy there because
40
40
  // the Electron main process is bundled in a different runtime context).
41
41
 
42
- import { execFileSync } from 'node:child_process';
42
+ import { execFileSync } from './child-process.ts';
43
43
  import { existsSync, copyFileSync } from 'node:fs';
44
44
  import path from 'node:path';
45
45
  import { homedir, platform } from 'node:os';
@@ -20,7 +20,7 @@
20
20
  * `gitIsAvailable()` probe.
21
21
  */
22
22
 
23
- import { spawn, execFile } from 'node:child_process';
23
+ import { spawn, execFile } from './child-process.ts';
24
24
  import { existsSync } from 'node:fs';
25
25
  import { join } from 'node:path';
26
26
  import { promisify } from 'node:util';
@@ -14,7 +14,7 @@
14
14
  * The detached codepath doesn't need it — once `child.unref()` runs the
15
15
  * CLI exits and the child owns its own PID file.
16
16
  */
17
- import { spawn, type ChildProcess } from 'node:child_process';
17
+ import { spawn, type ChildProcess } from './child-process.ts';
18
18
  import { existsSync, openSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
19
19
  import { PID_FILE, WORKER_LOG, WORKER_ERR, ensureHome } from './paths.ts';
20
20
 
@@ -21,7 +21,7 @@
21
21
  * The JS-heap env cap (see {@link applyHeapEnvCap}) is a secondary hint layered
22
22
  * on top everywhere — OS-level limits are the real enforcement.
23
23
  */
24
- import { spawnSync } from 'node:child_process';
24
+ import { spawnSync } from './child-process.ts';
25
25
  import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
26
26
  import { join } from 'node:path';
27
27
 
@@ -24,7 +24,7 @@
24
24
  * tar dep just for one-shot installs. macOS, Linux, and Windows
25
25
  * (since 1803) all ship a usable `tar` on PATH.
26
26
  */
27
- import { spawn } from 'node:child_process';
27
+ import { spawn } from './child-process.ts';
28
28
  import { createHash } from 'node:crypto';
29
29
  import {
30
30
  chmodSync,
@@ -31,7 +31,7 @@
31
31
  * - No Prisma reads. All policy comes from the spawn config.
32
32
  * - No security policy build. Cloud signs the policy and sends it.
33
33
  */
34
- import { type ChildProcess, spawn, spawnSync } from 'node:child_process';
34
+ import { type ChildProcess, spawn, spawnSync } from './child-process.ts';
35
35
  import { createHmac, randomBytes } from 'node:crypto';
36
36
  import { existsSync, mkdirSync, readdirSync } from 'node:fs';
37
37
  import { createConnection } from 'node:net';