(parent: Command, deps?: Partial<ProviderDeps>)
| 407 | } |
| 408 | |
| 409 | export function registerProviderCommand(parent: Command, deps?: Partial<ProviderDeps>): void { |
| 410 | const provider = parent |
| 411 | .command('provider') |
| 412 | .description('Manage LLM providers non-interactively.'); |
| 413 | |
| 414 | // Last-resort boundary: handlers report expected failures themselves, but |
| 415 | // anything that escapes (e.g. a config write rejected because config.toml |
| 416 | // is invalid) must end as a one-line error + exit 1, not an unhandled |
| 417 | // rejection dumping a stack trace. |
| 418 | const runAction = async (resolved: ProviderDeps, run: () => Promise<void>): Promise<void> => { |
| 419 | try { |
| 420 | await run(); |
| 421 | } catch (error) { |
| 422 | resolved.stderr.write(`${errorMessage(error)}\n`); |
| 423 | resolved.exit(1); |
| 424 | } |
| 425 | }; |
| 426 | |
| 427 | provider |
| 428 | .command('add <url>') |
| 429 | .description('Import every provider listed in a custom registry (api.json).') |
| 430 | .option('--api-key <key>', 'Registry API key. Falls back to KIMI_REGISTRY_API_KEY.') |
| 431 | .action(async (url: string, options: { apiKey?: string }) => { |
| 432 | const resolved = resolveDeps(deps); |
| 433 | await runAction(resolved, () => handleProviderAdd(resolved, url, { apiKey: options.apiKey })); |
| 434 | }); |
| 435 | |
| 436 | provider |
| 437 | .command('remove <providerId>') |
| 438 | .description('Remove a provider and every model alias that referenced it.') |
| 439 | .action(async (providerId: string) => { |
| 440 | const resolved = resolveDeps(deps); |
| 441 | await runAction(resolved, () => handleProviderRemove(resolved, providerId)); |
| 442 | }); |
| 443 | |
| 444 | provider |
| 445 | .command('list') |
| 446 | .description('Show configured providers and their model counts.') |
| 447 | .option('--json', 'Emit the raw providers/models config as JSON.', false) |
| 448 | .action(async (options: { json?: boolean }) => { |
| 449 | const resolved = resolveDeps(deps); |
| 450 | await runAction(resolved, () => handleProviderList(resolved, { json: options.json === true })); |
| 451 | }); |
| 452 | |
| 453 | const catalog = provider |
| 454 | .command('catalog') |
| 455 | .description('Discover and import providers from the public models.dev catalog.'); |
| 456 | |
| 457 | catalog |
| 458 | .command('list [providerId]') |
| 459 | .description('List providers in the catalog, or models when a providerId is given.') |
| 460 | .option('--filter <substring>', 'Case-insensitive id/name substring filter.') |
| 461 | .option('--url <url>', `Override catalog URL. Defaults to ${DEFAULT_CATALOG_URL}.`) |
| 462 | .option('--json', 'Emit the matching catalog slice as JSON.', false) |
| 463 | .action( |
| 464 | async ( |
| 465 | providerId: string | undefined, |
| 466 | options: { filter?: string; url?: string; json?: boolean }, |
no test coverage detected