sk-hermes-cli 1.0.0

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 ADDED
@@ -0,0 +1,147 @@
1
+ # sk-hermes-cli
2
+
3
+ `sk-Hermes`(Java AI Agent)的 npm 分发壳。装完即可在任意目录敲 `hermes` 或 `sk-hermes` 进入对话,
4
+ 等价于 Claude Code 的 `claude` 命令。
5
+
6
+ > 本包只是一个 **JS → Java 的桥接 shim**:`bin/hermes.js` 用 `child_process.spawn` 启动 `java -jar hermes.jar`,
7
+ > 并透传参数、stdin/stdout、退出码。npm 在这里只承担「分发 + 注册全局命令」的职责;
8
+ > 181MB 的 jar 不进 npm 包,而是发布到 **GitHub Release**,安装时自动下载(esbuild 同款思路)。
9
+
10
+ ## 用户体验(安装者视角)
11
+
12
+ ```bash
13
+ npm install -g sk-hermes-cli
14
+ # postinstall 自动从 GitHub Release 下载 hermes.jar
15
+ hermes # 任意目录直接对话,模型以当前目录为项目根
16
+ ```
17
+
18
+ ### 安装者前置依赖
19
+ - **JDK 21+**(`java` 在 PATH 中,或设置 `JAVA_HOME`)
20
+ - 首次运行前配置 `~/.skhermes/config.yaml` 的 `model.api_key`
21
+ - 能访问 GitHub(下载 jar);被墙时可设 `SKHERMES_JAR_URL` 指向镜像,或 `SKHERMES_JAR` 指向本地文件
22
+
23
+ ### 使用
24
+
25
+ ```bash
26
+ hermes # 在当前目录直接进入对话,模型以当前目录为项目根
27
+ hermes -m fast # 指定模型别名
28
+ hermes --help # 查看全部子命令(chat / sessions / resume)
29
+ hermes chat -s # 显式子命令 + 流式输出
30
+ ```
31
+
32
+ ### jar 查找优先级(运行时)
33
+ 1. 环境变量 `SKHERMES_JAR`(显式指定,适合多版本共存 / 开发调试)
34
+ 2. 包内 `jar/hermes.jar`(postinstall 下载的落地位置,或作者手动捆绑)
35
+ 3. `~/.hermes/hermes.jar`(兼容 `tools/install-hermes.ps1` 的本地安装)
36
+
37
+ ---
38
+
39
+ ## 作者发布教程(模式 B:GitHub Release 按需下载)
40
+
41
+ > 下载地址不再依赖环境变量:**package.json 内置了默认 `hermes.jarUrl`**(本仓库发布版已指向
42
+ > `https://github.com/chen-2024-cn/Hermes-agent-Java-/releases/download/v1.0.0/hermes.jar`),
43
+ > 环境变量 `SKHERMES_JAR_URL` 仅作临时覆盖(优先于内置值)。
44
+
45
+ ### 第 0 步:一次性准备(只做一次)
46
+
47
+ | 检查项 | 命令 | 要求 |
48
+ |---|---|---|
49
+ | GitHub CLI | `gh auth status` | 已登录且 token 有 repo 权限 |
50
+ | npm 账号 | `npm whoami` | 未登录则 `npm login`(浏览器注册/验证) |
51
+ | 仓库可见性 | GitHub 仓库设置 | 必须 **public**(Release 资产匿名可下载,postinstall 才不用带 token) |
52
+ | 包名可用性 | `npm view sk-hermes-cli` | 返回 404 = 未占用,可直接用 |
53
+
54
+ ### 第 1 步:构建 jar 并发布到 GitHub Release(一键脚本,幂等)
55
+
56
+ ```powershell
57
+ cd <项目根>
58
+ powershell -ExecutionPolicy Bypass -File tools\release.ps1
59
+ # 指定版本号: -Version 1.1.0(默认取 npm-package\package.json 的 version)
60
+ ```
61
+
62
+ 脚本内部等价于三步(理解原理用):
63
+
64
+ ```powershell
65
+ mvn -f sk-hermes\pom.xml clean package -DskipTests # ① 构建 fat jar
66
+ gh release create v1.0.0 --title "..." --target master # ② 建 Release(tag 须与 jarUrl 一致)
67
+ Copy-Item sk-hermes\target\sk-hermes-1.0-SNAPSHOT.jar $env:TEMP\hermes.jar # ③ 先复制成 hermes.jar
68
+ gh release upload v1.0.0 $env:TEMP\hermes.jar --clobber # 再上传(见下方"坑")
69
+ ```
70
+
71
+ > ⚠️ **gh 的 `path#name` 重命名语法不可靠**(gh 2.97 实测 `#name` 只是 Web UI 显示标签,
72
+ > 资产真实名始终 = 源文件 basename)。而 postinstall 下载 URL 尾段必须是 `hermes.jar`,
73
+ > 所以**必须先把 jar 复制成物理文件名 `hermes.jar` 再上传**,否则用户安装时 404。
74
+ > release.ps1 已内置这个复制步骤 + 上传后资产核验(大文件上传可能静默失败)。
75
+
76
+ **验证下载地址可用**(期望 HTTP/2 200):
77
+
78
+ ```powershell
79
+ curl.exe -sIL "https://github.com/chen-2024-cn/Hermes-agent-Java-/releases/download/v1.0.0/hermes.jar" | Select-Object -First 1
80
+ ```
81
+
82
+ ### 第 2 步:核对 package.json 内置地址
83
+
84
+ ```json
85
+ "hermes": { "jarUrl": "https://github.com/<owner>/<repo>/releases/download/v1.0.0/hermes.jar" }
86
+ ```
87
+
88
+ 发新版本时 tag 变了,两种策略二选一:
89
+ - **URL 带具体 tag**(当前方案):每次发版改一行;地址永久有效、支持版本回滚
90
+ - **`/releases/latest/download/hermes.jar`**:永远指向最新 Release,发版不用改;但用户无法锁旧版
91
+
92
+ ### 第 3 步:发布前本地全链路验证(隔离,不污染全局)
93
+
94
+ ```powershell
95
+ cd npm-package
96
+ npm pack --dry-run # 包内应只有 3~4 个小文件(无 jar),体积 ~3.5KB
97
+
98
+ $pfx = "$env:TEMP\hermes_test"; New-Item -ItemType Directory -Force -Path $pfx | Out-Null
99
+ npm install -g .\npm-package --prefix $pfx
100
+ # 期望输出: [sk-hermes] 正在从 https://github.com/... 下载 hermes.jar ...
101
+ # [sk-hermes] jar 已下载到 ...\jar\hermes.jar(181.x MB)
102
+ & "$pfx\hermes.cmd" --version # 期望 1.0.0,退出码 0
103
+ Remove-Item -Recurse -Force $pfx
104
+ ```
105
+
106
+ > ⚠️ 若 npm 开启了 install-scripts 安全策略(提示 `allow-scripts`),postinstall 会被拦截,
107
+ > 执行 `npm approve-scripts sk-hermes-cli` 放行后重装。
108
+
109
+ ### 第 4 步:发布
110
+
111
+ ```powershell
112
+ cd npm-package
113
+ npm publish --access public
114
+ ```
115
+
116
+ ### 第 5 步:日常迭代发版循环
117
+
118
+ ```powershell
119
+ mvn -f sk-hermes\pom.xml clean package -DskipTests # 1) 构建
120
+ cd npm-package; npm version 1.1.0 --no-git-tag-version; cd .. # 2) 升版本号(npm 禁止同版本重复发布)
121
+ powershell -ExecutionPolicy Bypass -File tools\release.ps1 # 3) 传新 tag 的 Release
122
+ # 4) 同步修改 package.json 的 hermes.jarUrl → v1.1.0
123
+ npm pack --dry-run; 第 3 步验证; npm publish # 5) 验证后发布
124
+ ```
125
+
126
+ ## 常见坑(全部实测踩过)
127
+
128
+ | 坑 | 现象 | 解法 |
129
+ |---|---|---|
130
+ | Release 资产名不对 | postinstall 下载 404 | 资产真名必须叫 `hermes.jar`;gh 的 `#重命名`不生效,须先 `Copy-Item` 成 hermes.jar 再 upload |
131
+ | 大文件上传静默失败 | 资产列表为空/不全 | `gh release view v1.0.0 --json assets` 核验;失败重跑 release.ps1(幂等,含上传后自动核验) |
132
+ | 仓库是 private | 匿名下载 404 | 仓库设 public;或 postinstall 带 token(复杂度高,不推荐) |
133
+ | npm 拦截 postinstall | 装完没 jar,运行时提示找不到 | `npm approve-scripts sk-hermes-cli` 或 `npm rebuild` |
134
+ | 包名被占用 | `npm publish` 403 | `npm view <名字>` 先探测;占用则换名或用 scoped 包 `@你/sk-hermes-cli` |
135
+ | 含中文 .ps1 报语法错 | "字符串缺少终止符"、输出乱码 | 必须存**带 BOM 的 UTF-8**(PS 5.1 无 BOM 按 GBK 解析) |
136
+ | gh stderr 触发 PS 终止 | `$ErrorActionPreference='Stop'` 时探测命令被误杀 | 探测类 gh 调用临时切回 Continue(见 release.ps1 的 Invoke-GhProbe) |
137
+
138
+ ## 项目结构
139
+
140
+ ```
141
+ npm-package/
142
+ ├── package.json # bin 声明(hermes + sk-hermes)+ hermes.jarUrl 内置下载地址
143
+ ├── bin/hermes.js # shim:定位 jar → 定位 java → spawn 透传(stdio inherit + 退出码)
144
+ ├── scripts/postinstall.js # 安装钩子:env > jarUrl 两级取值下载;任何失败都 exit 0 不阻断安装
145
+ └── README.md
146
+ ```
147
+
package/bin/hermes.js ADDED
@@ -0,0 +1,104 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ /**
4
+ * hermes CLI 入口(npm bin shim)。
5
+ *
6
+ * 职责链:定位 hermes.jar(找不到则懒加载下载兜底)→ 定位 java → spawn 子进程
7
+ *
8
+ * 关键点:
9
+ * - jar 懒下载:不依赖 postinstall(npm 新版本可能拦截安装脚本),首次运行发现无 jar
10
+ * 时现场下载(复用 scripts/download-jar.js 的重试逻辑),成功后才 spawn Java
11
+ * - stdio:'inherit' 让 Java 进程直接接管终端(交互式对话、Ctrl+C 都正常)
12
+ * - 透传退出码,保证脚本化调用(if errorlevel 1)语义不被破坏
13
+ */
14
+ const { spawn, spawnSync } = require('child_process');
15
+ const fs = require('fs');
16
+ const os = require('os');
17
+ const path = require('path');
18
+
19
+ const PKG_ROOT = path.join(__dirname, '..');
20
+
21
+ /**
22
+ * 按优先级定位 jar:
23
+ * 1. SKHERMES_JAR 环境变量(显式指定,开发/多版本共存用)
24
+ * 2. 包内 jar/hermes.jar(捆绑发布 或 postinstall 从 Release 下载的)
25
+ * 3. ~/.hermes/hermes.jar(兼容 install-hermes.ps1 的老安装位置)
26
+ */
27
+ function resolveJar() {
28
+ const candidates = [
29
+ process.env.SKHERMES_JAR,
30
+ path.join(PKG_ROOT, 'jar', 'hermes.jar'),
31
+ path.join(os.homedir(), '.hermes', 'hermes.jar'),
32
+ ];
33
+ for (const c of candidates) {
34
+ // 加体积校验(>1MB):排除之前下载中断遗留的空文件/半截 jar,避免把坏文件当好文件用
35
+ if (c && fs.existsSync(c) && fs.statSync(c).size > 1024 * 1024) return c;
36
+ }
37
+ return null;
38
+ }
39
+
40
+ /** 定位 java:JAVA_HOME 优先(版本可控),否则交给 PATH。 */
41
+ function findJava() {
42
+ if (process.env.JAVA_HOME) {
43
+ const exe = process.platform === 'win32' ? 'java.exe' : 'java';
44
+ const cand = path.join(process.env.JAVA_HOME, 'bin', exe);
45
+ if (fs.existsSync(cand)) return cand;
46
+ }
47
+ return 'java';
48
+ }
49
+
50
+ async function main() {
51
+ // 找 jar:找不到 → 懒加载下载兜底(不依赖 postinstall,这是可靠性的核心)
52
+ let jar = resolveJar();
53
+ if (!jar) {
54
+ const { ensureJar, resolveJarUrl } = require('../scripts/download-jar');
55
+ const dest = path.join(PKG_ROOT, 'jar', 'hermes.jar');
56
+ const ok = await ensureJar({ dest, url: resolveJarUrl(PKG_ROOT), maxAttempts: 5 });
57
+ if (!ok) {
58
+ console.error('[sk-hermes] 无法获取 hermes.jar,退出。请按上方提示手动提供 jar 后重试。');
59
+ process.exit(1);
60
+ }
61
+ jar = dest;
62
+ }
63
+
64
+ // Windows 控制台默认代码页是 GBK,Java 侧用 -Dstdout.encoding=UTF-8 写出 UTF-8 字节,
65
+ // 必须把控制台代码页也切到 UTF-8,两边编码对齐才不乱码(与 hermes.cmd 的 chcp 65001 同理)。
66
+ // chcp 是 chcp.com 这个真实可执行文件,spawnSync 不带 shell 时必须写全名。
67
+ if (process.platform === 'win32') {
68
+ try {
69
+ spawnSync('chcp.com', ['65001'], { stdio: 'ignore' });
70
+ } catch (_) {
71
+ /* best-effort:失败也不阻断启动 */
72
+ }
73
+ }
74
+
75
+ const child = spawn(
76
+ findJava(),
77
+ [
78
+ '-Dfile.encoding=UTF-8',
79
+ '-Dstdout.encoding=UTF-8',
80
+ '-Dstderr.encoding=UTF-8',
81
+ '-jar', jar,
82
+ ...process.argv.slice(2),
83
+ ],
84
+ { stdio: 'inherit' }
85
+ );
86
+
87
+ // POSIX 下把信号转发给子进程(Windows 控制台 Ctrl+C 由系统直接发给整个进程组,无需转发)
88
+ const forward = (sig) => { try { child.kill(sig); } catch (_) { /* 已退出 */ } };
89
+ process.on('SIGINT', forward);
90
+ process.on('SIGTERM', forward);
91
+
92
+ child.on('error', (err) => {
93
+ console.error(`[sk-hermes] 无法启动 java(${err.message})。`);
94
+ console.error('请确认已安装 JDK 21+ 且 java 在 PATH 中,或设置 JAVA_HOME 环境变量。');
95
+ process.exit(1);
96
+ });
97
+
98
+ // 透传退出码:CLI 的退出码是与调用方的返回值契约,不能丢
99
+ child.on('close', (code, signal) => {
100
+ process.exit(signal ? 1 : (code === null ? 1 : code));
101
+ });
102
+ }
103
+
104
+ main();
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "sk-hermes-cli",
3
+ "version": "1.0.0",
4
+ "description": "sk-Hermes:可自我进化的 AI Agent 命令行工具(Java fat-jar 的 npm 分发壳)",
5
+ "keywords": [
6
+ "cli",
7
+ "ai",
8
+ "agent",
9
+ "llm",
10
+ "chatbot",
11
+ "rag",
12
+ "java"
13
+ ],
14
+ "license": "MIT",
15
+ "bin": {
16
+ "hermes": "bin/hermes.js",
17
+ "sk-hermes": "bin/hermes.js"
18
+ },
19
+ "files": [
20
+ "bin/",
21
+ "scripts/",
22
+ "README.md"
23
+ ],
24
+ "scripts": {
25
+ "postinstall": "node scripts/postinstall.js"
26
+ },
27
+ "hermes": {
28
+ "jarUrl": "https://github.com/chen-2024-cn/Hermes-agent-Java-/releases/download/v1.0.0/hermes.jar"
29
+ },
30
+ "engines": {
31
+ "node": ">=18"
32
+ }
33
+ }
@@ -0,0 +1,133 @@
1
+ 'use strict';
2
+ /**
3
+ * 可复用的 hermes.jar 下载器。
4
+ *
5
+ * 被两处调用:
6
+ * - scripts/postinstall.js:安装时 best-effort 预下载(可能被 npm allow-scripts 策略拦截)
7
+ * - bin/hermes.js:首次运行时懒加载兜底(不依赖任何安装脚本,最可靠)
8
+ *
9
+ * 设计要点(针对不稳定网络,尤其国内访问 GitHub Release CDN 易被 Connection reset):
10
+ * - 整包重试 + 递增退避(重试窗口内大概率能撞上一次通畅)
11
+ * - 重定向跟随(GitHub download 链接 302 到 CDN),手动解析 Location 不依赖 new URL
12
+ * - .part 临时文件 + 原子 rename,避免半截文件被后续启动误判为有效 jar
13
+ * - 单请求超时保护,防止连接假死挂起
14
+ */
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+ const https = require('https');
18
+ const http = require('http');
19
+
20
+ const MAX_REDIRECTS = 5;
21
+ const REQ_TIMEOUT_MS = 60000;
22
+
23
+ /** 解析下载地址:环境变量 SKHERMES_JAR_URL 优先,其次 package.json 内置 hermes.jarUrl。 */
24
+ function resolveJarUrl(pkgRoot) {
25
+ let pkgJson = {};
26
+ try {
27
+ pkgJson = JSON.parse(fs.readFileSync(path.join(pkgRoot, 'package.json'), 'utf8'));
28
+ } catch (_) {
29
+ /* package.json 读失败则仅依赖环境变量 */
30
+ }
31
+ return process.env.SKHERMES_JAR_URL || (pkgJson.hermes && pkgJson.hermes.jarUrl) || '';
32
+ }
33
+
34
+ /**
35
+ * 解析重定向 Location:支持绝对 URL 与相对路径,不依赖 new URL(避免环境差异与变量遮蔽)。
36
+ */
37
+ function resolveRedirect(base, location) {
38
+ if (/^https?:\/\//i.test(location)) return location;
39
+ const m = base.match(/^(https?:)\/\/([^/]+)(\/.*)?$/i);
40
+ if (!m) return location;
41
+ if (location.startsWith('/')) return `${m[1]}//${m[2]}${location}`;
42
+ const dir = (m[3] || '/').replace(/[^/]*$/, '');
43
+ return `${m[1]}//${m[2]}${dir}${location}`;
44
+ }
45
+
46
+ /** 单次下载尝试(含重定向跟随)。resolve()=成功;reject(err)=可重试错误。 */
47
+ function downloadOnce(url, dest, redirects) {
48
+ return new Promise((resolve, reject) => {
49
+ const mod = url.startsWith('https') ? https : http;
50
+ const req = mod.get(url, { headers: { 'User-Agent': 'sk-hermes-npm' } }, (res) => {
51
+ const code = res.statusCode || 0;
52
+ if (code >= 300 && code < 400 && res.headers.location) {
53
+ res.resume();
54
+ if (redirects >= MAX_REDIRECTS) return reject(new Error('重定向次数过多'));
55
+ return downloadOnce(resolveRedirect(url, res.headers.location), dest, redirects + 1)
56
+ .then(resolve, reject);
57
+ }
58
+ if (code !== 200) {
59
+ res.resume();
60
+ return reject(new Error(`HTTP ${code}(${url})`));
61
+ }
62
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
63
+ const tmp = dest + '.part';
64
+ const file = fs.createWriteStream(tmp);
65
+ res.pipe(file);
66
+ file.on('finish', () => {
67
+ file.close(() => {
68
+ try {
69
+ fs.renameSync(tmp, dest); // 原子替换,避免半截文件
70
+ resolve();
71
+ } catch (e) {
72
+ reject(e);
73
+ }
74
+ });
75
+ });
76
+ res.on('error', reject); // 服务端中途断流也走重试
77
+ });
78
+ req.setTimeout(REQ_TIMEOUT_MS, () => req.destroy(new Error(`请求超时(${REQ_TIMEOUT_MS}ms 无响应)`)));
79
+ req.on('error', reject);
80
+ });
81
+ }
82
+
83
+ /**
84
+ * 确保 dest 处存在 jar:已存在(>1MB)则跳过;否则带重试下载。
85
+ *
86
+ * @param {object} opts
87
+ * @param {string} opts.dest 目标 jar 路径
88
+ * @param {string} opts.url 下载地址
89
+ * @param {number} [opts.maxAttempts=5] 最大重试次数
90
+ * @param {(msg:string)=>void} [opts.log] 日志输出(默认写 stdout)
91
+ * @returns {Promise<boolean>} 是否最终拿到了 jar(true=可用;false=下载失败,调用方应给出人话提示)
92
+ */
93
+ async function ensureJar({ dest, url, maxAttempts = 5, log = (m) => process.stdout.write(`[sk-hermes] ${m}\n`) }) {
94
+ // 已有可用 jar(>1MB,排除空文件/半截文件)→ 直接用
95
+ if (fs.existsSync(dest)) {
96
+ try {
97
+ if (fs.statSync(dest).size > 1024 * 1024) return true;
98
+ } catch (_) {
99
+ /* 忽略统计失败,继续走下载 */
100
+ }
101
+ }
102
+ if (!url) {
103
+ log('未找到 jar 下载地址(package.json 的 hermes.jarUrl 与环境变量 SKHERMES_JAR_URL 均未设置)。');
104
+ log('请设 SKHERMES_JAR 指向你本地构建的 jar,或将 jar 放到 ' + dest);
105
+ return false;
106
+ }
107
+
108
+ log(`正在下载 hermes.jar(约 45 MB,最多重试 ${maxAttempts} 次)...`);
109
+ log(`来源: ${url}`);
110
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
111
+ try {
112
+ await downloadOnce(url, dest, 0);
113
+ const mb = (fs.statSync(dest).size / 1024 / 1024).toFixed(1);
114
+ log(`下载完成: ${dest}(${mb} MB)`);
115
+ return true;
116
+ } catch (err) {
117
+ log(`第 ${attempt}/${maxAttempts} 次下载失败:${err.message}`);
118
+ try { fs.unlinkSync(dest + '.part'); } catch (_) { /* 忽略 */ }
119
+ if (attempt < maxAttempts) {
120
+ const wait = attempt * 3000; // 3s→6s→9s→12s 递增退避
121
+ log(`${wait / 1000}s 后重试...`);
122
+ await new Promise((r) => setTimeout(r, wait));
123
+ }
124
+ }
125
+ }
126
+ log('自动下载失败(可能是网络无法稳定访问 GitHub CDN)。可手动解决:');
127
+ log(` 用浏览器/下载工具获取 ${url}`);
128
+ log(` 放到 ${dest}`);
129
+ log(` 或设置环境变量 SKHERMES_JAR 指向该 jar 文件。`);
130
+ return false;
131
+ }
132
+
133
+ module.exports = { ensureJar, resolveJarUrl, downloadOnce, resolveRedirect };
@@ -0,0 +1,31 @@
1
+ 'use strict';
2
+ /**
3
+ * postinstall 钩子:安装后 best-effort 预下载 hermes.jar。
4
+ *
5
+ * 定位是「加分项」而非「必需项」——下载逻辑同时内置于 bin/hermes.js(首次运行懒加载兜底),
6
+ * 因此即使本脚本被 npm 的 allow-scripts / ignore-scripts 策略拦截(npm 新版本默认可能拦),
7
+ * 或下载因网络失败,用户敲 hermes 时依然会自动补下载,不影响可用性。
8
+ *
9
+ * 铁律:任何情况下都 exit 0,绝不阻断 npm install。
10
+ * 真正的下载实现见 ./download-jar.js(bin 与此处共用同一份,避免逻辑漂移)。
11
+ */
12
+ const path = require('path');
13
+ const { ensureJar, resolveJarUrl } = require('./download-jar');
14
+
15
+ process.on('uncaughtException', (err) => {
16
+ process.stdout.write(`[sk-hermes] postinstall 异常(已忽略,不阻断安装):${err && err.message}\n`);
17
+ process.exit(0);
18
+ });
19
+
20
+ const PKG_ROOT = path.join(__dirname, '..');
21
+
22
+ (async () => {
23
+ // 安装阶段少试几次(maxAttempts=3);失败无所谓,首次运行 hermes 时还会带 5 次重试再下
24
+ await ensureJar({
25
+ dest: path.join(PKG_ROOT, 'jar', 'hermes.jar'),
26
+ url: resolveJarUrl(PKG_ROOT),
27
+ maxAttempts: 3,
28
+ });
29
+ process.exit(0);
30
+ })();
31
+