import { Command } from 'commander'; import chalk from 'chalk'; import ora from 'ora'; import inquirer from 'inquirer'; import { installSkill } from '../utils/install'; import { getSystemLocale, getTranslatedDescription } from '../utils/locale'; interface Translation { locale: string; name: string; description: string; } interface SearchResult { id: string; name: string; description: string; category: string | null; repo: string; path: string; version: string | null; star: number; fork: number; updateAt: string; translations?: Translation[]; } interface SearchResponse { data: SearchResult[]; pagination: { page: number; limit: number; total: string; totalPages: number; }; } export const searchCommand = new Command('search') .description('Search for skills') .argument('', 'Search keyword') .option('--info', 'Show search results only (no install prompt)') .action(async (keyword: string, options) => { const spinner = ora('Searching skills...').start(); try { // Get system locale for translation const systemLocale = getSystemLocale(); // Build URL with query parameters directly const url = `https://www.skills-router.com/api/skills?filter=${encodeURIComponent(keyword)}&page=1&limit=12`; // Use fetch instead of axios for better compatibility const response = await fetch(url, { headers: { 'Accept': 'application/json', }, }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const data = (await response.json()) as SearchResponse; // Check if response has expected structure if (!data || typeof data !== 'object') { spinner.fail('Invalid API response'); console.error(chalk.red('\nThe API returned an invalid response format.')); process.exit(1); } const total = parseInt(data.pagination?.total || '0', 10); const results = data.data || []; const page = data.pagination?.page || 1; const limit = data.pagination?.limit || 12; const totalPages = data.pagination?.totalPages || 1; spinner.succeed(`Found ${total} skill(s)`); if (results.length === 0) { console.log(chalk.yellow('\nNo skills found matching your search.')); return; } console.log(`\n${chalk.bold('Search Results:')} ${keyword}\n`); results.forEach((skill: SearchResult, index: number) => { // Display name on its own line (use original name, not translated) console.log(`${chalk.cyan.bold(`${index + 1}. ${skill.name}`)}`); // Get translated description based on system locale const translatedDescription = getTranslatedDescription( skill.translations, systemLocale, skill.description ); // Display description on a separate line with better formatting console.log(` ${chalk.white(translatedDescription)}`); // Display repository info more prominently console.log(` ${chalk.gray('─── Repository Info ───')}`); console.log(` ${chalk.blue('📦 Repository:')} ${chalk.white(skill.repo)}`); console.log(` ${chalk.yellow('⭐ Stars:')} ${chalk.white(skill.star.toString())} ${chalk.gray('|')} ${chalk.yellow('🍴 Forks:')} ${chalk.white(skill.fork.toString())}`); // Display version if available if (skill.version) { console.log(` ${chalk.magenta('📋 Version:')} ${chalk.white(skill.version)}`); } // Display category if available if (skill.category) { console.log(` ${chalk.green('🏷️ Category:')} ${chalk.white(skill.category)}`); } console.log(); // Empty line between skills }); console.log(chalk.gray(`Page ${page} of ${totalPages} (Total: ${total} skills)`)); if (options.info) { return; } // Prompt user to select skills to install const { selectedSkills, confirmInstall } = await inquirer.prompt([ { type: 'checkbox', name: 'selectedSkills', message: 'Select skills to install (press Space to select, Enter to continue):', choices: results.map(skill => ({ name: skill.name, value: skill, })), }, { type: 'confirm', name: 'confirmInstall', message: 'Do you want to install the selected skills?', default: true, when: (answers: any) => answers.selectedSkills && answers.selectedSkills.length > 0, }, ]); if (selectedSkills.length === 0 || !confirmInstall) { console.log(chalk.yellow('No skills selected or cancelled')); return; } // Install selected skills console.log(chalk.bold(`\nInstalling ${selectedSkills.length} skill(s)...\n`)); for (const skill of selectedSkills) { try { const repoUrl = `https://github.com/${skill.repo}.git`; await installSkill({ repoUrl, gitPath: skill.path, }); } catch (error: any) { console.error(chalk.red(`Failed to install ${skill.name}: ${error.message}`)); } } } catch (error: any) { spinner.fail('Search failed'); if (error.response) { console.error(chalk.red(`\nAPI Error: ${error.response.status} - ${error.response.statusText}`)); } else if (error.request) { console.error(chalk.red('\nNetwork error: Could not reach the API')); } else { console.error(chalk.red(`\nError: ${error.message}`)); } process.exit(1); } });