uniner-cli 1.0.3 → 1.0.6

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/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2026 Uniner Tech. All rights reserved.
2
-
3
- This software is proprietary. Use is subject to the terms of the
4
- license agreement at https://www.uniner.com/.
1
+ Copyright (c) 2026 Uniner Tech. All rights reserved.
2
+
3
+ This software is proprietary. Use is subject to the terms of the
4
+ license agreement at https://www.uniner.com/.
package/README.md CHANGED
@@ -1,171 +1,181 @@
1
- # uniner-cli
2
-
3
- `uniner-cli` 是 [Uniner](https://www.uniner.com/) 的 RPA 自动化命令行工具,提供:
4
-
5
- - **Web 自动化**(Playwright + CDP)—— 浏览器抓取、表单填写、元素交互
6
- - **Excel 操作** —— 读写、批量处理、样式
7
- - **OCR 识别** —— ddddocr(验证码)+ PaddleOCR(通用)
8
- - **桌面 / SAP 自动化**(仅 Windows)
9
- - **通用工具** —— 加密、HTTP、邮件、文件、PDF、压缩
10
-
11
- 安装时会自动注册一个 [Claude Code](https://claude.com/claude-code) 技能(`~/.claude/skills/uniner-cli/SKILL.md`),让 Claude 一启动就能识别并调用本 CLI。
12
-
13
- ---
14
-
15
- ## 安装
16
-
17
- ```bash
18
- npm install -g uniner-cli
19
- ```
20
-
21
- 安装过程:
22
-
23
- 1. npm 装本壳包(约 50KB)
24
- 2. `postinstall` 钩子根据当前平台从 `static.uniner.com` 拉取便携包:
25
- - Windows: `uniner-cli-v<version>-win-x64.zip`
26
- - Linux: `uniner-cli-v<version>-linux-x64.tar.gz`
27
- 3. 解压到用户目录:
28
- - Windows: `%USERPROFILE%\.uniner-cli\<version>\`
29
- - Linux: `~/.uniner-cli/<version>/`
30
- 4. 写入 Claude Code skill:`~/.claude/skills/uniner-cli/SKILL.md`
31
-
32
- 完成后任意终端:
33
-
34
- ```bash
35
- uniner-cli --help
36
- ```
37
-
38
- ## 系统要求
39
-
40
- | 项 | 要求 |
41
- |---|---|
42
- | 操作系统 | Windows 10/11 x64;Ubuntu 24.04+ x64 |
43
- | Node.js | >= 14 |
44
- | 磁盘空间 | ≥ 1.5 GB |
45
- | 网络 | 能访问 `static.uniner.com` |
46
-
47
- Linux 说明:
48
- - Linux 包当前基于 Ubuntu 24.04 / glibc 2.39 构建和验证。
49
- - 已验证模块:`tool` / `excel` / `skill` / `ddddocr OCR`。
50
- - `web` 模块需要目标机安装 Chrome/Chromium。
51
- - `desktop` 模块不支持 Linux。
52
-
53
- ## 使用示例
54
-
55
- ```bash
56
- # 看一级命令
57
- uniner-cli --help
58
-
59
- # 浏览器自动化
60
- uniner-cli web browser open --url "https://example.com"
61
- uniner-cli web element click --xpath "//button[@id='submit']"
62
- uniner-cli web element get-text --xpath "//h1"
63
-
64
- # Excel
65
- uniner-cli excel data read --file data.xlsx --cell A1
66
- uniner-cli excel data write --file out.xlsx --cell B2 --value "hello"
67
-
68
- # OCR
69
- uniner-cli tool ocr ddddocr --img captcha.png
70
- ```
71
-
72
- 完整命令树(一级 + 二级 + 三级参数)见安装后:
73
- - Windows: `%USERPROFILE%\.uniner-cli\<version>\skills\cli\SKILL.md`
74
- - Linux: `~/.uniner-cli/<version>/skills/cli/SKILL.md`
75
-
76
- ## 升级
77
-
78
- ```bash
79
- npm update -g uniner-cli
80
- ```
81
-
82
- 或者重新安装:
83
-
84
- ```bash
85
- npm install -g uniner-cli@latest
86
- ```
87
-
88
- > 不支持降级。如需回滚,请手动卸载后再装旧版。
89
-
90
- ## 卸载
91
-
92
- ```bash
93
- # 1. 先清理运行时数据(删 ~/.uniner-cli/ 和 Claude Code 技能)
94
- uniner-cli-uninstall
95
-
96
- # 2. 再卸载 npm 包本身
97
- npm uninstall -g uniner-cli
98
- ```
99
-
100
- > **为什么是两步?** npm 7+ 出于安全考虑不再在卸载时运行 lifecycle 脚本,所以我们提供 `uniner-cli-uninstall` 命令让你显式触发清理。
101
-
102
- 清理会移除:
103
-
104
- - `%USERPROFILE%\.uniner-cli\<version>\` 安装目录
105
- - `%USERPROFILE%\.claude\skills\uniner-cli\SKILL.md` 技能
106
-
107
- ## 环境变量
108
-
109
- 无需手动配置。npm 全局 bin 目录已自动加入 PATH。
110
-
111
- ## 故障排查
112
-
113
- ### `uniner-cli: command not found`
114
-
115
- 确认 npm 全局 bin 在 PATH 上:
116
-
117
- ```powershell
118
- npm config get prefix
119
- # 应输出类似 C:\Users\xxx\AppData\Roaming\npm
120
- $env:Path -split ';' | Where-Object { $_ -like '*npm*' }
121
- ```
122
-
123
- 如果没有,手动加 `%APPDATA%\npm` 到 PATH。
124
-
125
- ### 安装时下载失败 / 超时
126
-
127
- 公司代理或防火墙可能屏蔽 `static.uniner.com`。试试:
128
-
129
- ```bash
130
- npm config get proxy
131
- npm config get https-proxy
132
- # 设置代理:
133
- npm config set https-proxy http://your-proxy:port
134
- ```
135
-
136
- 或者直接走原生安装方式(不走 npm):
137
-
138
- ```powershell
139
- iwr https://static.uniner.com/soft/editor/uninercli/install.ps1 -UseBasicParsing | iex
140
- ```
141
-
142
- ### postinstall 卡在解压
143
-
144
- 杀软(360 / 火绒)会扫描 1279 个新文件,慢则 5–10 分钟。让它跑完即可。
145
-
146
- ### 想重装
147
-
148
- ```bash
149
- npm uninstall -g uniner-cli
150
- npm install -g uniner-cli
151
- ```
152
-
153
- ## 备选安装方式(不走 npm)
154
-
155
- 如果你不想要 Node.js 依赖,可以用 PowerShell 一行安装(基于 Inno Setup 安装器):
156
-
157
- ```powershell
158
- iwr https://static.uniner.com/soft/editor/uninercli/install.ps1 -UseBasicParsing | iex
159
- ```
160
-
161
- 两种方式互不干扰,但**不要**同时启用——会有两份 SKILL.md / 两份二进制。
162
-
163
- ## 链接
164
-
165
- - 官网:[https://www.uniner.com/](https://www.uniner.com/)
166
- - 技术支持:support@uniner.com
167
- - Claude Code:[https://claude.com/claude-code](https://claude.com/claude-code)
168
-
169
- ## License
170
-
171
- Proprietary. (C) 2026 Uniner Tech.
1
+ # uniner-cli
2
+
3
+ `uniner-cli` 是 [Uniner](https://www.uniner.com/) 的 RPA 自动化命令行工具,提供:
4
+
5
+ - **Web 自动化**(Playwright + CDP)—— 浏览器抓取、表单填写、元素交互
6
+ - **Excel 操作** —— 读写、批量处理、样式
7
+ - **OCR 识别** —— ddddocr(验证码)+ PaddleOCR(通用)
8
+ - **桌面 / SAP 自动化**(仅 Windows)
9
+ - **通用工具** —— 加密、HTTP、邮件、文件、PDF、压缩
10
+
11
+ 安装时会自动注册一个 [Claude Code](https://claude.com/claude-code) 技能(`~/.claude/skills/uniner-cli/SKILL.md`),让 Claude 一启动就能识别并调用本 CLI。
12
+
13
+ ---
14
+
15
+ ## 安装
16
+
17
+ ```bash
18
+ npm install -g uniner-cli
19
+ ```
20
+
21
+ 安装过程:
22
+
23
+ 1. npm 装本壳包(约 50KB)
24
+ 2. `postinstall` 钩子从 `static.uniner.com` 拉取对应平台的便携包(Windows ~280MB / macOS ~75MB / Linux ~185MB)
25
+ 3. 解压到 `~/.uniner-cli/<version>/`
26
+ 4. 写入 `~/.claude/skills/uniner-cli/SKILL.md`
27
+
28
+ 完成后任意终端:
29
+
30
+ ```bash
31
+ uniner-cli --help
32
+ ```
33
+
34
+ ## 系统要求
35
+
36
+ | 项 | 要求 |
37
+ |---|---|
38
+ | 操作系统 | Windows 10/11 x64 · macOS 12+ ARM64 · Linux x64 |
39
+ | Node.js | >= 14 |
40
+ | 磁盘空间 | ≥ 500 MB(单平台) |
41
+ | 网络 | 能访问 `static.uniner.com` |
42
+
43
+ > 安装脚本会自动检测当前系统并下载对应版本。
44
+
45
+ ## 使用示例
46
+
47
+ ```bash
48
+ # 看一级命令
49
+ uniner-cli --help
50
+
51
+ # 浏览器自动化
52
+ uniner-cli web browser open --url "https://example.com"
53
+ uniner-cli web element click --xpath "//button[@id='submit']"
54
+ uniner-cli web element get-text --xpath "//h1"
55
+
56
+ # Excel
57
+ uniner-cli excel data read --file data.xlsx --cell A1
58
+ uniner-cli excel data write --file out.xlsx --cell B2 --value "hello"
59
+
60
+ # OCR
61
+ uniner-cli tool ocr ddddocr --img captcha.png
62
+ ```
63
+
64
+ 完整命令树(一级 + 二级 + 三级参数)见安装后 `~/.uniner-cli/<version>/skills/cli/SKILL.md`。
65
+
66
+ ## 升级
67
+
68
+ ```bash
69
+ npm update -g uniner-cli
70
+ ```
71
+
72
+ 或者重新安装:
73
+
74
+ ```bash
75
+ npm install -g uniner-cli@latest
76
+ ```
77
+
78
+ > 不支持降级。如需回滚,请手动卸载后再装旧版。
79
+
80
+ ## 卸载
81
+
82
+ ```bash
83
+ npm uninstall -g uniner-cli
84
+ ```
85
+
86
+ 卸载时 `preuninstall` 钩子会自动清理:
87
+
88
+ - `~/.uniner-cli/` 整个运行时目录(含所有版本)
89
+ - `~/.claude/skills/uniner-cli/SKILL.md` 技能文件
90
+
91
+ > `~/.claude/skills/uniner-cli/scenarios/` 下用户积累的场景数据不会被自动删除,需手动清理。
92
+
93
+ 如需手动触发清理(如 npm 包已不在但残留仍在):
94
+
95
+ ```bash
96
+ uniner-cli-uninstall
97
+ ```
98
+
99
+ ## 环境变量
100
+
101
+ 无需手动配置。npm 全局 bin 目录已自动加入 PATH。
102
+
103
+ ## 故障排查
104
+
105
+ ### `uniner-cli: command not found`
106
+
107
+ 确认 npm 全局 bin 在 PATH 上:
108
+
109
+ ```bash
110
+ # macOS / Linux
111
+ npm config get prefix
112
+ echo $PATH | tr ':' '\n' | grep npm
113
+
114
+ # Windows (PowerShell)
115
+ npm config get prefix
116
+ $env:Path -split ';' | Where-Object { $_ -like '*npm*' }
117
+ ```
118
+
119
+ 如果没有,手动将 npm 全局 bin 目录加入 PATH:
120
+ - macOS / Linux: `$(npm config get prefix)/bin`
121
+ - Windows: `%APPDATA%\npm`
122
+
123
+ ### 安装时下载失败 / 超时
124
+
125
+ 公司代理或防火墙可能屏蔽 `static.uniner.com`。试试:
126
+
127
+ ```bash
128
+ npm config get proxy
129
+ npm config get https-proxy
130
+ # 设置代理:
131
+ npm config set https-proxy http://your-proxy:port
132
+ ```
133
+
134
+ 或者直接走原生安装方式(不走 npm):
135
+
136
+ ```powershell
137
+ iwr https://static.uniner.com/soft/editor/uninercli/install.ps1 -UseBasicParsing | iex
138
+ ```
139
+
140
+ ### postinstall 卡在解压
141
+
142
+ 杀软(360 / 火绒)会扫描 1279 个新文件,慢则 5–10 分钟。让它跑完即可。
143
+
144
+ ### 想重装
145
+
146
+ ```bash
147
+ npm uninstall -g uniner-cli
148
+ npm install -g uniner-cli
149
+ ```
150
+
151
+ ## 备选安装方式(仅 Windows,不走 npm)
152
+
153
+ 如果你不想要 Node.js 依赖,可以用 PowerShell 一行安装(基于 Inno Setup 安装器):
154
+
155
+ ```powershell
156
+ iwr https://static.uniner.com/soft/editor/uninercli/install.ps1 -UseBasicParsing | iex
157
+ ```
158
+
159
+ > macOS / Linux 用户请使用 npm 安装。
160
+
161
+ 两种方式互不干扰,但**不要**同时启用——会有两份 SKILL.md / 两份二进制。
162
+
163
+ ## 链接
164
+
165
+ - 官网:[https://www.uniner.com/](https://www.uniner.com/)
166
+ - 技术支持:support@uniner.com
167
+ - Claude Code:[https://claude.com/claude-code](https://claude.com/claude-code)
168
+
169
+ ## Changelog
170
+
171
+ ### 1.0.6 (2026-06-28)
172
+
173
+ - **fix**: 修复 `npm uninstall -g uniner-cli` 未清理 `~/.uniner-cli/` 和 `~/.claude/skills/uniner-cli/SKILL.md` 的问题(新增 `preuninstall` 钩子)
174
+
175
+ ### 1.0.5 (2026-06-27)
176
+
177
+ - 首个公开版本,支持 Windows 10/11 (x64)、macOS (Apple Silicon)、Linux (x64)
178
+
179
+ ## License
180
+
181
+ Proprietary. (C) 2026 Uniner Tech.
@@ -1,145 +1,48 @@
1
1
  #!/usr/bin/env node
2
- // uniner-cli-uninstall - removes the runtime tree and Claude Code skill.
2
+ // uniner-cli-uninstall — manual cleanup command.
3
3
  //
4
- // npm 7+ no longer runs preuninstall/postuninstall lifecycle scripts on
5
- // uninstall (security tightening). So we ship this command for users to
6
- // run BEFORE `npm uninstall -g uniner-cli` to clean their machine.
4
+ // Since v1.0.6 we register a `preuninstall` lifecycle hook that does the
5
+ // same cleanup automatically when you run `npm uninstall -g uniner-cli`.
6
+ // This manual command is still available for users who want verbose
7
+ // feedback, or who want to clean up without going through npm
8
+ // (e.g. the npm package is already gone but the runtime files remain).
7
9
  //
8
10
  // Recommended workflow:
9
- // uniner-cli-uninstall # this script: clean ~/.uniner-cli/ + skill
10
- // npm uninstall -g uniner-cli # npm: remove the npm package itself
11
+ // npm uninstall -g uniner-cli # hook runs the cleanup automatically
12
+ //
13
+ // If something is still left behind, run this to force a verbose cleanup:
14
+ // npx uniner-cli-uninstall # before uninstalling
15
+ // # or, after npm already removed the package:
16
+ // node ~/.uniner-cli/<version>/bin/uniner-cli-uninstall.js (if the shim is gone)
11
17
  //
12
18
  // Idempotent: if files are already gone, just no-ops.
13
19
 
14
20
  'use strict';
15
21
 
16
- const fs = require('fs');
17
- const path = require('path');
18
-
19
- const {
20
- pkg,
21
- rootDir,
22
- versionDir,
23
- skillDir,
24
- skillFile,
25
- } = require('../lib/paths');
22
+ const { cleanup } = require('../scripts/cleanup');
26
23
 
27
- function log(msg) { console.log(`[uniner-cli] ${msg}`); }
28
- function warn(msg) { console.warn(`[uniner-cli] [WARN] ${msg}`); }
24
+ console.log('');
25
+ console.log('==========================================');
26
+ console.log(' uniner-cli uninstall cleanup');
27
+ console.log('==========================================');
29
28
 
30
- function rmRf(p) {
31
- try {
32
- if (fs.existsSync(p)) {
33
- fs.rmSync(p, { recursive: true, force: true });
34
- log(`Removed ${p}`);
35
- return true;
36
- }
37
- return false;
38
- } catch (e) {
39
- warn(`Could not remove ${p}: ${e.message}`);
40
- return false;
41
- }
42
- }
29
+ const result = cleanup({ verbose: true });
43
30
 
44
- function rmFile(p) {
45
- try {
46
- if (fs.existsSync(p)) {
47
- fs.unlinkSync(p);
48
- log(`Removed ${p}`);
49
- return true;
50
- }
51
- return false;
52
- } catch (e) {
53
- warn(`Could not remove ${p}: ${e.message}`);
54
- return false;
55
- }
56
- }
57
-
58
- function main() {
59
- console.log('');
31
+ console.log('');
32
+ if (result.scenariosKept > 0) {
60
33
  console.log('==========================================');
61
- console.log(' uniner-cli uninstall cleanup');
34
+ console.log(` ${result.scenariosKept} scenario(s) PRESERVED at:`);
35
+ console.log(` ${result.scenariosDir}`);
36
+ console.log(' (your accumulated knowledge — not deleted)');
37
+ console.log(' To delete them too:');
38
+ console.log(` rm -rf "${result.scenariosDir}" # bash`);
39
+ console.log(` Remove-Item "${result.scenariosDir}" -Recurse -Force # PowerShell`);
62
40
  console.log('==========================================');
63
-
64
- const myVersion = pkg.version;
65
- const myVersionDir = versionDir(myVersion);
66
- let didSomething = false;
67
-
68
- // 1. Remove our version's runtime tree.
69
- if (rmRf(myVersionDir)) didSomething = true;
70
-
71
- // 2. If ~/.uniner-cli/ has no other versions, drop the whole directory.
72
- try {
73
- const root = rootDir();
74
- if (fs.existsSync(root)) {
75
- const remaining = fs.readdirSync(root);
76
- if (remaining.length === 0) {
77
- fs.rmdirSync(root);
78
- log(`Removed empty ${root}`);
79
- didSomething = true;
80
- } else {
81
- log(`Other uniner-cli versions still installed: ${remaining.join(', ')}`);
82
- log(`Keeping ${root}`);
83
- }
84
- }
85
- } catch (e) {
86
- warn(`Could not inspect ${rootDir()}: ${e.message}`);
87
- }
88
-
89
- // 3. Remove ONLY the SKILL.md file. NEVER rm -rf the whole skill dir,
90
- // because the user's accumulated scenarios/<name>/ subdirectories
91
- // live there and represent real work they've sedimented over time.
92
- // If they really want everything gone they can do it manually; we
93
- // surface the path below so they know where to look.
94
- const dir = skillDir();
95
- if (rmFile(skillFile())) didSomething = true;
96
-
97
- // 4. Look for scenarios/ — if there are any, keep them and tell the user.
98
- let scenariosKept = 0;
99
- const scenariosDir = path.join(dir, 'scenarios');
100
- if (fs.existsSync(scenariosDir)) {
101
- try {
102
- scenariosKept = fs.readdirSync(scenariosDir).filter(n => {
103
- const meta = path.join(scenariosDir, n, 'meta.json');
104
- return fs.existsSync(meta);
105
- }).length;
106
- } catch (_) {
107
- // best-effort
108
- }
109
- }
110
-
111
- // 5. If skill dir is now genuinely empty, remove it. Otherwise keep it.
112
- try {
113
- if (fs.existsSync(dir)) {
114
- const remaining = fs.readdirSync(dir);
115
- if (remaining.length === 0) {
116
- fs.rmdirSync(dir);
117
- log(`Removed empty ${dir}`);
118
- didSomething = true;
119
- }
120
- }
121
- } catch (_) {
122
- // best-effort
123
- }
124
-
125
- if (!didSomething) {
126
- log('Nothing to clean — already removed.');
127
- }
128
-
129
- console.log('');
130
- if (scenariosKept > 0) {
131
- console.log('==========================================');
132
- console.log(` ${scenariosKept} scenario(s) PRESERVED at:`);
133
- console.log(` ${scenariosDir}`);
134
- console.log(' (your accumulated knowledge — not deleted)');
135
- console.log(' To delete them too:');
136
- console.log(` rm -rf "${scenariosDir}" # bash`);
137
- console.log(` Remove-Item "${scenariosDir}" -Recurse -Force # PowerShell`);
138
- console.log('==========================================');
139
- }
140
- console.log('Cleanup done. To complete uninstall, also run:');
141
- console.log(' npm uninstall -g uniner-cli');
142
- console.log('');
143
41
  }
144
42
 
145
- main();
43
+ if (result.didSomething) {
44
+ console.log('Cleanup done.');
45
+ } else {
46
+ console.log('Nothing to clean — already removed.');
47
+ }
48
+ console.log('');
package/bin/uniner-cli.js CHANGED
@@ -1,42 +1,42 @@
1
- #!/usr/bin/env node
2
- // Tiny launcher: forward every CLI invocation to the real PyInstaller exe.
3
- //
4
- // `npm install -g uniner-cli` registers this file as the global `uniner-cli`
5
- // command (because of the `bin` entry in package.json). We just spawn the real
6
- // binary with the same argv/stdio and exit with its status code.
7
-
8
- 'use strict';
9
-
10
- const { spawnSync } = require('child_process');
11
- const fs = require('fs');
12
- const { realBinaryPath, isInstalled, pkg } = require('../lib/paths');
13
-
14
- const exe = realBinaryPath();
15
-
16
- if (!isInstalled()) {
17
- console.error('');
18
- console.error(`[uniner-cli] Real binary not found at: ${exe}`);
19
- console.error('[uniner-cli] The npm postinstall step must not have completed.');
20
- console.error('[uniner-cli] Try one of:');
21
- console.error('[uniner-cli] npm rebuild -g uniner-cli');
22
- console.error('[uniner-cli] npm uninstall -g uniner-cli && npm install -g uniner-cli');
23
- console.error('');
24
- process.exit(127);
25
- }
26
-
27
- // stdio:'inherit' forwards stdin/stdout/stderr unchanged so interactive prompts
28
- // and ANSI color survive. shell:false avoids any path-quoting surprises.
29
- const result = spawnSync(exe, process.argv.slice(2), {
30
- stdio: 'inherit',
31
- shell: false,
32
- windowsHide: false,
33
- });
34
-
35
- if (result.error) {
36
- console.error(`[uniner-cli] Failed to launch ${exe}: ${result.error.message}`);
37
- process.exit(1);
38
- }
39
-
40
- // `status` is null when the child was killed by a signal; in that case let
41
- // the user see we did not exit cleanly.
42
- process.exit(result.status === null ? 1 : result.status);
1
+ #!/usr/bin/env node
2
+ // Tiny launcher: forward every CLI invocation to the real PyInstaller exe.
3
+ //
4
+ // `npm install -g uniner-cli` registers this file as the global `uniner-cli`
5
+ // command (because of the `bin` entry in package.json). We just spawn the real
6
+ // binary with the same argv/stdio and exit with its status code.
7
+
8
+ 'use strict';
9
+
10
+ const { spawnSync } = require('child_process');
11
+ const fs = require('fs');
12
+ const { realBinaryPath, isInstalled, pkg } = require('../lib/paths');
13
+
14
+ const exe = realBinaryPath();
15
+
16
+ if (!isInstalled()) {
17
+ console.error('');
18
+ console.error(`[uniner-cli] Real binary not found at: ${exe}`);
19
+ console.error('[uniner-cli] The npm postinstall step must not have completed.');
20
+ console.error('[uniner-cli] Try one of:');
21
+ console.error('[uniner-cli] npm rebuild -g uniner-cli');
22
+ console.error('[uniner-cli] npm uninstall -g uniner-cli && npm install -g uniner-cli');
23
+ console.error('');
24
+ process.exit(127);
25
+ }
26
+
27
+ // stdio:'inherit' forwards stdin/stdout/stderr unchanged so interactive prompts
28
+ // and ANSI color survive. shell:false avoids any path-quoting surprises.
29
+ const result = spawnSync(exe, process.argv.slice(2), {
30
+ stdio: 'inherit',
31
+ shell: false,
32
+ windowsHide: false,
33
+ });
34
+
35
+ if (result.error) {
36
+ console.error(`[uniner-cli] Failed to launch ${exe}: ${result.error.message}`);
37
+ process.exit(1);
38
+ }
39
+
40
+ // `status` is null when the child was killed by a signal; in that case let
41
+ // the user see we did not exit cleanly.
42
+ process.exit(result.status === null ? 1 : result.status);