feat: add models scan and fallbacks

This commit is contained in:
Peter Steinberger
2026-01-04 17:50:55 +01:00
parent a2ba7ddf90
commit 734bb6b4fd
22 changed files with 2058 additions and 187 deletions

198
src/cli/models-cli.ts Normal file
View File

@@ -0,0 +1,198 @@
import type { Command } from "commander";
import {
modelsAliasesAddCommand,
modelsAliasesListCommand,
modelsAliasesRemoveCommand,
modelsFallbacksAddCommand,
modelsFallbacksClearCommand,
modelsFallbacksListCommand,
modelsFallbacksRemoveCommand,
modelsListCommand,
modelsScanCommand,
modelsSetCommand,
modelsStatusCommand,
} from "../commands/models.js";
import { defaultRuntime } from "../runtime.js";
export function registerModelsCli(program: Command) {
const models = program
.command("models")
.description("Model discovery, scanning, and configuration");
models
.command("list")
.description("List models (configured by default)")
.option("--all", "Show full model catalog", false)
.option("--local", "Filter to local models", false)
.option("--provider <name>", "Filter by provider")
.option("--json", "Output JSON", false)
.option("--plain", "Plain line output", false)
.action(async (opts) => {
try {
await modelsListCommand(opts, defaultRuntime);
} catch (err) {
defaultRuntime.error(String(err));
defaultRuntime.exit(1);
}
});
models
.command("status")
.description("Show configured model state")
.option("--json", "Output JSON", false)
.option("--plain", "Plain output", false)
.action(async (opts) => {
try {
await modelsStatusCommand(opts, defaultRuntime);
} catch (err) {
defaultRuntime.error(String(err));
defaultRuntime.exit(1);
}
});
models
.command("set")
.description("Set the default model")
.argument("<model>", "Model id or alias")
.action(async (model: string) => {
try {
await modelsSetCommand(model, defaultRuntime);
} catch (err) {
defaultRuntime.error(String(err));
defaultRuntime.exit(1);
}
});
const aliases = models
.command("aliases")
.description("Manage model aliases");
aliases
.command("list")
.description("List model aliases")
.option("--json", "Output JSON", false)
.option("--plain", "Plain output", false)
.action(async (opts) => {
try {
await modelsAliasesListCommand(opts, defaultRuntime);
} catch (err) {
defaultRuntime.error(String(err));
defaultRuntime.exit(1);
}
});
aliases
.command("add")
.description("Add or update a model alias")
.argument("<alias>", "Alias name")
.argument("<model>", "Model id or alias")
.action(async (alias: string, model: string) => {
try {
await modelsAliasesAddCommand(alias, model, defaultRuntime);
} catch (err) {
defaultRuntime.error(String(err));
defaultRuntime.exit(1);
}
});
aliases
.command("remove")
.description("Remove a model alias")
.argument("<alias>", "Alias name")
.action(async (alias: string) => {
try {
await modelsAliasesRemoveCommand(alias, defaultRuntime);
} catch (err) {
defaultRuntime.error(String(err));
defaultRuntime.exit(1);
}
});
const fallbacks = models
.command("fallbacks")
.description("Manage model fallback list");
fallbacks
.command("list")
.description("List fallback models")
.option("--json", "Output JSON", false)
.option("--plain", "Plain output", false)
.action(async (opts) => {
try {
await modelsFallbacksListCommand(opts, defaultRuntime);
} catch (err) {
defaultRuntime.error(String(err));
defaultRuntime.exit(1);
}
});
fallbacks
.command("add")
.description("Add a fallback model")
.argument("<model>", "Model id or alias")
.action(async (model: string) => {
try {
await modelsFallbacksAddCommand(model, defaultRuntime);
} catch (err) {
defaultRuntime.error(String(err));
defaultRuntime.exit(1);
}
});
fallbacks
.command("remove")
.description("Remove a fallback model")
.argument("<model>", "Model id or alias")
.action(async (model: string) => {
try {
await modelsFallbacksRemoveCommand(model, defaultRuntime);
} catch (err) {
defaultRuntime.error(String(err));
defaultRuntime.exit(1);
}
});
fallbacks
.command("clear")
.description("Clear all fallback models")
.action(async () => {
try {
await modelsFallbacksClearCommand(defaultRuntime);
} catch (err) {
defaultRuntime.error(String(err));
defaultRuntime.exit(1);
}
});
models
.command("scan")
.description("Scan OpenRouter free models for tools + images")
.option("--min-params <b>", "Minimum parameter size (billions)")
.option("--max-age-days <days>", "Skip models older than N days")
.option("--provider <name>", "Filter by provider prefix")
.option("--max-candidates <n>", "Max fallback candidates", "6")
.option("--timeout <ms>", "Per-probe timeout in ms")
.option("--concurrency <n>", "Probe concurrency")
.option("--yes", "Accept defaults without prompting", false)
.option("--no-input", "Disable prompts (use defaults)")
.option("--set-default", "Set agent.model to the first selection", false)
.option("--json", "Output JSON", false)
.action(async (opts) => {
try {
await modelsScanCommand(opts, defaultRuntime);
} catch (err) {
defaultRuntime.error(String(err));
defaultRuntime.exit(1);
}
});
models.action(async () => {
try {
await modelsStatusCommand({}, defaultRuntime);
} catch (err) {
defaultRuntime.error(String(err));
defaultRuntime.exit(1);
}
});
}