Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/cli/src/__tests__/commands.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ describe('command registration', () => {
'install',
'uninstall',
'search',
'info',
'list',
'publish',
'init',
Expand Down
65 changes: 65 additions & 0 deletions packages/cli/src/commands/info.ts
Original file line number Diff line number Diff line change
@@ -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>', '基因 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);
}
});
40 changes: 39 additions & 1 deletion packages/cli/src/commands/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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),
});
Comment thread
chenchenchenchencj marked this conversation as resolved.

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;
Comment thread
chenchenchenchencj marked this conversation as resolved.
}

const result = await client.federatedSearch({
q: keyword ?? '',
q: trimmedKeyword,
category: opts.category,
limit: Number(opts.limit) || 20,
});
Expand Down
2 changes: 2 additions & 0 deletions packages/cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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);
Expand Down
11 changes: 5 additions & 6 deletions packages/sdk/typescript/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type {
ApiResponse,
CreateAgentTemplateRequest,
CreateGenomeRequest,
FederatedSearchParams,
FederatedSearchResult,
Gene,
GeneListParams,
Expand Down Expand Up @@ -73,15 +74,13 @@ export class GeneHubClient {
}

/** 联邦搜索:合并本地 DB 与外部源(如 ClawHub)结果 */
async federatedSearch(params: {
q: string;
category?: string;
limit?: number;
}): Promise<FederatedSearchResult> {
async federatedSearch(params: FederatedSearchParams): Promise<FederatedSearchResult> {
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));
}
Comment on lines +81 to +83

Copilot AI Mar 6, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new Number.isFinite(params.limit) && (params.limit as number) > 0 guard means that a limit of 0 or a negative value will silently be omitted from the query string, which changes the previous behavior (previously limit: 0 would have been sent as limit=0). There is no test covering this new boundary condition — passing limit: 0 or limit: -1 should be verified to not append a limit parameter. A test case for this would make the new behavior explicit and prevent regressions.

Copilot uses AI. Check for mistakes.
return this.request<FederatedSearchResult>(`/api/v1/genes/search?${qs.toString()}`);
}

Expand Down