import type { Command } from "commander"; import { githubCopilotLoginCommand, modelsAliasesAddCommand, modelsAliasesListCommand, modelsAliasesRemoveCommand, modelsAuthAddCommand, modelsAuthLoginCommand, modelsAuthOrderClearCommand, modelsAuthOrderGetCommand, modelsAuthOrderSetCommand, modelsAuthPasteTokenCommand, modelsAuthSetupTokenCommand, modelsFallbacksAddCommand, modelsFallbacksClearCommand, modelsFallbacksListCommand, modelsFallbacksRemoveCommand, modelsImageFallbacksAddCommand, modelsImageFallbacksClearCommand, modelsImageFallbacksListCommand, modelsImageFallbacksRemoveCommand, modelsListCommand, modelsScanCommand, modelsSetCommand, modelsSetImageCommand, modelsStatusCommand, } from "../commands/models.js"; import { defaultRuntime } from "../runtime.js"; import { formatDocsLink } from "../terminal/links.js"; import { theme } from "../terminal/theme.js"; import { runCommandWithRuntime } from "./cli-utils.js"; function runModelsCommand(action: () => Promise) { return runCommandWithRuntime(defaultRuntime, action); } export function registerModelsCli(program: Command) { const models = program .command("models") .description("Model discovery, scanning, and configuration") .option("--status-json", "Output JSON (alias for `models status --json`)", false) .option("--status-plain", "Plain output (alias for `models status --plain`)", false) .addHelpText( "after", () => `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/models", "docs.clawd.bot/cli/models")}\n`, ); 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 ", "Filter by provider") .option("--json", "Output JSON", false) .option("--plain", "Plain line output", false) .action(async (opts) => { await runModelsCommand(async () => { await modelsListCommand(opts, defaultRuntime); }); }); models .command("status") .description("Show configured model state") .option("--json", "Output JSON", false) .option("--plain", "Plain output", false) .option( "--check", "Exit non-zero if auth is expiring/expired (1=expired/missing, 2=expiring)", false, ) .action(async (opts) => { await runModelsCommand(async () => { await modelsStatusCommand(opts, defaultRuntime); }); }); models .command("set") .description("Set the default model") .argument("", "Model id or alias") .action(async (model: string) => { await runModelsCommand(async () => { await modelsSetCommand(model, defaultRuntime); }); }); models .command("set-image") .description("Set the image model") .argument("", "Model id or alias") .action(async (model: string) => { await runModelsCommand(async () => { await modelsSetImageCommand(model, defaultRuntime); }); }); 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) => { await runModelsCommand(async () => { await modelsAliasesListCommand(opts, defaultRuntime); }); }); aliases .command("add") .description("Add or update a model alias") .argument("", "Alias name") .argument("", "Model id or alias") .action(async (alias: string, model: string) => { await runModelsCommand(async () => { await modelsAliasesAddCommand(alias, model, defaultRuntime); }); }); aliases .command("remove") .description("Remove a model alias") .argument("", "Alias name") .action(async (alias: string) => { await runModelsCommand(async () => { await modelsAliasesRemoveCommand(alias, defaultRuntime); }); }); 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) => { await runModelsCommand(async () => { await modelsFallbacksListCommand(opts, defaultRuntime); }); }); fallbacks .command("add") .description("Add a fallback model") .argument("", "Model id or alias") .action(async (model: string) => { await runModelsCommand(async () => { await modelsFallbacksAddCommand(model, defaultRuntime); }); }); fallbacks .command("remove") .description("Remove a fallback model") .argument("", "Model id or alias") .action(async (model: string) => { await runModelsCommand(async () => { await modelsFallbacksRemoveCommand(model, defaultRuntime); }); }); fallbacks .command("clear") .description("Clear all fallback models") .action(async () => { await runModelsCommand(async () => { await modelsFallbacksClearCommand(defaultRuntime); }); }); const imageFallbacks = models .command("image-fallbacks") .description("Manage image model fallback list"); imageFallbacks .command("list") .description("List image fallback models") .option("--json", "Output JSON", false) .option("--plain", "Plain output", false) .action(async (opts) => { await runModelsCommand(async () => { await modelsImageFallbacksListCommand(opts, defaultRuntime); }); }); imageFallbacks .command("add") .description("Add an image fallback model") .argument("", "Model id or alias") .action(async (model: string) => { await runModelsCommand(async () => { await modelsImageFallbacksAddCommand(model, defaultRuntime); }); }); imageFallbacks .command("remove") .description("Remove an image fallback model") .argument("", "Model id or alias") .action(async (model: string) => { await runModelsCommand(async () => { await modelsImageFallbacksRemoveCommand(model, defaultRuntime); }); }); imageFallbacks .command("clear") .description("Clear all image fallback models") .action(async () => { await runModelsCommand(async () => { await modelsImageFallbacksClearCommand(defaultRuntime); }); }); models .command("scan") .description("Scan OpenRouter free models for tools + images") .option("--min-params ", "Minimum parameter size (billions)") .option("--max-age-days ", "Skip models older than N days") .option("--provider ", "Filter by provider prefix") .option("--max-candidates ", "Max fallback candidates", "6") .option("--timeout ", "Per-probe timeout in ms") .option("--concurrency ", "Probe concurrency") .option("--no-probe", "Skip live probes; list free candidates only") .option("--yes", "Accept defaults without prompting", false) .option("--no-input", "Disable prompts (use defaults)") .option("--set-default", "Set agents.defaults.model to the first selection", false) .option("--set-image", "Set agents.defaults.imageModel to the first image selection", false) .option("--json", "Output JSON", false) .action(async (opts) => { await runModelsCommand(async () => { await modelsScanCommand(opts, defaultRuntime); }); }); models.action(async (opts) => { await runModelsCommand(async () => { await modelsStatusCommand( { json: Boolean(opts?.statusJson), plain: Boolean(opts?.statusPlain), }, defaultRuntime, ); }); }); const auth = models.command("auth").description("Manage model auth profiles"); auth .command("add") .description("Interactive auth helper (setup-token or paste token)") .action(async () => { await runModelsCommand(async () => { await modelsAuthAddCommand({}, defaultRuntime); }); }); auth .command("login") .description("Run a provider plugin auth flow (OAuth/API key)") .option("--provider ", "Provider id registered by a plugin") .option("--method ", "Provider auth method id") .option("--set-default", "Apply the provider's default model recommendation", false) .action(async (opts) => { await runModelsCommand(async () => { await modelsAuthLoginCommand( { provider: opts.provider as string | undefined, method: opts.method as string | undefined, setDefault: Boolean(opts.setDefault), }, defaultRuntime, ); }); }); auth .command("setup-token") .description("Run a provider CLI to create/sync a token (TTY required)") .option("--provider ", "Provider id (default: anthropic)") .option("--yes", "Skip confirmation", false) .action(async (opts) => { await runModelsCommand(async () => { await modelsAuthSetupTokenCommand( { provider: opts.provider as string | undefined, yes: Boolean(opts.yes), }, defaultRuntime, ); }); }); auth .command("paste-token") .description("Paste a token into auth-profiles.json and update config") .requiredOption("--provider ", "Provider id (e.g. anthropic)") .option("--profile-id ", "Auth profile id (default: :manual)") .option( "--expires-in ", "Optional expiry duration (e.g. 365d, 12h). Stored as absolute expiresAt.", ) .action(async (opts) => { await runModelsCommand(async () => { await modelsAuthPasteTokenCommand( { provider: opts.provider as string | undefined, profileId: opts.profileId as string | undefined, expiresIn: opts.expiresIn as string | undefined, }, defaultRuntime, ); }); }); auth .command("login-github-copilot") .description("Login to GitHub Copilot via GitHub device flow (TTY required)") .option("--profile-id ", "Auth profile id (default: github-copilot:github)") .option("--yes", "Overwrite existing profile without prompting", false) .action(async (opts) => { await runModelsCommand(async () => { await githubCopilotLoginCommand( { profileId: opts.profileId as string | undefined, yes: Boolean(opts.yes), }, defaultRuntime, ); }); }); const order = auth.command("order").description("Manage per-agent auth profile order overrides"); order .command("get") .description("Show per-agent auth order override (from auth-profiles.json)") .requiredOption("--provider ", "Provider id (e.g. anthropic)") .option("--agent ", "Agent id (default: configured default agent)") .option("--json", "Output JSON", false) .action(async (opts) => { await runModelsCommand(async () => { await modelsAuthOrderGetCommand( { provider: opts.provider as string, agent: opts.agent as string | undefined, json: Boolean(opts.json), }, defaultRuntime, ); }); }); order .command("set") .description("Set per-agent auth order override (locks rotation to this list)") .requiredOption("--provider ", "Provider id (e.g. anthropic)") .option("--agent ", "Agent id (default: configured default agent)") .argument("", "Auth profile ids (e.g. anthropic:claude-cli)") .action(async (profileIds: string[], opts) => { await runModelsCommand(async () => { await modelsAuthOrderSetCommand( { provider: opts.provider as string, agent: opts.agent as string | undefined, order: profileIds, }, defaultRuntime, ); }); }); order .command("clear") .description("Clear per-agent auth order override (fall back to config/round-robin)") .requiredOption("--provider ", "Provider id (e.g. anthropic)") .option("--agent ", "Agent id (default: configured default agent)") .action(async (opts) => { await runModelsCommand(async () => { await modelsAuthOrderClearCommand( { provider: opts.provider as string, agent: opts.agent as string | undefined, }, defaultRuntime, ); }); }); }