openclaw-weiyuan-init 1.0.79 → 1.0.80

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/lib/commands.js CHANGED
@@ -342,7 +342,7 @@ async function runInit(options) {
342
342
 
343
343
  spinner = ora('下载 weiyuan skill...').start();
344
344
  const zipPath = path.join(weiyuanPath, source.zipFilename);
345
- const downloadSuccess = await downloadFile(source.downloadUrl, zipPath, spinner);
345
+ const downloadSuccess = await downloadFile(source.downloadUrl, zipPath, spinner, source.sha256 || '');
346
346
  if (!downloadSuccess) {
347
347
  spinner.fail('下载失败');
348
348
  throw new Error('下载失败');
package/lib/downloader.js CHANGED
@@ -1,73 +1,104 @@
1
- const axios = require('axios');
2
- const fs = require('fs-extra');
3
-
4
- function trimSlash(url) {
5
- return String(url || '').replace(/\/+$/, '');
6
- }
7
-
8
- function toZipFilename(downloadUrl) {
9
- const clean = String(downloadUrl || '').split('?')[0].split('#')[0];
10
- const parts = clean.split('/');
11
- return parts[parts.length - 1] || 'weiyuan-openclaw-skill.zip';
12
- }
13
-
14
- async function resolvePackageSource({ downloadUrl, upgradeBaseUrl, spinner = null }) {
15
- if (downloadUrl) {
16
- return { downloadUrl, zipFilename: toZipFilename(downloadUrl), version: null, from: 'download_url' };
17
- }
18
-
19
- const base = trimSlash(upgradeBaseUrl);
20
- const latestUrl = `${base}/LATEST_SKILL_VERSION.txt`;
21
- const latestResp = await axios.get(latestUrl, { timeout: 10000 });
22
- const version = String(latestResp.data || '').trim();
23
- if (!version) {
24
- throw new Error('empty_latest_version');
25
- }
26
- const zipFilename = `weiyuan-openclaw-skill-v${version}.zip`;
27
- const resolvedDownloadUrl = `${base}/${zipFilename}`;
28
- if (spinner) {
29
- spinner.text = `已解析最新版本: v${version}`;
30
- }
31
- return { downloadUrl: resolvedDownloadUrl, zipFilename, version, from: 'latest_version' };
32
- }
33
-
34
- async function downloadFile(url, outputPath, spinner = null) {
35
- try {
36
- const response = await axios({
37
- method: 'GET',
38
- url: url,
39
- responseType: 'stream',
40
- timeout: 60000,
41
- maxContentLength: Infinity,
42
- maxBodyLength: Infinity
43
- });
44
-
45
- const totalLength = parseInt(response.headers['content-length'], 10);
46
- let downloadedLength = 0;
47
-
48
- const writer = fs.createWriteStream(outputPath);
49
-
50
- response.data.on('data', (chunk) => {
51
- downloadedLength += chunk.length;
52
- if (spinner && totalLength) {
53
- const percent = (downloadedLength / totalLength * 100).toFixed(1);
54
- spinner.text = `下载中... ${percent}% (${(downloadedLength / 1024 / 1024).toFixed(1)}MB / ${(totalLength / 1024 / 1024).toFixed(1)}MB)`;
55
- }
56
- });
57
-
58
- response.data.pipe(writer);
59
-
60
- return new Promise((resolve, reject) => {
61
- writer.on('finish', () => resolve(true));
62
- writer.on('error', reject);
63
- response.data.on('error', reject);
64
- });
65
- } catch (error) {
66
- if (spinner) {
67
- spinner.text = `下载失败: ${error.message}`;
68
- }
69
- return false;
70
- }
71
- }
72
-
73
- module.exports = { downloadFile, resolvePackageSource };
1
+ const axios = require('axios');
2
+ const fs = require('fs-extra');
3
+ const crypto = require('crypto');
4
+
5
+ function trimSlash(url) {
6
+ return String(url || '').replace(/\/+$/, '');
7
+ }
8
+
9
+ function toZipFilename(downloadUrl) {
10
+ const clean = String(downloadUrl || '').split('?')[0].split('#')[0];
11
+ const parts = clean.split('/');
12
+ return parts[parts.length - 1] || 'weiyuan-openclaw-skill.zip';
13
+ }
14
+
15
+ async function resolvePackageSource({ downloadUrl, upgradeBaseUrl, spinner = null }) {
16
+ if (downloadUrl) {
17
+ return { downloadUrl, zipFilename: toZipFilename(downloadUrl), version: null, sha256: '', from: 'download_url' };
18
+ }
19
+
20
+ const base = trimSlash(upgradeBaseUrl);
21
+ const nonce = Date.now();
22
+ try {
23
+ const latestMetaUrl = `${base}/SKILL_LATEST.json?nocache=${nonce}`;
24
+ const latestMetaResp = await axios.get(latestMetaUrl, { timeout: 10000 });
25
+ const meta = latestMetaResp.data || {};
26
+ const version = String(meta.version || '').trim();
27
+ if (version) {
28
+ const zipFilename = String(meta.zipName || '').trim() || `weiyuan-openclaw-skill-v${version}.zip`;
29
+ const resolvedDownloadUrl = `${base}/${zipFilename}?nocache=${nonce}`;
30
+ const sha256 = String(meta.sha256 || '').trim().toLowerCase();
31
+ if (spinner) {
32
+ spinner.text = `已解析最新版本: v${version}`;
33
+ }
34
+ return { downloadUrl: resolvedDownloadUrl, zipFilename, version, sha256, from: 'latest_json' };
35
+ }
36
+ } catch (_e) {
37
+ // fallback to txt
38
+ }
39
+ const latestUrl = `${base}/LATEST_SKILL_VERSION.txt?nocache=${nonce}`;
40
+ const latestResp = await axios.get(latestUrl, { timeout: 10000 });
41
+ const version = String(latestResp.data || '').trim();
42
+ if (!version) throw new Error('empty_latest_version');
43
+ const zipFilename = `weiyuan-openclaw-skill-v${version}.zip`;
44
+ const resolvedDownloadUrl = `${base}/${zipFilename}?nocache=${nonce}`;
45
+ if (spinner) {
46
+ spinner.text = `已解析最新版本: v${version}`;
47
+ }
48
+ return { downloadUrl: resolvedDownloadUrl, zipFilename, version, sha256: '', from: 'latest_version' };
49
+ }
50
+
51
+ async function downloadFile(url, outputPath, spinner = null, expectedSha256 = '') {
52
+ try {
53
+ const response = await axios({
54
+ method: 'GET',
55
+ url: url,
56
+ responseType: 'stream',
57
+ timeout: 60000,
58
+ maxContentLength: Infinity,
59
+ maxBodyLength: Infinity
60
+ });
61
+
62
+ const totalLength = parseInt(response.headers['content-length'], 10);
63
+ let downloadedLength = 0;
64
+
65
+ const writer = fs.createWriteStream(outputPath);
66
+
67
+ response.data.on('data', (chunk) => {
68
+ downloadedLength += chunk.length;
69
+ if (spinner && totalLength) {
70
+ const percent = (downloadedLength / totalLength * 100).toFixed(1);
71
+ spinner.text = `下载中... ${percent}% (${(downloadedLength / 1024 / 1024).toFixed(1)}MB / ${(totalLength / 1024 / 1024).toFixed(1)}MB)`;
72
+ }
73
+ });
74
+
75
+ response.data.pipe(writer);
76
+
77
+ return new Promise((resolve, reject) => {
78
+ writer.on('finish', async () => {
79
+ try {
80
+ if (expectedSha256) {
81
+ const fileBuffer = await fs.readFile(outputPath);
82
+ const actualSha256 = crypto.createHash('sha256').update(fileBuffer).digest('hex').toLowerCase();
83
+ const expected = String(expectedSha256).toLowerCase();
84
+ if (actualSha256 !== expected) {
85
+ return reject(new Error(`skill_package_hash_mismatch expected=${expected} got=${actualSha256}`));
86
+ }
87
+ }
88
+ resolve(true);
89
+ } catch (hashErr) {
90
+ reject(hashErr);
91
+ }
92
+ });
93
+ writer.on('error', reject);
94
+ response.data.on('error', reject);
95
+ });
96
+ } catch (error) {
97
+ if (spinner) {
98
+ spinner.text = `下载失败: ${error.message}`;
99
+ }
100
+ return false;
101
+ }
102
+ }
103
+
104
+ module.exports = { downloadFile, resolvePackageSource };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-weiyuan-init",
3
- "version": "1.0.79",
3
+ "version": "1.0.80",
4
4
  "description": "OpenClaw Weiyuan Skill 一键初始化工具",
5
5
  "main": "bin/cli.js",
6
6
  "bin": {