claw-subagent-service 0.0.15 → 0.0.17
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 +18 -11
- package/package.json +1 -1
- package/scripts/install-silent.js +8 -7
- package/scripts/post-install.js +31 -28
- package/scripts/pre-uninstall.js +1 -1
- package/service/modules/service-manager.js +6 -5
- package/version.json +2 -2
package/cli.js
CHANGED
|
@@ -51,17 +51,24 @@ function installService() {
|
|
|
51
51
|
|
|
52
52
|
if (platform === 'win32') {
|
|
53
53
|
// Windows: 使用 sc 命令直接创建服务,避免 node-windows 在包目录生成被锁定的 wrapper
|
|
54
|
-
console.log('[CLI] 使用 sc 命令安装服务...');
|
|
54
|
+
console.log('[CLI] 使用 sc.exe 命令安装服务...');
|
|
55
|
+
// sc.exe binPath 格式:外层引号包裹整个值,内层路径用转义引号
|
|
55
56
|
const binPath = process.pkg
|
|
56
|
-
?
|
|
57
|
-
:
|
|
57
|
+
? `\\"${execPath}\\" --run`
|
|
58
|
+
: `\\"${execPath}\\" \\"${DAEMON_PATH}\\"`;
|
|
58
59
|
|
|
59
60
|
// 先停止并删除旧服务(避免文件锁)
|
|
60
|
-
exec(`net stop "${SERVICE_NAME}" 2>nul & sc delete "${SERVICE_NAME}" 2>nul`, () => {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
exec(createCmd, { windowsHide: true }, (err2) => {
|
|
64
|
-
if (err2)
|
|
61
|
+
exec(`net stop "${SERVICE_NAME}" 2>nul & sc.exe delete "${SERVICE_NAME}" 2>nul`, () => {
|
|
62
|
+
const createCmd = `sc.exe create "${SERVICE_NAME}" binPath= "${binPath}" start= auto displayname= "OpenClaw Guard"`;
|
|
63
|
+
console.log(`[CLI] 执行: ${createCmd}`);
|
|
64
|
+
exec(createCmd, { windowsHide: true }, (err2, stdout, stderr) => {
|
|
65
|
+
if (err2) {
|
|
66
|
+
console.error(`[CLI] 服务安装失败: ${err2.message}`);
|
|
67
|
+
console.error(`[CLI] stdout: ${stdout}`);
|
|
68
|
+
console.error(`[CLI] stderr: ${stderr}`);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
console.log(`[CLI] 注册服务输出: ${stdout}`);
|
|
65
72
|
console.log('[CLI] 服务安装成功');
|
|
66
73
|
exec(`net start "${SERVICE_NAME}"`, (err3) => {
|
|
67
74
|
if (err3) console.error(`[CLI] 启动服务失败: ${err3.message}`);
|
|
@@ -146,8 +153,8 @@ function uninstallService() {
|
|
|
146
153
|
svc.on('uninstall', () => console.log('[CLI] 服务卸载成功'));
|
|
147
154
|
svc.uninstall();
|
|
148
155
|
} catch (err) {
|
|
149
|
-
console.log('[CLI] 使用 sc 命令卸载服务...');
|
|
150
|
-
exec(`sc stop ${SERVICE_NAME} 2>nul & sc delete ${SERVICE_NAME}`, (err2) => {
|
|
156
|
+
console.log('[CLI] 使用 sc.exe 命令卸载服务...');
|
|
157
|
+
exec(`sc.exe stop ${SERVICE_NAME} 2>nul & sc.exe delete ${SERVICE_NAME}`, (err2) => {
|
|
151
158
|
if (err2) console.error(`[CLI] 卸载失败: ${err2.message}`);
|
|
152
159
|
else console.log('[CLI] 服务卸载成功');
|
|
153
160
|
});
|
|
@@ -203,7 +210,7 @@ function checkStatus() {
|
|
|
203
210
|
let cmd;
|
|
204
211
|
|
|
205
212
|
if (platform === 'win32') {
|
|
206
|
-
cmd = `sc query ${SERVICE_NAME}`;
|
|
213
|
+
cmd = `sc.exe query ${SERVICE_NAME}`;
|
|
207
214
|
} else if (platform === 'linux') {
|
|
208
215
|
cmd = `systemctl status ${SERVICE_NAME}`;
|
|
209
216
|
} else if (platform === 'darwin') {
|
package/package.json
CHANGED
|
@@ -45,12 +45,13 @@ 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`, () => {
|
|
49
|
-
|
|
48
|
+
exec(`net stop "${SERVICE_NAME}" 2>nul & sc.exe delete "${SERVICE_NAME}" 2>nul`, () => {
|
|
49
|
+
// sc.exe binPath 格式:外层引号包裹整个值,内层路径用转义引号
|
|
50
|
+
const binPath = `\\"${process.execPath}\\" \\"${DAEMON_PATH}\\"`;
|
|
50
51
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
exec(createCmd, { windowsHide: true }, (err) => {
|
|
52
|
+
const createCmd = `sc.exe create "${SERVICE_NAME}" binPath= "${binPath}" start= auto displayname= "Node.js 静默后台服务"`;
|
|
53
|
+
log(`执行命令: ${createCmd}`);
|
|
54
|
+
exec(createCmd, { windowsHide: true }, (err, stdout, stderr) => {
|
|
54
55
|
if (err) {
|
|
55
56
|
log(`创建服务失败: ${err.message}`);
|
|
56
57
|
return finish(1, `创建服务失败: ${err.message}`);
|
|
@@ -58,14 +59,14 @@ if (platform === 'win32') {
|
|
|
58
59
|
log('服务注册成功');
|
|
59
60
|
|
|
60
61
|
// 设置恢复策略:崩溃后自动重启
|
|
61
|
-
const recoveryCmd = `sc failure "${SERVICE_NAME}" reset= 0 actions= restart/0/restart/0/restart/0`;
|
|
62
|
+
const recoveryCmd = `sc.exe failure "${SERVICE_NAME}" reset= 0 actions= restart/0/restart/0/restart/0`;
|
|
62
63
|
exec(recoveryCmd, (err) => {
|
|
63
64
|
if (err) log(`设置恢复策略失败: ${err.message}`);
|
|
64
65
|
else log('恢复策略已设置:服务崩溃后系统自动无限重启');
|
|
65
66
|
});
|
|
66
67
|
|
|
67
68
|
// 设置自动启动
|
|
68
|
-
exec(`sc config "${SERVICE_NAME}" start= auto`, (err) => {
|
|
69
|
+
exec(`sc.exe config "${SERVICE_NAME}" start= auto`, (err) => {
|
|
69
70
|
if (err) log(`设置自动启动失败: ${err.message}`);
|
|
70
71
|
else log('启动类型已设为:自动');
|
|
71
72
|
});
|
package/scripts/post-install.js
CHANGED
|
@@ -9,25 +9,23 @@ const SERVICE_NAME = 'claw-subagent-service';
|
|
|
9
9
|
const DAEMON_PATH = path.join(__dirname, '..', 'service', 'daemon.js');
|
|
10
10
|
|
|
11
11
|
function isGlobalInstall() {
|
|
12
|
-
// 方法1:
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
// 方法1: 检查 __dirname 是否在 node_modules 下且不在当前工作目录下
|
|
13
|
+
const pkgPath = path.normalize(__dirname).toLowerCase();
|
|
14
|
+
const cwd = process.cwd().toLowerCase();
|
|
15
|
+
const inNodeModules = pkgPath.includes('node_modules');
|
|
16
|
+
const notInCwd = !pkgPath.startsWith(cwd);
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const normalizedGlobal = path.normalize(globalRoot).toLowerCase();
|
|
24
|
-
const isGlobal = normalizedPkg.startsWith(normalizedGlobal);
|
|
25
|
-
console.log(`[postinstall] 全局安装检测: pkgPath=${normalizedPkg}, globalRoot=${normalizedGlobal}, result=${isGlobal}`);
|
|
26
|
-
return isGlobal;
|
|
27
|
-
} catch (e) {
|
|
28
|
-
console.log(`[postinstall] 全局安装检测失败(${e.message}),默认按全局安装处理`);
|
|
18
|
+
console.log(`[postinstall] 安装检测: pkgPath=${pkgPath}, cwd=${cwd}`);
|
|
19
|
+
console.log(`[postinstall] 安装检测: inNodeModules=${inNodeModules}, notInCwd=${notInCwd}`);
|
|
20
|
+
|
|
21
|
+
if (inNodeModules && notInCwd) {
|
|
22
|
+
console.log('[postinstall] 检测到全局安装(包在 node_modules 下且不在当前工作目录)');
|
|
29
23
|
return true;
|
|
30
24
|
}
|
|
25
|
+
|
|
26
|
+
// 方法2: 兜底 - 默认按全局安装处理(postinstall 只在安装时触发)
|
|
27
|
+
console.log('[postinstall] 兜底检测通过,默认按全局安装处理');
|
|
28
|
+
return true;
|
|
31
29
|
}
|
|
32
30
|
|
|
33
31
|
function isWindowsAdmin() {
|
|
@@ -54,30 +52,39 @@ function installAndStartService() {
|
|
|
54
52
|
|
|
55
53
|
console.log('[postinstall] 正在注册系统服务...');
|
|
56
54
|
|
|
57
|
-
|
|
55
|
+
// sc.exe binPath 格式:外层引号包裹整个值,内层路径用转义引号
|
|
56
|
+
// 这样 sc.exe 会把整个字符串作为 binPath 的单一值
|
|
57
|
+
const binPath = `\\"${process.execPath}\\" \\"${DAEMON_PATH}\\"`;
|
|
58
58
|
|
|
59
59
|
// 先停止并删除旧服务(避免冲突)
|
|
60
60
|
try { execSync(`net stop "${SERVICE_NAME}" 2>nul`, { stdio: 'ignore', timeout: 10000 }); } catch (e) {}
|
|
61
|
-
try { execSync(`sc delete "${SERVICE_NAME}" 2>nul`, { stdio: 'ignore', timeout: 10000 }); } catch (e) {}
|
|
61
|
+
try { execSync(`sc.exe delete "${SERVICE_NAME}" 2>nul`, { stdio: 'ignore', timeout: 10000 }); } catch (e) {}
|
|
62
|
+
|
|
63
|
+
// sc.exe create 命令:binPath= 后面的值用引号包裹,内部路径用转义引号
|
|
64
|
+
const createCmd = `sc.exe create "${SERVICE_NAME}" binPath= "${binPath}" start= auto displayname= "OpenClaw Guard"`;
|
|
65
|
+
console.log(`[postinstall] 执行: ${createCmd}`);
|
|
62
66
|
|
|
63
|
-
|
|
64
|
-
const createCmd = `sc create "${SERVICE_NAME}" binPath= ${binPath} start= auto displayname= "OpenClaw Guard"`;
|
|
65
|
-
exec(createCmd, { windowsHide: true }, (err) => {
|
|
67
|
+
exec(createCmd, { windowsHide: true }, (err, stdout, stderr) => {
|
|
66
68
|
if (err) {
|
|
67
69
|
console.error(`[postinstall] 注册服务失败: ${err.message}`);
|
|
70
|
+
console.error(`[postinstall] stdout: ${stdout}`);
|
|
71
|
+
console.error(`[postinstall] stderr: ${stderr}`);
|
|
68
72
|
return;
|
|
69
73
|
}
|
|
74
|
+
console.log(`[postinstall] 注册服务输出: ${stdout}`);
|
|
70
75
|
console.log('[postinstall] 服务注册成功');
|
|
71
76
|
|
|
72
77
|
// 设置恢复策略
|
|
73
|
-
exec(`sc failure "${SERVICE_NAME}" reset= 0 actions= restart/0/restart/0/restart/0`, (err) => {
|
|
78
|
+
exec(`sc.exe failure "${SERVICE_NAME}" reset= 0 actions= restart/0/restart/0/restart/0`, (err) => {
|
|
74
79
|
if (!err) console.log('[postinstall] 恢复策略已设置');
|
|
75
80
|
});
|
|
76
81
|
|
|
77
82
|
// 启动服务
|
|
78
|
-
exec(`net start "${SERVICE_NAME}"`, (err) => {
|
|
83
|
+
exec(`net start "${SERVICE_NAME}"`, (err, stdout, stderr) => {
|
|
79
84
|
if (err) {
|
|
80
85
|
console.error(`[postinstall] 启动服务失败: ${err.message}`);
|
|
86
|
+
console.error(`[postinstall] stdout: ${stdout}`);
|
|
87
|
+
console.error(`[postinstall] stderr: ${stderr}`);
|
|
81
88
|
} else {
|
|
82
89
|
console.log('[postinstall] 服务已启动');
|
|
83
90
|
}
|
|
@@ -87,13 +94,9 @@ function installAndStartService() {
|
|
|
87
94
|
|
|
88
95
|
// 主逻辑
|
|
89
96
|
console.log('[postinstall] 脚本开始执行...');
|
|
90
|
-
console.log(`[postinstall] __dirname=${__dirname}`);
|
|
91
|
-
console.log(`[postinstall] cwd=${process.cwd()}`);
|
|
92
|
-
console.log(`[postinstall] npm_config_global=${process.env.npm_config_global}`);
|
|
93
|
-
console.log(`[postinstall] npm_config_prefix=${process.env.npm_config_prefix}`);
|
|
94
97
|
|
|
95
98
|
if (isGlobalInstall()) {
|
|
96
|
-
console.log('[postinstall]
|
|
99
|
+
console.log('[postinstall] 准备自动注册服务...');
|
|
97
100
|
installAndStartService();
|
|
98
101
|
} else {
|
|
99
102
|
console.log('[postinstall] 本地安装模式,跳过自动注册服务');
|
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
|
|
|
@@ -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':
|
|
@@ -157,14 +157,15 @@ class ServiceManager {
|
|
|
157
157
|
|
|
158
158
|
// Windows 服务安装(使用 sc 命令,避免 node-windows 在包目录生成被锁定的 wrapper)
|
|
159
159
|
async installWindows() {
|
|
160
|
-
|
|
160
|
+
// sc.exe binPath 格式:外层引号包裹整个值,内层路径用转义引号
|
|
161
|
+
const binPath = `\\"${process.execPath}\\" \\"${this.scriptPath}\\"`;
|
|
161
162
|
|
|
162
163
|
// 先停止并删除旧服务(避免文件锁导致更新失败)
|
|
163
164
|
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) {}
|
|
165
|
+
try { await this.execCommand(`sc.exe delete "${this.serviceName}" 2>nul`); } catch (e) {}
|
|
165
166
|
|
|
166
|
-
|
|
167
|
-
|
|
167
|
+
const createCmd = `sc.exe create "${this.serviceName}" binPath= "${binPath}" start= auto displayname= "${this.serviceDesc}"`;
|
|
168
|
+
this.log?.info(`[ServiceManager] 执行: ${createCmd}`);
|
|
168
169
|
await this.execCommand(createCmd);
|
|
169
170
|
this.log?.info('[ServiceManager] Windows 服务注册成功');
|
|
170
171
|
|
package/version.json
CHANGED