claw-subagent-service 0.0.14 → 0.0.16
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/cli.js +8 -12
- package/package.json +1 -1
- package/scripts/install-silent.js +7 -11
- package/scripts/post-install.js +39 -18
- package/scripts/pre-uninstall.js +1 -1
- package/service/modules/service-manager.js +6 -15
- package/version.json +2 -2
package/cli.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* node cli.js --status # 查看服务状态
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
const { spawn, exec
|
|
15
|
+
const { spawn, exec } = require('child_process');
|
|
16
16
|
const path = require('path');
|
|
17
17
|
const fs = require('fs');
|
|
18
18
|
const os = require('os');
|
|
@@ -57,14 +57,10 @@ function installService() {
|
|
|
57
57
|
: `"${execPath}" "${DAEMON_PATH}"`;
|
|
58
58
|
|
|
59
59
|
// 先停止并删除旧服务(避免文件锁)
|
|
60
|
-
exec(`net stop "${SERVICE_NAME}" 2>nul & sc delete "${SERVICE_NAME}" 2>nul`, () => {
|
|
61
|
-
//
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
'binPath=', binPath,
|
|
65
|
-
'start=', 'auto',
|
|
66
|
-
'displayname=', 'OpenClaw Guard'
|
|
67
|
-
], { windowsHide: true }, (err2) => {
|
|
60
|
+
exec(`net stop "${SERVICE_NAME}" 2>nul & sc.exe delete "${SERVICE_NAME}" 2>nul`, () => {
|
|
61
|
+
// sc.exe 是 cmd 时代的工具,必须用 exec(走 cmd 解析)才能正确传递 binPath
|
|
62
|
+
const createCmd = `sc.exe create "${SERVICE_NAME}" binPath= ${binPath} start= auto displayname= "OpenClaw Guard"`;
|
|
63
|
+
exec(createCmd, { windowsHide: true }, (err2) => {
|
|
68
64
|
if (err2) return console.error(`[CLI] 服务安装失败: ${err2.message}`);
|
|
69
65
|
console.log('[CLI] 服务安装成功');
|
|
70
66
|
exec(`net start "${SERVICE_NAME}"`, (err3) => {
|
|
@@ -150,8 +146,8 @@ function uninstallService() {
|
|
|
150
146
|
svc.on('uninstall', () => console.log('[CLI] 服务卸载成功'));
|
|
151
147
|
svc.uninstall();
|
|
152
148
|
} catch (err) {
|
|
153
|
-
console.log('[CLI] 使用 sc 命令卸载服务...');
|
|
154
|
-
exec(`sc stop ${SERVICE_NAME} 2>nul & sc delete ${SERVICE_NAME}`, (err2) => {
|
|
149
|
+
console.log('[CLI] 使用 sc.exe 命令卸载服务...');
|
|
150
|
+
exec(`sc.exe stop ${SERVICE_NAME} 2>nul & sc.exe delete ${SERVICE_NAME}`, (err2) => {
|
|
155
151
|
if (err2) console.error(`[CLI] 卸载失败: ${err2.message}`);
|
|
156
152
|
else console.log('[CLI] 服务卸载成功');
|
|
157
153
|
});
|
|
@@ -207,7 +203,7 @@ function checkStatus() {
|
|
|
207
203
|
let cmd;
|
|
208
204
|
|
|
209
205
|
if (platform === 'win32') {
|
|
210
|
-
cmd = `sc query ${SERVICE_NAME}`;
|
|
206
|
+
cmd = `sc.exe query ${SERVICE_NAME}`;
|
|
211
207
|
} else if (platform === 'linux') {
|
|
212
208
|
cmd = `systemctl status ${SERVICE_NAME}`;
|
|
213
209
|
} else if (platform === 'darwin') {
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
|
-
const { exec
|
|
2
|
+
const { exec } = require('child_process');
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
|
|
5
5
|
const ROOT = process.argv[2] || process.env.SILENT_SERVICE_DIR || path.join(__dirname, '..');
|
|
@@ -45,16 +45,12 @@ if (platform === 'win32') {
|
|
|
45
45
|
setTimeout(() => finish(1, '安装操作超时'), 60000);
|
|
46
46
|
|
|
47
47
|
// 先停止并删除旧服务(避免文件锁导致更新失败)
|
|
48
|
-
exec(`net stop "${SERVICE_NAME}" 2>nul & sc delete "${SERVICE_NAME}" 2>nul`, () => {
|
|
48
|
+
exec(`net stop "${SERVICE_NAME}" 2>nul & sc.exe delete "${SERVICE_NAME}" 2>nul`, () => {
|
|
49
49
|
const binPath = `"${process.execPath}" "${DAEMON_PATH}"`;
|
|
50
50
|
|
|
51
|
-
//
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
'binPath=', binPath,
|
|
55
|
-
'start=', 'auto',
|
|
56
|
-
'displayname=', 'Node.js 静默后台服务'
|
|
57
|
-
], { windowsHide: true }, (err) => {
|
|
51
|
+
// sc.exe 是 cmd 时代的工具,必须用 exec(走 cmd 解析)才能正确传递 binPath
|
|
52
|
+
const createCmd = `sc.exe create "${SERVICE_NAME}" binPath= ${binPath} start= auto displayname= "Node.js 静默后台服务"`;
|
|
53
|
+
exec(createCmd, { windowsHide: true }, (err) => {
|
|
58
54
|
if (err) {
|
|
59
55
|
log(`创建服务失败: ${err.message}`);
|
|
60
56
|
return finish(1, `创建服务失败: ${err.message}`);
|
|
@@ -62,14 +58,14 @@ if (platform === 'win32') {
|
|
|
62
58
|
log('服务注册成功');
|
|
63
59
|
|
|
64
60
|
// 设置恢复策略:崩溃后自动重启
|
|
65
|
-
const recoveryCmd = `sc failure "${SERVICE_NAME}" reset= 0 actions= restart/0/restart/0/restart/0`;
|
|
61
|
+
const recoveryCmd = `sc.exe failure "${SERVICE_NAME}" reset= 0 actions= restart/0/restart/0/restart/0`;
|
|
66
62
|
exec(recoveryCmd, (err) => {
|
|
67
63
|
if (err) log(`设置恢复策略失败: ${err.message}`);
|
|
68
64
|
else log('恢复策略已设置:服务崩溃后系统自动无限重启');
|
|
69
65
|
});
|
|
70
66
|
|
|
71
67
|
// 设置自动启动
|
|
72
|
-
exec(`sc config "${SERVICE_NAME}" start= auto`, (err) => {
|
|
68
|
+
exec(`sc.exe config "${SERVICE_NAME}" start= auto`, (err) => {
|
|
73
69
|
if (err) log(`设置自动启动失败: ${err.message}`);
|
|
74
70
|
else log('启动类型已设为:自动');
|
|
75
71
|
});
|
package/scripts/post-install.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* npm postinstall 钩子
|
|
3
3
|
* 全局安装完成后自动注册并启动 Windows 服务
|
|
4
4
|
*/
|
|
5
|
-
const { exec,
|
|
5
|
+
const { exec, execSync } = require('child_process');
|
|
6
6
|
const path = require('path');
|
|
7
7
|
|
|
8
8
|
const SERVICE_NAME = 'claw-subagent-service';
|
|
@@ -15,20 +15,45 @@ function isGlobalInstall() {
|
|
|
15
15
|
return true;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
// 方法2: 检查包路径是否在全局 node_modules
|
|
18
|
+
// 方法2: 检查包路径是否在全局 node_modules 下(支持 nvm)
|
|
19
19
|
try {
|
|
20
|
-
const
|
|
21
|
-
const globalModules = path.join(globalPrefix, 'node_modules');
|
|
20
|
+
const globalRoot = execSync('npm root -g', { encoding: 'utf8', timeout: 5000 }).trim();
|
|
22
21
|
const pkgPath = path.join(__dirname, '..');
|
|
23
22
|
const normalizedPkg = path.normalize(pkgPath).toLowerCase();
|
|
24
|
-
const normalizedGlobal = path.normalize(
|
|
23
|
+
const normalizedGlobal = path.normalize(globalRoot).toLowerCase();
|
|
25
24
|
const isGlobal = normalizedPkg.startsWith(normalizedGlobal);
|
|
26
|
-
console.log(`[postinstall] 全局安装检测: pkgPath=${normalizedPkg},
|
|
27
|
-
return
|
|
25
|
+
console.log(`[postinstall] 全局安装检测: pkgPath=${normalizedPkg}, globalRoot=${normalizedGlobal}, result=${isGlobal}`);
|
|
26
|
+
if (isGlobal) return true;
|
|
28
27
|
} catch (e) {
|
|
29
|
-
console.log(`[postinstall]
|
|
28
|
+
console.log(`[postinstall] npm root -g 检测失败: ${e.message}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// 方法3: 检查包路径是否在 node 安装目录的 node_modules 下(nvm 场景)
|
|
32
|
+
try {
|
|
33
|
+
const nodeDir = path.dirname(process.execPath);
|
|
34
|
+
const nodeModulesDir = path.join(nodeDir, 'node_modules');
|
|
35
|
+
const pkgPath = path.join(__dirname, '..');
|
|
36
|
+
const normalizedPkg = path.normalize(pkgPath).toLowerCase();
|
|
37
|
+
const normalizedNodeModules = path.normalize(nodeModulesDir).toLowerCase();
|
|
38
|
+
const isGlobal = normalizedPkg.startsWith(normalizedNodeModules);
|
|
39
|
+
console.log(`[postinstall] nvm 检测: pkgPath=${normalizedPkg}, nodeModules=${normalizedNodeModules}, result=${isGlobal}`);
|
|
40
|
+
if (isGlobal) return true;
|
|
41
|
+
} catch (e) {
|
|
42
|
+
console.log(`[postinstall] nvm 检测失败: ${e.message}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 方法4: 兜底 - 如果包在 node_modules 下且路径较深,认为是全局安装
|
|
46
|
+
const pkgPath = path.normalize(path.join(__dirname, '..')).toLowerCase();
|
|
47
|
+
const hasNodeModules = pkgPath.includes('node_modules');
|
|
48
|
+
const notInCwd = !process.cwd().toLowerCase().includes(pkgPath);
|
|
49
|
+
console.log(`[postinstall] 兜底检测: hasNodeModules=${hasNodeModules}, notInCwd=${notInCwd}`);
|
|
50
|
+
if (hasNodeModules && notInCwd) {
|
|
51
|
+
console.log('[postinstall] 兜底检测通过,按全局安装处理');
|
|
30
52
|
return true;
|
|
31
53
|
}
|
|
54
|
+
|
|
55
|
+
console.log('[postinstall] 所有检测均失败,按本地安装处理');
|
|
56
|
+
return false;
|
|
32
57
|
}
|
|
33
58
|
|
|
34
59
|
function isWindowsAdmin() {
|
|
@@ -59,15 +84,11 @@ function installAndStartService() {
|
|
|
59
84
|
|
|
60
85
|
// 先停止并删除旧服务(避免冲突)
|
|
61
86
|
try { execSync(`net stop "${SERVICE_NAME}" 2>nul`, { stdio: 'ignore', timeout: 10000 }); } catch (e) {}
|
|
62
|
-
try { execSync(`sc delete "${SERVICE_NAME}" 2>nul`, { stdio: 'ignore', timeout: 10000 }); } catch (e) {}
|
|
63
|
-
|
|
64
|
-
//
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
'binPath=', binPath,
|
|
68
|
-
'start=', 'auto',
|
|
69
|
-
'displayname=', 'OpenClaw Guard'
|
|
70
|
-
], { windowsHide: true }, (err) => {
|
|
87
|
+
try { execSync(`sc.exe delete "${SERVICE_NAME}" 2>nul`, { stdio: 'ignore', timeout: 10000 }); } catch (e) {}
|
|
88
|
+
|
|
89
|
+
// sc.exe 是 cmd 时代的工具,必须用 exec(走 cmd 解析)才能正确传递 binPath
|
|
90
|
+
const createCmd = `sc.exe create "${SERVICE_NAME}" binPath= ${binPath} start= auto displayname= "OpenClaw Guard"`;
|
|
91
|
+
exec(createCmd, { windowsHide: true }, (err) => {
|
|
71
92
|
if (err) {
|
|
72
93
|
console.error(`[postinstall] 注册服务失败: ${err.message}`);
|
|
73
94
|
return;
|
|
@@ -75,7 +96,7 @@ function installAndStartService() {
|
|
|
75
96
|
console.log('[postinstall] 服务注册成功');
|
|
76
97
|
|
|
77
98
|
// 设置恢复策略
|
|
78
|
-
exec(`sc failure "${SERVICE_NAME}" reset= 0 actions= restart/0/restart/0/restart/0`, (err) => {
|
|
99
|
+
exec(`sc.exe failure "${SERVICE_NAME}" reset= 0 actions= restart/0/restart/0/restart/0`, (err) => {
|
|
79
100
|
if (!err) console.log('[postinstall] 恢复策略已设置');
|
|
80
101
|
});
|
|
81
102
|
|
package/scripts/pre-uninstall.js
CHANGED
|
@@ -12,7 +12,7 @@ if (process.platform === 'win32') {
|
|
|
12
12
|
execSync(`net stop "${name}" 2>nul`, { stdio: 'ignore', timeout: 10000 });
|
|
13
13
|
} catch (e) {}
|
|
14
14
|
try {
|
|
15
|
-
execSync(`sc delete "${name}" 2>nul`, { stdio: 'ignore', timeout: 10000 });
|
|
15
|
+
execSync(`sc.exe delete "${name}" 2>nul`, { stdio: 'ignore', timeout: 10000 });
|
|
16
16
|
} catch (e) {}
|
|
17
17
|
}
|
|
18
18
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* 系统服务管理器
|
|
3
3
|
* 支持 Windows/Linux/macOS 系统服务注册
|
|
4
4
|
*/
|
|
5
|
-
const { spawn, exec
|
|
5
|
+
const { spawn, exec } = require('child_process');
|
|
6
6
|
const fs = require('fs');
|
|
7
7
|
const path = require('path');
|
|
8
8
|
|
|
@@ -141,7 +141,7 @@ class ServiceManager {
|
|
|
141
141
|
try {
|
|
142
142
|
switch (this.platform) {
|
|
143
143
|
case 'win32':
|
|
144
|
-
return await this.execCommand(`sc query ${this.serviceName}`);
|
|
144
|
+
return await this.execCommand(`sc.exe query ${this.serviceName}`);
|
|
145
145
|
case 'linux':
|
|
146
146
|
return await this.execCommand(`systemctl status ${this.serviceName}`);
|
|
147
147
|
case 'darwin':
|
|
@@ -161,20 +161,11 @@ class ServiceManager {
|
|
|
161
161
|
|
|
162
162
|
// 先停止并删除旧服务(避免文件锁导致更新失败)
|
|
163
163
|
try { await this.execCommand(`net stop "${this.serviceName}" 2>nul`); } catch (e) {}
|
|
164
|
-
try { await this.execCommand(`sc delete "${this.serviceName}" 2>nul`); } catch (e) {}
|
|
164
|
+
try { await this.execCommand(`sc.exe delete "${this.serviceName}" 2>nul`); } catch (e) {}
|
|
165
165
|
|
|
166
|
-
//
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
'create', this.serviceName,
|
|
170
|
-
'binPath=', binPath,
|
|
171
|
-
'start=', 'auto',
|
|
172
|
-
'displayname=', this.serviceDesc
|
|
173
|
-
], { windowsHide: true }, (err) => {
|
|
174
|
-
if (err) reject(err);
|
|
175
|
-
else resolve();
|
|
176
|
-
});
|
|
177
|
-
});
|
|
166
|
+
// sc.exe 是 cmd 时代的工具,必须用 exec(走 cmd 解析)才能正确传递 binPath
|
|
167
|
+
const createCmd = `sc.exe create "${this.serviceName}" binPath= ${binPath} start= auto displayname= "${this.serviceDesc}"`;
|
|
168
|
+
await this.execCommand(createCmd);
|
|
178
169
|
this.log?.info('[ServiceManager] Windows 服务注册成功');
|
|
179
170
|
|
|
180
171
|
// 启动服务
|
package/version.json
CHANGED