claw-subagent-service 0.0.16 → 0.0.18

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 CHANGED
@@ -50,24 +50,39 @@ function installService() {
50
50
  const execPath = process.execPath;
51
51
 
52
52
  if (platform === 'win32') {
53
- // Windows: 使用 sc 命令直接创建服务,避免 node-windows 在包目录生成被锁定的 wrapper
54
- console.log('[CLI] 使用 sc 命令安装服务...');
55
- const binPath = process.pkg
56
- ? `"${execPath}" --run`
57
- : `"${execPath}" "${DAEMON_PATH}"`;
58
-
59
- // 先停止并删除旧服务(避免文件锁)
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) => {
64
- if (err2) return console.error(`[CLI] 服务安装失败: ${err2.message}`);
53
+ // Windows: 使用 node-windows 注册服务(正确处理路径引号)
54
+ console.log('[CLI] 使用 node-windows 安装服务...');
55
+ try {
56
+ const Service = require('node-windows').Service;
57
+ const svc = new Service({
58
+ name: SERVICE_NAME,
59
+ description: 'OpenClaw Guard',
60
+ script: DAEMON_PATH,
61
+ nodeOptions: ['--harmony', '--max_old_space_size=4096']
62
+ });
63
+
64
+ svc.on('install', () => {
65
65
  console.log('[CLI] 服务安装成功');
66
- exec(`net start "${SERVICE_NAME}"`, (err3) => {
67
- if (err3) console.error(`[CLI] 启动服务失败: ${err3.message}`);
68
- });
66
+ svc.start();
69
67
  });
70
- });
68
+
69
+ svc.on('start', () => {
70
+ console.log('[CLI] 服务已启动');
71
+ });
72
+
73
+ svc.on('error', (err) => {
74
+ console.error(`[CLI] 服务安装失败: ${err.message}`);
75
+ });
76
+
77
+ svc.on('alreadyinstalled', () => {
78
+ console.log('[CLI] 服务已存在,尝试启动...');
79
+ svc.start();
80
+ });
81
+
82
+ svc.install();
83
+ } catch (err) {
84
+ console.error(`[CLI] 安装服务异常: ${err.message}`);
85
+ }
71
86
  } else if (platform === 'linux') {
72
87
  // Linux: systemd
73
88
  const execStart = `/usr/bin/node ${DAEMON_PATH}`;
@@ -138,19 +153,15 @@ function uninstallService() {
138
153
  const platform = process.platform;
139
154
 
140
155
  if (platform === 'win32') {
141
- // Windows: 优先使用 node-windows,失败回退 sc 命令
156
+ // Windows: 使用 node-windows 卸载服务
142
157
  try {
143
- if (process.pkg) throw new Error('pkg 环境');
144
158
  const Service = require('node-windows').Service;
145
159
  const svc = new Service({ name: SERVICE_NAME, script: DAEMON_PATH });
146
160
  svc.on('uninstall', () => console.log('[CLI] 服务卸载成功'));
161
+ svc.on('error', (err) => console.error(`[CLI] 卸载失败: ${err.message}`));
147
162
  svc.uninstall();
148
163
  } catch (err) {
149
- console.log('[CLI] 使用 sc.exe 命令卸载服务...');
150
- exec(`sc.exe stop ${SERVICE_NAME} 2>nul & sc.exe delete ${SERVICE_NAME}`, (err2) => {
151
- if (err2) console.error(`[CLI] 卸载失败: ${err2.message}`);
152
- else console.log('[CLI] 服务卸载成功');
153
- });
164
+ console.error(`[CLI] 卸载服务异常: ${err.message}`);
154
165
  }
155
166
  } else if (platform === 'linux') {
156
167
  exec(`systemctl stop ${SERVICE_NAME} && systemctl disable ${SERVICE_NAME} && rm -f /etc/systemd/system/${SERVICE_NAME}.service && systemctl daemon-reload`, (err) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claw-subagent-service",
3
- "version": "0.0.16",
3
+ "version": "0.0.18",
4
4
  "description": "虾说静态服务",
5
5
  "main": "cli.js",
6
6
  "bin": {
@@ -44,44 +44,55 @@ if (platform === 'win32') {
44
44
  // 兜底超时
45
45
  setTimeout(() => finish(1, '安装操作超时'), 60000);
46
46
 
47
- // 先停止并删除旧服务(避免文件锁导致更新失败)
48
- exec(`net stop "${SERVICE_NAME}" 2>nul & sc.exe delete "${SERVICE_NAME}" 2>nul`, () => {
49
- const binPath = `"${process.execPath}" "${DAEMON_PATH}"`;
50
-
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) => {
54
- if (err) {
55
- log(`创建服务失败: ${err.message}`);
56
- return finish(1, `创建服务失败: ${err.message}`);
57
- }
58
- log('服务注册成功');
59
-
60
- // 设置恢复策略:崩溃后自动重启
61
- const recoveryCmd = `sc.exe failure "${SERVICE_NAME}" reset= 0 actions= restart/0/restart/0/restart/0`;
62
- exec(recoveryCmd, (err) => {
47
+ // 使用 node-windows 注册服务(正确处理路径引号)
48
+ try {
49
+ const Service = require('node-windows').Service;
50
+ const svc = new Service({
51
+ name: SERVICE_NAME,
52
+ description: 'Node.js 静默后台服务(开机自启/崩溃自动恢复/自动更新)',
53
+ script: DAEMON_PATH,
54
+ wait: 2,
55
+ grow: 0.5,
56
+ abortOnError: false
57
+ });
58
+
59
+ svc.on('install', () => {
60
+ log('服务安装成功,正在启动...');
61
+ svc.start();
62
+
63
+ const cmd = `sc.exe failure "${SERVICE_NAME}" reset= 0 actions= restart/0/restart/0/restart/0`;
64
+ exec(cmd, (err) => {
63
65
  if (err) log(`设置恢复策略失败: ${err.message}`);
64
66
  else log('恢复策略已设置:服务崩溃后系统自动无限重启');
65
67
  });
66
68
 
67
- // 设置自动启动
68
69
  exec(`sc.exe config "${SERVICE_NAME}" start= auto`, (err) => {
69
70
  if (err) log(`设置自动启动失败: ${err.message}`);
70
71
  else log('启动类型已设为:自动');
71
72
  });
73
+ });
72
74
 
73
- // 启动服务
74
- exec(`net start "${SERVICE_NAME}"`, (err) => {
75
- if (err) {
76
- log(`启动服务失败: ${err.message}`);
77
- finish(1, `启动服务失败: ${err.message}`);
78
- } else {
79
- log('服务已启动');
80
- finish(0, '服务安装成功并已启动');
81
- }
82
- });
75
+ svc.on('alreadyinstalled', () => {
76
+ log('服务已存在,尝试启动...');
77
+ svc.start();
83
78
  });
84
- });
79
+
80
+ svc.on('start', () => {
81
+ log('服务已启动');
82
+ finish(0);
83
+ });
84
+
85
+ svc.on('error', (err) => {
86
+ log(`安装错误: ${err.message}`);
87
+ finish(1);
88
+ });
89
+
90
+ log('开始安装服务...');
91
+ svc.install();
92
+ } catch (err) {
93
+ log(`安装异常: ${err.message}`);
94
+ finish(1);
95
+ }
85
96
  });
86
97
  } else if (platform === 'linux') {
87
98
  const serviceFile = `/etc/systemd/system/${SERVICE_NAME}.service`;
@@ -2,58 +2,28 @@
2
2
  * npm postinstall 钩子
3
3
  * 全局安装完成后自动注册并启动 Windows 服务
4
4
  */
5
- const { exec, execSync } = require('child_process');
5
+ const { execSync } = require('child_process');
6
6
  const path = require('path');
7
7
 
8
8
  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: npm 全局安装时会设置此环境变量
13
- if (process.env.npm_config_global === 'true') {
14
- console.log('[postinstall] 通过 npm_config_global 检测到全局安装');
15
- return true;
16
- }
12
+ const pkgPath = path.normalize(__dirname).toLowerCase();
13
+ const cwd = process.cwd().toLowerCase();
14
+ const inNodeModules = pkgPath.includes('node_modules');
15
+ const notInCwd = !pkgPath.startsWith(cwd);
17
16
 
18
- // 方法2: 检查包路径是否在全局 node_modules 下(支持 nvm)
19
- try {
20
- const globalRoot = execSync('npm root -g', { encoding: 'utf8', timeout: 5000 }).trim();
21
- const pkgPath = path.join(__dirname, '..');
22
- const normalizedPkg = path.normalize(pkgPath).toLowerCase();
23
- const normalizedGlobal = path.normalize(globalRoot).toLowerCase();
24
- const isGlobal = normalizedPkg.startsWith(normalizedGlobal);
25
- console.log(`[postinstall] 全局安装检测: pkgPath=${normalizedPkg}, globalRoot=${normalizedGlobal}, result=${isGlobal}`);
26
- if (isGlobal) return true;
27
- } catch (e) {
28
- console.log(`[postinstall] npm root -g 检测失败: ${e.message}`);
29
- }
17
+ console.log(`[postinstall] 安装检测: inNodeModules=${inNodeModules}, notInCwd=${notInCwd}`);
30
18
 
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] 兜底检测通过,按全局安装处理');
19
+ if (inNodeModules && notInCwd) {
20
+ console.log('[postinstall] 检测到全局安装');
52
21
  return true;
53
22
  }
54
23
 
55
- console.log('[postinstall] 所有检测均失败,按本地安装处理');
56
- return false;
24
+ // 兜底 - postinstall 只在安装时触发,默认按全局安装处理
25
+ console.log('[postinstall] 兜底检测通过,默认按全局安装处理');
26
+ return true;
57
27
  }
58
28
 
59
29
  function isWindowsAdmin() {
@@ -68,59 +38,58 @@ function isWindowsAdmin() {
68
38
 
69
39
  function installAndStartService() {
70
40
  if (process.platform !== 'win32') {
71
- console.log(`[postinstall] 跳过自动注册服务(非 Windows 平台: ${process.platform})`);
41
+ console.log(`[postinstall] 跳过(非 Windows: ${process.platform})`);
72
42
  return;
73
43
  }
74
44
 
75
45
  if (!isWindowsAdmin()) {
76
- console.log('[postinstall] 当前非管理员权限,跳过自动注册服务');
77
- console.log('[postinstall] 如需注册服务,请以管理员身份运行: claw-subagent-service --install');
46
+ console.log('[postinstall] 非管理员权限,跳过自动注册');
47
+ console.log('[postinstall] 请运行: claw-subagent-service --install');
78
48
  return;
79
49
  }
80
50
 
81
51
  console.log('[postinstall] 正在注册系统服务...');
82
52
 
83
- const binPath = `"${process.execPath}" "${DAEMON_PATH}"`;
53
+ try {
54
+ // 使用 node-windows 注册服务(正确处理路径引号)
55
+ const Service = require('node-windows').Service;
56
+ const svc = new Service({
57
+ name: SERVICE_NAME,
58
+ description: 'OpenClaw Guard',
59
+ script: DAEMON_PATH,
60
+ nodeOptions: ['--harmony', '--max_old_space_size=4096']
61
+ });
84
62
 
85
- // 先停止并删除旧服务(避免冲突)
86
- try { execSync(`net stop "${SERVICE_NAME}" 2>nul`, { stdio: 'ignore', timeout: 10000 }); } catch (e) {}
87
- try { execSync(`sc.exe delete "${SERVICE_NAME}" 2>nul`, { stdio: 'ignore', timeout: 10000 }); } catch (e) {}
63
+ svc.on('install', () => {
64
+ console.log('[postinstall] 服务注册成功,正在启动...');
65
+ svc.start();
66
+ });
88
67
 
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) => {
92
- if (err) {
93
- console.error(`[postinstall] 注册服务失败: ${err.message}`);
94
- return;
95
- }
96
- console.log('[postinstall] 服务注册成功');
68
+ svc.on('start', () => {
69
+ console.log('[postinstall] 服务已启动');
70
+ });
97
71
 
98
- // 设置恢复策略
99
- exec(`sc.exe failure "${SERVICE_NAME}" reset= 0 actions= restart/0/restart/0/restart/0`, (err) => {
100
- if (!err) console.log('[postinstall] 恢复策略已设置');
72
+ svc.on('error', (err) => {
73
+ console.error(`[postinstall] 服务错误: ${err.message}`);
101
74
  });
102
75
 
103
- // 启动服务
104
- exec(`net start "${SERVICE_NAME}"`, (err) => {
105
- if (err) {
106
- console.error(`[postinstall] 启动服务失败: ${err.message}`);
107
- } else {
108
- console.log('[postinstall] 服务已启动');
109
- }
76
+ svc.on('alreadyinstalled', () => {
77
+ console.log('[postinstall] 服务已存在,尝试启动...');
78
+ svc.start();
110
79
  });
111
- });
80
+
81
+ svc.install();
82
+ } catch (err) {
83
+ console.error(`[postinstall] 注册服务异常: ${err.message}`);
84
+ }
112
85
  }
113
86
 
114
87
  // 主逻辑
115
88
  console.log('[postinstall] 脚本开始执行...');
116
- console.log(`[postinstall] __dirname=${__dirname}`);
117
- console.log(`[postinstall] cwd=${process.cwd()}`);
118
- console.log(`[postinstall] npm_config_global=${process.env.npm_config_global}`);
119
- console.log(`[postinstall] npm_config_prefix=${process.env.npm_config_prefix}`);
120
89
 
121
90
  if (isGlobalInstall()) {
122
- console.log('[postinstall] 检测到全局安装,准备自动注册服务...');
91
+ console.log('[postinstall] 准备自动注册服务...');
123
92
  installAndStartService();
124
93
  } else {
125
- console.log('[postinstall] 本地安装模式,跳过自动注册服务');
94
+ console.log('[postinstall] 本地安装模式,跳过自动注册');
126
95
  }
@@ -2,15 +2,26 @@
2
2
  * npm preuninstall 钩子
3
3
  * 在卸载旧版本前停止并删除 Windows 服务,释放文件锁
4
4
  */
5
+ const path = require('path');
5
6
  const { execSync } = require('child_process');
6
7
 
7
8
  const SERVICES = ['SilentNodeService', 'claw-subagent-service'];
9
+ const DAEMON_PATH = path.join(__dirname, '..', 'service', 'daemon.js');
8
10
 
9
11
  if (process.platform === 'win32') {
10
12
  for (const name of SERVICES) {
11
13
  try {
12
14
  execSync(`net stop "${name}" 2>nul`, { stdio: 'ignore', timeout: 10000 });
13
15
  } catch (e) {}
16
+
17
+ // 使用 node-windows 卸载服务(删除 wrapper 文件,释放文件锁)
18
+ try {
19
+ const Service = require('node-windows').Service;
20
+ const svc = new Service({ name, script: DAEMON_PATH });
21
+ svc.uninstall();
22
+ } catch (e) {}
23
+
24
+ // 兜底 - 使用 sc.exe 删除服务
14
25
  try {
15
26
  execSync(`sc.exe delete "${name}" 2>nul`, { stdio: 'ignore', timeout: 10000 });
16
27
  } catch (e) {}
@@ -155,24 +155,44 @@ class ServiceManager {
155
155
  }
156
156
  }
157
157
 
158
- // Windows 服务安装(使用 sc 命令,避免 node-windows 在包目录生成被锁定的 wrapper)
158
+ // Windows 服务安装(使用 node-windows,正确处理路径引号)
159
159
  async installWindows() {
160
- const binPath = `"${process.execPath}" "${this.scriptPath}"`;
160
+ return new Promise((resolve, reject) => {
161
+ try {
162
+ const Service = require('node-windows').Service;
163
+ const svc = new Service({
164
+ name: this.serviceName,
165
+ description: this.serviceDesc,
166
+ script: this.scriptPath,
167
+ nodeOptions: ['--harmony', '--max_old_space_size=4096']
168
+ });
161
169
 
162
- // 先停止并删除旧服务(避免文件锁导致更新失败)
163
- try { await this.execCommand(`net stop "${this.serviceName}" 2>nul`); } catch (e) {}
164
- try { await this.execCommand(`sc.exe delete "${this.serviceName}" 2>nul`); } catch (e) {}
170
+ svc.on('install', () => {
171
+ this.log?.info('[ServiceManager] Windows 服务注册成功');
172
+ svc.start();
173
+ });
165
174
 
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);
169
- this.log?.info('[ServiceManager] Windows 服务注册成功');
175
+ svc.on('start', () => {
176
+ this.log?.info('[ServiceManager] Windows 服务已启动');
177
+ resolve(true);
178
+ });
170
179
 
171
- // 启动服务
172
- await this.execCommand(`net start "${this.serviceName}"`);
173
- this.log?.info('[ServiceManager] Windows 服务已启动');
180
+ svc.on('error', (err) => {
181
+ this.log?.error(`[ServiceManager] Windows 服务安装失败: ${err.message}`);
182
+ reject(err);
183
+ });
174
184
 
175
- return true;
185
+ svc.on('alreadyinstalled', () => {
186
+ this.log?.info('[ServiceManager] 服务已存在,尝试启动...');
187
+ svc.start();
188
+ });
189
+
190
+ svc.install();
191
+ } catch (err) {
192
+ this.log?.error(`[ServiceManager] 安装异常: ${err.message}`);
193
+ reject(err);
194
+ }
195
+ });
176
196
  }
177
197
 
178
198
  // Linux 服务安装 (systemd)
package/version.json CHANGED
@@ -1,4 +1,4 @@
1
1
  {
2
- "version": "0.0.16",
3
- "updatedAt": "2026-04-30T06:15:00.000Z"
2
+ "version": "0.0.18",
3
+ "updatedAt": "2026-04-30T07:00:00.000Z"
4
4
  }