@0xobelisk/sui-cli 1.2.0-pre.40 → 1.2.0-pre.41

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.
@@ -1,10 +1,12 @@
1
1
  import type { CommandModule } from 'yargs';
2
2
  import chalk from 'chalk';
3
3
  import { spawn } from 'child_process';
4
- import { existsSync } from 'fs';
5
- import { homedir } from 'os';
6
- import { join } from 'path';
7
4
  import Table from 'cli-table3';
5
+ import inquirer from 'inquirer';
6
+ import * as cliProgress from 'cli-progress';
7
+ import * as fs from 'fs';
8
+ import * as path from 'path';
9
+ import * as os from 'os';
8
10
 
9
11
  // Check result type
10
12
  interface CheckResult {
@@ -14,6 +16,90 @@ interface CheckResult {
14
16
  fixSuggestion?: string;
15
17
  }
16
18
 
19
+ // GitHub Release type
20
+ interface GitHubRelease {
21
+ tag_name: string;
22
+ name: string;
23
+ assets: Array<{
24
+ name: string;
25
+ browser_download_url: string;
26
+ }>;
27
+ published_at: string;
28
+ }
29
+
30
+ // Tool configuration
31
+ interface ToolConfig {
32
+ name: string;
33
+ repo: string;
34
+ binaryName: string;
35
+ installDir: string;
36
+ }
37
+
38
+ // System information
39
+ interface SystemInfo {
40
+ platform: string;
41
+ arch: string;
42
+ platformForAsset: string;
43
+ archForAsset: string;
44
+ }
45
+
46
+ // Get system information
47
+ function getSystemInfo(): SystemInfo {
48
+ const platform = process.platform;
49
+ const arch = process.arch;
50
+
51
+ let platformForAsset: string;
52
+ let archForAsset: string;
53
+
54
+ switch (platform) {
55
+ case 'darwin':
56
+ platformForAsset = 'macos';
57
+ break;
58
+ case 'win32':
59
+ platformForAsset = 'windows';
60
+ break;
61
+ case 'linux':
62
+ platformForAsset = 'ubuntu';
63
+ break;
64
+ default:
65
+ platformForAsset = platform;
66
+ }
67
+
68
+ switch (arch) {
69
+ case 'x64':
70
+ archForAsset = 'x86_64';
71
+ break;
72
+ case 'arm64':
73
+ archForAsset = 'aarch64';
74
+ break;
75
+ default:
76
+ archForAsset = arch;
77
+ }
78
+
79
+ return {
80
+ platform,
81
+ arch,
82
+ platformForAsset,
83
+ archForAsset
84
+ };
85
+ }
86
+
87
+ // Tool configurations
88
+ const TOOL_CONFIGS: Record<string, ToolConfig> = {
89
+ sui: {
90
+ name: 'sui',
91
+ repo: 'MystenLabs/sui',
92
+ binaryName: 'sui',
93
+ installDir: path.join(os.homedir(), '.dubhe', 'bin')
94
+ },
95
+ 'dubhe-indexer': {
96
+ name: 'dubhe-indexer',
97
+ repo: '0xobelisk/dubhe-wip',
98
+ binaryName: 'dubhe-indexer',
99
+ installDir: path.join(os.homedir(), '.dubhe', 'bin')
100
+ }
101
+ };
102
+
17
103
  // Execute shell command and return output
18
104
  async function execCommand(
19
105
  command: string,
@@ -42,6 +128,605 @@ async function execCommand(
42
128
  });
43
129
  }
44
130
 
131
+ // Download file with progress bar
132
+ async function downloadFileWithProgress(url: string, outputPath: string): Promise<void> {
133
+ const response = await fetch(url, {
134
+ headers: {
135
+ 'User-Agent': 'dubhe-cli'
136
+ }
137
+ });
138
+
139
+ if (!response.ok) {
140
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
141
+ }
142
+
143
+ const contentLength = response.headers.get('content-length');
144
+ const totalSize = contentLength ? parseInt(contentLength) : 0;
145
+
146
+ // 创建进度条
147
+ const progressBar = new cliProgress.SingleBar({
148
+ format:
149
+ chalk.cyan('Download Progress') +
150
+ ' |{bar}| {percentage}% | {value}/{total} MB | Speed: {speed} MB/s | ETA: {eta}s',
151
+ barCompleteChar: '\u2588',
152
+ barIncompleteChar: '\u2591',
153
+ hideCursor: true,
154
+ barsize: 30,
155
+ forceRedraw: true
156
+ });
157
+
158
+ if (totalSize > 0) {
159
+ progressBar.start(Math.round((totalSize / 1024 / 1024) * 100) / 100, 0, {
160
+ speed: '0.00'
161
+ });
162
+ } else {
163
+ console.log(chalk.blue('📥 Downloading... (unable to get file size)'));
164
+ }
165
+
166
+ const reader = response.body?.getReader();
167
+ if (!reader) {
168
+ throw new Error('Unable to get response stream');
169
+ }
170
+
171
+ const chunks: Uint8Array[] = [];
172
+ let downloadedBytes = 0;
173
+ const startTime = Date.now();
174
+
175
+ try {
176
+ while (true) {
177
+ const { done, value } = await reader.read();
178
+
179
+ if (done) break;
180
+
181
+ chunks.push(value);
182
+ downloadedBytes += value.length;
183
+
184
+ // 更新进度条
185
+ if (totalSize > 0) {
186
+ const downloadedMB = Math.round((downloadedBytes / 1024 / 1024) * 100) / 100;
187
+ const elapsedTime = (Date.now() - startTime) / 1000;
188
+ const speed = elapsedTime > 0 ? Math.round((downloadedMB / elapsedTime) * 100) / 100 : 0;
189
+
190
+ progressBar.update(downloadedMB, {
191
+ speed: speed.toFixed(2)
192
+ });
193
+ }
194
+ }
195
+
196
+ // 合并所有 chunks
197
+ const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);
198
+ const buffer = new Uint8Array(totalLength);
199
+ let offset = 0;
200
+
201
+ for (const chunk of chunks) {
202
+ buffer.set(chunk, offset);
203
+ offset += chunk.length;
204
+ }
205
+
206
+ // 写入文件
207
+ fs.writeFileSync(outputPath, buffer);
208
+
209
+ if (totalSize > 0) {
210
+ progressBar.stop();
211
+ }
212
+
213
+ const totalMB = Math.round((downloadedBytes / 1024 / 1024) * 100) / 100;
214
+ const elapsedTime = (Date.now() - startTime) / 1000;
215
+ const avgSpeed = elapsedTime > 0 ? Math.round((totalMB / elapsedTime) * 100) / 100 : 0;
216
+
217
+ console.log(
218
+ chalk.green(`✓ Download completed! ${totalMB} MB, average speed: ${avgSpeed} MB/s`)
219
+ );
220
+ } catch (error) {
221
+ if (totalSize > 0) {
222
+ progressBar.stop();
223
+ }
224
+ throw error;
225
+ }
226
+ }
227
+
228
+ // Fetch GitHub releases with retry
229
+ async function fetchGitHubReleases(
230
+ repo: string,
231
+ count: number = 10,
232
+ retries: number = 3
233
+ ): Promise<GitHubRelease[]> {
234
+ const url = `https://api.github.com/repos/${repo}/releases?per_page=${count}`;
235
+
236
+ for (let attempt = 1; attempt <= retries; attempt++) {
237
+ try {
238
+ if (attempt > 1) {
239
+ console.log(chalk.gray(` Retry ${attempt}/${retries}...`));
240
+ }
241
+
242
+ const response = await fetch(url, {
243
+ headers: {
244
+ 'User-Agent': 'dubhe-cli',
245
+ Accept: 'application/vnd.github.v3+json'
246
+ }
247
+ });
248
+
249
+ if (!response.ok) {
250
+ if (response.status === 403) {
251
+ throw new Error(
252
+ `GitHub API rate limit: ${response.status}. Please retry later or set GITHUB_TOKEN environment variable`
253
+ );
254
+ }
255
+ throw new Error(`GitHub API request failed: ${response.status} ${response.statusText}`);
256
+ }
257
+
258
+ const releases = await response.json();
259
+ return releases;
260
+ } catch (error) {
261
+ if (attempt > 1) {
262
+ console.log(
263
+ chalk.yellow(
264
+ ` ⚠️ Attempt ${attempt} failed: ${error instanceof Error ? error.message : String(error)}`
265
+ )
266
+ );
267
+ }
268
+
269
+ if (attempt === retries) {
270
+ console.error(chalk.red(` ❌ Failed to fetch releases after ${retries} attempts`));
271
+ return [];
272
+ }
273
+
274
+ // Wait 1 second before retry
275
+ await new Promise((resolve) => setTimeout(resolve, 1000 * attempt));
276
+ }
277
+ }
278
+
279
+ return [];
280
+ }
281
+
282
+ // Find compatible assets for current system
283
+ function findCompatibleAssets(release: GitHubRelease, systemInfo: SystemInfo): string[] {
284
+ const assets = release.assets.filter((asset) => {
285
+ const name = asset.name.toLowerCase();
286
+
287
+ // Platform matching with various aliases
288
+ const platformVariants = [
289
+ systemInfo.platformForAsset.toLowerCase(),
290
+ ...(systemInfo.platformForAsset === 'macos' ? ['darwin', 'apple'] : []),
291
+ ...(systemInfo.platformForAsset === 'windows' ? ['win', 'win32', 'windows'] : []),
292
+ ...(systemInfo.platformForAsset === 'ubuntu' ? ['linux', 'gnu'] : [])
293
+ ];
294
+
295
+ // Architecture matching with various aliases
296
+ const archVariants = [
297
+ systemInfo.archForAsset.toLowerCase(),
298
+ ...(systemInfo.archForAsset === 'x86_64' ? ['amd64', 'x64'] : []),
299
+ ...(systemInfo.archForAsset === 'aarch64' ? ['arm64'] : [])
300
+ ];
301
+
302
+ const platformMatch = platformVariants.some((variant) => name.includes(variant));
303
+ const archMatch = archVariants.some((variant) => name.includes(variant));
304
+
305
+ // Check for archive formats
306
+ const isArchive =
307
+ name.endsWith('.tar.gz') ||
308
+ name.endsWith('.zip') ||
309
+ name.endsWith('.tgz') ||
310
+ name.endsWith('.tar.bz2') ||
311
+ name.endsWith('.tar.xz');
312
+
313
+ return platformMatch && archMatch && isArchive;
314
+ });
315
+
316
+ return assets.map((asset) => asset.name);
317
+ }
318
+
319
+ // Get available versions for a tool
320
+ async function getAvailableVersions(
321
+ toolName: string,
322
+ systemInfo: SystemInfo
323
+ ): Promise<Array<{ version: string; hasCompatibleAsset: boolean }>> {
324
+ const config = TOOL_CONFIGS[toolName];
325
+ if (!config) return [];
326
+
327
+ const releases = await fetchGitHubReleases(config.repo, 10);
328
+
329
+ return releases.map((release) => ({
330
+ version: release.tag_name,
331
+ hasCompatibleAsset: findCompatibleAssets(release, systemInfo).length > 0
332
+ }));
333
+ }
334
+
335
+ // Auto-add PATH to shell configuration file
336
+ async function autoAddToShellConfig(installDir: string): Promise<void> {
337
+ try {
338
+ // Detect current shell
339
+ const shell = detectCurrentShell();
340
+ if (!shell) {
341
+ console.log(chalk.gray(`Please add to PATH: export PATH="$PATH:${installDir}"`));
342
+ return;
343
+ }
344
+
345
+ const { shellName, configFile } = shell;
346
+ const pathCommand =
347
+ shellName === 'fish'
348
+ ? `set -gx PATH $PATH ${installDir}`
349
+ : `export PATH="$PATH:${installDir}"`;
350
+
351
+ // Check if PATH is already added
352
+ if (fs.existsSync(configFile)) {
353
+ const content = fs.readFileSync(configFile, 'utf8');
354
+ if (content.includes(installDir)) {
355
+ console.log(chalk.green(` ✓ PATH already configured in ${configFile}`));
356
+ return;
357
+ }
358
+ }
359
+
360
+ // Add PATH to configuration file
361
+ const comment = shellName === 'fish' ? '# Added by dubhe doctor' : '# Added by dubhe doctor';
362
+ const pathLine = `${comment}\n${pathCommand}`;
363
+
364
+ fs.appendFileSync(configFile, `\n${pathLine}\n`);
365
+
366
+ console.log(chalk.green(` ✓ Automatically added to PATH in ${configFile}`));
367
+ console.log(chalk.blue(` 📝 To apply changes: source ${configFile} or restart terminal`));
368
+ } catch (error) {
369
+ console.log(
370
+ chalk.yellow(
371
+ ` ⚠️ Could not auto-configure PATH: ${error instanceof Error ? error.message : String(error)}`
372
+ )
373
+ );
374
+ console.log(chalk.gray(` Please manually add to PATH: export PATH="$PATH:${installDir}"`));
375
+ }
376
+ }
377
+
378
+ // Detect current shell and return shell info
379
+ function detectCurrentShell(): { shellName: string; configFile: string } | null {
380
+ const homeDir = os.homedir();
381
+
382
+ // Method 1: Check SHELL environment variable
383
+ const shellEnv = process.env.SHELL;
384
+ if (shellEnv) {
385
+ if (shellEnv.includes('zsh')) {
386
+ return {
387
+ shellName: 'zsh',
388
+ configFile: path.join(homeDir, '.zshrc')
389
+ };
390
+ } else if (shellEnv.includes('bash')) {
391
+ // On macOS, prefer .bash_profile, on Linux prefer .bashrc
392
+ const bashProfile = path.join(homeDir, '.bash_profile');
393
+ const bashrc = path.join(homeDir, '.bashrc');
394
+ return {
395
+ shellName: 'bash',
396
+ configFile:
397
+ process.platform === 'darwin' && fs.existsSync(bashProfile) ? bashProfile : bashrc
398
+ };
399
+ } else if (shellEnv.includes('fish')) {
400
+ const fishConfigDir = path.join(homeDir, '.config', 'fish');
401
+ if (!fs.existsSync(fishConfigDir)) {
402
+ fs.mkdirSync(fishConfigDir, { recursive: true });
403
+ }
404
+ return {
405
+ shellName: 'fish',
406
+ configFile: path.join(fishConfigDir, 'config.fish')
407
+ };
408
+ }
409
+ }
410
+
411
+ // Method 2: Check which config files exist
412
+ const possibleConfigs = [
413
+ { name: 'zsh', file: path.join(homeDir, '.zshrc') },
414
+ {
415
+ name: 'bash',
416
+ file:
417
+ process.platform === 'darwin'
418
+ ? path.join(homeDir, '.bash_profile')
419
+ : path.join(homeDir, '.bashrc')
420
+ },
421
+ { name: 'bash', file: path.join(homeDir, '.bashrc') }
422
+ ];
423
+
424
+ for (const config of possibleConfigs) {
425
+ if (fs.existsSync(config.file)) {
426
+ return {
427
+ shellName: config.name,
428
+ configFile: config.file
429
+ };
430
+ }
431
+ }
432
+
433
+ // Method 3: Try to create default based on common patterns
434
+ if (process.env.ZSH || fs.existsSync('/bin/zsh')) {
435
+ return {
436
+ shellName: 'zsh',
437
+ configFile: path.join(homeDir, '.zshrc')
438
+ };
439
+ }
440
+
441
+ return null;
442
+ }
443
+
444
+ // Download and install tool
445
+ async function downloadAndInstallTool(toolName: string, version?: string): Promise<boolean> {
446
+ const config = TOOL_CONFIGS[toolName];
447
+ if (!config) {
448
+ console.error(`Unknown tool: ${toolName}`);
449
+ return false;
450
+ }
451
+
452
+ const systemInfo = getSystemInfo();
453
+ console.log(chalk.gray(` System: ${systemInfo.platform}/${systemInfo.arch}`));
454
+
455
+ try {
456
+ // Fetch releases
457
+ console.log(chalk.gray(` Fetching release information...`));
458
+ const releases = await fetchGitHubReleases(config.repo, 10);
459
+ if (releases.length === 0) {
460
+ console.error(chalk.red(` ❌ Unable to fetch releases for ${config.name}`));
461
+ return false;
462
+ }
463
+
464
+ let selectedRelease: GitHubRelease | null = null;
465
+
466
+ if (version) {
467
+ // Find specific version
468
+ selectedRelease = releases.find((r) => r.tag_name === version) || null;
469
+ if (!selectedRelease) {
470
+ console.error(`Version ${version} not found`);
471
+ return false;
472
+ }
473
+ } else {
474
+ // Find latest compatible version
475
+ for (const release of releases) {
476
+ const compatibleAssets = findCompatibleAssets(release, systemInfo);
477
+ if (compatibleAssets.length > 0) {
478
+ selectedRelease = release;
479
+ break;
480
+ }
481
+ }
482
+ }
483
+
484
+ if (!selectedRelease) {
485
+ console.error(`No compatible version found`);
486
+ return false;
487
+ }
488
+
489
+ // Find compatible asset
490
+ const compatibleAssets = findCompatibleAssets(selectedRelease, systemInfo);
491
+ if (compatibleAssets.length === 0) {
492
+ console.error(`Version ${selectedRelease.tag_name} has no compatible binaries`);
493
+ return false;
494
+ }
495
+
496
+ const assetName = compatibleAssets[0];
497
+ const asset = selectedRelease.assets.find((a) => a.name === assetName);
498
+ if (!asset) {
499
+ console.error(`Asset file not found: ${assetName}`);
500
+ return false;
501
+ }
502
+
503
+ console.log(chalk.green(` ✓ Found compatible version: ${selectedRelease.tag_name}`));
504
+ console.log(chalk.gray(` Download file: ${asset.name}`));
505
+
506
+ // Verify download link
507
+ try {
508
+ const headResponse = await fetch(asset.browser_download_url, {
509
+ method: 'HEAD',
510
+ headers: { 'User-Agent': 'dubhe-cli' }
511
+ });
512
+ if (!headResponse.ok) {
513
+ console.log(
514
+ chalk.yellow(` ⚠️ Warning: Unable to access download file (${headResponse.status})`)
515
+ );
516
+ } else {
517
+ const fileSize = headResponse.headers.get('content-length');
518
+ if (fileSize) {
519
+ console.log(
520
+ chalk.gray(
521
+ ` File size: ${Math.round((parseInt(fileSize) / 1024 / 1024) * 100) / 100} MB`
522
+ )
523
+ );
524
+ }
525
+ }
526
+ } catch (error) {
527
+ console.log(chalk.yellow(` ⚠️ Warning: Unable to verify download file`));
528
+ }
529
+
530
+ // Create install directory
531
+ if (!fs.existsSync(config.installDir)) {
532
+ fs.mkdirSync(config.installDir, { recursive: true });
533
+ console.log(chalk.gray(` Created install directory: ${config.installDir}`));
534
+ }
535
+
536
+ // Download file with retry and progress bar
537
+ console.log(chalk.blue(` 📥 Downloading...`));
538
+
539
+ const tempFile = path.join(os.tmpdir(), asset.name);
540
+ const maxRetries = 3;
541
+
542
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
543
+ try {
544
+ if (attempt > 1) {
545
+ console.log(chalk.gray(` Attempt ${attempt} to download...`));
546
+ }
547
+
548
+ await downloadFileWithProgress(asset.browser_download_url, tempFile);
549
+ break;
550
+ } catch (error) {
551
+ const errorMsg = error instanceof Error ? error.message : String(error);
552
+ console.log(chalk.yellow(` ⚠️ Download failed (attempt ${attempt}): ${errorMsg}`));
553
+
554
+ if (attempt === maxRetries) {
555
+ throw new Error(`Download failed after ${maxRetries} attempts: ${errorMsg}`);
556
+ }
557
+
558
+ // Wait before retry
559
+ console.log(chalk.gray(` Waiting ${attempt * 2} seconds before retry...`));
560
+ await new Promise((resolve) => setTimeout(resolve, attempt * 2000));
561
+ }
562
+ }
563
+
564
+ // Extract and install
565
+ console.log(chalk.blue(' 📦 Extracting and installing...'));
566
+
567
+ const extractDir = path.join(os.tmpdir(), `extract_${Date.now()}`);
568
+ fs.mkdirSync(extractDir, { recursive: true });
569
+
570
+ if (asset.name.endsWith('.tar.gz') || asset.name.endsWith('.tgz')) {
571
+ // Extract tar.gz / tgz
572
+ const tarResult = await execCommand('tar', ['-xzf', tempFile, '-C', extractDir]);
573
+ if (tarResult.code !== 0) {
574
+ throw new Error(`Extraction failed: ${tarResult.stderr}`);
575
+ }
576
+ } else if (asset.name.endsWith('.tar.bz2')) {
577
+ // Extract tar.bz2
578
+ const tarResult = await execCommand('tar', ['-xjf', tempFile, '-C', extractDir]);
579
+ if (tarResult.code !== 0) {
580
+ throw new Error(`Extraction failed: ${tarResult.stderr}`);
581
+ }
582
+ } else if (asset.name.endsWith('.tar.xz')) {
583
+ // Extract tar.xz
584
+ const tarResult = await execCommand('tar', ['-xJf', tempFile, '-C', extractDir]);
585
+ if (tarResult.code !== 0) {
586
+ throw new Error(`Extraction failed: ${tarResult.stderr}`);
587
+ }
588
+ } else if (asset.name.endsWith('.zip')) {
589
+ // Extract zip (requires unzip command)
590
+ const unzipResult = await execCommand('unzip', ['-q', tempFile, '-d', extractDir]);
591
+ if (unzipResult.code !== 0) {
592
+ throw new Error(`Extraction failed: ${unzipResult.stderr}`);
593
+ }
594
+ } else {
595
+ throw new Error(`Unsupported compression format: ${asset.name}`);
596
+ }
597
+
598
+ // Find binary file
599
+ const findBinary = (dir: string): string | null => {
600
+ const files = fs.readdirSync(dir, { withFileTypes: true });
601
+ for (const file of files) {
602
+ const fullPath = path.join(dir, file.name);
603
+ if (file.isDirectory()) {
604
+ const result = findBinary(fullPath);
605
+ if (result) return result;
606
+ } else if (file.name === config.binaryName || file.name === `${config.binaryName}.exe`) {
607
+ return fullPath;
608
+ }
609
+ }
610
+ return null;
611
+ };
612
+
613
+ const binaryPath = findBinary(extractDir);
614
+ if (!binaryPath) {
615
+ throw new Error(`Cannot find ${config.binaryName} binary in extracted files`);
616
+ }
617
+
618
+ // Copy binary to install directory
619
+ const targetPath = path.join(
620
+ config.installDir,
621
+ config.binaryName + (process.platform === 'win32' ? '.exe' : '')
622
+ );
623
+ fs.copyFileSync(binaryPath, targetPath);
624
+
625
+ // Make executable on Unix systems
626
+ if (process.platform !== 'win32') {
627
+ fs.chmodSync(targetPath, 0o755);
628
+ }
629
+
630
+ // Cleanup
631
+ fs.rmSync(tempFile, { force: true });
632
+ fs.rmSync(extractDir, { recursive: true, force: true });
633
+
634
+ console.log(chalk.green(` ✅ Installation completed!`));
635
+ console.log(chalk.gray(` Location: ${targetPath}`));
636
+ console.log(chalk.gray(` Version: ${selectedRelease.tag_name}`));
637
+
638
+ // Check if install directory is in PATH
639
+ const currentPath = process.env.PATH || '';
640
+ if (!currentPath.includes(config.installDir)) {
641
+ console.log(
642
+ chalk.yellow(' ⚠️ Warning: Install directory is not in PATH environment variable')
643
+ );
644
+
645
+ if (process.platform === 'win32') {
646
+ console.log(chalk.gray(` Please add to PATH: set PATH=%PATH%;${config.installDir}`));
647
+ } else {
648
+ // Auto-add to shell configuration file
649
+ await autoAddToShellConfig(config.installDir);
650
+ }
651
+ }
652
+
653
+ return true;
654
+ } catch (error) {
655
+ console.error(chalk.red(`❌ Installation failed: ${error}`));
656
+ return false;
657
+ }
658
+ }
659
+
660
+ // Interactive version selection
661
+ async function selectVersion(toolName: string): Promise<string | null> {
662
+ const systemInfo = getSystemInfo();
663
+ const versions = await getAvailableVersions(toolName, systemInfo);
664
+
665
+ if (versions.length === 0) {
666
+ console.log(chalk.red(`Unable to get version information for ${toolName}`));
667
+ return null;
668
+ }
669
+
670
+ const compatibleVersions = versions.filter((v) => v.hasCompatibleAsset).slice(0, 5);
671
+
672
+ if (compatibleVersions.length === 0) {
673
+ console.log(chalk.red(`No compatible versions found for current system`));
674
+ return null;
675
+ }
676
+
677
+ console.log(chalk.blue(`\n📋 Select version for ${toolName}`));
678
+ console.log(chalk.gray(`System: ${systemInfo.platform}/${systemInfo.arch}`));
679
+ console.log(chalk.gray(`Compatible versions (latest 5):\n`));
680
+
681
+ const choices = compatibleVersions.map((version, index) => ({
682
+ name: `${version.version} ${index === 0 ? chalk.green('(latest)') : chalk.gray('(available)')}`,
683
+ value: version.version,
684
+ short: version.version
685
+ }));
686
+
687
+ try {
688
+ const answer = await inquirer.prompt([
689
+ {
690
+ type: 'list',
691
+ name: 'version',
692
+ message: 'Please select a version to install:',
693
+ choices: [
694
+ ...choices,
695
+ new inquirer.Separator(),
696
+ {
697
+ name: chalk.gray('Cancel installation'),
698
+ value: 'cancel'
699
+ }
700
+ ],
701
+ default: choices[0].value
702
+ }
703
+ ]);
704
+
705
+ if (answer.version === 'cancel') {
706
+ console.log(chalk.gray('Installation cancelled'));
707
+ return null;
708
+ }
709
+
710
+ return answer.version;
711
+ } catch (error) {
712
+ console.log(chalk.gray('\nInstallation cancelled'));
713
+ return null;
714
+ }
715
+ }
716
+
717
+ // Check if binary exists in install directory
718
+ function checkBinaryExists(toolName: string): boolean {
719
+ const config = TOOL_CONFIGS[toolName];
720
+ if (!config) return false;
721
+
722
+ const binaryPath = path.join(
723
+ config.installDir,
724
+ config.binaryName + (process.platform === 'win32' ? '.exe' : '')
725
+ );
726
+
727
+ return fs.existsSync(binaryPath);
728
+ }
729
+
45
730
  // Check if command is available in PATH
46
731
  async function checkCommand(
47
732
  command: string,
@@ -57,6 +742,19 @@ async function checkCommand(
57
742
  message: `Installed ${version}`
58
743
  };
59
744
  } else {
745
+ // Check if binary exists in install directory but not in PATH
746
+ if (checkBinaryExists(command)) {
747
+ const shell = detectCurrentShell();
748
+ const shellConfig = shell ? shell.configFile : '~/.zshrc (or ~/.bashrc)';
749
+
750
+ return {
751
+ name: command,
752
+ status: 'warning',
753
+ message: 'Installed but not in PATH',
754
+ fixSuggestion: `Binary exists in install directory. Please apply PATH changes: source ${shellConfig} (or restart terminal), then run dubhe doctor again`
755
+ };
756
+ }
757
+
60
758
  return {
61
759
  name: command,
62
760
  status: 'error',
@@ -65,6 +763,19 @@ async function checkCommand(
65
763
  };
66
764
  }
67
765
  } catch (error) {
766
+ // Check if binary exists in install directory but not in PATH
767
+ if (checkBinaryExists(command)) {
768
+ const shell = detectCurrentShell();
769
+ const shellConfig = shell ? shell.configFile : '~/.zshrc (or ~/.bashrc)';
770
+
771
+ return {
772
+ name: command,
773
+ status: 'warning',
774
+ message: 'Installed but not in PATH',
775
+ fixSuggestion: `Binary exists in install directory. Please apply PATH changes: source ${shellConfig} (or restart terminal), then run dubhe doctor again`
776
+ };
777
+ }
778
+
68
779
  return {
69
780
  name: command,
70
781
  status: 'error',
@@ -79,26 +790,16 @@ function getInstallSuggestion(command: string): string {
79
790
  const suggestions: Record<string, string> = {
80
791
  docker: 'Visit https://docs.docker.com/get-docker/ to install Docker',
81
792
  'docker-compose': 'Visit https://docs.docker.com/compose/install/ to install Docker Compose',
82
- sui: 'Visit https://docs.sui.io/guides/developer/getting-started/sui-install to install Sui CLI',
793
+ sui: 'Run `dubhe doctor --install sui` to auto-install, or visit https://docs.sui.io/guides/developer/getting-started/sui-install',
794
+ 'dubhe-indexer':
795
+ 'Run `dubhe doctor --install dubhe-indexer` to auto-install, or download from https://github.com/0xobelisk/dubhe-wip/releases',
83
796
  pnpm: 'Run: npm install -g pnpm',
84
- node: 'Visit https://nodejs.org/ to download and install Node.js',
85
- git: 'Visit https://git-scm.com/downloads to install Git'
797
+ node: 'Visit https://nodejs.org/ to download and install Node.js'
86
798
  };
87
799
 
88
800
  return suggestions[command] || `Please install ${command}`;
89
801
  }
90
802
 
91
- // Check if file exists
92
- function checkFileExists(filePath: string, description: string): CheckResult {
93
- const exists = existsSync(filePath);
94
- return {
95
- name: description,
96
- status: exists ? 'success' : 'warning',
97
- message: exists ? 'Available' : 'Not found',
98
- fixSuggestion: exists ? undefined : `Please create file: ${filePath}`
99
- };
100
- }
101
-
102
803
  // Check Node.js version
103
804
  async function checkNodeVersion(): Promise<CheckResult> {
104
805
  try {
@@ -109,13 +810,13 @@ async function checkNodeVersion(): Promise<CheckResult> {
109
810
 
110
811
  if (versionNumber >= 18) {
111
812
  return {
112
- name: 'Node.js version',
813
+ name: 'Node.js Version',
113
814
  status: 'success',
114
815
  message: `${version} (meets requirement >=18.0)`
115
816
  };
116
817
  } else {
117
818
  return {
118
- name: 'Node.js version',
819
+ name: 'Node.js Version',
119
820
  status: 'warning',
120
821
  message: `${version} (recommend upgrade to >=18.0)`,
121
822
  fixSuggestion: 'Please upgrade Node.js to 18.0 or higher'
@@ -123,7 +824,7 @@ async function checkNodeVersion(): Promise<CheckResult> {
123
824
  }
124
825
  } else {
125
826
  return {
126
- name: 'Node.js version',
827
+ name: 'Node.js Version',
127
828
  status: 'error',
128
829
  message: 'Not installed',
129
830
  fixSuggestion: 'Please install Node.js 18.0 or higher'
@@ -131,7 +832,7 @@ async function checkNodeVersion(): Promise<CheckResult> {
131
832
  }
132
833
  } catch (error) {
133
834
  return {
134
- name: 'Node.js version',
835
+ name: 'Node.js Version',
135
836
  status: 'error',
136
837
  message: 'Check failed',
137
838
  fixSuggestion: 'Please install Node.js'
@@ -145,13 +846,13 @@ async function checkDockerService(): Promise<CheckResult> {
145
846
  const result = await execCommand('docker', ['info']);
146
847
  if (result.code === 0) {
147
848
  return {
148
- name: 'Docker service',
849
+ name: 'Docker Service',
149
850
  status: 'success',
150
851
  message: 'Running'
151
852
  };
152
853
  } else {
153
854
  return {
154
- name: 'Docker service',
855
+ name: 'Docker Service',
155
856
  status: 'warning',
156
857
  message: 'Not running',
157
858
  fixSuggestion: 'Please start Docker service'
@@ -159,7 +860,7 @@ async function checkDockerService(): Promise<CheckResult> {
159
860
  }
160
861
  } catch (error) {
161
862
  return {
162
- name: 'Docker service',
863
+ name: 'Docker Service',
163
864
  status: 'error',
164
865
  message: 'Check failed',
165
866
  fixSuggestion: 'Please install and start Docker'
@@ -174,21 +875,21 @@ async function checkNpmConfig(): Promise<CheckResult> {
174
875
  if (result.code === 0) {
175
876
  const registry = result.stdout.trim();
176
877
  return {
177
- name: 'NPM configuration',
878
+ name: 'NPM Configuration',
178
879
  status: 'success',
179
880
  message: `Configured (${registry})`
180
881
  };
181
882
  } else {
182
883
  return {
183
- name: 'NPM configuration',
884
+ name: 'NPM Configuration',
184
885
  status: 'warning',
185
886
  message: 'Configuration issue',
186
- fixSuggestion: 'Check NPM config: npm config list'
887
+ fixSuggestion: 'Check NPM configuration: npm config list'
187
888
  };
188
889
  }
189
890
  } catch (error) {
190
891
  return {
191
- name: 'NPM configuration',
892
+ name: 'NPM Configuration',
192
893
  status: 'warning',
193
894
  message: 'Check failed',
194
895
  fixSuggestion: 'Please install Node.js and NPM'
@@ -205,9 +906,9 @@ function formatTableRow(result: CheckResult): string[] {
205
906
  };
206
907
 
207
908
  const statusText = {
208
- success: chalk.green('YES'),
209
- warning: chalk.yellow('WARN'),
210
- error: chalk.red('NO')
909
+ success: chalk.green('Pass'),
910
+ warning: chalk.yellow('Warning'),
911
+ error: chalk.red('Fail')
211
912
  };
212
913
 
213
914
  // Decide what content to display based on status
@@ -222,10 +923,108 @@ function formatTableRow(result: CheckResult): string[] {
222
923
  }
223
924
 
224
925
  // Main check function
225
- async function runDoctorChecks() {
226
- console.log(chalk.bold.blue('\n🔍 Dubhe Doctor - Environment Check Tool\n'));
926
+ async function runDoctorChecks(options: {
927
+ install?: string;
928
+ selectVersion?: boolean;
929
+ listVersions?: string;
930
+ debug?: boolean;
931
+ }) {
932
+ console.log(chalk.bold.blue('\n🔍 Dubhe Doctor - Development Environment Checker\n'));
933
+
934
+ // Handle list-versions option
935
+ if (options.listVersions) {
936
+ const toolName = options.listVersions;
937
+ if (!TOOL_CONFIGS[toolName]) {
938
+ console.error(chalk.red(`❌ Unsupported tool: ${toolName}`));
939
+ process.exit(1);
940
+ }
941
+
942
+ console.log(chalk.blue(`📋 Available versions for ${toolName}:`));
943
+ const systemInfo = getSystemInfo();
944
+ console.log(chalk.gray(`System: ${systemInfo.platform}/${systemInfo.arch}\n`));
945
+
946
+ // Get 10 versions directly to avoid duplicate calls
947
+ const config = TOOL_CONFIGS[toolName];
948
+ const releases = await fetchGitHubReleases(config.repo, 10);
949
+
950
+ if (releases.length === 0) {
951
+ console.log(chalk.red('Unable to get version information'));
952
+ process.exit(1);
953
+ }
954
+
955
+ // Process version compatibility check
956
+ const versions = releases.map((release) => ({
957
+ version: release.tag_name,
958
+ hasCompatibleAsset: findCompatibleAssets(release, systemInfo).length > 0,
959
+ publishDate: new Date(release.published_at).toLocaleDateString('en-US')
960
+ }));
961
+
962
+ const table = new Table({
963
+ head: [
964
+ chalk.bold.cyan('Version'),
965
+ chalk.bold.cyan('Compatibility'),
966
+ chalk.bold.cyan('Release Date')
967
+ ],
968
+ colWidths: [30, 15, 25]
969
+ });
970
+
971
+ versions.forEach((version) => {
972
+ table.push([
973
+ version.version,
974
+ version.hasCompatibleAsset ? chalk.green('✓ Compatible') : chalk.red('✗ Incompatible'),
975
+ version.publishDate
976
+ ]);
977
+ });
978
+
979
+ console.log(table.toString());
980
+
981
+ if (options.debug && versions.length > 0) {
982
+ console.log(chalk.blue('\n🔍 Debug Information:'));
983
+ const latestCompatible = versions.find((v) => v.hasCompatibleAsset);
984
+ if (latestCompatible) {
985
+ const release = releases.find((r) => r.tag_name === latestCompatible.version);
986
+ if (release) {
987
+ console.log(chalk.gray(`Latest compatible version: ${latestCompatible.version}`));
988
+ console.log(chalk.gray(`Available asset files:`));
989
+ release.assets.forEach((asset) => {
990
+ console.log(chalk.gray(` - ${asset.name}`));
991
+ });
992
+
993
+ const compatibleAssets = findCompatibleAssets(release, systemInfo);
994
+ console.log(chalk.gray(`Compatible asset files:`));
995
+ compatibleAssets.forEach((asset) => {
996
+ console.log(chalk.green(` ✓ ${asset}`));
997
+ });
998
+ }
999
+ }
1000
+ }
1001
+
1002
+ process.exit(0);
1003
+ }
1004
+
227
1005
  console.log(chalk.gray('Checking your development environment...\n'));
228
1006
 
1007
+ // Handle install option
1008
+ if (options.install) {
1009
+ const toolName = options.install;
1010
+ if (!TOOL_CONFIGS[toolName]) {
1011
+ console.error(chalk.red(`❌ Unsupported tool: ${toolName}`));
1012
+ console.log(chalk.gray(`Supported tools: ${Object.keys(TOOL_CONFIGS).join(', ')}`));
1013
+ process.exit(1);
1014
+ }
1015
+
1016
+ let version: string | null = null;
1017
+ if (options.selectVersion) {
1018
+ version = await selectVersion(toolName);
1019
+ if (!version) {
1020
+ process.exit(1);
1021
+ }
1022
+ }
1023
+
1024
+ const success = await downloadAndInstallTool(toolName, version || undefined);
1025
+ process.exit(success ? 0 : 1);
1026
+ }
1027
+
229
1028
  const results: CheckResult[] = [];
230
1029
 
231
1030
  // Execute all checks
@@ -238,9 +1037,6 @@ async function runDoctorChecks() {
238
1037
  const pnpmCheck = await checkCommand('pnpm');
239
1038
  results.push(pnpmCheck);
240
1039
 
241
- const gitCheck = await checkCommand('git');
242
- results.push(gitCheck);
243
-
244
1040
  // Package manager configuration check
245
1041
  const npmConfigCheck = await checkNpmConfig();
246
1042
  // Treat npm config as optional, don't affect overall status
@@ -266,14 +1062,18 @@ async function runDoctorChecks() {
266
1062
  const suiCheck = await checkCommand('sui');
267
1063
  results.push(suiCheck);
268
1064
 
269
- // Configuration files check
270
- const gitConfigCheck = checkFileExists(join(homedir(), '.gitconfig'), 'Git configuration');
271
- results.push(gitConfigCheck);
1065
+ // Dubhe indexer check
1066
+ const dubheIndexerCheck = await checkCommand('dubhe-indexer');
1067
+ results.push(dubheIndexerCheck);
272
1068
 
273
1069
  // Create and display table
274
1070
  const table = new Table({
275
- head: [chalk.bold.cyan('Criteria'), chalk.bold.cyan('Result'), chalk.bold.cyan('Fix')],
276
- colWidths: [25, 15, 50],
1071
+ head: [
1072
+ chalk.bold.cyan('Check Item'),
1073
+ chalk.bold.cyan('Result'),
1074
+ chalk.bold.cyan('Description/Fix Suggestion')
1075
+ ],
1076
+ colWidths: [25, 15, 60],
277
1077
  style: {
278
1078
  head: ['cyan'],
279
1079
  border: ['grey']
@@ -298,23 +1098,161 @@ async function runDoctorChecks() {
298
1098
  console.log('\n' + chalk.bold('📊 Check Summary:'));
299
1099
  console.log(` ${chalk.green('✓ Passed:')} ${summary.success} items`);
300
1100
  console.log(` ${chalk.yellow('! Warning:')} ${summary.warning} items`);
301
- console.log(` ${chalk.red('✗ Error:')} ${summary.error} items`);
1101
+ console.log(` ${chalk.red('✗ Failed:')} ${summary.error} items`);
302
1102
 
303
- if (summary.error > 0) {
304
- console.log(
305
- chalk.red(
306
- '\n❌ Your environment has some issues, please fix them according to the suggestions above.'
307
- )
308
- );
309
- process.exit(1);
310
- } else if (summary.warning > 0) {
311
- console.log(
312
- chalk.yellow(
313
- '\n⚠️ Your environment is basically ready, but it is recommended to fix warning items for a better development experience.'
314
- )
1103
+ // Handle missing tools
1104
+ const allFailedTools = results.filter((r) => r.status === 'error');
1105
+ const autoInstallableTools = allFailedTools.filter((r) => TOOL_CONFIGS[r.name]);
1106
+ const manualInstallTools = allFailedTools.filter((r) => !TOOL_CONFIGS[r.name]);
1107
+
1108
+ // Show manual installation suggestions for non-auto-installable tools
1109
+ if (manualInstallTools.length > 0) {
1110
+ console.log(chalk.blue('\n🔧 Missing tools that require manual installation:'));
1111
+ manualInstallTools.forEach((tool) => {
1112
+ console.log(` ${chalk.red('✗')} ${tool.name}: ${tool.fixSuggestion || tool.message}`);
1113
+ });
1114
+ }
1115
+
1116
+ // Auto-install missing tools that support it
1117
+ if (autoInstallableTools.length > 0) {
1118
+ // Check if any of the tools are already installed in the install directory
1119
+ const alreadyInstalledTools = autoInstallableTools.filter((tool) =>
1120
+ checkBinaryExists(tool.name)
315
1121
  );
316
- } else {
317
- console.log(chalk.green('\n✅ Congratulations! Your development environment is fully ready!'));
1122
+ const notInstalledTools = autoInstallableTools.filter((tool) => !checkBinaryExists(tool.name));
1123
+
1124
+ if (alreadyInstalledTools.length > 0) {
1125
+ const installedNames = alreadyInstalledTools.map((tool) => tool.name).join(', ');
1126
+ const installDir = TOOL_CONFIGS[alreadyInstalledTools[0].name]?.installDir || '~/.dubhe/bin';
1127
+ const shell = detectCurrentShell();
1128
+ const shellConfig = shell ? shell.configFile : '~/.zshrc (or ~/.bashrc)';
1129
+
1130
+ console.log(chalk.yellow(`\n⚠️ Tools already installed but not in PATH: ${installedNames}`));
1131
+ console.log(chalk.gray(` Location: ${installDir}`));
1132
+ console.log(chalk.blue(' To fix this, apply PATH changes:'));
1133
+ console.log(chalk.green(` source ${shellConfig}`));
1134
+ console.log(chalk.blue(' Or restart your terminal, then run: dubhe doctor'));
1135
+ console.log(
1136
+ chalk.gray(
1137
+ ` If you want to reinstall, remove the files from ${installDir} and run dubhe doctor again`
1138
+ )
1139
+ );
1140
+ }
1141
+
1142
+ if (notInstalledTools.length > 0) {
1143
+ const notInstalledNames = notInstalledTools.map((tool) => tool.name).join(', ');
1144
+ console.log(chalk.blue(`\n🚀 Auto-installable tools detected: ${notInstalledNames}`));
1145
+
1146
+ try {
1147
+ const answer = await inquirer.prompt([
1148
+ {
1149
+ type: 'confirm',
1150
+ name: 'installAll',
1151
+ message: `Would you like to automatically install these tools? (${notInstalledNames})`,
1152
+ default: true
1153
+ }
1154
+ ]);
1155
+
1156
+ if (answer.installAll) {
1157
+ console.log(chalk.blue('\n📦 Starting installation of auto-installable tools...\n'));
1158
+
1159
+ let installationResults: Array<{ name: string; success: boolean }> = [];
1160
+
1161
+ for (const tool of notInstalledTools) {
1162
+ console.log(chalk.blue(`${'='.repeat(60)}`));
1163
+ console.log(chalk.blue(`📦 Installing ${tool.name}...`));
1164
+ console.log(chalk.blue(`${'='.repeat(60)}`));
1165
+
1166
+ const success = await downloadAndInstallTool(tool.name);
1167
+ installationResults.push({ name: tool.name, success });
1168
+
1169
+ if (success) {
1170
+ console.log(chalk.green(`\n✅ ${tool.name} installation completed successfully!`));
1171
+ } else {
1172
+ console.log(chalk.red(`\n❌ ${tool.name} installation failed`));
1173
+ console.log(
1174
+ chalk.gray(` Manual installation: dubhe doctor --install ${tool.name}`)
1175
+ );
1176
+ }
1177
+ console.log(''); // Add spacing between tools
1178
+ }
1179
+
1180
+ // Show installation summary
1181
+ console.log(chalk.blue(`${'='.repeat(60)}`));
1182
+ console.log(chalk.bold('📋 Installation Summary:'));
1183
+ console.log(chalk.blue(`${'='.repeat(60)}`));
1184
+
1185
+ installationResults.forEach((result) => {
1186
+ const status = result.success ? chalk.green('✅ Success') : chalk.red('❌ Failed');
1187
+ console.log(` ${result.name}: ${status}`);
1188
+ });
1189
+
1190
+ const successCount = installationResults.filter((r) => r.success).length;
1191
+ const failureCount = installationResults.length - successCount;
1192
+
1193
+ console.log(
1194
+ `\n ${chalk.green('Successful:')} ${successCount}/${installationResults.length}`
1195
+ );
1196
+ if (failureCount > 0) {
1197
+ console.log(` ${chalk.red('Failed:')} ${failureCount}/${installationResults.length}`);
1198
+ }
1199
+
1200
+ // Check if any tools were successfully installed
1201
+ if (successCount > 0) {
1202
+ const shell = detectCurrentShell();
1203
+ const shellConfig = shell ? shell.configFile : '~/.zshrc (or ~/.bashrc)';
1204
+
1205
+ console.log(chalk.blue('\n🔄 Next Steps:'));
1206
+ console.log(chalk.yellow(' 1. Apply PATH changes by running:'));
1207
+ console.log(chalk.green(` source ${shellConfig}`));
1208
+ console.log(chalk.yellow(' 2. Or restart your terminal'));
1209
+ console.log(chalk.yellow(' 3. Then run the doctor check again:'));
1210
+ console.log(chalk.green(' dubhe doctor'));
1211
+ console.log(
1212
+ chalk.gray('\n This will verify that all tools are properly configured.')
1213
+ );
1214
+ } else {
1215
+ console.log(
1216
+ chalk.red('\n❌ All installations failed. Please check the error messages above.')
1217
+ );
1218
+ }
1219
+ } else {
1220
+ console.log(
1221
+ chalk.gray('\nAuto-installation skipped. You can install them manually later:')
1222
+ );
1223
+ notInstalledTools.forEach((tool) => {
1224
+ console.log(chalk.gray(` dubhe doctor --install ${tool.name}`));
1225
+ });
1226
+ }
1227
+ } catch (error) {
1228
+ console.log(chalk.gray('\nInstallation cancelled. You can install them manually later:'));
1229
+ notInstalledTools.forEach((tool) => {
1230
+ console.log(chalk.gray(` dubhe doctor --install ${tool.name}`));
1231
+ });
1232
+ }
1233
+ }
1234
+ }
1235
+
1236
+ // If no auto-installable tools are missing, show final status
1237
+ if (autoInstallableTools.length === 0) {
1238
+ if (summary.error > 0) {
1239
+ console.log(
1240
+ chalk.red(
1241
+ '\n❌ Your environment has some issues. Please fix them according to the suggestions above.'
1242
+ )
1243
+ );
1244
+ process.exit(1);
1245
+ } else if (summary.warning > 0) {
1246
+ console.log(
1247
+ chalk.yellow(
1248
+ '\n⚠️ Your environment is basically ready, but we recommend fixing the warnings for better development experience.'
1249
+ )
1250
+ );
1251
+ } else {
1252
+ console.log(
1253
+ chalk.green('\n✅ Congratulations! Your development environment is fully ready!')
1254
+ );
1255
+ }
318
1256
  }
319
1257
 
320
1258
  console.log(
@@ -328,16 +1266,36 @@ const commandModule: CommandModule = {
328
1266
  command: 'doctor',
329
1267
  describe: 'Check if local development environment is ready',
330
1268
  builder(yargs) {
331
- return yargs.option('verbose', {
332
- alias: 'v',
333
- type: 'boolean',
334
- description: 'Show verbose output',
335
- default: false
336
- });
1269
+ return yargs
1270
+ .option('install', {
1271
+ type: 'string',
1272
+ describe: 'Auto-install specified tool (sui, dubhe-indexer)',
1273
+ choices: Object.keys(TOOL_CONFIGS)
1274
+ })
1275
+ .option('select-version', {
1276
+ type: 'boolean',
1277
+ default: false,
1278
+ describe: 'Select specific version for installation'
1279
+ })
1280
+ .option('list-versions', {
1281
+ type: 'string',
1282
+ describe: 'List available versions for specified tool',
1283
+ choices: Object.keys(TOOL_CONFIGS)
1284
+ })
1285
+ .option('debug', {
1286
+ type: 'boolean',
1287
+ default: false,
1288
+ describe: 'Show detailed debug information'
1289
+ });
337
1290
  },
338
- async handler() {
1291
+ async handler(argv) {
339
1292
  try {
340
- await runDoctorChecks();
1293
+ await runDoctorChecks({
1294
+ install: argv.install as string | undefined,
1295
+ selectVersion: argv['select-version'] as boolean,
1296
+ listVersions: argv['list-versions'] as string | undefined,
1297
+ debug: argv.debug as boolean
1298
+ });
341
1299
  } catch (error) {
342
1300
  console.error(chalk.red('Error occurred during check:'), error);
343
1301
  process.exit(1);