Merge pull request #962 from sgaluza/feat/cliproxy-catalog-json

feat(cliproxy): add --json flag to catalog command
This commit is contained in:
Kai (Tam Nhu) Tran
2026-04-12 16:02:03 -04:00
committed by GitHub
4 changed files with 200 additions and 0 deletions
@@ -5,10 +5,12 @@ import {
SYNCABLE_PROVIDERS,
getResolvedCatalog,
refreshCatalogFromProxy,
getAllResolvedCatalogs,
} from '../../cliproxy/catalog-cache';
import { getCatalogRoutingSnapshot } from '../../cliproxy/catalog-routing';
import { ensureManagedModelPrefixes } from '../../cliproxy/managed-model-prefixes';
import { getProxyTarget } from '../../cliproxy/proxy-target-resolver';
import type { ThinkingSupport } from '../../cliproxy/model-catalog';
import type { CLIProxyProvider } from '../../cliproxy/types';
import type { RemoteModelInfo } from '../../cliproxy/management-api-types';
import type { CliproxyProviderRoutingHints } from '../../shared/cliproxy-model-routing';
@@ -173,6 +175,50 @@ export async function handleCatalogRefresh(verbose: boolean): Promise<void> {
console.log('');
}
/** JSON-serialisable model entry emitted by `catalog --json`. */
interface CatalogJsonModel {
id: string;
name: string;
tier?: 'free' | 'pro' | 'ultra';
description?: string;
deprecated?: boolean;
deprecationReason?: string;
broken?: boolean;
issueUrl?: string;
thinking?: ThinkingSupport;
extendedContext?: boolean;
nativeImageInput?: boolean;
}
/**
* Output catalog as JSON for programmatic consumption.
* Used by OnSteroids and other tools to get available models per provider.
* Format: { [providerName: string]: CatalogJsonModel[] }
*/
export function handleCatalogJson(): void {
const catalogs = getAllResolvedCatalogs();
const result: Record<string, CatalogJsonModel[]> = {};
for (const [provider, catalog] of Object.entries(catalogs)) {
if (!catalog) {
continue;
}
result[provider] = catalog.models.map((m) => {
const entry: CatalogJsonModel = { id: m.id, name: m.name };
if (m.tier !== undefined) entry.tier = m.tier;
if (m.description !== undefined) entry.description = m.description;
if (m.deprecated !== undefined) entry.deprecated = m.deprecated;
if (m.deprecationReason !== undefined) entry.deprecationReason = m.deprecationReason;
if (m.broken !== undefined) entry.broken = m.broken;
if (m.issueUrl !== undefined) entry.issueUrl = m.issueUrl;
if (m.thinking !== undefined) entry.thinking = m.thinking;
if (m.extendedContext !== undefined) entry.extendedContext = m.extendedContext;
if (m.nativeImageInput !== undefined) entry.nativeImageInput = m.nativeImageInput;
return entry;
});
}
console.log(JSON.stringify(result));
}
/** Reset catalog cache */
export async function handleCatalogReset(): Promise<void> {
await initUI();
+1
View File
@@ -39,6 +39,7 @@ export async function showHelp(): Promise<void> {
['catalog', 'Show catalog status, routing hints, and pinned short prefixes'],
['catalog refresh', 'Sync models from remote CLIProxy'],
['catalog reset', 'Clear cache, revert to static catalog'],
['catalog --json', 'Output full model catalog as minified JSON'],
],
],
[
+7
View File
@@ -40,6 +40,7 @@ import {
handleCatalogStatus,
handleCatalogRefresh,
handleCatalogReset,
handleCatalogJson,
} from './catalog-subcommand';
/**
@@ -146,6 +147,12 @@ export async function handleCliproxyCommand(args: string[]): Promise<void> {
// Catalog commands
if (command === 'catalog') {
// --json takes priority over subcommands (refresh/reset) — it always
// outputs the current resolved catalog regardless of other arguments.
if (hasAnyFlag(remainingArgs, ['--json'])) {
handleCatalogJson();
return;
}
const subcommand = remainingArgs[1];
if (subcommand === 'refresh') {
await handleCatalogRefresh(verbose);