uniner-cli 1.0.24 → 1.0.26
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 +3 -4
- package/bin/uniner-cli-uninstall.js +2 -14
- package/bin/uniner-cli.js +1 -5
- package/lib/paths.js +0 -16
- package/package.json +5 -2
- package/scripts/cleanup.js +68 -123
- package/scripts/postinstall.js +10 -134
- package/scripts/preuninstall.js +4 -4
- package/lib/SKILL.md.template +0 -404
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
- **桌面 / SAP 自动化** —— 支持桌面软件的自动化(仅 Windows)
|
|
9
9
|
- **通用工具** —— 加密、HTTP、邮件、文件、PDF、压缩、FTP
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
在项目目录运行 `uniner-cli setup-for-agent` 可把 skill 文档部署到该项目 `.agents/skills/`,Claude Code / Codex / pi 等 Agent 即可识别并调用本 CLI。
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
@@ -112,10 +112,9 @@ uniner-cli uninstall --local
|
|
|
112
112
|
|
|
113
113
|
清理内容:
|
|
114
114
|
|
|
115
|
-
- `~/.uniner-cli/`
|
|
116
|
-
- `~/.claude/skills/uniner-cli/SKILL.md` 技能文件
|
|
115
|
+
- `~/.uniner-cli/` 整个运行时目录(含所有版本、文档与场景库 `scenarios/`)
|
|
117
116
|
|
|
118
|
-
>
|
|
117
|
+
> 项目里的 `.agents/skills/`(`setup-for-agent` 部署)与 `.claude/skills/` 桥接在用户项目内,卸载不会触碰,不需要时请自行删除。
|
|
119
118
|
|
|
120
119
|
**备选方式**(如果 npm 包已经先被卸载,`uniner-cli` 不在 PATH 上):
|
|
121
120
|
|
|
@@ -6,8 +6,7 @@
|
|
|
6
6
|
// lifecycle hook for globally installed packages. This means
|
|
7
7
|
// `npm uninstall -g uniner-cli` removes the npm package but leaves
|
|
8
8
|
// residual files behind:
|
|
9
|
-
// - ~/.uniner-cli/ (runtime binaries, skills, docs)
|
|
10
|
-
// - ~/.claude/skills/uniner-cli/SKILL.md (Claude Code skill file)
|
|
9
|
+
// - ~/.uniner-cli/ (runtime binaries, skills, docs, scenarios)
|
|
11
10
|
//
|
|
12
11
|
// To solve this, postinstall writes a self-contained cleanup script to
|
|
13
12
|
// ~/.uniner-cli/cleanup.js. That file survives `npm uninstall` and can
|
|
@@ -36,7 +35,7 @@ const standalonePath = path.join(os.homedir(), '.uniner-cli', 'cleanup.js');
|
|
|
36
35
|
|
|
37
36
|
if (fs.existsSync(standalonePath)) {
|
|
38
37
|
// Delegate to the standalone script. It handles everything: removing
|
|
39
|
-
// ~/.uniner-cli
|
|
38
|
+
// the ~/.uniner-cli/ tree (binaries, docs, scenarios).
|
|
40
39
|
// Use spawnSync to avoid any require() path issues.
|
|
41
40
|
const { spawnSync } = require('child_process');
|
|
42
41
|
const result = spawnSync(process.execPath, [standalonePath], {
|
|
@@ -65,17 +64,6 @@ console.log('');
|
|
|
65
64
|
const result = cleanup({ verbose: true });
|
|
66
65
|
|
|
67
66
|
console.log('');
|
|
68
|
-
if (result.scenariosKept > 0) {
|
|
69
|
-
console.log('==========================================');
|
|
70
|
-
console.log(` ${result.scenariosKept} scenario(s) PRESERVED at:`);
|
|
71
|
-
console.log(` ${result.scenariosDir}`);
|
|
72
|
-
console.log(' (your accumulated knowledge — not deleted)');
|
|
73
|
-
console.log(' To delete them too:');
|
|
74
|
-
console.log(` rm -rf "${result.scenariosDir}" # bash`);
|
|
75
|
-
console.log(` Remove-Item "${result.scenariosDir}" -Recurse -Force # PowerShell`);
|
|
76
|
-
console.log('==========================================');
|
|
77
|
-
}
|
|
78
|
-
|
|
79
67
|
if (result.didSomething) {
|
|
80
68
|
console.log('Cleanup done.');
|
|
81
69
|
} else {
|
package/bin/uniner-cli.js
CHANGED
|
@@ -21,7 +21,7 @@ function printUninstallHelp() {
|
|
|
21
21
|
console.log('');
|
|
22
22
|
console.log('Usage: uniner-cli uninstall [--global|--local]');
|
|
23
23
|
console.log('');
|
|
24
|
-
console.log('Cleans ~/.uniner-cli
|
|
24
|
+
console.log('Cleans ~/.uniner-cli (binaries, docs, scenarios),');
|
|
25
25
|
console.log('then removes this npm package from the detected install scope.');
|
|
26
26
|
console.log('');
|
|
27
27
|
}
|
|
@@ -196,10 +196,6 @@ function runFullUninstall() {
|
|
|
196
196
|
const status = result.status === null ? 1 : result.status;
|
|
197
197
|
if (status === 0) {
|
|
198
198
|
console.log('');
|
|
199
|
-
if (cleanupResult.scenariosKept > 0) {
|
|
200
|
-
console.log(`[uniner-cli] Preserved ${cleanupResult.scenariosKept} scenario(s) at:`);
|
|
201
|
-
console.log(`[uniner-cli] ${cleanupResult.scenariosDir}`);
|
|
202
|
-
}
|
|
203
199
|
console.log('[uniner-cli] Uninstall complete.');
|
|
204
200
|
console.log('');
|
|
205
201
|
}
|
package/lib/paths.js
CHANGED
|
@@ -44,19 +44,6 @@ function realBinaryPath(version) {
|
|
|
44
44
|
return path.join(versionDir(ver), 'bin', 'uniner-cli', 'uniner-cli');
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
function skillDir() {
|
|
48
|
-
// Same place Claude Code looks for user-global skills.
|
|
49
|
-
return path.join(homeDir(), '.claude', 'skills', 'uniner-cli');
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function skillFile() {
|
|
53
|
-
return path.join(skillDir(), 'SKILL.md');
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function templatePath() {
|
|
57
|
-
return path.join(__dirname, 'SKILL.md.template');
|
|
58
|
-
}
|
|
59
|
-
|
|
60
47
|
function isInstalled(version) {
|
|
61
48
|
try {
|
|
62
49
|
return fs.statSync(realBinaryPath(version)).isFile();
|
|
@@ -71,8 +58,5 @@ module.exports = {
|
|
|
71
58
|
rootDir,
|
|
72
59
|
versionDir,
|
|
73
60
|
realBinaryPath,
|
|
74
|
-
skillDir,
|
|
75
|
-
skillFile,
|
|
76
|
-
templatePath,
|
|
77
61
|
isInstalled,
|
|
78
62
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "uniner-cli",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "uniner-cli — RPA automation CLI: browser automation (Playwright/CDP), Excel, OCR, desktop/SAP, crypto/HTTP/email tools.
|
|
3
|
+
"version": "1.0.26",
|
|
4
|
+
"description": "uniner-cli — RPA automation CLI: browser automation (Playwright/CDP), Excel, OCR, desktop/SAP, crypto/HTTP/email tools. Deploy Agent skills to your project via `uniner-cli setup-for-agent`.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"rpa",
|
|
7
7
|
"automation",
|
|
@@ -9,7 +9,10 @@
|
|
|
9
9
|
"playwright",
|
|
10
10
|
"excel",
|
|
11
11
|
"ocr",
|
|
12
|
+
"agent-skills",
|
|
12
13
|
"claude-code",
|
|
14
|
+
"codex",
|
|
15
|
+
"pi",
|
|
13
16
|
"ai-agent"
|
|
14
17
|
],
|
|
15
18
|
"homepage": "https://www.uniner.com/",
|
package/scripts/cleanup.js
CHANGED
|
@@ -1,123 +1,68 @@
|
|
|
1
|
-
// Shared cleanup logic for uninstall.
|
|
2
|
-
// Extracted so both the npm lifecycle hook (preuninstall.js) and the
|
|
3
|
-
// manual command (bin/uniner-cli-uninstall.js) can share it.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
log(`Removed ${root}`);
|
|
70
|
-
didSomething = true;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// 2. Remove ONLY the SKILL.md file. NEVER rm -rf the whole skill dir,
|
|
74
|
-
// because the user's accumulated scenarios/<name>/ subdirectories
|
|
75
|
-
// live there and represent real work they've sedimented over time.
|
|
76
|
-
const dir = skillDir();
|
|
77
|
-
if (rmFile(skillFile())) {
|
|
78
|
-
log(`Removed ${skillFile()}`);
|
|
79
|
-
didSomething = true;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// 3. Count scenarios/ subdirs that contain a meta.json.
|
|
83
|
-
let scenariosKept = 0;
|
|
84
|
-
const scenariosPath = path.join(dir, 'scenarios');
|
|
85
|
-
if (fs.existsSync(scenariosPath)) {
|
|
86
|
-
try {
|
|
87
|
-
scenariosKept = fs.readdirSync(scenariosPath).filter(n => {
|
|
88
|
-
const meta = path.join(scenariosPath, n, 'meta.json');
|
|
89
|
-
return fs.existsSync(meta);
|
|
90
|
-
}).length;
|
|
91
|
-
} catch (_) {
|
|
92
|
-
// best-effort
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// 4. If skill dir is now genuinely empty, remove it. Otherwise keep it.
|
|
97
|
-
try {
|
|
98
|
-
if (fs.existsSync(dir)) {
|
|
99
|
-
const remaining = fs.readdirSync(dir);
|
|
100
|
-
if (remaining.length === 0) {
|
|
101
|
-
fs.rmdirSync(dir);
|
|
102
|
-
log(`Removed empty ${dir}`);
|
|
103
|
-
didSomething = true;
|
|
104
|
-
} else if (verbose) {
|
|
105
|
-
log(`Keeping ${dir} (${scenariosKept} scenario(s) preserved)`);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
} catch (_) {
|
|
109
|
-
// best-effort
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if (!didSomething) {
|
|
113
|
-
log('Nothing to clean — already removed.');
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return {
|
|
117
|
-
didSomething,
|
|
118
|
-
scenariosKept,
|
|
119
|
-
scenariosDir: scenariosKept > 0 ? scenariosPath : null,
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
module.exports = { cleanup };
|
|
1
|
+
// Shared cleanup logic for uninstall.
|
|
2
|
+
// Extracted so both the npm lifecycle hook (preuninstall.js) and the
|
|
3
|
+
// manual command (bin/uniner-cli-uninstall.js) can share it.
|
|
4
|
+
//
|
|
5
|
+
// Full removal: ~/.uniner-cli/ holds binaries, docs AND the scenarios
|
|
6
|
+
// library (moved out of ~/.claude/ in 1.0.25), so deleting the whole tree
|
|
7
|
+
// is the correct uninstall semantics. Project-level .agents/skills/ and
|
|
8
|
+
// .claude/ bridges created by `uniner-cli setup-for-agent` live inside
|
|
9
|
+
// user projects and are intentionally NOT touched here.
|
|
10
|
+
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
|
|
15
|
+
const {
|
|
16
|
+
rootDir,
|
|
17
|
+
} = require('../lib/paths');
|
|
18
|
+
|
|
19
|
+
function rmRf(p) {
|
|
20
|
+
try {
|
|
21
|
+
if (fs.existsSync(p)) {
|
|
22
|
+
fs.rmSync(p, { recursive: true, force: true });
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
return false;
|
|
26
|
+
} catch (_) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Run the full uninstall cleanup.
|
|
33
|
+
*
|
|
34
|
+
* @param {object} [opts]
|
|
35
|
+
* @param {boolean} [opts.verbose=false] Print progress lines to stdout.
|
|
36
|
+
* The preuninstall hook should pass false
|
|
37
|
+
* because npm swallows lifecycle output
|
|
38
|
+
* unless --verbose is used; the manual
|
|
39
|
+
* command passes true so the user sees
|
|
40
|
+
* what happened.
|
|
41
|
+
* @returns {{ didSomething: boolean }}
|
|
42
|
+
*/
|
|
43
|
+
function cleanup(opts) {
|
|
44
|
+
const verbose = (opts && opts.verbose) || false;
|
|
45
|
+
|
|
46
|
+
function log(msg) {
|
|
47
|
+
if (verbose) {
|
|
48
|
+
console.log(`[uniner-cli] ${msg}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const root = rootDir();
|
|
53
|
+
|
|
54
|
+
// Remove the entire ~/.uniner-cli/ tree (all versions, scenarios, docs).
|
|
55
|
+
// The version-specific layout exists so the installer can support
|
|
56
|
+
// multi-version coexistence, but on uninstall the user wants a full
|
|
57
|
+
// removal — orphaned old version dirs would just waste disk space.
|
|
58
|
+
const didSomething = rmRf(root);
|
|
59
|
+
if (didSomething) {
|
|
60
|
+
log(`Removed ${root}`);
|
|
61
|
+
} else {
|
|
62
|
+
log('Nothing to clean — already removed.');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return { didSomething };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
module.exports = { cleanup };
|
package/scripts/postinstall.js
CHANGED
|
@@ -9,9 +9,7 @@
|
|
|
9
9
|
// - Up to 3 retries with exponential backoff.
|
|
10
10
|
// 4. Verify SHA-256.
|
|
11
11
|
// 5. Extract via PowerShell's Expand-Archive (no native dep).
|
|
12
|
-
// 6.
|
|
13
|
-
// and write to ~/.claude/skills/uniner-cli/SKILL.md.
|
|
14
|
-
// 7. Print "ready" and how to validate.
|
|
12
|
+
// 6. Print "ready", how to enable the Agent skill (setup-for-agent), validate.
|
|
15
13
|
//
|
|
16
14
|
// Failures throw -> non-zero exit -> npm marks install as failed (strict mode).
|
|
17
15
|
// User can then `npm install -g uniner-cli` again to retry cleanly.
|
|
@@ -32,9 +30,6 @@ const {
|
|
|
32
30
|
versionDir,
|
|
33
31
|
realBinaryPath,
|
|
34
32
|
isInstalled,
|
|
35
|
-
skillDir,
|
|
36
|
-
skillFile,
|
|
37
|
-
templatePath,
|
|
38
33
|
} = require('../lib/paths');
|
|
39
34
|
|
|
40
35
|
const {
|
|
@@ -196,48 +191,6 @@ function sha256OfFile(file) {
|
|
|
196
191
|
});
|
|
197
192
|
}
|
|
198
193
|
|
|
199
|
-
// ----------------------------------------------------------------------
|
|
200
|
-
// SKILL.md template render
|
|
201
|
-
// ----------------------------------------------------------------------
|
|
202
|
-
|
|
203
|
-
function writeSkillMd(installPath, version) {
|
|
204
|
-
const tplPath = templatePath();
|
|
205
|
-
if (!fs.existsSync(tplPath)) {
|
|
206
|
-
log(` [WARN] SKILL.md.template missing at ${tplPath}; skipping skill install.`);
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
let content = fs.readFileSync(tplPath, 'utf8');
|
|
210
|
-
// PowerShell-side StringChangeEx is case-insensitive; here we keep it strict
|
|
211
|
-
// because our template uses exactly these tokens.
|
|
212
|
-
content = content.split('{{INSTALL_PATH}}').join(installPath);
|
|
213
|
-
content = content.split('{{VERSION}}').join(version);
|
|
214
|
-
|
|
215
|
-
const dir = skillDir();
|
|
216
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
217
|
-
|
|
218
|
-
// IMPORTANT: only writeFileSync the single SKILL.md file. Do NOT touch
|
|
219
|
-
// anything else under ~/.claude/skills/uniner-cli/. In particular, the
|
|
220
|
-
// scenarios/ subdirectory is the user's accumulated knowledge — if we
|
|
221
|
-
// ever rmdir or rmSync this directory we'd wipe their library on every
|
|
222
|
-
// upgrade. Single-file write is the contract.
|
|
223
|
-
fs.writeFileSync(skillFile(), content, 'utf8');
|
|
224
|
-
log(` SKILL.md written to ${skillFile()}`);
|
|
225
|
-
|
|
226
|
-
// Tell the user we kept their scenarios (if any).
|
|
227
|
-
const scenariosDir = path.join(dir, 'scenarios');
|
|
228
|
-
if (fs.existsSync(scenariosDir)) {
|
|
229
|
-
try {
|
|
230
|
-
const count = fs.readdirSync(scenariosDir).filter(n => {
|
|
231
|
-
const meta = path.join(scenariosDir, n, 'meta.json');
|
|
232
|
-
return fs.existsSync(meta);
|
|
233
|
-
}).length;
|
|
234
|
-
log(` [INFO] Preserved ${count} scenario(s) in ${scenariosDir}`);
|
|
235
|
-
} catch (_) {
|
|
236
|
-
// best-effort; never fail postinstall over a logging stat
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
194
|
// ----------------------------------------------------------------------
|
|
242
195
|
// Write standalone cleanup script to ~/.uniner-cli/cleanup.js
|
|
243
196
|
//
|
|
@@ -264,9 +217,8 @@ function writeCleanupScript() {
|
|
|
264
217
|
// --------------------
|
|
265
218
|
// npm 7+ (including npm 11) does NOT reliably execute the \`preuninstall\`
|
|
266
219
|
// lifecycle hook for globally installed packages. Running
|
|
267
|
-
// \`npm uninstall -g uniner-cli\` removes the npm package but leaves
|
|
268
|
-
//
|
|
269
|
-
// - ~/.claude/skills/uniner-cli/SKILL.md (Claude Code skill file)
|
|
220
|
+
// \`npm uninstall -g uniner-cli\` removes the npm package but leaves
|
|
221
|
+
// ~/.uniner-cli/ (runtime binaries, skills, docs, scenarios).
|
|
270
222
|
//
|
|
271
223
|
// This script was written to this location by the postinstall hook. Because
|
|
272
224
|
// it lives OUTSIDE the npm package directory, it survives \`npm uninstall\`
|
|
@@ -276,24 +228,14 @@ function writeCleanupScript() {
|
|
|
276
228
|
//
|
|
277
229
|
// Idempotent: running it twice is harmless; the second run just reports
|
|
278
230
|
// "nothing to clean".
|
|
279
|
-
//
|
|
280
|
-
// WHAT IT PRESERVES
|
|
281
|
-
// -----------------
|
|
282
|
-
// The \`scenarios/\` subdirectory under
|
|
283
|
-
// ~/.claude/skills/uniner-cli/scenarios/
|
|
284
|
-
// contains user-accumulated knowledge (saved workflows). Those are NEVER
|
|
285
|
-
// deleted by this script — only the single SKILL.md file is removed.
|
|
286
231
|
|
|
287
232
|
'use strict';
|
|
288
233
|
|
|
289
|
-
const fs
|
|
234
|
+
const fs = require('fs');
|
|
290
235
|
const path = require('path');
|
|
291
|
-
const os
|
|
236
|
+
const os = require('os');
|
|
292
237
|
|
|
293
|
-
const rootDir
|
|
294
|
-
const claudeSkillRoot = path.join(os.homedir(), '.claude', 'skills', 'uniner-cli');
|
|
295
|
-
const skillFilePath = path.join(claudeSkillRoot, 'SKILL.md');
|
|
296
|
-
const scenariosDir = path.join(claudeSkillRoot, 'scenarios');
|
|
238
|
+
const rootDir = path.join(os.homedir(), '.uniner-cli');
|
|
297
239
|
|
|
298
240
|
function rmRf(p) {
|
|
299
241
|
try {
|
|
@@ -305,73 +247,14 @@ function rmRf(p) {
|
|
|
305
247
|
} catch (_) { return false; }
|
|
306
248
|
}
|
|
307
249
|
|
|
308
|
-
function rmFile(p) {
|
|
309
|
-
try {
|
|
310
|
-
if (fs.existsSync(p)) {
|
|
311
|
-
fs.unlinkSync(p);
|
|
312
|
-
return true;
|
|
313
|
-
}
|
|
314
|
-
return false;
|
|
315
|
-
} catch (_) { return false; }
|
|
316
|
-
}
|
|
317
|
-
|
|
318
250
|
console.log('');
|
|
319
251
|
console.log('==========================================');
|
|
320
252
|
console.log(' uniner-cli uninstall cleanup');
|
|
321
253
|
console.log('==========================================');
|
|
322
254
|
|
|
323
|
-
let didSomething = false;
|
|
324
|
-
|
|
325
|
-
// 1. Remove entire ~/.uniner-cli/ tree (all versions + this script itself).
|
|
326
255
|
if (rmRf(rootDir)) {
|
|
327
256
|
console.log('[uniner-cli] Removed ' + rootDir);
|
|
328
|
-
didSomething = true;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
// 2. Remove ONLY the SKILL.md file. NEVER rm -rf the whole skill dir —
|
|
332
|
-
// scenarios/ lives there and is the user's accumulated knowledge.
|
|
333
|
-
if (rmFile(skillFilePath)) {
|
|
334
|
-
console.log('[uniner-cli] Removed ' + skillFilePath);
|
|
335
|
-
didSomething = true;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
// 3. Count preserved scenarios.
|
|
339
|
-
let scenariosKept = 0;
|
|
340
|
-
if (fs.existsSync(scenariosDir)) {
|
|
341
|
-
try {
|
|
342
|
-
scenariosKept = fs.readdirSync(scenariosDir).filter(function(n) {
|
|
343
|
-
return fs.existsSync(path.join(scenariosDir, n, 'meta.json'));
|
|
344
|
-
}).length;
|
|
345
|
-
} catch (_) {}
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
// 4. If the skill dir is now empty, remove it. Otherwise keep it.
|
|
349
|
-
try {
|
|
350
|
-
if (fs.existsSync(claudeSkillRoot)) {
|
|
351
|
-
var remaining = fs.readdirSync(claudeSkillRoot);
|
|
352
|
-
if (remaining.length === 0) {
|
|
353
|
-
fs.rmdirSync(claudeSkillRoot);
|
|
354
|
-
console.log('[uniner-cli] Removed empty ' + claudeSkillRoot);
|
|
355
|
-
didSomething = true;
|
|
356
|
-
} else if (scenariosKept > 0) {
|
|
357
|
-
console.log('[uniner-cli] Keeping ' + claudeSkillRoot +
|
|
358
|
-
' (' + scenariosKept + ' scenario(s) preserved)');
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
} catch (_) {}
|
|
362
|
-
|
|
363
|
-
console.log('');
|
|
364
|
-
if (scenariosKept > 0) {
|
|
365
|
-
console.log(' ' + scenariosKept + ' scenario(s) PRESERVED at:');
|
|
366
|
-
console.log(' ' + scenariosDir);
|
|
367
|
-
console.log(' (your accumulated knowledge — not deleted)');
|
|
368
|
-
console.log(' To delete them too:');
|
|
369
|
-
console.log(' rm -rf "' + scenariosDir + '" # bash');
|
|
370
|
-
console.log(' Remove-Item "' + scenariosDir + '" -Recurse -Force # PowerShell');
|
|
371
257
|
console.log('');
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
if (didSomething) {
|
|
375
258
|
console.log('Cleanup done.');
|
|
376
259
|
} else {
|
|
377
260
|
console.log('Nothing to clean — already removed.');
|
|
@@ -418,9 +301,6 @@ async function main() {
|
|
|
418
301
|
log(`Already installed at ${realBinaryPath(version)}; skipping download.`);
|
|
419
302
|
log('To force a clean reinstall:');
|
|
420
303
|
log(' npm uninstall -g uniner-cli && npm install -g uniner-cli');
|
|
421
|
-
// Still re-render the skill — it's tiny and lets users edit the template
|
|
422
|
-
// and re-run rebuild to refresh.
|
|
423
|
-
writeSkillMd(versionDir(version), version);
|
|
424
304
|
// Refresh the cleanup helper too — it's idempotent and ensures the
|
|
425
305
|
// script is always present after any postinstall run.
|
|
426
306
|
writeCleanupScript();
|
|
@@ -498,13 +378,6 @@ async function main() {
|
|
|
498
378
|
try { fs.rmdirSync(tmpDir); } catch (_) {}
|
|
499
379
|
log(' Removed temporary files.');
|
|
500
380
|
|
|
501
|
-
step('Installing Claude Code skill');
|
|
502
|
-
writeSkillMd(targetDir, version);
|
|
503
|
-
|
|
504
|
-
// Write the standalone cleanup helper to ~/.uniner-cli/cleanup.js.
|
|
505
|
-
// This survives `npm uninstall` (the npm package dir is gone but this
|
|
506
|
-
// file persists), giving users a reliable way to clean up residual
|
|
507
|
-
// files even when the `preuninstall` hook doesn't fire.
|
|
508
381
|
step('Writing uninstall cleanup helper');
|
|
509
382
|
writeCleanupScript();
|
|
510
383
|
|
|
@@ -514,7 +387,10 @@ async function main() {
|
|
|
514
387
|
console.log('==========================================');
|
|
515
388
|
console.log('');
|
|
516
389
|
console.log(` Location: ${targetDir}`);
|
|
517
|
-
console.log(
|
|
390
|
+
console.log('');
|
|
391
|
+
console.log(' Enable the Agent skill (Claude Code / Codex / pi):');
|
|
392
|
+
console.log(' cd <your-project-dir>');
|
|
393
|
+
console.log(' uniner-cli setup-for-agent');
|
|
518
394
|
console.log('');
|
|
519
395
|
console.log(' To uninstall:');
|
|
520
396
|
console.log(' uniner-cli uninstall # auto-detects global vs local install');
|
package/scripts/preuninstall.js
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
// preuninstall.js — npm lifecycle hook, runs automatically on
|
|
3
3
|
// `npm uninstall -g uniner-cli`.
|
|
4
4
|
//
|
|
5
|
-
// Cleans up the runtime tree (~/.uniner-cli
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
5
|
+
// Cleans up the runtime tree (~/.uniner-cli/ — binaries, docs, scenarios;
|
|
6
|
+
// full removal). Project-level .agents/skills/ and .claude/ bridges created
|
|
7
|
+
// by `uniner-cli setup-for-agent` live inside user projects and are not
|
|
8
|
+
// touched.
|
|
9
9
|
//
|
|
10
10
|
// IMPORTANT: This script MUST exit 0 in all cases, otherwise it would
|
|
11
11
|
// abort the user's `npm uninstall`. Errors are swallowed silently.
|
package/lib/SKILL.md.template
DELETED
|
@@ -1,404 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: uniner-cli
|
|
3
|
-
description: RPA 自动化 CLI 工具,提供浏览器自动化、Excel 操作、OCR 识别、魔像云流程开发与发布(流程发布/上传流程/魔微子)、加密/HTTP/邮件等通用工具能力。Use when user mentions RPA、自动化、抓取网页、抓数据、填表、读 Excel、写 Excel、识别验证码、OCR、流程发布、上传流程、业务流程、魔像云、魔微子、uniner-cli、爬虫、表单自动化.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# uniner-cli Skill
|
|
7
|
-
|
|
8
|
-
> 由 UninerCLI Installer v{{VERSION}} 自动安装到 `~/.claude/skills/uniner-cli/`
|
|
9
|
-
> 安装位置(绝对路径):`{{INSTALL_PATH}}`
|
|
10
|
-
|
|
11
|
-
## 🛑 任务结束硬性规则(最重要,先读这一节)
|
|
12
|
-
|
|
13
|
-
**每次用 uniner-cli 完成一个任务后,在你要说"任务完成"或写汇总表之前,必须先执行下面这段:**
|
|
14
|
-
|
|
15
|
-
如果这次任务**满足任一条件**:
|
|
16
|
-
|
|
17
|
-
- 调用了 ≥ 3 个 uniner-cli 命令
|
|
18
|
-
- 涉及 web / OCR 任何一个模块(desktop 暂不开放,不在其列)
|
|
19
|
-
- 涉及魔像云流程开发(init/validate/publish 多步链路,沉淀后可 0 token 重放)
|
|
20
|
-
- 用户提到过"以后还会跑" / "每天都要" / "做成自动化"
|
|
21
|
-
- 任务里踩过坑(XPath 不稳、参数名错、Vue 组件、超时调整等)
|
|
22
|
-
|
|
23
|
-
→ **必须主动问用户**:"这个流程涉及多步,要不要保存为可重放场景?以后 `uniner-cli skill run <name> --param ...` 就能 0 token 重放,可以放定时任务/RPA 平台里跑。要保存吗?"
|
|
24
|
-
|
|
25
|
-
不要等用户开口,**主动**问。这是最有价值的一步——你刚刚花了上千 token 调通的流程,沉淀下来下次就能秒跑。
|
|
26
|
-
|
|
27
|
-
如果用户说"好" / "可以" / "保存吧":按下面"场景沉淀(4 步流程)"章节执行。
|
|
28
|
-
|
|
29
|
-
如果用户说"不用":直接结束,不再追问。
|
|
30
|
-
|
|
31
|
-
**违反这条规则 = 任务未完成。** 不管你跑得多顺,没问就是没问完整。
|
|
32
|
-
|
|
33
|
-
---
|
|
34
|
-
|
|
35
|
-
## 何时使用本 Skill
|
|
36
|
-
|
|
37
|
-
用户提到以下任一关键词或任务,**优先调用 uniner-cli**:
|
|
38
|
-
|
|
39
|
-
- **网页类**:打开网页、抓取数据、点击/填表/登录、获取标题/表格、OCR 验证码
|
|
40
|
-
- **Excel 类**:创建/读写 Excel、单元格操作、批量数据导出
|
|
41
|
-
- **工具类**:MD5/AES/RSA 加密、HTTP 请求、收发邮件、文件/PDF/ZIP 操作
|
|
42
|
-
|
|
43
|
-
## 调用前提(已自动配置)
|
|
44
|
-
|
|
45
|
-
- ✅ `uniner-cli` 命令已加入 PATH(安装时自动配置,新终端立即生效)
|
|
46
|
-
- ✅ 环境变量 `UNINER_CLI_HOME = {{INSTALL_PATH}}`
|
|
47
|
-
- ✅ Web 模块自动通过 CDP 连接本机 Chrome / Edge —— **不要手动启动 Chrome**
|
|
48
|
-
- ⚠️ desktop 模块**暂不开放**(测试覆盖少);桌面/SAP 场景告知用户后改用 web 或 tool system execute
|
|
49
|
-
|
|
50
|
-
## 命令结构(一级 + 二级)
|
|
51
|
-
|
|
52
|
-
```
|
|
53
|
-
uniner-cli <模块> <子模块> <命令> [选项]
|
|
54
|
-
|
|
55
|
-
excel Excel 文件操作
|
|
56
|
-
├── file 创建 / 打开 / 关闭 / 保存 / 复制 / 删除 / info / list
|
|
57
|
-
├── sheet 工作表 创建 / 删除 / 重命名 / 复制 / info / list
|
|
58
|
-
├── data 读 / 写 / 区域读 / 区域写 / 查找 / 清除
|
|
59
|
-
└── style 字体 / 边框 / 填充 / 对齐 / 数字格式
|
|
60
|
-
|
|
61
|
-
web 浏览器自动化(Playwright + CDP)
|
|
62
|
-
├── browser open / close / reconnect
|
|
63
|
-
├── navigation goto / back / forward / refresh / wait
|
|
64
|
-
├── element click / input / get-text / get-value / select / check / hover / screenshot
|
|
65
|
-
├── iframe enter / exit / list
|
|
66
|
-
├── page list / switch / close
|
|
67
|
-
└── action screenshot / pdf / execute-js / get-cookie / set-cookie
|
|
68
|
-
|
|
69
|
-
tool 通用工具
|
|
70
|
-
├── list 创建 / append / remove / find / sort / unique
|
|
71
|
-
├── dict 创建 / get / set / remove / keys / values
|
|
72
|
-
├── time now / format / parse / calc / diff
|
|
73
|
-
├── string format / replace / split / join / trim / upper / lower
|
|
74
|
-
├── data json-parse / json-stringify / csv-parse / csv-write
|
|
75
|
-
├── file read / write / copy / move / delete / list / info / exists
|
|
76
|
-
├── system execute / kill-app / env / clipboard [Windows]
|
|
77
|
-
├── ui alert / confirm / prompt [Windows]
|
|
78
|
-
├── crypto md5 / sha256 / aes-encrypt / aes-decrypt / rsa-* / base64-*
|
|
79
|
-
├── math add / sub / mul / div / round / abs / min / max
|
|
80
|
-
├── pdf read / merge / split / extract-text
|
|
81
|
-
├── ocr ddddocr(验证码) / paddleocr(通用) / image-to-text
|
|
82
|
-
├── zip create / extract / list
|
|
83
|
-
├── ftp connect / upload / download / list / delete
|
|
84
|
-
├── email send / receive / list
|
|
85
|
-
└── http get / post / put / delete
|
|
86
|
-
|
|
87
|
-
desktop 桌面自动化 [暂不开放] —— 测试覆盖少,当前版本未开放。
|
|
88
|
-
AI 检测到任务需要桌面/SAP 操作时:停下告知用户"desktop 模块
|
|
89
|
-
暂不开放",改用 web(浏览器场景)或 tool system execute
|
|
90
|
-
(执行外部程序),不要尝试调用 desktop 命令。
|
|
91
|
-
|
|
92
|
-
cloud 魔像云操作(编辑器账号登录态;见"魔像云流程"章节)
|
|
93
|
-
├── login 登录(多组织时交互列出、**必须用户本人输入序号选定**)
|
|
94
|
-
├── logout 登出
|
|
95
|
-
├── status 查看登录状态(含当前组织)
|
|
96
|
-
└── flow 流程管理
|
|
97
|
-
├── init 生成流程脚手架(无参 = 当前目录五件套、流程名=目录名;--dir <路径> 指定目录自动创建;init <流程名> 已废弃)
|
|
98
|
-
├── validate 基础检查流程目录(publish 时自动执行,也可单独运行)
|
|
99
|
-
├── test 本地真跑流程(不强制,建议发布前手动跑)
|
|
100
|
-
├── list-local 列本地流程、发布状态与绑定组织
|
|
101
|
-
├── list 列云端流程(标注来源 CLI / 编辑器)
|
|
102
|
-
├── publish 发布流程目录(自动 validate + 版本号用户确认 + 外发确认)
|
|
103
|
-
├── download 下载云端流程回流当前目录(覆盖前确认,默认 N)
|
|
104
|
-
└── pack 打包当前目录流程为 .7z(无参数,输出上级目录)
|
|
105
|
-
|
|
106
|
-
config 配置管理
|
|
107
|
-
├── set 设置配置项(仅 editor.path:魔微子安装目录)
|
|
108
|
-
├── list 查看当前配置
|
|
109
|
-
└── clear 清除配置
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
## 全局选项(写在子命令前)
|
|
113
|
-
|
|
114
|
-
```bash
|
|
115
|
-
uniner-cli [全局选项] <模块> <子命令> <命令> [命令选项]
|
|
116
|
-
|
|
117
|
-
--timeout, -t <毫秒> 超时(默认 3000ms)
|
|
118
|
-
--exc <0|1> 异常处理:0=抛错退出,1=静默返回错误 JSON
|
|
119
|
-
--verbose, -v 详细日志
|
|
120
|
-
--quiet, -q 静默
|
|
121
|
-
--json/--plain 输出格式(默认 --json)
|
|
122
|
-
--config, -c <PATH> 配置文件路径
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
## 输出格式(始终是 JSON)
|
|
126
|
-
|
|
127
|
-
```json
|
|
128
|
-
// 成功
|
|
129
|
-
{"success": true, "command": "web browser open", "data": {"session_id": "abc123"}, "timestamp": "..."}
|
|
130
|
-
|
|
131
|
-
// 失败
|
|
132
|
-
{"success": false, "error": "元素未找到: xpath=...", "error_type": "ElementNotFoundError"}
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
调用方应 `json.loads(stdout)` 解析,**不要 grep/regex**。
|
|
136
|
-
|
|
137
|
-
## 场景 → 命令映射(高频)
|
|
138
|
-
|
|
139
|
-
| 用户意图 | 命令模板 |
|
|
140
|
-
|---|---|
|
|
141
|
-
| 打开网页 | `web browser open --url <URL>` |
|
|
142
|
-
| 等待元素出现 | `web navigation wait --type element --xpath <X>` |
|
|
143
|
-
| 点击 | `web element click --xpath <X>` |
|
|
144
|
-
| 输入文本 | `web element input --xpath <X> --value <V>` |
|
|
145
|
-
| 获取文本 | `web element get-text --xpath <X>` |
|
|
146
|
-
| 获取表格 / 复杂提取 | `web action execute-js --code "..."` |
|
|
147
|
-
| 元素截图 | `web element screenshot --xpath <X> --output <PATH>` |
|
|
148
|
-
| 进 iframe | `web iframe enter --xpath <X>`(或 XPath JSON 写法) |
|
|
149
|
-
| 创建 Excel | `excel file create --file <PATH>` |
|
|
150
|
-
| 读单元格 | `excel data read --file <PATH> --cell A1` |
|
|
151
|
-
| 写单元格 | `excel data write --file <PATH> --cell A1 --value <V>` |
|
|
152
|
-
| 区域写入 | `excel data write-range --file <PATH> --cell A1 --values <2D-JSON>` |
|
|
153
|
-
| OCR 验证码 | `tool ocr ddddocr --img <PATH>` |
|
|
154
|
-
| OCR 通用 | `tool ocr paddleocr --img <PATH>` |
|
|
155
|
-
| MD5 / SHA256 | `tool crypto md5 --text <S>` |
|
|
156
|
-
| HTTP GET | `tool http get --url <URL>` |
|
|
157
|
-
| 发送邮件 | `tool email send --to <addr> --subject <S> --body <B>` |
|
|
158
|
-
| 结束进程 | `tool system kill-app --name chrome.exe` |
|
|
159
|
-
| SAP / 桌面操作 | **不可用**:desktop 暂不开放,停下告知用户并改用 web / tool system execute | "登录 SAP 系统" |
|
|
160
|
-
| 流程发布 / 上传流程 | **先 Read** [{{INSTALL_PATH}}/skills/cloud-flow-publish.md]({{INSTALL_PATH}}/skills/cloud-flow-publish.md),再走 `cloud login → cloud flow init → validate → publish`;加输入参数看该文档「添加输入参数的标准动作」(字段表 + 可复制示例,validate V6/V7 静态拦错) | "把这个流程发到魔像云" |
|
|
161
|
-
| 下载云端流程 | `cloud flow download [流程名]`(覆盖前确认默认 N;AI 不得代答) | "把云端流程拉下来改" |
|
|
162
|
-
|
|
163
|
-
## 命令选择优先级
|
|
164
|
-
|
|
165
|
-
1. **网页操作只用 `web`**(即使是桌面里嵌的 web)
|
|
166
|
-
2. **`web action execute-js` 是万能 fallback**:Vue/React 组件、动态元素、复杂表格提取,普通 element 命令做不到时直接 JS
|
|
167
|
-
3. **iframe 嵌套**用 XPath JSON 格式:`{"ElementName":"...","XPaths":[["iframe_xpath","element_xpath"],["backup_xpath"]]}`
|
|
168
|
-
4. **session 自动管理**:第一次 `web browser open` 后,后续 web 命令默认用最新 session,可省 `--session`;报 `Session not found`(如 CLI 进程退出过)时先 `web browser reconnect` 恢复,不要重开浏览器
|
|
169
|
-
5. **业务产物写当前目录**:Excel/JSON/中间文件等业务产物一律用**相对路径**写当前工作目录(或流程目录),**禁止写 `{{INSTALL_PATH}}/workdir/`**——workdir 是 CLI 安装目录里的临时工作区(路径含版本号,升级即失效),内容也不随流程发布进包
|
|
170
|
-
|
|
171
|
-
## 关键注意事项(6 条 TOC,详情看链接)
|
|
172
|
-
|
|
173
|
-
1. **Chrome CDP 自动管理** — 别手动 kill Chrome;客户机必须装 Chrome 或 Edge
|
|
174
|
-
2. **session 持久化** — `$TEMP/.uniner-cli/sessions.json`(Windows: `%TEMP%\.uniner-cli\sessions.json`);CLI 退出后用 `web browser reconnect` 恢复
|
|
175
|
-
3. **Vue/Element UI 组件** — 日期选择器、级联下拉等,用 `execute-js` 直接操作组件模型,不要硬 input
|
|
176
|
-
4. **desktop 暂不开放** — 测试覆盖少,当前版本未开放;任何 desktop 子命令都提示暂不开放,改用 `web action execute-js` 或 `tool system execute`
|
|
177
|
-
5. **Linux 平台** — Playwright 默认 HTTPS-only,访问 HTTP 站点需改 wrapper 加 `--disable-features=HttpsOnlyMode`,或用 Firefox `--browser-type 1`
|
|
178
|
-
6. **Windows GBK 编码** — CLI 内部已强制 UTF-8 输出;调用方读 stdout 也要 UTF-8
|
|
179
|
-
|
|
180
|
-
## 详细文档(按需 Read)
|
|
181
|
-
|
|
182
|
-
| 主题 | 路径 |
|
|
183
|
-
|---|---|
|
|
184
|
-
| **魔像云流程开发与发布工作流**(login→init→validate→publish,含版本号确认 + 添加入参标准动作) | [{{INSTALL_PATH}}/skills/cloud-flow-publish.md]({{INSTALL_PATH}}/skills/cloud-flow-publish.md) |
|
|
185
|
-
| 命令树全集 + 三级参数 | [{{INSTALL_PATH}}/skills/cli/SKILL.md]({{INSTALL_PATH}}/skills/cli/SKILL.md) |
|
|
186
|
-
| 完整命令结构(120+ 命令) | [{{INSTALL_PATH}}/skills/cli/command-tree.md]({{INSTALL_PATH}}/skills/cli/command-tree.md) |
|
|
187
|
-
| execute-js 使用指南 | [{{INSTALL_PATH}}/skills/cli/execute-js-guide.md]({{INSTALL_PATH}}/skills/cli/execute-js-guide.md) |
|
|
188
|
-
| 典型场景示例 | [{{INSTALL_PATH}}/skills/cli/examples.md]({{INSTALL_PATH}}/skills/cli/examples.md) |
|
|
189
|
-
| Windows 配置指南 | [{{INSTALL_PATH}}/skills/windows-setup-guide.md]({{INSTALL_PATH}}/skills/windows-setup-guide.md) |
|
|
190
|
-
| macOS 配置指南 | [{{INSTALL_PATH}}/skills/macos-setup-guide.md]({{INSTALL_PATH}}/skills/macos-setup-guide.md) |
|
|
191
|
-
| Linux 配置指南 | [{{INSTALL_PATH}}/skills/linux-setup-guide.md]({{INSTALL_PATH}}/skills/linux-setup-guide.md) |
|
|
192
|
-
| 实战案例(TMS 抓单) | [{{INSTALL_PATH}}/skills/tms-workflow-test-report.md]({{INSTALL_PATH}}/skills/tms-workflow-test-report.md) |
|
|
193
|
-
| Helper 脚本(Chrome 启动 / Vue 日期 / 表格提取) | [{{INSTALL_PATH}}/skills/cli/helpers/]({{INSTALL_PATH}}/skills/cli/helpers/) |
|
|
194
|
-
| 用户手册 | [{{INSTALL_PATH}}/docs/]({{INSTALL_PATH}}/docs/) |
|
|
195
|
-
| 工作目录(CLI 内部临时缓存:截图/下载;**业务产物不要写这里**,写当前目录) | [{{INSTALL_PATH}}/workdir/]({{INSTALL_PATH}}/workdir/) |
|
|
196
|
-
|
|
197
|
-
## 触发词路由
|
|
198
|
-
|
|
199
|
-
用户提到【流程发布】【上传流程】【业务流程】【魔像云】【魔微子】等关键词时,
|
|
200
|
-
**先 Read** [{{INSTALL_PATH}}/skills/cloud-flow-publish.md]({{INSTALL_PATH}}/skills/cloud-flow-publish.md)
|
|
201
|
-
再操作——流程生产走 CLI 命令链(login 选组织 → init → validate → (建议手动
|
|
202
|
-
test)→ publish,publish 时提示云端最新版本号、版本号由用户确认),有独立的
|
|
203
|
-
环境前提与 AI 行为红线。
|
|
204
|
-
|
|
205
|
-
## 典型完整流程(参考)
|
|
206
|
-
|
|
207
|
-
**网页 → OCR → 提取 → Excel**:
|
|
208
|
-
|
|
209
|
-
```bash
|
|
210
|
-
# 1. 打开浏览器
|
|
211
|
-
uniner-cli web browser open --url "https://example.com/login"
|
|
212
|
-
|
|
213
|
-
# 2. 输账号密码 + 截验证码 + OCR(截图 temp 文件放当前目录,勿写 workdir)
|
|
214
|
-
uniner-cli web element input --xpath "//input[@id='user']" --value "u01"
|
|
215
|
-
uniner-cli web element input --xpath "//input[@id='pwd']" --value "p01"
|
|
216
|
-
uniner-cli web element screenshot --xpath "//img[@id='captcha']" --output "cap.png"
|
|
217
|
-
uniner-cli tool ocr ddddocr --img "cap.png"
|
|
218
|
-
# 从返回 JSON 的 data.result 取识别文本
|
|
219
|
-
|
|
220
|
-
# 3. 提交登录
|
|
221
|
-
uniner-cli web element input --xpath "//input[@id='captcha']" --value "<识别结果>"
|
|
222
|
-
uniner-cli web element click --xpath "//button[@type='submit']"
|
|
223
|
-
|
|
224
|
-
# 4. 等表格 + 提取
|
|
225
|
-
uniner-cli web navigation wait --type element --xpath "//table"
|
|
226
|
-
uniner-cli web action execute-js --code "return Array.from(document.querySelectorAll('table tr')).map(r => Array.from(r.cells).map(c => c.textContent.trim()))"
|
|
227
|
-
|
|
228
|
-
# 5. 写 Excel(产物写当前目录)
|
|
229
|
-
uniner-cli excel file create --file "out.xlsx"
|
|
230
|
-
uniner-cli excel data write-range --file "out.xlsx" --cell A1 --values "<上一步的 JSON 二维数组>"
|
|
231
|
-
|
|
232
|
-
# 6. 关浏览器
|
|
233
|
-
uniner-cli web browser close
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
## 场景沉淀(4 步流程)
|
|
237
|
-
|
|
238
|
-
> 这是顶部"任务结束硬性规则"提到的"4 步流程"。用户同意保存场景后,按这里走。
|
|
239
|
-
|
|
240
|
-
每个场景一个目录,三件套:`meta.json` + `steps.json` + `SKILL.md`,存到:
|
|
241
|
-
|
|
242
|
-
```
|
|
243
|
-
~/.claude/skills/uniner-cli/scenarios/<kebab-case-name>/
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
(Windows: `%USERPROFILE%\.claude\skills\uniner-cli\scenarios\<kebab-case-name>\`)
|
|
247
|
-
|
|
248
|
-
**4 步标准流程**:
|
|
249
|
-
|
|
250
|
-
```bash
|
|
251
|
-
# Step 1: 生成骨架(自动创建目录和模板文件)
|
|
252
|
-
uniner-cli skill init <name>
|
|
253
|
-
|
|
254
|
-
# Step 2: 用 Write 工具改 meta.json / steps.json / SKILL.md(见下方 schema)
|
|
255
|
-
|
|
256
|
-
# Step 3: 校验
|
|
257
|
-
uniner-cli skill validate <name>
|
|
258
|
-
|
|
259
|
-
# Step 4: 试跑一次
|
|
260
|
-
uniner-cli skill run <name> --param ...
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
### meta.json schema
|
|
264
|
-
|
|
265
|
-
```json
|
|
266
|
-
{
|
|
267
|
-
"name": "<kebab-case-name>",
|
|
268
|
-
"version": "1.0",
|
|
269
|
-
"description": "一句话描述(会被用来匹配触发关键词)",
|
|
270
|
-
"params": {
|
|
271
|
-
"URL": { "type": "string", "required": true, "description": "登录页地址" },
|
|
272
|
-
"USERNAME": { "type": "string", "required": true, "description": "账号" },
|
|
273
|
-
"PASSWORD": { "type": "string", "required": true, "sensitive": true, "description": "密码" },
|
|
274
|
-
"DATE_FROM": { "type": "string", "required": false, "default": "today", "pattern": "^\\d{4}-\\d{2}-\\d{2}$" }
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
```
|
|
278
|
-
|
|
279
|
-
### steps.json schema
|
|
280
|
-
|
|
281
|
-
```json
|
|
282
|
-
{
|
|
283
|
-
"version": "1.0",
|
|
284
|
-
"name": "<同 meta.name>",
|
|
285
|
-
"steps": [
|
|
286
|
-
{
|
|
287
|
-
"id": "open-browser",
|
|
288
|
-
"cmd": "web browser open",
|
|
289
|
-
"args": { "url": "${URL}" },
|
|
290
|
-
"timeout_ms": 10000
|
|
291
|
-
},
|
|
292
|
-
{
|
|
293
|
-
"id": "ocr-captcha",
|
|
294
|
-
"cmd": "tool ocr ddddocr",
|
|
295
|
-
"args": { "img": "out/captcha.png" },
|
|
296
|
-
"store": "ocrResult",
|
|
297
|
-
"comment": "整个 stdout JSON 存到 ocrResult,下面用 ${ocrResult.data.result} 取"
|
|
298
|
-
},
|
|
299
|
-
{
|
|
300
|
-
"id": "input-captcha",
|
|
301
|
-
"cmd": "web element input",
|
|
302
|
-
"args": { "xpath": "//input[@placeholder='验证码']", "value": "${ocrResult.data.result}" }
|
|
303
|
-
},
|
|
304
|
-
{
|
|
305
|
-
"id": "close-browser",
|
|
306
|
-
"cmd": "web browser close",
|
|
307
|
-
"always_run": true,
|
|
308
|
-
"comment": "无论前面成功与否都跑(清理用)"
|
|
309
|
-
}
|
|
310
|
-
]
|
|
311
|
-
}
|
|
312
|
-
```
|
|
313
|
-
|
|
314
|
-
### 每个 step 支持的字段
|
|
315
|
-
|
|
316
|
-
| 字段 | 必需 | 说明 |
|
|
317
|
-
|---|---|---|
|
|
318
|
-
| `id` | ✅ | 步骤 ID(唯一) |
|
|
319
|
-
| `cmd` | ✅ | uniner-cli 子命令,**不带 `uniner-cli` 前缀**,如 `web element input` |
|
|
320
|
-
| `args` | ✅ | dict 形式参数,对应 CLI 的 `--key value` |
|
|
321
|
-
| `store` | ❌ | 把 stdout JSON 整个存到变量名 |
|
|
322
|
-
| `if` | ❌ | 条件表达式(见下) |
|
|
323
|
-
| `on_fail` | ❌ | `abort`(默认)/ `continue` / `goto:<step_id>` |
|
|
324
|
-
| `retry` | ❌ | 失败重试次数(不含首次) |
|
|
325
|
-
| `retry_delay_ms` | ❌ | 重试间隔(默认 1000) |
|
|
326
|
-
| `timeout_ms` | ❌ | 单步超时(覆盖全局) |
|
|
327
|
-
| `always_run` | ❌ | 即使前面失败也跑(清理用) |
|
|
328
|
-
| `comment` | ❌ | 给人/AI 看的注释 |
|
|
329
|
-
|
|
330
|
-
### 变量替换语法
|
|
331
|
-
|
|
332
|
-
| 写法 | 含义 |
|
|
333
|
-
|---|---|
|
|
334
|
-
| `${URL}` | 用户传入的 `--param URL=xxx` |
|
|
335
|
-
| `${ocrResult.data.result}` | 上一步 store 的 dict 字段(点号路径) |
|
|
336
|
-
| `${tableData.0.1}` | list 索引(0-based) |
|
|
337
|
-
| `${env:USERPROFILE}` | 环境变量 |
|
|
338
|
-
|
|
339
|
-
### 条件表达式语法(白名单)
|
|
340
|
-
|
|
341
|
-
| 写法 | 含义 |
|
|
342
|
-
|---|---|
|
|
343
|
-
| `${var}` | 真值判断 |
|
|
344
|
-
| `!${var}` | 取反 |
|
|
345
|
-
| `${var} == 'yes'` | 字符串/数值相等 |
|
|
346
|
-
| `${var} != 'no'` | 不等 |
|
|
347
|
-
| `${var} > 5` | 数值比较(>=, <=, < 同理) |
|
|
348
|
-
| `${var}.includes('login')` | 包含 |
|
|
349
|
-
| `${var}.startsWith('https')` / `endsWith('.xlsx')` | 前/后缀 |
|
|
350
|
-
| `!${var}.includes('admin')` | 取反组合 |
|
|
351
|
-
|
|
352
|
-
**故意不支持** `&&` / `||` / `()`。要复合逻辑用多步 + `if` + `on_fail` 拆开。
|
|
353
|
-
|
|
354
|
-
### 失败时的客户体验
|
|
355
|
-
|
|
356
|
-
```
|
|
357
|
-
$ uniner-cli skill run tms-export --param URL=...
|
|
358
|
-
{"success": false, "failed_step": "click-search", ...}
|
|
359
|
-
|
|
360
|
-
────────── How to debug ──────────
|
|
361
|
-
Step 'click-search' failed: timeout
|
|
362
|
-
Open Claude Code, paste this:
|
|
363
|
-
|
|
364
|
-
I ran 'uniner-cli skill run' and step 'click-search' failed.
|
|
365
|
-
Error: timeout
|
|
366
|
-
Please look at the scenario at:
|
|
367
|
-
~/.claude/skills/uniner-cli/scenarios/tms-export
|
|
368
|
-
Especially steps.json. Help me figure out why and suggest a fix.
|
|
369
|
-
──────────────────────────────────
|
|
370
|
-
```
|
|
371
|
-
|
|
372
|
-
客户复制这段给你 → 你 Read steps.json 调试 → 改完 `Write` 回去 → 让客户重跑。
|
|
373
|
-
|
|
374
|
-
### 给 Claude 的对话范式(用户同意保存后)
|
|
375
|
-
|
|
376
|
-
按这个顺序,**不要跳步**:
|
|
377
|
-
|
|
378
|
-
1. 想一个 kebab-case 名字(如 `tms-export-waybills` / `sap-monthly-report`)
|
|
379
|
-
2. Bash 跑 `uniner-cli skill init <name>`(生成骨架)
|
|
380
|
-
3. Write 写**真正的**:
|
|
381
|
-
- `meta.json`:含动态参数 schema(URL / USERNAME / PASSWORD / DATE_FROM 等)
|
|
382
|
-
- `steps.json`:把刚才跑过的命令序列**完整**写进去,含已知坑的避坑配置(retry / on_fail / always_run)
|
|
383
|
-
- `SKILL.md`:经验文档(已知坑、用法说明)
|
|
384
|
-
4. Bash 跑 `uniner-cli skill validate <name>` 确认格式无误
|
|
385
|
-
5. 让用户 `uniner-cli skill run <name> --param ...` 自己跑一次确认 0 token 重放可行
|
|
386
|
-
|
|
387
|
-
**关键**:steps.json 不是命令清单的注释——是**真正可执行的工作流**。要把 `${URL}` `${USERNAME}` `${ocrResult.data.result}` 这种变量绑好,让 `skill run` 真能跑通。
|
|
388
|
-
|
|
389
|
-
### 已沉淀的场景库
|
|
390
|
-
|
|
391
|
-
```bash
|
|
392
|
-
uniner-cli skill list # 看所有
|
|
393
|
-
uniner-cli skill show <name> # 看某个的元数据 + 步骤摘要
|
|
394
|
-
uniner-cli skill show <name> --steps # 看完整步骤
|
|
395
|
-
uniner-cli skill where # 看场景库根目录(可手动浏览)
|
|
396
|
-
uniner-cli skill delete <name> --yes # 删除(默认 dry-run)
|
|
397
|
-
```
|
|
398
|
-
|
|
399
|
-
## 不确定时
|
|
400
|
-
|
|
401
|
-
- `uniner-cli --help` 看一级
|
|
402
|
-
- `uniner-cli web --help` 看二级
|
|
403
|
-
- `uniner-cli web element click --help` 看具体参数
|
|
404
|
-
- 仍不确定 → Read [{{INSTALL_PATH}}/skills/cli/SKILL.md]({{INSTALL_PATH}}/skills/cli/SKILL.md)
|