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 +31 -0
- package/dist/core/runtime/index.js +1 -1
- package/dist/core/runtime/port-bind.js +5 -0
- package/dist/grix.js +10 -5
- package/dist/mcp/stream-http/security.js +1 -1
- package/dist/service/platform-adapter.js +19 -12
- package/dist/service/service-manager.js +3 -3
- package/dist/service/service-paths.js +1 -1
- package/package.json +1 -1
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
|
|
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
|
|
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
|
|
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=
|
|
31
|
-
Valid commands: ${
|
|
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(
|
|
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
|
|
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,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}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>${
|
|
6
|
+
<string>${y(t.serviceID)}</string>
|
|
7
7
|
<key>ProgramArguments</key>
|
|
8
8
|
<array>
|
|
9
|
-
${r.map(e=>` <string>${
|
|
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>${
|
|
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>${
|
|
21
|
+
<string>${y(t.environmentPath)}</string>
|
|
22
22
|
</dict>
|
|
23
23
|
`:""} <key>StandardOutPath</key>
|
|
24
|
-
<string>${
|
|
24
|
+
<string>${y(t.stdoutPath)}</string>
|
|
25
25
|
<key>StandardErrorPath</key>
|
|
26
|
-
<string>${
|
|
26
|
+
<string>${y(t.stderrPath)}</string>
|
|
27
27
|
</dict>
|
|
28
28
|
</plist>
|
|
29
|
-
`}function
|
|
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=${
|
|
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
|
|
45
|
-
`);return{path:e,content:
|
|
46
|
-
`)}function
|
|
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
|
|
2
|
-
`))}tailLogForError(t,i,r=20){if(!
|
|
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(
|
|
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(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/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
|
|
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(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/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
|
|
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",
|