fix(cli): replace execSync unzip with unzipper for cross-platform support

- Add @types/unzipper for TypeScript support
- Fix location header type assertion (string | string[] -> string)
- Bump version to 1.0.5
This commit is contained in:
chenbaowang 2026-04-16 11:18:20 +08:00
parent c970a76410
commit 616319fe35
4 changed files with 127 additions and 6 deletions

View file

@ -1,6 +1,6 @@
{
"name": "motovis-skillhub",
"version": "1.0.3",
"version": "1.0.5",
"type": "module",
"description": "SkillHub CLI - 企业级 Agent Skill 管理工具,支持命名空间",
"bin": {
@ -19,11 +19,13 @@
"ora": "^9.3.0",
"picocolors": "^1.1.1",
"semver": "^7.7.4",
"undici": "^7.24.0"
"undici": "^7.24.0",
"unzipper": "^0.12.3"
},
"devDependencies": {
"@types/node": "^22.0.0",
"@types/semver": "^7.5.8",
"@types/unzipper": "^0.10.11",
"typescript": "^5.6.0",
"unbuild": "^3.0.0",
"vitest": "^3.0.0"

View file

@ -29,6 +29,9 @@ importers:
undici:
specifier: ^7.24.0
version: 7.24.7
unzipper:
specifier: ^0.12.3
version: 0.12.3
devDependencies:
'@types/node':
specifier: ^22.0.0
@ -36,6 +39,9 @@ importers:
'@types/semver':
specifier: ^7.5.8
version: 7.7.1
'@types/unzipper':
specifier: ^0.10.11
version: 0.10.11
typescript:
specifier: ^5.6.0
version: 5.9.3
@ -603,6 +609,9 @@ packages:
'@types/semver@7.7.1':
resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==}
'@types/unzipper@0.10.11':
resolution: {integrity: sha512-D25im2zjyMCcgL9ag6N46+wbtJBnXIr7SI4zHf9eJD2Dw2tEB5e+p5MYkrxKIVRscs5QV0EhtU9rgXSPx90oJg==}
'@vitest/expect@3.2.4':
resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==}
@ -657,6 +666,9 @@ packages:
engines: {node: '>=6.0.0'}
hasBin: true
bluebird@3.7.2:
resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==}
boolbase@1.0.0:
resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
@ -722,6 +734,9 @@ packages:
convert-source-map@2.0.0:
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
core-util-is@1.0.3:
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
css-declaration-sorter@7.3.1:
resolution: {integrity: sha512-gz6x+KkgNCjxq3Var03pRYLhyNfwhkKF1g/yoLgDNtFvVu0/fOLV9C8fFEZRjACp/XQLumjAYo7JVjzH3wLbxA==}
engines: {node: ^14 || ^16 || >=18}
@ -803,6 +818,9 @@ packages:
domutils@3.2.2:
resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==}
duplexer2@0.1.4:
resolution: {integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==}
electron-to-chromium@1.5.331:
resolution: {integrity: sha512-IbxXrsTlD3hRodkLnbxAPP4OuJYdWCeM3IOdT+CpcMoIwIoDfCmRpEtSPfwBXxVkg9xmBeY7Lz2Eo2TDn/HC3Q==}
@ -864,6 +882,10 @@ packages:
fraction.js@5.3.4:
resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==}
fs-extra@11.3.4:
resolution: {integrity: sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==}
engines: {node: '>=14.14'}
fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
@ -876,6 +898,9 @@ packages:
resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==}
engines: {node: '>=18'}
graceful-fs@4.2.11:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
hasown@2.0.2:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'}
@ -883,6 +908,9 @@ packages:
hookable@5.5.3:
resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==}
inherits@2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
is-core-module@2.16.1:
resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==}
engines: {node: '>= 0.4'}
@ -901,6 +929,9 @@ packages:
resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==}
engines: {node: '>=18'}
isarray@1.0.0:
resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
jiti@1.21.7:
resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==}
hasBin: true
@ -915,6 +946,9 @@ packages:
js-tokens@9.0.1:
resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==}
jsonfile@6.2.0:
resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==}
knitwork@1.3.0:
resolution: {integrity: sha512-4LqMNoONzR43B1W0ek0fhXMsDNW/zxa1NdFAVMY+k28pgZLovR4G3PB5MrpTxCy1QaZCqNoiaKPr5w5qZHfSNw==}
@ -980,6 +1014,9 @@ packages:
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
node-int64@0.4.0:
resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==}
node-releases@2.0.37:
resolution: {integrity: sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==}
@ -1200,6 +1237,12 @@ packages:
resolution: {integrity: sha512-nODzvTiYVRGRqAOvE84Vk5JDPyyxsVk0/fbA/bq7RqlnhksGpset09XTxbpvLTIjoaF7K8Z8DG8yHtKGTPSYRw==}
engines: {node: '>=20'}
process-nextick-args@2.0.1:
resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
readable-stream@2.3.8:
resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==}
resolve@1.22.11:
resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==}
engines: {node: '>= 0.4'}
@ -1221,6 +1264,9 @@ packages:
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
safe-buffer@5.1.2:
resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
sax@1.6.0:
resolution: {integrity: sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==}
engines: {node: '>=11.0.0'}
@ -1261,6 +1307,9 @@ packages:
resolution: {integrity: sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw==}
engines: {node: '>=20'}
string_decoder@1.1.1:
resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==}
strip-ansi@7.2.0:
resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==}
engines: {node: '>=12'}
@ -1329,10 +1378,17 @@ packages:
resolution: {integrity: sha512-H/nlJ/h0ggGC+uRL3ovD+G0i4bqhvsDOpbDv7At5eFLlj2b41L8QliGbnl2H7SnDiYhENphh1tQFJZf+MyfLsQ==}
engines: {node: '>=20.18.1'}
universalify@2.0.1:
resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
engines: {node: '>= 10.0.0'}
untyped@2.0.0:
resolution: {integrity: sha512-nwNCjxJTjNuLCgFr42fEak5OcLuB3ecca+9ksPFNvtfYSLpjf+iJqSIaSnIile6ZPbKYxI5k2AfXqeopGudK/g==}
hasBin: true
unzipper@0.12.3:
resolution: {integrity: sha512-PZ8hTS+AqcGxsaQntl3IRBw65QrBI6lxzqDEL7IAo/XCEqRTKGfOX56Vea5TH9SZczRVxuzk1re04z/YjuYCJA==}
update-browserslist-db@1.2.3:
resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==}
hasBin: true
@ -1764,6 +1820,10 @@ snapshots:
'@types/semver@7.7.1': {}
'@types/unzipper@0.10.11':
dependencies:
'@types/node': 22.19.15
'@vitest/expect@3.2.4':
dependencies:
'@types/chai': 5.2.3
@ -1823,6 +1883,8 @@ snapshots:
baseline-browser-mapping@2.10.13: {}
bluebird@3.7.2: {}
boolbase@1.0.0: {}
browserslist@4.28.2:
@ -1880,6 +1942,8 @@ snapshots:
convert-source-map@2.0.0: {}
core-util-is@1.0.3: {}
css-declaration-sorter@7.3.1(postcss@8.5.8):
dependencies:
postcss: 8.5.8
@ -1982,6 +2046,10 @@ snapshots:
domelementtype: 2.3.0
domhandler: 5.0.3
duplexer2@0.1.4:
dependencies:
readable-stream: 2.3.8
electron-to-chromium@1.5.331: {}
entities@4.5.0: {}
@ -2080,6 +2148,12 @@ snapshots:
fraction.js@5.3.4: {}
fs-extra@11.3.4:
dependencies:
graceful-fs: 4.2.11
jsonfile: 6.2.0
universalify: 2.0.1
fsevents@2.3.3:
optional: true
@ -2087,12 +2161,16 @@ snapshots:
get-east-asian-width@1.5.0: {}
graceful-fs@4.2.11: {}
hasown@2.0.2:
dependencies:
function-bind: 1.1.2
hookable@5.5.3: {}
inherits@2.0.4: {}
is-core-module@2.16.1:
dependencies:
hasown: 2.0.2
@ -2107,6 +2185,8 @@ snapshots:
is-unicode-supported@2.1.0: {}
isarray@1.0.0: {}
jiti@1.21.7: {}
jiti@2.6.1: {}
@ -2116,6 +2196,12 @@ snapshots:
js-tokens@9.0.1: {}
jsonfile@6.2.0:
dependencies:
universalify: 2.0.1
optionalDependencies:
graceful-fs: 4.2.11
knitwork@1.3.0: {}
lilconfig@3.1.3: {}
@ -2170,6 +2256,8 @@ snapshots:
nanoid@3.3.11: {}
node-int64@0.4.0: {}
node-releases@2.0.37: {}
nth-check@2.1.1:
@ -2382,6 +2470,18 @@ snapshots:
pretty-bytes@7.1.0: {}
process-nextick-args@2.0.1: {}
readable-stream@2.3.8:
dependencies:
core-util-is: 1.0.3
inherits: 2.0.4
isarray: 1.0.0
process-nextick-args: 2.0.1
safe-buffer: 5.1.2
string_decoder: 1.1.1
util-deprecate: 1.0.2
resolve@1.22.11:
dependencies:
is-core-module: 2.16.1
@ -2435,6 +2535,8 @@ snapshots:
'@rollup/rollup-win32-x64-msvc': 4.60.1
fsevents: 2.3.3
safe-buffer@5.1.2: {}
sax@1.6.0: {}
scule@1.3.0: {}
@ -2460,6 +2562,10 @@ snapshots:
get-east-asian-width: 1.5.0
strip-ansi: 7.2.0
string_decoder@1.1.1:
dependencies:
safe-buffer: 5.1.2
strip-ansi@7.2.0:
dependencies:
ansi-regex: 6.2.2
@ -2543,6 +2649,8 @@ snapshots:
undici@7.24.7: {}
universalify@2.0.1: {}
untyped@2.0.0:
dependencies:
citty: 0.1.6
@ -2551,6 +2659,14 @@ snapshots:
knitwork: 1.3.0
scule: 1.3.0
unzipper@0.12.3:
dependencies:
bluebird: 3.7.2
duplexer2: 0.1.4
fs-extra: 11.3.4
graceful-fs: 4.2.11
node-int64: 0.4.0
update-browserslist-db@1.2.3(browserslist@4.28.2):
dependencies:
browserslist: 4.28.2

View file

@ -46,7 +46,7 @@ export function registerDownload(program: Command) {
spinner.fail(`Redirect response has no Location header`);
process.exit(1);
}
response = await request(location, { method: "GET" });
response = await request(location as string, { method: "GET" });
}
const { statusCode, body } = response;

View file

@ -2,7 +2,7 @@ import { Command } from "commander";
import { mkdtemp, rm, readFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { createWriteStream, existsSync, mkdirSync } from "node:fs";
import { createWriteStream, createReadStream, existsSync, mkdirSync } from "node:fs";
import { ApiClient } from "../core/api-client.js";
import { loadConfig } from "../core/config.js";
import { readToken } from "../core/auth-token.js";
@ -12,6 +12,7 @@ import { getAllAgents, detectInstalledAgents, getUniversalAgents, getNonUniversa
import { parseSource, getCloneUrl } from "../core/source-parser.js";
import { addToLock } from "../core/skill-lock.js";
import { success, error, info, dim } from "../utils/logger.js";
import unzipper from "unzipper";
import { multiSelect, sectionMultiSelect } from "../utils/prompts.js";
import { searchMultiselect, cancelSymbol } from "../utils/search-multiselect.js";
import { runInteractiveSearch, searchSkills } from "../core/interactive-search.js";
@ -296,7 +297,7 @@ async function installFromRegistry(slug: string, opts: Record<string, string | s
await rm(tmpDir, { recursive: true, force: true });
process.exit(1);
}
response = await request(location, { method: "GET" });
response = await request(location as string, { method: "GET" });
}
const { statusCode, body } = response;
@ -312,7 +313,9 @@ async function installFromRegistry(slug: string, opts: Record<string, string | s
spinner.text = "Extracting";
const extractDir = join(tmpDir, "extracted");
mkdirSync(extractDir, { recursive: true });
execSync(`unzip -o "${zipPath}" -d "${extractDir}"`, { stdio: "pipe" });
await createReadStream(zipPath)
.pipe(unzipper.Extract({ path: extractDir }))
.promise();
const skills = discoverSkills(extractDir);
if (skills.length === 0) {