uniner-cli 1.0.3 → 1.0.5
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 +4 -4
- package/README.md +161 -171
- package/bin/uniner-cli-uninstall.js +145 -145
- package/bin/uniner-cli.js +42 -42
- package/lib/SKILL.md.template +371 -371
- package/lib/paths.js +78 -78
- package/package.json +49 -47
- package/scripts/postinstall-helpers.js +140 -0
- package/scripts/postinstall.js +385 -428
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,161 @@
|
|
|
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`
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
#
|
|
57
|
-
uniner-cli --
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
uniner-cli
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
uniner-cli
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
uniner-cli
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
##
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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` 拉取约 250MB 便携包
|
|
25
|
+
3. 解压到 `%USERPROFILE%\.uniner-cli\<version>\`
|
|
26
|
+
4. 写入 `%USERPROFILE%\.claude\skills\uniner-cli\SKILL.md`
|
|
27
|
+
|
|
28
|
+
完成后任意终端:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
uniner-cli --help
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## 系统要求
|
|
35
|
+
|
|
36
|
+
| 项 | 要求 |
|
|
37
|
+
|---|---|
|
|
38
|
+
| 操作系统 | 64 位 Windows 10 / 11 |
|
|
39
|
+
| Node.js | >= 14 |
|
|
40
|
+
| 磁盘空间 | ≥ 1.5 GB |
|
|
41
|
+
| 网络 | 能访问 `static.uniner.com` |
|
|
42
|
+
|
|
43
|
+
> Linux / macOS 支持在路上。
|
|
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
|
+
完整命令树(一级 + 二级 + 三级参数)见安装后 `%USERPROFILE%\.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
|
+
# 1. 先清理运行时数据(删 ~/.uniner-cli/ 和 Claude Code 技能)
|
|
84
|
+
uniner-cli-uninstall
|
|
85
|
+
|
|
86
|
+
# 2. 再卸载 npm 包本身
|
|
87
|
+
npm uninstall -g uniner-cli
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
> **为什么是两步?** npm 7+ 出于安全考虑不再在卸载时运行 lifecycle 脚本,所以我们提供 `uniner-cli-uninstall` 命令让你显式触发清理。
|
|
91
|
+
|
|
92
|
+
清理会移除:
|
|
93
|
+
|
|
94
|
+
- `%USERPROFILE%\.uniner-cli\<version>\` 安装目录
|
|
95
|
+
- `%USERPROFILE%\.claude\skills\uniner-cli\SKILL.md` 技能
|
|
96
|
+
|
|
97
|
+
## 环境变量
|
|
98
|
+
|
|
99
|
+
无需手动配置。npm 全局 bin 目录已自动加入 PATH。
|
|
100
|
+
|
|
101
|
+
## 故障排查
|
|
102
|
+
|
|
103
|
+
### `uniner-cli: command not found`
|
|
104
|
+
|
|
105
|
+
确认 npm 全局 bin 在 PATH 上:
|
|
106
|
+
|
|
107
|
+
```powershell
|
|
108
|
+
npm config get prefix
|
|
109
|
+
# 应输出类似 C:\Users\xxx\AppData\Roaming\npm
|
|
110
|
+
$env:Path -split ';' | Where-Object { $_ -like '*npm*' }
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
如果没有,手动加 `%APPDATA%\npm` 到 PATH。
|
|
114
|
+
|
|
115
|
+
### 安装时下载失败 / 超时
|
|
116
|
+
|
|
117
|
+
公司代理或防火墙可能屏蔽 `static.uniner.com`。试试:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
npm config get proxy
|
|
121
|
+
npm config get https-proxy
|
|
122
|
+
# 设置代理:
|
|
123
|
+
npm config set https-proxy http://your-proxy:port
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
或者直接走原生安装方式(不走 npm):
|
|
127
|
+
|
|
128
|
+
```powershell
|
|
129
|
+
iwr https://static.uniner.com/soft/editor/uninercli/install.ps1 -UseBasicParsing | iex
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### postinstall 卡在解压
|
|
133
|
+
|
|
134
|
+
杀软(360 / 火绒)会扫描 1279 个新文件,慢则 5–10 分钟。让它跑完即可。
|
|
135
|
+
|
|
136
|
+
### 想重装
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
npm uninstall -g uniner-cli
|
|
140
|
+
npm install -g uniner-cli
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## 备选安装方式(不走 npm)
|
|
144
|
+
|
|
145
|
+
如果你不想要 Node.js 依赖,可以用 PowerShell 一行安装(基于 Inno Setup 安装器):
|
|
146
|
+
|
|
147
|
+
```powershell
|
|
148
|
+
iwr https://static.uniner.com/soft/editor/uninercli/install.ps1 -UseBasicParsing | iex
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
两种方式互不干扰,但**不要**同时启用——会有两份 SKILL.md / 两份二进制。
|
|
152
|
+
|
|
153
|
+
## 链接
|
|
154
|
+
|
|
155
|
+
- 官网:[https://www.uniner.com/](https://www.uniner.com/)
|
|
156
|
+
- 技术支持:support@uniner.com
|
|
157
|
+
- Claude Code:[https://claude.com/claude-code](https://claude.com/claude-code)
|
|
158
|
+
|
|
159
|
+
## License
|
|
160
|
+
|
|
161
|
+
Proprietary. (C) 2026 Uniner Tech.
|
|
@@ -1,145 +1,145 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// uniner-cli-uninstall - removes the runtime tree and Claude Code skill.
|
|
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.
|
|
7
|
-
//
|
|
8
|
-
// 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
|
-
//
|
|
12
|
-
// Idempotent: if files are already gone, just no-ops.
|
|
13
|
-
|
|
14
|
-
'use strict';
|
|
15
|
-
|
|
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');
|
|
26
|
-
|
|
27
|
-
function log(msg) { console.log(`[uniner-cli] ${msg}`); }
|
|
28
|
-
function warn(msg) { console.warn(`[uniner-cli] [WARN] ${msg}`); }
|
|
29
|
-
|
|
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
|
-
}
|
|
43
|
-
|
|
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('');
|
|
60
|
-
console.log('==========================================');
|
|
61
|
-
console.log(' uniner-cli uninstall cleanup');
|
|
62
|
-
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
|
-
}
|
|
144
|
-
|
|
145
|
-
main();
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// uniner-cli-uninstall - removes the runtime tree and Claude Code skill.
|
|
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.
|
|
7
|
+
//
|
|
8
|
+
// 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
|
+
//
|
|
12
|
+
// Idempotent: if files are already gone, just no-ops.
|
|
13
|
+
|
|
14
|
+
'use strict';
|
|
15
|
+
|
|
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');
|
|
26
|
+
|
|
27
|
+
function log(msg) { console.log(`[uniner-cli] ${msg}`); }
|
|
28
|
+
function warn(msg) { console.warn(`[uniner-cli] [WARN] ${msg}`); }
|
|
29
|
+
|
|
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
|
+
}
|
|
43
|
+
|
|
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('');
|
|
60
|
+
console.log('==========================================');
|
|
61
|
+
console.log(' uniner-cli uninstall cleanup');
|
|
62
|
+
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
|
+
}
|
|
144
|
+
|
|
145
|
+
main();
|