diff --git a/docs/cli/models.md b/docs/cli/models.md index 42dccf3149f0..552bd4fa7e27 100644 --- a/docs/cli/models.md +++ b/docs/cli/models.md @@ -28,7 +28,7 @@ openclaw models set-image openclaw models scan ``` -`status`, `list`, and `auth` subcommands accept `--agent ` to target a configured agent; `fallbacks`/`image-fallbacks` always use the configured default agent, and `set`, `set-image`, `scan`, and `aliases` reject `--agent` outright because they are global and never agent-scoped. When omitted, `--agent`-aware commands use `OPENCLAW_AGENT_DIR` if set, otherwise the configured default agent. +`status`, `list`, and `auth` subcommands accept `--agent ` to target a configured agent; `fallbacks`/`image-fallbacks` always use the configured default agent, and `set`, `set-image`, `scan`, `refresh`, and `aliases` reject `--agent` outright because they are global and never agent-scoped. When omitted, `--agent`-aware commands use `OPENCLAW_AGENT_DIR` if set, otherwise the configured default agent. `models set` and `models set-image` require the provider to be declared by an installed plugin or configured under `models.providers`. An unknown provider exits nonzero without changing config. If the provider is known but the model is absent from the local catalog, the command saves the selection and prints a warning because newly released and self-hosted models may not be cataloged yet. `openclaw doctor --json` reports configured unknown providers; add `--severity-min info` to also see active models that the local catalog cannot confirm. @@ -73,7 +73,7 @@ For OpenAI ChatGPT/Codex OAuth troubleshooting, `openclaw models status`, `openc `openclaw models list` is read-only: it reads config, auth profiles, existing catalog state, and provider-owned catalog rows, but never rewrites `models.json`. -`openclaw models refresh [--json]` forces an immediate hosted catalog check. +`openclaw models refresh [--json]` forces an immediate hosted catalog check. Like `scan`, it rejects `--agent` because the hosted catalog is global, not agent-scoped. Updated rows apply to a running Gateway after its next restart. The command prints a clear disabled result when `models.catalogRefresh.enabled` is `false`. The catalog's public change history lives in diff --git a/src/cli/models-cli.runtime.ts b/src/cli/models-cli.runtime.ts index 05539eb3bc75..5f17fae921a5 100644 --- a/src/cli/models-cli.runtime.ts +++ b/src/cli/models-cli.runtime.ts @@ -27,7 +27,8 @@ export type GlobalOnlyModelCommandName = | "scan" | "aliases list" | "aliases add" - | "aliases remove"; + | "aliases remove" + | "refresh"; export function rejectAgentScopedModelCommand( command: Command, diff --git a/src/cli/models-cli.test.ts b/src/cli/models-cli.test.ts index b5a4c3da823d..ffc2b3971a7b 100644 --- a/src/cli/models-cli.test.ts +++ b/src/cli/models-cli.test.ts @@ -11,6 +11,7 @@ const mocks = vi.hoisted(() => ({ modelsStatusCommand: vi.fn().mockResolvedValue(undefined), modelsSetCommand: vi.fn().mockResolvedValue(undefined), modelsSetImageCommand: vi.fn().mockResolvedValue(undefined), + modelsRefreshCommand: vi.fn().mockResolvedValue(undefined), noopAsync: vi.fn(async () => undefined), modelsAliasesAddCommand: vi.fn().mockResolvedValue(undefined), modelsAliasesListCommand: vi.fn().mockResolvedValue(undefined), @@ -42,6 +43,7 @@ const { modelsAuthPasteApiKeyCommand, modelsAuthPasteTokenCommand, modelsAuthSetupTokenCommand, + modelsRefreshCommand, modelsScanCommand, modelsSetCommand, modelsSetImageCommand, @@ -92,6 +94,9 @@ vi.mock("../commands/models/set.js", () => ({ vi.mock("../commands/models/set-image.js", () => ({ modelsSetImageCommand: mocks.modelsSetImageCommand, })); +vi.mock("../commands/models/refresh.js", () => ({ + modelsRefreshCommand: mocks.modelsRefreshCommand, +})); describe("models cli", () => { beforeEach(() => { @@ -99,6 +104,7 @@ describe("models cli", () => { modelsAliasesAddCommand.mockClear(); modelsAliasesListCommand.mockClear(); modelsAliasesRemoveCommand.mockClear(); + modelsRefreshCommand.mockClear(); modelsScanCommand.mockClear(); modelsAuthAddCommand.mockClear(); modelsAuthListCommand.mockClear(); @@ -576,6 +582,11 @@ describe("models cli", () => { args: ["models", "--agent", "poe", "scan", "--no-probe", "--no-input"], command: modelsScanCommand, }, + { + label: "refresh", + args: ["models", "--agent", "poe", "refresh"], + command: modelsRefreshCommand, + }, ])("rejects parent --agent for models $label", async ({ args, command }) => { await expect(runModelsCommand(args)).rejects.toThrow("does not support --agent"); @@ -593,6 +604,11 @@ describe("models cli", () => { args: ["models", "scan", "--no-probe", "--no-input"], command: modelsScanCommand, }, + { + label: "refresh", + args: ["models", "refresh"], + command: modelsRefreshCommand, + }, ])("still runs models $label without --agent", async ({ args, command }) => { await runModelsCommand(args); diff --git a/src/cli/models-cli.ts b/src/cli/models-cli.ts index 21eb450610bc..cc9519965e75 100644 --- a/src/cli/models-cli.ts +++ b/src/cli/models-cli.ts @@ -128,10 +128,12 @@ export function registerModelsCli(program: Command) { .command("refresh") .description("Refresh the hosted model catalog") .option("--json", "Output JSON", false) - .action(async (opts) => { - await withModelsRuntime(async ({ defaultRuntime }) => { + .action(async (opts, command: Command) => { + const runtime = await loadModelsRuntime(); + runtime.rejectAgentScopedModelCommand(command, "refresh"); + await runtime.runModelsCommand(async () => { const { modelsRefreshCommand } = await import("../commands/models/refresh.js"); - await modelsRefreshCommand({ json: hasJsonOutput(opts) }, defaultRuntime); + await modelsRefreshCommand({ json: hasJsonOutput(opts) }, runtime.defaultRuntime); }); });