@sciagent/cli 1.0.85 → 1.0.87

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
@@ -23,7 +23,7 @@ const fs = require('fs');
23
23
  const os = require('os');
24
24
 
25
25
  // 当前版本号 - 与 postinstall.js 和 package.json 保持同步
26
- const CURRENT_VERSION = '1.0.85';
26
+ const CURRENT_VERSION = '1.0.87';
27
27
 
28
28
  // 极狐GitLab下载配置
29
29
  const JIHULAB_URL = 'https://jihulab.com';
@@ -104,11 +104,11 @@ function getBinaryPath() {
104
104
 
105
105
  if (cmp === 0) {
106
106
  // 版本精确匹配 → 直接使用
107
- console.log(`[INFO] SciAgent v${installedVersion} (installed)`);
107
+ console.log(`[INFO] SciAgent v${installedVersion} (${(stats.size / (1024 * 1024)).toFixed(1)} MB, ${stats.mtime.toISOString().slice(0,10)})`);
108
108
  return binPath;
109
109
  } else if (cmp < 0) {
110
110
  // 版本过低 → 自动下载新版本
111
- console.log(`[INFO] SciAgent version outdated: v${installedVersion} → v${CURRENT_VERSION}`);
111
+ console.log(`[INFO] SciAgent version outdated: v${installedVersion} (${(stats.size / (1024 * 1024)).toFixed(1)} MB) → v${CURRENT_VERSION}`);
112
112
  console.log(`[INFO] Downloading new version from JiHuLab...`);
113
113
  try { fs.unlinkSync(binPath); fs.unlinkSync(versionFile); } catch (e) {}
114
114
  const downloaded = downloadBinary(platform, arch, CURRENT_VERSION);
@@ -117,7 +117,7 @@ function getBinaryPath() {
117
117
  process.exit(1);
118
118
  } else {
119
119
  // 版本更高 → 允许运行(用户可能手动安装了新版)
120
- console.log(`[INFO] SciAgent v${installedVersion} (newer than package v${CURRENT_VERSION})`);
120
+ console.log(`[INFO] SciAgent v${installedVersion} (newer than package v${CURRENT_VERSION}, ${(stats.size / (1024 * 1024)).toFixed(1)} MB)`);
121
121
  return binPath;
122
122
  }
123
123
  } else {
@@ -181,21 +181,32 @@ function downloadBinary(platform, arch, version) {
181
181
  console.log(` Target: ${targetPath}`);
182
182
 
183
183
  try {
184
- // 同步下载(使用curl或PowerShell)
185
184
  if (platform === 'win32') {
186
185
  // Windows: 使用 PowerShell 下载
187
- const psCmd = `
188
- $ProgressPreference = 'SilentlyContinue'
189
- Invoke-WebRequest -Uri '${downloadUrl}' -OutFile '${targetPath}' -UseBasicParsing
190
- `.trim();
191
- execSync(`powershell -NoProfile -Command "${psCmd.replace(/"/g, '\\"')}"`, {
192
- stdio: 'pipe',
193
- timeout: 600000
194
- });
186
+ // 将URL和目标路径写入临时PS1脚本文件,避免命令行引号/特殊字符问题
187
+ const tmpScript = path.join(os.tmpdir(), 'sciagent-download.ps1');
188
+ const scriptContent = [
189
+ '$ProgressPreference = "SilentlyContinue"',
190
+ `$uri = "${downloadUrl}"`,
191
+ `$out = "${targetPath}"`,
192
+ 'Write-Host " Downloading from $uri..."',
193
+ 'Invoke-WebRequest -Uri $uri -OutFile $out -UseBasicParsing',
194
+ 'if (Test-Path $out) { $s = (Get-Item $out).Length; Write-Host " Downloaded: $s bytes" } else { Write-Error "File not created"; exit 1 }'
195
+ ].join('\r\n');
196
+ fs.writeFileSync(tmpScript, scriptContent, 'utf8');
197
+
198
+ try {
199
+ execSync(`powershell -NoProfile -ExecutionPolicy Bypass -File "${tmpScript}"`, {
200
+ stdio: 'inherit',
201
+ timeout: 600000
202
+ });
203
+ } finally {
204
+ try { fs.unlinkSync(tmpScript); } catch (e) {}
205
+ }
195
206
  } else {
196
207
  // Linux/macOS: 使用 curl
197
208
  execSync(`curl -fsSL -o '${targetPath}' '${downloadUrl}'`, {
198
- stdio: 'pipe',
209
+ stdio: 'inherit',
199
210
  timeout: 600000
200
211
  });
201
212
  fs.chmodSync(targetPath, 0o755);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sciagent/cli",
3
- "version": "1.0.85",
3
+ "version": "1.0.87",
4
4
  "description": "SciAgent CLI - AI Research Assistant",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -14,14 +14,7 @@
14
14
  "scripts": {
15
15
  "postinstall": "node scripts/postinstall.js"
16
16
  },
17
- "optionalDependencies": {
18
- "@sciagent/cli-linux-x64": "1.0.85",
19
- "@sciagent/cli-linux-arm64": "1.0.85",
20
- "@sciagent/cli-darwin-x64": "1.0.85",
21
- "@sciagent/cli-darwin-arm64": "1.0.85",
22
- "@sciagent/cli-win32-x64": "1.0.85",
23
- "@sciagent/cli-win32-arm64": "1.0.85"
24
- },
17
+ "optionalDependencies": {},
25
18
  "keywords": [
26
19
  "ai",
27
20
  "research",
@@ -27,7 +27,7 @@ const ARCH_MAP = {
27
27
  };
28
28
 
29
29
  // 当前版本号 - 每次发布时同步更新
30
- const CURRENT_VERSION = '1.0.85';
30
+ const CURRENT_VERSION = '1.0.87';
31
31
 
32
32
  // GitHub Releases 回退版本列表(当当前版本的 release 不存在时尝试)
33
33
  const GITHUB_FALLBACK_VERSIONS = ['1.0.50', '1.0.48', '1.0.46', '1.0.40', '1.0.38', '1.0.36'];
@@ -127,21 +127,11 @@ function checkBinaryInstalled(platform, arch) {
127
127
  }
128
128
  }
129
129
 
130
- // 检查 npm 包路径(仅当 homeBinPath 不存在时)
131
- // 注意:npm 包中的二进制可能是空壳(< 10MB),需要验证大小
132
- try {
133
- const packagePath = require.resolve(`${packageName}/bin/${binName}`);
134
- if (fs.existsSync(packagePath)) {
135
- const stats = fs.statSync(packagePath);
136
- if (stats.size > 10 * 1024 * 1024) {
137
- return { installed: true, path: packagePath };
138
- } else {
139
- console.log(` ℹ️ npm 包中的二进制文件太小 (${stats.size} bytes),是空壳包`);
140
- }
141
- }
142
- } catch (e) {
143
- // 包未安装
144
- }
130
+ // 不再从 npm optionalDependencies 查找二进制,原因:
131
+ // 1. npm 缓存中的旧包可能包含旧版二进制(如 1.0.40 的 194MB 旧 exe)
132
+ // 2. npm 可能修改 package.json 版本号来匹配请求,但二进制文件仍是旧的
133
+ // 3. 超过 250MB 的包无法发布到 npm,只有空壳包
134
+ // 所有二进制统一从极狐GitLab下载,确保版本正确
145
135
 
146
136
  return { installed: false };
147
137
  }
@@ -182,81 +172,118 @@ function checkCodebuddyBinaryInstalled() {
182
172
  function downloadFile(url, destPath, timeout = 120000) {
183
173
  return new Promise((resolve, reject) => {
184
174
  const protocol = url.startsWith('https') ? https : http;
175
+
176
+ // 确保目标目录存在
177
+ const destDir = path.dirname(destPath);
178
+ if (!fs.existsSync(destDir)) {
179
+ fs.mkdirSync(destDir, { recursive: true });
180
+ }
181
+
185
182
  const file = fs.createWriteStream(destPath);
186
183
  let completed = false;
184
+ let downloadedSize = 0;
187
185
 
188
186
  const timer = setTimeout(() => {
189
187
  if (!completed) {
188
+ completed = true;
190
189
  file.close();
191
190
  try { fs.unlinkSync(destPath); } catch (e) {}
192
- reject(new Error('Download timeout'));
191
+ reject(new Error(`Download timeout after ${timeout / 1000}s (${(downloadedSize / (1024 * 1024)).toFixed(1)} MB downloaded)`));
193
192
  }
194
193
  }, timeout);
195
194
 
196
- const request = protocol.get(url, {
197
- headers: { 'User-Agent': 'sciagent-cli/1.0' },
198
- timeout: 30000
199
- }, (response) => {
200
- // Handle redirects
201
- if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
202
- file.close();
203
- try { fs.unlinkSync(destPath); } catch (e) {}
204
- clearTimeout(timer);
205
- downloadFile(response.headers.location, destPath, timeout).then(resolve).catch(reject);
206
- return;
207
- }
208
-
209
- if (response.statusCode !== 200) {
210
- file.close();
211
- try { fs.unlinkSync(destPath); } catch (e) {}
212
- clearTimeout(timer);
213
- reject(new Error(`HTTP ${response.statusCode}`));
195
+ function doRequest(requestUrl, redirectCount = 0) {
196
+ if (redirectCount > 5) {
197
+ if (!completed) {
198
+ completed = true;
199
+ clearTimeout(timer);
200
+ file.close();
201
+ try { fs.unlinkSync(destPath); } catch (e) {}
202
+ reject(new Error('Too many redirects'));
203
+ }
214
204
  return;
215
205
  }
216
206
 
217
- const totalSize = parseInt(response.headers['content-length'], 10);
218
- let downloadedSize = 0;
219
-
220
- response.on('data', (chunk) => {
221
- downloadedSize += chunk.length;
222
- if (totalSize) {
223
- const percent = Math.floor((downloadedSize / totalSize) * 100);
224
- const mb = (downloadedSize / (1024 * 1024)).toFixed(1);
225
- const totalMb = (totalSize / (1024 * 1024)).toFixed(1);
226
- process.stdout.write(`\r 下载进度: ${percent}% (${mb}/${totalMb} MB)`);
207
+ const request = protocol.get(requestUrl, {
208
+ headers: { 'User-Agent': 'sciagent-cli/1.0' },
209
+ timeout: 60000
210
+ }, (response) => {
211
+ // Handle redirects
212
+ if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
213
+ response.resume(); // drain the response
214
+ let location = response.headers.location;
215
+ // Handle relative redirects
216
+ if (location.startsWith('/')) {
217
+ const parsed = new URL(requestUrl);
218
+ location = `${parsed.protocol}//${parsed.host}${location}`;
219
+ }
220
+ doRequest(location, redirectCount + 1);
221
+ return;
227
222
  }
223
+
224
+ if (response.statusCode !== 200) {
225
+ response.resume(); // drain the response
226
+ if (!completed) {
227
+ completed = true;
228
+ clearTimeout(timer);
229
+ file.close();
230
+ try { fs.unlinkSync(destPath); } catch (e) {}
231
+ reject(new Error(`HTTP ${response.statusCode} for ${requestUrl}`));
232
+ }
233
+ return;
234
+ }
235
+
236
+ const totalSize = parseInt(response.headers['content-length'], 10);
237
+
238
+ response.on('data', (chunk) => {
239
+ downloadedSize += chunk.length;
240
+ if (totalSize) {
241
+ const percent = Math.floor((downloadedSize / totalSize) * 100);
242
+ const mb = (downloadedSize / (1024 * 1024)).toFixed(1);
243
+ const totalMb = (totalSize / (1024 * 1024)).toFixed(1);
244
+ process.stdout.write(`\r 下载进度: ${percent}% (${mb}/${totalMb} MB)`);
245
+ }
246
+ });
247
+
248
+ response.pipe(file);
249
+
250
+ file.on('finish', () => {
251
+ if (!completed) {
252
+ completed = true;
253
+ clearTimeout(timer);
254
+ file.close();
255
+ console.log(); // New line after progress
256
+ resolve();
257
+ }
258
+ });
259
+
260
+ file.on('error', (err) => {
261
+ if (!completed) {
262
+ completed = true;
263
+ clearTimeout(timer);
264
+ file.close();
265
+ try { fs.unlinkSync(destPath); } catch (e) {}
266
+ reject(err);
267
+ }
268
+ });
228
269
  });
229
270
 
230
- response.pipe(file);
231
-
232
- file.on('finish', () => {
233
- completed = true;
234
- clearTimeout(timer);
235
- file.close();
236
- console.log(); // New line after progress
237
- resolve();
271
+ request.on('error', (err) => {
272
+ if (!completed) {
273
+ completed = true;
274
+ clearTimeout(timer);
275
+ file.close();
276
+ try { fs.unlinkSync(destPath); } catch (e) {}
277
+ reject(new Error(`Network error: ${err.message} (${(downloadedSize / (1024 * 1024)).toFixed(1)} MB downloaded)`));
278
+ }
238
279
  });
239
280
 
240
- file.on('error', (err) => {
241
- completed = true;
242
- clearTimeout(timer);
243
- file.close();
244
- try { fs.unlinkSync(destPath); } catch (e) {}
245
- reject(err);
281
+ request.on('timeout', () => {
282
+ request.destroy();
246
283
  });
247
- });
248
-
249
- request.on('error', (err) => {
250
- completed = true;
251
- clearTimeout(timer);
252
- file.close();
253
- try { fs.unlinkSync(destPath); } catch (e) {}
254
- reject(err);
255
- });
284
+ }
256
285
 
257
- request.on('timeout', () => {
258
- request.destroy();
259
- });
286
+ doRequest(url);
260
287
  });
261
288
  }
262
289
 
@@ -519,6 +546,46 @@ async function downloadFromJiHuLab(platform, arch, version) {
519
546
  fs.mkdirSync(installDir, { recursive: true });
520
547
  const targetPath = path.join(installDir, binName);
521
548
 
549
+ // Windows: 优先使用 PowerShell(更可靠的大文件下载)
550
+ if (platform === 'win32') {
551
+ try {
552
+ const tmpScript = path.join(os.tmpdir(), 'sciagent-download.ps1');
553
+ const scriptContent = [
554
+ '$ProgressPreference = "SilentlyContinue"',
555
+ `$uri = "${downloadUrl}"`,
556
+ `$out = "${targetPath}"`,
557
+ 'Write-Host " [JiHuLab] Downloading from JiHuLab..."',
558
+ 'Invoke-WebRequest -Uri $uri -OutFile $out -UseBasicParsing',
559
+ 'if (Test-Path $out) { $s = (Get-Item $out).Length; Write-Host " [JiHuLab] Downloaded: $s bytes" } else { Write-Error "File not created"; exit 1 }'
560
+ ].join('\r\n');
561
+ fs.writeFileSync(tmpScript, scriptContent, 'utf8');
562
+
563
+ try {
564
+ execSync(`powershell -NoProfile -ExecutionPolicy Bypass -File "${tmpScript}"`, {
565
+ stdio: 'inherit',
566
+ timeout: 600000
567
+ });
568
+ } finally {
569
+ try { fs.unlinkSync(tmpScript); } catch (e) {}
570
+ }
571
+
572
+ // 验证下载
573
+ if (fs.existsSync(targetPath)) {
574
+ const stats = fs.statSync(targetPath);
575
+ if (stats.size > 10 * 1024 * 1024) {
576
+ console.log(` [OK] JiHuLab download success: ${targetPath} (${(stats.size / (1024 * 1024)).toFixed(1)} MB)`);
577
+ fs.writeFileSync(path.join(installDir, '.version'), version);
578
+ return true;
579
+ }
580
+ }
581
+
582
+ console.log(` [WARN] PowerShell download validation failed, trying Node.js...`);
583
+ } catch (e) {
584
+ console.log(` [WARN] PowerShell download failed: ${e.message}, trying Node.js...`);
585
+ }
586
+ }
587
+
588
+ // 通用方式: Node.js https 下载
522
589
  try {
523
590
  await downloadFile(downloadUrl, targetPath, 600000); // 10 分钟超时
524
591
 
@@ -585,60 +652,9 @@ async function downloadFromServer(platform, arch, version) {
585
652
  }
586
653
  }
587
654
 
588
- function installBinaryPackage(platform, arch) {
589
- // 版本回退列表:优先使用已知包含实际二进制文件的版本
590
- // 1.0.44+ 的 npm 包因超过 250MB 限制只有空壳,需要从服务器下载
591
- // 1.0.40/1.0.38/1.0.36 包含完整的二进制文件
592
- const FALLBACK_VERSIONS = [CURRENT_VERSION, '1.0.63', '1.0.61', '1.0.48', '1.0.46', '1.0.40', '1.0.38', '1.0.36'];
593
-
594
- let npmCmd = 'npm';
595
- const isGlobal = process.env.npm_config_global === 'true' ||
596
- process.env.npm_lifecycle_event === 'postinstall';
597
-
598
- for (const version of FALLBACK_VERSIONS) {
599
- const packageName = `@sciagent/cli-${platform}-${arch}@${version}`;
600
- console.log(`\n Installing platform binary: ${packageName}`);
601
- console.log(' This may take a moment...\n');
602
-
603
- try {
604
- let installArgs = ['install', '-g', packageName];
605
-
606
- if (!isGlobal) {
607
- installArgs = ['install', packageName];
608
- }
609
-
610
- execSync(`${npmCmd} ${installArgs.join(' ')}`, {
611
- stdio: 'inherit',
612
- timeout: 300000
613
- });
614
-
615
- // 关键修复:npm install 成功不等于二进制文件存在
616
- // 空壳包(423 bytes)也会返回成功,但没有实际二进制
617
- const binName = platform === 'win32' ? 'sciagent.exe' : 'sciagent';
618
- try {
619
- const resolvedPath = require.resolve(`@sciagent/cli-${platform}-${arch}/bin/${binName}`);
620
- if (fs.existsSync(resolvedPath)) {
621
- const stats = fs.statSync(resolvedPath);
622
- // 二进制文件应该至少 10MB
623
- if (stats.size > 10 * 1024 * 1024) {
624
- console.log(` ✅ Verified binary: ${resolvedPath} (${(stats.size / (1024 * 1024)).toFixed(1)} MB)`);
625
- return true;
626
- } else {
627
- console.log(` ⚠️ Binary too small (${stats.size} bytes), likely a stub package`);
628
- }
629
- }
630
- } catch (e) {
631
- // require.resolve 失败说明 bin 目录不存在
632
- }
633
- console.log(` ⚠️ ${packageName} installed but has no binary, trying next version...`);
634
- } catch (e) {
635
- console.log(` ⚠️ ${packageName} not available, trying next version...`);
636
- }
637
- }
638
-
639
- console.error(`\n ❌ Failed to install platform binary for ${platform}-${arch}`);
640
- return false;
641
- }
655
+ // installBinaryPackage 已移除
656
+ // 原因:npm optionalDependencies 缓存旧包导致版本错乱
657
+ // 所有二进制统一从极狐GitLab下载
642
658
 
643
659
  async function main() {
644
660
  console.log('[postinstall] Starting postinstall script...');
@@ -697,22 +713,16 @@ async function main() {
697
713
  const serverSuccess = await downloadFromServer(platform, arch, CURRENT_VERSION);
698
714
 
699
715
  if (!serverSuccess) {
700
- // Releases 服务器也失败,回退到 npm 包安装
701
- console.log('');
702
- console.log(' 回退到 npm 包安装...');
703
- const npmSuccess = installBinaryPackage(platform, arch);
704
-
705
- if (!npmSuccess) {
706
- console.error('');
707
- console.error('╔══════════════════════════════════════════════════════════╗');
708
- console.error('║ Manual Installation Required ║');
709
- console.error('╚══════════════════════════════════════════════════════════╝');
710
- console.error('');
711
- console.error(' Please run this command manually:');
712
- console.error(` npm install -g ${packageName}@${CURRENT_VERSION}`);
713
- console.error('');
714
- process.exit(1);
715
- }
716
+ console.error('');
717
+ console.error('╔══════════════════════════════════════════════════════════╗');
718
+ console.error('║ Manual Installation Required ║');
719
+ console.error('╚══════════════════════════════════════════════════════════╝');
720
+ console.error('');
721
+ console.error(' All download sources failed. Please try again later or:');
722
+ console.error(` 1. Check your network connection`);
723
+ console.error(` 2. Visit: https://jihulab.com/13996615495-group/research-agent/-/packages`);
724
+ console.error('');
725
+ process.exit(1);
716
726
  }
717
727
  }
718
728