grix-connector 3.3.5 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- import{createServer as u}from"node:http";import{log as c}from"../log/logger.js";class y{server=null;token;handler=null;upgradeHandler=null;probeHandler=null;installHandler=null;proxyHandler=null;constructor(e){this.token=e}setAgentHandler(e){this.handler=e}setUpgradeHandler(e){this.upgradeHandler=e}setProbeHandler(e){this.probeHandler=e}setInstallHandler(e){this.installHandler=e}setProxyHandler(e){this.proxyHandler=e}async start(e){return new Promise((r,t)=>{this.server=u((s,n)=>this.handleRequest(s,n)),this.server.listen(e,"127.0.0.1",()=>{c.info("admin",`Listening on 127.0.0.1:${e}`),r()}),this.server.on("error",t)})}async stop(){if(this.server)return new Promise(e=>{this.server.close(()=>e())})}handleRequest(e,r){const t=e.method??"",s=e.url??"";if(s==="/api/agents"&&t==="GET")this.handleList(r);else if(s==="/api/agents"&&t==="POST")this.readBody(e).then(n=>this.handleAdd(r,n)).catch(n=>this.error(r,n));else if(t==="DELETE"&&s.startsWith("/api/agents/")){const n=decodeURIComponent(s.slice(12));this.handleRemove(r,n)}else if(t==="POST"&&s.match(/^\/api\/agents\/[^/]+\/restart$/)){const n=decodeURIComponent(s.slice(12,s.lastIndexOf("/restart")));this.handleRestart(r,n)}else if(s==="/api/reload"&&t==="POST")this.handleReload(r);else if(s==="/api/upgrade"&&t==="GET")this.handleCheckUpgrade(r);else if(s==="/api/upgrade"&&t==="POST")this.handleTriggerUpgrade(r);else if(t==="GET"&&s.startsWith("/api/probe"))this.handleProbe(e,r,s);else if(s==="/api/install"&&t==="GET")this.handleInstallList(r);else if(s==="/api/install"&&t==="POST")this.readBody(e).then(n=>this.handleInstall(r,n)).catch(n=>this.error(r,n));else if(t==="GET"&&s.startsWith("/api/install/")){const n=decodeURIComponent(s.slice(13));this.handleInstallProgress(r,n)}else if(s==="/api/proxy"&&t==="GET")this.handleProxyStatus(r);else if(t==="PUT"&&s==="/api/proxy/enabled")this.readBody(e).then(n=>this.handleProxySetEnabled(r,n)).catch(n=>this.error(r,n));else if(t==="PUT"&&s==="/api/proxy/default-route")this.readBody(e).then(n=>this.handleProxySetDefaultRoute(r,n)).catch(n=>this.error(r,n));else if(t==="PUT"&&s==="/api/proxy/intercept-hosts")this.readBody(e).then(n=>this.handleProxySetInterceptHosts(r,n)).catch(n=>this.error(r,n));else if(t==="PUT"&&s.match(/^\/api\/proxy\/routes\/[^/]+$/)){const n=decodeURIComponent(s.slice(18));this.readBody(e).then(o=>this.handleProxySetRoute(r,n,o)).catch(o=>this.error(r,o))}else if(t==="DELETE"&&s.startsWith("/api/proxy/routes/")){const n=decodeURIComponent(s.slice(18));this.handleProxyDeleteRoute(r,n)}else this.json(r,404,{error:"not_found"})}ensureProxy(e){return this.proxyHandler?!0:(this.json(e,501,{error:"proxy not configured"}),!1)}handleProxyStatus(e){if(this.ensureProxy(e))try{this.json(e,200,this.proxyHandler.status())}catch(r){this.error(e,r)}}async handleProxySetRoute(e,r,t){if(!this.ensureProxy(e))return;const s=t;if(!s||typeof s.targetBaseUrl!="string"||!s.targetBaseUrl.trim()){this.json(e,400,{error:"targetBaseUrl is required"});return}try{new URL(s.targetBaseUrl)}catch{this.json(e,400,{error:"targetBaseUrl must be a valid URL"});return}if(s.headers!==void 0&&!this.isStringRecord(s.headers)){this.json(e,400,{error:"headers must be a string map"});return}if(s.model!==void 0&&typeof s.model!="string"){this.json(e,400,{error:"model must be a string"});return}if(s.modelMap!==void 0&&!this.isStringRecord(s.modelMap)){this.json(e,400,{error:"modelMap must be a string map"});return}if(s.passthrough!==void 0&&typeof s.passthrough!="boolean"){this.json(e,400,{error:"passthrough must be a boolean"});return}try{const n=await this.proxyHandler.setRoute(r,t);this.json(e,200,n??{ok:!0})}catch(n){this.error(e,n)}}handleProxyDeleteRoute(e,r){this.ensureProxy(e)&&this.proxyHandler.deleteRoute(r).then(()=>{e.writeHead(204),e.end()}).catch(t=>this.error(e,t))}async handleProxySetEnabled(e,r){if(!this.ensureProxy(e))return;const t=r;if(typeof t?.enabled!="boolean"){this.json(e,400,{error:"enabled must be a boolean"});return}try{const s=t.enabled?await this.proxyHandler.enable():await this.proxyHandler.disable();this.json(e,200,s??{ok:!0})}catch(s){this.error(e,s)}}async handleProxySetDefaultRoute(e,r){if(!this.ensureProxy(e))return;const s=r?.routeKey??null;if(s!==null&&typeof s!="string"){this.json(e,400,{error:"routeKey must be a string or null"});return}try{const n=await this.proxyHandler.setDefaultRoute(s);this.json(e,200,n??{ok:!0})}catch(n){this.error(e,n)}}async handleProxySetInterceptHosts(e,r){if(!this.ensureProxy(e))return;const t=r;if(!Array.isArray(t?.hosts)||!t.hosts.every(s=>typeof s=="string")){this.json(e,400,{error:"hosts must be a string array"});return}try{const s=await this.proxyHandler.setInterceptHosts(t.hosts);this.json(e,200,s??{ok:!0})}catch(s){this.error(e,s)}}isStringRecord(e){return typeof e!="object"||e===null||Array.isArray(e)?!1:Object.values(e).every(r=>typeof r=="string")}handleList(e){try{const r=this.handler?.list()??[];this.json(e,200,r)}catch(r){this.error(e,r)}}async handleAdd(e,r){try{const t=await this.handler.add(r);this.json(e,201,t??{ok:!0})}catch(t){this.error(e,t)}}handleRemove(e,r){this.handler.remove(r).then(()=>{e.writeHead(204),e.end()}).catch(t=>this.error(e,t))}handleRestart(e,r){this.handler.restart(r).then(()=>{this.json(e,200,{ok:!0})}).catch(t=>this.error(e,t))}handleReload(e){this.handler.reload().then(r=>{this.json(e,200,{ok:!0,result:r})}).catch(r=>this.error(e,r))}handleCheckUpgrade(e){if(!this.upgradeHandler){this.json(e,501,{error:"upgrade not configured"});return}this.upgradeHandler.check().then(r=>{this.json(e,200,r)}).catch(r=>this.error(e,r))}handleTriggerUpgrade(e){if(!this.upgradeHandler){this.json(e,501,{error:"upgrade not configured"});return}this.upgradeHandler.trigger(),this.json(e,200,{ok:!0,message:"upgrade check triggered"})}error(e,r){const t=r;t.code==="NOT_FOUND"?this.json(e,404,{error:t.message??"not found"}):t.code==="RELOAD_UNSAFE"?this.json(e,409,{error:t.message,code:t.code}):t.code==="UNKNOWN_AGENT"||t.code==="UNSUPPORTED_OS"?this.json(e,400,{error:t.message,code:t.code}):t.code==="ALREADY_INSTALLED"||t.code==="INSTALL_IN_PROGRESS"?this.json(e,409,{error:t.message,code:t.code}):t.code==="INSTALL_FAILED"||t.code==="INSTALL_TIMEOUT"||t.code==="PREFLIGHT_FAILED"||t.code==="VERIFICATION_FAILED"||t.code==="PREREQ_MISSING"||t.code==="PREREQ_INSTALL_FAILED"||t.code==="FALLBACK_EXHAUSTED"||t.code==="ENVIRONMENT_UNSUPPORTED"?this.json(e,500,{error:t.message,code:t.code}):(c.error("admin",`Handler error: ${t.message??r}`),this.json(e,500,{error:t.message??"internal error"}))}json(e,r,t){const s=JSON.stringify(t);e.writeHead(r,{"Content-Type":"application/json"}),e.end(s)}readBody(e){return new Promise((r,t)=>{let s="";e.setEncoding("utf8"),e.on("data",n=>{s+=n}),e.on("end",()=>{try{r(JSON.parse(s))}catch{t(new Error("invalid JSON body"))}}),e.on("error",t)})}handleProbe(e,r,t){if(!this.probeHandler){this.json(r,501,{error:"probe not configured"});return}const s=t.indexOf("?"),n=s>=0?t.slice(0,s):t,o=s>=0?new URLSearchParams(t.slice(s+1)):new URLSearchParams,a={};o.get("conversation")==="true"&&(a.conversation=!0),o.get("fresh")==="true"&&(a.fresh=!0);const l=Number(o.get("timeoutMs"));Number.isFinite(l)&&l>0&&(a.timeoutMs=l);const d=n.match(/^\/api\/probe\/(.+)$/);if(d){const i=decodeURIComponent(d[1]);this.probeHandler.probeOne(i,a).then(h=>{this.json(r,200,h)}).catch(h=>this.error(r,h));return}this.probeHandler.probeAll(a).then(i=>{this.json(r,200,i)}).catch(i=>this.error(r,i))}handleInstallList(e){if(!this.installHandler){this.json(e,501,{error:"install not configured"});return}try{const r=this.installHandler.listInstallable();this.json(e,200,r)}catch(r){this.error(e,r)}}handleInstallProgress(e,r){if(!this.installHandler){this.json(e,501,{error:"install not configured"});return}const t=this.installHandler.getInstallProgress(r);if(!t){this.json(e,200,{agentType:r,status:"unknown",inProgress:!1,progress:null});return}let s,n,o;switch(t.phase){case"completed":s="done",n="\u5B89\u88C5\u5B8C\u6210";break;case"failed":s="error",o=t.outputTail||"\u5B89\u88C5\u5931\u8D25";break;case"preflight":s="pending",n="\u68C0\u67E5\u524D\u7F6E\u4F9D\u8D56...";break;case"installing_prereq":s="downloading",n=t.currentPrereq?`\u6B63\u5728\u5B89\u88C5\u524D\u7F6E\u4F9D\u8D56: ${t.currentPrereq}`:"\u6B63\u5728\u5B89\u88C5\u524D\u7F6E\u4F9D\u8D56...";break;case"installing":s="installing",n=`\u6B63\u5728\u5B89\u88C5 ${r}...`;break;case"verifying":s="installing",n="\u9A8C\u8BC1\u5B89\u88C5...";break;default:s="unknown"}this.json(e,200,{agentType:r,status:s,inProgress:!0,progress:t.elapsedMs?Math.min(.9,t.elapsedMs/3e4):.1,message:n,error:o})}async handleInstall(e,r){if(!this.installHandler){this.json(e,501,{error:"install not configured"});return}try{const t=r;if(!t||typeof t.agentType!="string"||!t.agentType){this.json(e,400,{error:"agentType is required"});return}const s=await this.installHandler.installAgent(t);if(s.ok)this.json(e,200,s);else{const n=s.error?.code;n==="UNKNOWN_AGENT"||n==="UNSUPPORTED_OS"?this.json(e,400,s):n==="INSTALL_IN_PROGRESS"?this.json(e,409,s):this.json(e,500,s)}}catch(t){this.error(e,t)}}}export{y as AdminServer};
1
+ import{createServer as u}from"node:http";import{log as c}from"../log/logger.js";class f{server=null;token;handler=null;upgradeHandler=null;probeHandler=null;installHandler=null;proxyHandler=null;constructor(e){this.token=e}setAgentHandler(e){this.handler=e}setUpgradeHandler(e){this.upgradeHandler=e}setProbeHandler(e){this.probeHandler=e}setInstallHandler(e){this.installHandler=e}setProxyHandler(e){this.proxyHandler=e}async start(e){return new Promise((n,t)=>{this.server=u((r,s)=>this.handleRequest(r,s)),this.server.listen(e,"127.0.0.1",()=>{c.info("admin",`Listening on 127.0.0.1:${e}`),n()}),this.server.on("error",t)})}async stop(){if(this.server)return new Promise(e=>{this.server.close(()=>e())})}handleRequest(e,n){const t=e.method??"",r=e.url??"";if(r==="/api/agents"&&t==="GET")this.handleList(n);else if(r==="/api/agents"&&t==="POST")this.readBody(e).then(s=>this.handleAdd(n,s)).catch(s=>this.error(n,s));else if(t==="DELETE"&&r.startsWith("/api/agents/")){const s=decodeURIComponent(r.slice(12));this.handleRemove(n,s)}else if(t==="POST"&&r.match(/^\/api\/agents\/[^/]+\/restart$/)){const s=decodeURIComponent(r.slice(12,r.lastIndexOf("/restart")));this.handleRestart(n,s)}else if(r==="/api/reload"&&t==="POST")this.handleReload(n);else if(r==="/api/upgrade"&&t==="GET")this.handleCheckUpgrade(n);else if(r==="/api/upgrade"&&t==="POST")this.handleTriggerUpgrade(n);else if(t==="GET"&&r.startsWith("/api/probe"))this.handleProbe(e,n,r);else if(r==="/api/install"&&t==="GET")this.handleInstallList(n);else if(r==="/api/install"&&t==="POST")this.readBody(e).then(s=>this.handleInstall(n,s)).catch(s=>this.error(n,s));else if(t==="GET"&&r.startsWith("/api/install/")){const s=decodeURIComponent(r.slice(13));this.handleInstallProgress(n,s)}else if(r==="/api/proxy"&&t==="GET")this.handleProxyStatus(n);else if(t==="PUT"&&r.match(/^\/api\/proxy\/agents\/[^/]+\/enabled$/)){const s=decodeURIComponent(r.slice(18,-8));this.readBody(e).then(o=>this.handleProxySetAgentRelay(n,s,o)).catch(o=>this.error(n,o))}else if(t==="PUT"&&r==="/api/proxy/enabled")this.readBody(e).then(s=>this.handleProxySetEnabled(n,s)).catch(s=>this.error(n,s));else if(t==="PUT"&&r==="/api/proxy/default-route")this.readBody(e).then(s=>this.handleProxySetDefaultRoute(n,s)).catch(s=>this.error(n,s));else if(t==="PUT"&&r==="/api/proxy/intercept-hosts")this.readBody(e).then(s=>this.handleProxySetInterceptHosts(n,s)).catch(s=>this.error(n,s));else if(t==="PUT"&&r.match(/^\/api\/proxy\/routes\/[^/]+$/)){const s=decodeURIComponent(r.slice(18));this.readBody(e).then(o=>this.handleProxySetRoute(n,s,o)).catch(o=>this.error(n,o))}else if(t==="DELETE"&&r.startsWith("/api/proxy/routes/")){const s=decodeURIComponent(r.slice(18));this.handleProxyDeleteRoute(n,s)}else this.json(n,404,{error:"not_found"})}ensureProxy(e){return this.proxyHandler?!0:(this.json(e,501,{error:"proxy not configured"}),!1)}handleProxyStatus(e){if(this.ensureProxy(e))try{this.json(e,200,this.proxyHandler.status())}catch(n){this.error(e,n)}}async handleProxySetRoute(e,n,t){if(!this.ensureProxy(e))return;const r=t;if(!r||typeof r.targetBaseUrl!="string"||!r.targetBaseUrl.trim()){this.json(e,400,{error:"targetBaseUrl is required"});return}try{new URL(r.targetBaseUrl)}catch{this.json(e,400,{error:"targetBaseUrl must be a valid URL"});return}if(r.headers!==void 0&&!this.isStringRecord(r.headers)){this.json(e,400,{error:"headers must be a string map"});return}if(r.model!==void 0&&typeof r.model!="string"){this.json(e,400,{error:"model must be a string"});return}if(r.modelMap!==void 0&&!this.isStringRecord(r.modelMap)){this.json(e,400,{error:"modelMap must be a string map"});return}if(r.passthrough!==void 0&&typeof r.passthrough!="boolean"){this.json(e,400,{error:"passthrough must be a boolean"});return}if(r.relayOnly!==void 0&&typeof r.relayOnly!="boolean"){this.json(e,400,{error:"relayOnly must be a boolean"});return}if(r.codexResponsesToChat!==void 0&&typeof r.codexResponsesToChat!="boolean"){this.json(e,400,{error:"codexResponsesToChat must be a boolean"});return}if(r.passthrough===!0&&r.codexResponsesToChat===!0){this.json(e,400,{error:"passthrough and codexResponsesToChat are mutually exclusive"});return}try{const s=await this.proxyHandler.setRoute(n,t);this.json(e,200,s??{ok:!0})}catch(s){this.error(e,s)}}handleProxyDeleteRoute(e,n){this.ensureProxy(e)&&this.proxyHandler.deleteRoute(n).then(()=>{e.writeHead(204),e.end()}).catch(t=>this.error(e,t))}async handleProxySetAgentRelay(e,n,t){if(!this.ensureProxy(e))return;const r=n.trim();if(!r){this.json(e,400,{error:"agent name is required"});return}const s=t;if(typeof s?.enabled!="boolean"){this.json(e,400,{error:"enabled must be a boolean"});return}try{const o=await this.proxyHandler.setAgentRelay(r,s.enabled);this.json(e,200,o??{ok:!0})}catch(o){this.error(e,o)}}async handleProxySetEnabled(e,n){if(!this.ensureProxy(e))return;const t=n;if(typeof t?.enabled!="boolean"){this.json(e,400,{error:"enabled must be a boolean"});return}if(t.enabled){this.json(e,400,{error:'relay is per-agent; use PUT /api/proxy/agents/<name>/enabled {"enabled":true}'});return}try{const r=await this.proxyHandler.disableAll();this.json(e,200,r??{ok:!0})}catch(r){this.error(e,r)}}async handleProxySetDefaultRoute(e,n){if(!this.ensureProxy(e))return;const r=n?.routeKey??null;if(r!==null&&typeof r!="string"){this.json(e,400,{error:"routeKey must be a string or null"});return}try{const s=await this.proxyHandler.setDefaultRoute(r);this.json(e,200,s??{ok:!0})}catch(s){this.error(e,s)}}async handleProxySetInterceptHosts(e,n){if(!this.ensureProxy(e))return;const t=n;if(!Array.isArray(t?.hosts)||!t.hosts.every(r=>typeof r=="string")){this.json(e,400,{error:"hosts must be a string array"});return}try{const r=await this.proxyHandler.setInterceptHosts(t.hosts);this.json(e,200,r??{ok:!0})}catch(r){this.error(e,r)}}isStringRecord(e){return typeof e!="object"||e===null||Array.isArray(e)?!1:Object.values(e).every(n=>typeof n=="string")}handleList(e){try{const n=this.handler?.list()??[];this.json(e,200,n)}catch(n){this.error(e,n)}}async handleAdd(e,n){try{const t=await this.handler.add(n);this.json(e,201,t??{ok:!0})}catch(t){this.error(e,t)}}handleRemove(e,n){this.handler.remove(n).then(()=>{e.writeHead(204),e.end()}).catch(t=>this.error(e,t))}handleRestart(e,n){this.handler.restart(n).then(()=>{this.json(e,200,{ok:!0})}).catch(t=>this.error(e,t))}handleReload(e){this.handler.reload().then(n=>{this.json(e,200,{ok:!0,result:n})}).catch(n=>this.error(e,n))}handleCheckUpgrade(e){if(!this.upgradeHandler){this.json(e,501,{error:"upgrade not configured"});return}this.upgradeHandler.check().then(n=>{this.json(e,200,n)}).catch(n=>this.error(e,n))}handleTriggerUpgrade(e){if(!this.upgradeHandler){this.json(e,501,{error:"upgrade not configured"});return}this.upgradeHandler.trigger(),this.json(e,200,{ok:!0,message:"upgrade check triggered"})}error(e,n){const t=n;t.code==="NOT_FOUND"?this.json(e,404,{error:t.message??"not found"}):t.code==="RELOAD_UNSAFE"||t.code==="NO_RELAY_ROUTE"?this.json(e,409,{error:t.message,code:t.code}):t.code==="UNKNOWN_AGENT"||t.code==="UNSUPPORTED_OS"?this.json(e,400,{error:t.message,code:t.code}):t.code==="ALREADY_INSTALLED"||t.code==="INSTALL_IN_PROGRESS"?this.json(e,409,{error:t.message,code:t.code}):t.code==="INSTALL_FAILED"||t.code==="INSTALL_TIMEOUT"||t.code==="PREFLIGHT_FAILED"||t.code==="VERIFICATION_FAILED"||t.code==="PREREQ_MISSING"||t.code==="PREREQ_INSTALL_FAILED"||t.code==="FALLBACK_EXHAUSTED"||t.code==="ENVIRONMENT_UNSUPPORTED"?this.json(e,500,{error:t.message,code:t.code}):(c.error("admin",`Handler error: ${t.message??n}`),this.json(e,500,{error:t.message??"internal error"}))}json(e,n,t){const r=JSON.stringify(t);e.writeHead(n,{"Content-Type":"application/json"}),e.end(r)}readBody(e){return new Promise((n,t)=>{let r="";e.setEncoding("utf8"),e.on("data",s=>{r+=s}),e.on("end",()=>{try{n(JSON.parse(r))}catch{t(new Error("invalid JSON body"))}}),e.on("error",t)})}handleProbe(e,n,t){if(!this.probeHandler){this.json(n,501,{error:"probe not configured"});return}const r=t.indexOf("?"),s=r>=0?t.slice(0,r):t,o=r>=0?new URLSearchParams(t.slice(r+1)):new URLSearchParams,a={};o.get("conversation")==="true"&&(a.conversation=!0),o.get("fresh")==="true"&&(a.fresh=!0);const l=Number(o.get("timeoutMs"));Number.isFinite(l)&&l>0&&(a.timeoutMs=l);const d=s.match(/^\/api\/probe\/(.+)$/);if(d){const i=decodeURIComponent(d[1]);this.probeHandler.probeOne(i,a).then(h=>{this.json(n,200,h)}).catch(h=>this.error(n,h));return}this.probeHandler.probeAll(a).then(i=>{this.json(n,200,i)}).catch(i=>this.error(n,i))}handleInstallList(e){if(!this.installHandler){this.json(e,501,{error:"install not configured"});return}try{const n=this.installHandler.listInstallable();this.json(e,200,n)}catch(n){this.error(e,n)}}handleInstallProgress(e,n){if(!this.installHandler){this.json(e,501,{error:"install not configured"});return}const t=this.installHandler.getInstallProgress(n);if(!t){this.json(e,200,{agentType:n,status:"unknown",inProgress:!1,progress:null});return}let r,s,o;switch(t.phase){case"completed":r="done",s="\u5B89\u88C5\u5B8C\u6210";break;case"failed":r="error",o=t.outputTail||"\u5B89\u88C5\u5931\u8D25";break;case"preflight":r="pending",s="\u68C0\u67E5\u524D\u7F6E\u4F9D\u8D56...";break;case"installing_prereq":r="downloading",s=t.currentPrereq?`\u6B63\u5728\u5B89\u88C5\u524D\u7F6E\u4F9D\u8D56: ${t.currentPrereq}`:"\u6B63\u5728\u5B89\u88C5\u524D\u7F6E\u4F9D\u8D56...";break;case"installing":r="installing",s=`\u6B63\u5728\u5B89\u88C5 ${n}...`;break;case"verifying":r="installing",s="\u9A8C\u8BC1\u5B89\u88C5...";break;default:r="unknown"}this.json(e,200,{agentType:n,status:r,inProgress:!0,progress:t.elapsedMs?Math.min(.9,t.elapsedMs/3e4):.1,message:s,error:o})}async handleInstall(e,n){if(!this.installHandler){this.json(e,501,{error:"install not configured"});return}try{const t=n;if(!t||typeof t.agentType!="string"||!t.agentType){this.json(e,400,{error:"agentType is required"});return}const r=await this.installHandler.installAgent(t);if(r.ok)this.json(e,200,r);else{const s=r.error?.code;s==="UNKNOWN_AGENT"||s==="UNSUPPORTED_OS"?this.json(e,400,r):s==="INSTALL_IN_PROGRESS"?this.json(e,409,r):this.json(e,500,r)}}catch(t){this.error(e,t)}}}export{f as AdminServer};
@@ -1,11 +1,11 @@
1
- import{execFile as N,spawn as D}from"node:child_process";import{existsSync as y}from"node:fs";import{delimiter as k,dirname as O,join as L}from"node:path";import{log as m}from"../log/logger.js";import{resolveCliPath as S,getCliVersion as x,resolveWindowsInstalledCli as V,invalidateWindowsRegistryPathCache as C}from"../util/cli-probe.js";import{getInstallCommand as R,getCliBinary as U,isKnownAgent as F,detectPlatformOS as q,formatInstallCommand as H}from"./registry.js";import{checkPrerequisites as b,getMissingPrerequisites as P}from"./preflight.js";import{installMissingPrerequisites as W}from"./prereq-installer.js";import{detectEnvironment as j,formatEnvironmentInfo as G,isEnvironmentSupported as B}from"./env-detect.js";import{generateManualGuide as M}from"./manual-guide.js";import{npmInstallWithMirror as K,isTransientInstallError as X}from"./npm-registry.js";import{getAllAgentInstallInfo as Y}from"./registry.js";class c extends Error{code;constructor(n,t){super(t),this.name="InstallerError",this.code=n}}const w=64*1024,Q=20,v=2,z=3e3;class ue{os;activeInstalls=new Map;constructor(){this.os=q()}listInstallable(){return{platform:this.os,agents:Y(this.os)}}getProgress(n){return this.activeInstalls.get(n)}isInProgress(n){return this.activeInstalls.has(n)}async install(n){const{agentType:t}=n,e=Date.now();if(this.activeInstalls.has(t))return this.fail(t,"preflight",e,new c("INSTALL_IN_PROGRESS",`${t} is already being installed`));try{return await this._doInstall(n,e)}catch(s){this.activeInstalls.delete(t);const o=s instanceof Error?s.message:String(s);return m.error("installer",`${t} install unexpected error: ${o}`),{agentType:t,ok:!1,phase:"failed",error:{code:"INTERNAL",message:`Unexpected error: ${o}`},durationMs:Date.now()-e,output:""}}}async _doInstall(n,t){const{agentType:e}=n;let s;try{s=await j()}catch(i){const u=i instanceof Error?i.message:String(i);return this.fail(e,"preflight",t,new c("INTERNAL",`Environment detection failed: ${u}`))}m.info("installer",`Install request: ${e}
2
- ${G(s)}`);const o=B(s);if(!o.supported)return this.fail(e,"preflight",t,new c("ENVIRONMENT_UNSUPPORTED",`Current environment is not supported for automatic installation: ${o.reason}. Please install ${e} manually.`),s);if(this.setProgress(e,"preflight",t),!F(e))return this.fail(e,"preflight",t,new c("UNKNOWN_AGENT",`Unknown agent type: ${e}`),s);const l=R(e,this.os);if(!l){const i=this.getManualHint(e,this.os),u=i?`Installation of ${e} is not supported on ${this.os}. ${i}`:`Installation of ${e} is not supported on ${this.os}`;return this.fail(e,"preflight",t,new c("UNSUPPORTED_OS",u),s)}const a=U(e),f=await S(a),p=f?(await x(f)).version:null;if(f&&!n.force&&!n.dryRun){this.activeInstalls.delete(e);const i=Date.now()-t;return m.info("installer",`${e} already installed at ${f}${p?` (v${p})`:""}`),{agentType:e,ok:!0,phase:"completed",installedPath:f,installedVersion:p,durationMs:i,output:"",environment:s}}const h=l.prerequisites??[];let r=[];if(h.length>0){m.info("installer",`Checking prerequisites for ${e}: ${h.join(", ")}`),r=await b(h,this.os),m.info("installer",`Prerequisites: ${r.map(u=>`${u.label}=${u.met?u.version:"missing"}`).join(", ")}`);const i=P(r);if(i.length>0){const u=i.map(d=>`${d.label}${d.minVersion?` >= ${d.minVersion}`:""}`).join(", ");if(!n.dryRun){if(n.skipPrereqInstall)return this.fail(e,"preflight",t,new c("PREREQ_MISSING",`Missing prerequisites: ${u}. Install them first or retry without skipPrereqInstall.`),s,r);const d=i.map($=>`${$.label}${$.minVersion?` >= ${$.minVersion}`:""}`);m.info("installer",`Will auto-install prerequisites: ${d.join(", ")}`),this.setProgress(e,"installing_prereq",t,i[0].label,d);const I=await W(i,this.os);if(!I.allOk){const $=I.results.find(A=>!A.ok),T=$?`Failed to install prerequisite ${$.prereq.label}: ${$.output}`:"Prerequisite installation failed";return this.fail(e,"installing_prereq",t,new c("PREREQ_INSTALL_FAILED",T),s,r)}m.info("installer",`All prerequisites installed for ${e}`),r=await b(h,this.os)}}}if(n.dryRun){this.activeInstalls.delete(e);const i=P(r),u=this.getManualHint(e,this.os),d={agentType:e,environment:s,canInstall:!0,alreadyInstalled:!!f,installedPath:f,installedVersion:p,installCommand:H(l),installMode:l.mode,prerequisites:r,missingPrerequisites:i,fallbackCommand:l.fallback?.command??null,manualHint:u},I=M({agentType:e,os:this.os,env:s,missingPrereqs:i});return{agentType:e,ok:!0,phase:"completed",durationMs:Date.now()-t,output:"dry-run: no commands executed",environment:s,dryRun:d,manualGuide:I}}this.setProgress(e,"installing",t),m.info("installer",`Installing ${e}: ${l.command}`);let g;try{g=await this.executeWithRetry(l,e,t,n.timeoutMs)}catch(i){if(l.fallback&&i instanceof c&&(i.code==="INSTALL_FAILED"||i.code==="INSTALL_TIMEOUT")){m.info("installer",`Primary install failed after retries, trying fallback: ${l.fallback.command}`),this.setProgress(e,"installing",t);try{g=await this.executeWithRetry(l.fallback,e,t,n.timeoutMs),g=`[primary failed, fallback succeeded]
3
- ${g}`}catch(u){const d=i.message,I=u instanceof c?u.message:String(u);return this.fail(e,"installing",t,new c("FALLBACK_EXHAUSTED",`Both primary and fallback install methods failed.
1
+ import{execFile as R,spawn as _}from"node:child_process";import{log as m}from"../log/logger.js";import{resolveCliPath as N,getCliVersion as T,invalidateCliPathCache as x}from"../util/cli-probe.js";import{getInstallCommand as k,getCliBinary as A,isKnownAgent as D,detectPlatformOS as O,formatInstallCommand as U}from"./registry.js";import{checkPrerequisites as y,getMissingPrerequisites as L}from"./preflight.js";import{installMissingPrerequisites as C}from"./prereq-installer.js";import{detectEnvironment as V,formatEnvironmentInfo as F,isEnvironmentSupported as q}from"./env-detect.js";import{generateManualGuide as P}from"./manual-guide.js";import{npmInstallWithMirror as G,isTransientInstallError as B}from"./npm-registry.js";import{getAllAgentInstallInfo as H}from"./registry.js";class c extends Error{code;constructor(n,t){super(t),this.name="InstallerError",this.code=n}}const w=64*1024,j=20,v=2,W=3e3;class ie{os;activeInstalls=new Map;constructor(){this.os=O()}listInstallable(){return{platform:this.os,agents:H(this.os)}}getProgress(n){return this.activeInstalls.get(n)}isInProgress(n){return this.activeInstalls.has(n)}async install(n){const{agentType:t}=n,e=Date.now();if(this.activeInstalls.has(t))return this.fail(t,"preflight",e,new c("INSTALL_IN_PROGRESS",`${t} is already being installed`));try{return await this._doInstall(n,e)}catch(i){this.activeInstalls.delete(t);const r=i instanceof Error?i.message:String(i);return m.error("installer",`${t} install unexpected error: ${r}`),{agentType:t,ok:!1,phase:"failed",error:{code:"INTERNAL",message:`Unexpected error: ${r}`},durationMs:Date.now()-e,output:""}}}async _doInstall(n,t){const{agentType:e}=n;let i;try{i=await V()}catch(s){const a=s instanceof Error?s.message:String(s);return this.fail(e,"preflight",t,new c("INTERNAL",`Environment detection failed: ${a}`))}m.info("installer",`Install request: ${e}
2
+ ${F(i)}`);const r=q(i);if(!r.supported)return this.fail(e,"preflight",t,new c("ENVIRONMENT_UNSUPPORTED",`Current environment is not supported for automatic installation: ${r.reason}. Please install ${e} manually.`),i);if(this.setProgress(e,"preflight",t),!D(e))return this.fail(e,"preflight",t,new c("UNKNOWN_AGENT",`Unknown agent type: ${e}`),i);const l=k(e,this.os);if(!l){const s=this.getManualHint(e,this.os),a=s?`Installation of ${e} is not supported on ${this.os}. ${s}`:`Installation of ${e} is not supported on ${this.os}`;return this.fail(e,"preflight",t,new c("UNSUPPORTED_OS",a),i)}const u=A(e),h=await N(u),p=h?(await T(h)).version:null;if(h&&!n.force&&!n.dryRun){this.activeInstalls.delete(e);const s=Date.now()-t;return m.info("installer",`${e} already installed at ${h}${p?` (v${p})`:""}`),{agentType:e,ok:!0,phase:"completed",installedPath:h,installedVersion:p,durationMs:s,output:"",environment:i}}const f=l.prerequisites??[];let o=[];if(f.length>0){m.info("installer",`Checking prerequisites for ${e}: ${f.join(", ")}`),o=await y(f,this.os),m.info("installer",`Prerequisites: ${o.map(a=>`${a.label}=${a.met?a.version:"missing"}`).join(", ")}`);const s=L(o);if(s.length>0){const a=s.map(d=>`${d.label}${d.minVersion?` >= ${d.minVersion}`:""}`).join(", ");if(!n.dryRun){if(n.skipPrereqInstall)return this.fail(e,"preflight",t,new c("PREREQ_MISSING",`Missing prerequisites: ${a}. Install them first or retry without skipPrereqInstall.`),i,o);const d=s.map($=>`${$.label}${$.minVersion?` >= ${$.minVersion}`:""}`);m.info("installer",`Will auto-install prerequisites: ${d.join(", ")}`),this.setProgress(e,"installing_prereq",t,s[0].label,d);const I=await C(s,this.os);if(!I.allOk){const $=I.results.find(M=>!M.ok),b=$?`Failed to install prerequisite ${$.prereq.label}: ${$.output}`:"Prerequisite installation failed";return this.fail(e,"installing_prereq",t,new c("PREREQ_INSTALL_FAILED",b),i,o)}m.info("installer",`All prerequisites installed for ${e}`),o=await y(f,this.os)}}}if(n.dryRun){this.activeInstalls.delete(e);const s=L(o),a=this.getManualHint(e,this.os),d={agentType:e,environment:i,canInstall:!0,alreadyInstalled:!!h,installedPath:h,installedVersion:p,installCommand:U(l),installMode:l.mode,prerequisites:o,missingPrerequisites:s,fallbackCommand:l.fallback?.command??null,manualHint:a},I=P({agentType:e,os:this.os,env:i,missingPrereqs:s});return{agentType:e,ok:!0,phase:"completed",durationMs:Date.now()-t,output:"dry-run: no commands executed",environment:i,dryRun:d,manualGuide:I}}this.setProgress(e,"installing",t),m.info("installer",`Installing ${e}: ${l.command}`);let g;try{g=await this.executeWithRetry(l,e,t,n.timeoutMs)}catch(s){if(l.fallback&&s instanceof c&&(s.code==="INSTALL_FAILED"||s.code==="INSTALL_TIMEOUT")){m.info("installer",`Primary install (${l.command}) failed: ${s.message}`),m.info("installer",`Trying fallback: ${l.fallback.command}`),this.setProgress(e,"installing",t);try{g=await this.executeWithRetry(l.fallback,e,t,n.timeoutMs),g=`[primary failed, fallback succeeded]
3
+ ${g}`}catch(a){const d=s.message,I=a instanceof c?a.message:String(a);return this.fail(e,"installing",t,new c("FALLBACK_EXHAUSTED",`Both primary and fallback install methods failed.
4
4
  Primary: ${d}
5
- Fallback: ${I}`),s,r)}}else return i instanceof c?this.fail(e,"installing",t,new c(i.code,i.message),s,r):this.fail(e,"installing",t,new c("INTERNAL",i instanceof Error?i.message:String(i)),s,r)}{const i=(g??"").trim().split(`
5
+ Fallback: ${I}`),i,o)}}else return s instanceof c?this.fail(e,"installing",t,new c(s.code,s.message),i,o):this.fail(e,"installing",t,new c("INTERNAL",s instanceof Error?s.message:String(s)),i,o)}{const s=(g??"").trim().split(`
6
6
  `).slice(-12).join(`
7
7
  `);m.info("installer",`${e} \u5B89\u88C5\u547D\u4EE4\u8F93\u51FA(\u5C3E\u90E8):
8
- ${i||"<\u7A7A>"}`)}if(C(),!n.skipVerify&&!l.skipVerification){this.setProgress(e,"verifying",t),m.info("installer",`Verifying ${e} installation...`);let i=await S(a);if(i||(i=await this.resolveViaNpmBin(a)),!i&&(i=await this.resolveViaRegistryPath(a),i)){const I=O(i);(process.env.PATH??"").split(k).some(T=>T.toLowerCase()===I.toLowerCase())||(process.env.PATH=`${I}${k}${process.env.PATH??""}`,m.info("installer",`Injected ${I} into process PATH (picked up from registry)`))}if(!i)return this.fail(e,"verifying",t,new c("VERIFICATION_FAILED",`${a} not found on PATH after installation. You may need to open a new terminal or run: source ~/.zshrc (or ~/.bashrc)`),s,r);const{version:u}=await x(i),d=Date.now()-t;return this.activeInstalls.delete(e),m.info("installer",`${e} installed successfully at ${i} (v${u??"unknown"}, ${d}ms)`),{agentType:e,ok:!0,phase:"completed",installedPath:i,installedVersion:u,durationMs:d,output:g,prerequisites:r.length>0?r:void 0,environment:s}}const E=Date.now()-t;return this.activeInstalls.delete(e),m.info("installer",`${e} install command completed (${E}ms, verification skipped)`),{agentType:e,ok:!0,phase:"completed",installedPath:null,installedVersion:null,durationMs:E,output:g,prerequisites:r.length>0?r:void 0,environment:s}}getManualHint(n,t){const s=R(n,t)?.fallback,l={claude:"https://docs.anthropic.com/en/docs/claude-code/overview",codex:"https://github.com/openai/codex",gemini:"https://github.com/google-gemini/gemini-cli",qwen:"https://github.com/QwenLM/qwen-code",cursor:"https://cursor.com/docs/cli/installation",copilot:"https://docs.github.com/en/copilot/managing-copilot/configure-personal-settings/installing-github-copilot-in-the-cli",kiro:"https://kiro.dev/docs/cli/",openclaw:"https://github.com/openclaw/openclaw",reasonix:"https://github.com/esengine/DeepSeek-Reasonix",openhuman:"https://github.com/tinyhumansai/openhuman/issues/128"}[n],a=[];return l&&a.push(`Docs: ${l}`),s&&a.push(`Alternative: ${s.command}`),a.length>0?a.join(" | "):null}setProgress(n,t,e,s,o){this.activeInstalls.set(n,{agentType:n,phase:t,startedAt:e,elapsedMs:Date.now()-e,...s?{currentPrereq:s}:{},...o?{pendingPrereqs:o}:{}})}fail(n,t,e,s,o,l,a){const f=a??this.activeInstalls.get(n)?.outputTail??"";this.activeInstalls.delete(n),m.error("installer",`${n} install failed at ${t}: ${s.message}`);const p=l?P(l):[],h=M({agentType:n,os:this.os,env:o??{platform:this.os,osVersion:"unknown",arch:process.arch,shell:process.env.SHELL??null,nodeVersion:null,npmVersion:null,isDocker:!1,isCI:!1},missingPrereqs:p,primaryFailed:t==="installing",fallbackFailed:s.code==="FALLBACK_EXHAUSTED",error:s.message});return{agentType:n,ok:!1,phase:"failed",error:{code:s.code,message:s.message},durationMs:Date.now()-e,output:f,environment:o,prerequisites:l,manualGuide:h}}async executeWithRetry(n,t,e,s){let o=null;for(let l=0;l<=v;l++)try{return l>0&&(m.info("installer",`Retry ${l}/${v} for ${t}...`),await this.sleep(z)),await this.executeCommand(n,t,e,s)}catch(a){if(o=a instanceof c?a:new c("INTERNAL",String(a)),!(o.code==="INSTALL_TIMEOUT"||o.code==="INSTALL_FAILED"&&X(o.message))||l>=v)throw o;m.info("installer",`Attempt ${l+1} failed (retryable): ${o.message}`)}throw o??new c("INTERNAL","Unexpected retry loop exit")}sleep(n){return new Promise(t=>setTimeout(t,n))}executeCommand(n,t,e,s){const o=s??n.timeoutMs;switch(n.mode){case"npm":return this.executeNpm(n.npmPackage,o,t,e);case"shell":return this.executeShell(n.command,o,t,e);case"exec":return this.executeExec(n.command,n.execArgs??[],o,t,e);default:return Promise.reject(new c("INTERNAL",`Unknown install mode: ${n.mode}`))}}async executeNpm(n,t,e,s){try{const{output:o,registry:l}=await K(n,t,w);return m.info("installer",`npm install ${n} succeeded via ${l}`),o}catch(o){const l=o instanceof Error?o.message:String(o);throw l.includes("timed out")||l.includes("ETIMEDOUT")?new c("INSTALL_TIMEOUT",`npm install timed out (tried all mirrors): ${l}`):new c("INSTALL_FAILED",`npm install failed (tried all mirrors): ${l}`)}}executeShell(n,t,e,s){return new Promise((o,l)=>{const a=process.platform==="win32",f=a?"cmd.exe":"sh",p=a?["/d","/c",n]:["-c",n];m.info("installer",`exec: ${f} ${p.join(" ")}`);const h=D(f,p,{timeout:t,stdio:["ignore","pipe","pipe"],...a?{windowsVerbatimArguments:!0}:{},env:{...process.env,NONINTERACTIVE:"1",DEBIAN_FRONTEND:"noninteractive"}});let r="",g=!1;const E=setTimeout(()=>{g=!0;try{h.kill("SIGTERM")}catch{}setTimeout(()=>{try{h.kill("SIGKILL")}catch{}},5e3).unref()},t);h.stdout?.on("data",i=>{const u=i.toString("utf-8");r+=u,r.length>w&&(r=r.slice(-w)),this.updateOutputTail(e,s,r)}),h.stderr?.on("data",i=>{const u=i.toString("utf-8");r+=u,r.length>w&&(r=r.slice(-w)),this.updateOutputTail(e,s,r)}),h.on("error",i=>{clearTimeout(E),l(new c("INSTALL_FAILED",`Spawn error: ${i.message}`))}),h.on("close",i=>{if(clearTimeout(E),g){l(new c("INSTALL_TIMEOUT",`Install timed out after ${t/1e3}s`));return}if(i!==0){const u=r.slice(-1024);l(new c("INSTALL_FAILED",`Process exited with code ${i}: ${u}`));return}o(r.trim())})})}executeExec(n,t,e,s,o){return new Promise((l,a)=>{m.info("installer",`exec: ${n} ${t.join(" ")}`);const f=N(n,t,{timeout:e,maxBuffer:w},(p,h,r)=>{if(p){if(p.killed)a(new c("INSTALL_TIMEOUT",`${n} timed out after ${e/1e3}s`));else{const g=r?.trim()||p.message;a(new c("INSTALL_FAILED",`${n} failed: ${g}`))}return}l(`${h??""}
9
- ${r??""}`.trim())});this.trackOutput(f,s,o)})}trackOutput(n,t,e){n.on("close",()=>{const s=this.activeInstalls.get(t);s&&(s.elapsedMs=Date.now()-e)})}async resolveViaNpmBin(n){return new Promise(t=>{const e=process.platform==="win32";N(e?"cmd.exe":"npm",e?["/c","npm","prefix","-g"]:["prefix","-g"],{timeout:5e3,encoding:"utf-8"},(l,a)=>{if(l){t(null);return}const f=a.trim();if(!f){t(null);return}const p=this.os==="windows"?L(f,`${n}.cmd`):L(f,"bin",n);if(y(p)){t(p);return}const h=L(f,n);if(y(h)){t(h);return}t(null)})})}async resolveViaRegistryPath(n){const t=await V(n);return m.info("installer",`\u5E38\u89C1\u76EE\u5F55/\u6CE8\u518C\u8868\u515C\u5E95\u67E5\u627E ${n}: \u7ED3\u679C=${t??"\u672A\u627E\u5230"}`),t}updateOutputTail(n,t,e){const o=e.split(`
10
- `).slice(-Q).join(`
11
- `),l=this.activeInstalls.get(n);l&&(l.outputTail=o,l.elapsedMs=Date.now()-t)}}export{ue as AgentInstaller,c as InstallerError};
8
+ ${s||"<\u7A7A>"}`)}if(x(),!n.skipVerify&&!l.skipVerification){this.setProgress(e,"verifying",t),m.info("installer",`Verifying ${e} installation...`);const s=await N(u);if(!s)return this.fail(e,"verifying",t,new c("VERIFICATION_FAILED",`${u} not found on PATH after installation. You may need to open a new terminal or run: source ~/.zshrc (or ~/.bashrc)`),i,o);let{version:a,error:d}=await T(s);a===null&&d&&(await this.sleep(1e3),{version:a,error:d}=await T(s));const I=Date.now()-t;return this.activeInstalls.delete(e),m.info("installer",`${e} installed successfully at ${s} (v${a??"unknown"}, ${I}ms)`),{agentType:e,ok:!0,phase:"completed",installedPath:s,installedVersion:a,durationMs:I,output:g,prerequisites:o.length>0?o:void 0,environment:i}}const E=Date.now()-t;return this.activeInstalls.delete(e),m.info("installer",`${e} install command completed (${E}ms, verification skipped)`),{agentType:e,ok:!0,phase:"completed",installedPath:null,installedVersion:null,durationMs:E,output:g,prerequisites:o.length>0?o:void 0,environment:i}}getManualHint(n,t){const i=k(n,t)?.fallback,l={claude:"https://docs.anthropic.com/en/docs/claude-code/overview",codex:"https://github.com/openai/codex",gemini:"https://github.com/google-gemini/gemini-cli",qwen:"https://github.com/QwenLM/qwen-code",cursor:"https://cursor.com/docs/cli/installation",copilot:"https://github.com/github/copilot-cli",kiro:"https://kiro.dev/docs/cli/",openclaw:"https://github.com/openclaw/openclaw",reasonix:"https://github.com/esengine/DeepSeek-Reasonix",openhuman:"https://github.com/tinyhumansai/openhuman/issues/128"}[n],u=[];return l&&u.push(`Docs: ${l}`),i&&u.push(`Alternative: ${i.command}`),u.length>0?u.join(" | "):null}setProgress(n,t,e,i,r){this.activeInstalls.set(n,{agentType:n,phase:t,startedAt:e,elapsedMs:Date.now()-e,...i?{currentPrereq:i}:{},...r?{pendingPrereqs:r}:{}})}fail(n,t,e,i,r,l,u){const h=u??this.activeInstalls.get(n)?.outputTail??"";this.activeInstalls.delete(n),m.error("installer",`${n} install failed at ${t}: ${i.message}`);const p=l?L(l):[],f=P({agentType:n,os:this.os,env:r??{platform:this.os,osVersion:"unknown",arch:process.arch,shell:process.env.SHELL??null,nodeVersion:null,npmVersion:null,isDocker:!1,isCI:!1},missingPrereqs:p,primaryFailed:t==="installing",fallbackFailed:i.code==="FALLBACK_EXHAUSTED",error:i.message});return{agentType:n,ok:!1,phase:"failed",error:{code:i.code,message:i.message},durationMs:Date.now()-e,output:h,environment:r,prerequisites:l,manualGuide:f}}async executeWithRetry(n,t,e,i){let r=null;for(let l=0;l<=v;l++)try{return l>0&&(m.info("installer",`Retry ${l}/${v} for ${t}...`),await this.sleep(W)),await this.executeCommand(n,t,e,i)}catch(u){if(r=u instanceof c?u:new c("INTERNAL",String(u)),!(r.code==="INSTALL_TIMEOUT"||r.code==="INSTALL_FAILED"&&B(r.message))||l>=v)throw r;m.info("installer",`Attempt ${l+1} failed (retryable): ${r.message}`)}throw r??new c("INTERNAL","Unexpected retry loop exit")}sleep(n){return new Promise(t=>setTimeout(t,n))}executeCommand(n,t,e,i){const r=i??n.timeoutMs;switch(n.mode){case"npm":return this.executeNpm(n.npmPackage,r,t,e);case"shell":return this.executeShell(n.command,r,t,e);case"exec":return this.executeExec(n.command,n.execArgs??[],r,t,e);default:return Promise.reject(new c("INTERNAL",`Unknown install mode: ${n.mode}`))}}async executeNpm(n,t,e,i){try{const{output:r,registry:l}=await G(n,t,w);return m.info("installer",`npm install ${n} succeeded via ${l}`),r}catch(r){const l=r instanceof Error?r.message:String(r);throw l.includes("timed out")||l.includes("ETIMEDOUT")?new c("INSTALL_TIMEOUT",`npm install timed out (tried all mirrors): ${l}`):new c("INSTALL_FAILED",`npm install failed (tried all mirrors): ${l}`)}}executeShell(n,t,e,i){return new Promise((r,l)=>{const u=process.platform==="win32",h=u?"cmd.exe":"bash",p=u?["/d","/c",n]:["-c",`set -o pipefail; ${n}`];m.info("installer",`exec: ${h} ${p.join(" ")}`);const f=_(h,p,{timeout:t,stdio:["ignore","pipe","pipe"],...u?{windowsVerbatimArguments:!0}:{},env:{...process.env,NONINTERACTIVE:"1",DEBIAN_FRONTEND:"noninteractive"}});let o="",g=!1;const E=setTimeout(()=>{g=!0;try{f.kill("SIGTERM")}catch{}setTimeout(()=>{try{f.kill("SIGKILL")}catch{}},5e3).unref()},t);f.stdout?.on("data",s=>{const a=s.toString("utf-8");o+=a,o.length>w&&(o=o.slice(-w)),this.updateOutputTail(e,i,o)}),f.stderr?.on("data",s=>{const a=s.toString("utf-8");o+=a,o.length>w&&(o=o.slice(-w)),this.updateOutputTail(e,i,o)}),f.on("error",s=>{clearTimeout(E),l(new c("INSTALL_FAILED",`Spawn error: ${s.message}`))}),f.on("close",s=>{if(clearTimeout(E),g){l(new c("INSTALL_TIMEOUT",`Install timed out after ${t/1e3}s`));return}if(s!==0){const a=o.slice(-1024);l(new c("INSTALL_FAILED",`Process exited with code ${s}: ${a}`));return}r(o.trim())})})}executeExec(n,t,e,i,r){return new Promise((l,u)=>{m.info("installer",`exec: ${n} ${t.join(" ")}`);const h=R(n,t,{timeout:e,maxBuffer:w},(p,f,o)=>{if(p){if(p.killed)u(new c("INSTALL_TIMEOUT",`${n} timed out after ${e/1e3}s`));else{const g=o?.trim()||p.message;u(new c("INSTALL_FAILED",`${n} failed: ${g}`))}return}l(`${f??""}
9
+ ${o??""}`.trim())});this.trackOutput(h,i,r)})}trackOutput(n,t,e){n.on("close",()=>{const i=this.activeInstalls.get(t);i&&(i.elapsedMs=Date.now()-e)})}updateOutputTail(n,t,e){const r=e.split(`
10
+ `).slice(-j).join(`
11
+ `),l=this.activeInstalls.get(n);l&&(l.outputTail=r,l.elapsedMs=Date.now()-t)}}export{ie as AgentInstaller,c as InstallerError};
@@ -1,2 +1,2 @@
1
- import{getInstallCommand as g,getCliBinary as f}from"./registry.js";import{getPrereqManualInstructions as $}from"./prereq-installer.js";import{getMirrorInstallCommands as r}from"./npm-registry.js";function v(u){const{agentType:e,os:p,env:c,missingPrereqs:h}=u,s=[],a=f(e)??e,n=g(e,p),l=b(p);if(s.push(`=== ${e} \u4EBA\u5DE5\u5B89\u88C5\u6307\u5357 (${l}) ===`),s.push(""),s.push("\u5F53\u524D\u73AF\u5883:"),s.push(` \u7CFB\u7EDF: ${c.osVersion} (${c.arch})`),s.push(` Node.js: ${c.nodeVersion??"\u672A\u5B89\u88C5"}`),s.push(` npm: ${c.npmVersion??"\u672A\u5B89\u88C5"}`),s.push(""),u.error&&(s.push("\u81EA\u52A8\u5B89\u88C5\u5931\u8D25\u539F\u56E0:"),s.push(` ${u.error}`),s.push("")),h.length>0){s.push("--- \u6B65\u9AA4 1: \u5B89\u88C5\u524D\u7F6E\u4F9D\u8D56 ---"),s.push("");for(const o of h){const i=$(o.id,p);s.push(`\u25B8 ${o.label}${o.minVersion?` (\u9700\u8981 >= ${o.minVersion})`:""}`),i&&s.push(` \u5B89\u88C5\u65B9\u6CD5: ${i}`),s.push(` \u9A8C\u8BC1: ${o.id==="node"?"node --version":o.id==="npm"?"npm --version":`${o.id} --version`}`),s.push("")}}const m=h.length>0?"2":"1";if(s.push(`--- \u6B65\u9AA4 ${m}: \u5B89\u88C5 ${e} ---`),s.push(""),n){if(s.push(" \u63A8\u8350\u65B9\u5F0F:"),s.push(` ${n.command}`),s.push(""),n.mode==="npm"&&n.npmPackage){const o=r(n.npmPackage);s.push(" \u955C\u50CF\u5B89\u88C5\uFF08\u56FD\u5185\u7F51\u7EDC\u6162\u6216\u88AB\u5899\u65F6\u4F7F\u7528\uFF09:");for(const i of o)s.push(` # ${i.label}`),s.push(` ${i.command}`);s.push("")}if(n.fallback){if(s.push(" \u5907\u9009\u65B9\u5F0F\uFF08\u63A8\u8350\u5B89\u88C5\u5931\u8D25\u65F6\u4F7F\u7528\uFF09:"),s.push(` ${n.fallback.command}`),n.fallback.mode==="npm"&&n.fallback.npmPackage){const o=r(n.fallback.npmPackage);for(const i of o)s.push(` # ${i.label}`),s.push(` ${i.command}`)}s.push("")}}const d=h.length>0?"3":"2";s.push(`--- \u6B65\u9AA4 ${d}: \u9A8C\u8BC1\u5B89\u88C5 ---`),s.push(""),s.push(` ${a} --version`),s.push(""),s.push(" \u5982\u679C\u663E\u793A\u7248\u672C\u53F7\uFF0C\u8BF4\u660E\u5B89\u88C5\u6210\u529F\u3002"),s.push(""),s.push("--- \u5E38\u89C1\u95EE\u9898 ---"),s.push(""),s.push(" Q: \u5B89\u88C5\u540E\u547D\u4EE4\u627E\u4E0D\u5230\uFF1F"),s.push(" A: \u53EF\u80FD\u9700\u8981\u91CD\u65B0\u6253\u5F00\u7EC8\u7AEF\uFF0C\u6216\u8FD0\u884C:"),p==="macos"||p==="linux"?(s.push(" source ~/.zshrc # zsh"),s.push(" source ~/.bashrc # bash")):s.push(" \u91CD\u65B0\u6253\u5F00 PowerShell \u6216 CMD \u7A97\u53E3"),s.push(""),n?.mode==="npm"&&(s.push(" Q: npm install -g \u62A5\u6743\u9650\u9519\u8BEF\uFF1F"),p==="macos"||p==="linux"?(s.push(" A: \u4E0D\u8981\u7528 sudo npm install -g\u3002\u5EFA\u8BAE\u7528\u4EE5\u4E0B\u65B9\u5F0F\u4E4B\u4E00:"),s.push(" 1. brew install node (macOS\uFF0C\u63A8\u8350)"),s.push(" 2. \u4F7F\u7528 fnm/nvm \u7BA1\u7406 Node.js (Linux)"),s.push(" 3. \u914D\u7F6E npm \u5168\u5C40\u76EE\u5F55: npm config set prefix ~/.npm-global")):s.push(" A: \u4EE5\u7BA1\u7406\u5458\u8EAB\u4EFD\u6253\u5F00 PowerShell \u540E\u91CD\u8BD5"),s.push("")),s.push("--- \u5B98\u65B9\u6587\u6863 ---"),s.push("");const t={claude:"https://docs.anthropic.com/en/docs/claude-code/overview",codex:"https://github.com/openai/codex",gemini:"https://github.com/google-gemini/gemini-cli",qwen:"https://github.com/QwenLM/qwen-code",cursor:"https://cursor.com/docs/cli/installation",copilot:"https://docs.github.com/en/copilot/managing-copilot/configure-personal-settings/installing-github-copilot-in-the-cli",kiro:"https://kiro.dev/docs/cli/",openclaw:"https://github.com/openclaw/openclaw",reasonix:"https://github.com/esengine/DeepSeek-Reasonix"};return t[e]&&s.push(` ${e}: ${t[e]}`),s.join(`
1
+ import{getInstallCommand as f,getCliBinary as g}from"./registry.js";import{getPrereqManualInstructions as $}from"./prereq-installer.js";import{getMirrorInstallCommands as t}from"./npm-registry.js";function v(u){const{agentType:e,os:i,env:h,missingPrereqs:c}=u,s=[],a=g(e)??e,o=f(e,i),m=b(i);if(s.push(`=== ${e} \u4EBA\u5DE5\u5B89\u88C5\u6307\u5357 (${m}) ===`),s.push(""),s.push("\u5F53\u524D\u73AF\u5883:"),s.push(` \u7CFB\u7EDF: ${h.osVersion} (${h.arch})`),s.push(` Node.js: ${h.nodeVersion??"\u672A\u5B89\u88C5"}`),s.push(` npm: ${h.npmVersion??"\u672A\u5B89\u88C5"}`),s.push(""),u.error&&(s.push("\u81EA\u52A8\u5B89\u88C5\u5931\u8D25\u539F\u56E0:"),s.push(` ${u.error}`),s.push("")),c.length>0){s.push("--- \u6B65\u9AA4 1: \u5B89\u88C5\u524D\u7F6E\u4F9D\u8D56 ---"),s.push("");for(const n of c){const p=$(n.id,i);s.push(`\u25B8 ${n.label}${n.minVersion?` (\u9700\u8981 >= ${n.minVersion})`:""}`),p&&s.push(` \u5B89\u88C5\u65B9\u6CD5: ${p}`),s.push(` \u9A8C\u8BC1: ${n.id==="node"?"node --version":n.id==="npm"?"npm --version":`${n.id} --version`}`),s.push("")}}const l=c.length>0?"2":"1";if(s.push(`--- \u6B65\u9AA4 ${l}: \u5B89\u88C5 ${e} ---`),s.push(""),o){if(s.push(" \u63A8\u8350\u65B9\u5F0F:"),s.push(` ${o.command}`),s.push(""),o.mode==="npm"&&o.npmPackage){const n=t(o.npmPackage);s.push(" \u955C\u50CF\u5B89\u88C5\uFF08\u56FD\u5185\u7F51\u7EDC\u6162\u6216\u88AB\u5899\u65F6\u4F7F\u7528\uFF09:");for(const p of n)s.push(` # ${p.label}`),s.push(` ${p.command}`);s.push("")}if(o.fallback){if(s.push(" \u5907\u9009\u65B9\u5F0F\uFF08\u63A8\u8350\u5B89\u88C5\u5931\u8D25\u65F6\u4F7F\u7528\uFF09:"),s.push(` ${o.fallback.command}`),o.fallback.mode==="npm"&&o.fallback.npmPackage){const n=t(o.fallback.npmPackage);for(const p of n)s.push(` # ${p.label}`),s.push(` ${p.command}`)}s.push("")}}const d=c.length>0?"3":"2";s.push(`--- \u6B65\u9AA4 ${d}: \u9A8C\u8BC1\u5B89\u88C5 ---`),s.push(""),s.push(` ${a} --version`),s.push(""),s.push(" \u5982\u679C\u663E\u793A\u7248\u672C\u53F7\uFF0C\u8BF4\u660E\u5B89\u88C5\u6210\u529F\u3002"),s.push(""),s.push("--- \u5E38\u89C1\u95EE\u9898 ---"),s.push(""),s.push(" Q: \u5B89\u88C5\u540E\u547D\u4EE4\u627E\u4E0D\u5230\uFF1F"),s.push(" A: \u53EF\u80FD\u9700\u8981\u91CD\u65B0\u6253\u5F00\u7EC8\u7AEF\uFF0C\u6216\u8FD0\u884C:"),i==="macos"||i==="linux"?(s.push(" source ~/.zshrc # zsh"),s.push(" source ~/.bashrc # bash")):s.push(" \u91CD\u65B0\u6253\u5F00 PowerShell \u6216 CMD \u7A97\u53E3"),s.push(""),o?.mode==="npm"&&(s.push(" Q: npm install -g \u62A5\u6743\u9650\u9519\u8BEF\uFF1F"),i==="macos"||i==="linux"?(s.push(" A: \u4E0D\u8981\u7528 sudo npm install -g\u3002\u5EFA\u8BAE\u7528\u4EE5\u4E0B\u65B9\u5F0F\u4E4B\u4E00:"),s.push(" 1. brew install node (macOS\uFF0C\u63A8\u8350)"),s.push(" 2. \u4F7F\u7528 fnm/nvm \u7BA1\u7406 Node.js (Linux)"),s.push(" 3. \u914D\u7F6E npm \u5168\u5C40\u76EE\u5F55: npm config set prefix ~/.npm-global")):s.push(" A: \u4EE5\u7BA1\u7406\u5458\u8EAB\u4EFD\u6253\u5F00 PowerShell \u540E\u91CD\u8BD5"),s.push("")),s.push("--- \u5B98\u65B9\u6587\u6863 ---"),s.push("");const r={claude:"https://docs.anthropic.com/en/docs/claude-code/overview",codex:"https://github.com/openai/codex",gemini:"https://github.com/google-gemini/gemini-cli",qwen:"https://github.com/QwenLM/qwen-code",cursor:"https://cursor.com/docs/cli/installation",copilot:"https://github.com/github/copilot-cli",kiro:"https://kiro.dev/docs/cli/",openclaw:"https://github.com/openclaw/openclaw",reasonix:"https://github.com/esengine/DeepSeek-Reasonix"};return r[e]&&s.push(` ${e}: ${r[e]}`),s.join(`
2
2
  `)}function b(u){switch(u){case"macos":return"macOS";case"linux":return"Linux";case"windows":return"Windows"}}export{v as generateManualGuide};
@@ -1 +1 @@
1
- function e(i,s){return{command:`npm install -g ${i}`,mode:"npm",npmPackage:i,timeoutMs:s?.timeoutMs??12e4,prerequisites:["node","npm"],minNodeVersion:s?.minNodeVersion}}function n(i,s){return{command:i,mode:"shell",timeoutMs:s?.timeoutMs??12e4,prerequisites:s?.prerequisites}}function r(i,s,o){return{command:i,mode:"exec",execArgs:s,timeoutMs:o?.timeoutMs??6e4,prerequisites:o?.prerequisites,skipVerification:o?.skipVerification}}const l={claude:{cliBinary:"claude",macos:e("@anthropic-ai/claude-code",{minNodeVersion:"18.0"}),linux:e("@anthropic-ai/claude-code",{minNodeVersion:"18.0"}),windows:e("@anthropic-ai/claude-code",{minNodeVersion:"18.0"})},codex:{cliBinary:"codex",macos:{...n("curl -fsSL https://chatgpt.com/codex/install.sh | sh",{prerequisites:["curl"]}),fallback:e("@openai/codex")},linux:{...n("curl -fsSL https://chatgpt.com/codex/install.sh | sh",{prerequisites:["curl"]}),fallback:e("@openai/codex")},windows:{...n('powershell -ExecutionPolicy ByPass -c "irm https://chatgpt.com/codex/install.ps1 | iex"'),fallback:e("@openai/codex")}},gemini:{cliBinary:"gemini",macos:{...e("@google/gemini-cli",{minNodeVersion:"18.0"}),fallback:n("npx @google/gemini-cli --yes",{timeoutMs:12e4})},linux:e("@google/gemini-cli",{minNodeVersion:"18.0"}),windows:e("@google/gemini-cli",{minNodeVersion:"18.0"})},qwen:{cliBinary:"qwen",macos:{...n('bash -c "$(curl -fsSL https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen.sh)"',{prerequisites:["curl"]}),fallback:e("@qwen-code/qwen-code@latest",{minNodeVersion:"22.0"})},linux:{...n('bash -c "$(curl -fsSL https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen.sh)"',{prerequisites:["curl"]}),fallback:e("@qwen-code/qwen-code@latest",{minNodeVersion:"22.0"})},windows:{...n(`powershell -Command "Invoke-WebRequest 'https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen.bat' -OutFile (Join-Path $env:TEMP 'install-qwen.bat'); & (Join-Path $env:TEMP 'install-qwen.bat')"`),fallback:e("@qwen-code/qwen-code@latest",{minNodeVersion:"22.0"})}},cursor:{cliBinary:"agent",macos:n("curl https://cursor.com/install -fsS | bash",{prerequisites:["curl"]}),linux:n("curl https://cursor.com/install -fsS | bash",{prerequisites:["curl"]}),windows:n("irm 'https://cursor.com/install?win32=true' | iex")},copilot:{cliBinary:"copilot",macos:r("gh",["extension","install","github/gh-copilot"],{prerequisites:["gh"],skipVerification:!0}),linux:r("gh",["extension","install","github/gh-copilot"],{prerequisites:["gh"],skipVerification:!0}),windows:r("gh",["extension","install","github/gh-copilot"],{prerequisites:["gh"],skipVerification:!0})},kiro:{cliBinary:"kiro-cli",macos:n("curl -fsSL https://cli.kiro.dev/install | bash",{prerequisites:["curl"]}),linux:n("curl -fsSL https://cli.kiro.dev/install | bash",{prerequisites:["curl"]}),windows:null},openclaw:{cliBinary:"openclaw",macos:e("openclaw@latest",{minNodeVersion:"22.0"}),linux:e("openclaw@latest",{minNodeVersion:"22.0"}),windows:e("openclaw@latest",{minNodeVersion:"22.0"})},reasonix:{cliBinary:"reasonix",macos:e("reasonix"),linux:e("reasonix"),windows:e("reasonix")},pi:{cliBinary:"pi",macos:e("@earendil-works/pi-coding-agent",{minNodeVersion:"22.19"}),linux:e("@earendil-works/pi-coding-agent",{minNodeVersion:"22.19"}),windows:e("@earendil-works/pi-coding-agent",{minNodeVersion:"22.19"})},agy:{cliBinary:"agy",macos:n("curl -fsSL https://antigravity.google/cli/install.sh | bash",{prerequisites:["curl"]}),linux:n("curl -fsSL https://antigravity.google/cli/install.sh | bash",{prerequisites:["curl"]}),windows:n('powershell -ExecutionPolicy ByPass -c "irm https://antigravity.google/cli/install.ps1 | iex"')},hermes:{cliBinary:"hermes",macos:null,linux:null,windows:null},codewhale:{cliBinary:"codewhale",macos:e("codewhale"),linux:e("codewhale"),windows:e("codewhale")},opencode:{cliBinary:"opencode",macos:{...n("curl -fsSL https://opencode.ai/install | bash",{prerequisites:["curl"]}),fallback:e("opencode-ai")},linux:{...n("curl -fsSL https://opencode.ai/install | bash",{prerequisites:["curl"]}),fallback:e("opencode-ai")},windows:e("opencode-ai")},openhuman:{cliBinary:"openhuman-core",macos:n("curl -fsSL https://raw.githubusercontent.com/tinyhumansai/openhuman/main/scripts/install.sh | bash",{prerequisites:["curl"]}),linux:n("curl -fsSL https://raw.githubusercontent.com/tinyhumansai/openhuman/main/scripts/install.sh | bash",{prerequisites:["curl"]}),windows:null}};function u(){switch(process.platform){case"darwin":return"macos";case"linux":return"linux";case"win32":return"windows";default:return"linux"}}function a(i){return i.mode==="exec"&&i.execArgs&&i.execArgs.length>0?[i.command,...i.execArgs].join(" "):i.command}function p(i,s){const o=l[i];return o?o[s]:null}function m(i){return l[i]?.cliBinary??null}function c(i,s){const o=l[i];if(!o)return null;const t=o[s];return{agentType:i,cliBinary:o.cliBinary,supported:t!==null,installCommand:t?a(t):null,prerequisites:t?.prerequisites}}function d(i){return Object.keys(l).sort().map(s=>c(s,i)).filter(s=>s!==null)}function h(i){return i in l}export{u as detectPlatformOS,a as formatInstallCommand,c as getAgentInstallInfo,d as getAllAgentInstallInfo,m as getCliBinary,p as getInstallCommand,h as isKnownAgent};
1
+ function e(i,s){return{command:`npm install -g ${i}`,mode:"npm",npmPackage:i,timeoutMs:s?.timeoutMs??12e4,prerequisites:["node","npm"],minNodeVersion:s?.minNodeVersion}}function n(i,s){return{command:i,mode:"shell",timeoutMs:s?.timeoutMs??12e4,prerequisites:s?.prerequisites}}function c(i,s,o){return{command:i,mode:"exec",execArgs:s,timeoutMs:o?.timeoutMs??6e4,prerequisites:o?.prerequisites,skipVerification:o?.skipVerification}}const l={claude:{cliBinary:"claude",macos:e("@anthropic-ai/claude-code",{minNodeVersion:"18.0"}),linux:e("@anthropic-ai/claude-code",{minNodeVersion:"18.0"}),windows:e("@anthropic-ai/claude-code",{minNodeVersion:"18.0"})},codex:{cliBinary:"codex",macos:{...n("curl -fsSL https://chatgpt.com/codex/install.sh | sh",{prerequisites:["curl"]}),fallback:e("@openai/codex")},linux:{...n("curl -fsSL https://chatgpt.com/codex/install.sh | sh",{prerequisites:["curl"]}),fallback:e("@openai/codex")},windows:{...n('powershell -ExecutionPolicy ByPass -c "irm https://chatgpt.com/codex/install.ps1 | iex"'),fallback:e("@openai/codex")}},gemini:{cliBinary:"gemini",macos:{...e("@google/gemini-cli",{minNodeVersion:"18.0"}),fallback:n("npx @google/gemini-cli --yes",{timeoutMs:12e4})},linux:e("@google/gemini-cli",{minNodeVersion:"18.0"}),windows:e("@google/gemini-cli",{minNodeVersion:"18.0"})},qwen:{cliBinary:"qwen",macos:{...n("curl -fsSL https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen.sh | bash",{prerequisites:["curl"]}),fallback:e("@qwen-code/qwen-code@latest",{minNodeVersion:"22.0"})},linux:{...n("curl -fsSL https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen.sh | bash",{prerequisites:["curl"]}),fallback:e("@qwen-code/qwen-code@latest",{minNodeVersion:"22.0"})},windows:{...n(`powershell -Command "Invoke-WebRequest 'https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen.bat' -OutFile (Join-Path $env:TEMP 'install-qwen.bat'); & (Join-Path $env:TEMP 'install-qwen.bat')"`),fallback:e("@qwen-code/qwen-code@latest",{minNodeVersion:"22.0"})}},cursor:{cliBinary:"agent",macos:n("curl https://cursor.com/install -fsS | bash",{prerequisites:["curl"]}),linux:n("curl https://cursor.com/install -fsS | bash",{prerequisites:["curl"]}),windows:n(`powershell -ExecutionPolicy ByPass -c "irm 'https://cursor.com/install?win32=true' | iex"`)},copilot:{cliBinary:"copilot",macos:e("@github/copilot",{minNodeVersion:"22.0"}),linux:e("@github/copilot",{minNodeVersion:"22.0"}),windows:e("@github/copilot",{minNodeVersion:"22.0"})},kiro:{cliBinary:"kiro-cli",macos:n("curl -fsSL https://cli.kiro.dev/install | bash",{prerequisites:["curl"]}),linux:n("curl -fsSL https://cli.kiro.dev/install | bash",{prerequisites:["curl"]}),windows:null},openclaw:{cliBinary:"openclaw",macos:e("openclaw@latest",{minNodeVersion:"22.0"}),linux:e("openclaw@latest",{minNodeVersion:"22.0"}),windows:e("openclaw@latest",{minNodeVersion:"22.0"})},reasonix:{cliBinary:"reasonix",macos:e("reasonix"),linux:e("reasonix"),windows:e("reasonix")},pi:{cliBinary:"pi",macos:e("@earendil-works/pi-coding-agent",{minNodeVersion:"22.19"}),linux:e("@earendil-works/pi-coding-agent",{minNodeVersion:"22.19"}),windows:e("@earendil-works/pi-coding-agent",{minNodeVersion:"22.19"})},agy:{cliBinary:"agy",macos:n("curl -fsSL https://antigravity.google/cli/install.sh | bash",{prerequisites:["curl"]}),linux:n("curl -fsSL https://antigravity.google/cli/install.sh | bash",{prerequisites:["curl"]}),windows:n('powershell -ExecutionPolicy ByPass -c "irm https://antigravity.google/cli/install.ps1 | iex"')},hermes:{cliBinary:"hermes",macos:null,linux:null,windows:null},codewhale:{cliBinary:"codewhale",macos:e("codewhale"),linux:e("codewhale"),windows:e("codewhale")},opencode:{cliBinary:"opencode",macos:{...n("curl -fsSL https://opencode.ai/install | bash",{prerequisites:["curl"]}),fallback:e("opencode-ai")},linux:{...n("curl -fsSL https://opencode.ai/install | bash",{prerequisites:["curl"]}),fallback:e("opencode-ai")},windows:e("opencode-ai")},openhuman:{cliBinary:"openhuman-core",macos:n("curl -fsSL https://raw.githubusercontent.com/tinyhumansai/openhuman/main/scripts/install.sh | bash",{prerequisites:["curl"]}),linux:n("curl -fsSL https://raw.githubusercontent.com/tinyhumansai/openhuman/main/scripts/install.sh | bash",{prerequisites:["curl"]}),windows:null}};function u(){switch(process.platform){case"darwin":return"macos";case"linux":return"linux";case"win32":return"windows";default:return"linux"}}function r(i){return i.mode==="exec"&&i.execArgs&&i.execArgs.length>0?[i.command,...i.execArgs].join(" "):i.command}function m(i,s){const o=l[i];return o?o[s]:null}function p(i){return l[i]?.cliBinary??null}function a(i,s){const o=l[i];if(!o)return null;const t=o[s];return{agentType:i,cliBinary:o.cliBinary,supported:t!==null,installCommand:t?r(t):null,prerequisites:t?.prerequisites}}function d(i){return Object.keys(l).sort().map(s=>a(s,i)).filter(s=>s!==null)}function h(i){return i in l}export{u as detectPlatformOS,r as formatInstallCommand,a as getAgentInstallInfo,d as getAllAgentInstallInfo,p as getCliBinary,m as getInstallCommand,h as isKnownAgent};
@@ -1 +1 @@
1
- import{CodexResponsesEventTranslator as s,shouldUseCodexOpenaiChatCompat as o,resolveCodexOpenaiChatCompatPath as t,stripCodexOpenaiChatCompatHeaders as p,translateResponsesRequestToChatCompletions as r,parseSseFrames as C,parseSseData as n,encodeResponsesSseEvent as d}from"./translator.js";import{forwardCodexOpenaiChatCompatRequest as h}from"./forwarder.js";export{s as CodexResponsesEventTranslator,d as encodeResponsesSseEvent,h as forwardCodexOpenaiChatCompatRequest,n as parseSseData,C as parseSseFrames,t as resolveCodexOpenaiChatCompatPath,o as shouldUseCodexOpenaiChatCompat,p as stripCodexOpenaiChatCompatHeaders,r as translateResponsesRequestToChatCompletions};
1
+ import{CodexResponsesEventTranslator as s,shouldUseCodexOpenaiChatCompat as o,resolveCodexOpenaiChatCompatPath as t,stripCodexOpenaiChatCompatHeaders as C,translateResponsesRequestToChatCompletions as p,parseSseFrames as r,parseSseData as n,encodeResponsesSseEvent as E,CODEX_OPENAI_WIRE_COMPAT_HEADER as O,CODEX_OPENAI_WIRE_COMPAT_MODE as d}from"./translator.js";import{forwardCodexOpenaiChatCompatRequest as R}from"./forwarder.js";export{O as CODEX_OPENAI_WIRE_COMPAT_HEADER,d as CODEX_OPENAI_WIRE_COMPAT_MODE,s as CodexResponsesEventTranslator,E as encodeResponsesSseEvent,R as forwardCodexOpenaiChatCompatRequest,n as parseSseData,r as parseSseFrames,t as resolveCodexOpenaiChatCompatPath,o as shouldUseCodexOpenaiChatCompat,C as stripCodexOpenaiChatCompatHeaders,p as translateResponsesRequestToChatCompletions};
@@ -6,4 +6,4 @@ import{randomUUID as I}from"node:crypto";const O="x-grix-openai-wire-compat",M="
6
6
 
7
7
  `,s);if(o<0)break;e.push(n.slice(s,o)),s=o+2}return{frames:e,remaining:n.slice(s)}}function B(t){const n=t.split(`
8
8
  `).filter(e=>e.startsWith("data:")).map(e=>e.slice(5).trimStart());return n.length===0?null:n.join(`
9
- `)}export{Z as CodexResponsesEventTranslator,w as encodeResponsesSseEvent,B as parseSseData,q as parseSseFrames,Q as resolveCodexOpenaiChatCompatPath,G as shouldUseCodexOpenaiChatCompat,K as stripCodexOpenaiChatCompatHeaders,Y as translateResponsesRequestToChatCompletions};
9
+ `)}export{O as CODEX_OPENAI_WIRE_COMPAT_HEADER,M as CODEX_OPENAI_WIRE_COMPAT_MODE,Z as CodexResponsesEventTranslator,w as encodeResponsesSseEvent,B as parseSseData,q as parseSseFrames,Q as resolveCodexOpenaiChatCompatPath,G as shouldUseCodexOpenaiChatCompat,K as stripCodexOpenaiChatCompatHeaders,Y as translateResponsesRequestToChatCompletions};
@@ -1 +1 @@
1
- import{CodexResponsesEventTranslator as r,shouldUseCodexOpenaiChatCompat as n,forwardCodexOpenaiChatCompatRequest as s,resolveCodexOpenaiChatCompatPath as i,stripCodexOpenaiChatCompatHeaders as p,translateResponsesRequestToChatCompletions as C,parseSseFrames as d,parseSseData as m,encodeResponsesSseEvent as h}from"./codex-openai/index.js";import{AnthropicMessagesEventTranslator as R,shouldUseCodexAnthropicCompat as x,forwardCodexAnthropicCompatRequest as u,resolveCodexAnthropicCompatPath as A,stripCodexAnthropicCompatHeaders as c}from"./codex-anthropic/index.js";import{shouldUseCodexGeminiCompat as G,forwardCodexGeminiCompatRequest as v,resolveCodexGeminiCompatPath as T,stripCodexGeminiCompatHeaders as O,applyGeminiApiKeyHeader as f,translateResponsesRequestToGeminiGenerateContent as q,translateGeminiStreamChunkToResponsesEvents as M,translateGeminiGenerateContentResponseToResponsesEvents as S}from"./codex-gemini/index.js";import{AnthropicStreamEventTranslator as H,shouldUseAnthropicChatCompat as y,forwardAnthropicChatCompatRequest as D,resolveAnthropicChatCompatPath as P,stripAnthropicProxyHeaders as U,translateAnthropicMessagesToChatCompletions as _,translateChatCompletionToAnthropicMessage as g,encodeAnthropicSseEvent as B}from"./anthropic/index.js";import{GeminiChatCompletionStreamTranslator as k,shouldUseGeminiOpenaiChatCompat as F,forwardGeminiOpenaiChatCompatRequest as I,resolveGeminiOpenaiChatCompatPath as K,stripGeminiOpenaiChatCompatHeaders as V,translateGeminiRequestToChatCompletions as W,translateChatCompletionToGeminiGenerateContentResponse as X,encodeGeminiStreamSseEvent as Y,encodeGeminiSseEvent as b}from"./gemini/index.js";import{CODEX_MODEL_OVERRIDE_HEADER as z,readCodexModelOverrideHeader as J,stripCodexModelOverrideHeaders as N,applyCodexModelOverrideToRequestBody as Q,shouldRewriteCodexRequestModel as Z,readAndRewriteCodexRequestBody as $,shouldRewriteAnthropicRequestModel as ee,readAndRewriteAnthropicRequestBody as te,patchAnthropicThinkingHistory as oe,resolveMappedModel as ae,DEFAULT_GATEWAY_MODEL_MAP as re}from"./model-override.js";function t(e){return e instanceof Error?e:new Error(String(e))}export{R as AnthropicMessagesEventTranslator,H as AnthropicStreamEventTranslator,z as CODEX_MODEL_OVERRIDE_HEADER,r as CodexResponsesEventTranslator,re as DEFAULT_GATEWAY_MODEL_MAP,k as GeminiChatCompletionStreamTranslator,Q as applyCodexModelOverrideToRequestBody,f as applyGeminiApiKeyHeader,B as encodeAnthropicSseEvent,b as encodeGeminiSseEvent,Y as encodeGeminiStreamSseEvent,h as encodeResponsesSseEvent,D as forwardAnthropicChatCompatRequest,u as forwardCodexAnthropicCompatRequest,v as forwardCodexGeminiCompatRequest,s as forwardCodexOpenaiChatCompatRequest,I as forwardGeminiOpenaiChatCompatRequest,m as parseSseData,d as parseSseFrames,oe as patchAnthropicThinkingHistory,te as readAndRewriteAnthropicRequestBody,$ as readAndRewriteCodexRequestBody,J as readCodexModelOverrideHeader,P as resolveAnthropicChatCompatPath,A as resolveCodexAnthropicCompatPath,T as resolveCodexGeminiCompatPath,i as resolveCodexOpenaiChatCompatPath,K as resolveGeminiOpenaiChatCompatPath,ae as resolveMappedModel,ee as shouldRewriteAnthropicRequestModel,Z as shouldRewriteCodexRequestModel,y as shouldUseAnthropicChatCompat,x as shouldUseCodexAnthropicCompat,G as shouldUseCodexGeminiCompat,n as shouldUseCodexOpenaiChatCompat,F as shouldUseGeminiOpenaiChatCompat,U as stripAnthropicProxyHeaders,c as stripCodexAnthropicCompatHeaders,O as stripCodexGeminiCompatHeaders,N as stripCodexModelOverrideHeaders,p as stripCodexOpenaiChatCompatHeaders,V as stripGeminiOpenaiChatCompatHeaders,t as toError,_ as translateAnthropicMessagesToChatCompletions,g as translateChatCompletionToAnthropicMessage,X as translateChatCompletionToGeminiGenerateContentResponse,S as translateGeminiGenerateContentResponseToResponsesEvents,W as translateGeminiRequestToChatCompletions,M as translateGeminiStreamChunkToResponsesEvents,C as translateResponsesRequestToChatCompletions,q as translateResponsesRequestToGeminiGenerateContent};
1
+ import{CodexResponsesEventTranslator as r,shouldUseCodexOpenaiChatCompat as n,forwardCodexOpenaiChatCompatRequest as s,resolveCodexOpenaiChatCompatPath as i,stripCodexOpenaiChatCompatHeaders as p,translateResponsesRequestToChatCompletions as C,parseSseFrames as d,parseSseData as m,encodeResponsesSseEvent as h,CODEX_OPENAI_WIRE_COMPAT_HEADER as l,CODEX_OPENAI_WIRE_COMPAT_MODE as R}from"./codex-openai/index.js";import{AnthropicMessagesEventTranslator as A,shouldUseCodexAnthropicCompat as x,forwardCodexAnthropicCompatRequest as u,resolveCodexAnthropicCompatPath as c,stripCodexAnthropicCompatHeaders as O}from"./codex-anthropic/index.js";import{shouldUseCodexGeminiCompat as v,forwardCodexGeminiCompatRequest as T,resolveCodexGeminiCompatPath as M,stripCodexGeminiCompatHeaders as f,applyGeminiApiKeyHeader as _,translateResponsesRequestToGeminiGenerateContent as q,translateGeminiStreamChunkToResponsesEvents as D,translateGeminiGenerateContentResponseToResponsesEvents as H}from"./codex-gemini/index.js";import{AnthropicStreamEventTranslator as S,shouldUseAnthropicChatCompat as w,forwardAnthropicChatCompatRequest as y,resolveAnthropicChatCompatPath as U,stripAnthropicProxyHeaders as g,translateAnthropicMessagesToChatCompletions as I,translateChatCompletionToAnthropicMessage as B,encodeAnthropicSseEvent as L}from"./anthropic/index.js";import{GeminiChatCompletionStreamTranslator as X,shouldUseGeminiOpenaiChatCompat as k,forwardGeminiOpenaiChatCompatRequest as F,resolveGeminiOpenaiChatCompatPath as N,stripGeminiOpenaiChatCompatHeaders as K,translateGeminiRequestToChatCompletions as V,translateChatCompletionToGeminiGenerateContentResponse as Y,encodeGeminiStreamSseEvent as b,encodeGeminiSseEvent as j}from"./gemini/index.js";import{CODEX_MODEL_OVERRIDE_HEADER as J,readCodexModelOverrideHeader as Q,stripCodexModelOverrideHeaders as Z,applyCodexModelOverrideToRequestBody as $,shouldRewriteCodexRequestModel as ee,readAndRewriteCodexRequestBody as te,shouldRewriteAnthropicRequestModel as oe,readAndRewriteAnthropicRequestBody as ae,patchAnthropicThinkingHistory as re,resolveMappedModel as ne,DEFAULT_GATEWAY_MODEL_MAP as se}from"./model-override.js";function t(e){return e instanceof Error?e:new Error(String(e))}export{A as AnthropicMessagesEventTranslator,S as AnthropicStreamEventTranslator,J as CODEX_MODEL_OVERRIDE_HEADER,l as CODEX_OPENAI_WIRE_COMPAT_HEADER,R as CODEX_OPENAI_WIRE_COMPAT_MODE,r as CodexResponsesEventTranslator,se as DEFAULT_GATEWAY_MODEL_MAP,X as GeminiChatCompletionStreamTranslator,$ as applyCodexModelOverrideToRequestBody,_ as applyGeminiApiKeyHeader,L as encodeAnthropicSseEvent,j as encodeGeminiSseEvent,b as encodeGeminiStreamSseEvent,h as encodeResponsesSseEvent,y as forwardAnthropicChatCompatRequest,u as forwardCodexAnthropicCompatRequest,T as forwardCodexGeminiCompatRequest,s as forwardCodexOpenaiChatCompatRequest,F as forwardGeminiOpenaiChatCompatRequest,m as parseSseData,d as parseSseFrames,re as patchAnthropicThinkingHistory,ae as readAndRewriteAnthropicRequestBody,te as readAndRewriteCodexRequestBody,Q as readCodexModelOverrideHeader,U as resolveAnthropicChatCompatPath,c as resolveCodexAnthropicCompatPath,M as resolveCodexGeminiCompatPath,i as resolveCodexOpenaiChatCompatPath,N as resolveGeminiOpenaiChatCompatPath,ne as resolveMappedModel,oe as shouldRewriteAnthropicRequestModel,ee as shouldRewriteCodexRequestModel,w as shouldUseAnthropicChatCompat,x as shouldUseCodexAnthropicCompat,v as shouldUseCodexGeminiCompat,n as shouldUseCodexOpenaiChatCompat,k as shouldUseGeminiOpenaiChatCompat,g as stripAnthropicProxyHeaders,O as stripCodexAnthropicCompatHeaders,f as stripCodexGeminiCompatHeaders,Z as stripCodexModelOverrideHeaders,p as stripCodexOpenaiChatCompatHeaders,K as stripGeminiOpenaiChatCompatHeaders,t as toError,I as translateAnthropicMessagesToChatCompletions,B as translateChatCompletionToAnthropicMessage,Y as translateChatCompletionToGeminiGenerateContentResponse,H as translateGeminiGenerateContentResponseToResponsesEvents,V as translateGeminiRequestToChatCompletions,D as translateGeminiStreamChunkToResponsesEvents,C as translateResponsesRequestToChatCompletions,q as translateResponsesRequestToGeminiGenerateContent};
@@ -1 +1 @@
1
- import{ProxyManager as i}from"./proxy-manager.js";import{ProxyManager as y}from"./proxy-manager.js";let o=null;function c(r){return o||(o=new i(r)),o}function s(){return o}function p(){o=null}const a="localhost,127.0.0.1,::1";function P(r){const e=o?.getInjectionEnv();if(!e)return r;const t={...r,...e},n=r?.NO_PROXY??r?.no_proxy??process.env.NO_PROXY??process.env.no_proxy;return t.NO_PROXY=n&&n.trim()?`${n},${a}`:a,t}export{y as ProxyManager,P as applyProxyEnv,s as getProxyManager,c as initProxyManager,p as resetProxyManagerForTest};
1
+ import{ProxyManager as l}from"./proxy-manager.js";import{ProxyManager as g,ProxyNoRelayRouteError as O}from"./proxy-manager.js";import{relayHostForClientType as E}from"./relay-hosts.js";let o=null;function y(r){return o||(o=new l(r)),o}function c(){return o}function u(){o=null}class s extends Error{agentName;code="RELAY_PROXY_UNAVAILABLE";constructor(e){super(`agent "${e}" has Grix relay enabled but the local MITM proxy is not running; refusing to start it (it would silently fall back to your own account). Fix the proxy or turn relay off.`),this.agentName=e,this.name="ProxyRelayUnavailableError"}}const i="localhost,127.0.0.1,::1";function p(r,e){if(o?.isRelayExpectedButUnavailable(e))throw new s(e);const n=o?.getInjectionEnv(e);if(!n)return r;const a={...r,...n},t=r?.NO_PROXY??r?.no_proxy??process.env.NO_PROXY??process.env.no_proxy;return a.NO_PROXY=t&&t.trim()?`${t},${i}`:i,a}export{g as ProxyManager,O as ProxyNoRelayRouteError,s as ProxyRelayUnavailableError,p as applyProxyEnv,c as getProxyManager,y as initProxyManager,E as relayHostForClientType,u as resetProxyManagerForTest};
@@ -1,7 +1,7 @@
1
- import{existsSync as w,readFileSync as E}from"node:fs";import{mkdir as l,rm as p,writeFile as a}from"node:fs/promises";import{join as i}from"node:path";import{rootCertificates as x}from"node:tls";import{DynamicCertificateAuthority as D}from"./crypto/index.js";import{TrafficCaptureWriter as I,TrafficEventEmitter as H}from"./capture/index.js";import{HostInterceptList as C,RuntimeRouteRegistry as S}from"./routing/index.js";import{AcpTrafficProxyServer as _}from"./server/index.js";import{logProxyRuntimeStderr as n}from"./server/logger.js";class u{filePath="";append(){}close(){return Promise.resolve()}}const c="proxy-state.json",m="routes.json";class B{proxyDataDir;certificateAuthority=null;captureWriter=new u;runtimeRoutes=new S;interceptHosts=new C;server=null;runtimeInfo=null;injectionEnabled=!1;configExtras={};persistQueue=Promise.resolve();constructor(e){this.proxyDataDir=i(e,"proxy")}async start(e={}){if(this.runtimeInfo)return this.runtimeInfo;await l(this.proxyDataDir,{recursive:!0});const t=this.loadRoutesFile(),r=e.captureEnabled??t?.capture??process.env.GRIX_PROXY_CAPTURE==="1";this.captureWriter=r?await I.create(this.proxyDataDir,{onCleanupError:(R,o)=>{const P=o instanceof Error?o.message:String(o);n("warn",`stale capture cleanup skipped ${R}: ${P}`)}}):new u,this.certificateAuthority=new D(this.proxyDataDir);const s=await this.certificateAuthority.ensureRootCertificate(),y=await this.writeCaBundle(s.certPem);t&&(this.applyRoutesFile(t),t.capture!==void 0&&(this.configExtras.capture=t.capture),t.mitmPort!==void 0&&(this.configExtras.mitmPort=t.mitmPort));const h=e.interceptHosts??t?.interceptHosts;h&&this.interceptHosts.setHosts(h),this.server=new _(this.captureWriter,this.certificateAuthority,new H,this.runtimeRoutes,this.interceptHosts);const g=e.port??t?.mitmPort??0,{port:f}=await this.server.start(g,"127.0.0.1");return this.runtimeInfo={mitmPort:f,proxyUrl:`http://127.0.0.1:${f}`,caCertPath:s.certPath,caBundlePath:y,captureFilePath:this.captureWriter.filePath,interceptHosts:this.interceptHosts.getHosts()},await this.writeStateFile(this.runtimeInfo),this.runtimeInfo}async enable(e={}){const t=await this.start(e);return this.injectionEnabled=!0,t}disable(){this.injectionEnabled=!1}isInjectionEnabled(){return this.injectionEnabled}async stop(){this.server&&(await this.server.stop().catch(()=>{}),this.server=null),await this.captureWriter.close().catch(()=>{}),this.captureWriter=new u,this.runtimeInfo=null,this.injectionEnabled=!1,await p(i(this.proxyDataDir,c),{force:!0}).catch(()=>{})}async clearRuntimeState(){this.runtimeInfo||await p(i(this.proxyDataDir,c),{force:!0}).catch(()=>{})}getRuntimeInfo(){return this.runtimeInfo}getMitmPort(){return this.runtimeInfo?.mitmPort??null}getCaCertPath(){return this.runtimeInfo?.caCertPath??null}getCaBundlePath(){return this.runtimeInfo?.caBundlePath??null}setRoute(e){this.runtimeRoutes.setRoute(e)}deleteRoute(e){this.runtimeRoutes.deleteRoute(e)}setDefaultRouteKey(e){this.runtimeRoutes.setDefaultRouteKey(e)}getInterceptHosts(){return this.interceptHosts.getHosts()}setInterceptHosts(e){this.interceptHosts.setHosts(e),this.runtimeInfo&&(this.runtimeInfo={...this.runtimeInfo,interceptHosts:this.interceptHosts.getHosts()},this.writeStateFile(this.runtimeInfo))}getConfigSnapshot(){const e={routes:this.runtimeRoutes.listRoutes(),interceptHosts:this.interceptHosts.getHosts()},t=this.runtimeRoutes.getDefaultRouteKey();return t&&(e.defaultRouteKey=t),this.configExtras.capture!==void 0&&(e.capture=this.configExtras.capture),this.configExtras.mitmPort!==void 0&&(e.mitmPort=this.configExtras.mitmPort),e}async persistConfig(){const e=this.getConfigSnapshot(),t=this.persistQueue.then(()=>this.writeConfigFile(e));return this.persistQueue=t.catch(()=>{}),t}async writeConfigFile(e){await l(this.proxyDataDir,{recursive:!0});const t=i(this.proxyDataDir,m);await a(t,`${JSON.stringify(e,null,2)}
2
- `,"utf8")}getInjectionEnv(){return!this.injectionEnabled||!this.runtimeInfo?null:{HTTPS_PROXY:this.runtimeInfo.proxyUrl,NODE_EXTRA_CA_CERTS:this.runtimeInfo.caCertPath,SSL_CERT_FILE:this.runtimeInfo.caBundlePath}}async writeCaBundle(e){const t=i(this.proxyDataDir,"ca-bundle.pem"),r=x.join(`
3
- `),s=e.endsWith(`
4
- `)?e:`${e}
5
- `;return await a(t,`${r}
6
- ${s}`,"utf8"),t}loadRoutesFile(){const e=i(this.proxyDataDir,m);if(!w(e))return null;try{return JSON.parse(E(e,"utf8"))}catch(t){const r=t instanceof Error?t.message:String(t);return n("warn",`proxy routes file ignored (${e}): ${r}`),null}}applyRoutesFile(e){for(const t of e.routes??[])try{this.runtimeRoutes.handleControlLine(JSON.stringify(t.type==="delete_route"?t:{type:"set_route",routeKey:t.routeKey,targetBaseUrl:t.targetBaseUrl,...t.headers?{headers:t.headers}:{},...t.model?{model:t.model}:{},...t.modelMap?{modelMap:t.modelMap}:{},...t.passthrough?{passthrough:!0}:{}})),n("info",`proxy route loaded: ${t.routeKey} \u2192 ${t.targetBaseUrl}`)}catch(r){const s=r instanceof Error?r.message:String(r);n("warn",`proxy route "${t.routeKey}" skipped: ${s}`)}e.defaultRouteKey&&(this.runtimeRoutes.setDefaultRouteKey(e.defaultRouteKey),n("info",`proxy default route key: ${e.defaultRouteKey}`))}async writeStateFile(e){const t=i(this.proxyDataDir,c),r={...e,pid:process.pid,updated_at:Date.now()};await a(t,`${JSON.stringify(r,null,2)}
7
- `,"utf8")}}export{B as ProxyManager};
1
+ import{existsSync as w,readFileSync as x}from"node:fs";import{mkdir as p,rm as m,writeFile as c}from"node:fs/promises";import{join as o}from"node:path";import{rootCertificates as A}from"node:tls";import{DynamicCertificateAuthority as I}from"./crypto/index.js";import{TrafficCaptureWriter as E,TrafficEventEmitter as H}from"./capture/index.js";import{HostInterceptList as D,RuntimeRouteRegistry as P}from"./routing/index.js";import{AcpTrafficProxyServer as C}from"./server/index.js";import{logProxyRuntimeStderr as i}from"./server/logger.js";class l{filePath="";append(){}close(){return Promise.resolve()}}class S extends Error{agentName;host;code="NO_RELAY_ROUTE";constructor(t,e){super(`no usable relay route for agent "${t}" (host ${e}); provision a gateway key first`),this.agentName=t,this.host=e,this.name="ProxyNoRelayRouteError"}}const h="proxy-state.json",d="routes.json",a="grix-gateway";class k{proxyDataDir;certificateAuthority=null;captureWriter=new l;runtimeRoutes=new P;interceptHosts=new D;server=null;runtimeInfo=null;relayAgents=new Map;configExtras={};persistQueue=Promise.resolve();configLoaded=!1;lifecycleQueue=Promise.resolve();enqueueLifecycle(t){const e=this.lifecycleQueue.then(t,t);return this.lifecycleQueue=e.catch(()=>{}),e}constructor(t){this.proxyDataDir=o(t,"proxy"),this.loadConfigFromDisk()}loadConfigFromDisk(){if(this.configLoaded)return;this.configLoaded=!0;const t=this.loadRoutesFile();t&&(this.applyRoutesFile(t),t.capture!==void 0&&(this.configExtras.capture=t.capture),t.mitmPort!==void 0&&(this.configExtras.mitmPort=t.mitmPort),t.interceptHosts&&this.interceptHosts.setHosts(t.interceptHosts),this.relayAgents.size>0&&this.syncInterceptHosts())}async start(t={}){return this.enqueueLifecycle(()=>this.startInternal(t))}async startInternal(t={}){if(this.runtimeInfo)return this.runtimeInfo;await p(this.proxyDataDir,{recursive:!0});const e=t.captureEnabled??this.configExtras.capture??process.env.GRIX_PROXY_CAPTURE==="1";this.captureWriter=e?await E.create(this.proxyDataDir,{onCleanupError:(g,u)=>{const R=u instanceof Error?u.message:String(u);i("warn",`stale capture cleanup skipped ${g}: ${R}`)}}):new l,this.certificateAuthority=new I(this.proxyDataDir);const r=await this.certificateAuthority.ensureRootCertificate(),s=await this.writeCaBundle(r.certPem);t.interceptHosts&&this.interceptHosts.setHosts(t.interceptHosts),this.server=new C(this.captureWriter,this.certificateAuthority,new H,this.runtimeRoutes,this.interceptHosts);const n=t.port??this.configExtras.mitmPort??0,{port:y}=await this.server.start(n,"127.0.0.1");return this.runtimeInfo={mitmPort:y,proxyUrl:`http://127.0.0.1:${y}`,caCertPath:r.certPath,caBundlePath:s,captureFilePath:this.captureWriter.filePath,interceptHosts:this.interceptHosts.getHosts()},await this.writeStateFile(this.runtimeInfo),this.runtimeInfo}async setAgentRelayEnabled(t,e,r={}){return this.enqueueLifecycle(()=>this.setAgentRelayEnabledInternal(t,e,r))}async setAgentRelayEnabledInternal(t,e,r={}){const s=t.trim();if(s){if(e){const n=r.relayHost?.trim();if(!n)throw new Error(`relayHost is required to enable relay for agent "${s}"`);if(!this.runtimeRoutes.hasUsableHostRoute(n))throw new S(s,n);await this.startInternal(),this.relayAgents.set(s,n)}else this.relayAgents.delete(s);this.syncInterceptHosts(),await this.persistConfig()}}syncInterceptHosts(){this.interceptHosts.setHosts([...new Set(this.relayAgents.values())]),this.runtimeInfo&&(this.runtimeInfo={...this.runtimeInfo,interceptHosts:this.interceptHosts.getHosts()},this.writeStateFile(this.runtimeInfo).catch(t=>{i("warn",`failed to write proxy-state.json: ${t}`)}))}isAgentRelayEnabled(t){return this.relayAgents.has(t.trim())}getRelayAgents(){return[...this.relayAgents.keys()]}isRelayExpectedButUnavailable(t){return this.isAgentRelayEnabled(t)&&!this.runtimeInfo}getDegradedRelayAgents(){return this.runtimeInfo?[]:[...this.relayAgents.keys()]}async startIfAnyRelayAgent(){this.relayAgents.size!==0&&await this.start()}async stopIfUnused(t){return this.enqueueLifecycle(async()=>!this.runtimeInfo||this.relayAgents.size>0||t?.()?!1:(await this.stopInternal(),!0))}async disableAllAgentRelay(){return this.enqueueLifecycle(async()=>{const t=[...this.relayAgents.keys()];return this.relayAgents.clear(),this.syncInterceptHosts(),await this.persistConfig(),t})}async stop(){return this.enqueueLifecycle(()=>this.stopInternal())}async stopInternal(){this.server&&(await this.server.stop().catch(()=>{}),this.server=null),await this.captureWriter.close().catch(()=>{}),this.captureWriter=new l,this.runtimeInfo=null,await m(o(this.proxyDataDir,h),{force:!0}).catch(()=>{})}async clearRuntimeState(){this.runtimeInfo||await m(o(this.proxyDataDir,h),{force:!0}).catch(()=>{})}getRuntimeInfo(){return this.runtimeInfo}getMitmPort(){return this.runtimeInfo?.mitmPort??null}getCaCertPath(){return this.runtimeInfo?.caCertPath??null}getCaBundlePath(){return this.runtimeInfo?.caBundlePath??null}setRoute(t){this.runtimeRoutes.setRoute(t)}deleteRoute(t){this.runtimeRoutes.deleteRoute(t)}setDefaultRouteKey(t){this.runtimeRoutes.setDefaultRouteKey(t)}setHostDefaultRoute(t,e){this.runtimeRoutes.setHostDefaultRoute(t,e)}getInterceptHosts(){return this.interceptHosts.getHosts()}setInterceptHosts(t){this.interceptHosts.setHosts(t),this.runtimeInfo&&(this.runtimeInfo={...this.runtimeInfo,interceptHosts:this.interceptHosts.getHosts()},this.writeStateFile(this.runtimeInfo).catch(e=>{i("warn",`failed to write proxy-state.json: ${e}`)}))}getConfigSnapshot(){const t={routes:this.runtimeRoutes.listRoutes(),interceptHosts:this.interceptHosts.getHosts(),relayAgents:Object.fromEntries(this.relayAgents)},e=this.runtimeRoutes.getDefaultRouteKey();e&&(t.defaultRouteKey=e);const r=this.runtimeRoutes.getHostDefaultRoutes();return Object.keys(r).length>0&&(t.hostDefaultRoutes=r),this.configExtras.capture!==void 0&&(t.capture=this.configExtras.capture),this.configExtras.mitmPort!==void 0&&(t.mitmPort=this.configExtras.mitmPort),t}async persistConfig(){const t=this.getConfigSnapshot(),e=this.persistQueue.then(()=>this.writeConfigFile(t));return this.persistQueue=e.catch(()=>{}),e}async writeConfigFile(t){await p(this.proxyDataDir,{recursive:!0});const e=o(this.proxyDataDir,d);await c(e,`${JSON.stringify(t,null,2)}
2
+ `,"utf8")}getInjectionEnv(t){return!this.isAgentRelayEnabled(t)||!this.runtimeInfo?null:{HTTPS_PROXY:this.runtimeInfo.proxyUrl,NODE_EXTRA_CA_CERTS:this.runtimeInfo.caCertPath,SSL_CERT_FILE:this.runtimeInfo.caBundlePath}}async writeCaBundle(t){const e=o(this.proxyDataDir,"ca-bundle.pem"),r=A.join(`
3
+ `),s=t.endsWith(`
4
+ `)?t:`${t}
5
+ `;return await c(e,`${r}
6
+ ${s}`,"utf8"),e}loadRoutesFile(){const t=o(this.proxyDataDir,d);if(!w(t))return null;try{return JSON.parse(x(t,"utf8"))}catch(e){const r=e instanceof Error?e.message:String(e);return i("warn",`proxy routes file ignored (${t}): ${r}`),null}}applyRoutesFile(t){if(Array.isArray(t.relayAgents))i("warn","relayAgents in routes.json is an outdated array format (no host info): discarding it. Re-enable Grix relay per agent in the desktop app.");else for(const[e,r]of Object.entries(t.relayAgents??{})){const s=e.trim(),n=String(r??"").trim();s&&n&&this.relayAgents.set(s,n)}this.relayAgents.size>0&&i("info",`proxy relay agents restored: ${[...this.relayAgents.keys()].join(", ")}`),t.relayAgents===void 0&&(t.routes??[]).some(e=>e.routeKey.startsWith("grix-gateway"))&&i("warn","found legacy Grix relay config without per-agent list: relay is now per-agent and starts OFF. Re-enable it per agent in the desktop app (Settings \u2192 Grix relay).");for(const e of t.routes??[]){if(e.routeKey===a){i("info",`proxy legacy route dropped: ${a}`);continue}try{this.runtimeRoutes.handleControlLine(JSON.stringify(e.type==="delete_route"?e:{type:"set_route",routeKey:e.routeKey,targetBaseUrl:e.targetBaseUrl,...e.headers?{headers:e.headers}:{},...e.model?{model:e.model}:{},...e.modelMap?{modelMap:e.modelMap}:{},...e.passthrough?{passthrough:!0}:{},...e.relayOnly?{relayOnly:!0}:{},...e.codexResponsesToChat?{codexResponsesToChat:!0}:{}})),i("info",`proxy route loaded: ${e.routeKey} \u2192 ${e.targetBaseUrl}`)}catch(r){const s=r instanceof Error?r.message:String(r);i("warn",`proxy route "${e.routeKey}" skipped: ${s}`)}}t.defaultRouteKey&&t.defaultRouteKey!==a?(this.runtimeRoutes.setDefaultRouteKey(t.defaultRouteKey),i("info",`proxy default route key: ${t.defaultRouteKey}`)):t.defaultRouteKey===a&&i("info",`proxy legacy default route key dropped: ${a}`);for(const[e,r]of Object.entries(t.hostDefaultRoutes??{}))this.runtimeRoutes.setHostDefaultRoute(e,r),i("info",`proxy host default route: ${e} \u2192 ${r}`)}async writeStateFile(t){const e=o(this.proxyDataDir,h),r={...t,pid:process.pid,updated_at:Date.now()};await c(e,`${JSON.stringify(r,null,2)}
7
+ `,"utf8")}}export{k as ProxyManager,S as ProxyNoRelayRouteError};
@@ -0,0 +1 @@
1
+ const e={claude:"api.anthropic.com",codex:"api.openai.com"};function t(o){return e[String(o??"").trim().toLowerCase()]}export{t as relayHostForClientType};
@@ -0,0 +1 @@
1
+ async function f(a,t){return a.getRuntimeInfo()?a.stopIfUnused(()=>t.getAgentsWithStaleRelayEnv().length>0):!1}async function u(a,t,n){for(const e of a.getRelayAgents())t.markAgentRelayEnvStale(e);const s=await a.disableAllAgentRelay();for(const e of s)t.markAgentRelayEnvStale(e);for(const e of s){if(t.isAgentBusy(e)){n.info(`relay disabled for "${e}" but agent is busy; env stays stale until its next message`);continue}await t.restartAgent(e).catch(r=>{if(r?.code==="NOT_FOUND"){n.info(`relay disabled for "${e}" (agent no longer exists; nothing to restart)`);return}n.warn(`relay disabled for "${e}" but restart failed (env stays stale): ${r}`)})}return await f(a,t),s}async function g(a,t,n,s){const{agentName:e,enabled:r,relayHost:y}=s;r||t.markAgentRelayEnvStale(e),await a.setAgentRelayEnabled(e,r,r?{relayHost:y}:{});let l=!1,o=!1;if(t.isAgentBusy(e))o=!0,t.markAgentRelayEnvStale(e),n.info(`relay toggled for "${e}" but agent is busy; env stays stale until its next message`);else try{await t.restartAgent(e),l=!0}catch(i){if(i?.code==="NOT_FOUND")n.info(`relay toggled for "${e}" (agent no longer exists; nothing to restart)`);else{const d=t.markAgentRelayEnvStale(e);n.warn(`relay toggled for "${e}" but restart failed${d?" (old instance still holds the proxy env; marked stale)":" (no running instance; nothing holds the proxy env)"}: ${i}`)}}return await f(a,t),{restarted:l,busy:o}}export{u as disableAllRelayAndRestart,g as setOneAgentRelayAndRestart,f as stopProxyIfNobodyNeedsIt};
@@ -1 +1 @@
1
- const o=["api.anthropic.com","api.openai.com"];function c(n){let t=n.trim().toLowerCase();if(t.startsWith("[")){const s=t.indexOf("]");return s>0?t.slice(1,s):t}const e=t.lastIndexOf(":");return e>0&&t.indexOf(":")===e&&(t=t.slice(0,e)),t.replace(/\.+$/,"")}class f{entries=[];constructor(t=o){this.setHosts(t)}setHosts(t){const e=[];for(const s of t){const r=s.trim().toLowerCase();r&&!e.includes(r)&&e.push(r)}this.entries=e}getHosts(){return[...this.entries]}matches(t){if(this.entries.length===0)return!1;const e=c(t);if(!e)return!1;for(const s of this.entries){if(s==="*")return!0;if(s.startsWith(".")){const r=s,i=s.slice(1);if(e===i||e.endsWith(r))return!0}else if(e===s)return!0}return!1}}export{o as DEFAULT_INTERCEPT_HOSTS,f as HostInterceptList};
1
+ const o=["api.anthropic.com","api.openai.com"];function c(n){let t=n.trim().toLowerCase();if(t.startsWith("[")){const s=t.indexOf("]");return s>0?t.slice(1,s):t}const e=t.lastIndexOf(":");return e>0&&t.indexOf(":")===e&&(t=t.slice(0,e)),t.replace(/\.+$/,"")}class f{entries=[];constructor(t=o){this.setHosts(t)}setHosts(t){const e=[];for(const s of t){const r=s.trim().toLowerCase();r&&!e.includes(r)&&e.push(r)}this.entries=e}getHosts(){return[...this.entries]}matches(t){if(this.entries.length===0)return!1;const e=c(t);if(!e)return!1;for(const s of this.entries){if(s==="*")return!0;if(s.startsWith(".")){const r=s,i=s.slice(1);if(e===i||e.endsWith(r))return!0}else if(e===s)return!0}return!1}}export{o as DEFAULT_INTERCEPT_HOSTS,f as HostInterceptList,c as normalizeHost};
@@ -1 +1 @@
1
- const l="x-grix-proxy-route-key";function u(r){return Array.isArray(r)?r[0]?.trim()||null:r?.trim()||null}function i(r){return typeof r!="object"||r===null||Array.isArray(r)?!1:Object.values(r).every(e=>typeof e=="string")}function a(r){if(typeof r!="object"||r===null||Array.isArray(r))return!1;const e=r;return e.type!=="set_route"&&e.type!=="delete_route"&&e.type!=="pin_route"&&e.type!=="unpin_route"||typeof e.routeKey!="string"||e.routeKey.trim().length===0?!1:e.type==="unpin_route"?typeof e.sessionId=="string"&&e.sessionId.trim().length>0:e.type==="delete_route"?!0:typeof e.targetBaseUrl=="string"&&e.targetBaseUrl.trim().length>0&&(e.headers===void 0||i(e.headers))&&(e.model===void 0||typeof e.model=="string")&&(e.modelMap===void 0||i(e.modelMap))&&(e.passthrough===void 0||typeof e.passthrough=="boolean")&&(e.type==="set_route"||typeof e.sessionId=="string"&&e.sessionId.trim().length>0)}function d(r){const e=r.model?.trim();return{routeKey:r.routeKey.trim(),targetBaseUrl:r.targetBaseUrl.trim(),...r.headers?{headers:{...r.headers}}:{},...e?{model:e}:{},...r.modelMap?{modelMap:{...r.modelMap}}:{},...r.passthrough?{passthrough:!0}:{}}}class p{routes=new Map;pinnedRoutes=new Map;defaultRouteKey=null;setDefaultRouteKey(e){this.defaultRouteKey=e?.trim()||null}getDefaultRouteKey(){return this.defaultRouteKey}listRoutes(){return[...this.routes.values()].map(e=>({...e}))}setRoute(e){const s=d(e);this.routes.set(s.routeKey,s)}deleteRoute(e){this.routes.delete(e.trim())}pinRoute(e,s){const t=d(e);this.pinnedRoutes.set(this.buildPinnedRouteKey(t.routeKey,s),t)}unpinRoute(e,s){this.pinnedRoutes.delete(this.buildPinnedRouteKey(e,s))}resolveRequestRouteInfo(e){const s=u(e.headers[l]);if(!s){if(this.defaultRouteKey){const n=this.routes.get(this.defaultRouteKey);if(n)return{route:n,routeKey:this.defaultRouteKey,pinned:!1}}return null}const t=u(e.headers["x-claude-code-session-id"]);if(t){const n=this.pinnedRoutes.get(this.buildPinnedRouteKey(s,t));if(n)return{route:n,routeKey:s,sessionId:t,pinned:!0}}const o=this.routes.get(s);return o?{route:o,routeKey:s,...t?{sessionId:t}:{},pinned:!1}:null}resolveRequestRoute(e){return this.resolveRequestRouteInfo(e)?.route??null}buildPinnedRouteKey(e,s){return`${e.trim()}:${s.trim()}`}handleControlLine(e){const s=e.trim();if(!s)return!1;const t=JSON.parse(s);if(!a(t))return!1;if(t.type==="delete_route")return this.deleteRoute(t.routeKey),!0;if(t.type==="unpin_route")return this.unpinRoute(t.routeKey,t.sessionId??""),!0;const o=t.targetBaseUrl;if(!o)return!1;const n={routeKey:t.routeKey,targetBaseUrl:o,...t.headers?{headers:t.headers}:{},...t.model?{model:t.model}:{},...t.modelMap?{modelMap:t.modelMap}:{},...t.passthrough?{passthrough:!0}:{}};return t.type==="pin_route"?(this.pinRoute(n,t.sessionId??""),!0):(this.setRoute(n),!0)}}export{l as GRIX_PROXY_ROUTE_KEY_HEADER,p as RuntimeRouteRegistry};
1
+ import{normalizeHost as i}from"./intercept-list.js";const h="x-grix-proxy-route-key";function a(s){return Array.isArray(s)?s[0]?.trim()||null:s?.trim()||null}function d(s){return typeof s!="object"||s===null||Array.isArray(s)?!1:Object.values(s).every(e=>typeof e=="string")}function y(s){if(typeof s!="object"||s===null||Array.isArray(s))return!1;const e=s;return e.type!=="set_route"&&e.type!=="delete_route"&&e.type!=="pin_route"&&e.type!=="unpin_route"||typeof e.routeKey!="string"||e.routeKey.trim().length===0?!1:e.type==="unpin_route"?typeof e.sessionId=="string"&&e.sessionId.trim().length>0:e.type==="delete_route"?!0:typeof e.targetBaseUrl=="string"&&e.targetBaseUrl.trim().length>0&&(e.headers===void 0||d(e.headers))&&(e.model===void 0||typeof e.model=="string")&&(e.modelMap===void 0||d(e.modelMap))&&(e.passthrough===void 0||typeof e.passthrough=="boolean")&&(e.type==="set_route"||typeof e.sessionId=="string"&&e.sessionId.trim().length>0)}function f(s){const e=s.model?.trim();return{routeKey:s.routeKey.trim(),targetBaseUrl:s.targetBaseUrl.trim(),...s.headers?{headers:{...s.headers}}:{},...e?{model:e}:{},...s.modelMap?{modelMap:{...s.modelMap}}:{},...s.passthrough?{passthrough:!0}:{},...s.relayOnly?{relayOnly:!0}:{},...s.codexResponsesToChat?{codexResponsesToChat:!0}:{}}}class R{routes=new Map;pinnedRoutes=new Map;defaultRouteKey=null;hostDefaultRoutes=new Map;setDefaultRouteKey(e){this.defaultRouteKey=e?.trim()||null}getDefaultRouteKey(){return this.defaultRouteKey}setHostDefaultRoute(e,r){const t=i(e);if(!t)return;const o=r?.trim();o?this.hostDefaultRoutes.set(t,o):this.hostDefaultRoutes.delete(t)}getHostDefaultRoutes(){return Object.fromEntries(this.hostDefaultRoutes)}hasUsableHostRoute(e){const r=this.hostDefaultRoutes.get(i(e));return!!r&&this.routes.has(r)}listRoutes(){return[...this.routes.values()].map(e=>({...e}))}setRoute(e){const r=f(e);this.routes.set(r.routeKey,r)}deleteRoute(e){this.routes.delete(e.trim())}pinRoute(e,r){const t=f(e);this.pinnedRoutes.set(this.buildPinnedRouteKey(t.routeKey,r),t)}unpinRoute(e,r){this.pinnedRoutes.delete(this.buildPinnedRouteKey(e,r))}resolveRequestRouteInfo(e,r){const t=a(e.headers[h]);if(!t){if(r){const n=this.hostDefaultRoutes.get(i(r));if(n){const l=this.routes.get(n);if(l)return{route:l,routeKey:n,pinned:!1}}}if(this.defaultRouteKey){const n=this.routes.get(this.defaultRouteKey);if(n)return{route:n,routeKey:this.defaultRouteKey,pinned:!1}}return null}const o=a(e.headers["x-claude-code-session-id"]);if(o){const n=this.pinnedRoutes.get(this.buildPinnedRouteKey(t,o));if(n)return{route:n,routeKey:t,sessionId:o,pinned:!0}}const u=this.routes.get(t);return u?{route:u,routeKey:t,...o?{sessionId:o}:{},pinned:!1}:null}resolveRequestRoute(e){return this.resolveRequestRouteInfo(e)?.route??null}buildPinnedRouteKey(e,r){return`${e.trim()}:${r.trim()}`}handleControlLine(e){const r=e.trim();if(!r)return!1;const t=JSON.parse(r);if(!y(t))return!1;if(t.type==="delete_route")return this.deleteRoute(t.routeKey),!0;if(t.type==="unpin_route")return this.unpinRoute(t.routeKey,t.sessionId??""),!0;const o=t.targetBaseUrl;if(!o)return!1;const u={routeKey:t.routeKey,targetBaseUrl:o,...t.headers?{headers:t.headers}:{},...t.model?{model:t.model}:{},...t.modelMap?{modelMap:t.modelMap}:{},...t.passthrough?{passthrough:!0}:{},...t.relayOnly?{relayOnly:!0}:{},...t.codexResponsesToChat?{codexResponsesToChat:!0}:{}};return t.type==="pin_route"?(this.pinRoute(u,t.sessionId??""),!0):(this.setRoute(u),!0)}}export{h as GRIX_PROXY_ROUTE_KEY_HEADER,R as RuntimeRouteRegistry};
@@ -1,15 +1,15 @@
1
- import{randomUUID as u}from"node:crypto";import m from"node:http";import H from"node:net";import S from"node:tls";import{GRIX_PROXY_ROUTE_KEY_HEADER as P,parseConnectAuthority as x,resolveAbsoluteRequestTarget as _,resolveMitmRequestTarget as w,resolveRuntimeRoutedRequestTarget as R}from"../routing/index.js";import{forwardHttpRequest as g}from"../forwarding/index.js";import{handleWebSocketUpgrade as E}from"./websocket-handler.js";import{logProxyRuntimeStderr as v}from"./logger.js";const f=1e4;function I(c){if(!c||!c.startsWith("http://")&&!c.startsWith("https://"))return null;try{return new URL(c).hostname}catch{return null}}function y(c){return c?{routeSource:c.pinned?"pinned":"workspace",routeKey:c.routeKey,...c.sessionId?{routeSessionId:c.sessionId}:{},routeTargetBaseUrl:c.route.targetBaseUrl,...c.route.model?{routeModel:c.route.model}:{}}:{routeSource:"direct"}}class q{captureWriter;certificateAuthority;events;runtimeRoutes;interceptHosts;proxyServer;mitmHttpServer;mitmPlainHttpServer;constructor(e,o,s,n,i=null){this.captureWriter=e,this.certificateAuthority=o,this.events=s,this.runtimeRoutes=n,this.interceptHosts=i,this.proxyServer=m.createServer((r,t)=>{this.handlePlainHttpRequest(r,t)}),this.proxyServer.on("connect",(r,t,a)=>{this.handleConnectRequest(r,t,a)}),this.proxyServer.on("clientError",(r,t)=>{v("warn","proxy client error:",r.message),t.end(`HTTP/1.1 400 Bad Request\r
1
+ import{randomUUID as u}from"node:crypto";import g from"node:http";import H from"node:net";import _ from"node:tls";import{GRIX_PROXY_ROUTE_KEY_HEADER as E,parseConnectAuthority as S,resolveAbsoluteRequestTarget as P,resolveMitmRequestTarget as x,resolveRuntimeRoutedRequestTarget as R}from"../routing/index.js";import{forwardHttpRequest as f}from"../forwarding/index.js";import{CODEX_OPENAI_WIRE_COMPAT_HEADER as w,CODEX_OPENAI_WIRE_COMPAT_MODE as I}from"../compat/index.js";import{handleWebSocketUpgrade as C}from"./websocket-handler.js";import{logProxyRuntimeStderr as m}from"./logger.js";const y=1e4;function M(c){if(!c||!c.startsWith("http://")&&!c.startsWith("https://"))return null;try{return new URL(c).hostname}catch{return null}}function W(c){let t;try{t=new URL(c,"http://grix.internal").pathname}catch{t=c}return t.includes("/messages")||t.includes("/chat/completions")||t.includes("/responses")}function O(c){let t;try{t=new URL(c,"http://grix.internal").pathname}catch{t=c}return t.endsWith("/responses")}function v(c){return c?{routeSource:c.pinned?"pinned":"workspace",routeKey:c.routeKey,...c.sessionId?{routeSessionId:c.sessionId}:{},routeTargetBaseUrl:c.route.targetBaseUrl,...c.route.model?{routeModel:c.route.model}:{}}:{routeSource:"direct"}}class K{captureWriter;certificateAuthority;events;runtimeRoutes;interceptHosts;proxyServer;mitmHttpServer;mitmPlainHttpServer;constructor(t,r,a,n,i=null){this.captureWriter=t,this.certificateAuthority=r,this.events=a,this.runtimeRoutes=n,this.interceptHosts=i,this.proxyServer=g.createServer((o,e)=>{this.handlePlainHttpRequest(o,e)}),this.proxyServer.on("connect",(o,e,s)=>{this.handleConnectRequest(o,e,s)}),this.proxyServer.on("clientError",(o,e)=>{m("warn","proxy client error:",o.message),e.end(`HTTP/1.1 400 Bad Request\r
2
2
  \r
3
- `)}),this.mitmHttpServer=m.createServer((r,t)=>{this.handleMitmHttpRequest(r,t)}),this.mitmHttpServer.on("upgrade",(r,t,a)=>{this.handleMitmUpgrade(r,t,a)}),this.mitmHttpServer.on("clientError",(r,t)=>{v("warn","mitm client error:",r.message),t.end(`HTTP/1.1 400 Bad Request\r
3
+ `)}),this.mitmHttpServer=g.createServer((o,e)=>{this.handleMitmHttpRequest(o,e)}),this.mitmHttpServer.on("upgrade",(o,e,s)=>{this.handleMitmUpgrade(o,e,s)}),this.mitmHttpServer.on("clientError",(o,e)=>{m("warn","mitm client error:",o.message),e.end(`HTTP/1.1 400 Bad Request\r
4
4
  \r
5
- `)}),this.mitmPlainHttpServer=m.createServer((r,t)=>{this.handleMitmHttpRequest(r,t)}),this.mitmPlainHttpServer.on("upgrade",(r,t,a)=>{this.handleMitmUpgrade(r,t,a)}),this.mitmPlainHttpServer.on("clientError",(r,t)=>{v("warn","mitm plain client error:",r.message),t.end(`HTTP/1.1 400 Bad Request\r
5
+ `)}),this.mitmPlainHttpServer=g.createServer((o,e)=>{this.handleMitmHttpRequest(o,e)}),this.mitmPlainHttpServer.on("upgrade",(o,e,s)=>{this.handleMitmUpgrade(o,e,s)}),this.mitmPlainHttpServer.on("clientError",(o,e)=>{m("warn","mitm plain client error:",o.message),e.end(`HTTP/1.1 400 Bad Request\r
6
6
  \r
7
- `)})}async start(e,o="0.0.0.0"){await new Promise((n,i)=>{this.proxyServer.once("error",i),this.proxyServer.listen(e??0,o,()=>{this.proxyServer.off("error",i),n()})});const s=this.proxyServer.address();if(!s||typeof s=="string")throw new Error("Proxy server failed to resolve listen port");return{port:s.port}}async stop(){this.mitmHttpServer.close(),this.mitmPlainHttpServer.close(),await new Promise(e=>{this.proxyServer.close(()=>e())})}async handlePlainHttpRequest(e,o){const s=u();let n=this.runtimeRoutes.resolveRequestRouteInfo(e);if(n&&this.interceptHosts&&!e.headers[P]){const l=I(e.url);l&&!this.interceptHosts.matches(l)&&(n=null)}if(!n&&e.url&&!e.url.startsWith("http://")&&!e.url.startsWith("https://")){o.writeHead(502,{"content-type":"text/plain"}),o.end("Direct HTTP requests without a matched route are not supported on the MITM port. Use the reverse proxy port instead.");return}const{target:i,path:r,headerOverrides:t,modelOverride:a,modelMapOverride:h,passthrough:p}=n?R(e,n.route):{..._(e),headerOverrides:void 0,modelOverride:void 0,modelMapOverride:void 0,passthrough:void 0};this.captureWriter.append({type:"http_request_start",requestId:s,connectionId:s,targetHost:i.host,targetPort:i.port,protocol:i.protocol,method:e.method??"GET",path:r,headers:e.headers}),await g({requestId:s,connectionId:s,target:i,path:r,headerOverrides:t,modelOverride:a,modelMapOverride:h,passthrough:p,request:e,response:o,captureWriter:this.captureWriter,events:this.events,routeDiagnostics:y(n)})}async handleConnectRequest(e,o,s){const n=u();let i=e.url??"",r=443;try{const t=x(e.url??"");if(i=t.host,r=t.port,this.captureWriter.append({type:"connect_start",connectionId:n,targetHost:t.host,targetPort:t.port}),this.interceptHosts&&!this.interceptHosts.matches(t.host)){this.blindTunnel(o,t,s,n);return}o.write(`HTTP/1.1 200 Connection Established\r
7
+ `)})}async start(t,r="0.0.0.0"){await new Promise((n,i)=>{this.proxyServer.once("error",i),this.proxyServer.listen(t??0,r,()=>{this.proxyServer.off("error",i),n()})});const a=this.proxyServer.address();if(!a||typeof a=="string")throw new Error("Proxy server failed to resolve listen port");return{port:a.port}}async stop(){this.mitmHttpServer.close(),this.mitmPlainHttpServer.close(),await new Promise(t=>{this.proxyServer.close(()=>t())})}async handlePlainHttpRequest(t,r){const a=u();let n=this.runtimeRoutes.resolveRequestRouteInfo(t);if(n&&this.interceptHosts&&!t.headers[E]){const l=M(t.url);l&&!this.interceptHosts.matches(l)&&(n=null)}if(!n&&t.url&&!t.url.startsWith("http://")&&!t.url.startsWith("https://")){r.writeHead(502,{"content-type":"text/plain"}),r.end("Direct HTTP requests without a matched route are not supported on the MITM port. Use the reverse proxy port instead.");return}const{target:i,path:o,headerOverrides:e,modelOverride:s,modelMapOverride:p,passthrough:h}=n?R(t,n.route):{...P(t),headerOverrides:void 0,modelOverride:void 0,modelMapOverride:void 0,passthrough:void 0};this.captureWriter.append({type:"http_request_start",requestId:a,connectionId:a,targetHost:i.host,targetPort:i.port,protocol:i.protocol,method:t.method??"GET",path:o,headers:t.headers}),await f({requestId:a,connectionId:a,target:i,path:o,headerOverrides:e,modelOverride:s,modelMapOverride:p,passthrough:h,request:t,response:r,captureWriter:this.captureWriter,events:this.events,routeDiagnostics:v(n)})}async handleConnectRequest(t,r,a){const n=u();let i=t.url??"",o=443;try{const e=S(t.url??"");if(i=e.host,o=e.port,this.captureWriter.append({type:"connect_start",connectionId:n,targetHost:e.host,targetPort:e.port}),this.interceptHosts&&!this.interceptHosts.matches(e.host)){this.blindTunnel(r,e,a,n);return}r.write(`HTTP/1.1 200 Connection Established\r
8
8
  \r
9
- `),s.length>0&&o.unshift(s);const a=p=>{this.captureWriter.append({type:"connect_error",connectionId:n,targetHost:t.host,error:p.message}),this.events.emitConnectError({requestId:n,host:t.host,port:t.port,error:p.message})};o.on("error",a);const h=()=>{const p=o.read(1);if(!p||p.length===0){o.once("readable",h);return}if(o.unshift(p),p[0]===22)this.startTlsMitm({socket:o,target:t,connectionId:n,reportConnectError:a});else{const d=o;d.__grixConnectionId=n,d.__grixProxyTarget={...t,protocol:"http:"},d.on("error",a),this.mitmPlainHttpServer.emit("connection",d)}};h()}catch(t){const a=t instanceof Error?t.message:String(t);this.captureWriter.append({type:"connect_error",connectionId:n,targetHost:i,error:a}),this.events.emitConnectError({requestId:n,host:i,port:r,error:a}),o.end(`HTTP/1.1 502 Bad Gateway\r
9
+ `),a.length>0&&r.unshift(a);const s=h=>{this.captureWriter.append({type:"connect_error",connectionId:n,targetHost:e.host,error:h.message}),this.events.emitConnectError({requestId:n,host:e.host,port:e.port,error:h.message})};r.on("error",s);const p=()=>{const h=r.read(1);if(!h||h.length===0){r.once("readable",p);return}if(r.unshift(h),h[0]===22)this.startTlsMitm({socket:r,target:e,connectionId:n,reportConnectError:s});else{const d=r;d.__grixConnectionId=n,d.__grixProxyTarget={...e,protocol:"http:"},d.on("error",s),this.mitmPlainHttpServer.emit("connection",d)}};p()}catch(e){const s=e instanceof Error?e.message:String(e);this.captureWriter.append({type:"connect_error",connectionId:n,targetHost:i,error:s}),this.events.emitConnectError({requestId:n,host:i,port:o,error:s}),r.end(`HTTP/1.1 502 Bad Gateway\r
10
10
  \r
11
- `)}}blindTunnel(e,o,s,n){const i=H.connect(o.port,o.host);let r=!1,t=!1,a;const h=p=>{r||(r=!0,clearTimeout(a),this.captureWriter.append({type:"connect_error",connectionId:n,targetHost:o.host,error:p.message}),this.events.emitConnectError({requestId:n,host:o.host,port:o.port,error:p.message}),t?e.destroy():e.end(`HTTP/1.1 502 Bad Gateway\r
11
+ `)}}blindTunnel(t,r,a,n){const i=H.connect(r.port,r.host);let o=!1,e=!1,s;const p=h=>{o||(o=!0,clearTimeout(s),this.captureWriter.append({type:"connect_error",connectionId:n,targetHost:r.host,error:h.message}),this.events.emitConnectError({requestId:n,host:r.host,port:r.port,error:h.message}),e?t.destroy():t.end(`HTTP/1.1 502 Bad Gateway\r
12
12
  \r
13
- `),i.destroy())};a=setTimeout(()=>{h(new Error(`blind tunnel connect timeout after ${f}ms`))},f),i.once("connect",()=>{t=!0,clearTimeout(a),e.write(`HTTP/1.1 200 Connection Established\r
13
+ `),i.destroy())};s=setTimeout(()=>{p(new Error(`blind tunnel connect timeout after ${y}ms`))},y),i.once("connect",()=>{e=!0,clearTimeout(s),t.write(`HTTP/1.1 200 Connection Established\r
14
14
  \r
15
- `),s.length>0&&i.write(s),e.pipe(i),i.pipe(e)}),i.on("error",h),e.on("error",h)}async startTlsMitm(e){const{socket:o,target:s,connectionId:n,reportConnectError:i}=e;try{const r=await this.certificateAuthority.getLeafCertificate(s.host),t=new S.TLSSocket(o,{isServer:!0,secureContext:r.secureContext,ALPNProtocols:["http/1.1"]});t.__grixConnectionId=n,t.__grixProxyTarget=s,t.once("secure",()=>{this.mitmHttpServer.emit("connection",t)}),t.on("error",i)}catch(r){i(r instanceof Error?r:new Error(String(r))),o.destroy()}}async handleMitmHttpRequest(e,o){const s=e.socket,n=s.__grixProxyTarget,i=s.__grixConnectionId??u();if(!n){o.writeHead(502),o.end("Missing MITM target");return}const r=u(),t=this.runtimeRoutes.resolveRequestRouteInfo(e),{target:a,path:h,headerOverrides:p,modelOverride:l,modelMapOverride:d,passthrough:T}=w({request:e,fallbackTarget:n,route:t?.route});this.captureWriter.append({type:"http_request_start",requestId:r,connectionId:i,targetHost:a.host,targetPort:a.port,protocol:a.protocol,method:e.method??"GET",path:h,headers:e.headers}),await g({requestId:r,connectionId:i,target:a,path:h,headerOverrides:p,modelOverride:l,modelMapOverride:d,passthrough:T,request:e,response:o,captureWriter:this.captureWriter,events:this.events,routeDiagnostics:y(t)})}handleMitmUpgrade(e,o,s){E(this.captureWriter,this.events,e,o,s)}}export{q as AcpTrafficProxyServer};
15
+ `),a.length>0&&i.write(a),t.pipe(i),i.pipe(t)}),i.on("error",p),t.on("error",p)}async startTlsMitm(t){const{socket:r,target:a,connectionId:n,reportConnectError:i}=t;try{const o=await this.certificateAuthority.getLeafCertificate(a.host),e=new _.TLSSocket(r,{isServer:!0,secureContext:o.secureContext,ALPNProtocols:["http/1.1"]});e.__grixConnectionId=n,e.__grixProxyTarget=a,e.once("secure",()=>{this.mitmHttpServer.emit("connection",e)}),e.on("error",i)}catch(o){i(o instanceof Error?o:new Error(String(o))),r.destroy()}}async handleMitmHttpRequest(t,r){const a=t.socket,n=a.__grixProxyTarget,i=a.__grixConnectionId??u();if(!n){r.writeHead(502),r.end("Missing MITM target");return}const o=u(),e=this.runtimeRoutes.resolveRequestRouteInfo(t,n.host);if(!e){this.captureWriter.append({type:"http_request_start",requestId:o,connectionId:i,targetHost:n.host,targetPort:n.port,protocol:n.protocol,method:t.method??"GET",path:t.url??"",headers:t.headers}),m("error",`intercepted ${n.host} but no relay route is configured; refusing to fall back to the real endpoint (that would silently use the agent's own account). Reconfigure Grix relay for this agent.`),t.resume(),r.writeHead(502,{"content-type":"text/plain"}),r.end("grix relay: intercepted host has no route; refusing silent direct connect");return}const{target:s,path:p,headerOverrides:h,modelOverride:l,modelMapOverride:d,passthrough:T}=x({request:t,fallbackTarget:n,route:e.route});if(e?.route.relayOnly&&!W(p)){this.captureWriter.append({type:"http_request_start",requestId:o,connectionId:i,targetHost:s.host,targetPort:s.port,protocol:s.protocol,method:t.method??"GET",path:p,headers:t.headers}),t.resume(),r.writeHead(404,{"content-type":"text/plain"}),r.end("relay: non-inference endpoint blocked");return}e?.route.codexResponsesToChat&&(t.method??"GET").toUpperCase()==="POST"&&O(p)&&(t.headers[w]=I),this.captureWriter.append({type:"http_request_start",requestId:o,connectionId:i,targetHost:s.host,targetPort:s.port,protocol:s.protocol,method:t.method??"GET",path:p,headers:t.headers}),await f({requestId:o,connectionId:i,target:s,path:p,headerOverrides:h,modelOverride:l,modelMapOverride:d,passthrough:T,request:t,response:r,captureWriter:this.captureWriter,events:this.events,routeDiagnostics:v(e)})}handleMitmUpgrade(t,r,a){C(this.captureWriter,this.events,t,r,a)}}export{K as AcpTrafficProxyServer};
@@ -1,2 +1,2 @@
1
- import{execFile as u}from"node:child_process";import{existsSync as w}from"node:fs";import{join as h,dirname as x,delimiter as m}from"node:path";import{promisify as f}from"node:util";import{log as d}from"../log/logger.js";const g=3e3;function y(t,r){const e=[".cmd",".bat",".exe",""];for(const o of t)if(o)for(const i of e){const n=h(o,`${r}${i}`);if(w(n))return n}return null}let c=null;const v=1e4;function M(){c=null}async function P(){if(c&&Date.now()-c.at<v)return c.dirs;const t=f(u),r=async o=>{try{const{stdout:i}=await t("powershell",["-NoProfile","-NonInteractive","-Command",`[Environment]::GetEnvironmentVariable('Path','${o}')`],{timeout:5e3,encoding:"utf-8",windowsHide:!0});return i.split(";").map(n=>n.trim()).filter(Boolean)}catch(i){return d.warn("cli-probe",`\u8BFB\u53D6\u6CE8\u518C\u8868 ${o} PATH \u5931\u8D25: ${i instanceof Error?i.message:String(i)}`),[]}},e=[...await r("User"),...await r("Machine")];return c={dirs:e,at:Date.now()},e}async function p(t){if(process.platform!=="win32")return null;const r=await P(),e=y(r,t);return e&&$(x(e)),e}function $(t){const r=process.env.PATH??"";r.split(m).some(o=>o.toLowerCase()===t.toLowerCase())||(process.env.PATH=r?`${t}${m}${r}`:t,d.info("cli-probe",`\u5DF2\u5C06 ${t} \u6CE8\u5165\u5F53\u524D\u8FDB\u7A0B PATH\uFF08\u5B89\u88C5\u540E\u81EA\u6108\uFF09`))}async function S(t){const r=f(u),e=process.platform==="win32",o=e?"where":"which";try{const{stdout:i}=await r(o,[t],{timeout:3e3,encoding:"utf-8"}),n=i.trim().split(/\r?\n/).map(s=>s.trim()).filter(Boolean);if(n.length>0){if(e){const s=n.find(l=>/\.(cmd|bat)$/i.test(l));if(s)return s;const a=n.find(l=>/\.exe$/i.test(l));return a||n[0]}return n[0]}return e?await p(t):null}catch{return e?await p(t):null}}async function W(t,r=["--version"],e=g){const o=f(u),i=process.platform==="win32";try{const n=i&&/\s/.test(t)&&!t.startsWith('"')?`"${t}"`:t,{stdout:s,stderr:a}=await o(n,r,{timeout:e,encoding:"utf-8",...i?{shell:!0}:{}});return{version:(s||a||"").trim().split(`
2
- `)[0]?.trim()||null||null}}catch(n){const s=n;return s.killed||s.code==="ETIMEDOUT"?{version:null,error:{code:"version_timeout",message:"version check timed out"}}:{version:null,error:{code:"cli_error",message:s.message??String(n)}}}}export{y as findWindowsExecutableInDirs,W as getCliVersion,M as invalidateWindowsRegistryPathCache,S as resolveCliPath,p as resolveWindowsInstalledCli};
1
+ import{execFile as c}from"node:child_process";import{accessSync as b,constants as A,existsSync as H,statSync as T}from"node:fs";import{join as h,dirname as m,basename as $,delimiter as p}from"node:path";import{promisify as l}from"node:util";import{log as a}from"../log/logger.js";const I=3e3,C=5e3,d="__GRIX_SHELL_PATH__";function x(t,e){const n=[".cmd",".bat",".exe",""];for(const r of t)if(r)for(const i of n){const s=h(r,`${e}${i}`);if(H(s))return s}return null}const M=1e4,P={cache:null,inflight:null},_={cache:null,inflight:null},S={cache:null,inflight:null};let w=0;function z(){w++;for(const t of[P,_,S])t.cache=null,t.inflight=null}function g(t,e){if(t.cache&&Date.now()-t.cache.at<M)return Promise.resolve(t.cache.value);if(t.inflight)return t.inflight;const n=w,r=e().then(i=>(w===n&&(t.cache={value:i,at:Date.now()}),t.inflight===r&&(t.inflight=null),i),i=>{throw t.inflight===r&&(t.inflight=null),i});return t.inflight=r,r}function D(){return g(P,async()=>{const t=l(c),e=async n=>{try{const{stdout:r}=await t("powershell",["-NoProfile","-NonInteractive","-Command",`[Environment]::GetEnvironmentVariable('Path','${n}')`],{timeout:5e3,encoding:"utf-8",windowsHide:!0});return r.split(";").map(i=>i.trim()).filter(Boolean)}catch(r){return a.warn("cli-probe",`\u8BFB\u53D6\u6CE8\u518C\u8868 ${n} PATH \u5931\u8D25: ${r instanceof Error?r.message:String(r)}`),[]}};return[...await e("User"),...await e("Machine")]})}async function F(t){if(process.platform!=="win32")return null;const e=await D(),n=x(e,t)??await E(t);return n&&L(m(n)),n}function O(t){try{return T(t).isFile()?(b(t,A.X_OK),!0):!1}catch{return!1}}function y(t,e){for(const n of t){if(!n)continue;const r=h(n,e);if(O(r))return r}return null}const W=new Set(["sh","bash","zsh","ksh","dash","ash"]);function G(){return g(_,async()=>{const t=process.env.SHELL||"/bin/sh";if(!W.has($(t)))return a.info("cli-probe",`\u767B\u5F55 shell ${t} \u4E0D\u652F\u6301 -lic\uFF0C\u8DF3\u8FC7 shell PATH \u8BFB\u53D6`),[];const e=l(c);try{const n=e(t,["-lic",`printf '\\n${d}%s\\n' "$PATH"`],{timeout:C,killSignal:"SIGKILL",encoding:"utf-8"});n.child?.stdin?.end();const{stdout:r}=await n,i=r.split(/\r?\n/).reverse().find(s=>s.startsWith(d));return i?i.slice(d.length).split(p).map(s=>s.trim()).filter(Boolean):[]}catch(n){return a.warn("cli-probe",`\u8BFB\u53D6\u767B\u5F55 shell PATH \u5931\u8D25: ${n instanceof Error?n.message:String(n)}`),[]}})}function k(){return g(S,async()=>{const t=l(c),e=process.platform==="win32";try{const{stdout:n}=await t(e?"cmd.exe":"npm",e?["/c","npm","prefix","-g"]:["prefix","-g"],{timeout:5e3,killSignal:"SIGKILL",encoding:"utf-8",...e?{windowsHide:!0}:{}});return n.trim()||null}catch{return null}})}async function E(t){const e=await k();return e?process.platform==="win32"?x([e],t):y([h(e,"bin"),e],t):null}async function N(t){if(process.platform==="win32")return null;const e=await G(),n=y(e,t)??await E(t);return a.info("cli-probe",`\u767B\u5F55 shell PATH / npm \u5168\u5C40 bin \u515C\u5E95\u67E5\u627E ${t}: \u7ED3\u679C=${n??"\u672A\u627E\u5230"}`),n&&L(m(n)),n}function L(t){const e=process.env.PATH??"",n=process.platform==="win32"?(r,i)=>r.toLowerCase()===i.toLowerCase():(r,i)=>r===i;e.split(p).some(r=>n(r,t))||(process.env.PATH=e?`${e}${p}${t}`:t,a.info("cli-probe",`\u5DF2\u5C06 ${t} \u8FFD\u52A0\u5230\u5F53\u524D\u8FDB\u7A0B PATH\uFF08\u5B89\u88C5\u540E\u81EA\u6108\uFF09`))}async function q(t){const e=l(c),n=process.platform==="win32",r=n?"where":"which";try{const{stdout:i}=await e(r,[t],{timeout:3e3,encoding:"utf-8"}),s=i.trim().split(/\r?\n/).map(o=>o.trim()).filter(Boolean);if(s.length>0){if(n){const o=s.find(f=>/\.(cmd|bat)$/i.test(f));if(o)return o;const u=s.find(f=>/\.exe$/i.test(f));return u||s[0]}return s[0]}return await v(t)}catch{return await v(t)}}function v(t){return process.platform==="win32"?F(t):N(t)}async function B(t,e=["--version"],n=I){const r=l(c),i=process.platform==="win32";try{const s=i&&/\s/.test(t)&&!t.startsWith('"')?`"${t}"`:t,{stdout:o,stderr:u}=await r(s,e,{timeout:n,encoding:"utf-8",...i?{shell:!0}:{}});return{version:(o||u||"").trim().split(`
2
+ `)[0]?.trim()||null||null}}catch(s){const o=s;return o.killed||o.code==="ETIMEDOUT"?{version:null,error:{code:"version_timeout",message:"version check timed out"}}:{version:null,error:{code:"cli_error",message:o.message??String(s)}}}}export{y as findPosixExecutableInDirs,x as findWindowsExecutableInDirs,B as getCliVersion,z as invalidateCliPathCache,q as resolveCliPath,N as resolvePosixInstalledCli,E as resolveViaNpmGlobalBin,F as resolveWindowsInstalledCli};
package/dist/grix.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import h from"node:path";import{writeFileSync as A}from"node:fs";import{Manager as _}from"./manager.js";import{ensureGrixDirs as N,initLogger as G,log as n,installProcessLogRotation as M,setConsoleOutput as j}from"./core/log/index.js";import{HealthServer as B,bindPortOrFail as $}from"./core/runtime/index.js";import{writePidFile as V,removePidFile as w,readDaemonPid as W}from"./core/runtime/index.js";import{resolveRuntimePaths as E}from"./core/config/index.js";import{ServiceManager as X}from"./service/service-manager.js";import{killProcessesByCommandLine as J,isWindowsElevated as K}from"./service/process-control.js";import{acquireDaemonLock as q,isLockHolderSameProcess as z,readDaemonLock as Q,releaseDaemonLock as S}from"./runtime/daemon-lock.js";import{writeDaemonStatus as k,removeDaemonStatus as Y}from"./runtime/service-state.js";import{AdminServer as Z,generateToken as ee,writeTokenFile as te}from"./core/admin/index.js";import{initSentry as oe,closeSentry as y,reportFatal as v}from"./core/observability/sentry.js";import{initProxyManager as re,getProxyManager as p}from"./core/proxy/index.js";import{resolveClientVersion as ae}from"./core/util/client-version.js";import{parseCliArgs as ne}from"./core/util/cli-args.js";const{command:u,flags:d,unknownFlags:P}=ne(process.argv.slice(2));if(P.length>0&&(console.error(`Unknown option${P.length>1?"s":""}: ${P.map(t=>`--${t}`).join(", ")}
3
- Run \`grix-connector --help\` to see the supported commands and options.`),process.exit(1)),d.version&&(console.log(ae()),process.exit(0)),d.help&&(console.log(`grix-connector \u2014 Unified AI Agent Bridge
2
+ import x from"node:path";import{writeFileSync as C}from"node:fs";import{Manager as V}from"./manager.js";import{ensureGrixDirs as X,initLogger as J,log as o,installProcessLogRotation as K,setConsoleOutput as q}from"./core/log/index.js";import{HealthServer as Y,bindPortOrFail as U}from"./core/runtime/index.js";import{writePidFile as z,removePidFile as S,readDaemonPid as Q}from"./core/runtime/index.js";import{resolveRuntimePaths as v}from"./core/config/index.js";import{ServiceManager as Z}from"./service/service-manager.js";import{killProcessesByCommandLine as ee,isWindowsElevated as te}from"./service/process-control.js";import{acquireDaemonLock as oe,isLockHolderSameProcess as re,readDaemonLock as ne,releaseDaemonLock as R}from"./runtime/daemon-lock.js";import{writeDaemonStatus as I,removeDaemonStatus as ae}from"./runtime/service-state.js";import{AdminServer as se,generateToken as ie,writeTokenFile as ce}from"./core/admin/index.js";import{initSentry as le,closeSentry as b,reportFatal as T}from"./core/observability/sentry.js";import{initProxyManager as de,getProxyManager as m,relayHostForClientType as pe}from"./core/proxy/index.js";import{stopProxyIfNobodyNeedsIt as me,disableAllRelayAndRestart as ge,setOneAgentRelayAndRestart as ue}from"./core/proxy/relay-orchestration.js";import{resolveClientVersion as fe}from"./core/util/client-version.js";import{parseCliArgs as he}from"./core/util/cli-args.js";const{command:u,flags:g,unknownFlags:F}=he(process.argv.slice(2));if(F.length>0&&(console.error(`Unknown option${F.length>1?"s":""}: ${F.map(t=>`--${t}`).join(", ")}
3
+ Run \`grix-connector --help\` to see the supported commands and options.`),process.exit(1)),g.version&&(console.log(fe()),process.exit(0)),g.help&&(console.log(`grix-connector \u2014 Unified AI Agent Bridge
4
4
 
5
5
  Usage: grix-connector <command> [options]
6
6
 
@@ -34,8 +34,8 @@ Examples:
34
34
  grix-connector start # Start as system service
35
35
  grix-connector status # Check service status
36
36
  grix-connector restart # Restart the service
37
- `),process.exit(0)),u==="reload"){const t=W();t||(console.error("reload failed: daemon is not running (no pid file)"),process.exit(1));try{process.kill(t,0)}catch{console.error(`reload failed: daemon process ${t} is not running (stale pid file)`),process.exit(1)}{const c=Q(E().daemonLockFile);c&&c.pid===t&&!z(c)&&(console.error(`reload failed: pid ${t} has been reused by another process (stale pid file)`),process.exit(1))}try{process.kill(t,"SIGHUP"),console.log(JSON.stringify({ok:!0,signaled:t},null,2)),process.exit(0)}catch(c){console.error(`reload failed: ${c instanceof Error?c.message:c}`),process.exit(1)}}const T=["start","stop","restart","status"];if(u&&T.includes(u)){process.platform==="win32"&&["start","stop","restart"].includes(u)&&!K()&&console.warn(`Warning: Not running as administrator. Task Scheduler registration is skipped;
37
+ `),process.exit(0)),u==="reload"){const t=Q();t||(console.error("reload failed: daemon is not running (no pid file)"),process.exit(1));try{process.kill(t,0)}catch{console.error(`reload failed: daemon process ${t} is not running (stale pid file)`),process.exit(1)}{const d=ne(v().daemonLockFile);d&&d.pid===t&&!re(d)&&(console.error(`reload failed: pid ${t} has been reused by another process (stale pid file)`),process.exit(1))}try{process.kill(t,"SIGHUP"),console.log(JSON.stringify({ok:!0,signaled:t},null,2)),process.exit(0)}catch(d){console.error(`reload failed: ${d instanceof Error?d.message:d}`),process.exit(1)}}const N=["start","stop","restart","status"];if(u&&N.includes(u)){process.platform==="win32"&&["start","stop","restart"].includes(u)&&!te()&&console.warn(`Warning: Not running as administrator. Task Scheduler registration is skipped;
38
38
  using Startup folder auto-start instead. For full Task Scheduler integration,
39
- right-click the terminal and select "Run as administrator".`);const t=E(),c=d["config-dir"]??(d.profile?h.join(t.configDir,d.profile):void 0),g=h.resolve(process.argv[1]||`${t.rootDir}/dist/grix.js`),r=new X({cliPath:g,nodePath:process.execPath});try{let s;switch(u){case"start":(await r.status({rootDir:t.rootDir})).installed?s=await r.start({rootDir:t.rootDir}):s=await r.install({rootDir:t.rootDir,configDir:c});break;case"stop":s=await r.stop({rootDir:t.rootDir});break;case"restart":(await r.status({rootDir:t.rootDir})).installed?s=await r.restart({rootDir:t.rootDir}):s=await r.install({rootDir:t.rootDir,configDir:c});break;case"status":s=await r.status({rootDir:t.rootDir});break}console.log(JSON.stringify(s,null,2)),process.exit(0)}catch(s){console.error(`${u} failed: ${s instanceof Error?s.message:s}`),process.exit(1)}}else u&&(console.error(`Unknown command: ${u}
40
- Valid commands: ${T.join(", ")}`),process.exit(1));const a=E(),ie=d["config-dir"]??(d.profile?`${a.configDir}/${d.profile}`:void 0),i=new _,x=new B,O=ee(),f=new Z(O);let R=!1;async function C(t){process.stderr.write(t.message+`
41
- `),n.error("main",t.message.replace(/\n/g," \u2014 ")),await k(a.daemonStatusFile,{state:"failed",pid:process.pid,updated_at:Date.now(),reason:`port_bind_${t.kind}:${t.label}:${t.port}`}).catch(()=>{}),await y(),S(a.daemonLockFile).catch(()=>{}),w(),process.exit(1)}async function D(t){if(R)return;R=!0,n.info("main",`Received ${t}, shutting down...`),x.markShuttingDown();const c=setTimeout(()=>{n.error("main","Shutdown timed out, forcing exit"),S(a.daemonLockFile).catch(()=>{}),w(),process.exit(2)},1e4);try{await i.stop(),await p()?.stop().catch(()=>{}),await f.stop(),await x.stop(),await y(),await S(a.daemonLockFile),await Y(a.daemonStatusFile).catch(()=>{}),clearTimeout(c),w(),n.info("main","Shutdown complete"),process.exit(0)}catch(g){n.error("main",`Shutdown error: ${g}`),S(a.daemonLockFile).catch(()=>{}),w(),process.exit(2)}}async function se(){N(),G(),await oe(),M(a.stdoutLogFile,a.stderrLogFile),j(!1),process.platform==="win32"&&await J("GrixConnectorDaemon",{platform:"win32"});try{await q(a.daemonLockFile,a.rootDir)}catch(e){console.error(e instanceof Error?e.message:e),process.exit(1)}V(),n.info("main",`grix-connector starting (PID ${process.pid})`),await k(a.daemonStatusFile,{state:"starting",pid:process.pid,updated_at:Date.now()});const t=parseInt(d["health-port"]??process.env.GRIX_HEALTH_PORT??"19579",10);{const e=await $({label:"health",port:t,envVar:"GRIX_HEALTH_PORT",cliFlag:"health-port",start:o=>x.start(o)});e&&await C(e)}const c=h.join(a.dataDir,"health-port");A(c,String(t),"utf-8"),process.on("SIGINT",()=>D("SIGINT")),process.on("SIGTERM",()=>D("SIGTERM")),process.on("SIGHUP",()=>{R||(n.info("main","Received SIGHUP, reloading config..."),i.reload().then(e=>n.info("main",`reload done: ${JSON.stringify(e)}`)).catch(e=>n.error("main",`reload failed: ${e instanceof Error?e.message:e}`)))});let g="",r=0,s;process.on("uncaughtException",e=>{const o=e instanceof Error?e.stack??e.message:String(e);o===g?(r++,(r<=3||r%100===0)&&n.error("main",`Uncaught exception (x${r}): ${o}`)):(r>3&&n.error("main",`Previous exception repeated ${r} times total`),g=o,r=1,n.error("main",`Uncaught exception: ${e instanceof Error?e.stack:e}`),s||(s=setTimeout(()=>{r>3&&n.error("main",`Previous exception repeated ${r} times total`),g="",r=0,s=void 0},1e4).unref())),!L(e)&&(v(e,"uncaughtException"),D("uncaughtException"))}),process.on("unhandledRejection",e=>{n.error("main",`Unhandled rejection: ${e}`),!L(e)&&(v(e,"unhandledRejection"),D("unhandledRejection"))}),await re(a.dataDir).clearRuntimeState(),n.info("main",'mitm proxy disabled by default (enable online: PUT /api/proxy/enabled {"enabled":true}; not persisted, resets to off on restart)'),x.setStatusProvider(()=>i.getAgentsStatus()),await i.start(ie);const I=parseInt(d["admin-port"]??process.env.GRIX_ADMIN_PORT??"19580",10);if(f.setAgentHandler({list:()=>i.getAgentsStatus(),add:e=>i.addAgent(e),remove:e=>i.removeAgent(e),restart:e=>i.restartAgent(e),reload:()=>i.reload()}),f.setUpgradeHandler({check:()=>i.checkUpgrade(),trigger:()=>i.triggerUpgrade()}),f.setProbeHandler({probeAll:e=>i.probeAll(e),probeOne:(e,o)=>i.probeOne(e,o)}),f.setInstallHandler({listInstallable:()=>i.listInstallable(),installAgent:e=>i.installAgent(e),getInstallProgress:e=>i.getInstallProgress(e)}),p()){const e=()=>{const o=p();return{enabled:o.isInjectionEnabled(),runtime:o.getRuntimeInfo(),config:o.getConfigSnapshot()}};f.setProxyHandler({status:()=>e(),enable:async()=>(await p().enable(),e()),disable:async()=>(await p().disable(),e()),setRoute:async(o,l)=>{const m=l,F=p();return F.setRoute({routeKey:o,targetBaseUrl:m.targetBaseUrl,...m.headers?{headers:m.headers}:{},...m.model?{model:m.model}:{},...m.modelMap?{modelMap:m.modelMap}:{},...m.passthrough?{passthrough:!0}:{}}),await F.persistConfig(),e()},deleteRoute:async o=>{const l=p();l.deleteRoute(o),await l.persistConfig()},setDefaultRoute:async o=>{const l=p();return l.setDefaultRouteKey(o),await l.persistConfig(),e()},setInterceptHosts:async o=>{const l=p();return l.setInterceptHosts(o),await l.persistConfig(),e()}})}{const e=await $({label:"admin",port:I,envVar:"GRIX_ADMIN_PORT",cliFlag:"admin-port",start:o=>f.start(o)});e&&await C(e)}const H=h.join(a.dataDir,"admin-token"),U=h.join(a.dataDir,"admin-port");te(H,O),A(U,String(I),"utf-8"),await k(a.daemonStatusFile,{state:"running",pid:process.pid,updated_at:Date.now()}),process.send&&process.send("ready"),n.info("main","grix-connector ready")}se().catch(async t=>{n.error("main",`Fatal: ${t}`),v(t,"startup"),await y(),S(a.daemonLockFile).catch(()=>{}),w(),process.exit(1)});const ce=new Set(["ECONNRESET","ECONNREFUSED","ETIMEDOUT","EPIPE","EAI_AGAIN","ENOTFOUND","EHOSTUNREACH","ENETUNREACH","EIO"]);function L(t){return t instanceof Error&&"code"in t?ce.has(t.code):!1}
39
+ right-click the terminal and select "Run as administrator".`);const t=v(),d=g["config-dir"]??(g.profile?x.join(t.configDir,g.profile):void 0),f=x.resolve(process.argv[1]||`${t.rootDir}/dist/grix.js`),s=new Z({cliPath:f,nodePath:process.execPath});try{let l;switch(u){case"start":(await s.status({rootDir:t.rootDir})).installed?l=await s.start({rootDir:t.rootDir}):l=await s.install({rootDir:t.rootDir,configDir:d});break;case"stop":l=await s.stop({rootDir:t.rootDir});break;case"restart":(await s.status({rootDir:t.rootDir})).installed?l=await s.restart({rootDir:t.rootDir}):l=await s.install({rootDir:t.rootDir,configDir:d});break;case"status":l=await s.status({rootDir:t.rootDir});break}console.log(JSON.stringify(l,null,2)),process.exit(0)}catch(l){console.error(`${u} failed: ${l instanceof Error?l.message:l}`),process.exit(1)}}else u&&(console.error(`Unknown command: ${u}
40
+ Valid commands: ${N.join(", ")}`),process.exit(1));const i=v(),ye=g["config-dir"]??(g.profile?`${i.configDir}/${g.profile}`:void 0),n=new V,D=new Y,H=ie(),h=new se(H);let $=!1;async function L(t){process.stderr.write(t.message+`
41
+ `),o.error("main",t.message.replace(/\n/g," \u2014 ")),await I(i.daemonStatusFile,{state:"failed",pid:process.pid,updated_at:Date.now(),reason:`port_bind_${t.kind}:${t.label}:${t.port}`}).catch(()=>{}),await b(),R(i.daemonLockFile).catch(()=>{}),S(),process.exit(1)}async function A(t){if($)return;$=!0,o.info("main",`Received ${t}, shutting down...`),D.markShuttingDown();const d=setTimeout(()=>{o.error("main","Shutdown timed out, forcing exit"),R(i.daemonLockFile).catch(()=>{}),S(),process.exit(2)},1e4);try{await n.stop(),await m()?.stop().catch(()=>{}),await h.stop(),await D.stop(),await b(),await R(i.daemonLockFile),await ae(i.daemonStatusFile).catch(()=>{}),clearTimeout(d),S(),o.info("main","Shutdown complete"),process.exit(0)}catch(f){o.error("main",`Shutdown error: ${f}`),R(i.daemonLockFile).catch(()=>{}),S(),process.exit(2)}}async function we(){X(),J(),await le(),K(i.stdoutLogFile,i.stderrLogFile),q(!1),process.platform==="win32"&&await ee("GrixConnectorDaemon",{platform:"win32"});try{await oe(i.daemonLockFile,i.rootDir)}catch(e){console.error(e instanceof Error?e.message:e),process.exit(1)}z(),o.info("main",`grix-connector starting (PID ${process.pid})`),await I(i.daemonStatusFile,{state:"starting",pid:process.pid,updated_at:Date.now()});const t=parseInt(g["health-port"]??process.env.GRIX_HEALTH_PORT??"19579",10);{const e=await U({label:"health",port:t,envVar:"GRIX_HEALTH_PORT",cliFlag:"health-port",start:c=>D.start(c)});e&&await L(e)}const d=x.join(i.dataDir,"health-port");C(d,String(t),"utf-8"),process.on("SIGINT",()=>A("SIGINT")),process.on("SIGTERM",()=>A("SIGTERM")),process.on("SIGHUP",()=>{$||(o.info("main","Received SIGHUP, reloading config..."),n.reload().then(e=>o.info("main",`reload done: ${JSON.stringify(e)}`)).catch(e=>o.error("main",`reload failed: ${e instanceof Error?e.message:e}`)))});let f="",s=0,l;process.on("uncaughtException",e=>{const c=e instanceof Error?e.stack??e.message:String(e);c===f?(s++,(s<=3||s%100===0)&&o.error("main",`Uncaught exception (x${s}): ${c}`)):(s>3&&o.error("main",`Previous exception repeated ${s} times total`),f=c,s=1,o.error("main",`Uncaught exception: ${e instanceof Error?e.stack:e}`),l||(l=setTimeout(()=>{s>3&&o.error("main",`Previous exception repeated ${s} times total`),f="",s=0,l=void 0},1e4).unref())),!_(e)&&(T(e,"uncaughtException"),A("uncaughtException"))}),process.on("unhandledRejection",e=>{o.error("main",`Unhandled rejection: ${e}`),!_(e)&&(T(e,"unhandledRejection"),A("unhandledRejection"))});const w=de(i.dataDir);await w.clearRuntimeState();const P=w.getRelayAgents();if(P.length>0)try{await w.startIfAnyRelayAgent(),o.info("main",`mitm proxy started for relay agents: ${P.join(", ")} (per-agent; others stay direct)`)}catch(e){o.error("main",`mitm proxy FAILED to start (relay agents: ${P.join(", ")}): ${e}. These agents will REFUSE to start (they must not silently fall back to your own account). Fix the proxy or turn relay off for them. Other agents are unaffected.`)}else o.info("main",'mitm proxy idle (no agent has Grix relay enabled; enable: PUT /api/proxy/agents/<name>/enabled {"enabled":true})');D.setStatusProvider(()=>n.getAgentsStatus()),await n.start(ye);const O=parseInt(g["admin-port"]??process.env.GRIX_ADMIN_PORT??"19580",10);if(h.setAgentHandler({list:()=>n.getAgentsStatus(),add:e=>n.addAgent(e),remove:e=>n.removeAgent(e),restart:e=>n.restartAgent(e),reload:()=>n.reload()}),h.setUpgradeHandler({check:()=>n.checkUpgrade(),trigger:()=>n.triggerUpgrade()}),h.setProbeHandler({probeAll:e=>n.probeAll(e),probeOne:(e,c)=>n.probeOne(e,c)}),h.setInstallHandler({listInstallable:()=>n.listInstallable(),installAgent:e=>n.installAgent(e),getInstallProgress:e=>n.getInstallProgress(e)}),m()){const e=async()=>{const r=m();r&&await me(r,n)&&o.info("main","no agent uses Grix relay anymore; mitm proxy stopped")};n.setRelayEnvSettledHandler(()=>{e().catch(r=>{o.warn("main",`failed to stop idle mitm proxy: ${r}`)})});const c=()=>{const r=m(),a=r.getRelayAgents(),p=r.getDegradedRelayAgents();return{enabled:a.length>0,relayAgents:a,staleRelayAgents:n.getAgentsWithStaleRelayEnv(),degradedRelayAgents:p,runtime:r.getRuntimeInfo(),config:r.getConfigSnapshot()}};h.setProxyHandler({status:()=>c(),setAgentRelay:async(r,a)=>{const p=m();let y;if(a){const k=n.getAgentsStatus().find(W=>W.name===r);if(!k)throw Object.assign(new Error(`Agent "${r}" not found`),{code:"NOT_FOUND"});if(y=pe(k.clientType),!y)throw Object.assign(new Error(`client type "${k.clientType??""}" does not support Grix relay`),{code:"UNSUPPORTED_CLIENT_TYPE"})}const{restarted:M,busy:B}=await ue(p,n,{info:E=>o.info("main",E),warn:E=>o.warn("main",E)},{agentName:r,enabled:a,relayHost:y});return{...c(),restarted:M,busy:B}},disableAll:async()=>{const r=m();return await ge(r,n,{info:a=>o.info("main",a),warn:a=>o.warn("main",a)}),c()},setRoute:async(r,a)=>{const p=a,y=m();return y.setRoute({routeKey:r,targetBaseUrl:p.targetBaseUrl,...p.headers?{headers:p.headers}:{},...p.model?{model:p.model}:{},...p.modelMap?{modelMap:p.modelMap}:{},...p.passthrough?{passthrough:!0}:{},...p.relayOnly?{relayOnly:!0}:{},...p.codexResponsesToChat?{codexResponsesToChat:!0}:{}}),await y.persistConfig(),c()},deleteRoute:async r=>{const a=m();a.deleteRoute(r),await a.persistConfig()},setDefaultRoute:async r=>{const a=m();return a.setDefaultRouteKey(r),await a.persistConfig(),c()},setInterceptHosts:async r=>{const a=m();return a.setInterceptHosts(r),await a.persistConfig(),c()}})}{const e=await U({label:"admin",port:O,envVar:"GRIX_ADMIN_PORT",cliFlag:"admin-port",start:c=>h.start(c)});e&&await L(e)}const G=x.join(i.dataDir,"admin-token"),j=x.join(i.dataDir,"admin-port");ce(G,H),C(j,String(O),"utf-8"),await I(i.daemonStatusFile,{state:"running",pid:process.pid,updated_at:Date.now()}),process.send&&process.send("ready"),o.info("main","grix-connector ready")}we().catch(async t=>{o.error("main",`Fatal: ${t}`),T(t,"startup"),await b(),R(i.daemonLockFile).catch(()=>{}),S(),process.exit(1)});const xe=new Set(["ECONNRESET","ECONNREFUSED","ETIMEDOUT","EPIPE","EAI_AGAIN","ENOTFOUND","EHOSTUNREACH","ENETUNREACH","EIO"]);function _(t){return t instanceof Error&&"code"in t?xe.has(t.code):!1}