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 CHANGED
@@ -21,9 +21,13 @@ npm install -g uniner-cli
21
21
  安装过程:
22
22
 
23
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`
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
- | 操作系统 | 64 位 Windows 10 / 11 |
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
- > Linux / macOS 支持在路上。
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
- 完整命令树(一级 + 二级 + 三级参数)见安装后 `%USERPROFILE%\.uniner-cli\<version>\skills\cli\SKILL.md`。
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
- "name": "uniner-cli",
3
- "version": "1.0.2",
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": "\u003e=14"
32
- },
33
- "os": [
34
- "win32"
35
- ],
36
- "cpu": [
37
- "x64"
38
- ],
39
- "files": [
40
- "bin/",
41
- "lib/",
42
- "scripts/",
43
- "README.md",
44
- "LICENSE"
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
+ }
@@ -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 portable_url + sha256.
7
- // 3. Stream-download the matching portable .zip into %TEMP%.
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's Expand-Archive (no native dep).
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 win = meta && meta.windows && meta.windows.x64;
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's own version artifacts ` +
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 zipPath = path.join(tmpDir, path.basename(win.portable_url));
353
+ const archivePath = path.join(tmpDir, path.basename(platform.archiveUrl));
314
354
 
315
- log(` From: ${win.portable_url}`);
316
- log(` To: ${zipPath}`);
317
- await downloadWithRetry(win.portable_url, zipPath, win.portable_size);
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(zipPath);
321
- if (actual.toLowerCase() !== win.portable_sha256.toLowerCase()) {
322
- try { fs.unlinkSync(zipPath); } catch (_) {}
323
- fail(`SHA-256 mismatch.\n expected: ${win.portable_sha256}\n got: ${actual}`);
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
- extractZipPS(zipPath, targetDir);
375
+ platform.extract(archivePath, targetDir);
336
376
 
337
- // build_portable.bat puts everything inside an extra `uniner-cli-portable`
338
- // top-level folder. If we see exactly one subdir and the binary is one
339
- // level deeper, flatten so realBinaryPath() resolves cleanly.
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, 'bin', 'uniner-cli', 'uniner-cli.exe'))) {
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(zipPath); } catch (_) {}
400
+ try { fs.unlinkSync(archivePath); } catch (_) {}
360
401
  try { fs.rmdirSync(tmpDir); } catch (_) {}
361
402
  log(' Removed temporary files.');
362
403