( deps: ProviderDeps, providerId: string | undefined, opts: CatalogListOptions, )
| 209 | * mirrors the discovery half of the TUI "Known third-party provider" flow. |
| 210 | */ |
| 211 | export async function handleCatalogList( |
| 212 | deps: ProviderDeps, |
| 213 | providerId: string | undefined, |
| 214 | opts: CatalogListOptions, |
| 215 | ): Promise<void> { |
| 216 | const url = opts.url ?? DEFAULT_CATALOG_URL; |
| 217 | const catalog = await loadCatalogOrExit(deps, url); |
| 218 | |
| 219 | if (providerId !== undefined) { |
| 220 | const entry = catalog[providerId]; |
| 221 | if (entry === undefined) { |
| 222 | deps.stderr.write(`Provider "${providerId}" not found in catalog at ${url}.\n`); |
| 223 | deps.exit(1); |
| 224 | } |
| 225 | const models = catalogProviderModels(entry); |
| 226 | if (opts.json) { |
| 227 | deps.stdout.write( |
| 228 | `${JSON.stringify({ providerId, name: entry.name ?? providerId, models }, null, 2)}\n`, |
| 229 | ); |
| 230 | return; |
| 231 | } |
| 232 | if (models.length === 0) { |
| 233 | deps.stdout.write(`Provider "${providerId}" lists no usable models in this catalog.\n`); |
| 234 | return; |
| 235 | } |
| 236 | deps.stdout.write(`${entry.name ?? providerId} (${providerId})\n`); |
| 237 | for (const model of models) { |
| 238 | const cap: string[] = []; |
| 239 | if (model.capability.tool_use) cap.push('tool_use'); |
| 240 | if (model.capability.thinking) cap.push('thinking'); |
| 241 | if (model.capability.image_in) cap.push('image_in'); |
| 242 | const ctx = |
| 243 | typeof model.capability.max_context_tokens === 'number' |
| 244 | ? String(model.capability.max_context_tokens) |
| 245 | : '?'; |
| 246 | const capLabel = cap.length > 0 ? ` [${cap.join(',')}]` : ''; |
| 247 | deps.stdout.write(` ${model.id} ctx=${ctx}${capLabel}\n`); |
| 248 | } |
| 249 | return; |
| 250 | } |
| 251 | |
| 252 | const filter = opts.filter?.toLowerCase(); |
| 253 | const entries = Object.entries(catalog) |
| 254 | .filter(([id, entry]) => { |
| 255 | if (filter === undefined) return true; |
| 256 | const haystack = `${id} ${entry.name ?? ''}`.toLowerCase(); |
| 257 | return haystack.includes(filter); |
| 258 | }) |
| 259 | .toSorted(([a], [b]) => a.localeCompare(b)); |
| 260 | |
| 261 | if (opts.json) { |
| 262 | const out: Record<string, CatalogProviderEntry> = {}; |
| 263 | for (const [id, entry] of entries) out[id] = entry; |
| 264 | deps.stdout.write(`${JSON.stringify(out, null, 2)}\n`); |
| 265 | return; |
| 266 | } |
| 267 | |
| 268 | if (entries.length === 0) { |
no test coverage detected