uniner-cli 1.0.2 → 1.0.3
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 +16 -6
- package/package.json +47 -46
- package/scripts/postinstall.js +75 -34
package/README.md
CHANGED
|
@@ -21,9 +21,13 @@ npm install -g uniner-cli
|
|
|
21
21
|
安装过程:
|
|
22
22
|
|
|
23
23
|
1. npm 装本壳包(约 50KB)
|
|
24
|
-
2. `postinstall`
|
|
25
|
-
|
|
26
|
-
|
|
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`
|
|
27
31
|
|
|
28
32
|
完成后任意终端:
|
|
29
33
|
|
|
@@ -35,12 +39,16 @@ uniner-cli --help
|
|
|
35
39
|
|
|
36
40
|
| 项 | 要求 |
|
|
37
41
|
|---|---|
|
|
38
|
-
| 操作系统 |
|
|
42
|
+
| 操作系统 | Windows 10/11 x64;Ubuntu 24.04+ x64 |
|
|
39
43
|
| Node.js | >= 14 |
|
|
40
44
|
| 磁盘空间 | ≥ 1.5 GB |
|
|
41
45
|
| 网络 | 能访问 `static.uniner.com` |
|
|
42
46
|
|
|
43
|
-
|
|
47
|
+
Linux 说明:
|
|
48
|
+
- Linux 包当前基于 Ubuntu 24.04 / glibc 2.39 构建和验证。
|
|
49
|
+
- 已验证模块:`tool` / `excel` / `skill` / `ddddocr OCR`。
|
|
50
|
+
- `web` 模块需要目标机安装 Chrome/Chromium。
|
|
51
|
+
- `desktop` 模块不支持 Linux。
|
|
44
52
|
|
|
45
53
|
## 使用示例
|
|
46
54
|
|
|
@@ -61,7 +69,9 @@ uniner-cli excel data write --file out.xlsx --cell B2 --value "hello"
|
|
|
61
69
|
uniner-cli tool ocr ddddocr --img captcha.png
|
|
62
70
|
```
|
|
63
71
|
|
|
64
|
-
完整命令树(一级 + 二级 +
|
|
72
|
+
完整命令树(一级 + 二级 + 三级参数)见安装后:
|
|
73
|
+
- Windows: `%USERPROFILE%\.uniner-cli\<version>\skills\cli\SKILL.md`
|
|
74
|
+
- Linux: `~/.uniner-cli/<version>/skills/cli/SKILL.md`
|
|
65
75
|
|
|
66
76
|
## 升级
|
|
67
77
|
|
package/package.json
CHANGED
|
@@ -1,46 +1,47 @@
|
|
|
1
|
-
{
|
|
2
|
-
|
|
3
|
-
|
|
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
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "uniner-cli",
|
|
3
|
+
"version": "1.0.3",
|
|
4
|
+
"description": "uniner-cli — RPA automation CLI: browser automation (Playwright/CDP), Excel, OCR, desktop/SAP, crypto/HTTP/email tools. Auto-installs a Claude Code skill on install.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"rpa",
|
|
7
|
+
"automation",
|
|
8
|
+
"uniner",
|
|
9
|
+
"playwright",
|
|
10
|
+
"excel",
|
|
11
|
+
"ocr",
|
|
12
|
+
"claude-code",
|
|
13
|
+
"ai-agent"
|
|
14
|
+
],
|
|
15
|
+
"homepage": "https://www.uniner.com/",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://www.uniner.com/",
|
|
18
|
+
"email": "support@uniner.com"
|
|
19
|
+
},
|
|
20
|
+
"license": "Proprietary",
|
|
21
|
+
"author": "Uniner Tech",
|
|
22
|
+
"main": "lib/paths.js",
|
|
23
|
+
"bin": {
|
|
24
|
+
"uniner-cli": "bin/uniner-cli.js",
|
|
25
|
+
"uniner-cli-uninstall": "bin/uniner-cli-uninstall.js"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"postinstall": "node scripts/postinstall.js"
|
|
29
|
+
},
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=14"
|
|
32
|
+
},
|
|
33
|
+
"os": [
|
|
34
|
+
"win32",
|
|
35
|
+
"linux"
|
|
36
|
+
],
|
|
37
|
+
"cpu": [
|
|
38
|
+
"x64"
|
|
39
|
+
],
|
|
40
|
+
"files": [
|
|
41
|
+
"bin/",
|
|
42
|
+
"lib/",
|
|
43
|
+
"scripts/",
|
|
44
|
+
"README.md",
|
|
45
|
+
"LICENSE"
|
|
46
|
+
]
|
|
47
|
+
}
|
package/scripts/postinstall.js
CHANGED
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
//
|
|
4
4
|
// What it does:
|
|
5
5
|
// 1. If the binary is already in place at ~/.uniner-cli/<version>/, exit (idempotent).
|
|
6
|
-
// 2. Fetch latest.json from the OSS metadata URL to get
|
|
7
|
-
// 3. Stream-download the matching portable
|
|
6
|
+
// 2. Fetch latest.json from the OSS metadata URL to get platform URL + sha256.
|
|
7
|
+
// 3. Stream-download the matching portable archive into temp dir.
|
|
8
|
+
// - Windows: .zip
|
|
9
|
+
// - Linux: .tar.gz
|
|
8
10
|
// - Live progress every 2 seconds.
|
|
9
11
|
// - Up to 3 retries with exponential backoff.
|
|
10
12
|
// 4. Verify SHA-256.
|
|
11
|
-
// 5. Extract via PowerShell
|
|
13
|
+
// 5. Extract via platform-native tools (PowerShell Expand-Archive / tar).
|
|
12
14
|
// 6. Render SKILL.md.template with {{INSTALL_PATH}} / {{VERSION}} substituted
|
|
13
15
|
// and write to ~/.claude/skills/uniner-cli/SKILL.md.
|
|
14
16
|
// 7. Print "ready" and how to validate.
|
|
@@ -213,6 +215,58 @@ function extractZipPS(zipFile, destDir) {
|
|
|
213
215
|
}
|
|
214
216
|
}
|
|
215
217
|
|
|
218
|
+
function extractTarGz(tarFile, destDir) {
|
|
219
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
220
|
+
|
|
221
|
+
const result = spawnSync('tar', ['-xzf', tarFile, '-C', destDir], {
|
|
222
|
+
stdio: 'inherit',
|
|
223
|
+
});
|
|
224
|
+
if (result.error) {
|
|
225
|
+
fail(`Could not invoke tar to extract: ${result.error.message}`);
|
|
226
|
+
}
|
|
227
|
+
if (result.status !== 0) {
|
|
228
|
+
fail(`tar exited with status ${result.status}`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function getPlatformInfo(meta) {
|
|
233
|
+
if (process.arch !== 'x64') {
|
|
234
|
+
fail(`Unsupported architecture: ${process.arch}. Only x64 is supported.`);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (process.platform === 'win32') {
|
|
238
|
+
const info = meta && meta.windows && meta.windows.x64;
|
|
239
|
+
if (!info || !info.portable_url || !info.portable_sha256) {
|
|
240
|
+
fail('latest.json is missing windows.x64.portable_url / portable_sha256.');
|
|
241
|
+
}
|
|
242
|
+
return {
|
|
243
|
+
platformKey: 'windows.x64',
|
|
244
|
+
archiveUrl: info.portable_url,
|
|
245
|
+
archiveSha256: info.portable_sha256,
|
|
246
|
+
archiveSize: info.portable_size,
|
|
247
|
+
extract: extractZipPS,
|
|
248
|
+
expectedInnerBinary: path.join('bin', 'uniner-cli', 'uniner-cli.exe'),
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (process.platform === 'linux') {
|
|
253
|
+
const info = meta && meta.linux && meta.linux.x64;
|
|
254
|
+
if (!info || !info.portable_url || !info.portable_sha256) {
|
|
255
|
+
fail('latest.json is missing linux.x64.portable_url / portable_sha256.');
|
|
256
|
+
}
|
|
257
|
+
return {
|
|
258
|
+
platformKey: 'linux.x64',
|
|
259
|
+
archiveUrl: info.portable_url,
|
|
260
|
+
archiveSha256: info.portable_sha256,
|
|
261
|
+
archiveSize: info.portable_size,
|
|
262
|
+
extract: extractTarGz,
|
|
263
|
+
expectedInnerBinary: path.join('bin', 'uniner-cli', 'uniner-cli'),
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
fail(`Unsupported platform: ${process.platform}. Supported: win32-x64, linux-x64.`);
|
|
268
|
+
}
|
|
269
|
+
|
|
216
270
|
// ----------------------------------------------------------------------
|
|
217
271
|
// SKILL.md template render
|
|
218
272
|
// ----------------------------------------------------------------------
|
|
@@ -265,16 +319,6 @@ async function main() {
|
|
|
265
319
|
console.log(' uniner-cli postinstall');
|
|
266
320
|
console.log('==========================================');
|
|
267
321
|
|
|
268
|
-
// We only build a Windows binary right now. Refuse other platforms loudly
|
|
269
|
-
// so users don't end up with a broken install.
|
|
270
|
-
if (process.platform !== 'win32') {
|
|
271
|
-
fail('Only Windows x64 is supported in this release. ' +
|
|
272
|
-
'Linux/macOS support is on the roadmap.');
|
|
273
|
-
}
|
|
274
|
-
if (process.arch !== 'x64') {
|
|
275
|
-
fail(`Unsupported architecture: ${process.arch}. Only x64 is supported.`);
|
|
276
|
-
}
|
|
277
|
-
|
|
278
322
|
const version = pkg.version;
|
|
279
323
|
|
|
280
324
|
// Skip when a working binary is already there. This makes
|
|
@@ -293,34 +337,30 @@ async function main() {
|
|
|
293
337
|
log(` URL: ${METADATA_URL}`);
|
|
294
338
|
const meta = await getJSON(METADATA_URL);
|
|
295
339
|
|
|
296
|
-
const
|
|
297
|
-
if (!win || !win.portable_url || !win.portable_sha256) {
|
|
298
|
-
fail('latest.json is missing windows.x64.portable_url / portable_sha256. ' +
|
|
299
|
-
'The OSS metadata may be from an older release.');
|
|
300
|
-
}
|
|
340
|
+
const platform = getPlatformInfo(meta);
|
|
301
341
|
log(` Latest version: ${meta.version}`);
|
|
302
342
|
log(` Package version (this npm release): ${version}`);
|
|
343
|
+
log(` Platform: ${platform.platformKey}`);
|
|
303
344
|
|
|
304
345
|
if (meta.version !== version) {
|
|
305
346
|
log(` [INFO] Server has ${meta.version} but this npm package is pinned ` +
|
|
306
|
-
`to ${version}. Will fetch the package
|
|
307
|
-
`from the OSS path that the URL implies.`);
|
|
347
|
+
`to ${version}. Will fetch the package artifacts from latest.json.`);
|
|
308
348
|
}
|
|
309
349
|
|
|
310
350
|
step('Downloading portable archive');
|
|
311
351
|
const tmpDir = path.join(os.tmpdir(), 'uniner-cli-npm-install');
|
|
312
352
|
fs.mkdirSync(tmpDir, { recursive: true });
|
|
313
|
-
const
|
|
353
|
+
const archivePath = path.join(tmpDir, path.basename(platform.archiveUrl));
|
|
314
354
|
|
|
315
|
-
log(` From: ${
|
|
316
|
-
log(` To: ${
|
|
317
|
-
await downloadWithRetry(
|
|
355
|
+
log(` From: ${platform.archiveUrl}`);
|
|
356
|
+
log(` To: ${archivePath}`);
|
|
357
|
+
await downloadWithRetry(platform.archiveUrl, archivePath, platform.archiveSize);
|
|
318
358
|
|
|
319
359
|
step('Verifying SHA-256');
|
|
320
|
-
const actual = await sha256OfFile(
|
|
321
|
-
if (actual.toLowerCase() !==
|
|
322
|
-
try { fs.unlinkSync(
|
|
323
|
-
fail(`SHA-256 mismatch.\n expected: ${
|
|
360
|
+
const actual = await sha256OfFile(archivePath);
|
|
361
|
+
if (actual.toLowerCase() !== platform.archiveSha256.toLowerCase()) {
|
|
362
|
+
try { fs.unlinkSync(archivePath); } catch (_) {}
|
|
363
|
+
fail(`SHA-256 mismatch.\n expected: ${platform.archiveSha256}\n got: ${actual}`);
|
|
324
364
|
}
|
|
325
365
|
log(' Checksum verified.');
|
|
326
366
|
|
|
@@ -332,16 +372,17 @@ async function main() {
|
|
|
332
372
|
log(' Removing previous half-extracted tree...');
|
|
333
373
|
fs.rmSync(targetDir, { recursive: true, force: true });
|
|
334
374
|
}
|
|
335
|
-
|
|
375
|
+
platform.extract(archivePath, targetDir);
|
|
336
376
|
|
|
337
|
-
//
|
|
338
|
-
//
|
|
339
|
-
//
|
|
377
|
+
// Portable archives put everything inside an extra top-level folder
|
|
378
|
+
// (`uniner-cli-portable` on Windows, `uniner-cli-linux-portable` on Linux).
|
|
379
|
+
// If we see exactly one subdir and the expected binary is one level deeper,
|
|
380
|
+
// flatten so realBinaryPath() resolves cleanly.
|
|
340
381
|
const entries = fs.readdirSync(targetDir);
|
|
341
382
|
if (entries.length === 1) {
|
|
342
383
|
const inner = path.join(targetDir, entries[0]);
|
|
343
384
|
if (fs.statSync(inner).isDirectory() &&
|
|
344
|
-
fs.existsSync(path.join(inner,
|
|
385
|
+
fs.existsSync(path.join(inner, platform.expectedInnerBinary))) {
|
|
345
386
|
log(' Flattening extracted tree...');
|
|
346
387
|
for (const item of fs.readdirSync(inner)) {
|
|
347
388
|
fs.renameSync(path.join(inner, item), path.join(targetDir, item));
|
|
@@ -356,7 +397,7 @@ async function main() {
|
|
|
356
397
|
}
|
|
357
398
|
|
|
358
399
|
step('Cleaning up');
|
|
359
|
-
try { fs.unlinkSync(
|
|
400
|
+
try { fs.unlinkSync(archivePath); } catch (_) {}
|
|
360
401
|
try { fs.rmdirSync(tmpDir); } catch (_) {}
|
|
361
402
|
log(' Removed temporary files.');
|
|
362
403
|
|