grix-connector 2.1.4 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -147,6 +147,37 @@ grix-connector restart # Restart the service
147
147
  grix-connector status # Check service status
148
148
  ```
149
149
 
150
+ ### Ports
151
+
152
+ The daemon binds two local loopback ports (127.0.0.1 only):
153
+
154
+ | Purpose | Default | Override (env) | Override (CLI) |
155
+ |---|---|---|---|
156
+ | Health check (`/healthz`) | `19579` | `GRIX_HEALTH_PORT` | `--health-port <port>` |
157
+ | Admin API (used by the local CLI) | `19580` | `GRIX_ADMIN_PORT` | `--admin-port <port>` |
158
+
159
+ If a port is already in use, the daemon refuses to start and writes a clear message to `~/.grix/service/daemon.err.log` and the main log, and marks `~/.grix/daemon-status.json` as `state: "failed"` with a `reason` like `port_bind_in_use:health:19579`.
160
+
161
+ To pick different ports:
162
+
163
+ ```bash
164
+ # via environment
165
+ GRIX_HEALTH_PORT=29579 GRIX_ADMIN_PORT=29580 grix-connector restart
166
+
167
+ # or via CLI flags (when running the daemon directly)
168
+ grix-connector --health-port 29579 --admin-port 29580
169
+ ```
170
+
171
+ To find what is occupying a port:
172
+
173
+ ```bash
174
+ # macOS / Linux
175
+ lsof -nP -iTCP:19579 -sTCP:LISTEN
176
+
177
+ # Windows (PowerShell or cmd)
178
+ netstat -ano | findstr :19579
179
+ ```
180
+
150
181
  ## OpenClaw Plugin
151
182
 
152
183
  grix-connector can also be installed as an [OpenClaw](https://openclaw.io) plugin, providing a Grix channel transport with admin tools and operator CLI.
@@ -1 +1 @@
1
- import{HealthServer as i}from"./health.js";import{writePidFile as t,removePidFile as l}from"./pidfile.js";export{i as HealthServer,l as removePidFile,t as writePidFile};
1
+ import{HealthServer as o}from"./health.js";import{writePidFile as t,removePidFile as l}from"./pidfile.js";import{bindPortOrFail as d}from"./port-bind.js";export{o as HealthServer,d as bindPortOrFail,l as removePidFile,t as writePidFile};
@@ -0,0 +1,5 @@
1
+ async function l(n){try{return await n.start(n.port),null}catch(e){const r=i(e);if(r==="EADDRINUSE")return{kind:"in_use",port:n.port,label:n.label,message:`[grix-connector] ${n.label} \u7AEF\u53E3 ${n.port} \u5DF2\u88AB\u5360\u7528\uFF0Cdaemon \u65E0\u6CD5\u542F\u52A8\u3002
2
+ \u8BF7\u91CA\u653E\u8BE5\u7AEF\u53E3\uFF0C\u6216\u901A\u8FC7\u73AF\u5883\u53D8\u91CF ${n.envVar} / \u547D\u4EE4\u884C\u53C2\u6570 --${n.cliFlag} \u6307\u5B9A\u5176\u4ED6\u7AEF\u53E3\u540E\u91CD\u65B0\u542F\u52A8\u3002
3
+ \u67E5\u5360\u7528\u8FDB\u7A0B\u793A\u4F8B\uFF1Alsof -nP -iTCP:${n.port} -sTCP:LISTEN (Windows: netstat -ano | findstr :${n.port})`};if(r==="EACCES")return{kind:"no_permission",port:n.port,label:n.label,message:`[grix-connector] \u5F53\u524D\u7528\u6237\u65E0\u6743\u76D1\u542C ${n.label} \u7AEF\u53E3 ${n.port}\uFF08EACCES\uFF0C1024 \u4EE5\u4E0B\u7AEF\u53E3\u901A\u5E38\u9700\u8981\u7279\u6743\uFF09\u3002
4
+ \u8BF7\u6539\u7528 1024 \u4EE5\u4E0A\u7684\u7AEF\u53E3\uFF1A\u73AF\u5883\u53D8\u91CF ${n.envVar} \u6216\u547D\u4EE4\u884C\u53C2\u6570 --${n.cliFlag}\u3002`};const a=e instanceof Error?e.stack??e.message:String(e);return{kind:"other",port:n.port,label:n.label,message:`[grix-connector] ${n.label} \u7AEF\u53E3 ${n.port} \u7ED1\u5B9A\u5931\u8D25\uFF1A${a}
5
+ \u53EF\u901A\u8FC7\u73AF\u5883\u53D8\u91CF ${n.envVar} \u6216\u547D\u4EE4\u884C\u53C2\u6570 --${n.cliFlag} \u66F4\u6362\u7AEF\u53E3\u540E\u91CD\u8BD5\u3002`}}}function i(n){if(n&&typeof n=="object"&&"code"in n){const e=n.code;return typeof e=="string"?e:void 0}}export{l as bindPortOrFail};
package/dist/grix.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import g from"node:path";import{writeFileSync as x}from"node:fs";import{Manager as $}from"./manager.js";import{ensureGrixDirs as L,initLogger as N,log as n,installProcessLogRotation as b,setConsoleOutput as C}from"./core/log/index.js";import{HealthServer as U}from"./core/runtime/index.js";import{writePidFile as H,removePidFile as h}from"./core/runtime/index.js";import{resolveRuntimePaths as v}from"./core/config/index.js";import{ServiceManager as j}from"./service/service-manager.js";import{killProcessesByCommandLine as _,isWindowsElevated as G}from"./service/process-control.js";import{acquireDaemonLock as W,releaseDaemonLock as w}from"./runtime/daemon-lock.js";import{writeDaemonStatus as k,removeDaemonStatus as M}from"./runtime/service-state.js";import{AdminServer as B,generateToken as V,writeTokenFile as X}from"./core/admin/index.js";import{initSentry as q,closeSentry as P,reportFatal as E}from"./core/observability/sentry.js";const c=process.argv.slice(2),A=[],s={};for(let t=0;t<c.length;t++)c[t].startsWith("--")&&c[t+1]&&!c[t+1].startsWith("--")?(s[c[t].slice(2)]=c[t+1],t++):c[t].startsWith("--")?s[c[t].slice(2)]="true":A.push(c[t]);s.help&&(console.log(`grix-connector \u2014 Unified AI Agent Bridge
2
+ import g from"node:path";import{writeFileSync as v}from"node:fs";import{Manager as $}from"./manager.js";import{ensureGrixDirs as L,initLogger as N,log as n,installProcessLogRotation as C,setConsoleOutput as U}from"./core/log/index.js";import{HealthServer as H,bindPortOrFail as P}from"./core/runtime/index.js";import{writePidFile as G,removePidFile as f}from"./core/runtime/index.js";import{resolveRuntimePaths as R}from"./core/config/index.js";import{ServiceManager as j}from"./service/service-manager.js";import{killProcessesByCommandLine as M,isWindowsElevated as W}from"./service/process-control.js";import{acquireDaemonLock as B,releaseDaemonLock as h}from"./runtime/daemon-lock.js";import{writeDaemonStatus as E,removeDaemonStatus as X}from"./runtime/service-state.js";import{AdminServer as V,generateToken as q,writeTokenFile as J}from"./core/admin/index.js";import{initSentry as K,closeSentry as k,reportFatal as x}from"./core/observability/sentry.js";const l=process.argv.slice(2),A=[],s={};for(let t=0;t<l.length;t++)l[t].startsWith("--")&&l[t+1]&&!l[t+1].startsWith("--")?(s[l[t].slice(2)]=l[t+1],t++):l[t].startsWith("--")?s[l[t].slice(2)]="true":A.push(l[t]);s.help&&(console.log(`grix-connector \u2014 Unified AI Agent Bridge
3
3
 
4
4
  Usage: grix-connector <command> [options]
5
5
 
@@ -18,14 +18,19 @@ Options:
18
18
 
19
19
  Platform services:
20
20
  macOS: launchd (LaunchAgent)
21
- Linux: systemd --user
21
+ Linux: systemd --user (auto-falls back to a detached daemon on systems
22
+ without a user bus \u2014 WSL, slim containers, or
23
+ GRIX_FORCE_BARE_DAEMON=1; the fallback also self-registers an
24
+ autostart snippet in ~/.profile so the daemon comes back on
25
+ every new login shell)
22
26
  Windows: Task Scheduler (hidden WScript launcher)
23
27
 
24
28
  Examples:
25
29
  grix-connector start # Start as system service
26
30
  grix-connector status # Check service status
27
31
  grix-connector restart # Restart the service
28
- `),process.exit(0));const d=A[0],I=["start","stop","restart","status"];if(d&&I.includes(d)){process.platform==="win32"&&["start","stop","restart"].includes(d)&&!G()&&console.warn(`Warning: Not running as administrator. Task Scheduler registration is skipped;
32
+ `),process.exit(0));const m=A[0],F=["start","stop","restart","status"];if(m&&F.includes(m)){process.platform==="win32"&&["start","stop","restart"].includes(m)&&!W()&&console.warn(`Warning: Not running as administrator. Task Scheduler registration is skipped;
29
33
  using Startup folder auto-start instead. For full Task Scheduler integration,
30
- right-click the terminal and select "Run as administrator".`);const t=v(),m=s["config-dir"]??(s.profile?g.join(t.configDir,s.profile):void 0),l=g.resolve(process.argv[1]||`${t.rootDir}/dist/grix.js`),r=new j({cliPath:l,nodePath:process.execPath});try{let o;switch(d){case"start":(await r.status({rootDir:t.rootDir})).installed?o=await r.start({rootDir:t.rootDir}):o=await r.install({rootDir:t.rootDir,configDir:m});break;case"stop":o=await r.stop({rootDir:t.rootDir});break;case"restart":(await r.status({rootDir:t.rootDir})).installed?o=await r.restart({rootDir:t.rootDir}):o=await r.install({rootDir:t.rootDir,configDir:m});break;case"status":o=await r.status({rootDir:t.rootDir});break}console.log(JSON.stringify(o,null,2)),process.exit(0)}catch(o){console.error(`${d} failed: ${o instanceof Error?o.message:o}`),process.exit(1)}}else d&&(console.error(`Unknown command: ${d}
31
- Valid commands: ${I.join(", ")}`),process.exit(1));const i=v(),J=s["config-dir"]??(s.profile?`${i.configDir}/${s.profile}`:void 0),a=new $,S=new U,R=V(),p=new B(R);let T=!1;async function D(t){if(T)return;T=!0,n.info("main",`Received ${t}, shutting down...`),S.markShuttingDown();const m=setTimeout(()=>{n.error("main","Shutdown timed out, forcing exit"),w(i.daemonLockFile).catch(()=>{}),h(),process.exit(2)},1e4);try{await a.stop(),await p.stop(),await S.stop(),await P(),await w(i.daemonLockFile),await M(i.daemonStatusFile).catch(()=>{}),clearTimeout(m),h(),n.info("main","Shutdown complete"),process.exit(0)}catch(l){n.error("main",`Shutdown error: ${l}`),w(i.daemonLockFile).catch(()=>{}),h(),process.exit(2)}}async function K(){L(),N(),await q(),b(i.stdoutLogFile,i.stderrLogFile),C(!1),process.platform==="win32"&&await _("GrixConnectorDaemon",{platform:"win32"});try{await W(i.daemonLockFile,i.rootDir)}catch(e){console.error(e instanceof Error?e.message:e),process.exit(1)}H(),n.info("main",`grix-connector starting (PID ${process.pid})`),await k(i.daemonStatusFile,{state:"starting",pid:process.pid,updated_at:Date.now()});const t=parseInt(s["health-port"]??process.env.GRIX_HEALTH_PORT??"19579",10);await S.start(t);const m=g.join(i.dataDir,"health-port");x(m,String(t),"utf-8"),process.on("SIGINT",()=>D("SIGINT")),process.on("SIGTERM",()=>D("SIGTERM"));let l="",r=0,o;process.on("uncaughtException",e=>{const u=e instanceof Error?e.stack??e.message:String(e);u===l?(r++,(r<=3||r%100===0)&&n.error("main",`Uncaught exception (x${r}): ${u}`)):(r>3&&n.error("main",`Previous exception repeated ${r} times total`),l=u,r=1,n.error("main",`Uncaught exception: ${e instanceof Error?e.stack:e}`),o||(o=setTimeout(()=>{r>3&&n.error("main",`Previous exception repeated ${r} times total`),l="",r=0,o=void 0},1e4).unref())),!F(e)&&(E(e,"uncaughtException"),D("uncaughtException"))}),process.on("unhandledRejection",e=>{n.error("main",`Unhandled rejection: ${e}`),!F(e)&&(E(e,"unhandledRejection"),D("unhandledRejection"))}),S.setStatusProvider(()=>a.getAgentsStatus()),await a.start(J);const f=parseInt(s["admin-port"]??process.env.GRIX_ADMIN_PORT??"19580",10);p.setAgentHandler({list:()=>a.getAgentsStatus(),add:e=>a.addAgent(e),remove:e=>a.removeAgent(e),restart:e=>a.restartAgent(e)}),p.setUpgradeHandler({check:()=>a.checkUpgrade(),trigger:()=>a.triggerUpgrade()}),p.setProbeHandler({probeAll:e=>a.probeAll(e),probeOne:(e,u)=>a.probeOne(e,u)}),p.setInstallHandler({listInstallable:()=>a.listInstallable(),installAgent:e=>a.installAgent(e),getInstallProgress:e=>a.getInstallProgress(e)}),await p.start(f);const y=g.join(i.dataDir,"admin-token"),O=g.join(i.dataDir,"admin-port");X(y,R),x(O,String(f),"utf-8"),await k(i.daemonStatusFile,{state:"running",pid:process.pid,updated_at:Date.now()}),process.send&&process.send("ready"),n.info("main","grix-connector ready")}K().catch(async t=>{n.error("main",`Fatal: ${t}`),E(t,"startup"),await P(),w(i.daemonLockFile).catch(()=>{}),h(),process.exit(1)});const z=new Set(["ECONNRESET","ECONNREFUSED","ETIMEDOUT","EPIPE","EAI_AGAIN","ENOTFOUND","EHOSTUNREACH","ENETUNREACH","EIO"]);function F(t){return t instanceof Error&&"code"in t?z.has(t.code):!1}
34
+ right-click the terminal and select "Run as administrator".`);const t=R(),p=s["config-dir"]??(s.profile?g.join(t.configDir,s.profile):void 0),d=g.resolve(process.argv[1]||`${t.rootDir}/dist/grix.js`),r=new j({cliPath:d,nodePath:process.execPath});try{let a;switch(m){case"start":(await r.status({rootDir:t.rootDir})).installed?a=await r.start({rootDir:t.rootDir}):a=await r.install({rootDir:t.rootDir,configDir:p});break;case"stop":a=await r.stop({rootDir:t.rootDir});break;case"restart":(await r.status({rootDir:t.rootDir})).installed?a=await r.restart({rootDir:t.rootDir}):a=await r.install({rootDir:t.rootDir,configDir:p});break;case"status":a=await r.status({rootDir:t.rootDir});break}console.log(JSON.stringify(a,null,2)),process.exit(0)}catch(a){console.error(`${m} failed: ${a instanceof Error?a.message:a}`),process.exit(1)}}else m&&(console.error(`Unknown command: ${m}
35
+ Valid commands: ${F.join(", ")}`),process.exit(1));const o=R(),z=s["config-dir"]??(s.profile?`${o.configDir}/${s.profile}`:void 0),i=new $,S=new H,I=q(),u=new V(I);let T=!1;async function b(t){process.stderr.write(t.message+`
36
+ `),n.error("main",t.message.replace(/\n/g," \u2014 ")),await E(o.daemonStatusFile,{state:"failed",pid:process.pid,updated_at:Date.now(),reason:`port_bind_${t.kind}:${t.label}:${t.port}`}).catch(()=>{}),await k(),h(o.daemonLockFile).catch(()=>{}),f(),process.exit(1)}async function D(t){if(T)return;T=!0,n.info("main",`Received ${t}, shutting down...`),S.markShuttingDown();const p=setTimeout(()=>{n.error("main","Shutdown timed out, forcing exit"),h(o.daemonLockFile).catch(()=>{}),f(),process.exit(2)},1e4);try{await i.stop(),await u.stop(),await S.stop(),await k(),await h(o.daemonLockFile),await X(o.daemonStatusFile).catch(()=>{}),clearTimeout(p),f(),n.info("main","Shutdown complete"),process.exit(0)}catch(d){n.error("main",`Shutdown error: ${d}`),h(o.daemonLockFile).catch(()=>{}),f(),process.exit(2)}}async function Q(){L(),N(),await K(),C(o.stdoutLogFile,o.stderrLogFile),U(!1),process.platform==="win32"&&await M("GrixConnectorDaemon",{platform:"win32"});try{await B(o.daemonLockFile,o.rootDir)}catch(e){console.error(e instanceof Error?e.message:e),process.exit(1)}G(),n.info("main",`grix-connector starting (PID ${process.pid})`),await E(o.daemonStatusFile,{state:"starting",pid:process.pid,updated_at:Date.now()});const t=parseInt(s["health-port"]??process.env.GRIX_HEALTH_PORT??"19579",10);{const e=await P({label:"health",port:t,envVar:"GRIX_HEALTH_PORT",cliFlag:"health-port",start:c=>S.start(c)});e&&await b(e)}const p=g.join(o.dataDir,"health-port");v(p,String(t),"utf-8"),process.on("SIGINT",()=>D("SIGINT")),process.on("SIGTERM",()=>D("SIGTERM"));let d="",r=0,a;process.on("uncaughtException",e=>{const c=e instanceof Error?e.stack??e.message:String(e);c===d?(r++,(r<=3||r%100===0)&&n.error("main",`Uncaught exception (x${r}): ${c}`)):(r>3&&n.error("main",`Previous exception repeated ${r} times total`),d=c,r=1,n.error("main",`Uncaught exception: ${e instanceof Error?e.stack:e}`),a||(a=setTimeout(()=>{r>3&&n.error("main",`Previous exception repeated ${r} times total`),d="",r=0,a=void 0},1e4).unref())),!y(e)&&(x(e,"uncaughtException"),D("uncaughtException"))}),process.on("unhandledRejection",e=>{n.error("main",`Unhandled rejection: ${e}`),!y(e)&&(x(e,"unhandledRejection"),D("unhandledRejection"))}),S.setStatusProvider(()=>i.getAgentsStatus()),await i.start(z);const w=parseInt(s["admin-port"]??process.env.GRIX_ADMIN_PORT??"19580",10);u.setAgentHandler({list:()=>i.getAgentsStatus(),add:e=>i.addAgent(e),remove:e=>i.removeAgent(e),restart:e=>i.restartAgent(e)}),u.setUpgradeHandler({check:()=>i.checkUpgrade(),trigger:()=>i.triggerUpgrade()}),u.setProbeHandler({probeAll:e=>i.probeAll(e),probeOne:(e,c)=>i.probeOne(e,c)}),u.setInstallHandler({listInstallable:()=>i.listInstallable(),installAgent:e=>i.installAgent(e),getInstallProgress:e=>i.getInstallProgress(e)});{const e=await P({label:"admin",port:w,envVar:"GRIX_ADMIN_PORT",cliFlag:"admin-port",start:c=>u.start(c)});e&&await b(e)}const O=g.join(o.dataDir,"admin-token"),_=g.join(o.dataDir,"admin-port");J(O,I),v(_,String(w),"utf-8"),await E(o.daemonStatusFile,{state:"running",pid:process.pid,updated_at:Date.now()}),process.send&&process.send("ready"),n.info("main","grix-connector ready")}Q().catch(async t=>{n.error("main",`Fatal: ${t}`),x(t,"startup"),await k(),h(o.daemonLockFile).catch(()=>{}),f(),process.exit(1)});const Y=new Set(["ECONNRESET","ECONNREFUSED","ETIMEDOUT","EPIPE","EAI_AGAIN","ENOTFOUND","EHOSTUNREACH","ENETUNREACH","EIO"]);function y(t){return t instanceof Error&&"code"in t?Y.has(t.code):!1}
@@ -1 +1 @@
1
- function a(t){const o=new Set([`http://127.0.0.1:${t.serverPort}`,`http://localhost:${t.serverPort}`,...t.allowedOrigins]),e=new Set([`127.0.0.1:${t.serverPort}`,`localhost:${t.serverPort}`,...t.allowedHosts]);return{validateRequest(s){const r=i(s,o);if(!r.ok)return r;const n=l(s,e);return n.ok?{ok:!0}:n}}}function i(t,o){const e=t.headers.origin;return e?o.has(e)?{ok:!0}:{ok:!1,statusCode:403,message:`Origin not allowed: ${e}`}:{ok:!0}}function l(t,o){const e=t.headers.host;return e?o.has(e)?{ok:!0}:{ok:!1,statusCode:403,message:`Host not allowed: ${e}`}:{ok:!1,statusCode:403,message:"Missing Host header"}}export{a as createSecurityPolicy};
1
+ function a(o){const e=new Set([`http://127.0.0.1:${o.serverPort}`,`http://localhost:${o.serverPort}`,...o.allowedOrigins]),t=new Set([`127.0.0.1:${o.serverPort}`,`localhost:${o.serverPort}`,...o.allowedHosts]);return{validateRequest(s){const r=i(s,e);if(!r.ok)return r;const n=l(s,t);return n.ok?{ok:!0}:n}}}function i(o,e){const t=o.headers.origin;return t?e.has(t)?{ok:!0}:{ok:!1,statusCode:403,message:`Origin not allowed: ${t}`}:{ok:!0}}function l(o,e){const t=o.headers.host;return t?e.has(t)?{ok:!0}:{ok:!1,statusCode:403,message:`Host not allowed: ${t}`}:{ok:!1,statusCode:403,message:"Missing Host header"}}export{a as createSecurityPolicy};
@@ -1,12 +1,12 @@
1
- import{mkdir as P,readdir as b,readFile as k,rm as g,stat as E,writeFile as S}from"node:fs/promises";import p from"node:os";import d from"node:path";import{runCommand as u,spawnDetached as W,killProcessesByCommandLine as L,isWindowsElevated as D}from"./process-control.js";import{getServicePrefix as x,parseConfigDirFromPlistXML as j,parseConfigDirFromSystemdUnit as R,resolveLinuxUserUnitPath as M,resolveMacOSLaunchAgentPath as O}from"./service-paths.js";function h(t){return String(t??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function B(t){return`'${String(t??"").replace(/'/g,"'\\''")}'`}function T(t){return[t.nodePath,t.cliPath,...t.configDir?["--config-dir",t.configDir]:[]]}function U(t){const r=T(t);return`<?xml version="1.0" encoding="UTF-8"?>
1
+ import{existsSync as R,readFileSync as j}from"node:fs";import{mkdir as P,readdir as C,readFile as f,rm as w,stat as V,writeFile as h}from"node:fs/promises";import p from"node:os";import u from"node:path";import{isProcessRunning as v,runCommand as d,spawnDetached as W,isWindowsElevated as G,killProcessesByCommandLine as T}from"./process-control.js";import{getServicePrefix as A,parseConfigDirFromPlistXML as Q,parseConfigDirFromSystemdUnit as z,resolveBareDaemonMarkerPath as g,resolveLinuxUserUnitPath as H,resolveMacOSLaunchAgentPath as J}from"./service-paths.js";function y(t){return String(t??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function F(t){return`'${String(t??"").replace(/'/g,"'\\''")}'`}function M(t){return[t.nodePath,t.cliPath,...t.configDir?["--config-dir",t.configDir]:[]]}function X(t){const r=M(t);return`<?xml version="1.0" encoding="UTF-8"?>
2
2
  <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
3
  <plist version="1.0">
4
4
  <dict>
5
5
  <key>Label</key>
6
- <string>${h(t.serviceID)}</string>
6
+ <string>${y(t.serviceID)}</string>
7
7
  <key>ProgramArguments</key>
8
8
  <array>
9
- ${r.map(e=>` <string>${h(e)}</string>`).join(`
9
+ ${r.map(e=>` <string>${y(e)}</string>`).join(`
10
10
  `)}
11
11
  </array>
12
12
  <key>RunAtLoad</key>
@@ -14,26 +14,26 @@ ${r.map(e=>` <string>${h(e)}</string>`).join(`
14
14
  <key>KeepAlive</key>
15
15
  <true/>
16
16
  <key>WorkingDirectory</key>
17
- <string>${h(d.dirname(t.cliPath))}</string>
17
+ <string>${y(u.dirname(t.cliPath))}</string>
18
18
  ${t.environmentPath?` <key>EnvironmentVariables</key>
19
19
  <dict>
20
20
  <key>PATH</key>
21
- <string>${h(t.environmentPath)}</string>
21
+ <string>${y(t.environmentPath)}</string>
22
22
  </dict>
23
23
  `:""} <key>StandardOutPath</key>
24
- <string>${h(t.stdoutPath)}</string>
24
+ <string>${y(t.stdoutPath)}</string>
25
25
  <key>StandardErrorPath</key>
26
- <string>${h(t.stderrPath)}</string>
26
+ <string>${y(t.stderrPath)}</string>
27
27
  </dict>
28
28
  </plist>
29
- `}function V(t){const r=T(t).map(e=>B(e)).join(" ");return`[Unit]
29
+ `}function Z(t){const r=M(t).map(e=>F(e)).join(" ");return`[Unit]
30
30
  Description=grix-connector daemon (${t.serviceID})
31
31
  After=network.target
32
32
 
33
33
  [Service]
34
34
  Type=simple
35
35
  ExecStart=${r}
36
- WorkingDirectory=${d.dirname(t.cliPath)}
36
+ WorkingDirectory=${u.dirname(t.cliPath)}
37
37
  Restart=always
38
38
  RestartSec=2
39
39
  StandardOutput=append:${t.stdoutPath}
@@ -41,6 +41,13 @@ StandardError=append:${t.stderrPath}
41
41
 
42
42
  [Install]
43
43
  WantedBy=default.target
44
- `}function w(t,r){const e=t.replace(/[^a-zA-Z0-9._-]/g,"_");return d.join(r,".grix",`${e}-wrapper.vbs`)}function A(t,r){const e=t.replace(/[^a-zA-Z0-9._-]/g,"_"),a=d.join(r,"AppData","Roaming","Microsoft","Windows","Start Menu","Programs","Startup");return d.join(a,`${e}.lnk`)}function f(t){return String(t??"").replace(/"/g,'""')}function Q(t,r){const e=d.join(d.dirname(t),d.basename(r).replace(/\.lnk$/,"-create.vbs")),a=["Option Explicit","Dim shell, shortcut",'Set shell = CreateObject("WScript.Shell")',`Set shortcut = shell.CreateShortcut("${f(r)}")`,'shortcut.TargetPath = "wscript.exe"',`shortcut.Arguments = "//B //NoLogo \\"${f(t)}\\""`,"shortcut.WindowStyle = 7","shortcut.Save",""].join(`\r
45
- `);return{path:e,content:a}}function _(t){const r=[`"${f(t.nodePath)}"`,`"${f(t.cliPath)}"`,...t.configDir?["--config-dir",`"${f(t.configDir)}"`]:[]].join(" ");return["Option Explicit","Dim shell, command, delayMs, rapidCount",`command = "${f(r)}"`,'Set shell = CreateObject("WScript.Shell")',"delayMs = 5000","rapidCount = 0","Do"," shell.Run command, 0, True"," rapidCount = rapidCount + 1"," If rapidCount >= 10 Then"," WScript.Sleep 300000"," rapidCount = 0"," Else"," WScript.Sleep delayMs"," End If","Loop",""].join(`\r
46
- `)}function y(t){return`gui/${t??0}`}function v(t){const r=String(t?.stdout??"").trim();return String(t?.stderr??"").trim()||r||`exit=${Number(t?.exitCode??-1)}`}function F(t,r){if(Number(t?.exitCode??0)!==0)throw new Error(`${r}: ${v(t)}`)}function z(){return{platform:"darwin",kind:"launchd",async install({serviceID:t,nodePath:r,cliPath:e,configDir:a,stdoutPath:n,stderrPath:i,environmentPath:c="",homeDir:o=p.homedir()}){const s=O(t,o);return await P(d.dirname(s),{recursive:!0}),await S(s,U({serviceID:t,nodePath:r,cliPath:e,configDir:a,stdoutPath:n,stderrPath:i,environmentPath:c}),{encoding:"utf8",mode:384}),{definitionPath:s}},async start({serviceID:t,definitionPath:r,runCommand:e=u,uid:a=process.getuid?.()??0}){const n=y(a);let i=await e("launchctl",["bootstrap",n,r],{allowFailure:!0});Number(i?.exitCode??0)!==0&&(await e("launchctl",["bootout",`${n}/${t}`],{allowFailure:!0}),i=await e("launchctl",["bootstrap",n,r],{allowFailure:!0}),F(i,`launchctl bootstrap ${n}`));const c=await e("launchctl",["kickstart","-k",`${n}/${t}`],{allowFailure:!0});if(Number(c?.exitCode??0)!==0){const o=Number(i?.exitCode??0)===0?"":`, bootstrap=${v(i)}`;throw new Error(`launchctl start failed for ${n}/${t}: ${v(c)}${o}`)}},async stop({serviceID:t,runCommand:r=u,uid:e=process.getuid?.()??0}){const a=y(e);await r("launchctl",["bootout",`${a}/${t}`],{allowFailure:!0})},async restart({serviceID:t,definitionPath:r,runCommand:e=u,uid:a=process.getuid?.()??0}){const n=y(a);await e("launchctl",["bootout",`${n}/${t}`],{allowFailure:!0});for(let o=0;o<20&&(await e("launchctl",["print",`${n}/${t}`],{allowFailure:!0})).exitCode===0;o+=1)await new Promise(l=>setTimeout(l,250));try{await E(r)}catch{throw new Error(`launchd plist missing: ${r}`)}const i=await e("launchctl",["bootstrap",n,r],{allowFailure:!0});F(i,`launchctl bootstrap ${n}`);const c=await e("launchctl",["kickstart","-k",`${n}/${t}`],{allowFailure:!0});F(c,`launchctl kickstart ${n}/${t}`)},async uninstall({serviceID:t,definitionPath:r,runCommand:e=u,uid:a=process.getuid?.()??0}){const n=y(a);await e("launchctl",["bootout",`${n}/${t}`],{allowFailure:!0}),await g(r,{force:!0})},async discoverServices({homeDir:t=p.homedir()}={}){const r=d.join(t,"Library","LaunchAgents"),e=await b(r).catch(()=>[]),a=x("darwin"),n=[];for(const i of e){if(!i.startsWith(a)||!i.endsWith(".plist"))continue;const c=i.slice(0,-6),o=d.join(r,i);let s=null;try{const l=await k(o,"utf8");s=j(l)}catch{}n.push({serviceID:c,definitionPath:o,configDir:s})}return n},async isServiceLoaded({serviceID:t,runCommand:r=u,uid:e=process.getuid?.()??0}){const a=y(e),n=await r("launchctl",["print",`${a}/${t}`],{allowFailure:!0});return Number(n?.exitCode??1)===0}}}function H(){return{platform:"win32",kind:"task-scheduler",async install({serviceID:t,nodePath:r,cliPath:e,configDir:a,runCommand:n=u,homeDir:i=p.homedir()}){const c=w(t,i);await P(d.dirname(c),{recursive:!0}),await S(c,_({nodePath:r,cliPath:e,configDir:a}),"utf8");let o=!1;if(D())try{await n("schtasks",["/Create","/TN",t,"/SC","ONCE","/ST","00:00","/SD","2099/12/31","/RL","LIMITED","/F","/TR",`wscript.exe //B //NoLogo "${c}"`]),o=!0}catch{}const s=A(t,i),l=Q(c,s);return await S(l.path,l.content,"utf8"),await n("wscript.exe",[l.path,"//B","//NoLogo"]).catch(()=>{}),await g(l.path,{force:!0}),{definitionPath:o?`task:${t}`:`startup:${t}`}},async start({serviceID:t,definitionPath:r,runCommand:e=u,homeDir:a=p.homedir()}){const n=w(t,a);if(r.startsWith("task:"))try{await e("schtasks",["/Run","/TN",t]);return}catch{}W("wscript.exe",["//B","//NoLogo",n])},async stop({serviceID:t,runCommand:r=u,homeDir:e=p.homedir()}){await r("schtasks",["/End","/TN",t],{allowFailure:!0});const a=`${t}-wrapper.vbs`;await L(a,{platform:"win32"})},async restart({serviceID:t,definitionPath:r,runCommand:e=u,homeDir:a=p.homedir()}){await e("schtasks",["/End","/TN",t],{allowFailure:!0});const n=`${t}-wrapper.vbs`;await L(n,{platform:"win32"});const i=w(t,a);if(r?.startsWith("task:"))try{await e("schtasks",["/Run","/TN",t]);return}catch{}W("wscript.exe",["//B","//NoLogo",i])},async uninstall({serviceID:t,runCommand:r=u,homeDir:e=p.homedir()}){await r("schtasks",["/Delete","/TN",t,"/F"],{allowFailure:!0});const a=w(t,e);await g(a,{force:!0});const n=A(t,e);await g(n,{force:!0})},async discoverServices({homeDir:t=p.homedir(),runCommand:r=u}={}){const e=x("win32"),a=await r("schtasks",["/Query","/FO","CSV","/NH"],{allowFailure:!0}),n=[];if(Number(a?.exitCode??-1)===0){const o=String(a.stdout??"").split(/\r?\n/);for(const s of o){const l=s.match(/^"([^"]+)"/);if(!l)continue;const m=l[1];if(!m.startsWith(e))continue;let C=null;try{const $=w(m,t),N=(await k($,"utf8")).match(/--config-dir\s+""([^""]+)""/);N&&(C=N[1])}catch{}n.push({serviceID:m,definitionPath:`task:${m}`,configDir:C})}}const i=d.join(t,"AppData","Roaming","Microsoft","Windows","Start Menu","Programs","Startup"),c=await b(i).catch(()=>[]);for(const o of c){if(!o.startsWith(e)||!o.endsWith(".lnk"))continue;const s=o.slice(0,-4);if(n.some(m=>m.serviceID===s))continue;let l=null;try{const m=w(s,t),$=(await k(m,"utf8")).match(/--config-dir\s+""([^""]+)""/);$&&(l=$[1])}catch{}n.push({serviceID:s,definitionPath:`startup:${s}`,configDir:l})}return n},async isServiceLoaded({serviceID:t,runCommand:r=u}){const e=await r("schtasks",["/Query","/TN",t,"/NH"],{allowFailure:!0});return Number(e?.exitCode??1)===0}}}function Z(){return{platform:"linux",kind:"systemd-user",async install({serviceID:t,nodePath:r,cliPath:e,configDir:a,stdoutPath:n,stderrPath:i,homeDir:c=p.homedir(),runCommand:o=u}){const s=M(t,c);return await P(d.dirname(s),{recursive:!0}),await S(s,V({serviceID:t,nodePath:r,cliPath:e,configDir:a,stdoutPath:n,stderrPath:i}),{encoding:"utf8",mode:384}),await o("systemctl",["--user","daemon-reload"]),await o("systemctl",["--user","enable",`${t}.service`]),{definitionPath:s}},async start({serviceID:t,runCommand:r=u}){await r("systemctl",["--user","start",`${t}.service`])},async stop({serviceID:t,runCommand:r=u}){await r("systemctl",["--user","stop",`${t}.service`],{allowFailure:!0})},async restart({serviceID:t,runCommand:r=u}){await r("systemctl",["--user","restart",`${t}.service`])},async uninstall({serviceID:t,definitionPath:r,runCommand:e=u}){await e("systemctl",["--user","stop",`${t}.service`],{allowFailure:!0}),await e("systemctl",["--user","disable",`${t}.service`],{allowFailure:!0}),await g(r,{force:!0}),await e("systemctl",["--user","daemon-reload"])},async discoverServices({homeDir:t=p.homedir()}={}){const r=d.join(t,".config","systemd","user"),e=await b(r).catch(()=>[]),a=x("linux"),n=[];for(const i of e){if(!i.startsWith(a)||!i.endsWith(".service"))continue;const c=i.slice(0,-8),o=d.join(r,i);let s=null;try{const l=await k(o,"utf8");s=R(l)}catch{}n.push({serviceID:c,definitionPath:o,configDir:s})}return n},async isServiceLoaded({serviceID:t,runCommand:r=u}){const e=await r("systemctl",["--user","is-active",`${t}.service`],{allowFailure:!0});return String(e?.stdout??"").trim()==="active"}}}function I(t=process.platform){return t==="darwin"?z():t==="win32"?H():Z()}export{I as getPlatformServiceAdapter};
44
+ `}function k(t,r){const e=t.replace(/[^a-zA-Z0-9._-]/g,"_");return u.join(r,".grix",`${e}-wrapper.vbs`)}function B(t,r){const e=t.replace(/[^a-zA-Z0-9._-]/g,"_"),n=u.join(r,"AppData","Roaming","Microsoft","Windows","Start Menu","Programs","Startup");return u.join(n,`${e}.lnk`)}function $(t){return String(t??"").replace(/"/g,'""')}function q(t,r){const e=u.join(u.dirname(t),u.basename(r).replace(/\.lnk$/,"-create.vbs")),n=["Option Explicit","Dim shell, shortcut",'Set shell = CreateObject("WScript.Shell")',`Set shortcut = shell.CreateShortcut("${$(r)}")`,'shortcut.TargetPath = "wscript.exe"',`shortcut.Arguments = "//B //NoLogo \\"${$(t)}\\""`,"shortcut.WindowStyle = 7","shortcut.Save",""].join(`\r
45
+ `);return{path:e,content:n}}function K(t){const r=[`"${$(t.nodePath)}"`,`"${$(t.cliPath)}"`,...t.configDir?["--config-dir",`"${$(t.configDir)}"`]:[]].join(" ");return["Option Explicit","Dim shell, command, delayMs, rapidCount",`command = "${$(r)}"`,'Set shell = CreateObject("WScript.Shell")',"delayMs = 5000","rapidCount = 0","Do"," shell.Run command, 0, True"," rapidCount = rapidCount + 1"," If rapidCount >= 10 Then"," WScript.Sleep 300000"," rapidCount = 0"," Else"," WScript.Sleep delayMs"," End If","Loop",""].join(`\r
46
+ `)}function b(t){return`gui/${t??0}`}function E(t){const r=String(t?.stdout??"").trim();return String(t?.stderr??"").trim()||r||`exit=${Number(t?.exitCode??-1)}`}function L(t,r){if(Number(t?.exitCode??0)!==0)throw new Error(`${r}: ${E(t)}`)}function Y(){return{platform:"darwin",kind:"launchd",async install({serviceID:t,nodePath:r,cliPath:e,configDir:n,stdoutPath:a,stderrPath:i,environmentPath:s="",homeDir:o=p.homedir()}){const c=J(t,o);return await P(u.dirname(c),{recursive:!0}),await h(c,X({serviceID:t,nodePath:r,cliPath:e,configDir:n,stdoutPath:a,stderrPath:i,environmentPath:s}),{encoding:"utf8",mode:384}),{definitionPath:c}},async start({serviceID:t,definitionPath:r,runCommand:e=d,uid:n=process.getuid?.()??0}){const a=b(n);let i=await e("launchctl",["bootstrap",a,r],{allowFailure:!0});Number(i?.exitCode??0)!==0&&(await e("launchctl",["bootout",`${a}/${t}`],{allowFailure:!0}),i=await e("launchctl",["bootstrap",a,r],{allowFailure:!0}),L(i,`launchctl bootstrap ${a}`));const s=await e("launchctl",["kickstart","-k",`${a}/${t}`],{allowFailure:!0});if(Number(s?.exitCode??0)!==0){const o=Number(i?.exitCode??0)===0?"":`, bootstrap=${E(i)}`;throw new Error(`launchctl start failed for ${a}/${t}: ${E(s)}${o}`)}},async stop({serviceID:t,runCommand:r=d,uid:e=process.getuid?.()??0}){const n=b(e);await r("launchctl",["bootout",`${n}/${t}`],{allowFailure:!0})},async restart({serviceID:t,definitionPath:r,runCommand:e=d,uid:n=process.getuid?.()??0}){const a=b(n);await e("launchctl",["bootout",`${a}/${t}`],{allowFailure:!0});for(let o=0;o<20&&(await e("launchctl",["print",`${a}/${t}`],{allowFailure:!0})).exitCode===0;o+=1)await new Promise(l=>setTimeout(l,250));try{await V(r)}catch{throw new Error(`launchd plist missing: ${r}`)}const i=await e("launchctl",["bootstrap",a,r],{allowFailure:!0});L(i,`launchctl bootstrap ${a}`);const s=await e("launchctl",["kickstart","-k",`${a}/${t}`],{allowFailure:!0});L(s,`launchctl kickstart ${a}/${t}`)},async uninstall({serviceID:t,definitionPath:r,runCommand:e=d,uid:n=process.getuid?.()??0}){const a=b(n);await e("launchctl",["bootout",`${a}/${t}`],{allowFailure:!0}),await w(r,{force:!0})},async discoverServices({homeDir:t=p.homedir()}={}){const r=u.join(t,"Library","LaunchAgents"),e=await C(r).catch(()=>[]),n=A("darwin"),a=[];for(const i of e){if(!i.startsWith(n)||!i.endsWith(".plist"))continue;const s=i.slice(0,-6),o=u.join(r,i);let c=null;try{const l=await f(o,"utf8");c=Q(l)}catch{}a.push({serviceID:s,definitionPath:o,configDir:c})}return a},async isServiceLoaded({serviceID:t,runCommand:r=d,uid:e=process.getuid?.()??0}){const n=b(e),a=await r("launchctl",["print",`${n}/${t}`],{allowFailure:!0});return Number(a?.exitCode??1)===0}}}function I(){return{platform:"win32",kind:"task-scheduler",async install({serviceID:t,nodePath:r,cliPath:e,configDir:n,runCommand:a=d,homeDir:i=p.homedir()}){const s=k(t,i);await P(u.dirname(s),{recursive:!0}),await h(s,K({nodePath:r,cliPath:e,configDir:n}),"utf8");let o=!1;if(G())try{await a("schtasks",["/Create","/TN",t,"/SC","ONCE","/ST","00:00","/SD","2099/12/31","/RL","LIMITED","/F","/TR",`wscript.exe //B //NoLogo "${s}"`]),o=!0}catch{}const c=B(t,i),l=q(s,c);return await h(l.path,l.content,"utf8"),await a("wscript.exe",[l.path,"//B","//NoLogo"]).catch(()=>{}),await w(l.path,{force:!0}),{definitionPath:o?`task:${t}`:`startup:${t}`}},async start({serviceID:t,definitionPath:r,runCommand:e=d,homeDir:n=p.homedir()}){const a=k(t,n);if(r.startsWith("task:"))try{await e("schtasks",["/Run","/TN",t]);return}catch{}W("wscript.exe",["//B","//NoLogo",a])},async stop({serviceID:t,runCommand:r=d,homeDir:e=p.homedir()}){await r("schtasks",["/End","/TN",t],{allowFailure:!0});const n=`${t}-wrapper.vbs`;await T(n,{platform:"win32"})},async restart({serviceID:t,definitionPath:r,runCommand:e=d,homeDir:n=p.homedir()}){await e("schtasks",["/End","/TN",t],{allowFailure:!0});const a=`${t}-wrapper.vbs`;await T(a,{platform:"win32"});const i=k(t,n);if(r?.startsWith("task:"))try{await e("schtasks",["/Run","/TN",t]);return}catch{}W("wscript.exe",["//B","//NoLogo",i])},async uninstall({serviceID:t,runCommand:r=d,homeDir:e=p.homedir()}){await r("schtasks",["/Delete","/TN",t,"/F"],{allowFailure:!0});const n=k(t,e);await w(n,{force:!0});const a=B(t,e);await w(a,{force:!0})},async discoverServices({homeDir:t=p.homedir(),runCommand:r=d}={}){const e=A("win32"),n=await r("schtasks",["/Query","/FO","CSV","/NH"],{allowFailure:!0}),a=[];if(Number(n?.exitCode??-1)===0){const o=String(n.stdout??"").split(/\r?\n/);for(const c of o){const l=c.match(/^"([^"]+)"/);if(!l)continue;const m=l[1];if(!m.startsWith(e))continue;let _=null;try{const x=k(m,t),D=(await f(x,"utf8")).match(/--config-dir\s+""([^""]+)""/);D&&(_=D[1])}catch{}a.push({serviceID:m,definitionPath:`task:${m}`,configDir:_})}}const i=u.join(t,"AppData","Roaming","Microsoft","Windows","Start Menu","Programs","Startup"),s=await C(i).catch(()=>[]);for(const o of s){if(!o.startsWith(e)||!o.endsWith(".lnk"))continue;const c=o.slice(0,-4);if(a.some(m=>m.serviceID===c))continue;let l=null;try{const m=k(c,t),x=(await f(m,"utf8")).match(/--config-dir\s+""([^""]+)""/);x&&(l=x[1])}catch{}a.push({serviceID:c,definitionPath:`startup:${c}`,configDir:l})}return a},async isServiceLoaded({serviceID:t,runCommand:r=d}){const e=await r("schtasks",["/Query","/TN",t,"/NH"],{allowFailure:!0});return Number(e?.exitCode??1)===0}}}function tt(){return{platform:"linux",kind:"systemd-user",async install({serviceID:t,nodePath:r,cliPath:e,configDir:n,stdoutPath:a,stderrPath:i,homeDir:s=p.homedir(),runCommand:o=d}){const c=H(t,s);return await P(u.dirname(c),{recursive:!0}),await h(c,Z({serviceID:t,nodePath:r,cliPath:e,configDir:n,stdoutPath:a,stderrPath:i}),{encoding:"utf8",mode:384}),await o("systemctl",["--user","daemon-reload"]),await o("systemctl",["--user","enable",`${t}.service`]),{definitionPath:c}},async start({serviceID:t,runCommand:r=d}){await r("systemctl",["--user","start",`${t}.service`])},async stop({serviceID:t,runCommand:r=d}){await r("systemctl",["--user","stop",`${t}.service`],{allowFailure:!0})},async restart({serviceID:t,runCommand:r=d}){await r("systemctl",["--user","restart",`${t}.service`])},async uninstall({serviceID:t,definitionPath:r,runCommand:e=d}){await e("systemctl",["--user","stop",`${t}.service`],{allowFailure:!0}),await e("systemctl",["--user","disable",`${t}.service`],{allowFailure:!0}),await w(r,{force:!0}),await e("systemctl",["--user","daemon-reload"])},async discoverServices({homeDir:t=p.homedir()}={}){const r=u.join(t,".config","systemd","user"),e=await C(r).catch(()=>[]),n=A("linux"),a=[];for(const i of e){if(!i.startsWith(n)||!i.endsWith(".service"))continue;const s=i.slice(0,-8),o=u.join(r,i);let c=null;try{const l=await f(o,"utf8");c=z(l)}catch{}a.push({serviceID:s,definitionPath:o,configDir:c})}return a},async isServiceLoaded({serviceID:t,runCommand:r=d}){const e=await r("systemctl",["--user","is-active",`${t}.service`],{allowFailure:!0});return String(e?.stdout??"").trim()==="active"}}}function S(t){try{const r=j(t,"utf8"),e=JSON.parse(r);if(e&&typeof e.service_id=="string"&&typeof e.node_path=="string"&&typeof e.cli_path=="string"&&typeof e.root_dir=="string")return e}catch{}return null}function rt(t){return u.join(t,"daemon.lock.json")}function N(t){const r=rt(t);try{const e=j(r,"utf8"),n=JSON.parse(e);if(typeof n?.pid=="number")return n.pid}catch{}return 0}function O(t){return u.join(t,".profile")}function et(t){return t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function nt(t){const r=[F(t.nodePath),F(t.cliPath)];t.configDir&&r.push("--config-dir",F(t.configDir));const e=r.join(" ");return`${e} status >/dev/null 2>&1 || (nohup ${e} start >/dev/null 2>&1 &)`}function at(t,r){return`# >>> grix-connector autostart [${t}] >>>
47
+ ${r}
48
+ # <<< grix-connector autostart [${t}] <<<
49
+ `}function U(t){const r=et(t);return new RegExp(`(?:^|\\n)# >>> grix-connector autostart \\[${r}\\] >>>\\n[\\s\\S]*?\\n# <<< grix-connector autostart \\[${r}\\] <<<\\n?`,"m")}async function it(t,r,e){let n="";try{n=await f(t,"utf8")}catch{}const a=at(r,e),i=U(r);let s;if(i.test(n))s=n.replace(i,`
50
+ ${a}`).replace(/^\n+/,"");else{const o=n.length===0||n.endsWith(`
51
+ `)?"":`
52
+ `;s=`${n}${o}${a}`}s!==n&&(await P(u.dirname(t),{recursive:!0}),await h(t,s,{encoding:"utf8"}))}async function ot(t,r){let e="";try{e=await f(t,"utf8")}catch{return}const n=U(r);if(!n.test(e))return;const a=e.replace(n,"").replace(/^\n+/,"");await h(t,a,{encoding:"utf8"})}function st(){return{platform:"linux",kind:"bare-daemon",async install({serviceID:t,nodePath:r,cliPath:e,configDir:n,stdoutPath:a,stderrPath:i,homeDir:s=p.homedir()}){const o=g(t,s);await P(u.dirname(o),{recursive:!0});const c=u.resolve(u.dirname(u.dirname(a||i||o))),l={schema_version:1,service_id:t,node_path:r,cli_path:e,config_dir:n??"",root_dir:c,stdout_path:a,stderr_path:i,installed_at:Date.now()};await h(o,`${JSON.stringify(l,null,2)}
53
+ `,{encoding:"utf8",mode:384});const m=nt({nodePath:r,cliPath:e,configDir:n??""});return await it(O(s),t,m),{definitionPath:o}},async start({serviceID:t,homeDir:r=p.homedir()}){const e=g(t,r),n=S(e);if(!n)throw new Error(`bare-daemon marker missing: ${e}`);const a=[n.cli_path];n.config_dir&&a.push("--config-dir",n.config_dir),W(n.node_path,a)},async stop({serviceID:t,homeDir:r=p.homedir()}){const e=g(t,r),n=S(e);if(!n)return;const a=N(n.root_dir);if(a>0&&v(a))try{process.kill(a,"SIGTERM")}catch{}},async restart(t){await this.stop(t);const r=Date.now()+5e3,e=g(t.serviceID,t.homeDir??p.homedir()),n=S(e);for(;n&&Date.now()<r;){const a=N(n.root_dir);if(a<=0||!v(a))break;await new Promise(i=>setTimeout(i,100))}await this.start(t)},async uninstall({serviceID:t,homeDir:r=p.homedir()}){const e=g(t,r),n=S(e);if(n){const a=N(n.root_dir);if(a>0&&v(a))try{process.kill(a,"SIGTERM")}catch{}}await w(e,{force:!0}),await ot(O(r),t)},async discoverServices({homeDir:t=p.homedir()}={}){const r=u.join(t,".grix","service"),e=await C(r).catch(()=>[]),n=A("linux"),a=[];for(const i of e){if(!i.startsWith(n)||!i.endsWith(".bare.json"))continue;const s=i.slice(0,-10),o=u.join(r,i),c=S(o);a.push({serviceID:s,definitionPath:o,configDir:c?.config_dir||null})}return a},async isServiceLoaded({serviceID:t,homeDir:r=p.homedir()}){const e=g(t,r),n=S(e);if(!n)return!1;const a=N(n.root_dir);return a>0&&v(a)}}}function ct(t=process.getuid?.()??0){if(process.env.GRIX_FORCE_BARE_DAEMON==="1")return!1;const r=process.env.XDG_RUNTIME_DIR;return!!(r&&R(u.join(r,"bus"))||R(`/run/user/${t}/bus`))}function wt(t=process.platform,r={}){return t==="darwin"?Y():t==="win32"?I():r.systemdUserAvailable??ct()?tt():st()}export{wt as getPlatformServiceAdapter,ct as isSystemdUserBusAvailable,ot as removeProfileAutostart,it as upsertProfileAutostart};
@@ -1,4 +1,4 @@
1
- import{createHash as w}from"node:crypto";import P from"node:fs";import S from"node:os";import c from"node:path";import m from"node:process";import{mkdir as p}from"node:fs/promises";import{setTimeout as _}from"node:timers/promises";import{resolveRuntimePaths as u}from"../core/config/paths.js";import{inspectDaemonState as l}from"../runtime/service-state.js";import{isProcessRunning as y,killOrphanedDaemonProcesses as F,runCommand as n,terminateProcessTree as L,waitForProcessExit as v}from"./process-control.js";import{readDaemonLock as g}from"../runtime/daemon-lock.js";import{ServiceInstallStore as z}from"./service-install-store.js";import{getPlatformServiceAdapter as R}from"./platform-adapter.js";import{buildServiceID as D,resolveServiceInstallRecordPath as b,resolveServiceStderrPath as I,resolveServiceStdoutPath as $}from"./service-paths.js";class G{adapter;platform;homeDir;uid;nodePath;cliPath;constructor(t){this.platform=t.platform??m.platform,this.homeDir=t.homeDir??S.homedir(),this.uid=t.uid??m.getuid?.()??0,this.nodePath=t.nodePath??m.execPath,this.cliPath=t.cliPath,this.adapter=R(this.platform)}normalizeRootDir(t){return c.resolve(String(t??"").trim())}buildDescriptor(t,i,r=""){const e=this.normalizeRootDir(t);return{schema_version:1,platform:this.platform,service_id:D(e,this.platform),node_path:this.nodePath,cli_path:this.cliPath,cli_hash:this.computeFileHash(this.cliPath)??"",definition_path:r,config_dir:i??"",installed_at:0,updated_at:0}}createStore(t){return new z(b(this.normalizeRootDir(t)))}toAdapterPayload(t){return{serviceID:t.service_id,nodePath:t.node_path,cliPath:t.cli_path,configDir:t.config_dir||void 0,environmentPath:this.buildServiceEnvironmentPath()}}async resolveServiceLogPaths(t,i){if(this.platform==="darwin"){const r=c.join(this.homeDir,"Library","Logs","grix-connector");return await p(r,{recursive:!0}),{stdoutPath:c.join(r,`${i}.out.log`),stderrPath:c.join(r,`${i}.err.log`)}}return await p(c.join(t,"service"),{recursive:!0}),{stdoutPath:$(t),stderrPath:I(t)}}buildServiceEnvironmentPath(){const t=[c.dirname(this.nodePath),"/opt/homebrew/bin","/usr/local/bin","/usr/bin","/bin","/usr/sbin","/sbin"],i=String(m.env.PATH??"").split(c.delimiter).filter(s=>s&&c.isAbsolute(s)).filter(s=>!s.includes("/node_modules/.bin")).filter(s=>!s.includes("/node-gyp-bin")),r=[],e=new Set;for(const s of[...t,...i]){if(e.has(s))continue;if(e.add(s),[...r,s].join(c.delimiter).length>2048)break;r.push(s)}return r.join(c.delimiter)}toStartParams(t){return{serviceID:t.service_id,definitionPath:t.definition_path,uid:this.uid,homeDir:this.homeDir,runCommand:n}}toStopParams(t){return{serviceID:t.service_id,uid:this.uid,homeDir:this.homeDir,runCommand:n}}toRestartParams(t){return{serviceID:t.service_id,definitionPath:t.definition_path,uid:this.uid,homeDir:this.homeDir,runCommand:n}}toUninstallParams(t){return{serviceID:t.service_id,definitionPath:t.definition_path,uid:this.uid,homeDir:this.homeDir,runCommand:n}}async loadDescriptor(t){const i=this.createStore(t),r=await i.load();if(!r)throw new Error("\u540E\u53F0\u670D\u52A1\u8FD8\u6CA1\u6709\u5B89\u88C5\uFF0C\u8BF7\u5148\u6267\u884C service install\u3002");return{store:i,descriptor:r}}isDescriptorCurrent(t){if(t.node_path!==this.nodePath||t.cli_path!==this.cliPath||!t.cli_hash)return!1;const i=this.computeFileHash(t.cli_path);return i!==null&&i===t.cli_hash}computeFileHash(t){try{const i=P.readFileSync(t);return w("sha256").update(i).digest("hex")}catch{return null}}async refreshDescriptor(t,i){const r=this.normalizeRootDir(t);await p(c.join(r,"service"),{recursive:!0});const e={...i,platform:this.platform,service_id:i.service_id||D(r,this.platform),node_path:this.nodePath,cli_path:this.cliPath,cli_hash:this.computeFileHash(this.cliPath)??"",config_dir:i.config_dir},s=await this.resolveServiceLogPaths(r,e.service_id),a=await this.adapter.install({...this.toAdapterPayload(e),stdoutPath:s.stdoutPath,stderrPath:s.stderrPath,homeDir:this.homeDir,uid:this.uid,runCommand:n}),o={...e,installed_at:i.installed_at,definition_path:a?.definitionPath||e.definition_path,updated_at:Date.now()};return await this.createStore(r).save(o),o}async resolveActiveDescriptor(t){const{descriptor:i}=await this.loadDescriptor(t);return this.isDescriptorCurrent(i)?i:this.refreshDescriptor(t,i)}async discoverConflictingServices(t){const i=this.normalizeRootDir(t),r=D(i,this.platform);return(await this.adapter.discoverServices({homeDir:this.homeDir,runCommand:n})).filter(s=>s.serviceID!==r)}async cleanupConflictingServices(t){const i=await this.discoverConflictingServices(t);if(i.length>0){const e=i.map(s=>s.serviceID).join(", ");console.log(`\u53D1\u73B0 ${i.length} \u4E2A\u6B8B\u7559\u670D\u52A1\uFF0C\u6B63\u5728\u68C0\u67E5: ${e}`)}let r=0;for(const e of i){if(await this.adapter.isServiceLoaded({serviceID:e.serviceID,uid:this.uid,runCommand:n})){console.log(`\u8DF3\u8FC7\u6B63\u5728\u8FD0\u884C\u7684\u670D\u52A1: ${e.serviceID}`);continue}try{await this.adapter.stop({serviceID:e.serviceID,uid:this.uid,runCommand:n})}catch{}try{await this.adapter.uninstall({serviceID:e.serviceID,definitionPath:e.definitionPath,uid:this.uid,runCommand:n}),r++}catch{}}return r}async waitForDaemonStarted(t,i={}){const{oldPid:r=0,timeoutMs:e=2e4}=i,s=Date.now();for(;Date.now()-s<e;){const h=g(t.daemonLockFile);if(h&&h.pid>0&&y(h.pid)&&h.pid!==r)return;const f=l(t.daemonStatusFile);if(f.running&&f.pid>0&&f.pid!==r)return;await _(100)}const a=g(t.daemonLockFile),o=l(t.daemonStatusFile),d=[`daemon start timeout (${e}ms)`,`status=${JSON.stringify(o)}`,`lock=${a?JSON.stringify(a):"missing"}`,this.tailLogForError("stderr",i.stderrLogFile??t.stderrLogFile),this.tailLogForError("stdout",i.stdoutLogFile??t.stdoutLogFile)].filter(Boolean);throw new Error(d.join(`
2
- `))}tailLogForError(t,i,r=20){if(!P.existsSync(i))return`${t}=missing`;try{const e=P.readFileSync(i,"utf8").trim();if(!e)return`${t}=empty`;const s=e.split(/\r?\n/).slice(-r);return`${t}:
1
+ import{createHash as w}from"node:crypto";import D from"node:fs";import S from"node:os";import h from"node:path";import m from"node:process";import{mkdir as p}from"node:fs/promises";import{setTimeout as _}from"node:timers/promises";import{resolveRuntimePaths as u}from"../core/config/paths.js";import{inspectDaemonState as l}from"../runtime/service-state.js";import{isProcessRunning as y,killOrphanedDaemonProcesses as F,runCommand as n,terminateProcessTree as L,waitForProcessExit as v}from"./process-control.js";import{readDaemonLock as g}from"../runtime/daemon-lock.js";import{ServiceInstallStore as z}from"./service-install-store.js";import{getPlatformServiceAdapter as R}from"./platform-adapter.js";import{buildServiceID as P,resolveServiceInstallRecordPath as b,resolveServiceStderrPath as I,resolveServiceStdoutPath as $}from"./service-paths.js";class G{adapter;platform;homeDir;uid;nodePath;cliPath;constructor(t){this.platform=t.platform??m.platform,this.homeDir=t.homeDir??S.homedir(),this.uid=t.uid??m.getuid?.()??0,this.nodePath=t.nodePath??m.execPath,this.cliPath=t.cliPath,this.adapter=R(this.platform)}normalizeRootDir(t){return h.resolve(String(t??"").trim())}buildDescriptor(t,i,r=""){const e=this.normalizeRootDir(t);return{schema_version:1,platform:this.platform,service_id:P(e,this.platform),node_path:this.nodePath,cli_path:this.cliPath,cli_hash:this.computeFileHash(this.cliPath)??"",definition_path:r,config_dir:i??"",installed_at:0,updated_at:0}}createStore(t){return new z(b(this.normalizeRootDir(t)))}toAdapterPayload(t){return{serviceID:t.service_id,nodePath:t.node_path,cliPath:t.cli_path,configDir:t.config_dir||void 0,environmentPath:this.buildServiceEnvironmentPath()}}async resolveServiceLogPaths(t,i){if(this.platform==="darwin"){const r=h.join(this.homeDir,"Library","Logs","grix-connector");return await p(r,{recursive:!0}),{stdoutPath:h.join(r,`${i}.out.log`),stderrPath:h.join(r,`${i}.err.log`)}}return await p(h.join(t,"service"),{recursive:!0}),{stdoutPath:$(t),stderrPath:I(t)}}buildServiceEnvironmentPath(){const t=[h.dirname(this.nodePath),"/opt/homebrew/bin","/usr/local/bin","/usr/bin","/bin","/usr/sbin","/sbin"],i=String(m.env.PATH??"").split(h.delimiter).filter(s=>s&&h.isAbsolute(s)).filter(s=>!s.includes("/node_modules/.bin")).filter(s=>!s.includes("/node-gyp-bin")),r=[],e=new Set;for(const s of[...t,...i]){if(e.has(s))continue;if(e.add(s),[...r,s].join(h.delimiter).length>2048)break;r.push(s)}return r.join(h.delimiter)}toStartParams(t){return{serviceID:t.service_id,definitionPath:t.definition_path,uid:this.uid,homeDir:this.homeDir,runCommand:n}}toStopParams(t){return{serviceID:t.service_id,uid:this.uid,homeDir:this.homeDir,runCommand:n}}toRestartParams(t){return{serviceID:t.service_id,definitionPath:t.definition_path,uid:this.uid,homeDir:this.homeDir,runCommand:n}}toUninstallParams(t){return{serviceID:t.service_id,definitionPath:t.definition_path,uid:this.uid,homeDir:this.homeDir,runCommand:n}}async loadDescriptor(t){const i=this.createStore(t),r=await i.load();if(!r)throw new Error("\u540E\u53F0\u670D\u52A1\u8FD8\u6CA1\u6709\u5B89\u88C5\uFF0C\u8BF7\u5148\u6267\u884C service install\u3002");return{store:i,descriptor:r}}isDescriptorCurrent(t){if(t.node_path!==this.nodePath||t.cli_path!==this.cliPath||!t.cli_hash)return!1;const i=this.computeFileHash(t.cli_path);return i!==null&&i===t.cli_hash}computeFileHash(t){try{const i=D.readFileSync(t);return w("sha256").update(i).digest("hex")}catch{return null}}async refreshDescriptor(t,i){const r=this.normalizeRootDir(t);await p(h.join(r,"service"),{recursive:!0});const e={...i,platform:this.platform,service_id:i.service_id||P(r,this.platform),node_path:this.nodePath,cli_path:this.cliPath,cli_hash:this.computeFileHash(this.cliPath)??"",config_dir:i.config_dir},s=await this.resolveServiceLogPaths(r,e.service_id),a=await this.adapter.install({...this.toAdapterPayload(e),stdoutPath:s.stdoutPath,stderrPath:s.stderrPath,homeDir:this.homeDir,uid:this.uid,runCommand:n}),o={...e,installed_at:i.installed_at,definition_path:a?.definitionPath||e.definition_path,updated_at:Date.now()};return await this.createStore(r).save(o),o}async resolveActiveDescriptor(t){const{descriptor:i}=await this.loadDescriptor(t);return this.isDescriptorCurrent(i)?i:this.refreshDescriptor(t,i)}async discoverConflictingServices(t){const i=this.normalizeRootDir(t),r=P(i,this.platform);return(await this.adapter.discoverServices({homeDir:this.homeDir,runCommand:n})).filter(s=>s.serviceID!==r)}async cleanupConflictingServices(t){const i=await this.discoverConflictingServices(t);if(i.length>0){const e=i.map(s=>s.serviceID).join(", ");console.log(`\u53D1\u73B0 ${i.length} \u4E2A\u6B8B\u7559\u670D\u52A1\uFF0C\u6B63\u5728\u68C0\u67E5: ${e}`)}let r=0;for(const e of i){if(await this.adapter.isServiceLoaded({serviceID:e.serviceID,uid:this.uid,homeDir:this.homeDir,runCommand:n})){console.log(`\u8DF3\u8FC7\u6B63\u5728\u8FD0\u884C\u7684\u670D\u52A1: ${e.serviceID}`);continue}try{await this.adapter.stop({serviceID:e.serviceID,uid:this.uid,homeDir:this.homeDir,runCommand:n})}catch{}try{await this.adapter.uninstall({serviceID:e.serviceID,definitionPath:e.definitionPath,uid:this.uid,homeDir:this.homeDir,runCommand:n}),r++}catch{}}return r}async waitForDaemonStarted(t,i={}){const{oldPid:r=0,timeoutMs:e=2e4}=i,s=Date.now();for(;Date.now()-s<e;){const c=g(t.daemonLockFile);if(c&&c.pid>0&&y(c.pid)&&c.pid!==r)return;const f=l(t.daemonStatusFile);if(f.running&&f.pid>0&&f.pid!==r)return;await _(100)}const a=g(t.daemonLockFile),o=l(t.daemonStatusFile),d=[`daemon start timeout (${e}ms)`,`status=${JSON.stringify(o)}`,`lock=${a?JSON.stringify(a):"missing"}`,this.tailLogForError("stderr",i.stderrLogFile??t.stderrLogFile),this.tailLogForError("stdout",i.stdoutLogFile??t.stdoutLogFile)].filter(Boolean);throw new Error(d.join(`
2
+ `))}tailLogForError(t,i,r=20){if(!D.existsSync(i))return`${t}=missing`;try{const e=D.readFileSync(i,"utf8").trim();if(!e)return`${t}=empty`;const s=e.split(/\r?\n/).slice(-r);return`${t}:
3
3
  ${s.join(`
4
- `)}`}catch(e){return`${t}=unreadable: ${e instanceof Error?e.message:String(e)}`}}async install(t){const i=this.normalizeRootDir(t.rootDir);await this.cleanupConflictingServices(i),await p(c.join(i,"service"),{recursive:!0});const r=this.buildDescriptor(i,t.configDir),e=await this.resolveServiceLogPaths(i,r.service_id),s=await this.adapter.install({...this.toAdapterPayload(r),stdoutPath:e.stdoutPath,stderrPath:e.stderrPath,homeDir:this.homeDir,uid:this.uid,runCommand:n}),a={...r,definition_path:s?.definitionPath??"",installed_at:Date.now(),updated_at:Date.now()};await this.createStore(i).save(a),await this.adapter.start(this.toStartParams(a));const o=u(i);return await this.waitForDaemonStarted(o,{stdoutLogFile:e.stdoutPath,stderrLogFile:e.stderrPath}),this.status({rootDir:i})}async start(t){const i=this.normalizeRootDir(t.rootDir);await this.cleanupConflictingServices(i);const r=u(i),e=l(r.daemonStatusFile),a=await this.createStore(i).load();if(e.running&&a&&this.isDescriptorCurrent(a))return this.status({rootDir:i});const o=a?this.isDescriptorCurrent(a)&&e.running?a:await this.refreshDescriptor(i,a):await this.resolveActiveDescriptor(i),d=await this.resolveServiceLogPaths(i,o.service_id);try{e.running?await this.adapter.restart(this.toRestartParams(o)):await this.adapter.start(this.toStartParams(o))}catch(h){if(h instanceof Error&&h.message.includes("plist missing"))await this.adapter.install({...this.toAdapterPayload(o),stdoutPath:d.stdoutPath,stderrPath:d.stderrPath,homeDir:this.homeDir,uid:this.uid,runCommand:n}),await this.adapter.start(this.toStartParams(o));else throw h}return await this.waitForDaemonStarted(r,{oldPid:e.pid,stdoutLogFile:d.stdoutPath,stderrLogFile:d.stderrPath}),this.status({rootDir:i})}async stop(t){const{descriptor:i}=await this.loadDescriptor(t.rootDir),r=u(i.config_dir||this.normalizeRootDir(t.rootDir)),e=l(r.daemonStatusFile);await this.adapter.stop(this.toStopParams(i)),e.running&&e.pid&&(await v(e.pid,{timeoutMs:5e3})||(await L(e.pid,{platform:this.platform,runCommandImpl:n}),await v(e.pid,{timeoutMs:5e3})));const s=await F(i.cli_path);return s>0&&console.log(`\u6E05\u7406\u4E86 ${s} \u4E2A\u6B8B\u7559 daemon \u8FDB\u7A0B`),this.status({rootDir:t.rootDir})}async restart(t){const i=this.normalizeRootDir(t.rootDir);await this.cleanupConflictingServices(i);const r=await this.resolveActiveDescriptor(i),e=u(i),s=l(e.daemonStatusFile),a=await this.resolveServiceLogPaths(i,r.service_id);try{await this.adapter.restart(this.toRestartParams(r))}catch(o){if(o instanceof Error&&o.message.includes("plist missing"))await this.adapter.install({...this.toAdapterPayload(r),stdoutPath:a.stdoutPath,stderrPath:a.stderrPath,homeDir:this.homeDir,uid:this.uid,runCommand:n}),await this.adapter.start(this.toStartParams(r));else throw o}return await this.waitForDaemonStarted(e,{oldPid:s.pid,stdoutLogFile:a.stdoutPath,stderrLogFile:a.stderrPath}),this.status({rootDir:i})}async uninstall(t){const i=this.normalizeRootDir(t.rootDir),r=this.createStore(i),e=await r.load();return e&&(await this.adapter.uninstall(this.toUninstallParams(e)),await r.clear()),this.status({rootDir:i})}async status(t){const i=this.normalizeRootDir(t.rootDir),e=await this.createStore(i).load(),s=u(i),a=l(s.daemonStatusFile);return e?{installed:!0,install_state:this.isDescriptorCurrent(e)?"current":"stale",service_kind:this.adapter.kind,service_id:e.service_id,definition_path:e.definition_path,root_dir:i,daemon_state:a.running?"running":a.state,pid:a.pid,connection_state:a.connection_state,updated_at:a.updated_at}:{installed:!1,install_state:"missing",service_kind:this.adapter.kind,root_dir:i,daemon_state:a.running?"running":a.state,pid:a.pid}}}export{G as ServiceManager};
4
+ `)}`}catch(e){return`${t}=unreadable: ${e instanceof Error?e.message:String(e)}`}}async install(t){const i=this.normalizeRootDir(t.rootDir);await this.cleanupConflictingServices(i),await p(h.join(i,"service"),{recursive:!0});const r=this.buildDescriptor(i,t.configDir),e=await this.resolveServiceLogPaths(i,r.service_id),s=await this.adapter.install({...this.toAdapterPayload(r),stdoutPath:e.stdoutPath,stderrPath:e.stderrPath,homeDir:this.homeDir,uid:this.uid,runCommand:n}),a={...r,definition_path:s?.definitionPath??"",installed_at:Date.now(),updated_at:Date.now()};await this.createStore(i).save(a),await this.adapter.start(this.toStartParams(a));const o=u(i);return await this.waitForDaemonStarted(o,{stdoutLogFile:e.stdoutPath,stderrLogFile:e.stderrPath}),this.status({rootDir:i})}async start(t){const i=this.normalizeRootDir(t.rootDir);await this.cleanupConflictingServices(i);const r=u(i),e=l(r.daemonStatusFile),a=await this.createStore(i).load();if(e.running&&a&&this.isDescriptorCurrent(a))return this.status({rootDir:i});const o=a?this.isDescriptorCurrent(a)&&e.running?a:await this.refreshDescriptor(i,a):await this.resolveActiveDescriptor(i),d=await this.resolveServiceLogPaths(i,o.service_id);try{e.running?await this.adapter.restart(this.toRestartParams(o)):await this.adapter.start(this.toStartParams(o))}catch(c){if(c instanceof Error&&c.message.includes("plist missing"))await this.adapter.install({...this.toAdapterPayload(o),stdoutPath:d.stdoutPath,stderrPath:d.stderrPath,homeDir:this.homeDir,uid:this.uid,runCommand:n}),await this.adapter.start(this.toStartParams(o));else throw c}return await this.waitForDaemonStarted(r,{oldPid:e.pid,stdoutLogFile:d.stdoutPath,stderrLogFile:d.stderrPath}),this.status({rootDir:i})}async stop(t){const{descriptor:i}=await this.loadDescriptor(t.rootDir),r=u(i.config_dir||this.normalizeRootDir(t.rootDir)),e=l(r.daemonStatusFile);await this.adapter.stop(this.toStopParams(i)),e.running&&e.pid&&(await v(e.pid,{timeoutMs:5e3})||(await L(e.pid,{platform:this.platform,runCommandImpl:n}),await v(e.pid,{timeoutMs:5e3})));const s=await F(i.cli_path);return s>0&&console.log(`\u6E05\u7406\u4E86 ${s} \u4E2A\u6B8B\u7559 daemon \u8FDB\u7A0B`),this.status({rootDir:t.rootDir})}async restart(t){const i=this.normalizeRootDir(t.rootDir);await this.cleanupConflictingServices(i);const r=await this.resolveActiveDescriptor(i),e=u(i),s=l(e.daemonStatusFile),a=await this.resolveServiceLogPaths(i,r.service_id);try{await this.adapter.restart(this.toRestartParams(r))}catch(o){if(o instanceof Error&&o.message.includes("plist missing"))await this.adapter.install({...this.toAdapterPayload(r),stdoutPath:a.stdoutPath,stderrPath:a.stderrPath,homeDir:this.homeDir,uid:this.uid,runCommand:n}),await this.adapter.start(this.toStartParams(r));else throw o}return await this.waitForDaemonStarted(e,{oldPid:s.pid,stdoutLogFile:a.stdoutPath,stderrLogFile:a.stderrPath}),this.status({rootDir:i})}async uninstall(t){const i=this.normalizeRootDir(t.rootDir),r=this.createStore(i),e=await r.load();return e&&(await this.adapter.uninstall(this.toUninstallParams(e)),await r.clear()),this.status({rootDir:i})}async status(t){const i=this.normalizeRootDir(t.rootDir),e=await this.createStore(i).load(),s=u(i),a=l(s.daemonStatusFile);return e?{installed:!0,install_state:this.isDescriptorCurrent(e)?"current":"stale",service_kind:this.adapter.kind,service_id:e.service_id,definition_path:e.definition_path,root_dir:i,daemon_state:a.running?"running":a.state,pid:a.pid,connection_state:a.connection_state,updated_at:a.updated_at}:{installed:!1,install_state:"missing",service_kind:this.adapter.kind,root_dir:i,daemon_state:a.running?"running":a.state,pid:a.pid}}}export{G as ServiceManager};
@@ -1 +1 @@
1
- import{createHash as u}from"node:crypto";import o from"node:os";import t from"node:path";import{normalizeString as a}from"../core/util/normalize-string.js";const i="com.dhfpub.grix-connector.daemon.",c="grix-connector-daemon-",s="GrixConnectorDaemon-";function f(r){return u("sha1").update(a(r)||"default").digest("hex").slice(0,12)}function x(r,e=process.platform){const n=f(r);return e==="win32"?`${s}${n}`:e==="darwin"?`${i}${n}`:`${c}${n}`}function h(r=process.platform){return r==="win32"?s:r==="darwin"?i:c}function l(r){return r.replace(/&amp;/g,"&").replace(/&lt;/g,"<").replace(/&gt;/g,">").replace(/&quot;/g,'"')}function v(r){const e=[...r.matchAll(/<string>([^<]+)<\/string>/g)].map(n=>l(n[1]));for(let n=0;n<e.length-1;n+=1)if(e[n]==="--config-dir")return e[n+1];return null}function S(r){const e=r.match(/--config-dir\s+(?:'([^']*)'|"([^"]*)"|(\S+))/);return e?e[1]??e[2]??e[3]??null:null}function I(r){return t.join(r,"daemon-service.json")}function P(r){return t.join(r,"service","daemon.out.log")}function E(r){return t.join(r,"service","daemon.err.log")}function R(r,e){return t.join(e??o.homedir(),"Library","LaunchAgents",`${r}.plist`)}function $(r,e){return t.join(e??o.homedir(),".config","systemd","user",`${r}.service`)}export{i as SERVICE_PREFIX_DARWIN,c as SERVICE_PREFIX_LINUX,s as SERVICE_PREFIX_WIN32,x as buildServiceID,h as getServicePrefix,v as parseConfigDirFromPlistXML,S as parseConfigDirFromSystemdUnit,$ as resolveLinuxUserUnitPath,R as resolveMacOSLaunchAgentPath,I as resolveServiceInstallRecordPath,E as resolveServiceStderrPath,P as resolveServiceStdoutPath};
1
+ import{createHash as u}from"node:crypto";import o from"node:os";import t from"node:path";import{normalizeString as a}from"../core/util/normalize-string.js";const i="com.dhfpub.grix-connector.daemon.",c="grix-connector-daemon-",s="GrixConnectorDaemon-";function f(r){return u("sha1").update(a(r)||"default").digest("hex").slice(0,12)}function x(r,e=process.platform){const n=f(r);return e==="win32"?`${s}${n}`:e==="darwin"?`${i}${n}`:`${c}${n}`}function h(r=process.platform){return r==="win32"?s:r==="darwin"?i:c}function l(r){return r.replace(/&amp;/g,"&").replace(/&lt;/g,"<").replace(/&gt;/g,">").replace(/&quot;/g,'"')}function v(r){const e=[...r.matchAll(/<string>([^<]+)<\/string>/g)].map(n=>l(n[1]));for(let n=0;n<e.length-1;n+=1)if(e[n]==="--config-dir")return e[n+1];return null}function S(r){const e=r.match(/--config-dir\s+(?:'([^']*)'|"([^"]*)"|(\S+))/);return e?e[1]??e[2]??e[3]??null:null}function I(r){return t.join(r,"daemon-service.json")}function P(r){return t.join(r,"service","daemon.out.log")}function E(r){return t.join(r,"service","daemon.err.log")}function $(r,e){return t.join(e??o.homedir(),"Library","LaunchAgents",`${r}.plist`)}function j(r,e){return t.join(e??o.homedir(),".config","systemd","user",`${r}.service`)}function R(r,e){return t.join(e??o.homedir(),".grix","service",`${r}.bare.json`)}export{i as SERVICE_PREFIX_DARWIN,c as SERVICE_PREFIX_LINUX,s as SERVICE_PREFIX_WIN32,x as buildServiceID,h as getServicePrefix,v as parseConfigDirFromPlistXML,S as parseConfigDirFromSystemdUnit,R as resolveBareDaemonMarkerPath,j as resolveLinuxUserUnitPath,$ as resolveMacOSLaunchAgentPath,I as resolveServiceInstallRecordPath,E as resolveServiceStderrPath,P as resolveServiceStdoutPath};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grix-connector",
3
- "version": "2.1.4",
3
+ "version": "2.2.1",
4
4
  "description": "Connect local AI coding agents (Claude, Codex, Gemini, Qwen, DeepSeek, Cursor, OpenCode, Pi, OpenHuman, Reasonix) to the Grix scheduling platform. Also serves as an OpenClaw plugin for Grix channel transport.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",