@shogo-ai/worker 1.13.0 → 1.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -1,29 +1,29 @@
1
- import{createRequire as V$}from"node:module";var Z$=Object.defineProperty;var z$=($)=>$;function J$($,X){this[$]=z$.bind(null,X)}var K$=($,X)=>{for(var Y in X)Z$($,Y,{get:X[Y],enumerable:!0,configurable:!0,set:J$.bind(X,Y)})};var q$=($,X)=>()=>($&&(X=$($=0)),X);var Q$=V$(import.meta.url);var J2={};K$(J2,{runGit:()=>T,isGitRepo:()=>t,gitIsAvailable:()=>D0,gitFetchUnshallow:()=>w0,gitFetchAndReset:()=>s,commitAndPush:()=>j$,cloneProject:()=>x0,buildGitUrl:()=>e});import{spawn as b$,execFile as v$}from"node:child_process";import{existsSync as T0}from"node:fs";import{join as y0}from"node:path";import{promisify as m$}from"node:util";async function D0($=!1){if(r!==null&&!$)return r;try{await h$("git",["--version"],{timeout:3000}),r=!0}catch{r=!1}return r}function e($,X){return`${$.replace(/\/+$/,"")}/api/projects/${X}/git`}function T($,X={}){let{cwd:Y,env:Z,timeoutMs:z=300000,logger:J}=X;return new Promise((K,q)=>{let Q=b$("git",$,{cwd:Y,env:{...process.env,...Z},stdio:["ignore","pipe","pipe"]}),V="",H="",w=[],W=[];Q.stdout.setEncoding("utf-8"),Q.stderr.setEncoding("utf-8"),Q.stdout.on("data",(O)=>{if(w.push(O),J)J.log(O.trimEnd())}),Q.stderr.on("data",(O)=>{if(W.push(O),J)J.warn(O.trimEnd())});let C=setTimeout(()=>{try{Q.kill("SIGKILL")}catch{}q(Error(`git ${$[0]} timed out after ${z}ms`))},z);Q.on("error",(O)=>{clearTimeout(C),q(O)}),Q.on("close",(O)=>{clearTimeout(C),V=w.join(""),H=W.join("");let A=O??-1;if(A===0)K({stdout:V,stderr:H,exitCode:A});else{let k=Error(`git ${$[0]} exited with code ${A}: ${H.slice(0,500)}`);k.exitCode=A,k.stdout=V,k.stderr=H,q(k)}})})}async function x0($){let{apiUrl:X,apiKey:Y,projectId:Z,localDir:z,shallow:J=!0,logger:K,timeoutMs:q}=$;if(T0(y0(z,".git")))throw Error(`cloneProject: ${z}/.git already exists; use gitFetchAndReset instead`);let Q=e(X,Z),V=["-c",`http.extraHeader=Authorization: Bearer ${Y}`,"clone"];if(J)V.push("--depth=1");return V.push(Q,z),await T(V,{logger:K,timeoutMs:q}),{commitSha:(await T(["rev-parse","HEAD"],{cwd:z,logger:K,timeoutMs:1e4})).stdout.trim()}}async function s($){let{apiUrl:X,apiKey:Y,projectId:Z,localDir:z,branch:J="HEAD",logger:K,timeoutMs:q}=$,Q=e(X,Z),V=["-c",`http.extraHeader=Authorization: Bearer ${Y}`];return await T([...V,"fetch",Q,J],{cwd:z,logger:K,timeoutMs:q}),await T(["reset","--hard","FETCH_HEAD"],{cwd:z,logger:K,timeoutMs:q}),{commitSha:(await T(["rev-parse","HEAD"],{cwd:z,logger:K,timeoutMs:1e4})).stdout.trim()}}async function w0($){let{apiUrl:X,apiKey:Y,projectId:Z,localDir:z,logger:J,timeoutMs:K}=$,q=e(X,Z),Q=["-c",`http.extraHeader=Authorization: Bearer ${Y}`];if(T0(y0(z,".git","shallow")))await T([...Q,"fetch","--unshallow",q],{cwd:z,logger:J,timeoutMs:K})}async function j$($){let{apiUrl:X,apiKey:Y,projectId:Z,localDir:z,message:J,branch:K="HEAD",authorEmail:q,authorName:Q,logger:V,timeoutMs:H}=$,w=e(X,Z),W={};if(q)W.GIT_AUTHOR_EMAIL=q,W.GIT_COMMITTER_EMAIL=q;if(Q)W.GIT_AUTHOR_NAME=Q,W.GIT_COMMITTER_NAME=Q;await T(["add","-A"],{cwd:z,env:W,logger:V,timeoutMs:H});let C=!1;try{await T(["diff","--cached","--quiet"],{cwd:z,env:W,logger:V,timeoutMs:H})}catch{C=!0}if(!C)return{committed:!1};await T(["commit","-m",J,"--no-verify"],{cwd:z,env:W,logger:V,timeoutMs:H});let A=(await T(["rev-parse","HEAD"],{cwd:z,env:W,logger:V,timeoutMs:1e4})).stdout.trim(),k=["-c",`http.extraHeader=Authorization: Bearer ${Y}`];return await T([...k,"push",w,K],{cwd:z,env:W,logger:V,timeoutMs:H}),{committed:!0,commitSha:A}}function t($){return T0(y0($,".git"))}var h$,r=null;var W0=q$(()=>{h$=m$(v$)});import{Command as N1}from"commander";import X$ from"picocolors";import G from"picocolors";import{existsSync as k6}from"node:fs";import{readFileSync as w$,writeFileSync as W$,existsSync as k$,chmodSync as G$}from"node:fs";import{hostname as C$}from"node:os";import{homedir as H$}from"node:os";import{join as D}from"node:path";import{mkdirSync as J0}from"node:fs";var m=D(H$(),".shogo"),h=D(m,"config.json"),S1=D(m,"credentials.json"),p=D(m,"device-id"),c=D(m,"worker.pid"),U0=D(m,"logs"),K0=D(U0,"worker.log"),q0=D(U0,"worker.err.log"),o=D(m,"runtime"),R=D(o,process.platform==="win32"?"agent-runtime.exe":"agent-runtime"),a=D(o,"version.json"),V0=D(m,"projects");function n($,X=V0){return D(X,$)}function j(){J0(m,{recursive:!0,mode:448}),J0(U0,{recursive:!0,mode:448})}function p0(){j(),J0(o,{recursive:!0,mode:448})}function c0($=V0){j(),J0($,{recursive:!0,mode:448})}var F0={cloudUrl:"https://studio.shogo.ai",port:8002,projectsDir:V0};function I(){if(!k$(h))return{};try{return JSON.parse(w$(h,"utf-8"))}catch($){throw Error(`Corrupt config at ${h}: ${$.message}`)}}function l($){j(),W$(h,JSON.stringify($,null,2),{mode:384}),G$(h,384)}function o0($,X){return{...$,...Object.fromEntries(Object.entries(X).filter(([,Y])=>Y!==void 0))}}function _($={}){let X=I(),Y={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},Z=o0(o0(X,Y),$);if(!Z.apiKey)throw Error("No API key. Run `shogo login` or pass --api-key / set SHOGO_API_KEY.");return{apiKey:Z.apiKey,cloudUrl:Z.cloudUrl??F0.cloudUrl,name:Z.name??C$(),workerDir:Z.workerDir??process.cwd(),port:Z.port??F0.port,projectsDir:Z.projectsDir??F0.projectsDir}}import{spawn as L$}from"node:child_process";import{existsSync as O$,openSync as a0,readFileSync as B$,unlinkSync as A$,writeFileSync as U$}from"node:fs";function Q0(){if(!O$(c))return null;let $=B$(c,"utf-8").trim(),X=parseInt($,10);return Number.isFinite(X)?X:null}function H0($){try{return process.kill($,0),!0}catch{return!1}}function N0(){try{A$(c)}catch{}}function l0($){j();let X=Q0();if(X&&H0(X))throw Error(`Worker already running (pid=${X}). Run \`shogo worker stop\` first.`);if(X)N0();let Y=$.inheritStdio?["ignore","inherit","inherit"]:["ignore",a0(K0,"a"),a0(q0,"a")],Z=L$($.runner,[$.entry],{cwd:$.cwd,env:$.env,detached:!!$.detach,stdio:Y});if(!Z.pid)throw Error("Failed to spawn worker process.");if(U$(c,String(Z.pid),{mode:384}),$.detach)Z.unref();return{pid:Z.pid,child:Z}}function r0($="SIGTERM"){let X=Q0();if(!X)return{killedPid:null};if(!H0(X))return N0(),{killedPid:null};try{process.kill(X,$)}catch{}return N0(),{killedPid:X}}import x from"picocolors";import{existsSync as N$}from"node:fs";import{request as F$}from"node:http";function s0($,X=process.env){let Y=(J)=>J&&J.trim().length>0?J.trim():void 0,Z=Y($);if(Z)return{url:e0(Z),source:"flag"};let z=[["HTTPS_PROXY",X.HTTPS_PROXY],["https_proxy",X.https_proxy],["HTTP_PROXY",X.HTTP_PROXY],["http_proxy",X.http_proxy]];for(let[J,K]of z){let q=Y(K);if(q)return{url:e0(q),source:J}}return null}function e0($){if(/^https?:\/\//i.test($))return $;return`http://${$}`}function M0($,X){if(!X)return $;return{...$,HTTPS_PROXY:$.HTTPS_PROXY??X.url,https_proxy:$.https_proxy??X.url,HTTP_PROXY:$.HTTP_PROXY??X.url,http_proxy:$.http_proxy??X.url}}async function t0($,X="api.shogo.ai",Y=5000){return new Promise((Z)=>{let z=!1,J=(K)=>{if(z)return;z=!0,Z(K)};try{let K=new URL($.url),q=K.hostname,Q=K.port?parseInt(K.port,10):K.protocol==="https:"?443:80,V=F$({method:"CONNECT",host:q,port:Q,path:`${X}:443`,timeout:Y});V.on("connect",(H)=>{V.destroy();let w=H.statusCode??0;if(w===200)J({ok:!0,detail:`CONNECT ${X}:443 → 200 (via ${q}:${Q})`});else if(w===407)J({ok:!1,detail:`407 Proxy Authentication Required — check credentials in ${$.source}`});else J({ok:!1,detail:`CONNECT → HTTP ${w}`})}),V.on("timeout",()=>{V.destroy(),J({ok:!1,detail:`timeout after ${Y}ms`})}),V.on("error",(H)=>{let w=H.code?` (${H.code})`:"";J({ok:!1,detail:`${H.message}${w}`})}),V.end()}catch(K){J({ok:!1,detail:K?.message??"unknown error"})}})}function $2($){let X;try{X=new URL($)}catch{return[]}let Y=X.host,Z=X.protocol.replace(":","")||"https",z=X.hostname.split("."),J=z.length>=2?z.slice(-2).join("."):X.hostname;return[{url:`${Z}://${Y}`,host:Y,purpose:"control",criticality:"fatal"},{url:`${Z}://api-direct.${J}`,host:`api-direct.${J}`,purpose:"tunnel-direct",criticality:"graceful"},{url:`${Z}://artifacts.${J}`,host:`artifacts.${J}`,purpose:"artifacts",criticality:"graceful"}]}var M$=5000;async function T$($,X){try{let Y=new AbortController,Z=setTimeout(()=>Y.abort(),X),z=await fetch(`${$.replace(/\/$/,"")}/health`,{signal:Y.signal}).catch(()=>null);if(clearTimeout(Z),!z)return{ok:!1,detail:"no response (firewall? DNS?)"};return{ok:!0,detail:`HTTP ${z.status}`}}catch(Y){return{ok:!1,detail:Y?.message??"unknown error"}}}var X2=($)=>{let X=[{name:"Runtime (node >= 20)",criticality:"fatal",async run(){let[Z]=process.versions.node.split(".").map(Number);return(Z??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 N$($.workerDir)?{ok:!0,detail:$.workerDir}:{ok:!1,detail:`${$.workerDir} does not exist`}}}];if($.proxy)X.push({name:`Proxy reachable (${y$($.proxy.url)})`,criticality:"fatal",async run(){return t0($.proxy)}});let Y=$2($.cloudUrl);for(let Z of Y)X.push({name:`Reach ${Z.host}${Z.purpose!=="control"?x.dim(` (${Z.purpose})`):""}`,criticality:Z.criticality,run:()=>T$(Z.url,M$)});return X.push({name:"API key valid",criticality:"fatal",async run(){try{let Z=await fetch(`${$.cloudUrl}/api/instances/heartbeat`,{method:"POST",headers:{"content-type":"application/json","x-api-key":$.apiKey},body:JSON.stringify({hostname:"preflight",name:"preflight",os:"preflight",arch:"preflight",metadata:{preflight:!0}})});if(Z.status===401||Z.status===403)return{ok:!1,detail:`HTTP ${Z.status} — key rejected`};return Z.ok?{ok:!0,detail:`HTTP ${Z.status}`}:{ok:!1,detail:`HTTP ${Z.status}`}}catch(Z){return{ok:!1,detail:Z?.message??"unknown error"}}}}),X};function y$($){try{return new URL($).host}catch{return $}}async function Y2($){console.log(x.bold(`
1
+ import{createRequire as w$}from"node:module";var z$=Object.defineProperty;var K$=($)=>$;function J$($,X){this[$]=K$.bind(null,X)}var V$=($,X)=>{for(var Y in X)z$($,Y,{get:X[Y],enumerable:!0,configurable:!0,set:J$.bind(X,Y)})};var q$=($,X)=>()=>($&&(X=$($=0)),X);var Q$=w$(import.meta.url);var J2={};V$(J2,{runGit:()=>y,isGitRepo:()=>$0,gitIsAvailable:()=>S0,gitFetchUnshallow:()=>W0,gitFetchAndReset:()=>t,commitAndPush:()=>I$,cloneProject:()=>R0,buildGitUrl:()=>s});import{spawn as v$,execFile as m$}from"node:child_process";import{existsSync as M0}from"node:fs";import{join as T0}from"node:path";import{promisify as h$}from"node:util";async function S0($=!1){if(e!==null&&!$)return e;try{await j$("git",["--version"],{timeout:3000}),e=!0}catch{e=!1}return e}function s($,X){return`${$.replace(/\/+$/,"")}/api/projects/${X}/git`}function y($,X={}){let{cwd:Y,env:Z,timeoutMs:z=300000,logger:K}=X;return new Promise((J,V)=>{let w=v$("git",$,{cwd:Y,env:{...process.env,...Z},stdio:["ignore","pipe","pipe"]}),q="",Q="",H=[],W=[];w.stdout.setEncoding("utf-8"),w.stderr.setEncoding("utf-8"),w.stdout.on("data",(L)=>{if(H.push(L),K)K.log(L.trimEnd())}),w.stderr.on("data",(L)=>{if(W.push(L),K)K.warn(L.trimEnd())});let G=setTimeout(()=>{try{w.kill("SIGKILL")}catch{}V(Error(`git ${$[0]} timed out after ${z}ms`))},z);w.on("error",(L)=>{clearTimeout(G),V(L)}),w.on("close",(L)=>{clearTimeout(G),q=H.join(""),Q=W.join("");let A=L??-1;if(A===0)J({stdout:q,stderr:Q,exitCode:A});else{let k=Error(`git ${$[0]} exited with code ${A}: ${Q.slice(0,500)}`);k.exitCode=A,k.stdout=q,k.stderr=Q,V(k)}})})}async function R0($){let{apiUrl:X,apiKey:Y,projectId:Z,localDir:z,shallow:K=!0,logger:J,timeoutMs:V}=$;if(M0(T0(z,".git")))throw Error(`cloneProject: ${z}/.git already exists; use gitFetchAndReset instead`);let w=s(X,Z),q=["-c",`http.extraHeader=Authorization: Bearer ${Y}`,"clone"];if(K)q.push("--depth=1");return q.push(w,z),await y(q,{logger:J,timeoutMs:V}),{commitSha:(await y(["rev-parse","HEAD"],{cwd:z,logger:J,timeoutMs:1e4})).stdout.trim()}}async function t($){let{apiUrl:X,apiKey:Y,projectId:Z,localDir:z,branch:K="HEAD",logger:J,timeoutMs:V}=$,w=s(X,Z),q=["-c",`http.extraHeader=Authorization: Bearer ${Y}`];return await y([...q,"fetch",w,K],{cwd:z,logger:J,timeoutMs:V}),await y(["reset","--hard","FETCH_HEAD"],{cwd:z,logger:J,timeoutMs:V}),{commitSha:(await y(["rev-parse","HEAD"],{cwd:z,logger:J,timeoutMs:1e4})).stdout.trim()}}async function W0($){let{apiUrl:X,apiKey:Y,projectId:Z,localDir:z,logger:K,timeoutMs:J}=$,V=s(X,Z),w=["-c",`http.extraHeader=Authorization: Bearer ${Y}`];if(M0(T0(z,".git","shallow")))await y([...w,"fetch","--unshallow",V],{cwd:z,logger:K,timeoutMs:J})}async function I$($){let{apiUrl:X,apiKey:Y,projectId:Z,localDir:z,message:K,branch:J="HEAD",authorEmail:V,authorName:w,logger:q,timeoutMs:Q}=$,H=s(X,Z),W={};if(V)W.GIT_AUTHOR_EMAIL=V,W.GIT_COMMITTER_EMAIL=V;if(w)W.GIT_AUTHOR_NAME=w,W.GIT_COMMITTER_NAME=w;await y(["add","-A"],{cwd:z,env:W,logger:q,timeoutMs:Q});let G=!1;try{await y(["diff","--cached","--quiet"],{cwd:z,env:W,logger:q,timeoutMs:Q})}catch{G=!0}if(!G)return{committed:!1};await y(["commit","-m",K,"--no-verify"],{cwd:z,env:W,logger:q,timeoutMs:Q});let A=(await y(["rev-parse","HEAD"],{cwd:z,env:W,logger:q,timeoutMs:1e4})).stdout.trim(),k=["-c",`http.extraHeader=Authorization: Bearer ${Y}`];return await y([...k,"push",H,J],{cwd:z,env:W,logger:q,timeoutMs:Q}),{committed:!0,commitSha:A}}function $0($){return M0(T0($,".git"))}var j$,e=null;var k0=q$(()=>{j$=h$(m$)});import{Command as _1}from"commander";import Y$ from"picocolors";import O from"picocolors";import{existsSync as O6}from"node:fs";import{readFileSync as W$,writeFileSync as k$,existsSync as O$,chmodSync as G$}from"node:fs";import{hostname as C$}from"node:os";import{homedir as H$}from"node:os";import{join as T}from"node:path";import{mkdirSync as J0}from"node:fs";var m=T(H$(),".shogo"),h=T(m,"config.json"),x1=T(m,"credentials.json"),c=T(m,"device-id"),o=T(m,"worker.pid"),N0=T(m,"logs"),V0=T(N0,"worker.log"),q0=T(N0,"worker.err.log"),a=T(m,"runtime"),x=T(a,process.platform==="win32"?"agent-runtime.exe":"agent-runtime"),l=T(a,"version.json"),w0=T(m,"projects");function i($,X=w0){return T(X,$)}function j(){J0(m,{recursive:!0,mode:448}),J0(N0,{recursive:!0,mode:448})}function c0(){j(),J0(a,{recursive:!0,mode:448})}function o0($=w0){j(),J0($,{recursive:!0,mode:448})}var F0={cloudUrl:"https://studio.shogo.ai",port:8002,projectsDir:w0};function I(){if(!O$(h))return{};try{return JSON.parse(W$(h,"utf-8"))}catch($){throw Error(`Corrupt config at ${h}: ${$.message}`)}}function r($){j(),k$(h,JSON.stringify($,null,2),{mode:384}),G$(h,384)}function a0($,X){return{...$,...Object.fromEntries(Object.entries(X).filter(([,Y])=>Y!==void 0))}}function P($={}){let X=I(),Y={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},Z=a0(a0(X,Y),$);if(!Z.apiKey)throw Error("No API key. Run `shogo login` or pass --api-key / set SHOGO_API_KEY.");return{apiKey:Z.apiKey,cloudUrl:Z.cloudUrl??F0.cloudUrl,name:Z.name??C$(),workerDir:Z.workerDir??process.cwd(),port:Z.port??F0.port,projectsDir:Z.projectsDir??F0.projectsDir}}import{spawn as L$}from"node:child_process";import{existsSync as B$,openSync as l0,readFileSync as A$,unlinkSync as U$,writeFileSync as N$}from"node:fs";function Q0(){if(!B$(o))return null;let $=A$(o,"utf-8").trim(),X=parseInt($,10);return Number.isFinite(X)?X:null}function H0($){try{return process.kill($,0),!0}catch{return!1}}function _0(){try{U$(o)}catch{}}function r0($){j();let X=Q0();if(X&&H0(X))throw Error(`Worker already running (pid=${X}). Run \`shogo worker stop\` first.`);if(X)_0();let Y=$.inheritStdio?["ignore","inherit","inherit"]:["ignore",l0(V0,"a"),l0(q0,"a")],Z=L$($.runner,[$.entry],{cwd:$.cwd,env:$.env,detached:!!$.detach,stdio:Y});if(!Z.pid)throw Error("Failed to spawn worker process.");if(N$(o,String(Z.pid),{mode:384}),$.detach)Z.unref();return{pid:Z.pid,child:Z}}function e0($="SIGTERM"){let X=Q0();if(!X)return{killedPid:null};if(!H0(X))return _0(),{killedPid:null};try{process.kill(X,$)}catch{}return _0(),{killedPid:X}}import S from"picocolors";import{existsSync as _$}from"node:fs";import{request as F$}from"node:http";function t0($,X=process.env){let Y=(K)=>K&&K.trim().length>0?K.trim():void 0,Z=Y($);if(Z)return{url:s0(Z),source:"flag"};let z=[["HTTPS_PROXY",X.HTTPS_PROXY],["https_proxy",X.https_proxy],["HTTP_PROXY",X.HTTP_PROXY],["http_proxy",X.http_proxy]];for(let[K,J]of z){let V=Y(J);if(V)return{url:s0(V),source:K}}return null}function s0($){if(/^https?:\/\//i.test($))return $;return`http://${$}`}function y0($,X){if(!X)return $;return{...$,HTTPS_PROXY:$.HTTPS_PROXY??X.url,https_proxy:$.https_proxy??X.url,HTTP_PROXY:$.HTTP_PROXY??X.url,http_proxy:$.http_proxy??X.url}}async function $2($,X="api.shogo.ai",Y=5000){return new Promise((Z)=>{let z=!1,K=(J)=>{if(z)return;z=!0,Z(J)};try{let J=new URL($.url),V=J.hostname,w=J.port?parseInt(J.port,10):J.protocol==="https:"?443:80,q=F$({method:"CONNECT",host:V,port:w,path:`${X}:443`,timeout:Y});q.on("connect",(Q)=>{q.destroy();let H=Q.statusCode??0;if(H===200)K({ok:!0,detail:`CONNECT ${X}:443 → 200 (via ${V}:${w})`});else if(H===407)K({ok:!1,detail:`407 Proxy Authentication Required — check credentials in ${$.source}`});else K({ok:!1,detail:`CONNECT → HTTP ${H}`})}),q.on("timeout",()=>{q.destroy(),K({ok:!1,detail:`timeout after ${Y}ms`})}),q.on("error",(Q)=>{let H=Q.code?` (${Q.code})`:"";K({ok:!1,detail:`${Q.message}${H}`})}),q.end()}catch(J){K({ok:!1,detail:J?.message??"unknown error"})}})}function X2($){let X;try{X=new URL($)}catch{return[]}let Y=X.host,Z=X.protocol.replace(":","")||"https",z=X.hostname.split("."),K=z.length>=2?z.slice(-2).join("."):X.hostname;return[{url:`${Z}://${Y}`,host:Y,purpose:"control",criticality:"fatal"},{url:`${Z}://api-direct.${K}`,host:`api-direct.${K}`,purpose:"tunnel-direct",criticality:"graceful"},{url:`${Z}://artifacts.${K}`,host:`artifacts.${K}`,purpose:"artifacts",criticality:"graceful"}]}var y$=5000;async function M$($,X){try{let Y=new AbortController,Z=setTimeout(()=>Y.abort(),X),z=await fetch(`${$.replace(/\/$/,"")}/health`,{signal:Y.signal}).catch(()=>null);if(clearTimeout(Z),!z)return{ok:!1,detail:"no response (firewall? DNS?)"};return{ok:!0,detail:`HTTP ${z.status}`}}catch(Y){return{ok:!1,detail:Y?.message??"unknown error"}}}var Y2=($)=>{let X=[{name:"Runtime (node >= 20)",criticality:"fatal",async run(){let[Z]=process.versions.node.split(".").map(Number);return(Z??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 _$($.workerDir)?{ok:!0,detail:$.workerDir}:{ok:!1,detail:`${$.workerDir} does not exist`}}}];if($.proxy)X.push({name:`Proxy reachable (${T$($.proxy.url)})`,criticality:"fatal",async run(){return $2($.proxy)}});let Y=X2($.cloudUrl);for(let Z of Y)X.push({name:`Reach ${Z.host}${Z.purpose!=="control"?S.dim(` (${Z.purpose})`):""}`,criticality:Z.criticality,run:()=>M$(Z.url,y$)});return X.push({name:"API key valid",criticality:"fatal",async run(){try{let Z=await fetch(`${$.cloudUrl}/api/instances/heartbeat`,{method:"POST",headers:{"content-type":"application/json","x-api-key":$.apiKey},body:JSON.stringify({hostname:"preflight",name:"preflight",os:"preflight",arch:"preflight",metadata:{preflight:!0}})});if(Z.status===401||Z.status===403)return{ok:!1,detail:`HTTP ${Z.status} — key rejected`};return Z.ok?{ok:!0,detail:`HTTP ${Z.status}`}:{ok:!1,detail:`HTTP ${Z.status}`}}catch(Z){return{ok:!1,detail:Z?.message??"unknown error"}}}}),X};function T$($){try{return new URL($).host}catch{return $}}async function Z2($){console.log(S.bold(`
2
2
  Shogo Worker — Preflight
3
- `));let X=!1,Y=0;for(let Z of $){process.stdout.write(` ${x.dim("...")} ${Z.name}`);let z=await Z.run();if(process.stdout.write("\r"),z.ok)console.log(` ${x.green("✓")} ${Z.name}${z.detail?x.dim(` — ${z.detail}`):""}`);else if(Z.criticality==="graceful")console.log(` ${x.yellow("◦")} ${Z.name}${z.detail?x.dim(` — ${z.detail}`):""}${x.yellow(" (optional — worker will still start)")}`),Y++;else console.log(` ${x.red("✗")} ${Z.name}${z.detail?x.dim(` — ${z.detail}`):""}`),X=!0}if(!X&&Y===0)console.log(x.green(`
3
+ `));let X=!1,Y=0;for(let Z of $){process.stdout.write(` ${S.dim("...")} ${Z.name}`);let z=await Z.run();if(process.stdout.write("\r"),z.ok)console.log(` ${S.green("✓")} ${Z.name}${z.detail?S.dim(` — ${z.detail}`):""}`);else if(Z.criticality==="graceful")console.log(` ${S.yellow("◦")} ${Z.name}${z.detail?S.dim(` — ${z.detail}`):""}${S.yellow(" (optional — worker will still start)")}`),Y++;else console.log(` ${S.red("✗")} ${Z.name}${z.detail?S.dim(` — ${z.detail}`):""}`),X=!0}if(!X&&Y===0)console.log(S.green(`
4
4
  All checks passed.
5
- `));else if(!X)console.log(x.yellow(`
5
+ `));else if(!X)console.log(S.yellow(`
6
6
  Starting with ${Y} optional host(s) blocked. See docs/my-machines-networking.md.
7
- `));else console.log(x.red(`
7
+ `));else console.log(S.red(`
8
8
  Preflight failed — fix blocking issues before starting.
9
- `));return!X}import{accessSync as D$,constants as x$,existsSync as P$}from"node:fs";import{delimiter as S$,join as R$}from"node:path";function u($={}){let X=$.env??process.env,Y=_$($);for(let J of Y)if(z2(J.path))return J;let Z=$.systemBinName??Z2(),z=E$(Z,X.PATH);if(z)return{path:z,source:"path"};return null}function _$($){let X=$.env??process.env,Y=[];if($.flag&&$.flag.trim())Y.push({path:$.flag.trim(),source:"flag"});let Z=X.SHOGO_AGENT_RUNTIME_BIN?.trim();if(Z)Y.push({path:Z,source:"env"});return Y.push({path:R,source:"home"}),Y}function Z2(){return process.platform==="win32"?"shogo-agent-runtime.exe":"shogo-agent-runtime"}function z2($){if(!P$($))return!1;try{if(process.platform==="win32")return!0;return D$($,x$.X_OK),!0}catch{return!1}}function E$($,X){if(!X)return null;for(let Y of X.split(S$)){if(!Y)continue;let Z=R$(Y,$);if(z2(Z))return Z}return null}function d($={}){let X=$.env??process.env,Y=[];if(Y.push("Error: agent-runtime binary not found."),Y.push(""),Y.push("Looked in (priority order):"),$.flag)Y.push(` --runtime-bin ${$.flag}`);if(X.SHOGO_AGENT_RUNTIME_BIN)Y.push(` $SHOGO_AGENT_RUNTIME_BIN = ${X.SHOGO_AGENT_RUNTIME_BIN}`);return Y.push(` ${R} (default install location)`),Y.push(` ${Z2()} on $PATH`),Y.push(""),Y.push("Fix:"),Y.push(" - Run `shogo runtime install` to download the latest binary, or"),Y.push(" - Pass `--runtime-bin <path>` to point at an existing build."),Y.join(`
10
- `)}import{spawn as p$,spawnSync as c$}from"node:child_process";import{createHmac as C2,randomBytes as o$}from"node:crypto";import{existsSync as P0,mkdirSync as q2,readdirSync as a$}from"node:fs";import{createConnection as l$}from"node:net";import{tmpdir as r$}from"node:os";import{dirname as e$,join as X0}from"node:path";import{CloudFileTransport as S0}from"@shogo-ai/sdk/cloud-file-transport";import{watch as K2,statSync as I$}from"node:fs";import{relative as XX,sep as u$,posix as f$}from"node:path";var d$=1500,n$=new Set(["node_modules",".git","dist","build",".vite",".cache"]),i$=[".shogo/"];function g$($){if(!$)return!0;return $.split("/").some((X)=>n$.has(X))}class $0{rootDir;transport;debounceMs;logger;onFlush;mode;git;commitAndPush;watcher=null;pending=new Set;timer=null;flushing=!1;stopped=!1;constructor($){if(this.rootDir=$.rootDir,this.transport=$.transport,this.debounceMs=$.debounceMs??d$,this.logger=$.logger??console,this.onFlush=$.onFlush,this.mode=$.mode??"files",this.mode==="git"&&!$.git)throw Error('CloudSyncWatcher: mode: "git" requires the `git` option block');this.git=$.git,this.commitAndPush=$.commitAndPush??(async(X)=>{return(await Promise.resolve().then(() => (W0(),J2))).commitAndPush(X)})}start(){if(this.watcher)return;if(this.stopped)throw Error("CloudSyncWatcher: already stopped, build a fresh instance");try{this.watcher=K2(this.rootDir,{recursive:!0},($,X)=>{if(!X)return;this.handleEvent(String(X))})}catch($){this.logger.warn(`[CloudSyncWatcher] recursive watch failed (${$?.message??$}); falling back to root-only watch.`),this.watcher=K2(this.rootDir,(X,Y)=>{if(!Y)return;this.handleEvent(String(Y))})}}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($){if(this.stopped)return;let X=$.split(u$).join(f$.sep);if(g$(X))return;try{let Y=`${this.rootDir}/${X}`;if(I$(Y).isDirectory())return}catch{return}this.pending.add(X),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 $=Array.from(this.pending);this.pending.clear();try{if(this.mode==="git")await this.flushGit($);else await this.flushFiles($)}catch(X){this.logger.error(`[CloudSyncWatcher] flush failed: ${X?.message??X}`);for(let Y of $)this.pending.add(Y);this.scheduleFlush()}finally{this.flushing=!1}}async flushFiles($){if($.length===0)return;let X=await this.transport.uploadFiles($);if(this.onFlush?.({uploaded:$,errors:X.errors.length}),X.errors.length>0)for(let Y of X.errors)this.logger.warn(`[CloudSyncWatcher] upload ${Y.path}: ${Y.message}`)}async flushGit($){if(!this.git)throw Error("CloudSyncWatcher: missing git options");let X=[],Y=[];for(let J of $)if(i$.some((K)=>J===K.slice(0,-1)||J.startsWith(K)))X.push(J);else Y.push(J);if(X.length>0){let J=await this.transport.uploadFiles(X);if(J.errors.length>0)for(let K of J.errors)this.logger.warn(`[CloudSyncWatcher] upload ${K.path}: ${K.message}`)}let Z=!1,z;if(Y.length>0){let J=`auto: ${new Date().toISOString()}`;try{let K=await this.commitAndPush({apiUrl:this.git.apiUrl,apiKey:this.git.apiKey,projectId:this.git.projectId,localDir:this.rootDir,message:J,branch:this.git.branch,authorEmail:this.git.authorEmail,authorName:this.git.authorName,logger:this.logger});Z=K.committed,z=K.commitSha}catch(K){throw K}}this.onFlush?.({uploaded:$,errors:0,committed:Z,commitSha:z})}}W0();var R0=37100,_0=37900,V2=1,s$=2,k0=16,t$=900000,$6=30000,X6=1000,Y6=60000,Q2=8,H2=300000,Z6=60000,z6=500,J6=30000,w2=25000,K6=5000,q6=500,V6=($)=>({command:$,args:[]}),E0=null;function L2(){if(!E0)E0=o$(32).toString("hex");return E0}function W2($){return C2("sha256",L2()).update(`runtime:${$}`).digest("hex")}function Q6($){return C2("sha256",L2()).update(`webhook:${$}`).digest("hex")}function k2($){let X=$.indexOf("?");if(X===-1)return{pathname:$,search:""};return{pathname:$.slice(0,X),search:$.slice(X)}}function G2($){try{return a$($).length===0}catch{return!0}}function b0($,X,Y){let Z=[];switch(Z.push(`Cannot spawn agent-runtime for project ${$}: no workspace directory available.`),Z.push(""),X){case"no-auto-pull-config":Z.push(" Reason: WorkerRuntimeManager was constructed without an `autoPull` config and no caller-provided `projectDir` was found on disk."),Z.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":Z.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":Z.push(` Reason: auto-pull was disabled (--no-auto-pull) and the expected pre-pulled workspace at ${Y} is missing or empty.`);break}return Z.push(""),Z.push(" How to fix (pick one):"),Z.push(" 1. Re-enable auto-pull (default). Drop the --no-auto-pull flag and restart"),Z.push(" the worker. The first inbound request for this project will clone its"),Z.push(" workspace from Shogo Cloud into <projectsDir>/<projectId>/."),Z.push(""),Z.push(" 2. Pre-pull manually with `shogo project pull <projectId>` before starting"),Z.push(" the worker. Use this when you want full control over when the clone runs"),Z.push(" (slow links, scheduled maintenance windows, etc.)."),Z.push(""),Z.push(" 3. Point the worker at an existing workspace by setting either:"),Z.push(" --projects-dir <path> (per-invocation flag)"),Z.push(" SHOGO_PROJECTS_DIR=<path> (env var, persists across restarts)"),Z.push(" shogo config set projectsDir <path>"),Z.push(" Whichever path you pick must contain a subdirectory named after the "),Z.push(` project id (e.g. <path>/${$}/) populated with the project's source.`),Z.push(""),Z.push(" Docs: https://shogo.ai/docs/self-hosted-worker#workspace-seeding"),Z.join(`
11
- `)}class v0{opts;log;runtimes=new Map;usedPorts=new Set;spawnCommand;resolved=null;stopped=!1;watchers=new Map;pulledProjects=new Set;syncModes=new Map;constructor($={}){this.opts=$,this.log=$.logger??console,this.spawnCommand=$.spawnCommand??V6}resolveBinary(){if(!this.resolved)this.resolved=this.opts.resolveBin?this.opts.resolveBin():u({flag:this.opts.runtimeBin,env:this.opts.env});return this.resolved}async resolveLocalUrl($,X){let{pathname:Y,search:Z}=k2($);if(!(Y.startsWith("/agent/")||Y==="/agent"))return null;if(!X){let K=this.getActiveProjects();if(K.length!==1)return null;X=K[0]}let z=await this.spawnConfigFor(X);if(!z)return this.log.warn(`[WorkerRuntimeManager] No spawn config for ${X} — set defaultSpawnConfig or enrichSpawnConfig`),null;let J=await this.ensureRunning(X,z);if(!J.agentPort)return null;return this.touch(X),`http://127.0.0.1:${J.agentPort}${Y}${Z}`}deriveRuntimeToken($){return W2($)}describeRejection($,X){let{pathname:Y}=k2($);if(!(Y.startsWith("/agent/")||Y==="/agent"))return{code:"CLI_WORKER_HAS_NO_DATA_API",message:`cli-worker only serves /agent/* paths; tried: ${Y}`};return{code:"CLI_WORKER_NO_PROJECT_FOR_PATH",message:`cli-worker received an /agent path without a single active project; projectId=${X??"none"}, path=${Y}`}}async spawnConfigFor($){let X=this.opts.defaultSpawnConfig;if(!X)return null;if(this.opts.enrichSpawnConfig)try{return await this.opts.enrichSpawnConfig($,X)}catch(Y){this.log.warn(`[WorkerRuntimeManager] enrichSpawnConfig failed for ${$}: ${Y?.message??Y}`)}return X}async ensureRunning($,X){if(this.stopped)throw Error("WorkerRuntimeManager is stopped");let Y=this.runtimes.get($);if(Y?.status==="failed")throw Error(`[WorkerRuntimeManager] cannot ensureRunning(${$}): ${Y.lastError??"runtime is in failed state"}. Call resetFailure(${$}) or stop(${$}) before retrying.`);X=await this.maybeAutoPull($,X);let Z=this.runtimes.get($);if(Z?.status==="running")return this.touch($),this.snapshot(Z);if(Z?.startPromise){let J=await Z.startPromise;return this.snapshot(J)}let z=Z??this.makeSlot($,X);if(!Z)this.runtimes.set($,z);z.spawnConfig=X,z.startPromise=this.doStart(z);try{let J=await z.startPromise;return this.enforceMaxRuntimes($),this.snapshot(J)}finally{z.startPromise=null}}enforceMaxRuntimes($){let X=this.opts.maxRuntimes;if(X==null||!Number.isFinite(X)||X<=0)return;let Y=Date.now(),Z=Array.from(this.runtimes.values()).filter((K)=>K.status==="running");if(Z.length<=X)return;let z=Z.filter((K)=>K.projectId!==$&&Y-K.lastUsedAt>=$6).sort((K,q)=>K.lastUsedAt-q.lastUsedAt),J=Z.length-X;for(let K of z){if(J<=0)break;let q=Y-K.lastUsedAt;this.log.log(`[WorkerRuntimeManager] maxRuntimes=${X} exceeded (${Z.length} running) — `+`LRU-evicting ${K.projectId} (idle ${Math.round(q/1000)}s)`),this.stop(K.projectId).catch((Q)=>{this.log.warn(`[WorkerRuntimeManager] maxRuntimes eviction of ${K.projectId} failed: ${Q?.message??Q}`)}),J--}if(J>0)this.log.log(`[WorkerRuntimeManager] maxRuntimes=${X} still exceeded by ${J} after eviction pass — `+"remaining over-cap slots are mid-stream; will retry on next spawn / idle reap")}async ensurePulled($,X){return this.maybeAutoPull($,X)}async maybeAutoPull($,X){let Y=this.opts.autoPull;if(X.projectDir&&P0(X.projectDir))return X;if(!Y)throw Error(b0($,"no-auto-pull-config",null));if(!Y.projectsDir)throw Error(b0($,"no-projects-dir",null));if(!Y.enabled){let K=X0(Y.projectsDir,$);if(P0(K)&&!G2(K))return{...X,projectDir:K};throw Error(b0($,"auto-pull-disabled",K))}if(this.pulledProjects.has($))return{...X,projectDir:X0(Y.projectsDir,$)};let Z=X0(Y.projectsDir,$),z=Y.logger??this.log,J={cloneProject:Y.gitOps?.cloneProject??x0,gitIsAvailable:Y.gitOps?.gitIsAvailable??D0,isGitRepo:Y.gitOps?.isGitRepo??t};this.pulledProjects.add($);try{q2(Z,{recursive:!0});let K=G2(Z),q=J.isGitRepo(Z),H=(Y.useGit!==!1?await J.gitIsAvailable():!1)&&(K||q)?"git":"files";if(this.syncModes.set($,H),H==="git"){if(K){z.log(`[WorkerRuntimeManager] auto-pull: git clone project ${$} into ${Z}`);try{let w=await J.cloneProject({apiUrl:X.cloudUrl,apiKey:X.apiKey,projectId:$,localDir:Z,shallow:!0,logger:z});z.log(`[WorkerRuntimeManager] auto-pull: ${$} cloned at ${w.commitSha.slice(0,8)}`)}catch(w){z.warn(`[WorkerRuntimeManager] auto-pull: git clone failed for ${$} (${w?.message??w}); falling back to CloudFileTransport.downloadAll`),this.syncModes.set($,"files"),await this.fileTransportClone($,Z,X,z)}}else if(q)z.log(`[WorkerRuntimeManager] auto-pull: ${$} already has .git/; skipping clone`);if(this.syncModes.get($)==="git")await this.topUpShogoState($,Z,X,z)}else if(K)await this.fileTransportClone($,Z,X,z);else z.log(`[WorkerRuntimeManager] auto-pull: ${$} workspace already populated; skipping clone`);if(Y.watch!==!1&&!this.watchers.has($))try{let w=new S0({apiUrl:X.cloudUrl,apiKey:X.apiKey,projectId:$,localDir:Z}),W=this.syncModes.get($)??"files",C=new $0({rootDir:Z,transport:w,logger:z,mode:W,git:W==="git"?{apiUrl:X.cloudUrl,apiKey:X.apiKey,projectId:$}:void 0});C.start(),this.watchers.set($,C)}catch(w){z.warn(`[WorkerRuntimeManager] auto-pull: watcher start failed for ${$}: ${w?.message??w}`)}}catch(K){z.warn(`[WorkerRuntimeManager] auto-pull: failed for ${$} — runtime will fall back to template defaults. `+`(${K?.message??K})`)}return{...X,projectDir:Z}}async fileTransportClone($,X,Y,Z){Z.log(`[WorkerRuntimeManager] auto-pull: file-transport clone of ${$} into ${X}`);let J=await new S0({apiUrl:Y.cloudUrl,apiKey:Y.apiKey,projectId:$,localDir:X}).downloadAll();Z.log(`[WorkerRuntimeManager] auto-pull: ${$} downloaded ${J.downloaded} files (${J.errors.length} errors)`)}async topUpShogoState($,X,Y,Z){try{let z=new S0({apiUrl:Y.cloudUrl,apiKey:Y.apiKey,projectId:$,localDir:X}),K=(await z.listManifest()).filter((Q)=>Q.path===".shogo"||Q.path.startsWith(".shogo/"));if(K.length===0)return;let q=await z.downloadFiles(K);Z.log(`[WorkerRuntimeManager] auto-pull: ${$} .shogo/ top-up downloaded ${q.downloaded} files (${q.errors.length} errors)`)}catch(z){Z.warn(`[WorkerRuntimeManager] auto-pull: .shogo top-up failed for ${$}: ${z?.message??z}`)}}shouldDeferToCloudSync($){return!!this.opts.autoPull?.enabled&&this.pulledProjects.has($)}status($){let X=this.runtimes.get($);return X?this.snapshot(X):null}getActiveProjects(){return Array.from(this.runtimes.keys()).filter(($)=>{let X=this.runtimes.get($);return X&&(X.status==="running"||X.status==="starting"||X.status==="restarting")})}touch($){let X=this.runtimes.get($);if(!X)return;X.lastUsedAt=Date.now(),this.armIdleTimer(X)}async stop($,X="SIGTERM"){let Y=this.runtimes.get($);if(!Y)return;if(Y.status="stopping",Y.restartTimer)clearTimeout(Y.restartTimer),Y.restartTimer=null;if(Y.idleTimer)clearTimeout(Y.idleTimer),Y.idleTimer=null;if(Y.graceTimer)clearTimeout(Y.graceTimer),Y.graceTimer=null;if(Y.proc){this.killProcessGroup(Y,X);try{Y.proc.kill(X)}catch{}await this.waitForExit(Y.proc,5000),this.killProcessGroup(Y,"SIGKILL")}Y.pid=null,this.releasePort(Y.agentPort),this.runtimes.delete($)}resetFailure($){let X=this.runtimes.get($);if(!X||X.status!=="failed")return!1;if(X.restartTimer)clearTimeout(X.restartTimer),X.restartTimer=null;if(X.idleTimer)clearTimeout(X.idleTimer),X.idleTimer=null;if(X.graceTimer)clearTimeout(X.graceTimer),X.graceTimer=null;return this.runtimes.delete($),this.log.log(`[WorkerRuntimeManager] resetFailure: ${$} cleared, next ensureRunning will respawn`),!0}async stopAll($="SIGTERM"){this.stopped=!0;let X=Array.from(this.watchers.keys());await Promise.all(X.map(async(Z)=>{let z=this.watchers.get(Z);if(this.watchers.delete(Z),z)try{await z.stop()}catch(J){this.log.warn(`[WorkerRuntimeManager] watcher stop ${Z}: ${J?.message??J}`)}}));let Y=Array.from(this.runtimes.keys());await Promise.all(Y.map((Z)=>this.stop(Z,$).catch((z)=>{this.log.error(`[WorkerRuntimeManager] Failed to stop ${Z}: ${z?.message??z}`)})))}makeSlot($,X){return{projectId:$,agentPort:0,apiServerPort:0,status:"starting",proc:null,pid:null,startedAt:0,lastStdoutAt:0,lastUsedAt:Date.now(),restarts:0,consecutiveFailures:0,lastFailureAt:0,graceTimer:null,restartTimer:null,idleTimer:null,spawnConfig:X,startPromise:null}}async doStart($){let X=this.resolveBinary();if(!X)throw $.status="error",$.lastError="agent-runtime binary not found (run `shogo runtime install`)",Error($.lastError);if(!$.agentPort)$.agentPort=await this.allocatePort(),$.apiServerPort=$.agentPort+V2;let Y=this.buildEnv($,X.path),Z=this.resolveCwd($),{command:z,args:J}=this.spawnCommand(X.path);this.log.log(`[WorkerRuntimeManager] Spawning agent-runtime for ${$.projectId} via ${z} ${J.join(" ")} (port=${$.agentPort}, source=${X.source})`);let K=process.platform!=="win32",q=p$(z,J,{cwd:Z,env:Y,detached:K,stdio:["ignore","pipe","pipe"]});if(K)try{q.unref()}catch{}$.proc=q,$.pid=q.pid??null,$.status="starting",$.startedAt=Date.now(),$.lastStdoutAt=$.startedAt,q.on("error",(V)=>{$.lastError=V?.message??String(V),this.log.error(`[WorkerRuntimeManager] spawn error for ${$.projectId}: ${$.lastError}`)}),q.on("exit",(V,H)=>{this.handleExit($,V,H)});let Q=`[runtime:${$.projectId.slice(0,8)}]`;q.stdout?.on("data",(V)=>{$.lastStdoutAt=Date.now();for(let H of V.toString().trimEnd().split(`
12
- `))if(H)this.log.log(`${Q} ${H}`)}),q.stderr?.on("data",(V)=>{$.lastStdoutAt=Date.now();for(let H of V.toString().trimEnd().split(`
13
- `))if(H)this.log.error(`${Q} ${H}`)});try{return await this.waitForHealth($,J6),$.status="running",$.lastUsedAt=Date.now(),this.armIdleTimer($),this.armGraceTimer($),$}catch(V){$.status="error",$.lastError=V?.message??String(V),this.killProcessGroup($,"SIGTERM");try{q.kill("SIGTERM")}catch{}throw this.releasePort($.agentPort),$.agentPort=0,$.apiServerPort=0,V}}killProcessGroup($,X){if(!$.pid)return;if(process.platform==="win32"){try{c$("taskkill",["/F","/T","/PID",String($.pid)],{stdio:"ignore",windowsHide:!0})}catch{}return}try{process.kill(-$.pid,X)}catch{}}armGraceTimer($){if($.graceTimer)clearTimeout($.graceTimer),$.graceTimer=null;$.graceTimer=setTimeout(()=>{if($.graceTimer=null,$.consecutiveFailures>0)$.consecutiveFailures=0},Z6);try{$.graceTimer.unref?.()}catch{}}buildEnv($,X){let Y=$.spawnConfig,Z={...this.opts.env??process.env,PROJECT_ID:$.projectId,PORT:String($.agentPort),API_SERVER_PORT:String($.apiServerPort),SKILL_SERVER_PORT:String($.apiServerPort),WORKSPACE_API_PORT_BASE:String($.agentPort+s$),NODE_ENV:"production",SHOGO_CLOUD_URL:Y.cloudUrl,SHOGO_API_URL:Y.cloudUrl,SHOGO_API_KEY:Y.apiKey,RUNTIME_AUTH_SECRET:W2($.projectId),WEBHOOK_TOKEN:Q6($.projectId)};if(Y.projectDir)Z.PROJECT_DIR=Y.projectDir,Z.WORKSPACE_DIR=Y.projectDir;if(this.shouldDeferToCloudSync($.projectId))Z.SHOGO_CLOUD_SYNC="1";if(Y.aiProxyUrl)Z.AI_PROXY_URL=Y.aiProxyUrl;if(Y.aiProxyToken)Z.AI_PROXY_TOKEN=Y.aiProxyToken;if(Y.techStackId)Z.TECH_STACK_ID=Y.techStackId;if(Y.name)Z.AGENT_NAME=Y.name;if(Y.workspaceId)Z.WORKSPACE_ID=Y.workspaceId;if(!Z.TREE_SITTER_WASM_DIR)Z.TREE_SITTER_WASM_DIR=X0(e$(X),"tree-sitter-wasm");if(Y.extraEnv)Object.assign(Z,Y.extraEnv);return Z}resolveCwd($){let X=$.spawnConfig;if(X.projectDir&&P0(X.projectDir))return X.projectDir;let Y=this.opts.runtimeWorkDir??X0(r$(),"shogo-runtime",$.projectId);return q2(Y,{recursive:!0}),Y}handleExit($,X,Y){let Z=Y===null&&X===0;if(this.log.log(`[WorkerRuntimeManager] runtime ${$.projectId} exited (code=${X}, signal=${Y})`),$.proc=null,$.graceTimer)clearTimeout($.graceTimer),$.graceTimer=null;if($.status==="stopping"||this.stopped){$.status="stopped",$.pid=null,this.releasePort($.agentPort),$.agentPort=0,$.apiServerPort=0;return}if(Z){$.status="stopped",$.pid=null,this.releasePort($.agentPort),$.agentPort=0,$.apiServerPort=0,this.runtimes.delete($.projectId);return}this.killProcessGroup($,"SIGKILL"),$.pid=null;let z=Date.now(),J=z-$.lastFailureAt<=H2;if($.consecutiveFailures=J?$.consecutiveFailures+1:1,$.lastFailureAt=z,$.restarts+=1,$.lastError=`exited code=${X} signal=${Y}`,$.consecutiveFailures>=Q2){if($.status="failed",$.lastError=`Circuit breaker tripped: ${$.consecutiveFailures} consecutive non-clean exits within ${Math.round(H2/1000)}s (last: code=${X} signal=${Y}). 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. Stop, fix the workspace, and call resetFailure(projectId) (or stop(projectId)) to allow another spawn attempt.`,this.releasePort($.agentPort),$.agentPort=0,$.apiServerPort=0,$.restartTimer)clearTimeout($.restartTimer),$.restartTimer=null;if($.idleTimer)clearTimeout($.idleTimer),$.idleTimer=null;this.log.error(`[WorkerRuntimeManager] ${$.lastError}`);return}let K=this.restartBackoffMs($.restarts);if($.status="restarting",this.log.warn(`[WorkerRuntimeManager] restarting ${$.projectId} in ${Math.round(K/1000)}s (restart #${$.restarts}, consecutive failures ${$.consecutiveFailures}/${Q2})`),$.restartTimer)clearTimeout($.restartTimer);$.restartTimer=setTimeout(()=>{if($.restartTimer=null,$.status==="failed"||this.stopped)return;$.startPromise=this.doStart($).then((q)=>{return $.startPromise=null,q}).catch((q)=>{return $.startPromise=null,this.log.error(`[WorkerRuntimeManager] restart of ${$.projectId} failed: ${q?.message??q}`),$})},K);try{$.restartTimer.unref?.()}catch{}}restartBackoffMs($){let X=Math.min(X6*Math.pow(2,Math.max(0,$-1)),Y6),Y=X*0.2*Math.random();return X+Y}armIdleTimer($){if($.idleTimer)clearTimeout($.idleTimer),$.idleTimer=null;let X=this.opts.idleMs??t$;if(!Number.isFinite(X)||X<=0)return;$.idleTimer=setTimeout(()=>{let Y=Date.now()-$.lastUsedAt;if(Y<X){this.armIdleTimer($);return}this.log.log(`[WorkerRuntimeManager] idle-evicting ${$.projectId} after ${Math.round(Y/1000)}s`),this.stop($.projectId).catch((Z)=>{this.log.warn(`[WorkerRuntimeManager] idle stop failed: ${Z?.message??Z}`)})},X)}async allocatePort(){let $=_0-R0,X=Math.min($,50);for(let Y=0;Y<X;Y++){let Z=R0+Math.floor(Math.random()*$);if(Z+k0-1>_0)continue;let z=!0;for(let q=0;q<k0;q++)if(this.usedPorts.has(Z+q)){z=!1;break}if(!z)continue;let J=await this.isPortListening(Z),K=await this.isPortListening(Z+V2);if(J||K)continue;for(let q=0;q<k0;q++)this.usedPorts.add(Z+q);return Z}throw Error(`Cannot allocate port in range ${R0}-${_0} after ${X} attempts`)}releasePort($){if(!$)return;for(let X=0;X<k0;X++)this.usedPorts.delete($+X)}async isPortListening($){let X=new AbortController,Y=setTimeout(()=>X.abort(),250);try{return await fetch(`http://127.0.0.1:${$}/`,{method:"HEAD",signal:X.signal}),clearTimeout(Y),!0}catch{return clearTimeout(Y),!1}}tcpProbe($){return new Promise((X)=>{let Y=!1,Z=(J)=>{if(Y)return;Y=!0;try{z.destroy()}catch{}X(J)},z=l$({host:"127.0.0.1",port:$});z.setTimeout(q6),z.once("connect",()=>Z(!0)),z.once("error",()=>Z(!1)),z.once("timeout",()=>Z(!1))})}async waitForHealth($,X){let{agentPort:Y,proc:Z}=$;if(!Z)throw Error(`waitForHealth: slot ${$.projectId} has no spawned process`);let z=Date.now(),J=z+X,K=null,q=!1,Q=0,V=0,H=0,w=0,W=z;while(Date.now()<J){if(w++,Z.exitCode!==null||Z.signalCode!=null||Z.killed)throw Error(`agent-runtime exited (code=${Z.exitCode}, signal=${Z.signalCode}) before becoming healthy on port ${Y}`);V++;let C=new AbortController,O=setTimeout(()=>C.abort(),1500);try{let k=await fetch(`http://127.0.0.1:${Y}/health`,{method:"GET",signal:C.signal});if(clearTimeout(O),k.ok){this.log.log(`[WorkerRuntimeManager] /health ready for ${$.projectId} on port ${Y} (HTTP ${k.status} after ${Date.now()-z}ms, ${w} iter, ${V} http)`);return}K=`HTTP /health returned ${k.status}`}catch(k){clearTimeout(O);let M=k?.name??"Error",g=k?.code??k?.cause?.code;K=`HTTP /health failed: ${M}${g?`(${g})`:""}: ${k?.message??k}`}if(H++,q=await this.tcpProbe(Y),q){Q=Date.now();let k=Date.now()-$.lastStdoutAt;if(k>=w2)throw Error(`agent-runtime wedged on port ${Y}: TCP listening but stdout silent for ${k}ms (> ${w2}ms window); ${V} /health attempts, last error: ${K??"n/a"}`)}let A=Date.now();if(A-W>=K6){let k=A-z,M=A-$.lastStdoutAt,g=Q>0?A-Q:null;this.log.log(`[WorkerRuntimeManager] still waiting for /health on ${$.projectId} port ${Y} (${(k/1000).toFixed(1)}s elapsed, tcpListening=${q}${g!=null?`(${g}ms ago)`:""}, lastStdout=${M}ms ago, ${V} http, ${H} tcp, lastError=${K??"n/a"})`),W=A}await new Promise((k)=>setTimeout(k,z6))}if(Z.exitCode!==null||Z.signalCode!=null||Z.killed)throw Error(`agent-runtime exited (code=${Z.exitCode}, signal=${Z.signalCode}) before becoming healthy on port ${Y}`);throw Error(`Timeout waiting for agent-runtime /health on port ${Y} after ${w} iter (httpAttempts=${V}, tcpAttempts=${H}, tcpListening=${q}, lastError=${K??"n/a"})`)}async waitForExit($,X){if($.exitCode!==null||$.signalCode!=null||$.killed)return;await new Promise((Y)=>{let Z=setTimeout(()=>{try{$.kill("SIGKILL")}catch{}Y()},X);$.once("exit",()=>{clearTimeout(Z),Y()})})}snapshot($){return{projectId:$.projectId,status:$.status,agentPort:$.agentPort||void 0,apiServerPort:$.apiServerPort||void 0,pid:$.proc?.pid,startedAt:$.startedAt||void 0,lastUsedAt:$.lastUsedAt,restarts:$.restarts,lastError:$.lastError}}}import{hostname as m0,platform as h0,arch as j0}from"node:os";class U2 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 Y0=60,G0=300,I0=3,H6=I0,w6=1800000,W6=25000,O2=1000,B2=60000,A2=3;class u0{opts;log;pollTimer=null;ws=null;wsIdleTimer=null;heartbeatTimer=null;stopped=!1;currentPollInterval=Y0;wsReconnectAttempt=0;lastHeartbeatError=null;consecutiveAuthFailures=0;consecutiveAuthSuccesses=0;serverPublishedWsUrl=null;activeAbortControllers=new Map;constructor($){this.opts=$,this.log=$.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=Y0,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 $=(this.opts.wsUrlOverride||process.env.SHOGO_TUNNEL_WS_URL||"").trim();if($)return $.replace(/\/$/,"");if(this.serverPublishedWsUrl)return this.serverPublishedWsUrl.replace(/\/$/,"");return this.getCloudUrl().replace(/^http/,"ws")}buildWsUrl(){return`${this.getWsBaseUrl()}/api/instances/ws`}supportsWebSocketConstructorHeaders($=globalThis){if(typeof $.Bun<"u"||typeof $.process?.versions?.bun==="string")return!0;let X=$.process?.versions?.node;if(X){if(parseInt(X.split(".")[0]??"0",10)>=21)return!0}return!1}createTunnelWebSocket($,X,Y=globalThis){if(!this.supportsWebSocketConstructorHeaders(Y))throw new U2;return new WebSocket($,X)}getReconnectDelayMs(){let $=Math.min(O2*Math.pow(2,this.wsReconnectAttempt),B2),X=$*0.2*Math.random();return $+X}async collectMetadata(){let $={hostname:m0(),os:h0(),arch:j0(),uptime:process.uptime(),protocolVersion:A2,tunnelStatus:this.ws?.readyState===WebSocket.OPEN?"connected":"polling",kind:this.opts.kind??"cli-worker"};try{let X=this.opts.resolver.getActiveProjects();$.activeProjects=X.length,$.projects=X.map((Y)=>{let Z=this.opts.resolver.status(Y);return{projectId:Y,status:Z?.status??"unknown",agentPort:Z?.agentPort}})}catch{$.activeProjects=0}return $}async sendHeartbeat(){let $=this.getCloudUrl(),X=await this.collectMetadata(),Y=m0(),Z=this.opts.name??Y,z=await fetch(`${$}/api/instances/heartbeat`,{method:"POST",headers:{"Content-Type":"application/json","x-api-key":this.opts.apiKey,"x-shogo-kind":this.opts.kind??"cli-worker"},body:JSON.stringify({hostname:Y,name:Z,os:h0(),arch:j0(),kind:this.opts.kind??"cli-worker",metadata:X})});if(!z.ok)throw Error(`Heartbeat failed: HTTP ${z.status}`);let J=await z.json();if(typeof J.wsUrl==="string"&&J.wsUrl.length>0){if(J.wsUrl!==this.serverPublishedWsUrl)this.log.log(`[WorkerTunnel] Cloud advertised tunnel WS URL: ${J.wsUrl}`);this.serverPublishedWsUrl=J.wsUrl}return J}scheduleNextPoll($){if(this.stopped)return;if(this.pollTimer)clearTimeout(this.pollTimer);let X=($??this.currentPollInterval)*1000;this.pollTimer=setTimeout(()=>void this.heartbeatLoop(),X)}async heartbeatLoop(){if(this.stopped)return;if(this.ws&&this.ws.readyState===WebSocket.OPEN){this.scheduleNextPoll(this.currentPollInterval);return}try{let $=await this.sendHeartbeat(),X=$.nextPollIn||Y0;if(this.consecutiveAuthFailures>=I0){if(this.consecutiveAuthSuccesses++,this.consecutiveAuthSuccesses<H6){this.currentPollInterval=G0,this.scheduleNextPoll();return}}if(this.currentPollInterval=X,this.lastHeartbeatError)this.log.log("[WorkerTunnel] Heartbeat recovered"),this.lastHeartbeatError=null;if(this.consecutiveAuthFailures=0,this.consecutiveAuthSuccesses=0,$.wsRequested&&!this.ws){this.log.log("[WorkerTunnel] Cloud requested WebSocket — connecting..."),this.connectWs();return}}catch($){let X=$?.message??String($);if(/HTTP 40[13]\b/.test(X))this.consecutiveAuthFailures++,this.consecutiveAuthSuccesses=0;else this.consecutiveAuthFailures=0,this.consecutiveAuthSuccesses=0;if(X!==this.lastHeartbeatError)this.log.error(`[WorkerTunnel] Heartbeat error: ${X}`),this.lastHeartbeatError=X;if(this.consecutiveAuthFailures>=I0){let Z=`tunnel saw ${this.consecutiveAuthFailures} consecutive auth failures from Shogo Cloud`;try{this.opts.onAuthRevoked?.(Z)}catch(z){this.log.warn(`[WorkerTunnel] onAuthRevoked threw: ${z?.message??z}`)}if(this.currentPollInterval!==G0)this.log.warn(`[WorkerTunnel] ${this.consecutiveAuthFailures} consecutive auth failures — `+`backing off to ${G0}s. Re-authenticate with \`shogo login\`.`);this.currentPollInterval=G0}else this.currentPollInterval=Y0}this.scheduleNextPoll()}async handleRequest($){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)return;this.resetWsIdleTimer();let X=new AbortController;this.activeAbortControllers.set($.requestId,X);try{let Y=await this.resolveLocalUrl($.path,$.projectId);if(!Y){let K=this.opts.resolver.describeRejection?this.opts.resolver.describeRejection($.path,$.projectId):{code:"NO_LOCAL_RUNTIME",message:`no local runtime available for path: ${$.path}`};this.sendFrame({type:"response",requestId:$.requestId,status:502,headers:{"content-type":"application/json"},body:JSON.stringify({code:K.code,message:K.message,path:$.path})});return}let Z={...$.headers??{}};if($.projectId&&($.path.startsWith("/agent/")||$.path==="/agent")){let K=this.opts.resolver.deriveRuntimeToken($.projectId);if(K)Z["x-runtime-token"]=K}let z={method:$.method,headers:Z,signal:X.signal};if($.body&&$.method!=="GET"&&$.method!=="HEAD")z.body=$.body;let J=await fetch(Y,z);if($.stream){let K=J.body?.getReader();if(!K){this.sendFrame({type:"stream-error",requestId:$.requestId,error:"No response body for stream"});return}let q=new TextDecoder;try{while(!0){let{done:Q,value:V}=await K.read();if(Q)break;if(this.ws?.readyState!==WebSocket.OPEN)break;this.sendFrame({type:"stream-chunk",requestId:$.requestId,data:q.decode(V,{stream:!0})})}if(this.ws?.readyState===WebSocket.OPEN)this.sendFrame({type:"stream-end",requestId:$.requestId})}catch(Q){if(Q?.name!=="AbortError"&&this.ws?.readyState===WebSocket.OPEN)this.sendFrame({type:"stream-error",requestId:$.requestId,error:Q?.message??String(Q)})}}else{let K=await J.text(),q={};J.headers.forEach((Q,V)=>{q[V]=Q}),this.sendFrame({type:"response",requestId:$.requestId,status:J.status,headers:q,body:K})}}catch(Y){if(Y?.name==="AbortError")return;if(this.ws?.readyState!==WebSocket.OPEN)return;let Z=$.stream?{type:"stream-error",requestId:$.requestId,error:Y?.message??String(Y)}:{type:"response",requestId:$.requestId,status:502,body:JSON.stringify({error:Y?.message??String(Y)})};this.sendFrame(Z)}finally{this.activeAbortControllers.delete($.requestId)}}async resolveLocalUrl($,X){return this.opts.resolver.resolveLocalUrl($,X)}sendFrame($){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)return;try{this.ws.send(JSON.stringify($))}catch(X){this.log.warn(`[WorkerTunnel] Frame send failed: ${X?.message??X}`)}}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{}}},w6)}startWsHeartbeat(){if(this.heartbeatTimer)clearInterval(this.heartbeatTimer);this.heartbeatTimer=setInterval(async()=>{if(!this.ws||this.ws.readyState!==WebSocket.OPEN)return;try{let $=await this.collectMetadata();this.sendFrame({type:"heartbeat",metadata:$})}catch{}},W6)}connectWs(){if(this.stopped||this.ws)return;let $=this.buildWsUrl(),X=m0(),Y=h0(),Z=j0(),z=this.opts.name??X;this.log.log(`[WorkerTunnel] Opening WebSocket to ${$} (hostname=${X})`);let J={headers:{Authorization:`Bearer ${this.opts.apiKey}`,"x-shogo-hostname":X,"x-shogo-name":z,"x-shogo-os":Y,"x-shogo-arch":Z,"x-shogo-kind":this.opts.kind??"cli-worker"}},K;try{K=this.createTunnelWebSocket($,J)}catch(q){this.log.error(`[WorkerTunnel] WebSocket creation failed: ${q?.message??q}`),this.ws=null,this.scheduleNextPoll(5);return}this.ws=K,K.onopen=()=>{this.log.log("[WorkerTunnel] WebSocket connected — session active"),this.wsReconnectAttempt=0,this.startWsHeartbeat(),this.resetWsIdleTimer()},K.onmessage=(q)=>{let Q;try{let V=typeof q.data==="string"?q.data:q.data.toString();Q=JSON.parse(V)}catch{return}if(Q.type==="ping"){this.sendFrame({type:"pong"}),this.resetWsIdleTimer();return}if(Q.type==="cancel"){let V=this.activeAbortControllers.get(Q.requestId);if(V)V.abort();return}if(Q.type==="request"){this.handleRequest(Q).catch((V)=>{this.log.error(`[WorkerTunnel] Error handling request: ${V?.message??V}`)});return}},K.onclose=(q)=>{if(this.log.log(`[WorkerTunnel] WebSocket closed: code=${q.code} reason=${q.reason||"none"}`),this.cleanupWs(),this.stopped)return;if(q.code===1000||q.code===4000)this.scheduleNextPoll(this.currentPollInterval);else{this.wsReconnectAttempt++;let Q=this.getReconnectDelayMs();this.log.log(`[WorkerTunnel] Reconnecting in ${Math.round(Q/1000)}s (attempt ${this.wsReconnectAttempt})`),this.scheduleNextPoll(Math.ceil(Q/1000))}},K.onerror=(q)=>{this.log.error("[WorkerTunnel] WebSocket error:",q?.message??"unknown")}}cleanupWs(){if(this.heartbeatTimer)clearInterval(this.heartbeatTimer),this.heartbeatTimer=null;if(this.wsIdleTimer)clearTimeout(this.wsIdleTimer),this.wsIdleTimer=null;for(let[,$]of this.activeAbortControllers)try{$.abort()}catch{}this.activeAbortControllers.clear(),this.ws=null}_testing(){let $=this;return{sendHeartbeat:()=>$.sendHeartbeat(),heartbeatLoop:()=>$.heartbeatLoop(),connectWs:()=>$.connectWs(),cleanupWs:()=>$.cleanupWs(),handleRequest:(X)=>$.handleRequest(X),installFakeWs:(X)=>{$.ws=X},getCloudUrl:()=>$.getCloudUrl(),getWsBaseUrl:()=>$.getWsBaseUrl(),buildWsUrl:()=>$.buildWsUrl(),getReconnectDelayMs:()=>$.getReconnectDelayMs(),supportsWebSocketConstructorHeaders:(X)=>$.supportsWebSocketConstructorHeaders(X),createTunnelWebSocket:(X,Y,Z)=>$.createTunnelWebSocket(X,Y,Z),DEFAULT_POLL_INTERVAL_S:Y0,BACKOFF_BASE_MS:O2,BACKOFF_MAX_MS:B2,TUNNEL_PROTOCOL_VERSION:A2,get currentPollInterval(){return $.currentPollInterval},set currentPollInterval(X){$.currentPollInterval=X},get wsReconnectAttempt(){return $.wsReconnectAttempt},set wsReconnectAttempt(X){$.wsReconnectAttempt=X},get ws(){return $.ws},get stopped(){return $.stopped},get serverPublishedWsUrl(){return $.serverPublishedWsUrl},set serverPublishedWsUrl(X){$.serverPublishedWsUrl=X}}}}async function F2($){let X=_({name:$.name,workerDir:$.workerDir,apiKey:$.apiKey,cloudUrl:$.cloudUrl,port:$.port?parseInt($.port,10):void 0,projectsDir:$.projectsDir}),Y=s0($.proxy);if(!$.foreground)return G6({cfg:X,proxy:Y,flags:$});let Z=u({flag:$.runtimeBin});if(!Z)console.error(G.red(d({flag:$.runtimeBin}))),process.exit(1);if(M0(process.env,Y),$.debug){if(!await Y2(X2({cloudUrl:X.cloudUrl,apiKey:X.apiKey,workerDir:X.workerDir,proxy:Y})))process.exit(1)}let z=!$.noAutoPull,J=!$.noGit;if(console.log(G.bold(`
14
- Shogo Worker — Starting`)),console.log(G.dim(" name ")+X.name),console.log(G.dim(" worker-dir ")+X.workerDir),console.log(G.dim(" cloud ")+X.cloudUrl),console.log(G.dim(" runtime ")+`${Z.path} ${G.dim(`(via ${Z.source})`)}`),console.log(G.dim(" auto-pull ")+(z?G.green("on")+G.dim(` → ${X.projectsDir}`):G.yellow("off"))),console.log(G.dim(" sync mode ")+(J?G.green("git")+G.dim(" (falls back to file transport if git is missing)"):G.yellow("files-only"))),$.project)console.log(G.dim(" project ")+$.project);if(Y)console.log(G.dim(" proxy ")+`${Y.url} ${G.dim(`(from ${Y.source})`)}`);console.log("");let K=`${X.cloudUrl.replace(/\/+$/,"")}/api/ai/v1`,q={cloudUrl:X.cloudUrl,apiKey:X.apiKey,aiProxyUrl:K,aiProxyToken:X.apiKey},Q=new v0({runtimeBin:$.runtimeBin,defaultSpawnConfig:q,autoPull:{enabled:z,projectsDir:X.projectsDir,watch:!0,useGit:J}});if(!Q.resolveBinary())console.error(G.red(d({flag:$.runtimeBin}))),process.exit(1);let V=new u0({apiKey:X.apiKey,cloudUrl:X.cloudUrl,name:X.name,kind:"cli-worker",resolver:Q,onAuthRevoked:(W)=>{console.error(G.red(`✗ Cloud auth revoked: ${W}`)),console.error(G.dim(" Run `shogo login` to re-authenticate; this worker will keep polling at the auth-failure backoff until then."))}}),H=!1,w=async(W)=>{if(H)return;H=!0,console.log(G.dim(`
15
- Received ${W} — shutting down...`));try{V.stop()}catch{}try{await Q.stopAll()}catch(C){console.warn(G.yellow(`stopAll: ${C?.message??C}`))}process.exit(0)};process.once("SIGINT",()=>void w("SIGINT")),process.once("SIGTERM",()=>void w("SIGTERM")),process.once("SIGHUP",()=>void w("SIGHUP")),V.start(),console.log(G.green("✓ Worker running. Ctrl-C to stop.")),await new Promise(()=>{})}function G6({cfg:$,proxy:X,flags:Y}){let{entry:Z,runner:z}=L6(),J=M0({...process.env,SHOGO_API_KEY:$.apiKey,SHOGO_CLOUD_URL:$.cloudUrl,SHOGO_INSTANCE_NAME:$.name,SHOGO_WORKER_DIR:$.workerDir,SHOGO_LOCAL_MODE:"true",PORT:String($.port)},X),K=C6(Y),{pid:q}=l0({entry:Z,runner:z,env:{...J,SHOGO_DETACHED_ARGS:K.join(" ")},cwd:$.workerDir,detach:!0,inheritStdio:!1});console.log(G.bold(`
16
- Shogo Worker — Started`)),console.log(G.dim(" pid: ")+q),console.log(G.dim(" name: ")+$.name),console.log(G.dim(" logs: ")+"~/.shogo/logs/worker.log"),console.log(G.dim(" stop: ")+"shogo worker stop")}function C6($){let X=["worker","start","--foreground"];if($.name)X.push("--name",$.name);if($.workerDir)X.push("--worker-dir",$.workerDir);if($.apiKey)X.push("--api-key",$.apiKey);if($.cloudUrl)X.push("--cloud-url",$.cloudUrl);if($.port)X.push("--port",$.port);if($.proxy)X.push("--proxy",$.proxy);if($.project)X.push("--project",$.project);if($.runtimeBin)X.push("--runtime-bin",$.runtimeBin);if($.debug)X.push("--debug");if($.noAutoPull)X.push("--no-auto-pull");if($.projectsDir)X.push("--projects-dir",$.projectsDir);if($.noGit)X.push("--no-git");return X}function L6(){let $=process.execPath,X=/\bbun(?:-[^/\\]*)?$/.test($)||typeof globalThis.Bun<"u",Y=process.argv[1];if(Y&&k6(Y)){let z=/\.ts$/.test(Y);return{entry:Y,runner:X?"bun":z?"tsx":"node"}}return{entry:new URL("../../bin/shogo.mjs",import.meta.url).pathname,runner:X?"bun":"node"}}import N2 from"picocolors";async function M2(){let{killedPid:$}=r0("SIGTERM");if($===null){console.log(N2.dim("No worker running."));return}console.log(N2.green(`✓ Worker stopped (pid=${$}).`))}import v from"picocolors";async function T2(){let $=Q0();if(!$){console.log(v.yellow("● stopped")+v.dim(" (no pid file)"));return}if(!H0($)){console.log(v.red("● dead")+v.dim(` (stale pid ${$})`));return}let X=I();if(console.log(v.green("● running")+v.dim(` (pid ${$})`)),X.name)console.log(v.dim(" name: ")+X.name);if(X.cloudUrl)console.log(v.dim(" cloud: ")+X.cloudUrl);if(X.workerDir)console.log(v.dim(" dir: ")+X.workerDir)}import{spawn as O6}from"node:child_process";import{existsSync as B6,statSync as A6}from"node:fs";import y2 from"picocolors";async function D2($){let X=$.err?q0:K0;if(!B6(X)){console.log(y2.dim("No logs yet."));return}let Y=$.follow?["-F","-n","200",X]:["-n","200",X];if(O6("tail",Y,{stdio:"inherit"}).on("exit",(z)=>process.exit(z??0)),!$.follow){if(A6(X).size===0)console.log(y2.dim("(log file is empty)"))}}import y from"picocolors";import{spawn as U6}from"node:child_process";import{hostname as F6,platform as N6,arch as M6}from"node:os";import f from"picocolors";class P extends Error{kind;constructor($,X){super($);this.kind=X;this.name="CloudLoginError"}}async function x2($){let X=$.cloudUrl.replace(/\/$/,""),Y=$.client??"cli",Z=$.deviceName??F6(),z=$.devicePlatform??`${N6()}-${M6()}`,J=$.appVersion??D6(),K=$.log??((k)=>console.log(k)),q=$.fetchImpl??fetch,Q=await q(`${X}/api/cli/login/start`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({deviceId:$.deviceId,deviceName:Z,devicePlatform:z,deviceAppVersion:J,workspaceId:$.workspaceId,client:Y}),signal:AbortSignal.timeout(1e4)}).catch((k)=>{throw new P(`Cannot reach Shogo Cloud at ${X}: ${k?.message??k}`,"transport")});if(!Q.ok){let k=await Q.text().catch(()=>"");throw new P(`Cloud rejected /api/cli/login/start (HTTP ${Q.status}): ${k||"no body"}`,"transport")}let V=await Q.json().catch(()=>({}));if(!V?.ok||!V.state||!V.authUrl)throw new P(`Cloud returned a malformed start response: ${V?.error??JSON.stringify(V)}`,"transport");let H=T6($.pollIntervalMs??V.pollIntervalMs),w=$.timeoutMs??V.expiresInMs,W=Date.now()+w;if(K(""),K(f.bold("Sign in to Shogo Cloud")),K(f.dim(" cloud: ")+X),K(f.dim(" device: ")+`${Z} (${z})`),K(f.dim(" user code: ")+f.cyan(V.userCode)),K(""),K(" Open this URL in your browser to approve:"),K(" "+f.cyan(V.authUrl)),K(""),$.openBrowser!==!1)if(typeof $.openBrowser==="function")try{await $.openBrowser(V.authUrl)}catch{}else x6(V.authUrl).catch(()=>{});K(f.dim("Waiting for approval..."));let C=$.installSignalHandlers??!0,O=()=>{A.abort()},A=new AbortController;if($.abortSignal)if($.abortSignal.aborted)A.abort();else $.abortSignal.addEventListener("abort",()=>A.abort(),{once:!0});if(C)process.once("SIGINT",O),process.once("SIGTERM",O);try{while(!0){if(A.signal.aborted)throw new P("Aborted by caller.","cancelled");if(Date.now()>=W)throw new P(`Timed out after ${Math.round(w/1000)}s waiting for approval.`,"timeout");let k=await q(`${X}/api/cli/login/poll?state=${encodeURIComponent(V.state)}`,{method:"GET",headers:{accept:"application/json"},signal:AbortSignal.timeout(1e4)}).catch((M)=>{return K(f.dim(` (poll error: ${M?.message??M} — retrying)`)),null});if(k&&k.ok){let M=await k.json().catch(()=>({}));if(M?.status==="approved"&&M.key)return{key:M.key,email:M.email??null,workspace:M.workspace??null,deviceId:M.deviceId??$.deviceId};if(M?.status==="denied")throw new P("Sign-in was denied in the browser.","denied");if(M?.status==="expired")throw new P("Sign-in request expired before it was approved.","expired")}await y6(H,A.signal)}}finally{if(C)process.removeListener("SIGINT",O),process.removeListener("SIGTERM",O)}}function T6($){if(!Number.isFinite($)||$<=0)return 2000;return Math.min(Math.max($,1000),1e4)}function y6($,X){return new Promise((Y,Z)=>{if(X?.aborted)return Z(new P("Aborted by caller.","cancelled"));let z=setTimeout(()=>{X?.removeEventListener("abort",J),Y()},$),J=()=>{clearTimeout(z),Z(new P("Aborted by caller.","cancelled"))};X?.addEventListener("abort",J,{once:!0})})}function D6(){if(typeof __SHOGO_WORKER_VERSION__==="string"&&__SHOGO_WORKER_VERSION__.length>0)return`shogo-cli/${__SHOGO_WORKER_VERSION__}`;return"shogo-cli/unknown"}function x6($){return new Promise((X)=>{let Y=process.platform==="darwin"?"open":process.platform==="win32"?"cmd":"xdg-open",Z=process.platform==="win32"?["/c","start",'""',$]:[$];try{let z=U6(Y,Z,{stdio:"ignore",detached:!0});z.on("error",()=>X()),z.unref(),X()}catch{X()}})}import{readFileSync as P6,writeFileSync as S6,existsSync as R6,chmodSync as _6}from"node:fs";import{randomUUID as E6}from"node:crypto";function P2(){if(R6(p)){let X=P6(p,"utf-8").trim();if(X)return X}j();let $=E6();return S6(p,$,{mode:384}),_6(p,384),$}var b6="https://studio.shogo.ai";async function S2($){let X=I(),Y=($.cloudUrl||X.cloudUrl||b6).replace(/\/$/,""),Z=$.apiKey||process.env.SHOGO_API_KEY;if(Z){await v6({key:Z,cloudUrl:Y,cfg:X,name:$.name});return}let z=P2(),J;try{J=await x2({cloudUrl:Y,deviceId:z,deviceName:$.name,workspaceId:$.workspace,openBrowser:!$.noBrowser})}catch(K){if(K instanceof P){if(console.error(y.red(`✗ ${K.message}`)),K.kind==="transport")console.error(y.dim(" If your network blocks browsers, run with --api-key <key> instead."));process.exit(1)}throw K}if(X.apiKey=J.key,X.cloudUrl=Y,$.name)X.name=$.name;if(l(X),console.log(""),console.log(y.green("✓ Signed in to Shogo Cloud")),J.workspace)console.log(y.dim(" workspace: ")+J.workspace);if(J.email)console.log(y.dim(" email: ")+J.email);console.log(y.dim(" saved to: ")+"~/.shogo/config.json"),console.log(""),console.log(y.dim(" next: ")+"shogo worker start")}async function v6({key:$,cloudUrl:X,cfg:Y,name:Z}){if(!/^shogo_sk_/.test($))console.error(y.red('✗ API key should start with "shogo_sk_". Copy it verbatim from the API Keys page.')),process.exit(1);let z;try{let J=await fetch(`${X}/api/api-keys/validate`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({key:$}),signal:AbortSignal.timeout(1e4)});if(z=await J.json().catch(()=>({})),!J.ok||!z?.valid)console.error(y.red(`✗ ${z?.error||`Cloud rejected the key (HTTP ${J.status}).`}`)),process.exit(1)}catch(J){console.error(y.red(`✗ Cannot reach Shogo Cloud at ${X}: ${J?.message??J}`)),process.exit(1)}if(Y.apiKey=$,Y.cloudUrl=X,Z)Y.name=Z;if(l(Y),console.log(y.green("✓ API key saved to ~/.shogo/config.json")),z.workspace?.name)console.log(y.dim(" workspace: ")+z.workspace.name);if(z.user?.email)console.log(y.dim(" email: ")+z.user.email);console.log(y.dim(" next: ")+"shogo worker start")}import Z0 from"picocolors";async function R2(){let $=I();if(Object.keys($).length===0){console.log(Z0.dim("(no config — run `shogo login` or `shogo config set`)"));return}let X={...$,apiKey:$.apiKey?`***${$.apiKey.slice(-4)}`:void 0};console.log(Z0.dim(`file: ${h}`)),console.log(JSON.stringify(X,null,2))}async function _2($,X){let Y=["apiKey","cloudUrl","name","workerDir","port"];if(!Y.includes($))console.error(Z0.red(`Unknown key: ${$}`)),console.error(Z0.dim(`Allowed: ${Y.join(", ")}`)),process.exit(1);let Z=I();Z[$]=$==="port"?parseInt(X,10):X,l(Z),console.log(Z0.green(`✓ ${$} set`))}import U from"picocolors";import{spawn as m6}from"node:child_process";import{createHash as h6}from"node:crypto";import{createWriteStream as j6,existsSync as z0,mkdirSync as f0,readFileSync as v2,renameSync as E2,rmSync as d0,writeFileSync as I6}from"node:fs";import{tmpdir as u6}from"node:os";import{dirname as f6,join as C0}from"node:path";import{Readable as d6}from"node:stream";import{pipeline as n6}from"node:stream/promises";var i6="https://github.com/shogo-labs/shogo-ai/releases/download";function m2(){let{platform:$,arch:X}=process,Y;if($==="darwin")Y="darwin";else if($==="linux")Y="linux";else if($==="win32")Y="windows";else throw Error(`Unsupported platform: ${$}`);let Z;if(X==="arm64")Z="arm64";else if(X==="x64")Z="x64";else throw Error(`Unsupported arch: ${X} (need arm64 or x64)`);return`${Y}-${Z}`}function L0(){if(!z0(a))return null;try{let $=v2(a,"utf-8");return JSON.parse($)}catch{return null}}async function g6($,X){let Y=X.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+)\/releases\/download/);if(!Y)throw Error(`Cannot auto-resolve latest version from non-GitHub baseUrl '${X}'. Pass --version explicitly.`);let Z=Y[1],z=Y[2];if($==="stable"){let H=`https://api.github.com/repos/${Z}/${z}/releases/latest`,w=await fetch(H,{headers:{Accept:"application/vnd.github+json"}});if(!w.ok)throw Error(`GitHub API ${w.status} for ${H}`);let W=await w.json();if(!W.tag_name)throw Error("GitHub API did not return tag_name");return b2(W.tag_name)}let J=`https://api.github.com/repos/${Z}/${z}/releases?per_page=30`,K=await fetch(J,{headers:{Accept:"application/vnd.github+json"}});if(!K.ok)throw Error(`GitHub API ${K.status} for ${J}`);let q=await K.json(),Q=$==="beta"?"-beta":"-nightly",V=q.find((H)=>H.prerelease&&/^v\d+\.\d+\.\d+-/.test(H.tag_name)&&H.tag_name.includes(Q));if(!V)throw Error(`No ${$} runtime release found`);return b2(V.tag_name)}function b2($){if(!/^v\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/.test($))throw Error(`Unexpected app tag '${$}' (expected vX.Y.Z[-prerelease])`);return $.slice(1)}function p6($,X,Y){let Z=`shogo-agent-runtime-${X}.tar.gz`,z=`v${$}`,J=`${Y.replace(/\/$/,"")}/${z}/${Z}`;return{tarball:J,sha256:`${J}.sha256`,assetName:Z}}async function c6($,X){let Y=await fetch($,{redirect:"follow"});if(!Y.ok)throw Error(`Download failed: HTTP ${Y.status} for ${$}`);if(!Y.body)throw Error(`Download failed: empty body for ${$}`);await n6(d6.fromWeb(Y.body),j6(X))}async function o6($){let X=await fetch($,{redirect:"follow"});if(!X.ok)throw Error(`SHA256 sidecar fetch failed: HTTP ${X.status} for ${$}`);let Z=(await X.text()).trim().split(/\s+/)[0];if(!/^[0-9a-f]{64}$/i.test(Z))throw Error(`SHA256 sidecar at ${$} did not contain a 64-char hex digest`);return Z.toLowerCase()}function a6($){let X=v2($);return h6("sha256").update(X).digest("hex")}async function l6($,X){await new Promise((Y,Z)=>{let z=m6("tar",["-xzf",$,"-C",X],{stdio:["ignore","pipe","pipe"]}),J="";z.stderr.on("data",(K)=>{J+=K.toString()}),z.on("error",Z),z.on("exit",(K)=>{if(K===0)Y();else Z(Error(`tar exited ${K}: ${J.trim()}`))})})}function r6($,X){p0();let Y=f6(X);if(!z0(Y))f0(Y,{recursive:!0,mode:448});let Z=`${X}.next`;if(z0(Z))d0(Z,{force:!0});E2($,Z);try{if(process.platform!=="win32"){let{chmodSync:z}=Q$("node:fs");z(Z,493)}}catch{}if(z0(X))d0(X,{force:!0});E2(Z,X)}async function h2($={}){let X=$.logger??console,Y=$.channel??"stable",Z=$.baseUrl??process.env.SHOGO_RUNTIME_RELEASES_URL??i6,z=$.target??m2(),J=$.version;if(!J)X.log(`[runtime install] Resolving latest ${Y} version...`),J=await g6(Y,Z),X.log(`[runtime install] Latest ${Y} = ${J}`);let K=L0();if(K&&K.version===J&&K.target===z&&!$.force)return X.log(`[runtime install] ${J} (${z}) already installed at ${R} — pass --force to reinstall`),{version:J,target:z,binPath:R,source:K.source,sha256:K.sha256,channel:Y};let q=p6(J,z,Z);X.log(`[runtime install] Downloading ${q.tarball}`);let Q=C0(u6(),`shogo-runtime-install-${process.pid}-${Date.now()}`);f0(Q,{recursive:!0});try{let V=C0(Q,q.assetName);await c6(q.tarball,V),X.log("[runtime install] Verifying SHA-256...");let H=await o6(q.sha256),w=a6(V);if(w!==H)throw Error(`SHA-256 mismatch for ${q.assetName}
17
- expected: ${H}
18
- actual: ${w}`);let W=C0(Q,"extract");f0(W,{recursive:!0}),await l6(V,W);let C=C0(W,"agent-runtime");if(!z0(C))throw Error(`Tarball ${q.assetName} did not contain ./agent-runtime`);r6(C,R);let O={version:J,target:z,installedAt:new Date().toISOString(),channel:Y,source:q.tarball,sha256:w};return I6(a,JSON.stringify(O,null,2)+`
19
- `,{mode:384}),X.log(`[runtime install] Installed agent-runtime ${J} (${z}) to ${R}`),{version:J,target:z,binPath:R,source:q.tarball,sha256:w,channel:Y}}finally{try{d0(Q,{recursive:!0,force:!0})}catch{}}}function j2(){return{runtimeDir:o,runtimeBin:R,versionFile:a}}async function n0($={}){let X=await h2({channel:$.channel,version:$.version,baseUrl:$.baseUrl,force:$.force});console.log(),console.log(U.green("✓"),`agent-runtime ${U.bold(X.version)} installed (${X.target})`),console.log(` ${U.dim("path: ")} ${X.binPath}`),console.log(` ${U.dim("source: ")} ${X.source}`),console.log(` ${U.dim("sha256: ")} ${X.sha256}`),console.log(` ${U.dim("channel: ")} ${X.channel}`)}function I2(){let $=L0();if(!$){console.log(U.yellow("No agent-runtime installed.")),console.log(`Run ${U.cyan("shogo runtime install")} to download the latest.`),process.exitCode=1;return}console.log(`${U.bold("agent-runtime")} ${$.version}`),console.log(` ${U.dim("target: ")} ${$.target}`),console.log(` ${U.dim("channel: ")} ${$.channel}`),console.log(` ${U.dim("installed at:")} ${$.installedAt}`),console.log(` ${U.dim("source: ")} ${$.source}`)}function u2(){let $=u(),X=j2();if(!$){console.log(U.yellow("agent-runtime binary not found on this machine.")),console.log(),console.log(U.dim("Default install path:"),X.runtimeBin),console.log(),console.log(d()),process.exitCode=1;return}if(console.log($.path),process.env.SHOGO_DEBUG||process.env.VERBOSE)console.log(U.dim(` (resolved via: ${$.source})`))}async function f2($={}){let X=L0(),Y=$.channel??X?.channel??"stable";if(X)console.log(`${U.dim("current:")} ${X.version} (${X.target}, ${X.channel})`);else console.log(U.dim("No existing install — installing fresh..."));await n0({channel:Y,baseUrl:$.baseUrl,force:!0})}import{existsSync as e6,mkdirSync as s6}from"node:fs";import{resolve as t6}from"node:path";import L from"picocolors";import{CloudFileTransport as $1}from"@shogo-ai/sdk/cloud-file-transport";async function d2($,X){if(!$)throw Error("projectId is required");let Y=_({apiKey:X.apiKey,cloudUrl:X.cloudUrl});c0(Y.projectsDir);let Z=t6(X.into??n($,Y.projectsDir));if(!e6(Z))s6(Z,{recursive:!0});let z=X.include?.split(",").map((q)=>q.trim()).filter(Boolean);if(console.log(L.bold(`
20
- shogo project pull ${L.cyan($)}`)),console.log(L.dim(" cloud ")+Y.cloudUrl),console.log(L.dim(" into ")+Z),z?.length)console.log(L.dim(" include ")+z.join(", "));console.log("");let J=new $1({apiUrl:Y.cloudUrl,apiKey:Y.apiKey,projectId:$,localDir:Z,include:z,onProgress:X1()}),K=await J.downloadAll();if(Y1("Pull",K),X.watch){console.log(L.bold(`
21
- Watching for local changes...`));let q=new $0({rootDir:Z,transport:J,onFlush:({uploaded:H,errors:w})=>{let W=H.length===1?H[0]:`${H.length} files`,C=w>0?L.red(` (${w} errors)`):"";console.log(L.dim(` ↑ ${W}${C}`))}});q.start();let Q=!1,V=async(H)=>{if(Q)return;Q=!0,console.log(L.dim(`
22
- Received ${H} — flushing pending uploads...`));try{await q.stop()}catch(w){console.warn(L.yellow(`watcher.stop: ${w?.message??w}`))}process.exit(0)};process.once("SIGINT",()=>void V("SIGINT")),process.once("SIGTERM",()=>void V("SIGTERM")),process.once("SIGHUP",()=>void V("SIGHUP")),await new Promise(()=>{})}}function X1(){return($)=>{let X=$.kind==="download"?"↓":$.kind==="upload"?"↑":$.kind==="delete"?"✗":"·",Y=$.total>0?` ${$.index+1}/${$.total}`:"",Z=$.bytes!=null?L.dim(` (${Z1($.bytes)})`):"";console.log(L.dim(` ${X}${Y} ${$.path}${Z}`))}}function Y1($,X){let Y=X.errors.length===0,Z=Y?L.green(`✓ ${$} complete`):L.red(`✗ ${$} completed with errors`);if(console.log(`
23
- ${Z}`),console.log(L.dim(" downloaded: ")+X.downloaded),X.uploaded)console.log(L.dim(" uploaded: ")+X.uploaded);if(X.deleted)console.log(L.dim(" deleted: ")+X.deleted);if(X.skipped)console.log(L.dim(" skipped: ")+X.skipped);if(!Y){console.log(L.dim(" errors: ")+X.errors.length);for(let z of X.errors.slice(0,5))console.log(L.dim(" ")+L.red(`${z.path}: ${z.message}`));if(X.errors.length>5)console.log(L.dim(` ... and ${X.errors.length-5} more`))}}function Z1($){if($<1024)return`${$}B`;if($<1048576)return`${($/1024).toFixed(1)}KB`;return`${($/1024/1024).toFixed(2)}MB`}import{existsSync as z1}from"node:fs";import{resolve as J1}from"node:path";import F from"picocolors";import{CloudFileTransport as K1}from"@shogo-ai/sdk/cloud-file-transport";async function n2($,X){if(!$)throw Error("projectId is required");let Y=_({apiKey:X.apiKey,cloudUrl:X.cloudUrl}),Z=J1(X.from??n($,Y.projectsDir));if(!z1(Z))throw Error(`Source directory does not exist: ${Z}`);let z=X.include?.split(",").map((q)=>q.trim()).filter(Boolean);if(console.log(F.bold(`
24
- shogo project push ${F.cyan($)}`)),console.log(F.dim(" cloud ")+Y.cloudUrl),console.log(F.dim(" from ")+Z),z?.length)console.log(F.dim(" include ")+z.join(", "));if(X.deleteRemote)console.log(F.yellow(" --delete-remote: remote files not present locally will be DELETED"));console.log("");let K=await new K1({apiUrl:Y.cloudUrl,apiKey:Y.apiKey,projectId:$,localDir:Z,include:z,onProgress:(q)=>{let Q=q.kind==="upload"?"↑":q.kind==="delete"?"✗":"·",V=q.total>0?` ${q.index+1}/${q.total}`:"",H=q.bytes!=null?F.dim(` (${V1(q.bytes)})`):"";console.log(F.dim(` ${Q}${V} ${q.path}${H}`))}}).uploadAll({deleteRemote:X.deleteRemote});q1("Push",K)}function q1($,X){let Y=X.errors.length===0,Z=Y?F.green(`✓ ${$} complete`):F.red(`✗ ${$} completed with errors`);if(console.log(`
25
- ${Z}`),console.log(F.dim(" uploaded: ")+X.uploaded),X.deleted)console.log(F.dim(" deleted: ")+X.deleted);if(!Y){console.log(F.dim(" errors: ")+X.errors.length);for(let z of X.errors.slice(0,5))console.log(F.dim(" ")+F.red(`${z.path}: ${z.message}`));if(X.errors.length>5)console.log(F.dim(` ... and ${X.errors.length-5} more`))}}function V1($){if($<1024)return`${$}B`;if($<1048576)return`${($/1024).toFixed(1)}KB`;return`${($/1024/1024).toFixed(2)}MB`}import{existsSync as Q1}from"node:fs";import{resolve as H1}from"node:path";import E from"picocolors";W0();async function i2($,X){if(!$)throw Error("projectId is required");let Y=_({apiKey:X.apiKey,cloudUrl:X.cloudUrl}),Z=H1(X.into??n($,Y.projectsDir));if(!Q1(Z))throw Error(`Local project dir does not exist: ${Z} — run \`shogo project pull\` first`);if(!t(Z))throw Error(`${Z} 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(E.bold(`
26
- shogo project checkout ${E.cyan($)}`)),console.log(E.dim(" cloud ")+Y.cloudUrl),console.log(E.dim(" local ")+Z),X.at)console.log(E.dim(" at ")+X.at);if(console.log(""),X.unshallow)console.log(E.dim("Unshallowing repo (this may take a moment)...")),await w0({apiUrl:Y.cloudUrl,apiKey:Y.apiKey,projectId:$,localDir:Z});if(!X.at){let J=await s({apiUrl:Y.cloudUrl,apiKey:Y.apiKey,projectId:$,localDir:Z});console.log(E.green(`✓ Reset to ${J.commitSha.slice(0,8)} (remote HEAD)`));return}let z;try{z=(await T(["rev-parse","--verify",`${X.at}^{commit}`],{cwd:Z})).stdout.trim()}catch{z=await w1(Y.cloudUrl,Y.apiKey,$,X.at),console.log(E.dim(` resolved checkpoint "${X.at}" → ${z.slice(0,8)}`))}try{await s({apiUrl:Y.cloudUrl,apiKey:Y.apiKey,projectId:$,localDir:Z,branch:z})}catch{if(!X.unshallow)console.log(E.yellow(" fetch failed; unshallowing and retrying...")),await w0({apiUrl:Y.cloudUrl,apiKey:Y.apiKey,projectId:$,localDir:Z}),await s({apiUrl:Y.cloudUrl,apiKey:Y.apiKey,projectId:$,localDir:Z,branch:z});else throw Error(`Cannot reach commit ${z} in local clone`)}await T(["reset","--hard",z],{cwd:Z}),console.log(E.green(`✓ Checked out ${z.slice(0,8)}`))}async function w1($,X,Y,Z){let z=`${$.replace(/\/+$/,"")}/api/projects/${encodeURIComponent(Y)}/checkpoints?limit=100`,J=await fetch(z,{headers:{Authorization:`Bearer ${X}`}});if(!J.ok)throw Error(`Failed to list checkpoints: HTTP ${J.status}`);let K=await J.json(),q=Z.toLowerCase(),Q=K.checkpoints.find((V)=>{if(V.name&&V.name.toLowerCase()===q)return!0;if(V.commitSha.startsWith(Z))return!0;if(V.commitMessage&&V.commitMessage.toLowerCase().includes(q))return!0;return!1});if(!Q)throw Error(`No checkpoint matches "${Z}" (searched ${K.checkpoints.length} most recent)`);return Q.commitSha}import{createInterface as L1}from"node:readline";import B from"picocolors";import{execFileSync as p2}from"node:child_process";import{existsSync as i,copyFileSync as g2}from"node:fs";import S from"node:path";import{homedir as c2,platform as o2}from"node:os";function a2($,X,Y){let z=p2($,["-e",Y],{env:{...process.env,DBP:X},encoding:"utf-8",stdio:["ignore","pipe","pipe"],timeout:5000}).trim();if(!z)throw Error(`bun script returned empty output for ${X}`);return JSON.parse(z)}function O0($,X){if(!i(X))return[];return a2($,X,`
9
+ `));return!X}import{accessSync as S$,constants as R$,existsSync as D$}from"node:fs";import{delimiter as x$,join as P$}from"node:path";function u($={}){let X=$.env??process.env,Y=b$($);for(let K of Y)if(K2(K.path))return K;let Z=$.systemBinName??z2(),z=E$(Z,X.PATH);if(z)return{path:z,source:"path"};return null}function b$($){let X=$.env??process.env,Y=[];if($.flag&&$.flag.trim())Y.push({path:$.flag.trim(),source:"flag"});let Z=X.SHOGO_AGENT_RUNTIME_BIN?.trim();if(Z)Y.push({path:Z,source:"env"});return Y.push({path:x,source:"home"}),Y}function z2(){return process.platform==="win32"?"shogo-agent-runtime.exe":"shogo-agent-runtime"}function K2($){if(!D$($))return!1;try{if(process.platform==="win32")return!0;return S$($,R$.X_OK),!0}catch{return!1}}function E$($,X){if(!X)return null;for(let Y of X.split(x$)){if(!Y)continue;let Z=P$(Y,$);if(K2(Z))return Z}return null}function n($={}){let X=$.env??process.env,Y=[];if(Y.push("Error: agent-runtime binary not found."),Y.push(""),Y.push("Looked in (priority order):"),$.flag)Y.push(` --runtime-bin ${$.flag}`);if(X.SHOGO_AGENT_RUNTIME_BIN)Y.push(` $SHOGO_AGENT_RUNTIME_BIN = ${X.SHOGO_AGENT_RUNTIME_BIN}`);return Y.push(` ${x} (default install location)`),Y.push(` ${z2()} on $PATH`),Y.push(""),Y.push("Fix:"),Y.push(" - Run `shogo runtime install` to download the latest binary, or"),Y.push(" - Pass `--runtime-bin <path>` to point at an existing build."),Y.join(`
10
+ `)}import{spawn as c$,spawnSync as o$}from"node:child_process";import{createHmac as C2,randomBytes as a$}from"node:crypto";import{existsSync as D0,mkdirSync as q2,readdirSync as l$}from"node:fs";import{createConnection as r$}from"node:net";import{tmpdir as e$}from"node:os";import{dirname as s$,join as Y0}from"node:path";import{CloudFileTransport as x0}from"@shogo-ai/sdk/cloud-file-transport";import{watch as V2,statSync as u$}from"node:fs";import{relative as YX,sep as f$,posix as n$}from"node:path";var d$=1500,i$=new Set(["node_modules",".git","dist","build",".vite",".cache"]),g$=[".shogo/"];function p$($){if(!$)return!0;return $.split("/").some((X)=>i$.has(X))}class X0{rootDir;transport;debounceMs;logger;onFlush;mode;git;commitAndPush;watcher=null;pending=new Set;timer=null;flushing=!1;stopped=!1;constructor($){if(this.rootDir=$.rootDir,this.transport=$.transport,this.debounceMs=$.debounceMs??d$,this.logger=$.logger??console,this.onFlush=$.onFlush,this.mode=$.mode??"files",this.mode==="git"&&!$.git)throw Error('CloudSyncWatcher: mode: "git" requires the `git` option block');this.git=$.git,this.commitAndPush=$.commitAndPush??(async(X)=>{return(await Promise.resolve().then(() => (k0(),J2))).commitAndPush(X)})}start(){if(this.watcher)return;if(this.stopped)throw Error("CloudSyncWatcher: already stopped, build a fresh instance");try{this.watcher=V2(this.rootDir,{recursive:!0},($,X)=>{if(!X)return;this.handleEvent(String(X))})}catch($){this.logger.warn(`[CloudSyncWatcher] recursive watch failed (${$?.message??$}); falling back to root-only watch.`),this.watcher=V2(this.rootDir,(X,Y)=>{if(!Y)return;this.handleEvent(String(Y))})}}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($){if(this.stopped)return;let X=$.split(f$).join(n$.sep);if(p$(X))return;try{let Y=`${this.rootDir}/${X}`;if(u$(Y).isDirectory())return}catch{return}this.pending.add(X),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 $=Array.from(this.pending);this.pending.clear();try{if(this.mode==="git")await this.flushGit($);else await this.flushFiles($)}catch(X){this.logger.error(`[CloudSyncWatcher] flush failed: ${X?.message??X}`);for(let Y of $)this.pending.add(Y);this.scheduleFlush()}finally{this.flushing=!1}}async flushFiles($){if($.length===0)return;let X=await this.transport.uploadFiles($);if(this.onFlush?.({uploaded:$,errors:X.errors.length}),X.errors.length>0)for(let Y of X.errors)this.logger.warn(`[CloudSyncWatcher] upload ${Y.path}: ${Y.message}`)}async flushGit($){if(!this.git)throw Error("CloudSyncWatcher: missing git options");let X=[],Y=[];for(let K of $)if(g$.some((J)=>K===J.slice(0,-1)||K.startsWith(J)))X.push(K);else Y.push(K);if(X.length>0){let K=await this.transport.uploadFiles(X);if(K.errors.length>0)for(let J of K.errors)this.logger.warn(`[CloudSyncWatcher] upload ${J.path}: ${J.message}`)}let Z=!1,z;if(Y.length>0){let K=`auto: ${new Date().toISOString()}`;try{let J=await this.commitAndPush({apiUrl:this.git.apiUrl,apiKey:this.git.apiKey,projectId:this.git.projectId,localDir:this.rootDir,message:K,branch:this.git.branch,authorEmail:this.git.authorEmail,authorName:this.git.authorName,logger:this.logger});Z=J.committed,z=J.commitSha}catch(J){throw J}}this.onFlush?.({uploaded:$,errors:0,committed:Z,commitSha:z})}}k0();var P0=37100,b0=37900,w2=1,t$=2,O0=16,$6=900000,X6=30000,Y6=1000,Z6=60000,Q2=8,H2=300000,z6=60000,K6=500,J6=30000,W2=25000,V6=5000,q6=500,w6=($)=>({command:$,args:[]}),E0=null;function L2(){if(!E0)E0=a$(32).toString("hex");return E0}function k2($){return C2("sha256",L2()).update(`runtime:${$}`).digest("hex")}function Q6($){return C2("sha256",L2()).update(`webhook:${$}`).digest("hex")}function O2($){let X=$.indexOf("?");if(X===-1)return{pathname:$,search:""};return{pathname:$.slice(0,X),search:$.slice(X)}}function G2($){try{return l$($).length===0}catch{return!0}}function v0($,X,Y){let Z=[];switch(Z.push(`Cannot spawn agent-runtime for project ${$}: no workspace directory available.`),Z.push(""),X){case"no-auto-pull-config":Z.push(" Reason: WorkerRuntimeManager was constructed without an `autoPull` config and no caller-provided `projectDir` was found on disk."),Z.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":Z.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":Z.push(` Reason: auto-pull was disabled (--no-auto-pull) and the expected pre-pulled workspace at ${Y} is missing or empty.`);break}return Z.push(""),Z.push(" How to fix (pick one):"),Z.push(" 1. Re-enable auto-pull (default). Drop the --no-auto-pull flag and restart"),Z.push(" the worker. The first inbound request for this project will clone its"),Z.push(" workspace from Shogo Cloud into <projectsDir>/<projectId>/."),Z.push(""),Z.push(" 2. Pre-pull manually with `shogo project pull <projectId>` before starting"),Z.push(" the worker. Use this when you want full control over when the clone runs"),Z.push(" (slow links, scheduled maintenance windows, etc.)."),Z.push(""),Z.push(" 3. Point the worker at an existing workspace by setting either:"),Z.push(" --projects-dir <path> (per-invocation flag)"),Z.push(" SHOGO_PROJECTS_DIR=<path> (env var, persists across restarts)"),Z.push(" shogo config set projectsDir <path>"),Z.push(" Whichever path you pick must contain a subdirectory named after the "),Z.push(` project id (e.g. <path>/${$}/) populated with the project's source.`),Z.push(""),Z.push(" Docs: https://shogo.ai/docs/self-hosted-worker#workspace-seeding"),Z.join(`
11
+ `)}class m0{opts;log;runtimes=new Map;usedPorts=new Set;spawnCommand;resolved=null;stopped=!1;watchers=new Map;pulledProjects=new Set;syncModes=new Map;constructor($={}){this.opts=$,this.log=$.logger??console,this.spawnCommand=$.spawnCommand??w6}resolveBinary(){if(!this.resolved)this.resolved=this.opts.resolveBin?this.opts.resolveBin():u({flag:this.opts.runtimeBin,env:this.opts.env});return this.resolved}async resolveLocalUrl($,X){let{pathname:Y,search:Z}=O2($);if(!(Y.startsWith("/agent/")||Y==="/agent"))return null;if(!X){let J=this.getActiveProjects();if(J.length!==1)return null;X=J[0]}let z=await this.spawnConfigFor(X);if(!z)return this.log.warn(`[WorkerRuntimeManager] No spawn config for ${X} — set defaultSpawnConfig or enrichSpawnConfig`),null;let K=await this.ensureRunning(X,z);if(!K.agentPort)return null;return this.touch(X),`http://127.0.0.1:${K.agentPort}${Y}${Z}`}deriveRuntimeToken($){return k2($)}describeRejection($,X){let{pathname:Y}=O2($);if(!(Y.startsWith("/agent/")||Y==="/agent"))return{code:"CLI_WORKER_HAS_NO_DATA_API",message:`cli-worker only serves /agent/* paths; tried: ${Y}`};return{code:"CLI_WORKER_NO_PROJECT_FOR_PATH",message:`cli-worker received an /agent path without a single active project; projectId=${X??"none"}, path=${Y}`}}async spawnConfigFor($){let X=this.opts.defaultSpawnConfig;if(!X)return null;if(this.opts.enrichSpawnConfig)try{return await this.opts.enrichSpawnConfig($,X)}catch(Y){this.log.warn(`[WorkerRuntimeManager] enrichSpawnConfig failed for ${$}: ${Y?.message??Y}`)}return X}async ensureRunning($,X){if(this.stopped)throw Error("WorkerRuntimeManager is stopped");let Y=this.runtimes.get($);if(Y?.status==="failed")throw Error(`[WorkerRuntimeManager] cannot ensureRunning(${$}): ${Y.lastError??"runtime is in failed state"}. Call resetFailure(${$}) or stop(${$}) before retrying.`);X=await this.maybeAutoPull($,X);let Z=this.runtimes.get($);if(Z?.status==="running")return this.touch($),this.snapshot(Z);if(Z?.startPromise){let K=await Z.startPromise;return this.snapshot(K)}let z=Z??this.makeSlot($,X);if(!Z)this.runtimes.set($,z);z.spawnConfig=X,z.startPromise=this.doStart(z);try{let K=await z.startPromise;return this.enforceMaxRuntimes($),this.snapshot(K)}finally{z.startPromise=null}}enforceMaxRuntimes($){let X=this.opts.maxRuntimes;if(X==null||!Number.isFinite(X)||X<=0)return;let Y=Date.now(),Z=Array.from(this.runtimes.values()).filter((J)=>J.status==="running");if(Z.length<=X)return;let z=Z.filter((J)=>J.projectId!==$&&Y-J.lastUsedAt>=X6).sort((J,V)=>J.lastUsedAt-V.lastUsedAt),K=Z.length-X;for(let J of z){if(K<=0)break;let V=Y-J.lastUsedAt;this.log.log(`[WorkerRuntimeManager] maxRuntimes=${X} exceeded (${Z.length} running) — `+`LRU-evicting ${J.projectId} (idle ${Math.round(V/1000)}s)`),this.stop(J.projectId).catch((w)=>{this.log.warn(`[WorkerRuntimeManager] maxRuntimes eviction of ${J.projectId} failed: ${w?.message??w}`)}),K--}if(K>0)this.log.log(`[WorkerRuntimeManager] maxRuntimes=${X} still exceeded by ${K} after eviction pass — `+"remaining over-cap slots are mid-stream; will retry on next spawn / idle reap")}async ensurePulled($,X){return this.maybeAutoPull($,X)}async maybeAutoPull($,X){let Y=this.opts.autoPull;if(X.projectDir&&D0(X.projectDir))return X;if(!Y)throw Error(v0($,"no-auto-pull-config",null));if(!Y.projectsDir)throw Error(v0($,"no-projects-dir",null));if(!Y.enabled){let J=Y0(Y.projectsDir,$);if(D0(J)&&!G2(J))return{...X,projectDir:J};throw Error(v0($,"auto-pull-disabled",J))}if(this.pulledProjects.has($))return{...X,projectDir:Y0(Y.projectsDir,$)};let Z=Y0(Y.projectsDir,$),z=Y.logger??this.log,K={cloneProject:Y.gitOps?.cloneProject??R0,gitIsAvailable:Y.gitOps?.gitIsAvailable??S0,isGitRepo:Y.gitOps?.isGitRepo??$0};this.pulledProjects.add($);try{q2(Z,{recursive:!0});let J=G2(Z),V=K.isGitRepo(Z),Q=(Y.useGit!==!1?await K.gitIsAvailable():!1)&&(J||V)?"git":"files";if(this.syncModes.set($,Q),Q==="git"){if(J){z.log(`[WorkerRuntimeManager] auto-pull: git clone project ${$} into ${Z}`);try{let H=await K.cloneProject({apiUrl:X.cloudUrl,apiKey:X.apiKey,projectId:$,localDir:Z,shallow:!0,logger:z});z.log(`[WorkerRuntimeManager] auto-pull: ${$} cloned at ${H.commitSha.slice(0,8)}`)}catch(H){z.warn(`[WorkerRuntimeManager] auto-pull: git clone failed for ${$} (${H?.message??H}); falling back to CloudFileTransport.downloadAll`),this.syncModes.set($,"files"),await this.fileTransportClone($,Z,X,z)}}else if(V)z.log(`[WorkerRuntimeManager] auto-pull: ${$} already has .git/; skipping clone`);if(this.syncModes.get($)==="git")await this.topUpShogoState($,Z,X,z)}else if(J)await this.fileTransportClone($,Z,X,z);else z.log(`[WorkerRuntimeManager] auto-pull: ${$} workspace already populated; skipping clone`);if(Y.watch!==!1&&!this.watchers.has($))try{let H=new x0({apiUrl:X.cloudUrl,apiKey:X.apiKey,projectId:$,localDir:Z}),W=this.syncModes.get($)??"files",G=new X0({rootDir:Z,transport:H,logger:z,mode:W,git:W==="git"?{apiUrl:X.cloudUrl,apiKey:X.apiKey,projectId:$}:void 0});G.start(),this.watchers.set($,G)}catch(H){z.warn(`[WorkerRuntimeManager] auto-pull: watcher start failed for ${$}: ${H?.message??H}`)}}catch(J){z.warn(`[WorkerRuntimeManager] auto-pull: failed for ${$} — runtime will fall back to template defaults. `+`(${J?.message??J})`)}return{...X,projectDir:Z}}async fileTransportClone($,X,Y,Z){Z.log(`[WorkerRuntimeManager] auto-pull: file-transport clone of ${$} into ${X}`);let K=await new x0({apiUrl:Y.cloudUrl,apiKey:Y.apiKey,projectId:$,localDir:X}).downloadAll();Z.log(`[WorkerRuntimeManager] auto-pull: ${$} downloaded ${K.downloaded} files (${K.errors.length} errors)`)}async topUpShogoState($,X,Y,Z){try{let z=new x0({apiUrl:Y.cloudUrl,apiKey:Y.apiKey,projectId:$,localDir:X}),J=(await z.listManifest()).filter((w)=>w.path===".shogo"||w.path.startsWith(".shogo/"));if(J.length===0)return;let V=await z.downloadFiles(J);Z.log(`[WorkerRuntimeManager] auto-pull: ${$} .shogo/ top-up downloaded ${V.downloaded} files (${V.errors.length} errors)`)}catch(z){Z.warn(`[WorkerRuntimeManager] auto-pull: .shogo top-up failed for ${$}: ${z?.message??z}`)}}shouldDeferToCloudSync($){return!!this.opts.autoPull?.enabled&&this.pulledProjects.has($)}status($){let X=this.runtimes.get($);return X?this.snapshot(X):null}getActiveProjects(){return Array.from(this.runtimes.keys()).filter(($)=>{let X=this.runtimes.get($);return X&&(X.status==="running"||X.status==="starting"||X.status==="restarting")})}touch($){let X=this.runtimes.get($);if(!X)return;X.lastUsedAt=Date.now(),this.armIdleTimer(X)}async stop($,X="SIGTERM"){let Y=this.runtimes.get($);if(!Y)return;if(Y.status="stopping",Y.restartTimer)clearTimeout(Y.restartTimer),Y.restartTimer=null;if(Y.idleTimer)clearTimeout(Y.idleTimer),Y.idleTimer=null;if(Y.graceTimer)clearTimeout(Y.graceTimer),Y.graceTimer=null;if(Y.proc){this.killProcessGroup(Y,X);try{Y.proc.kill(X)}catch{}await this.waitForExit(Y.proc,5000),this.killProcessGroup(Y,"SIGKILL")}Y.pid=null,this.releasePort(Y.agentPort),this.runtimes.delete($)}resetFailure($){let X=this.runtimes.get($);if(!X||X.status!=="failed")return!1;if(X.restartTimer)clearTimeout(X.restartTimer),X.restartTimer=null;if(X.idleTimer)clearTimeout(X.idleTimer),X.idleTimer=null;if(X.graceTimer)clearTimeout(X.graceTimer),X.graceTimer=null;return this.runtimes.delete($),this.log.log(`[WorkerRuntimeManager] resetFailure: ${$} cleared, next ensureRunning will respawn`),!0}async stopAll($="SIGTERM"){this.stopped=!0;let X=Array.from(this.watchers.keys());await Promise.all(X.map(async(Z)=>{let z=this.watchers.get(Z);if(this.watchers.delete(Z),z)try{await z.stop()}catch(K){this.log.warn(`[WorkerRuntimeManager] watcher stop ${Z}: ${K?.message??K}`)}}));let Y=Array.from(this.runtimes.keys());await Promise.all(Y.map((Z)=>this.stop(Z,$).catch((z)=>{this.log.error(`[WorkerRuntimeManager] Failed to stop ${Z}: ${z?.message??z}`)})))}makeSlot($,X){return{projectId:$,agentPort:0,apiServerPort:0,status:"starting",proc:null,pid:null,startedAt:0,lastStdoutAt:0,lastUsedAt:Date.now(),restarts:0,consecutiveFailures:0,lastFailureAt:0,graceTimer:null,restartTimer:null,idleTimer:null,spawnConfig:X,startPromise:null}}async doStart($){let X=this.resolveBinary();if(!X)throw $.status="error",$.lastError="agent-runtime binary not found (run `shogo runtime install`)",Error($.lastError);if(!$.agentPort)$.agentPort=await this.allocatePort(),$.apiServerPort=$.agentPort+w2;let Y=this.buildEnv($,X.path),Z=this.resolveCwd($),{command:z,args:K}=this.spawnCommand(X.path);this.log.log(`[WorkerRuntimeManager] Spawning agent-runtime for ${$.projectId} via ${z} ${K.join(" ")} (port=${$.agentPort}, source=${X.source})`);let J=process.platform!=="win32",V=c$(z,K,{cwd:Z,env:Y,detached:J,stdio:["ignore","pipe","pipe"]});if(J)try{V.unref()}catch{}$.proc=V,$.pid=V.pid??null,$.status="starting",$.startedAt=Date.now(),$.lastStdoutAt=$.startedAt,V.on("error",(q)=>{$.lastError=q?.message??String(q),this.log.error(`[WorkerRuntimeManager] spawn error for ${$.projectId}: ${$.lastError}`)}),V.on("exit",(q,Q)=>{this.handleExit($,q,Q)});let w=`[runtime:${$.projectId.slice(0,8)}]`;V.stdout?.on("data",(q)=>{$.lastStdoutAt=Date.now();for(let Q of q.toString().trimEnd().split(`
12
+ `))if(Q)this.log.log(`${w} ${Q}`)}),V.stderr?.on("data",(q)=>{$.lastStdoutAt=Date.now();for(let Q of q.toString().trimEnd().split(`
13
+ `))if(Q)this.log.error(`${w} ${Q}`)});try{return await this.waitForHealth($,J6),$.status="running",$.lastUsedAt=Date.now(),this.armIdleTimer($),this.armGraceTimer($),$}catch(q){$.status="error",$.lastError=q?.message??String(q),this.killProcessGroup($,"SIGTERM");try{V.kill("SIGTERM")}catch{}throw this.releasePort($.agentPort),$.agentPort=0,$.apiServerPort=0,q}}killProcessGroup($,X){if(!$.pid)return;if(process.platform==="win32"){try{o$("taskkill",["/F","/T","/PID",String($.pid)],{stdio:"ignore",windowsHide:!0})}catch{}return}try{process.kill(-$.pid,X)}catch{}}armGraceTimer($){if($.graceTimer)clearTimeout($.graceTimer),$.graceTimer=null;$.graceTimer=setTimeout(()=>{if($.graceTimer=null,$.consecutiveFailures>0)$.consecutiveFailures=0},z6);try{$.graceTimer.unref?.()}catch{}}buildEnv($,X){let Y=$.spawnConfig,Z={...this.opts.env??process.env,PROJECT_ID:$.projectId,PORT:String($.agentPort),API_SERVER_PORT:String($.apiServerPort),SKILL_SERVER_PORT:String($.apiServerPort),WORKSPACE_API_PORT_BASE:String($.agentPort+t$),NODE_ENV:"production",SHOGO_CLOUD_URL:Y.cloudUrl,SHOGO_API_URL:Y.cloudUrl,SHOGO_API_KEY:Y.apiKey,RUNTIME_AUTH_SECRET:k2($.projectId),WEBHOOK_TOKEN:Q6($.projectId)};if(Y.projectDir)Z.PROJECT_DIR=Y.projectDir,Z.WORKSPACE_DIR=Y.projectDir;if(this.shouldDeferToCloudSync($.projectId))Z.SHOGO_CLOUD_SYNC="1";if(Y.aiProxyUrl)Z.AI_PROXY_URL=Y.aiProxyUrl;if(Y.aiProxyToken)Z.AI_PROXY_TOKEN=Y.aiProxyToken;if(Y.techStackId)Z.TECH_STACK_ID=Y.techStackId;if(Y.name)Z.AGENT_NAME=Y.name;if(Y.workspaceId)Z.WORKSPACE_ID=Y.workspaceId;if(!Z.TREE_SITTER_WASM_DIR)Z.TREE_SITTER_WASM_DIR=Y0(s$(X),"tree-sitter-wasm");if(Y.extraEnv)Object.assign(Z,Y.extraEnv);return Z}resolveCwd($){let X=$.spawnConfig;if(X.projectDir&&D0(X.projectDir))return X.projectDir;let Y=this.opts.runtimeWorkDir??Y0(e$(),"shogo-runtime",$.projectId);return q2(Y,{recursive:!0}),Y}handleExit($,X,Y){let Z=Y===null&&X===0;if(this.log.log(`[WorkerRuntimeManager] runtime ${$.projectId} exited (code=${X}, signal=${Y})`),$.proc=null,$.graceTimer)clearTimeout($.graceTimer),$.graceTimer=null;if($.status==="stopping"||this.stopped){$.status="stopped",$.pid=null,this.releasePort($.agentPort),$.agentPort=0,$.apiServerPort=0;return}if(Z){$.status="stopped",$.pid=null,this.releasePort($.agentPort),$.agentPort=0,$.apiServerPort=0,this.runtimes.delete($.projectId);return}this.killProcessGroup($,"SIGKILL"),$.pid=null;let z=Date.now(),K=z-$.lastFailureAt<=H2;if($.consecutiveFailures=K?$.consecutiveFailures+1:1,$.lastFailureAt=z,$.restarts+=1,$.lastError=`exited code=${X} signal=${Y}`,$.consecutiveFailures>=Q2){if($.status="failed",$.lastError=`Circuit breaker tripped: ${$.consecutiveFailures} consecutive non-clean exits within ${Math.round(H2/1000)}s (last: code=${X} signal=${Y}). 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. Stop, fix the workspace, and call resetFailure(projectId) (or stop(projectId)) to allow another spawn attempt.`,this.releasePort($.agentPort),$.agentPort=0,$.apiServerPort=0,$.restartTimer)clearTimeout($.restartTimer),$.restartTimer=null;if($.idleTimer)clearTimeout($.idleTimer),$.idleTimer=null;this.log.error(`[WorkerRuntimeManager] ${$.lastError}`);return}let J=this.restartBackoffMs($.restarts);if($.status="restarting",this.log.warn(`[WorkerRuntimeManager] restarting ${$.projectId} in ${Math.round(J/1000)}s (restart #${$.restarts}, consecutive failures ${$.consecutiveFailures}/${Q2})`),$.restartTimer)clearTimeout($.restartTimer);$.restartTimer=setTimeout(()=>{if($.restartTimer=null,$.status==="failed"||this.stopped)return;$.startPromise=this.doStart($).then((V)=>{return $.startPromise=null,V}).catch((V)=>{return $.startPromise=null,this.log.error(`[WorkerRuntimeManager] restart of ${$.projectId} failed: ${V?.message??V}`),$})},J);try{$.restartTimer.unref?.()}catch{}}restartBackoffMs($){let X=Math.min(Y6*Math.pow(2,Math.max(0,$-1)),Z6),Y=X*0.2*Math.random();return X+Y}armIdleTimer($){if($.idleTimer)clearTimeout($.idleTimer),$.idleTimer=null;let X=this.opts.idleMs??$6;if(!Number.isFinite(X)||X<=0)return;$.idleTimer=setTimeout(()=>{let Y=Date.now()-$.lastUsedAt;if(Y<X){this.armIdleTimer($);return}this.log.log(`[WorkerRuntimeManager] idle-evicting ${$.projectId} after ${Math.round(Y/1000)}s`),this.stop($.projectId).catch((Z)=>{this.log.warn(`[WorkerRuntimeManager] idle stop failed: ${Z?.message??Z}`)})},X)}async allocatePort(){let $=b0-P0,X=Math.min($,50);for(let Y=0;Y<X;Y++){let Z=P0+Math.floor(Math.random()*$);if(Z+O0-1>b0)continue;let z=!0;for(let V=0;V<O0;V++)if(this.usedPorts.has(Z+V)){z=!1;break}if(!z)continue;let K=await this.isPortListening(Z),J=await this.isPortListening(Z+w2);if(K||J)continue;for(let V=0;V<O0;V++)this.usedPorts.add(Z+V);return Z}throw Error(`Cannot allocate port in range ${P0}-${b0} after ${X} attempts`)}releasePort($){if(!$)return;for(let X=0;X<O0;X++)this.usedPorts.delete($+X)}async isPortListening($){let X=new AbortController,Y=setTimeout(()=>X.abort(),250);try{return await fetch(`http://127.0.0.1:${$}/`,{method:"HEAD",signal:X.signal}),clearTimeout(Y),!0}catch{return clearTimeout(Y),!1}}tcpProbe($){return new Promise((X)=>{let Y=!1,Z=(K)=>{if(Y)return;Y=!0;try{z.destroy()}catch{}X(K)},z=r$({host:"127.0.0.1",port:$});z.setTimeout(q6),z.once("connect",()=>Z(!0)),z.once("error",()=>Z(!1)),z.once("timeout",()=>Z(!1))})}async waitForHealth($,X){let{agentPort:Y,proc:Z}=$;if(!Z)throw Error(`waitForHealth: slot ${$.projectId} has no spawned process`);let z=Date.now(),K=z+X,J=null,V=!1,w=0,q=0,Q=0,H=0,W=z;while(Date.now()<K){if(H++,Z.exitCode!==null||Z.signalCode!=null||Z.killed)throw Error(`agent-runtime exited (code=${Z.exitCode}, signal=${Z.signalCode}) before becoming healthy on port ${Y}`);q++;let G=new AbortController,L=setTimeout(()=>G.abort(),1500);try{let k=await fetch(`http://127.0.0.1:${Y}/health`,{method:"GET",signal:G.signal});if(clearTimeout(L),k.ok){this.log.log(`[WorkerRuntimeManager] /health ready for ${$.projectId} on port ${Y} (HTTP ${k.status} after ${Date.now()-z}ms, ${H} iter, ${q} http)`);return}J=`HTTP /health returned ${k.status}`}catch(k){clearTimeout(L);let _=k?.name??"Error",p=k?.code??k?.cause?.code;J=`HTTP /health failed: ${_}${p?`(${p})`:""}: ${k?.message??k}`}if(Q++,V=await this.tcpProbe(Y),V){w=Date.now();let k=Date.now()-$.lastStdoutAt;if(k>=W2)throw Error(`agent-runtime wedged on port ${Y}: TCP listening but stdout silent for ${k}ms (> ${W2}ms window); ${q} /health attempts, last error: ${J??"n/a"}`)}let A=Date.now();if(A-W>=V6){let k=A-z,_=A-$.lastStdoutAt,p=w>0?A-w:null;this.log.log(`[WorkerRuntimeManager] still waiting for /health on ${$.projectId} port ${Y} (${(k/1000).toFixed(1)}s elapsed, tcpListening=${V}${p!=null?`(${p}ms ago)`:""}, lastStdout=${_}ms ago, ${q} http, ${Q} tcp, lastError=${J??"n/a"})`),W=A}await new Promise((k)=>setTimeout(k,K6))}if(Z.exitCode!==null||Z.signalCode!=null||Z.killed)throw Error(`agent-runtime exited (code=${Z.exitCode}, signal=${Z.signalCode}) before becoming healthy on port ${Y}`);throw Error(`Timeout waiting for agent-runtime /health on port ${Y} after ${H} iter (httpAttempts=${q}, tcpAttempts=${Q}, tcpListening=${V}, lastError=${J??"n/a"})`)}async waitForExit($,X){if($.exitCode!==null||$.signalCode!=null||$.killed)return;await new Promise((Y)=>{let Z=setTimeout(()=>{try{$.kill("SIGKILL")}catch{}Y()},X);$.once("exit",()=>{clearTimeout(Z),Y()})})}snapshot($){return{projectId:$.projectId,status:$.status,agentPort:$.agentPort||void 0,apiServerPort:$.apiServerPort||void 0,pid:$.proc?.pid,startedAt:$.startedAt||void 0,lastUsedAt:$.lastUsedAt,restarts:$.restarts,lastError:$.lastError}}}import{hostname as h0,platform as j0,arch as I0}from"node:os";function d(){if(typeof __SHOGO_WORKER_VERSION__==="string"&&__SHOGO_WORKER_VERSION__.length>0)return`shogo-cli/${__SHOGO_WORKER_VERSION__}`;return"shogo-cli/unknown"}class N2 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 Z0=60,G0=300,u0=3,H6=u0,W6=1800000,k6=25000,B2=1000,A2=60000,U2=3;class f0{opts;log;pollTimer=null;ws=null;wsIdleTimer=null;heartbeatTimer=null;stopped=!1;currentPollInterval=Z0;wsReconnectAttempt=0;lastHeartbeatError=null;consecutiveAuthFailures=0;consecutiveAuthSuccesses=0;serverPublishedWsUrl=null;activeAbortControllers=new Map;constructor($){this.opts=$,this.log=$.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=Z0,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 $=(this.opts.wsUrlOverride||process.env.SHOGO_TUNNEL_WS_URL||"").trim();if($)return $.replace(/\/$/,"");if(this.serverPublishedWsUrl)return this.serverPublishedWsUrl.replace(/\/$/,"");return this.getCloudUrl().replace(/^http/,"ws")}buildWsUrl(){return`${this.getWsBaseUrl()}/api/instances/ws`}supportsWebSocketConstructorHeaders($=globalThis){if(typeof $.Bun<"u"||typeof $.process?.versions?.bun==="string")return!0;let X=$.process?.versions?.node;if(X){if(parseInt(X.split(".")[0]??"0",10)>=21)return!0}return!1}createTunnelWebSocket($,X,Y=globalThis){if(!this.supportsWebSocketConstructorHeaders(Y))throw new N2;return new WebSocket($,X)}getReconnectDelayMs(){let $=Math.min(B2*Math.pow(2,this.wsReconnectAttempt),A2),X=$*0.2*Math.random();return $+X}async collectMetadata(){let $={hostname:h0(),os:j0(),arch:I0(),uptime:process.uptime(),protocolVersion:U2,tunnelStatus:this.ws?.readyState===WebSocket.OPEN?"connected":"polling",kind:this.opts.kind??"cli-worker"};try{let X=this.opts.resolver.getActiveProjects();$.activeProjects=X.length,$.projects=X.map((Y)=>{let Z=this.opts.resolver.status(Y);return{projectId:Y,status:Z?.status??"unknown",agentPort:Z?.agentPort}})}catch{$.activeProjects=0}return $}async sendHeartbeat(){let $=this.getCloudUrl(),X=await this.collectMetadata(),Y=h0(),Z=this.opts.name??Y,z=await fetch(`${$}/api/instances/heartbeat`,{method:"POST",headers:{"Content-Type":"application/json","User-Agent":d(),"x-api-key":this.opts.apiKey,"x-shogo-kind":this.opts.kind??"cli-worker"},body:JSON.stringify({hostname:Y,name:Z,os:j0(),arch:I0(),kind:this.opts.kind??"cli-worker",metadata:X})});if(!z.ok)throw Error(`Heartbeat failed: HTTP ${z.status}`);let K=await z.json();if(typeof K.wsUrl==="string"&&K.wsUrl.length>0){if(K.wsUrl!==this.serverPublishedWsUrl)this.log.log(`[WorkerTunnel] Cloud advertised tunnel WS URL: ${K.wsUrl}`);this.serverPublishedWsUrl=K.wsUrl}return K}scheduleNextPoll($){if(this.stopped)return;if(this.pollTimer)clearTimeout(this.pollTimer);let X=($??this.currentPollInterval)*1000;this.pollTimer=setTimeout(()=>void this.heartbeatLoop(),X)}async heartbeatLoop(){if(this.stopped)return;if(this.ws&&this.ws.readyState===WebSocket.OPEN){this.scheduleNextPoll(this.currentPollInterval);return}try{let $=await this.sendHeartbeat(),X=$.nextPollIn||Z0;if(this.consecutiveAuthFailures>=u0){if(this.consecutiveAuthSuccesses++,this.consecutiveAuthSuccesses<H6){this.currentPollInterval=G0,this.scheduleNextPoll();return}}if(this.currentPollInterval=X,this.lastHeartbeatError)this.log.log("[WorkerTunnel] Heartbeat recovered"),this.lastHeartbeatError=null;if(this.consecutiveAuthFailures=0,this.consecutiveAuthSuccesses=0,$.wsRequested&&!this.ws){this.log.log("[WorkerTunnel] Cloud requested WebSocket — connecting..."),this.connectWs();return}}catch($){let X=$?.message??String($);if(/HTTP 40[13]\b/.test(X))this.consecutiveAuthFailures++,this.consecutiveAuthSuccesses=0;else this.consecutiveAuthFailures=0,this.consecutiveAuthSuccesses=0;if(X!==this.lastHeartbeatError)this.log.error(`[WorkerTunnel] Heartbeat error: ${X}`),this.lastHeartbeatError=X;if(this.consecutiveAuthFailures>=u0){let Z=`tunnel saw ${this.consecutiveAuthFailures} consecutive auth failures from Shogo Cloud`;try{this.opts.onAuthRevoked?.(Z)}catch(z){this.log.warn(`[WorkerTunnel] onAuthRevoked threw: ${z?.message??z}`)}if(this.currentPollInterval!==G0)this.log.warn(`[WorkerTunnel] ${this.consecutiveAuthFailures} consecutive auth failures — `+`backing off to ${G0}s. Re-authenticate with \`shogo login\`.`);this.currentPollInterval=G0}else this.currentPollInterval=Z0}this.scheduleNextPoll()}async handleRequest($){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)return;this.resetWsIdleTimer();let X=new AbortController;this.activeAbortControllers.set($.requestId,X);try{let Y=await this.resolveLocalUrl($.path,$.projectId);if(!Y){let J=this.opts.resolver.describeRejection?this.opts.resolver.describeRejection($.path,$.projectId):{code:"NO_LOCAL_RUNTIME",message:`no local runtime available for path: ${$.path}`};this.sendFrame({type:"response",requestId:$.requestId,status:502,headers:{"content-type":"application/json"},body:JSON.stringify({code:J.code,message:J.message,path:$.path})});return}let Z={...$.headers??{}};if($.projectId&&($.path.startsWith("/agent/")||$.path==="/agent")){let J=this.opts.resolver.deriveRuntimeToken($.projectId);if(J)Z["x-runtime-token"]=J}let z={method:$.method,headers:Z,signal:X.signal};if($.body&&$.method!=="GET"&&$.method!=="HEAD")z.body=$.body;let K=await fetch(Y,z);if($.stream){let J=K.body?.getReader();if(!J){this.sendFrame({type:"stream-error",requestId:$.requestId,error:"No response body for stream"});return}let V=new TextDecoder;try{while(!0){let{done:w,value:q}=await J.read();if(w)break;if(this.ws?.readyState!==WebSocket.OPEN)break;this.sendFrame({type:"stream-chunk",requestId:$.requestId,data:V.decode(q,{stream:!0})})}if(this.ws?.readyState===WebSocket.OPEN)this.sendFrame({type:"stream-end",requestId:$.requestId})}catch(w){if(w?.name!=="AbortError"&&this.ws?.readyState===WebSocket.OPEN)this.sendFrame({type:"stream-error",requestId:$.requestId,error:w?.message??String(w)})}}else{let J=await K.text(),V={};K.headers.forEach((w,q)=>{V[q]=w}),this.sendFrame({type:"response",requestId:$.requestId,status:K.status,headers:V,body:J})}}catch(Y){if(Y?.name==="AbortError")return;if(this.ws?.readyState!==WebSocket.OPEN)return;let Z=$.stream?{type:"stream-error",requestId:$.requestId,error:Y?.message??String(Y)}:{type:"response",requestId:$.requestId,status:502,body:JSON.stringify({error:Y?.message??String(Y)})};this.sendFrame(Z)}finally{this.activeAbortControllers.delete($.requestId)}}async resolveLocalUrl($,X){return this.opts.resolver.resolveLocalUrl($,X)}sendFrame($){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)return;try{this.ws.send(JSON.stringify($))}catch(X){this.log.warn(`[WorkerTunnel] Frame send failed: ${X?.message??X}`)}}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{}}},W6)}startWsHeartbeat(){if(this.heartbeatTimer)clearInterval(this.heartbeatTimer);this.heartbeatTimer=setInterval(async()=>{if(!this.ws||this.ws.readyState!==WebSocket.OPEN)return;try{let $=await this.collectMetadata();this.sendFrame({type:"heartbeat",metadata:$})}catch{}},k6)}connectWs(){if(this.stopped||this.ws)return;let $=this.buildWsUrl(),X=h0(),Y=j0(),Z=I0(),z=this.opts.name??X;this.log.log(`[WorkerTunnel] Opening WebSocket to ${$} (hostname=${X})`);let K={headers:{Authorization:`Bearer ${this.opts.apiKey}`,"User-Agent":d(),"x-shogo-hostname":X,"x-shogo-name":z,"x-shogo-os":Y,"x-shogo-arch":Z,"x-shogo-kind":this.opts.kind??"cli-worker"}},J;try{J=this.createTunnelWebSocket($,K)}catch(V){this.log.error(`[WorkerTunnel] WebSocket creation failed: ${V?.message??V}`),this.ws=null,this.scheduleNextPoll(5);return}this.ws=J,J.onopen=()=>{this.log.log("[WorkerTunnel] WebSocket connected — session active"),this.wsReconnectAttempt=0,this.startWsHeartbeat(),this.resetWsIdleTimer()},J.onmessage=(V)=>{let w;try{let q=typeof V.data==="string"?V.data:V.data.toString();w=JSON.parse(q)}catch{return}if(w.type==="ping"){this.sendFrame({type:"pong"}),this.resetWsIdleTimer();return}if(w.type==="cancel"){let q=this.activeAbortControllers.get(w.requestId);if(q)q.abort();return}if(w.type==="request"){this.handleRequest(w).catch((q)=>{this.log.error(`[WorkerTunnel] Error handling request: ${q?.message??q}`)});return}},J.onclose=(V)=>{if(this.log.log(`[WorkerTunnel] WebSocket closed: code=${V.code} reason=${V.reason||"none"}`),this.cleanupWs(),this.stopped)return;if(V.code===1000||V.code===4000)this.scheduleNextPoll(this.currentPollInterval);else{this.wsReconnectAttempt++;let w=this.getReconnectDelayMs();this.log.log(`[WorkerTunnel] Reconnecting in ${Math.round(w/1000)}s (attempt ${this.wsReconnectAttempt})`),this.scheduleNextPoll(Math.ceil(w/1000))}},J.onerror=(V)=>{this.log.error("[WorkerTunnel] WebSocket error:",V?.message??"unknown")}}cleanupWs(){if(this.heartbeatTimer)clearInterval(this.heartbeatTimer),this.heartbeatTimer=null;if(this.wsIdleTimer)clearTimeout(this.wsIdleTimer),this.wsIdleTimer=null;for(let[,$]of this.activeAbortControllers)try{$.abort()}catch{}this.activeAbortControllers.clear(),this.ws=null}_testing(){let $=this;return{sendHeartbeat:()=>$.sendHeartbeat(),heartbeatLoop:()=>$.heartbeatLoop(),connectWs:()=>$.connectWs(),cleanupWs:()=>$.cleanupWs(),handleRequest:(X)=>$.handleRequest(X),installFakeWs:(X)=>{$.ws=X},getCloudUrl:()=>$.getCloudUrl(),getWsBaseUrl:()=>$.getWsBaseUrl(),buildWsUrl:()=>$.buildWsUrl(),getReconnectDelayMs:()=>$.getReconnectDelayMs(),supportsWebSocketConstructorHeaders:(X)=>$.supportsWebSocketConstructorHeaders(X),createTunnelWebSocket:(X,Y,Z)=>$.createTunnelWebSocket(X,Y,Z),DEFAULT_POLL_INTERVAL_S:Z0,BACKOFF_BASE_MS:B2,BACKOFF_MAX_MS:A2,TUNNEL_PROTOCOL_VERSION:U2,get currentPollInterval(){return $.currentPollInterval},set currentPollInterval(X){$.currentPollInterval=X},get wsReconnectAttempt(){return $.wsReconnectAttempt},set wsReconnectAttempt(X){$.wsReconnectAttempt=X},get ws(){return $.ws},get stopped(){return $.stopped},get serverPublishedWsUrl(){return $.serverPublishedWsUrl},set serverPublishedWsUrl(X){$.serverPublishedWsUrl=X}}}}async function F2($){let X=P({name:$.name,workerDir:$.workerDir,apiKey:$.apiKey,cloudUrl:$.cloudUrl,port:$.port?parseInt($.port,10):void 0,projectsDir:$.projectsDir}),Y=t0($.proxy);if(!$.foreground)return G6({cfg:X,proxy:Y,flags:$});let Z=u({flag:$.runtimeBin});if(!Z)console.error(O.red(n({flag:$.runtimeBin}))),process.exit(1);if(y0(process.env,Y),$.debug){if(!await Z2(Y2({cloudUrl:X.cloudUrl,apiKey:X.apiKey,workerDir:X.workerDir,proxy:Y})))process.exit(1)}let z=!$.noAutoPull,K=!$.noGit;if(console.log(O.bold(`
14
+ Shogo Worker — Starting`)),console.log(O.dim(" name ")+X.name),console.log(O.dim(" worker-dir ")+X.workerDir),console.log(O.dim(" cloud ")+X.cloudUrl),console.log(O.dim(" runtime ")+`${Z.path} ${O.dim(`(via ${Z.source})`)}`),console.log(O.dim(" auto-pull ")+(z?O.green("on")+O.dim(` → ${X.projectsDir}`):O.yellow("off"))),console.log(O.dim(" sync mode ")+(K?O.green("git")+O.dim(" (falls back to file transport if git is missing)"):O.yellow("files-only"))),$.project)console.log(O.dim(" project ")+$.project);if(Y)console.log(O.dim(" proxy ")+`${Y.url} ${O.dim(`(from ${Y.source})`)}`);console.log("");let J=`${X.cloudUrl.replace(/\/+$/,"")}/api/ai/v1`,V={cloudUrl:X.cloudUrl,apiKey:X.apiKey,aiProxyUrl:J,aiProxyToken:X.apiKey},w=new m0({runtimeBin:$.runtimeBin,defaultSpawnConfig:V,autoPull:{enabled:z,projectsDir:X.projectsDir,watch:!0,useGit:K}});if(!w.resolveBinary())console.error(O.red(n({flag:$.runtimeBin}))),process.exit(1);let q=new f0({apiKey:X.apiKey,cloudUrl:X.cloudUrl,name:X.name,kind:"cli-worker",resolver:w,onAuthRevoked:(W)=>{console.error(O.red(`✗ Cloud auth revoked: ${W}`)),console.error(O.dim(" Run `shogo login` to re-authenticate; this worker will keep polling at the auth-failure backoff until then."))}}),Q=!1,H=async(W)=>{if(Q)return;Q=!0,console.log(O.dim(`
15
+ Received ${W} — shutting down...`));try{q.stop()}catch{}try{await w.stopAll()}catch(G){console.warn(O.yellow(`stopAll: ${G?.message??G}`))}process.exit(0)};process.once("SIGINT",()=>void H("SIGINT")),process.once("SIGTERM",()=>void H("SIGTERM")),process.once("SIGHUP",()=>void H("SIGHUP")),q.start(),console.log(O.green("✓ Worker running. Ctrl-C to stop.")),await new Promise(()=>{})}function G6({cfg:$,proxy:X,flags:Y}){let{entry:Z,runner:z}=L6(),K=y0({...process.env,SHOGO_API_KEY:$.apiKey,SHOGO_CLOUD_URL:$.cloudUrl,SHOGO_INSTANCE_NAME:$.name,SHOGO_WORKER_DIR:$.workerDir,SHOGO_LOCAL_MODE:"true",PORT:String($.port)},X),J=C6(Y),{pid:V}=r0({entry:Z,runner:z,env:{...K,SHOGO_DETACHED_ARGS:J.join(" ")},cwd:$.workerDir,detach:!0,inheritStdio:!1});console.log(O.bold(`
16
+ Shogo Worker — Started`)),console.log(O.dim(" pid: ")+V),console.log(O.dim(" name: ")+$.name),console.log(O.dim(" logs: ")+"~/.shogo/logs/worker.log"),console.log(O.dim(" stop: ")+"shogo worker stop")}function C6($){let X=["worker","start","--foreground"];if($.name)X.push("--name",$.name);if($.workerDir)X.push("--worker-dir",$.workerDir);if($.apiKey)X.push("--api-key",$.apiKey);if($.cloudUrl)X.push("--cloud-url",$.cloudUrl);if($.port)X.push("--port",$.port);if($.proxy)X.push("--proxy",$.proxy);if($.project)X.push("--project",$.project);if($.runtimeBin)X.push("--runtime-bin",$.runtimeBin);if($.debug)X.push("--debug");if($.noAutoPull)X.push("--no-auto-pull");if($.projectsDir)X.push("--projects-dir",$.projectsDir);if($.noGit)X.push("--no-git");return X}function L6(){let $=process.execPath,X=/\bbun(?:-[^/\\]*)?$/.test($)||typeof globalThis.Bun<"u",Y=process.argv[1];if(Y&&O6(Y)){let z=/\.ts$/.test(Y);return{entry:Y,runner:X?"bun":z?"tsx":"node"}}return{entry:new URL("../../bin/shogo.mjs",import.meta.url).pathname,runner:X?"bun":"node"}}import _2 from"picocolors";async function y2(){let{killedPid:$}=e0("SIGTERM");if($===null){console.log(_2.dim("No worker running."));return}console.log(_2.green(`✓ Worker stopped (pid=${$}).`))}import v from"picocolors";async function M2(){let $=Q0();if(!$){console.log(v.yellow("● stopped")+v.dim(" (no pid file)"));return}if(!H0($)){console.log(v.red("● dead")+v.dim(` (stale pid ${$})`));return}let X=I();if(console.log(v.green("● running")+v.dim(` (pid ${$})`)),X.name)console.log(v.dim(" name: ")+X.name);if(X.cloudUrl)console.log(v.dim(" cloud: ")+X.cloudUrl);if(X.workerDir)console.log(v.dim(" dir: ")+X.workerDir)}import{spawn as B6}from"node:child_process";import{existsSync as A6,statSync as U6}from"node:fs";import T2 from"picocolors";async function S2($){let X=$.err?q0:V0;if(!A6(X)){console.log(T2.dim("No logs yet."));return}let Y=$.follow?["-F","-n","200",X]:["-n","200",X];if(B6("tail",Y,{stdio:"inherit"}).on("exit",(z)=>process.exit(z??0)),!$.follow){if(U6(X).size===0)console.log(T2.dim("(log file is empty)"))}}import M from"picocolors";import{spawn as N6}from"node:child_process";import{hostname as F6,platform as _6,arch as y6}from"node:os";import f from"picocolors";class R extends Error{kind;constructor($,X){super($);this.kind=X;this.name="CloudLoginError"}}async function R2($){let X=$.cloudUrl.replace(/\/$/,""),Y=$.client??"cli",Z=$.deviceName??F6(),z=$.devicePlatform??`${_6()}-${y6()}`,K=$.appVersion??S6(),J=$.log??((k)=>console.log(k)),V=$.fetchImpl??fetch,w=await V(`${X}/api/cli/login/start`,{method:"POST",headers:{"content-type":"application/json","user-agent":d()},body:JSON.stringify({deviceId:$.deviceId,deviceName:Z,devicePlatform:z,deviceAppVersion:K,workspaceId:$.workspaceId,client:Y}),signal:AbortSignal.timeout(1e4)}).catch((k)=>{throw new R(`Cannot reach Shogo Cloud at ${X}: ${k?.message??k}`,"transport")});if(!w.ok){let k=await w.text().catch(()=>"");throw new R(`Cloud rejected /api/cli/login/start (HTTP ${w.status}): ${k||"no body"}`,"transport")}let q=await w.json().catch(()=>({}));if(!q?.ok||!q.state||!q.authUrl)throw new R(`Cloud returned a malformed start response: ${q?.error??JSON.stringify(q)}`,"transport");let Q=M6($.pollIntervalMs??q.pollIntervalMs),H=$.timeoutMs??q.expiresInMs,W=Date.now()+H;if(J(""),J(f.bold("Sign in to Shogo Cloud")),J(f.dim(" cloud: ")+X),J(f.dim(" device: ")+`${Z} (${z})`),J(f.dim(" user code: ")+f.cyan(q.userCode)),J(""),J(" Open this URL in your browser to approve:"),J(" "+f.cyan(q.authUrl)),J(""),$.openBrowser!==!1)if(typeof $.openBrowser==="function")try{await $.openBrowser(q.authUrl)}catch{}else R6(q.authUrl).catch(()=>{});J(f.dim("Waiting for approval..."));let G=$.installSignalHandlers??!0,L=()=>{A.abort()},A=new AbortController;if($.abortSignal)if($.abortSignal.aborted)A.abort();else $.abortSignal.addEventListener("abort",()=>A.abort(),{once:!0});if(G)process.once("SIGINT",L),process.once("SIGTERM",L);try{while(!0){if(A.signal.aborted)throw new R("Aborted by caller.","cancelled");if(Date.now()>=W)throw new R(`Timed out after ${Math.round(H/1000)}s waiting for approval.`,"timeout");let k=await V(`${X}/api/cli/login/poll?state=${encodeURIComponent(q.state)}`,{method:"GET",headers:{accept:"application/json","user-agent":d()},signal:AbortSignal.timeout(1e4)}).catch((_)=>{return J(f.dim(` (poll error: ${_?.message??_} — retrying)`)),null});if(k&&k.ok){let _=await k.json().catch(()=>({}));if(_?.status==="approved"&&_.key)return{key:_.key,email:_.email??null,workspace:_.workspace??null,deviceId:_.deviceId??$.deviceId};if(_?.status==="denied")throw new R("Sign-in was denied in the browser.","denied");if(_?.status==="expired")throw new R("Sign-in request expired before it was approved.","expired")}await T6(Q,A.signal)}}finally{if(G)process.removeListener("SIGINT",L),process.removeListener("SIGTERM",L)}}function M6($){if(!Number.isFinite($)||$<=0)return 2000;return Math.min(Math.max($,1000),1e4)}function T6($,X){return new Promise((Y,Z)=>{if(X?.aborted)return Z(new R("Aborted by caller.","cancelled"));let z=setTimeout(()=>{X?.removeEventListener("abort",K),Y()},$),K=()=>{clearTimeout(z),Z(new R("Aborted by caller.","cancelled"))};X?.addEventListener("abort",K,{once:!0})})}function S6(){return d()}function R6($){return new Promise((X)=>{let Y=process.platform==="darwin"?"open":process.platform==="win32"?"cmd":"xdg-open",Z=process.platform==="win32"?["/c","start",'""',$]:[$];try{let z=N6(Y,Z,{stdio:"ignore",detached:!0});z.on("error",()=>X()),z.unref(),X()}catch{X()}})}import{readFileSync as D6,writeFileSync as x6,existsSync as P6,chmodSync as b6}from"node:fs";import{randomUUID as E6}from"node:crypto";function D2(){if(P6(c)){let X=D6(c,"utf-8").trim();if(X)return X}j();let $=E6();return x6(c,$,{mode:384}),b6(c,384),$}var v6="https://studio.shogo.ai";async function x2($){let X=I(),Y=($.cloudUrl||X.cloudUrl||v6).replace(/\/$/,""),Z=$.apiKey||process.env.SHOGO_API_KEY;if(Z){await m6({key:Z,cloudUrl:Y,cfg:X,name:$.name});return}let z=D2(),K;try{K=await R2({cloudUrl:Y,deviceId:z,deviceName:$.name,workspaceId:$.workspace,openBrowser:!$.noBrowser})}catch(J){if(J instanceof R){if(console.error(M.red(`✗ ${J.message}`)),J.kind==="transport")console.error(M.dim(" If your network blocks browsers, run with --api-key <key> instead."));process.exit(1)}throw J}if(X.apiKey=K.key,X.cloudUrl=Y,$.name)X.name=$.name;if(r(X),console.log(""),console.log(M.green("✓ Signed in to Shogo Cloud")),K.workspace)console.log(M.dim(" workspace: ")+K.workspace);if(K.email)console.log(M.dim(" email: ")+K.email);console.log(M.dim(" saved to: ")+"~/.shogo/config.json"),console.log(""),console.log(M.dim(" next: ")+"shogo worker start")}async function m6({key:$,cloudUrl:X,cfg:Y,name:Z}){if(!/^shogo_sk_/.test($))console.error(M.red('✗ API key should start with "shogo_sk_". Copy it verbatim from the API Keys page.')),process.exit(1);let z;try{let K=await fetch(`${X}/api/api-keys/validate`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({key:$}),signal:AbortSignal.timeout(1e4)});if(z=await K.json().catch(()=>({})),!K.ok||!z?.valid)console.error(M.red(`✗ ${z?.error||`Cloud rejected the key (HTTP ${K.status}).`}`)),process.exit(1)}catch(K){console.error(M.red(`✗ Cannot reach Shogo Cloud at ${X}: ${K?.message??K}`)),process.exit(1)}if(Y.apiKey=$,Y.cloudUrl=X,Z)Y.name=Z;if(r(Y),console.log(M.green("✓ API key saved to ~/.shogo/config.json")),z.workspace?.name)console.log(M.dim(" workspace: ")+z.workspace.name);if(z.user?.email)console.log(M.dim(" email: ")+z.user.email);console.log(M.dim(" next: ")+"shogo worker start")}import z0 from"picocolors";async function P2(){let $=I();if(Object.keys($).length===0){console.log(z0.dim("(no config — run `shogo login` or `shogo config set`)"));return}let X={...$,apiKey:$.apiKey?`***${$.apiKey.slice(-4)}`:void 0};console.log(z0.dim(`file: ${h}`)),console.log(JSON.stringify(X,null,2))}async function b2($,X){let Y=["apiKey","cloudUrl","name","workerDir","port"];if(!Y.includes($))console.error(z0.red(`Unknown key: ${$}`)),console.error(z0.dim(`Allowed: ${Y.join(", ")}`)),process.exit(1);let Z=I();Z[$]=$==="port"?parseInt(X,10):X,r(Z),console.log(z0.green(`✓ ${$} set`))}import U from"picocolors";import{spawn as h6}from"node:child_process";import{createHash as j6}from"node:crypto";import{createWriteStream as I6,existsSync as K0,mkdirSync as n0,readFileSync as m2,renameSync as E2,rmSync as d0,writeFileSync as u6}from"node:fs";import{tmpdir as f6}from"node:os";import{dirname as n6,join as C0}from"node:path";import{Readable as d6}from"node:stream";import{pipeline as i6}from"node:stream/promises";var g6="https://github.com/shogo-labs/shogo-ai/releases/download";function h2(){let{platform:$,arch:X}=process,Y;if($==="darwin")Y="darwin";else if($==="linux")Y="linux";else if($==="win32")Y="windows";else throw Error(`Unsupported platform: ${$}`);let Z;if(X==="arm64")Z="arm64";else if(X==="x64")Z="x64";else throw Error(`Unsupported arch: ${X} (need arm64 or x64)`);return`${Y}-${Z}`}function L0(){if(!K0(l))return null;try{let $=m2(l,"utf-8");return JSON.parse($)}catch{return null}}async function p6($,X){let Y=X.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+)\/releases\/download/);if(!Y)throw Error(`Cannot auto-resolve latest version from non-GitHub baseUrl '${X}'. Pass --version explicitly.`);let Z=Y[1],z=Y[2];if($==="stable"){let Q=`https://api.github.com/repos/${Z}/${z}/releases/latest`,H=await fetch(Q,{headers:{Accept:"application/vnd.github+json"}});if(!H.ok)throw Error(`GitHub API ${H.status} for ${Q}`);let W=await H.json();if(!W.tag_name)throw Error("GitHub API did not return tag_name");return v2(W.tag_name)}let K=`https://api.github.com/repos/${Z}/${z}/releases?per_page=30`,J=await fetch(K,{headers:{Accept:"application/vnd.github+json"}});if(!J.ok)throw Error(`GitHub API ${J.status} for ${K}`);let V=await J.json(),w=$==="beta"?"-beta":"-nightly",q=V.find((Q)=>Q.prerelease&&/^v\d+\.\d+\.\d+-/.test(Q.tag_name)&&Q.tag_name.includes(w));if(!q)throw Error(`No ${$} runtime release found`);return v2(q.tag_name)}function v2($){if(!/^v\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/.test($))throw Error(`Unexpected app tag '${$}' (expected vX.Y.Z[-prerelease])`);return $.slice(1)}function c6($,X,Y){let Z=`shogo-agent-runtime-${X}.tar.gz`,z=`v${$}`,K=`${Y.replace(/\/$/,"")}/${z}/${Z}`;return{tarball:K,sha256:`${K}.sha256`,assetName:Z}}async function o6($,X){let Y=await fetch($,{redirect:"follow"});if(!Y.ok)throw Error(`Download failed: HTTP ${Y.status} for ${$}`);if(!Y.body)throw Error(`Download failed: empty body for ${$}`);await i6(d6.fromWeb(Y.body),I6(X))}async function a6($){let X=await fetch($,{redirect:"follow"});if(!X.ok)throw Error(`SHA256 sidecar fetch failed: HTTP ${X.status} for ${$}`);let Z=(await X.text()).trim().split(/\s+/)[0];if(!/^[0-9a-f]{64}$/i.test(Z))throw Error(`SHA256 sidecar at ${$} did not contain a 64-char hex digest`);return Z.toLowerCase()}function l6($){let X=m2($);return j6("sha256").update(X).digest("hex")}async function r6($,X){await new Promise((Y,Z)=>{let z=h6("tar",["-xzf",$,"-C",X],{stdio:["ignore","pipe","pipe"]}),K="";z.stderr.on("data",(J)=>{K+=J.toString()}),z.on("error",Z),z.on("exit",(J)=>{if(J===0)Y();else Z(Error(`tar exited ${J}: ${K.trim()}`))})})}function e6($,X){c0();let Y=n6(X);if(!K0(Y))n0(Y,{recursive:!0,mode:448});let Z=`${X}.next`;if(K0(Z))d0(Z,{force:!0});E2($,Z);try{if(process.platform!=="win32"){let{chmodSync:z}=Q$("node:fs");z(Z,493)}}catch{}if(K0(X))d0(X,{force:!0});E2(Z,X)}async function j2($={}){let X=$.logger??console,Y=$.channel??"stable",Z=$.baseUrl??process.env.SHOGO_RUNTIME_RELEASES_URL??g6,z=$.target??h2(),K=$.version;if(!K)X.log(`[runtime install] Resolving latest ${Y} version...`),K=await p6(Y,Z),X.log(`[runtime install] Latest ${Y} = ${K}`);let J=L0();if(J&&J.version===K&&J.target===z&&!$.force)return X.log(`[runtime install] ${K} (${z}) already installed at ${x} — pass --force to reinstall`),{version:K,target:z,binPath:x,source:J.source,sha256:J.sha256,channel:Y};let V=c6(K,z,Z);X.log(`[runtime install] Downloading ${V.tarball}`);let w=C0(f6(),`shogo-runtime-install-${process.pid}-${Date.now()}`);n0(w,{recursive:!0});try{let q=C0(w,V.assetName);await o6(V.tarball,q),X.log("[runtime install] Verifying SHA-256...");let Q=await a6(V.sha256),H=l6(q);if(H!==Q)throw Error(`SHA-256 mismatch for ${V.assetName}
17
+ expected: ${Q}
18
+ actual: ${H}`);let W=C0(w,"extract");n0(W,{recursive:!0}),await r6(q,W);let G=C0(W,"agent-runtime");if(!K0(G))throw Error(`Tarball ${V.assetName} did not contain ./agent-runtime`);e6(G,x);let L={version:K,target:z,installedAt:new Date().toISOString(),channel:Y,source:V.tarball,sha256:H};return u6(l,JSON.stringify(L,null,2)+`
19
+ `,{mode:384}),X.log(`[runtime install] Installed agent-runtime ${K} (${z}) to ${x}`),{version:K,target:z,binPath:x,source:V.tarball,sha256:H,channel:Y}}finally{try{d0(w,{recursive:!0,force:!0})}catch{}}}function I2(){return{runtimeDir:a,runtimeBin:x,versionFile:l}}async function i0($={}){let X=await j2({channel:$.channel,version:$.version,baseUrl:$.baseUrl,force:$.force});console.log(),console.log(U.green("✓"),`agent-runtime ${U.bold(X.version)} installed (${X.target})`),console.log(` ${U.dim("path: ")} ${X.binPath}`),console.log(` ${U.dim("source: ")} ${X.source}`),console.log(` ${U.dim("sha256: ")} ${X.sha256}`),console.log(` ${U.dim("channel: ")} ${X.channel}`)}function u2(){let $=L0();if(!$){console.log(U.yellow("No agent-runtime installed.")),console.log(`Run ${U.cyan("shogo runtime install")} to download the latest.`),process.exitCode=1;return}console.log(`${U.bold("agent-runtime")} ${$.version}`),console.log(` ${U.dim("target: ")} ${$.target}`),console.log(` ${U.dim("channel: ")} ${$.channel}`),console.log(` ${U.dim("installed at:")} ${$.installedAt}`),console.log(` ${U.dim("source: ")} ${$.source}`)}function f2(){let $=u(),X=I2();if(!$){console.log(U.yellow("agent-runtime binary not found on this machine.")),console.log(),console.log(U.dim("Default install path:"),X.runtimeBin),console.log(),console.log(n()),process.exitCode=1;return}if(console.log($.path),process.env.SHOGO_DEBUG||process.env.VERBOSE)console.log(U.dim(` (resolved via: ${$.source})`))}async function n2($={}){let X=L0(),Y=$.channel??X?.channel??"stable";if(X)console.log(`${U.dim("current:")} ${X.version} (${X.target}, ${X.channel})`);else console.log(U.dim("No existing install — installing fresh..."));await i0({channel:Y,baseUrl:$.baseUrl,force:!0})}import{existsSync as s6,mkdirSync as t6}from"node:fs";import{resolve as $1}from"node:path";import C from"picocolors";import{CloudFileTransport as X1}from"@shogo-ai/sdk/cloud-file-transport";async function d2($,X){if(!$)throw Error("projectId is required");let Y=P({apiKey:X.apiKey,cloudUrl:X.cloudUrl});o0(Y.projectsDir);let Z=$1(X.into??i($,Y.projectsDir));if(!s6(Z))t6(Z,{recursive:!0});let z=X.include?.split(",").map((V)=>V.trim()).filter(Boolean);if(console.log(C.bold(`
20
+ shogo project pull ${C.cyan($)}`)),console.log(C.dim(" cloud ")+Y.cloudUrl),console.log(C.dim(" into ")+Z),z?.length)console.log(C.dim(" include ")+z.join(", "));console.log("");let K=new X1({apiUrl:Y.cloudUrl,apiKey:Y.apiKey,projectId:$,localDir:Z,include:z,onProgress:Y1()}),J=await K.downloadAll();if(Z1("Pull",J),X.watch){console.log(C.bold(`
21
+ Watching for local changes...`));let V=new X0({rootDir:Z,transport:K,onFlush:({uploaded:Q,errors:H})=>{let W=Q.length===1?Q[0]:`${Q.length} files`,G=H>0?C.red(` (${H} errors)`):"";console.log(C.dim(` ↑ ${W}${G}`))}});V.start();let w=!1,q=async(Q)=>{if(w)return;w=!0,console.log(C.dim(`
22
+ Received ${Q} — flushing pending uploads...`));try{await V.stop()}catch(H){console.warn(C.yellow(`watcher.stop: ${H?.message??H}`))}process.exit(0)};process.once("SIGINT",()=>void q("SIGINT")),process.once("SIGTERM",()=>void q("SIGTERM")),process.once("SIGHUP",()=>void q("SIGHUP")),await new Promise(()=>{})}}function Y1(){return($)=>{let X=$.kind==="download"?"↓":$.kind==="upload"?"↑":$.kind==="delete"?"✗":"·",Y=$.total>0?` ${$.index+1}/${$.total}`:"",Z=$.bytes!=null?C.dim(` (${z1($.bytes)})`):"";console.log(C.dim(` ${X}${Y} ${$.path}${Z}`))}}function Z1($,X){let Y=X.errors.length===0,Z=Y?C.green(`✓ ${$} complete`):C.red(`✗ ${$} completed with errors`);if(console.log(`
23
+ ${Z}`),console.log(C.dim(" downloaded: ")+X.downloaded),X.uploaded)console.log(C.dim(" uploaded: ")+X.uploaded);if(X.deleted)console.log(C.dim(" deleted: ")+X.deleted);if(X.skipped)console.log(C.dim(" skipped: ")+X.skipped);if(!Y){console.log(C.dim(" errors: ")+X.errors.length);for(let z of X.errors.slice(0,5))console.log(C.dim(" ")+C.red(`${z.path}: ${z.message}`));if(X.errors.length>5)console.log(C.dim(` ... and ${X.errors.length-5} more`))}}function z1($){if($<1024)return`${$}B`;if($<1048576)return`${($/1024).toFixed(1)}KB`;return`${($/1024/1024).toFixed(2)}MB`}import{existsSync as K1}from"node:fs";import{resolve as J1}from"node:path";import N from"picocolors";import{CloudFileTransport as V1}from"@shogo-ai/sdk/cloud-file-transport";async function i2($,X){if(!$)throw Error("projectId is required");let Y=P({apiKey:X.apiKey,cloudUrl:X.cloudUrl}),Z=J1(X.from??i($,Y.projectsDir));if(!K1(Z))throw Error(`Source directory does not exist: ${Z}`);let z=X.include?.split(",").map((V)=>V.trim()).filter(Boolean);if(console.log(N.bold(`
24
+ shogo project push ${N.cyan($)}`)),console.log(N.dim(" cloud ")+Y.cloudUrl),console.log(N.dim(" from ")+Z),z?.length)console.log(N.dim(" include ")+z.join(", "));if(X.deleteRemote)console.log(N.yellow(" --delete-remote: remote files not present locally will be DELETED"));console.log("");let J=await new V1({apiUrl:Y.cloudUrl,apiKey:Y.apiKey,projectId:$,localDir:Z,include:z,onProgress:(V)=>{let w=V.kind==="upload"?"↑":V.kind==="delete"?"✗":"·",q=V.total>0?` ${V.index+1}/${V.total}`:"",Q=V.bytes!=null?N.dim(` (${w1(V.bytes)})`):"";console.log(N.dim(` ${w}${q} ${V.path}${Q}`))}}).uploadAll({deleteRemote:X.deleteRemote});q1("Push",J)}function q1($,X){let Y=X.errors.length===0,Z=Y?N.green(`✓ ${$} complete`):N.red(`✗ ${$} completed with errors`);if(console.log(`
25
+ ${Z}`),console.log(N.dim(" uploaded: ")+X.uploaded),X.deleted)console.log(N.dim(" deleted: ")+X.deleted);if(!Y){console.log(N.dim(" errors: ")+X.errors.length);for(let z of X.errors.slice(0,5))console.log(N.dim(" ")+N.red(`${z.path}: ${z.message}`));if(X.errors.length>5)console.log(N.dim(` ... and ${X.errors.length-5} more`))}}function w1($){if($<1024)return`${$}B`;if($<1048576)return`${($/1024).toFixed(1)}KB`;return`${($/1024/1024).toFixed(2)}MB`}import{existsSync as Q1}from"node:fs";import{resolve as H1}from"node:path";import b from"picocolors";k0();async function g2($,X){if(!$)throw Error("projectId is required");let Y=P({apiKey:X.apiKey,cloudUrl:X.cloudUrl}),Z=H1(X.into??i($,Y.projectsDir));if(!Q1(Z))throw Error(`Local project dir does not exist: ${Z} — run \`shogo project pull\` first`);if(!$0(Z))throw Error(`${Z} 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(b.bold(`
26
+ shogo project checkout ${b.cyan($)}`)),console.log(b.dim(" cloud ")+Y.cloudUrl),console.log(b.dim(" local ")+Z),X.at)console.log(b.dim(" at ")+X.at);if(console.log(""),X.unshallow)console.log(b.dim("Unshallowing repo (this may take a moment)...")),await W0({apiUrl:Y.cloudUrl,apiKey:Y.apiKey,projectId:$,localDir:Z});if(!X.at){let K=await t({apiUrl:Y.cloudUrl,apiKey:Y.apiKey,projectId:$,localDir:Z});console.log(b.green(`✓ Reset to ${K.commitSha.slice(0,8)} (remote HEAD)`));return}let z;try{z=(await y(["rev-parse","--verify",`${X.at}^{commit}`],{cwd:Z})).stdout.trim()}catch{z=await W1(Y.cloudUrl,Y.apiKey,$,X.at),console.log(b.dim(` resolved checkpoint "${X.at}" → ${z.slice(0,8)}`))}try{await t({apiUrl:Y.cloudUrl,apiKey:Y.apiKey,projectId:$,localDir:Z,branch:z})}catch{if(!X.unshallow)console.log(b.yellow(" fetch failed; unshallowing and retrying...")),await W0({apiUrl:Y.cloudUrl,apiKey:Y.apiKey,projectId:$,localDir:Z}),await t({apiUrl:Y.cloudUrl,apiKey:Y.apiKey,projectId:$,localDir:Z,branch:z});else throw Error(`Cannot reach commit ${z} in local clone`)}await y(["reset","--hard",z],{cwd:Z}),console.log(b.green(`✓ Checked out ${z.slice(0,8)}`))}async function W1($,X,Y,Z){let z=`${$.replace(/\/+$/,"")}/api/projects/${encodeURIComponent(Y)}/checkpoints?limit=100`,K=await fetch(z,{headers:{Authorization:`Bearer ${X}`}});if(!K.ok)throw Error(`Failed to list checkpoints: HTTP ${K.status}`);let J=await K.json(),V=Z.toLowerCase(),w=J.checkpoints.find((q)=>{if(q.name&&q.name.toLowerCase()===V)return!0;if(q.commitSha.startsWith(Z))return!0;if(q.commitMessage&&q.commitMessage.toLowerCase().includes(V))return!0;return!1});if(!w)throw Error(`No checkpoint matches "${Z}" (searched ${J.checkpoints.length} most recent)`);return w.commitSha}import{createInterface as L1}from"node:readline";import B from"picocolors";import{execFileSync as c2}from"node:child_process";import{existsSync as g,copyFileSync as p2}from"node:fs";import D from"node:path";import{homedir as o2,platform as a2}from"node:os";function l2($,X,Y){let z=c2($,["-e",Y],{env:{...process.env,DBP:X},encoding:"utf-8",stdio:["ignore","pipe","pipe"],timeout:5000}).trim();if(!z)throw Error(`bun script returned empty output for ${X}`);return JSON.parse(z)}function B0($,X){if(!g(X))return[];return l2($,X,`
27
27
  import { Database } from 'bun:sqlite';
28
28
  try {
29
29
  const db = new Database(process.env.DBP, { readonly: true });
@@ -37,7 +37,7 @@ shogo project checkout ${E.cyan($)}`)),console.log(E.dim(" cloud ")+Y.cloudUrl
37
37
  console.error(String(e?.stack || e));
38
38
  process.exit(2);
39
39
  }
40
- `)}function W1($){if(!i($))throw Error(`Database does not exist at ${$} — refusing to back up nothing`);let X=S.dirname($),Y=S.basename($),Z=new Date().toISOString().replace(/[:.]/g,"-"),z=S.join(X,`${Y}.bak-${Z}`);g2($,z);for(let J of["-wal","-shm"]){let K=`${$}${J}`;if(i(K))g2(K,`${z}${J}`)}return z}function k1($,X,Y){if(Y.length===0)return 0;if(!i(X))throw Error(`Cannot repair: database does not exist at ${X}`);let z=`
40
+ `)}function k1($){if(!g($))throw Error(`Database does not exist at ${$} — refusing to back up nothing`);let X=D.dirname($),Y=D.basename($),Z=new Date().toISOString().replace(/[:.]/g,"-"),z=D.join(X,`${Y}.bak-${Z}`);p2($,z);for(let K of["-wal","-shm"]){let J=`${$}${K}`;if(g(J))p2(J,`${z}${K}`)}return z}function O1($,X,Y){if(Y.length===0)return 0;if(!g(X))throw Error(`Cannot repair: database does not exist at ${X}`);let z=`
41
41
  import { Database } from 'bun:sqlite';
42
42
  const names = ${JSON.stringify(Y)};
43
43
  if (!Array.isArray(names) || names.some(n => typeof n !== 'string')) {
@@ -56,8 +56,8 @@ shogo project checkout ${E.cyan($)}`)),console.log(E.dim(" cloud ")+Y.cloudUrl
56
56
  console.error(String(e?.stack || e));
57
57
  process.exit(3);
58
58
  }
59
- `;return a2($,X,z).deleted}function l2($){let X=$.log??(()=>{}),{bunPath:Y,dbPath:Z}=$;if(!i(Z))return{status:"no-database",detected:[],cleared:[],remaining:[],message:`No database found at ${Z}. Nothing to repair — launch Shogo once to create it.`};let z=O0(Y,Z);if(z.length===0)return{status:"healthy",detected:[],cleared:[],remaining:[],message:"No failed migrations detected — the local database looks healthy."};X(`Found ${z.length} failed migration(s): ${z.map((w)=>w.name).join(", ")}`);let J;if(!$.skipBackup)J=W1(Z),X(`Backed up database to ${J}`);let K=z.map((w)=>w.name),q=k1(Y,Z,K);X(`Cleared ${q} failed migration row(s).`);let Q=O0(Y,Z),V=Q.length===0?"repaired":"failed",H=V==="repaired"?"Cleared the failed migration record. Relaunch Shogo to re-apply migrations cleanly.":`Repair incomplete — ${Q.length} migration(s) still failed: ${Q.map((w)=>w.name).join(", ")}.`;return{status:V,detected:z,backupPath:J,cleared:K.slice(0,q),remaining:Q,message:H}}function G1(){let $=c2(),X=o2(),Y;if(X==="darwin")Y=S.join($,"Library","Application Support");else if(X==="win32")Y=process.env.APPDATA??S.join($,"AppData","Roaming");else Y=process.env.XDG_CONFIG_HOME??S.join($,".config");return S.join(Y,"Shogo","data")}function r2(){return S.join(G1(),"shogo.db")}function C1(){let $=o2(),X=$==="win32"?"bun.exe":"bun",Y=c2(),Z=[];if($==="darwin")Z.push(S.join("/Applications","Shogo.app","Contents","Resources","bun",X),S.join(Y,"Applications","Shogo.app","Contents","Resources","bun",X));else if($==="linux")Z.push(S.join("/opt","Shogo","resources","bun",X),S.join("/usr","lib","shogo","resources","bun",X));return Z}function i0($){try{return p2($,["--version"],{stdio:"ignore",timeout:5000}),!0}catch{return!1}}function e2($){if($)return i0($)?$:null;if(process.versions.bun&&process.execPath)return process.execPath;for(let X of C1())if(i(X)&&i0(X))return X;if(i0("bun"))return"bun";return null}function O1($){for(let X of $){let Y=Number.isFinite(X.startedAt)?new Date(X.startedAt).toISOString():"unknown time",Z=(X.errorExcerpt??"").split(`
60
- `)[0]?.trim();if(console.log(` ${B.yellow("•")} ${B.bold(X.name)} ${B.dim(`(attempted ${Y})`)}`),Z)console.log(` ${B.dim(Z)}`)}}async function B1($){if(!process.stdin.isTTY)return console.log(B.dim("(stdin is not a TTY — re-run with --yes to repair non-interactively)")),!1;let X=L1({input:process.stdin,output:process.stdout});try{let Y=await new Promise((Z)=>X.question($,Z));return/^y(es)?$/i.test(Y.trim())}finally{X.close()}}async function s2($={}){let X=$.db??r2(),Y=e2($.bun);if(!Y)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
61
- `+" (e.g. /Applications/Shogo.app/Contents/Resources/bun/bun on macOS).");console.log(B.dim(`Database: ${X}`)),console.log(B.dim(`Bun: ${Y}`)),console.log();let Z=O0(Y,X);if(Z.length===0){console.log(B.green("✓ No failed migrations detected — your local database looks healthy."));return}if(console.log(B.red(`✗ Found ${Z.length} failed migration(s):`)),O1(Z),console.log(),$.check){console.log(B.dim("Run `shogo doctor` (without --check) to back up the database and clear these records.")),process.exitCode=1;return}if(!$.yes){let J=$.backup===!1?B.red("This will NOT create a backup (--no-backup)."):"Your database will be backed up to a .bak-<timestamp> file first.";if(console.log(J),!await B1(B.bold("Clear these failed migration records and repair? [y/N] "))){console.log(B.dim("Aborted — no changes made.")),process.exitCode=1;return}console.log()}let z=l2({bunPath:Y,dbPath:X,skipBackup:$.backup===!1,log:(J)=>console.log(B.dim(` ${J}`))});if(console.log(),z.status==="repaired"){if(console.log(B.green(`✓ ${z.message}`)),z.backupPath)console.log(B.dim(` Backup: ${z.backupPath}`));console.log(B.bold(`
62
- → Relaunch the Shogo app to finish applying migrations.`))}else{if(console.log(B.red(`✗ ${z.message}`)),z.backupPath)console.log(B.dim(` Backup: ${z.backupPath}`));process.exitCode=1}}import{spawn as A1}from"node:child_process";import{resolve as U1}from"node:path";import t2 from"picocolors";function F1($){let{flags:X,config:Y,runtime:Z}=$,z=X.cwd?U1(X.cwd):process.cwd(),J=["interactive"];if(X.print!==void 0)J.push("-p",X.print);if(X.noTui)J.push("--no-tui");let K=Y.cloudUrl.replace(/\/$/,""),q={...$.baseEnv??process.env,SHOGO_INTERACTIVE:"1",SHOGO_INTERACTIVE_CWD:z,PROJECT_DIR:z,WORKSPACE_DIR:z,SHOGO_API_URL:K,SHOGO_CLOUD_URL:K,SHOGO_API_KEY:Y.apiKey,AI_PROXY_URL:`${K}/api/ai/v1`,AI_PROXY_TOKEN:Y.apiKey,NODE_ENV:"production"};if(X.model)q.SHOGO_MODEL=X.model;if(X.print!==void 0)q.SHOGO_PRINT_PROMPT=X.print;return{bin:Z.path,args:J,env:q,cwd:z}}async function $$($={}){let X=_({apiKey:$.apiKey,cloudUrl:$.cloudUrl}),Y=u({flag:$.runtimeBin});if(!Y)console.error(t2.red(d({flag:$.runtimeBin}))),process.exit(1);let Z=F1({flags:$,config:{apiKey:X.apiKey,cloudUrl:X.cloudUrl},runtime:Y}),z=await new Promise((J)=>{let K=A1(Z.bin,Z.args,{cwd:Z.cwd,env:Z.env,stdio:"inherit"});K.on("error",(q)=>{console.error(t2.red(`✗ Failed to launch agent-runtime: ${q.message}`)),J(1)}),K.on("exit",(q)=>J(q??0))});process.exit(z)}var M1="0.1.0",b=new N1;b.name("shogo").description("Shogo Cloud Agent Worker — run Shogo agents on your own machine.").version(M1);b.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(($)=>N(()=>S2($)));var B0=b.command("worker").description("Manage the local worker process");B0.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(($)=>N(()=>F2($)));B0.command("stop").description("Stop the running worker").action(()=>N(M2));B0.command("status").description("Show worker status").action(()=>N(T2));B0.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(($)=>N(()=>D2($)));var A0=b.command("runtime").description("Install / inspect the local agent-runtime binary");A0.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(($)=>N(()=>n0($)));A0.command("version").description("Print the installed agent-runtime version").action(()=>N(()=>I2()));A0.command("where").description("Print the resolved agent-runtime binary path").action(()=>N(()=>u2()));A0.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(($)=>N(()=>f2($)));var Y$=b.command("config").description("Inspect or modify ~/.shogo/config.json");Y$.command("show").description("Print current config (API key masked)").action(()=>N(R2));Y$.command("set <key> <value>").description("Set apiKey / cloudUrl / name / workerDir / port / projectsDir").action(($,X)=>N(()=>_2($,X)));var g0=b.command("project").description("Clone/sync project workspaces between Shogo Cloud and this machine");g0.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(($,X)=>N(()=>d2($,X)));g0.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(($,X)=>N(()=>n2($,X)));g0.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(($,X)=>N(()=>i2($,X)));b.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(($)=>N(()=>s2($)));b.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(($,X)=>N(()=>$$({print:X.print??($.length?$.join(" "):void 0),model:X.model,cwd:X.cwd,runtimeBin:X.runtimeBin,noTui:X.tui===!1,apiKey:X.apiKey,cloudUrl:X.cloudUrl})));b.showHelpAfterError(X$.dim(`
63
- (use --help for usage)`));b.parseAsync(process.argv);async function N($){try{await $()}catch(X){if(console.error(X$.red(`✗ ${X?.message??X}`)),process.env.SHOGO_DEBUG)console.error(X?.stack);process.exit(1)}}
59
+ `;return l2($,X,z).deleted}function r2($){let X=$.log??(()=>{}),{bunPath:Y,dbPath:Z}=$;if(!g(Z))return{status:"no-database",detected:[],cleared:[],remaining:[],message:`No database found at ${Z}. Nothing to repair — launch Shogo once to create it.`};let z=B0(Y,Z);if(z.length===0)return{status:"healthy",detected:[],cleared:[],remaining:[],message:"No failed migrations detected — the local database looks healthy."};X(`Found ${z.length} failed migration(s): ${z.map((H)=>H.name).join(", ")}`);let K;if(!$.skipBackup)K=k1(Z),X(`Backed up database to ${K}`);let J=z.map((H)=>H.name),V=O1(Y,Z,J);X(`Cleared ${V} failed migration row(s).`);let w=B0(Y,Z),q=w.length===0?"repaired":"failed",Q=q==="repaired"?"Cleared the failed migration record. Relaunch Shogo to re-apply migrations cleanly.":`Repair incomplete — ${w.length} migration(s) still failed: ${w.map((H)=>H.name).join(", ")}.`;return{status:q,detected:z,backupPath:K,cleared:J.slice(0,V),remaining:w,message:Q}}function G1(){let $=o2(),X=a2(),Y;if(X==="darwin")Y=D.join($,"Library","Application Support");else if(X==="win32")Y=process.env.APPDATA??D.join($,"AppData","Roaming");else Y=process.env.XDG_CONFIG_HOME??D.join($,".config");return D.join(Y,"Shogo","data")}function e2(){return D.join(G1(),"shogo.db")}function C1(){let $=a2(),X=$==="win32"?"bun.exe":"bun",Y=o2(),Z=[];if($==="darwin")Z.push(D.join("/Applications","Shogo.app","Contents","Resources","bun",X),D.join(Y,"Applications","Shogo.app","Contents","Resources","bun",X));else if($==="linux")Z.push(D.join("/opt","Shogo","resources","bun",X),D.join("/usr","lib","shogo","resources","bun",X));return Z}function g0($){try{return c2($,["--version"],{stdio:"ignore",timeout:5000}),!0}catch{return!1}}function s2($){if($)return g0($)?$:null;if(process.versions.bun&&process.execPath)return process.execPath;for(let X of C1())if(g(X)&&g0(X))return X;if(g0("bun"))return"bun";return null}function B1($){for(let X of $){let Y=Number.isFinite(X.startedAt)?new Date(X.startedAt).toISOString():"unknown time",Z=(X.errorExcerpt??"").split(`
60
+ `)[0]?.trim();if(console.log(` ${B.yellow("•")} ${B.bold(X.name)} ${B.dim(`(attempted ${Y})`)}`),Z)console.log(` ${B.dim(Z)}`)}}async function A1($){if(!process.stdin.isTTY)return console.log(B.dim("(stdin is not a TTY — re-run with --yes to repair non-interactively)")),!1;let X=L1({input:process.stdin,output:process.stdout});try{let Y=await new Promise((Z)=>X.question($,Z));return/^y(es)?$/i.test(Y.trim())}finally{X.close()}}async function t2($={}){let X=$.db??e2(),Y=s2($.bun);if(!Y)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
61
+ `+" (e.g. /Applications/Shogo.app/Contents/Resources/bun/bun on macOS).");console.log(B.dim(`Database: ${X}`)),console.log(B.dim(`Bun: ${Y}`)),console.log();let Z=B0(Y,X);if(Z.length===0){console.log(B.green("✓ No failed migrations detected — your local database looks healthy."));return}if(console.log(B.red(`✗ Found ${Z.length} failed migration(s):`)),B1(Z),console.log(),$.check){console.log(B.dim("Run `shogo doctor` (without --check) to back up the database and clear these records.")),process.exitCode=1;return}if(!$.yes){let K=$.backup===!1?B.red("This will NOT create a backup (--no-backup)."):"Your database will be backed up to a .bak-<timestamp> file first.";if(console.log(K),!await A1(B.bold("Clear these failed migration records and repair? [y/N] "))){console.log(B.dim("Aborted — no changes made.")),process.exitCode=1;return}console.log()}let z=r2({bunPath:Y,dbPath:X,skipBackup:$.backup===!1,log:(K)=>console.log(B.dim(` ${K}`))});if(console.log(),z.status==="repaired"){if(console.log(B.green(`✓ ${z.message}`)),z.backupPath)console.log(B.dim(` Backup: ${z.backupPath}`));console.log(B.bold(`
62
+ → Relaunch the Shogo app to finish applying migrations.`))}else{if(console.log(B.red(`✗ ${z.message}`)),z.backupPath)console.log(B.dim(` Backup: ${z.backupPath}`));process.exitCode=1}}import{spawn as U1}from"node:child_process";import{resolve as N1}from"node:path";import $$ from"picocolors";function F1($){let{flags:X,config:Y,runtime:Z}=$,z=X.cwd?N1(X.cwd):process.cwd(),K=["interactive"];if(X.print!==void 0)K.push("-p",X.print);if(X.noTui)K.push("--no-tui");let J=Y.cloudUrl.replace(/\/$/,""),V={...$.baseEnv??process.env,SHOGO_INTERACTIVE:"1",SHOGO_INTERACTIVE_CWD:z,PROJECT_DIR:z,WORKSPACE_DIR:z,SHOGO_API_URL:J,SHOGO_CLOUD_URL:J,SHOGO_API_KEY:Y.apiKey,AI_PROXY_URL:`${J}/api/ai/v1`,AI_PROXY_TOKEN:Y.apiKey,NODE_ENV:"production"};if(X.model)V.SHOGO_MODEL=X.model;if(X.print!==void 0)V.SHOGO_PRINT_PROMPT=X.print;return{bin:Z.path,args:K,env:V,cwd:z}}async function X$($={}){let X=P({apiKey:$.apiKey,cloudUrl:$.cloudUrl}),Y=u({flag:$.runtimeBin});if(!Y)console.error($$.red(n({flag:$.runtimeBin}))),process.exit(1);let Z=F1({flags:$,config:{apiKey:X.apiKey,cloudUrl:X.cloudUrl},runtime:Y}),z=await new Promise((K)=>{let J=U1(Z.bin,Z.args,{cwd:Z.cwd,env:Z.env,stdio:"inherit"});J.on("error",(V)=>{console.error($$.red(`✗ Failed to launch agent-runtime: ${V.message}`)),K(1)}),J.on("exit",(V)=>K(V??0))});process.exit(z)}var y1="0.1.0",E=new _1;E.name("shogo").description("Shogo Cloud Agent Worker — run Shogo agents on your own machine.").version(y1);E.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(($)=>F(()=>x2($)));var A0=E.command("worker").description("Manage the local worker process");A0.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(($)=>F(()=>F2($)));A0.command("stop").description("Stop the running worker").action(()=>F(y2));A0.command("status").description("Show worker status").action(()=>F(M2));A0.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(($)=>F(()=>S2($)));var U0=E.command("runtime").description("Install / inspect the local agent-runtime binary");U0.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(($)=>F(()=>i0($)));U0.command("version").description("Print the installed agent-runtime version").action(()=>F(()=>u2()));U0.command("where").description("Print the resolved agent-runtime binary path").action(()=>F(()=>f2()));U0.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(($)=>F(()=>n2($)));var Z$=E.command("config").description("Inspect or modify ~/.shogo/config.json");Z$.command("show").description("Print current config (API key masked)").action(()=>F(P2));Z$.command("set <key> <value>").description("Set apiKey / cloudUrl / name / workerDir / port / projectsDir").action(($,X)=>F(()=>b2($,X)));var p0=E.command("project").description("Clone/sync project workspaces between Shogo Cloud and this machine");p0.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(($,X)=>F(()=>d2($,X)));p0.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(($,X)=>F(()=>i2($,X)));p0.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(($,X)=>F(()=>g2($,X)));E.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(($)=>F(()=>t2($)));E.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(($,X)=>F(()=>X$({print:X.print??($.length?$.join(" "):void 0),model:X.model,cwd:X.cwd,runtimeBin:X.runtimeBin,noTui:X.tui===!1,apiKey:X.apiKey,cloudUrl:X.cloudUrl})));E.showHelpAfterError(Y$.dim(`
63
+ (use --help for usage)`));E.parseAsync(process.argv);async function F($){try{await $()}catch(X){if(console.error(Y$.red(`✗ ${X?.message??X}`)),process.env.SHOGO_DEBUG)console.error(X?.stack);process.exit(1)}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shogo-ai/worker",
3
- "version": "1.13.0",
3
+ "version": "1.13.1",
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.",
@@ -37,6 +37,7 @@
37
37
  import { spawn } from 'node:child_process';
38
38
  import { hostname, platform as osPlatform, arch } from 'node:os';
39
39
  import pc from 'picocolors';
40
+ import { workerUserAgent } from './user-agent.ts';
40
41
 
41
42
  export interface CloudLoginResult {
42
43
  /** The minted shogo_sk_ key. */
@@ -135,7 +136,7 @@ export async function runCloudLogin(opts: CloudLoginOptions): Promise<CloudLogin
135
136
  // 1. Start
136
137
  const startRes = await fetchImpl(`${cloudUrl}/api/cli/login/start`, {
137
138
  method: 'POST',
138
- headers: { 'content-type': 'application/json' },
139
+ headers: { 'content-type': 'application/json', 'user-agent': workerUserAgent() },
139
140
  body: JSON.stringify({
140
141
  deviceId: opts.deviceId,
141
142
  deviceName,
@@ -233,7 +234,7 @@ export async function runCloudLogin(opts: CloudLoginOptions): Promise<CloudLogin
233
234
  `${cloudUrl}/api/cli/login/poll?state=${encodeURIComponent(start.state)}`,
234
235
  {
235
236
  method: 'GET',
236
- headers: { accept: 'application/json' },
237
+ headers: { accept: 'application/json', 'user-agent': workerUserAgent() },
237
238
  signal: AbortSignal.timeout(10_000),
238
239
  },
239
240
  ).catch((err) => {
@@ -292,32 +293,12 @@ function sleep(ms: number, signal?: AbortSignal): Promise<void> {
292
293
  });
293
294
  }
294
295
 
295
- // Build-time injected worker version.
296
- //
297
- // Bundlers replace this identifier via `--define '__SHOGO_WORKER_VERSION__="x.y.z"'`
298
- // (see `apps/desktop/scripts/bundle-main.mjs`). When this module is consumed
299
- // as an unbundled npm library the identifier is undeclared at runtime, and the
300
- // `typeof` guard returns `'undefined'` without throwing.
301
- //
302
- // Previously this used `new URL('../../package.json', import.meta.url)` to
303
- // read the worker's own `package.json` at runtime. That works in unbundled
304
- // ESM but fails catastrophically once the worker is bundled into the
305
- // desktop's `dist/main.js`: Bun / esbuild inline `import.meta.url` as the
306
- // source file's `file:///...` URL at build time, which leaks the build
307
- // host's filesystem layout into shipped artifacts and points at a path that
308
- // doesn't exist on end-user machines. The same problem affects
309
- // `require.resolve('@shogo-ai/worker/package.json')` (statically analyzed
310
- // and inlined to a build-time absolute path). Bundle consumers must pass
311
- // the version in via `--define`; standalone library consumers fall back
312
- // to an `unknown` tag, which is purely diagnostic (this function feeds a
313
- // telemetry User-Agent string, nothing load-bearing).
314
- declare const __SHOGO_WORKER_VERSION__: string | undefined;
315
-
296
+ // The build-time injected worker version (`shogo-cli/<version>`) is resolved
297
+ // by the shared `workerUserAgent()` helper — see `./user-agent.ts` for the
298
+ // bundler `--define` mechanism and the standalone-library fallback. It is used
299
+ // here as the `deviceAppVersion` reported to the cloud during device login.
316
300
  function readWorkerVersion(): string {
317
- if (typeof __SHOGO_WORKER_VERSION__ === 'string' && __SHOGO_WORKER_VERSION__.length > 0) {
318
- return `shogo-cli/${__SHOGO_WORKER_VERSION__}`;
319
- }
320
- return 'shogo-cli/unknown';
301
+ return workerUserAgent();
321
302
  }
322
303
 
323
304
  function openInBrowser(url: string): Promise<void> {
package/src/lib/tunnel.ts CHANGED
@@ -20,6 +20,7 @@
20
20
  * own state. The desktop AGPL copy keeps its module-global form.
21
21
  */
22
22
  import { hostname as osHostname, platform, arch as osArch } from 'node:os';
23
+ import { workerUserAgent } from './user-agent.ts';
23
24
 
24
25
  /**
25
26
  * Structured reason returned to the cloud (and ultimately to a Studio
@@ -299,6 +300,7 @@ export class WorkerTunnel {
299
300
  method: 'POST',
300
301
  headers: {
301
302
  'Content-Type': 'application/json',
303
+ 'User-Agent': workerUserAgent(),
302
304
  'x-api-key': this.opts.apiKey,
303
305
  'x-shogo-kind': this.opts.kind ?? 'cli-worker',
304
306
  },
@@ -573,6 +575,7 @@ export class WorkerTunnel {
573
575
  const wsInit: TunnelWebSocketInit = {
574
576
  headers: {
575
577
  Authorization: `Bearer ${this.opts.apiKey}`,
578
+ 'User-Agent': workerUserAgent(),
576
579
  'x-shogo-hostname': hn,
577
580
  'x-shogo-name': name,
578
581
  'x-shogo-os': os,
@@ -0,0 +1,28 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (C) 2026 Shogo Technologies, Inc.
3
+ /**
4
+ * Canonical product token for the Shogo worker CLI, e.g. `shogo-cli/1.2.3`.
5
+ *
6
+ * Sent as the `User-Agent` on every request the worker makes to Shogo Cloud
7
+ * (heartbeat, tunnel WebSocket, device login). Without it, Node/Bun `fetch`
8
+ * emits a generic runtime User-Agent (or none), which is indistinguishable
9
+ * from arbitrary bot traffic at the CDN edge — a problem for headless
10
+ * cloud/datacenter deployments where the source ASN already looks like
11
+ * "Hosting" to bot-management heuristics (see issue #783). A stable,
12
+ * self-identifying UA lets the edge scope bot-mitigation exceptions to
13
+ * genuine worker traffic instead of relying on IP allowlists.
14
+ *
15
+ * `__SHOGO_WORKER_VERSION__` is injected by bundlers via
16
+ * `--define '__SHOGO_WORKER_VERSION__="x.y.z"'`. The `typeof` guard returns
17
+ * `'undefined'` without throwing when the identifier was never defined
18
+ * (standalone library consumers), in which case we fall back to a purely
19
+ * diagnostic `unknown` tag.
20
+ */
21
+ declare const __SHOGO_WORKER_VERSION__: string | undefined;
22
+
23
+ export function workerUserAgent(): string {
24
+ if (typeof __SHOGO_WORKER_VERSION__ === 'string' && __SHOGO_WORKER_VERSION__.length > 0) {
25
+ return `shogo-cli/${__SHOGO_WORKER_VERSION__}`;
26
+ }
27
+ return 'shogo-cli/unknown';
28
+ }