@sciagent/cli 1.1.50 → 1.1.52

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/bin/sciagent.js CHANGED
@@ -25,10 +25,22 @@ const https = require('https');
25
25
  const http = require('http');
26
26
 
27
27
  // 当前版本号 - 与 postinstall.js 和 package.json 保持同步
28
- const CURRENT_VERSION = '1.1.50';
29
-
30
- // Releases 服务器下载配置
31
- const RELEASE_SERVER_URL = process.env.RELEASE_SERVER_URL || 'https://sciagent.tech';
28
+ const CURRENT_VERSION = '1.1.52';
29
+
30
+ // Releases 下载源配置(优先级从高到低)
31
+ // JihuLab 通用包仓库(国内 CDN,速度快)作为主源
32
+ // sciagent.tech 自建服务器作为备用源
33
+ const JIHULAB_PROJECT_ID = '351778';
34
+ const JIHULAB_DEPLOY_TOKEN_USER = 'gitlab+deploy-token-15400';
35
+ const JIHULAB_DEPLOY_TOKEN = 'gldt-5kp7mgt4ztyyBMx1Uvn6';
36
+ const JIHULAB_BASE_URL = `https://jihulab.com/api/v4/projects/${JIHULAB_PROJECT_ID}/packages/generic/sciagent`;
37
+ const SELF_HOSTED_URL = process.env.RELEASE_SERVER_URL || 'https://sciagent.tech';
38
+
39
+ // 下载源列表(按优先级排序)
40
+ const DOWNLOAD_MIRRORS = [
41
+ { name: 'JihuLab CDN', getUrl: (platform, arch, version, filename) => `${JIHULAB_BASE_URL}/${version}/${filename}`, auth: 'jihulab' },
42
+ { name: 'Self-hosted', getUrl: (platform, arch, version, filename) => `${SELF_HOSTED_URL}/api/releases/download/${platform}/${arch}/${version}`, auth: 'none' },
43
+ ];
32
44
 
33
45
  // 平台和架构映射
34
46
  const PLATFORM_MAP = {
@@ -156,8 +168,8 @@ function applyPendingUpdate(installDir) {
156
168
  */
157
169
  function fetchLatestVersion() {
158
170
  return new Promise((resolve) => {
159
- // 使用 /api/releases/list 获取所有版本,取最新的
160
- const url = `${RELEASE_SERVER_URL}/api/releases/list`;
171
+ // 使用自建服务器 /api/releases/list 获取所有版本,取最新的
172
+ const url = `${SELF_HOSTED_URL}/api/releases/list`;
161
173
 
162
174
  const protocol = url.startsWith('https') ? https : http;
163
175
  const request = protocol.get(url, {
@@ -196,16 +208,13 @@ function fetchLatestVersion() {
196
208
  * 后台下载新版本到临时目录
197
209
  * 下载完成后写入 .pending-update 标记
198
210
  * 这是异步操作,不阻塞主进程启动
211
+ * 支持多源下载:优先 JihuLab CDN,失败回退到自建服务器
199
212
  */
200
213
  function backgroundDownloadUpdate(platform, arch, latestVersion) {
201
214
  const binName = platform === 'win32' ? 'sciagent.exe' : 'sciagent';
202
215
  const ext = platform === 'win32' ? '.exe' : '';
203
216
  const filename = `sciagent-${platform}-${arch}${ext}`;
204
217
 
205
- const downloadUrl = (
206
- `${RELEASE_SERVER_URL}/api/releases/download/${platform}/${arch}/${latestVersion}`
207
- );
208
-
209
218
  const installDir = getInstallDir(platform);
210
219
  const tempDir = path.join(os.tmpdir(), 'sciagent-update');
211
220
  fs.mkdirSync(tempDir, { recursive: true });
@@ -216,73 +225,123 @@ function backgroundDownloadUpdate(platform, arch, latestVersion) {
216
225
 
217
226
  console.log(`[AUTO-UPDATE] Downloading v${latestVersion} in background...`);
218
227
 
228
+ // 构建下载 URL 列表(按优先级)
229
+ const downloadUrls = DOWNLOAD_MIRRORS.map(mirror => ({
230
+ url: mirror.getUrl(platform, arch, latestVersion, filename),
231
+ name: mirror.name,
232
+ auth: mirror.auth
233
+ }));
234
+
219
235
  if (platform === 'win32') {
220
- // Windows: 使用 PowerShell 后台下载
221
- const tmpScript = path.join(os.tmpdir(), 'sciagent-bg-download.ps1');
222
- const scriptContent = [
223
- '$ProgressPreference = "SilentlyContinue"',
224
- `$uri = "${downloadUrl}"`,
225
- `$out = "${tempPath}"`,
226
- 'Invoke-WebRequest -Uri $uri -OutFile $out -UseBasicParsing',
227
- `if (Test-Path $out) { $s = (Get-Item $out).Length; Write-Host "Downloaded: $s bytes" } else { exit 1 }`
228
- ].join('\r\n');
229
- fs.writeFileSync(tmpScript, scriptContent, 'utf8');
230
-
231
- const bgProcess = spawn('powershell', [
232
- '-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', tmpScript
233
- ], { stdio: 'pipe', detached: true, windowsHide: true });
234
-
235
- bgProcess.on('exit', (code) => {
236
- try { fs.unlinkSync(tmpScript); } catch (e) {}
237
- if (code === 0 && fs.existsSync(tempPath)) {
238
- const stats = fs.statSync(tempPath);
239
- if (stats.size > 10 * 1024 * 1024) {
240
- console.log(`[AUTO-UPDATE] ✅ Downloaded v${latestVersion} (${(stats.size / (1024 * 1024)).toFixed(1)} MB)`);
241
- // 写入 pending update 标记
242
- const pendingData = {
243
- version: latestVersion,
244
- tempPath: tempPath,
245
- timestamp: new Date().toISOString(),
246
- platform: process.platform
247
- };
248
- fs.writeFileSync(path.join(installDir, '.pending-update'), JSON.stringify(pendingData, null, 2), 'utf8');
249
- console.log(`[AUTO-UPDATE] 📋 Update will be applied on next restart.`);
250
- }
236
+ // Windows: 使用 PowerShell 后台下载,尝试多个源
237
+ const tryDownload = (urlIndex) => {
238
+ if (urlIndex >= downloadUrls.length) {
239
+ console.log(`[AUTO-UPDATE] ⚠️ Background download failed from all mirrors.`);
240
+ return;
241
+ }
242
+
243
+ const source = downloadUrls[urlIndex];
244
+ const tmpScript = path.join(os.tmpdir(), 'sciagent-bg-download.ps1');
245
+ let scriptLines = [
246
+ '$ProgressPreference = "SilentlyContinue"',
247
+ `$uri = "${source.url}"`,
248
+ `$out = "${tempPath}"`,
249
+ ];
250
+
251
+ if (source.auth === 'jihulab') {
252
+ // JihuLab 需要 Basic Auth
253
+ const b64 = Buffer.from(`${JIHULAB_DEPLOY_TOKEN_USER}:${JIHULAB_DEPLOY_TOKEN}`).toString('base64');
254
+ scriptLines.push(`$headers = @{ "Authorization" = "Basic ${b64}" }`);
255
+ scriptLines.push('Invoke-WebRequest -Uri $uri -OutFile $out -UseBasicParsing -Headers $headers');
251
256
  } else {
252
- console.log(`[AUTO-UPDATE] ⚠️ Background download failed.`);
253
- try { if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath); } catch (e) {}
257
+ scriptLines.push('Invoke-WebRequest -Uri $uri -OutFile $out -UseBasicParsing');
254
258
  }
255
- });
259
+ scriptLines.push(`if (Test-Path $out) { $s = (Get-Item $out).Length; Write-Host "Downloaded: $s bytes" } else { exit 1 }`);
260
+
261
+ fs.writeFileSync(tmpScript, scriptLines.join('\r\n'), 'utf8');
262
+
263
+ const bgProcess = spawn('powershell', [
264
+ '-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', tmpScript
265
+ ], { stdio: 'pipe', detached: true, windowsHide: true });
266
+
267
+ bgProcess.on('exit', (code) => {
268
+ try { fs.unlinkSync(tmpScript); } catch (e) {}
269
+ if (code === 0 && fs.existsSync(tempPath)) {
270
+ const stats = fs.statSync(tempPath);
271
+ if (stats.size > 10 * 1024 * 1024) {
272
+ console.log(`[AUTO-UPDATE] ✅ Downloaded v${latestVersion} from ${source.name} (${(stats.size / (1024 * 1024)).toFixed(1)} MB)`);
273
+ const pendingData = {
274
+ version: latestVersion,
275
+ tempPath: tempPath,
276
+ timestamp: new Date().toISOString(),
277
+ platform: process.platform
278
+ };
279
+ fs.writeFileSync(path.join(installDir, '.pending-update'), JSON.stringify(pendingData, null, 2), 'utf8');
280
+ console.log(`[AUTO-UPDATE] 📋 Update will be applied on next restart.`);
281
+ } else {
282
+ console.log(`[AUTO-UPDATE] ⚠️ Download from ${source.name} too small, trying next...`);
283
+ try { if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath); } catch (e) {}
284
+ tryDownload(urlIndex + 1);
285
+ }
286
+ } else {
287
+ console.log(`[AUTO-UPDATE] ⚠️ Download from ${source.name} failed, trying next...`);
288
+ try { if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath); } catch (e) {}
289
+ tryDownload(urlIndex + 1);
290
+ }
291
+ });
292
+
293
+ bgProcess.unref();
294
+ };
256
295
 
257
- bgProcess.unref();
296
+ tryDownload(0);
258
297
  } else {
259
- // Linux/macOS: 使用 curl 后台下载
260
- const bgProcess = spawn('curl', [
261
- '-fsSL', '-o', tempPath, downloadUrl
262
- ], { stdio: 'pipe', detached: true });
263
-
264
- bgProcess.on('exit', (code) => {
265
- if (code === 0 && fs.existsSync(tempPath)) {
266
- const stats = fs.statSync(tempPath);
267
- if (stats.size > 10 * 1024 * 1024) {
268
- fs.chmodSync(tempPath, 0o755);
269
- console.log(`[AUTO-UPDATE] ✅ Downloaded v${latestVersion} (${(stats.size / (1024 * 1024)).toFixed(1)} MB)`);
270
- const pendingData = {
271
- version: latestVersion,
272
- tempPath: tempPath,
273
- timestamp: new Date().toISOString(),
274
- platform: process.platform
275
- };
276
- fs.writeFileSync(path.join(installDir, '.pending-update'), JSON.stringify(pendingData, null, 2), 'utf8');
277
- console.log(`[AUTO-UPDATE] 📋 Update will be applied on next restart.`);
278
- }
279
- } else {
280
- console.log(`[AUTO-UPDATE] ⚠️ Background download failed.`);
281
- try { if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath); } catch (e) {}
298
+ // Linux/macOS: 使用 curl 后台下载,尝试多个源
299
+ const tryDownload = (urlIndex) => {
300
+ if (urlIndex >= downloadUrls.length) {
301
+ console.log(`[AUTO-UPDATE] ⚠️ Background download failed from all mirrors.`);
302
+ return;
282
303
  }
283
- });
304
+
305
+ const source = downloadUrls[urlIndex];
306
+ let curlArgs = ['-fsSL', '-o', tempPath];
307
+
308
+ if (source.auth === 'jihulab') {
309
+ curlArgs.push('-u', `${JIHULAB_DEPLOY_TOKEN_USER}:${JIHULAB_DEPLOY_TOKEN}`);
310
+ }
311
+ curlArgs.push(source.url);
312
+
313
+ const bgProcess = spawn('curl', curlArgs, { stdio: 'pipe', detached: true });
314
+
315
+ bgProcess.on('exit', (code) => {
316
+ if (code === 0 && fs.existsSync(tempPath)) {
317
+ const stats = fs.statSync(tempPath);
318
+ if (stats.size > 10 * 1024 * 1024) {
319
+ fs.chmodSync(tempPath, 0o755);
320
+ console.log(`[AUTO-UPDATE] ✅ Downloaded v${latestVersion} from ${source.name} (${(stats.size / (1024 * 1024)).toFixed(1)} MB)`);
321
+ const pendingData = {
322
+ version: latestVersion,
323
+ tempPath: tempPath,
324
+ timestamp: new Date().toISOString(),
325
+ platform: process.platform
326
+ };
327
+ fs.writeFileSync(path.join(installDir, '.pending-update'), JSON.stringify(pendingData, null, 2), 'utf8');
328
+ console.log(`[AUTO-UPDATE] 📋 Update will be applied on next restart.`);
329
+ } else {
330
+ console.log(`[AUTO-UPDATE] ⚠️ Download from ${source.name} too small, trying next...`);
331
+ try { if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath); } catch (e) {}
332
+ tryDownload(urlIndex + 1);
333
+ }
334
+ } else {
335
+ console.log(`[AUTO-UPDATE] ⚠️ Download from ${source.name} failed, trying next...`);
336
+ try { if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath); } catch (e) {}
337
+ tryDownload(urlIndex + 1);
338
+ }
339
+ });
340
+
341
+ bgProcess.unref();
342
+ };
284
343
 
285
- bgProcess.unref();
344
+ tryDownload(0);
286
345
  }
287
346
  }
288
347
 
@@ -473,6 +532,7 @@ function getBinaryPath() {
473
532
  /**
474
533
  * 从 Release Server 下载二进制文件到临时目录(不替换当前运行的二进制)
475
534
  * 下载完成后写入 .pending-update 标记,下次启动时自动应用
535
+ * 支持多源下载:优先 JihuLab CDN,失败回退到自建服务器
476
536
  * 返回: tempPath 或 null
477
537
  */
478
538
  function downloadBinaryToTemp(platform, arch, version) {
@@ -480,10 +540,6 @@ function downloadBinaryToTemp(platform, arch, version) {
480
540
  const ext = platform === 'win32' ? '.exe' : '';
481
541
  const filename = `sciagent-${platform}-${arch}${ext}`;
482
542
 
483
- const downloadUrl = (
484
- `${RELEASE_SERVER_URL}/api/releases/download/${platform}/${arch}/${version}`
485
- );
486
-
487
543
  const installDir = getInstallDir(platform);
488
544
  const tempDir = path.join(os.tmpdir(), 'sciagent-update');
489
545
  fs.mkdirSync(tempDir, { recursive: true });
@@ -495,80 +551,107 @@ function downloadBinaryToTemp(platform, arch, version) {
495
551
  console.log(` Downloading ${filename} v${version} to temp...`);
496
552
  console.log(` Temp: ${tempPath}`);
497
553
 
498
- try {
499
- if (platform === 'win32') {
500
- const tmpScript = path.join(os.tmpdir(), 'sciagent-download.ps1');
501
- const scriptContent = [
502
- '$ProgressPreference = "SilentlyContinue"',
503
- `$uri = "${downloadUrl}"`,
504
- `$out = "${tempPath}"`,
505
- 'Write-Host " Downloading from server..."',
506
- 'Invoke-WebRequest -Uri $uri -OutFile $out -UseBasicParsing',
507
- 'if (Test-Path $out) { $s = (Get-Item $out).Length; Write-Host " Downloaded: $s bytes" } else { Write-Error "File not created"; exit 1 }'
508
- ].join('\r\n');
509
- fs.writeFileSync(tmpScript, scriptContent, 'utf8');
510
-
511
- try {
512
- execSync(`powershell -NoProfile -ExecutionPolicy Bypass -File "${tmpScript}"`, {
513
- stdio: 'inherit',
514
- timeout: 600000
515
- });
516
- } finally {
517
- try { fs.unlinkSync(tmpScript); } catch (e) {}
518
- }
519
- } else {
520
- execSync(`curl -fsSL -o '${tempPath}' '${downloadUrl}'`, {
521
- stdio: 'pipe',
522
- timeout: 600000
523
- });
524
- fs.chmodSync(tempPath, 0o755);
525
- }
554
+ // 构建下载 URL 列表(按优先级)
555
+ const downloadUrls = DOWNLOAD_MIRRORS.map(mirror => ({
556
+ url: mirror.getUrl(platform, arch, version, filename),
557
+ name: mirror.name,
558
+ auth: mirror.auth
559
+ }));
560
+
561
+ for (const source of downloadUrls) {
562
+ console.log(` Trying ${source.name}...`);
526
563
 
527
- // 验证下载
528
- if (fs.existsSync(tempPath)) {
529
- const stats = fs.statSync(tempPath);
530
- if (stats.size > 10 * 1024 * 1024) {
531
- console.log(` ✅ Downloaded: ${(stats.size / (1024 * 1024)).toFixed(1)} MB`);
532
- // 写入 .pending-update 标记
533
- const pendingData = {
534
- version: version,
535
- tempPath: tempPath,
536
- timestamp: new Date().toISOString(),
537
- platform: process.platform
538
- };
539
- fs.writeFileSync(path.join(installDir, '.pending-update'), JSON.stringify(pendingData, null, 2), 'utf8');
540
- return tempPath;
564
+ try {
565
+ if (platform === 'win32') {
566
+ const tmpScript = path.join(os.tmpdir(), 'sciagent-download.ps1');
567
+ let scriptLines = [
568
+ '$ProgressPreference = "SilentlyContinue"',
569
+ `$uri = "${source.url}"`,
570
+ `$out = "${tempPath}"`,
571
+ 'Write-Host " Downloading..."',
572
+ ];
573
+
574
+ if (source.auth === 'jihulab') {
575
+ const b64 = Buffer.from(`${JIHULAB_DEPLOY_TOKEN_USER}:${JIHULAB_DEPLOY_TOKEN}`).toString('base64');
576
+ scriptLines.push(`$headers = @{ "Authorization" = "Basic ${b64}" }`);
577
+ scriptLines.push('Invoke-WebRequest -Uri $uri -OutFile $out -UseBasicParsing -Headers $headers');
578
+ } else {
579
+ scriptLines.push('Invoke-WebRequest -Uri $uri -OutFile $out -UseBasicParsing');
580
+ }
581
+ scriptLines.push('if (Test-Path $out) { $s = (Get-Item $out).Length; Write-Host " Downloaded: $s bytes" } else { Write-Error "File not created"; exit 1 }');
582
+
583
+ fs.writeFileSync(tmpScript, scriptLines.join('\r\n'), 'utf8');
584
+
585
+ try {
586
+ execSync(`powershell -NoProfile -ExecutionPolicy Bypass -File "${tmpScript}"`, {
587
+ stdio: 'inherit',
588
+ timeout: 600000
589
+ });
590
+ } finally {
591
+ try { fs.unlinkSync(tmpScript); } catch (e) {}
592
+ }
541
593
  } else {
542
- console.error(` ❌ Downloaded file too small: ${(stats.size / 1024).toFixed(0)} KB`);
543
- try { fs.unlinkSync(tempPath); } catch (e) {}
594
+ let curlCmd = `curl -fsSL -o '${tempPath}'`;
595
+ if (source.auth === 'jihulab') {
596
+ curlCmd += ` -u '${JIHULAB_DEPLOY_TOKEN_USER}:${JIHULAB_DEPLOY_TOKEN}'`;
597
+ }
598
+ curlCmd += ` '${source.url}'`;
599
+ execSync(curlCmd, { stdio: 'pipe', timeout: 600000 });
600
+ fs.chmodSync(tempPath, 0o755);
544
601
  }
602
+
603
+ // 验证下载
604
+ if (fs.existsSync(tempPath)) {
605
+ const stats = fs.statSync(tempPath);
606
+ if (stats.size > 10 * 1024 * 1024) {
607
+ console.log(` ✅ Downloaded from ${source.name}: ${(stats.size / (1024 * 1024)).toFixed(1)} MB`);
608
+ // 写入 .pending-update 标记
609
+ const pendingData = {
610
+ version: version,
611
+ tempPath: tempPath,
612
+ timestamp: new Date().toISOString(),
613
+ platform: process.platform
614
+ };
615
+ fs.writeFileSync(path.join(installDir, '.pending-update'), JSON.stringify(pendingData, null, 2), 'utf8');
616
+ return tempPath;
617
+ } else {
618
+ console.error(` ❌ Downloaded from ${source.name} too small: ${(stats.size / 1024).toFixed(0)} KB`);
619
+ try { fs.unlinkSync(tempPath); } catch (e) {}
620
+ }
621
+ }
622
+ } catch (e) {
623
+ console.error(` ❌ Download from ${source.name} failed: ${e.message}`);
624
+ try { if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath); } catch (e2) {}
545
625
  }
546
- } catch (e) {
547
- console.error(` ❌ Download failed: ${e.message}`);
548
- try { if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath); } catch (e2) {}
549
626
  }
550
627
 
628
+ console.error(` ❌ All download mirrors failed.`);
629
+
551
630
  return null;
552
631
  }
553
632
 
554
633
  /**
555
634
  * 从 Release Server 下载二进制文件(直接到目标路径,用于首次安装或版本更新)
556
635
  * 如果目标文件已存在,先下载到临时目录再替换
636
+ * 支持多源下载:优先 JihuLab CDN,失败回退到自建服务器
557
637
  */
558
638
  function downloadBinary(platform, arch, version) {
559
639
  const binName = platform === 'win32' ? 'sciagent.exe' : 'sciagent';
560
640
  const ext = platform === 'win32' ? '.exe' : '';
561
641
  const filename = `sciagent-${platform}-${arch}${ext}`;
562
642
 
563
- const downloadUrl = (
564
- `${RELEASE_SERVER_URL}/api/releases/download/${platform}/${arch}/${version}`
565
- );
566
-
567
643
  const installDir = getInstallDir(platform);
568
644
  fs.mkdirSync(installDir, { recursive: true });
569
645
  const targetPath = path.join(installDir, binName);
570
646
  const targetExists = fs.existsSync(targetPath);
571
647
 
648
+ // 构建下载 URL 列表(按优先级)
649
+ const downloadUrls = DOWNLOAD_MIRRORS.map(mirror => ({
650
+ url: mirror.getUrl(platform, arch, version, filename),
651
+ name: mirror.name,
652
+ auth: mirror.auth
653
+ }));
654
+
572
655
  // 如果目标文件已存在,先下载到临时目录再替换
573
656
  if (targetExists) {
574
657
  const tempDir = path.join(os.tmpdir(), 'sciagent-update');
@@ -579,149 +662,178 @@ function downloadBinary(platform, arch, version) {
579
662
 
580
663
  console.log(` Downloading ${filename} v${version} to temp...`);
581
664
 
582
- try {
583
- if (platform === 'win32') {
584
- const tmpScript = path.join(os.tmpdir(), 'sciagent-download.ps1');
585
- const scriptContent = [
586
- '$ProgressPreference = "SilentlyContinue"',
587
- `$uri = "${downloadUrl}"`,
588
- `$out = "${tempPath}"`,
589
- 'Write-Host " Downloading from server..."',
590
- 'Invoke-WebRequest -Uri $uri -OutFile $out -UseBasicParsing',
591
- 'if (Test-Path $out) { $s = (Get-Item $out).Length; Write-Host " Downloaded: $s bytes" } else { Write-Error "File not created"; exit 1 }'
592
- ].join('\r\n');
593
- fs.writeFileSync(tmpScript, scriptContent, 'utf8');
594
-
595
- try {
596
- execSync(`powershell -NoProfile -ExecutionPolicy Bypass -File "${tmpScript}"`, {
597
- stdio: 'inherit',
598
- timeout: 600000
599
- });
600
- } finally {
601
- try { fs.unlinkSync(tmpScript); } catch (e) {}
602
- }
603
- } else {
604
- execSync(`curl -fsSL -o '${tempPath}' '${downloadUrl}'`, {
605
- stdio: 'pipe',
606
- timeout: 600000
607
- });
608
- fs.chmodSync(tempPath, 0o755);
609
- }
665
+ for (const source of downloadUrls) {
666
+ console.log(` Trying ${source.name}...`);
610
667
 
611
- // 验证临时文件
612
- if (fs.existsSync(tempPath)) {
613
- const tempStats = fs.statSync(tempPath);
614
- if (tempStats.size > 10 * 1024 * 1024) {
615
- console.log(` ✅ Downloaded: ${(tempStats.size / (1024 * 1024)).toFixed(1)} MB`);
668
+ try {
669
+ if (platform === 'win32') {
670
+ const tmpScript = path.join(os.tmpdir(), 'sciagent-download.ps1');
671
+ let scriptLines = [
672
+ '$ProgressPreference = "SilentlyContinue"',
673
+ `$uri = "${source.url}"`,
674
+ `$out = "${tempPath}"`,
675
+ 'Write-Host " Downloading..."',
676
+ ];
677
+
678
+ if (source.auth === 'jihulab') {
679
+ const b64 = Buffer.from(`${JIHULAB_DEPLOY_TOKEN_USER}:${JIHULAB_DEPLOY_TOKEN}`).toString('base64');
680
+ scriptLines.push(`$headers = @{ "Authorization" = "Basic ${b64}" }`);
681
+ scriptLines.push('Invoke-WebRequest -Uri $uri -OutFile $out -UseBasicParsing -Headers $headers');
682
+ } else {
683
+ scriptLines.push('Invoke-WebRequest -Uri $uri -OutFile $out -UseBasicParsing');
684
+ }
685
+ scriptLines.push('if (Test-Path $out) { $s = (Get-Item $out).Length; Write-Host " Downloaded: $s bytes" } else { Write-Error "File not created"; exit 1 }');
686
+
687
+ fs.writeFileSync(tmpScript, scriptLines.join('\r\n'), 'utf8');
616
688
 
617
- // 尝试替换目标文件
618
689
  try {
619
- if (platform === 'win32') {
620
- const tmpScript = path.join(os.tmpdir(), 'sciagent-replace.ps1');
621
- const scriptContent = [
622
- `Copy-Item -Path "${tempPath}" -Destination "${targetPath}" -Force`,
623
- 'if (Test-Path $out) { Write-Host "OK" } else { Write-Error "Copy failed"; exit 1 }'
624
- ].join('\r\n');
625
- fs.writeFileSync(tmpScript, scriptContent, 'utf8');
626
- try {
627
- execSync(`powershell -NoProfile -ExecutionPolicy Bypass -File "${tmpScript}"`, {
628
- stdio: 'pipe', timeout: 30000
629
- });
630
- } finally {
631
- try { fs.unlinkSync(tmpScript); } catch (e) {}
632
- }
633
- } else {
634
- fs.copyFileSync(tempPath, targetPath);
635
- fs.chmodSync(targetPath, 0o755);
636
- }
690
+ execSync(`powershell -NoProfile -ExecutionPolicy Bypass -File "${tmpScript}"`, {
691
+ stdio: 'inherit',
692
+ timeout: 600000
693
+ });
694
+ } finally {
695
+ try { fs.unlinkSync(tmpScript); } catch (e) {}
696
+ }
697
+ } else {
698
+ let curlCmd = `curl -fsSL -o '${tempPath}'`;
699
+ if (source.auth === 'jihulab') {
700
+ curlCmd += ` -u '${JIHULAB_DEPLOY_TOKEN_USER}:${JIHULAB_DEPLOY_TOKEN}'`;
701
+ }
702
+ curlCmd += ` '${source.url}'`;
703
+ execSync(curlCmd, { stdio: 'pipe', timeout: 600000 });
704
+ fs.chmodSync(tempPath, 0o755);
705
+ }
706
+
707
+ // 验证临时文件
708
+ if (fs.existsSync(tempPath)) {
709
+ const tempStats = fs.statSync(tempPath);
710
+ if (tempStats.size > 10 * 1024 * 1024) {
711
+ console.log(` ✅ Downloaded from ${source.name}: ${(tempStats.size / (1024 * 1024)).toFixed(1)} MB`);
637
712
 
638
- // 验证替换成功
639
- if (fs.existsSync(targetPath)) {
640
- const targetStats = fs.statSync(targetPath);
641
- if (targetStats.size > 10 * 1024 * 1024) {
642
- fs.writeFileSync(path.join(installDir, '.version'), version);
643
- try { fs.unlinkSync(tempPath); } catch (e) {}
644
- return targetPath;
713
+ // 尝试替换目标文件
714
+ try {
715
+ if (platform === 'win32') {
716
+ const tmpScript = path.join(os.tmpdir(), 'sciagent-replace.ps1');
717
+ const scriptContent = [
718
+ `Copy-Item -Path "${tempPath}" -Destination "${targetPath}" -Force`,
719
+ 'if (Test-Path $out) { Write-Host "OK" } else { Write-Error "Copy failed"; exit 1 }'
720
+ ].join('\r\n');
721
+ fs.writeFileSync(tmpScript, scriptContent, 'utf8');
722
+ try {
723
+ execSync(`powershell -NoProfile -ExecutionPolicy Bypass -File "${tmpScript}"`, {
724
+ stdio: 'pipe', timeout: 30000
725
+ });
726
+ } finally {
727
+ try { fs.unlinkSync(tmpScript); } catch (e) {}
728
+ }
729
+ } else {
730
+ fs.copyFileSync(tempPath, targetPath);
731
+ fs.chmodSync(targetPath, 0o755);
645
732
  }
733
+
734
+ // 验证替换成功
735
+ if (fs.existsSync(targetPath)) {
736
+ const targetStats = fs.statSync(targetPath);
737
+ if (targetStats.size > 10 * 1024 * 1024) {
738
+ fs.writeFileSync(path.join(installDir, '.version'), version);
739
+ try { fs.unlinkSync(tempPath); } catch (e) {}
740
+ return targetPath;
741
+ }
742
+ }
743
+ } catch (replaceErr) {
744
+ // 替换失败(可能被锁定),写入 pending update
745
+ console.log(` ⚠️ Cannot replace running binary. Writing pending update...`);
746
+ const pendingData = {
747
+ version: version,
748
+ tempPath: tempPath,
749
+ timestamp: new Date().toISOString(),
750
+ platform: process.platform
751
+ };
752
+ fs.writeFileSync(path.join(installDir, '.pending-update'), JSON.stringify(pendingData, null, 2), 'utf8');
753
+ console.log(` 📋 Update will be applied on next restart.`);
754
+ return null;
646
755
  }
647
- } catch (replaceErr) {
648
- // 替换失败(可能被锁定),写入 pending update
649
- console.log(` ⚠️ Cannot replace running binary. Writing pending update...`);
650
- const pendingData = {
651
- version: version,
652
- tempPath: tempPath,
653
- timestamp: new Date().toISOString(),
654
- platform: process.platform
655
- };
656
- fs.writeFileSync(path.join(installDir, '.pending-update'), JSON.stringify(pendingData, null, 2), 'utf8');
657
- console.log(` 📋 Update will be applied on next restart.`);
658
- return null;
756
+ } else {
757
+ console.error(` ❌ Download from ${source.name} too small: ${(tempStats.size / 1024).toFixed(0)} KB`);
758
+ try { fs.unlinkSync(tempPath); } catch (e) {}
659
759
  }
660
760
  }
761
+ } catch (e) {
762
+ console.error(` ❌ Download from ${source.name} failed: ${e.message}`);
763
+ try { if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath); } catch (e2) {}
661
764
  }
662
- } catch (e) {
663
- console.error(` ❌ Download failed: ${e.message}`);
664
- try { if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath); } catch (e2) {}
665
765
  }
666
766
 
767
+ console.error(` ❌ All download mirrors failed.`);
667
768
  return null;
668
769
  }
669
770
 
670
- // 目标文件不存在(首次安装),直接下载到目标路径
771
+ // 目标文件不存在(首次安装),直接下载到目标路径,尝试多个源
671
772
  console.log(` Downloading ${filename} v${version}...`);
672
773
  console.log(` Target: ${targetPath}`);
673
774
 
674
- try {
675
- if (platform === 'win32') {
676
- // Windows: 使用 PowerShell 下载
677
- // 将URL和目标路径写入临时PS1脚本文件,避免命令行引号/特殊字符问题
678
- const tmpScript = path.join(os.tmpdir(), 'sciagent-download.ps1');
679
- const scriptContent = [
680
- '$ProgressPreference = "SilentlyContinue"',
681
- `$uri = "${downloadUrl}"`,
682
- `$out = "${targetPath}"`,
683
- 'Write-Host " Downloading from server..."',
684
- 'Invoke-WebRequest -Uri $uri -OutFile $out -UseBasicParsing',
685
- 'if (Test-Path $out) { $s = (Get-Item $out).Length; Write-Host " Downloaded: $s bytes" } else { Write-Error "File not created"; exit 1 }'
686
- ].join('\r\n');
687
- fs.writeFileSync(tmpScript, scriptContent, 'utf8');
688
-
689
- try {
690
- execSync(`powershell -NoProfile -ExecutionPolicy Bypass -File "${tmpScript}"`, {
691
- stdio: 'inherit',
692
- timeout: 600000
693
- });
694
- } finally {
695
- try { fs.unlinkSync(tmpScript); } catch (e) {}
696
- }
697
- } else {
698
- // Linux/macOS: 使用 curl
699
- execSync(`curl -fsSL -o '${targetPath}' '${downloadUrl}'`, {
700
- stdio: 'pipe',
701
- timeout: 600000
702
- });
703
- fs.chmodSync(targetPath, 0o755);
704
- }
775
+ for (const source of downloadUrls) {
776
+ console.log(` Trying ${source.name}...`);
705
777
 
706
- // 验证下载
707
- if (fs.existsSync(targetPath)) {
708
- const stats = fs.statSync(targetPath);
709
- if (stats.size > 10 * 1024 * 1024) {
710
- console.log(` ✅ Downloaded: ${(stats.size / (1024 * 1024)).toFixed(1)} MB`);
711
- // 写入版本文件
712
- fs.writeFileSync(path.join(installDir, '.version'), version);
713
- return targetPath;
778
+ try {
779
+ if (platform === 'win32') {
780
+ const tmpScript = path.join(os.tmpdir(), 'sciagent-download.ps1');
781
+ let scriptLines = [
782
+ '$ProgressPreference = "SilentlyContinue"',
783
+ `$uri = "${source.url}"`,
784
+ `$out = "${targetPath}"`,
785
+ 'Write-Host " Downloading..."',
786
+ ];
787
+
788
+ if (source.auth === 'jihulab') {
789
+ const b64 = Buffer.from(`${JIHULAB_DEPLOY_TOKEN_USER}:${JIHULAB_DEPLOY_TOKEN}`).toString('base64');
790
+ scriptLines.push(`$headers = @{ "Authorization" = "Basic ${b64}" }`);
791
+ scriptLines.push('Invoke-WebRequest -Uri $uri -OutFile $out -UseBasicParsing -Headers $headers');
792
+ } else {
793
+ scriptLines.push('Invoke-WebRequest -Uri $uri -OutFile $out -UseBasicParsing');
794
+ }
795
+ scriptLines.push('if (Test-Path $out) { $s = (Get-Item $out).Length; Write-Host " Downloaded: $s bytes" } else { Write-Error "File not created"; exit 1 }');
796
+
797
+ fs.writeFileSync(tmpScript, scriptLines.join('\r\n'), 'utf8');
798
+
799
+ try {
800
+ execSync(`powershell -NoProfile -ExecutionPolicy Bypass -File "${tmpScript}"`, {
801
+ stdio: 'inherit',
802
+ timeout: 600000
803
+ });
804
+ } finally {
805
+ try { fs.unlinkSync(tmpScript); } catch (e) {}
806
+ }
714
807
  } else {
715
- console.error(` ❌ Downloaded file too small: ${(stats.size / 1024).toFixed(0)} KB`);
716
- try { fs.unlinkSync(targetPath); } catch (e) {}
808
+ let curlCmd = `curl -fsSL -o '${targetPath}'`;
809
+ if (source.auth === 'jihulab') {
810
+ curlCmd += ` -u '${JIHULAB_DEPLOY_TOKEN_USER}:${JIHULAB_DEPLOY_TOKEN}'`;
811
+ }
812
+ curlCmd += ` '${source.url}'`;
813
+ execSync(curlCmd, { stdio: 'pipe', timeout: 600000 });
814
+ fs.chmodSync(targetPath, 0o755);
815
+ }
816
+
817
+ // 验证下载
818
+ if (fs.existsSync(targetPath)) {
819
+ const stats = fs.statSync(targetPath);
820
+ if (stats.size > 10 * 1024 * 1024) {
821
+ console.log(` ✅ Downloaded from ${source.name}: ${(stats.size / (1024 * 1024)).toFixed(1)} MB`);
822
+ // 写入版本文件
823
+ fs.writeFileSync(path.join(installDir, '.version'), version);
824
+ return targetPath;
825
+ } else {
826
+ console.error(` ❌ Download from ${source.name} too small: ${(stats.size / 1024).toFixed(0)} KB`);
827
+ try { fs.unlinkSync(targetPath); } catch (e) {}
828
+ }
717
829
  }
830
+ } catch (e) {
831
+ console.error(` ❌ Download from ${source.name} failed: ${e.message}`);
832
+ try { if (fs.existsSync(targetPath)) fs.unlinkSync(targetPath); } catch (e2) {}
718
833
  }
719
- } catch (e) {
720
- console.error(` ❌ Download failed: ${e.message}`);
721
- // 清理可能的部分下载
722
- try { if (fs.existsSync(targetPath)) fs.unlinkSync(targetPath); } catch (e2) {}
723
834
  }
724
835
 
836
+ console.error(` ❌ All download mirrors failed.`);
725
837
  return null;
726
838
  }
727
839
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sciagent/cli",
3
- "version": "1.1.50",
3
+ "version": "1.1.52",
4
4
  "description": "SciAgent CLI - AI Research Assistant",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -27,15 +27,25 @@ const ARCH_MAP = {
27
27
  };
28
28
 
29
29
  // 当前版本号 - 每次发布时同步更新
30
- const CURRENT_VERSION = '1.1.50';
30
+ const CURRENT_VERSION = '1.1.52';
31
31
 
32
32
  // GitHub Releases 回退版本列表(当当前版本的 release 不存在时尝试)
33
33
  const GITHUB_FALLBACK_VERSIONS = ['1.1.3', '1.0.50', '1.0.48', '1.0.46', '1.0.40', '1.0.38', '1.0.36'];
34
34
 
35
- // Releases 服务器配置
36
- // 优先使用环境变量,否则使用默认服务器
37
- const RELEASE_SERVER_URL = process.env.RELEASE_SERVER_URL ||
38
- 'https://sciagent.tech';
35
+ // JihuLab 通用包仓库配置(国内 CDN,速度快)
36
+ const JIHULAB_PROJECT_ID = '351778';
37
+ const JIHULAB_DEPLOY_TOKEN_USER = 'gitlab+deploy-token-15400';
38
+ const JIHULAB_DEPLOY_TOKEN = 'gldt-5kp7mgt4ztyyBMx1Uvn6';
39
+ const JIHULAB_BASE_URL = `https://jihulab.com/api/v4/projects/${JIHULAB_PROJECT_ID}/packages/generic/sciagent`;
40
+
41
+ // 自建服务器配置(备用源)
42
+ const SELF_HOSTED_URL = process.env.RELEASE_SERVER_URL || 'https://sciagent.tech';
43
+
44
+ // 下载源列表(按优先级排序)
45
+ const DOWNLOAD_MIRRORS = [
46
+ { name: 'JihuLab CDN', getUrl: (platform, arch, version, filename) => `${JIHULAB_BASE_URL}/${version}/${filename}`, auth: 'jihulab' },
47
+ { name: 'Self-hosted', getUrl: (platform, arch, version, filename) => `${SELF_HOSTED_URL}/api/releases/download/${platform}/${arch}/${version}`, auth: 'none' },
48
+ ];
39
49
 
40
50
  // PyPI 镜像源列表(国内优先)
41
51
  const PYPI_MIRRORS = [
@@ -185,7 +195,7 @@ function checkCodebuddyBinaryInstalled() {
185
195
  return { installed: false };
186
196
  }
187
197
 
188
- function downloadFile(url, destPath, timeout = 120000) {
198
+ function downloadFile(url, destPath, timeout = 120000, extraHeaders = {}) {
189
199
  return new Promise((resolve, reject) => {
190
200
  const protocol = url.startsWith('https') ? https : http;
191
201
 
@@ -221,7 +231,7 @@ function downloadFile(url, destPath, timeout = 120000) {
221
231
  }
222
232
 
223
233
  const request = protocol.get(requestUrl, {
224
- headers: { 'User-Agent': 'sciagent-cli/1.0' },
234
+ headers: { 'User-Agent': 'sciagent-cli/1.0', ...extraHeaders },
225
235
  timeout: 60000
226
236
  }, (response) => {
227
237
  // Handle redirects
@@ -513,7 +523,7 @@ sys.exit(1)
513
523
  fs.mkdirSync(binDir, { recursive: true });
514
524
  const binaryName = BINARY_NAMES[platform] || 'codebuddy-headless';
515
525
  const targetPath = path.join(binDir, binaryName);
516
- const downloadUrl = `${RELEASE_SERVER_URL}/api/releases/download/codebuddy/${platform}/${arch}/latest`;
526
+ const downloadUrl = `${SELF_HOSTED_URL}/api/releases/download/codebuddy/${platform}/${arch}/latest`;
517
527
 
518
528
  await downloadFile(downloadUrl, targetPath, 600000);
519
529
 
@@ -695,10 +705,6 @@ async function downloadFromServer(platform, arch, version) {
695
705
  const ext = platform === 'win32' ? '.exe' : '';
696
706
  const filename = `sciagent-${platform}-${arch}${ext}`;
697
707
 
698
- const downloadUrl = `${RELEASE_SERVER_URL}/api/releases/download/${platform}/${arch}/${version}`;
699
-
700
- console.log(`\n [Server] Downloading ${filename} v${version}...`);
701
-
702
708
  // 确定安装目录
703
709
  const installDir = getHomeBinDir();
704
710
  fs.mkdirSync(installDir, { recursive: true });
@@ -712,68 +718,98 @@ async function downloadFromServer(platform, arch, version) {
712
718
  // 清理旧的临时文件
713
719
  try { if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath); } catch (e) {}
714
720
 
715
- // Windows: 优先使用 PowerShell(更可靠的大文件下载)
716
- if (platform === 'win32') {
717
- try {
718
- const tmpScript = path.join(os.tmpdir(), 'sciagent-download.ps1');
719
- const scriptContent = [
720
- '$ProgressPreference = "SilentlyContinue"',
721
- `$uri = "${downloadUrl}"`,
722
- `$out = "${tempPath}"`,
723
- 'Write-Host " [Server] Downloading..."',
724
- 'Invoke-WebRequest -Uri $uri -OutFile $out -UseBasicParsing',
725
- 'if (Test-Path $out) { $s = (Get-Item $out).Length; Write-Host " [Server] Downloaded: $s bytes" } else { Write-Error "File not created"; exit 1 }'
726
- ].join('\r\n');
727
- fs.writeFileSync(tmpScript, scriptContent, 'utf8');
728
-
721
+ // 构建下载 URL 列表(按优先级)
722
+ const downloadUrls = DOWNLOAD_MIRRORS.map(mirror => ({
723
+ url: mirror.getUrl(platform, arch, version, filename),
724
+ name: mirror.name,
725
+ auth: mirror.auth
726
+ }));
727
+
728
+ for (const source of downloadUrls) {
729
+ console.log(`\n [${source.name}] Downloading ${filename} v${version}...`);
730
+
731
+ // Windows: 优先使用 PowerShell(更可靠的大文件下载)
732
+ if (platform === 'win32') {
729
733
  try {
730
- execSync(`powershell -NoProfile -ExecutionPolicy Bypass -File "${tmpScript}"`, {
731
- stdio: 'inherit',
732
- timeout: 600000
733
- });
734
- } finally {
735
- try { fs.unlinkSync(tmpScript); } catch (e) {}
734
+ const tmpScript = path.join(os.tmpdir(), 'sciagent-download.ps1');
735
+ let scriptLines = [
736
+ '$ProgressPreference = "SilentlyContinue"',
737
+ `$uri = "${source.url}"`,
738
+ `$out = "${tempPath}"`,
739
+ 'Write-Host " Downloading from ' + source.name + '..."',
740
+ ];
741
+
742
+ if (source.auth === 'jihulab') {
743
+ const b64 = Buffer.from(`${JIHULAB_DEPLOY_TOKEN_USER}:${JIHULAB_DEPLOY_TOKEN}`).toString('base64');
744
+ scriptLines.push(`$headers = @{ "Authorization" = "Basic ${b64}" }`);
745
+ // Use WebClient for large files - much faster than Invoke-WebRequest
746
+ scriptLines.push('$wc = New-Object System.Net.WebClient');
747
+ scriptLines.push('$wc.Headers.Add("Authorization", $headers["Authorization"])');
748
+ scriptLines.push('Write-Host " Using JihuLab CDN with auth..."');
749
+ scriptLines.push('$wc.DownloadFile($uri, $out)');
750
+ } else {
751
+ scriptLines.push('$wc = New-Object System.Net.WebClient');
752
+ scriptLines.push('Write-Host " Using ' + source.name + '..."');
753
+ scriptLines.push('$wc.DownloadFile($uri, $out)');
754
+ }
755
+ scriptLines.push('if (Test-Path $out) { $s = (Get-Item $out).Length; Write-Host " Downloaded: $s bytes ($([math]::Round($s/1MB,1)) MB)" } else { Write-Error "File not created"; exit 1 }');
756
+
757
+ fs.writeFileSync(tmpScript, scriptLines.join('\r\n'), 'utf8');
758
+
759
+ try {
760
+ execSync(`powershell -NoProfile -ExecutionPolicy Bypass -File "${tmpScript}"`, {
761
+ stdio: 'inherit',
762
+ timeout: 600000
763
+ });
764
+ } finally {
765
+ try { fs.unlinkSync(tmpScript); } catch (e) {}
766
+ }
767
+
768
+ // 验证下载到临时文件
769
+ if (fs.existsSync(tempPath)) {
770
+ const stats = fs.statSync(tempPath);
771
+ if (stats.size > 10 * 1024 * 1024) {
772
+ console.log(` [OK] Downloaded from ${source.name}: ${tempPath} (${(stats.size / (1024 * 1024)).toFixed(1)} MB)`);
773
+ // 尝试替换
774
+ return await applyDownloadedBinary(tempPath, targetPath, installDir, version, platform);
775
+ }
776
+ }
777
+
778
+ console.log(` [WARN] ${source.name} download validation failed, trying next...`);
779
+ } catch (e) {
780
+ console.log(` [WARN] ${source.name} download failed: ${e.message}, trying next...`);
736
781
  }
782
+ }
783
+
784
+ // 通用方式: Node.js https 下载到临时文件
785
+ try {
786
+ // 如果是 JihuLab,需要添加认证头
787
+ const headers = source.auth === 'jihulab'
788
+ ? { 'Authorization': `Basic ${Buffer.from(`${JIHULAB_DEPLOY_TOKEN_USER}:${JIHULAB_DEPLOY_TOKEN}`).toString('base64')}` }
789
+ : {};
790
+ await downloadFile(source.url, tempPath, 600000, headers);
737
791
 
738
- // 验证下载到临时文件
792
+ // 验证下载
739
793
  if (fs.existsSync(tempPath)) {
740
794
  const stats = fs.statSync(tempPath);
741
795
  if (stats.size > 10 * 1024 * 1024) {
742
- console.log(` [OK] Downloaded to temp: ${tempPath} (${(stats.size / (1024 * 1024)).toFixed(1)} MB)`);
796
+ console.log(` [OK] Downloaded from ${source.name}: ${tempPath} (${(stats.size / (1024 * 1024)).toFixed(1)} MB)`);
797
+ if (platform !== 'win32') {
798
+ fs.chmodSync(tempPath, 0o755);
799
+ }
743
800
  // 尝试替换
744
801
  return await applyDownloadedBinary(tempPath, targetPath, installDir, version, platform);
745
802
  }
746
803
  }
747
804
 
748
- console.log(` [WARN] PowerShell download validation failed, trying Node.js...`);
805
+ console.log(` [WARN] ${source.name} download validation failed, trying next...`);
749
806
  } catch (e) {
750
- console.log(` [WARN] PowerShell download failed: ${e.message}, trying Node.js...`);
807
+ console.log(` [WARN] ${source.name} download failed: ${e.message}, trying next...`);
751
808
  }
752
809
  }
753
810
 
754
- // 通用方式: Node.js https 下载到临时文件
755
- try {
756
- await downloadFile(downloadUrl, tempPath, 600000); // 10 分钟超时
757
-
758
- // 验证下载
759
- if (fs.existsSync(tempPath)) {
760
- const stats = fs.statSync(tempPath);
761
- if (stats.size > 10 * 1024 * 1024) {
762
- console.log(` [OK] Downloaded to temp: ${tempPath} (${(stats.size / (1024 * 1024)).toFixed(1)} MB)`);
763
- if (platform !== 'win32') {
764
- fs.chmodSync(tempPath, 0o755);
765
- }
766
- // 尝试替换
767
- return await applyDownloadedBinary(tempPath, targetPath, installDir, version, platform);
768
- }
769
- }
770
-
771
- console.log(` [WARN] Downloaded file validation failed`);
772
- return false;
773
- } catch (e) {
774
- console.log(` [WARN] Server download failed: ${e.message}`);
775
- return false;
776
- }
811
+ console.log(` [WARN] All download mirrors failed.`);
812
+ return false;
777
813
  }
778
814
 
779
815
  /**