diff --git a/packages/cli/src/__tests__/commands.test.ts b/packages/cli/src/__tests__/commands.test.ts index 8b9ba5e..42618cd 100644 --- a/packages/cli/src/__tests__/commands.test.ts +++ b/packages/cli/src/__tests__/commands.test.ts @@ -173,6 +173,7 @@ describe('command registration', () => { 'install', 'uninstall', 'search', + 'info', 'list', 'publish', 'init', diff --git a/packages/cli/src/commands/info.ts b/packages/cli/src/commands/info.ts new file mode 100644 index 0000000..f72245d --- /dev/null +++ b/packages/cli/src/commands/info.ts @@ -0,0 +1,65 @@ +import { GeneHubClient } from '@nodeskai/genehub-sdk'; +import type { Gene } from '@nodeskai/genehub-types'; +import { Command } from 'commander'; +import { loadConfig } from '../config.js'; +import * as output from '../output.js'; + +function formatAuthor(gene: Gene): string { + const a = gene.author; + if (!a) return '-'; + if (a.ref) return `${a.name} (${a.ref})`; + return `${a.name} (${a.type})`; +} + +function formatGene(gene: Gene): void { + console.log(` ${gene.slug} v${gene.version}`); + console.log(` ${gene.short_description || gene.description || ''}`); + console.log(''); + console.log(` Category: ${gene.category}`); + console.log(` Tags: ${gene.tags?.join(', ') || '(none)'}`); + console.log(` Author: ${formatAuthor(gene)}`); + console.log(` Status: ${gene.is_published ? 'published' : 'unpublished'}`); + console.log(` Downloads: ${gene.install_count}`); + console.log(''); + console.log(' Compatibility:'); + if (gene.compatibility?.length) { + for (const c of gene.compatibility) { + console.log(` - ${c}`); + } + } else { + console.log(' (none)'); + } + console.log(''); + console.log( + ' Dependencies:', + gene.dependencies?.length + ? gene.dependencies.map((d) => `${d.slug}@${d.version}`).join(', ') + : '(none)', + ); + console.log(''); + console.log(' Install:'); + console.log(` genehub install ${gene.slug}`); +} + +export const infoCommand = new Command('info') + .description('查看基因详情') + .argument('', '基因 slug') + .option('--json', 'JSON 格式输出', false) + .action(async (slug: string, opts: { json?: boolean }) => { + const config = await loadConfig(); + const client = new GeneHubClient({ registryUrl: config.registryUrl, token: config.token }); + + try { + const gene = await client.getGene(slug); + + if (opts.json) { + console.log(JSON.stringify(gene, null, 2)); + return; + } + + formatGene(gene); + } catch (err) { + output.fail(err instanceof Error ? err.message : String(err)); + process.exit(1); + } + }); diff --git a/packages/cli/src/commands/search.ts b/packages/cli/src/commands/search.ts index 766bf2b..1f6dca8 100644 --- a/packages/cli/src/commands/search.ts +++ b/packages/cli/src/commands/search.ts @@ -19,6 +19,8 @@ export const searchCommand = new Command('search') const client = new GeneHubClient({ registryUrl: config.registryUrl, token: config.token }); try { + const trimmedKeyword = keyword?.trim(); + if (opts.local) { const result = await client.searchGenes({ q: keyword, @@ -55,8 +57,44 @@ export const searchCommand = new Command('search') return; } + if (!trimmedKeyword) { + const result = await client.searchGenes({ + q: undefined, + category: opts.category, + tags: opts.tags?.split(','), + compatibility: opts.compat, + sort: opts.sort, + page: Number(opts.page), + }); + + if (opts.json) { + console.log(JSON.stringify(result, null, 2)); + return; + } + + if (result.items.length === 0) { + output.info('未找到匹配的基因'); + return; + } + + output.table( + ['slug', '名称', '版本', '分类', '兼容', '安装数'], + result.items.map((g) => [ + g.slug, + g.name, + g.version, + g.category, + (g.compatibility as string[]).join(', '), + String(g.install_count), + ]), + ); + + output.info(`共 ${result.total} 条结果,第 ${result.page}/${result.total_pages} 页`); + return; + } + const result = await client.federatedSearch({ - q: keyword ?? '', + q: trimmedKeyword, category: opts.category, limit: Number(opts.limit) || 20, }); diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 3ae70b0..4913400 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -5,6 +5,7 @@ import { Command } from 'commander'; import { authCommand } from './commands/auth.js'; import { configCommand } from './commands/config.js'; import { genomeCommand } from './commands/genome.js'; +import { infoCommand } from './commands/info.js'; import { initCommand } from './commands/init.js'; import { installCommand } from './commands/install.js'; import { learnCommand } from './commands/learn.js'; @@ -34,6 +35,7 @@ program.addCommand(authCommand); program.addCommand(installCommand); program.addCommand(uninstallCommand); program.addCommand(searchCommand); +program.addCommand(infoCommand); program.addCommand(listCommand); program.addCommand(publishCommand); program.addCommand(initCommand); diff --git a/packages/sdk/typescript/src/client.ts b/packages/sdk/typescript/src/client.ts index 9b0a57e..2f82246 100644 --- a/packages/sdk/typescript/src/client.ts +++ b/packages/sdk/typescript/src/client.ts @@ -5,6 +5,7 @@ import type { ApiResponse, CreateAgentTemplateRequest, CreateGenomeRequest, + FederatedSearchParams, FederatedSearchResult, Gene, GeneListParams, @@ -73,15 +74,13 @@ export class GeneHubClient { } /** 联邦搜索:合并本地 DB 与外部源(如 ClawHub)结果 */ - async federatedSearch(params: { - q: string; - category?: string; - limit?: number; - }): Promise { + async federatedSearch(params: FederatedSearchParams): Promise { const qs = new URLSearchParams(); qs.set('q', params.q); if (params.category) qs.set('category', params.category); - if (params.limit != null) qs.set('limit', String(params.limit)); + if (Number.isFinite(params.limit) && (params.limit as number) > 0) { + qs.set('limit', String(params.limit)); + } return this.request(`/api/v1/genes/search?${qs.toString()}`); }