mirror of
https://github.com/openclaw/openclaw.git
synced 2026-10-03 09:39:25 +00:00
Related: #136257 ## What Problem This Solves Fixes an issue where activating a verified replacement sign-in left existing and fresh chats using the old credential, even after a restart. An operator report on September 11 also found misleading model-access feedback, a “Replace key” action that could add an account, and missing command-line activation after unattended setup. ## Why This Change Was Made The shared credential selector now resolves the activated account for normal replies, `/btw` side questions, and worker inference, preserving explicit account choices and different-model behavior. Duplicate caller logic is removed. Activation also compares normalized runtime settings consistently, so omitted optional model fields do not cause a false connection-change error after saving. The existing setup owner also serves the new `openclaw models auth activate` command; both activation prompts default to Yes after verification. Saved model access is reported separately from application by the running Gateway, and the provider-wide key action is labeled accurately. ## User Impact - Automatic chats and `/btw` side questions use the activated account; explicit account pins retain precedence. - Unattended replacement setup prints a command that tests and activates the saved sign-in. - Enter accepts a successfully verified connection. - Saved-but-unapplied settings include recovery guidance instead of a failed-save message. ## Evidence - Real Gateway and recording-provider requests: old credential before; replacement afterward in existing and fresh chats. Explicit pins and a different model retain their expected account selection. - Real Telegram `/btw`: the activated replacement handles an existing automatic chat, while an explicit old-account pin still uses the old account. The same flow first exposed a false activation error for sparse saved model settings; the corrected flow succeeds. - Independent public CLI/browser validation confirms the printed activation command, Enter-to-activate, and the key editor. - Real Telegram Test Server: “Show all” saves the choice in both cases; disabled reload reports saved/unconfirmed, while enabled reload reports visible models. - The initial fixes passed 377 focused tests across nine files. The caller correction passed 309 tests across five files, including `/btw`, normal replies, worker inference, and person-linked account pins. The configured-account regression fails on the pinned base. A further 17 activation/acknowledgement tests pass, including sparse saved settings and rejection of real concurrent changes. - All 17 sanitized operator-config shapes reach Gateway readiness. The Tailscale-dependent fixture uses the supported per-run exposure override on the Linux test host; Tailscale Serve itself is not claimed. - Synthetic provider credentials only. No database, migration, configuration-key, or protocol changes in this PR. | Before | After | | --- | --- | |  |  | Captures are cropped to the changed control and exclude account identifiers. Additional catalog check: sign-in completes through the real xAI plugin against a simulated provider, but its existing endpoint metadata blocks live OAuth catalog discovery. That plugin is unchanged here. New-model discovery, first-account onboarding, and real vendor entitlement are not claimed by this PR. Co-authored-by: Ayaan Zaidi <hi@obviy.us>
586 lines
23 KiB
TypeScript
586 lines
23 KiB
TypeScript
// Commander registration for model catalog, status, auth, alias, and fallback commands.
|
|
import type { Command } from "commander";
|
|
import { formatDocsLink } from "../../packages/terminal-core/src/links.js";
|
|
import { theme } from "../../packages/terminal-core/src/theme.js";
|
|
import { registerModelsAccountsCli } from "./models-accounts-cli.js";
|
|
import { isModelsStatusJsonOutput } from "./models-output-mode.js";
|
|
import { setCommandJsonMode } from "./program/json-mode.js";
|
|
|
|
type ModelsCliRuntime = typeof import("./models-cli.runtime.js");
|
|
|
|
function createModuleLoader<T>(load: () => Promise<T>): () => Promise<T> {
|
|
// Model subcommands are heavy; load each implementation once on first use.
|
|
let promise: Promise<T> | undefined;
|
|
return () => (promise ??= load());
|
|
}
|
|
|
|
const loadModelsRuntime = createModuleLoader<ModelsCliRuntime>(
|
|
() => import("./models-cli.runtime.js"),
|
|
);
|
|
const loadModelsStatusCommands = createModuleLoader(
|
|
() => import("../commands/models/list.status-command.js"),
|
|
);
|
|
const loadModelsAliasesCommands = createModuleLoader(() => import("../commands/models/aliases.js"));
|
|
const loadModelsFallbacksCommands = createModuleLoader(
|
|
() => import("../commands/models/fallbacks-shared.js"),
|
|
);
|
|
const loadModelsAuthCommands = createModuleLoader(() => import("../commands/models/auth.js"));
|
|
const loadModelsAuthOrderCommands = createModuleLoader(
|
|
() => import("../commands/models/auth-order.js"),
|
|
);
|
|
|
|
async function withModelsRuntime(
|
|
action: (runtime: ModelsCliRuntime) => Promise<void>,
|
|
): Promise<void> {
|
|
const runtime = await loadModelsRuntime();
|
|
return runtime.runModelsCommand(() => action(runtime));
|
|
}
|
|
|
|
export function registerModelsCli(program: Command) {
|
|
const models = program
|
|
.command("models")
|
|
.description("Model discovery, scanning, and configuration")
|
|
.option("--json", "Output JSON (alias for `models status --json`)", false)
|
|
.option("--status-json", "Output JSON (alias for `models status --json`)", false)
|
|
.option("--status-plain", "Plain output (alias for `models status --plain`)", false)
|
|
.option("--agent <id>", "Agent id to inspect (overrides OPENCLAW_AGENT_DIR)")
|
|
.addHelpText(
|
|
"after",
|
|
() =>
|
|
`\n${theme.muted("Docs:")} ${formatDocsLink("/cli/models", "docs.openclaw.ai/cli/models")}\n`,
|
|
);
|
|
const hasJsonOutput = (opts?: { json?: boolean }): boolean =>
|
|
Boolean(opts?.json || models.opts<{ json?: boolean }>().json);
|
|
setCommandJsonMode(models, "output", ({ argv, command }) =>
|
|
isModelsStatusJsonOutput(argv, command),
|
|
);
|
|
registerModelsAccountsCli(models);
|
|
|
|
models
|
|
.command("list")
|
|
.description("List models (configured by default)")
|
|
.option("--refresh", "Refresh provider discovery before listing", false)
|
|
.option("--all", "Show full model catalog", false)
|
|
.option("--local", "Filter to local models", false)
|
|
.option("--provider <id>", "Filter by provider id")
|
|
.option("--agent <id>", "Agent id to inspect (overrides OPENCLAW_AGENT_DIR)")
|
|
.option("--json", "Output JSON", false)
|
|
.option("--plain", "Plain line output", false)
|
|
.action(async (opts, command) => {
|
|
await withModelsRuntime(async ({ defaultRuntime, resolveModelAgentOption }) => {
|
|
const { modelsListCommand } = await import("../commands/models/list.list-command.js");
|
|
await modelsListCommand(
|
|
{
|
|
...opts,
|
|
json: hasJsonOutput(opts),
|
|
agent: resolveModelAgentOption(command, opts),
|
|
},
|
|
defaultRuntime,
|
|
);
|
|
});
|
|
});
|
|
|
|
models
|
|
.command("status")
|
|
.description("Show configured model state")
|
|
.option("--json", "Output JSON", false)
|
|
.option("--plain", "Plain output", false)
|
|
.option(
|
|
"--check",
|
|
"Check auth/runtime readiness (1=missing/expired/unavailable/incompatible/indeterminate, 2=expiring)",
|
|
false,
|
|
)
|
|
.option("--probe", "Probe configured provider auth (live)", false)
|
|
.option("--probe-provider <name>", "Only probe a single provider")
|
|
.option(
|
|
"--probe-profile <id>",
|
|
"Only probe specific auth profile ids (repeat or comma-separated)",
|
|
(value, previous) => {
|
|
const next = Array.isArray(previous) ? previous : previous ? [previous] : [];
|
|
next.push(value);
|
|
return next;
|
|
},
|
|
)
|
|
.option("--probe-timeout <ms>", "Per-probe timeout in ms")
|
|
.option("--probe-concurrency <n>", "Concurrent probes")
|
|
.option("--probe-max-tokens <n>", "Probe max tokens (best-effort)")
|
|
.option("--agent <id>", "Agent id to inspect (overrides OPENCLAW_AGENT_DIR)")
|
|
.action(async (opts, command) => {
|
|
await withModelsRuntime(async ({ defaultRuntime, resolveModelAgentOption }) => {
|
|
const agent = resolveModelAgentOption(command, opts);
|
|
const { modelsStatusCommand } = await loadModelsStatusCommands();
|
|
await modelsStatusCommand(
|
|
{
|
|
json: hasJsonOutput(opts),
|
|
plain: Boolean(opts.plain),
|
|
check: Boolean(opts.check),
|
|
probe: Boolean(opts.probe),
|
|
probeProvider: opts.probeProvider as string | undefined,
|
|
probeProfile: opts.probeProfile as string | string[] | undefined,
|
|
probeTimeout: opts.probeTimeout as string | undefined,
|
|
probeConcurrency: opts.probeConcurrency as string | undefined,
|
|
probeMaxTokens: opts.probeMaxTokens as string | undefined,
|
|
agent,
|
|
},
|
|
defaultRuntime,
|
|
);
|
|
});
|
|
});
|
|
|
|
models
|
|
.command("refresh")
|
|
.description("Refresh the hosted model catalog")
|
|
.option("--json", "Output JSON", false)
|
|
.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) }, runtime.defaultRuntime);
|
|
});
|
|
});
|
|
|
|
models
|
|
.command("set")
|
|
.description("Set the default model")
|
|
.argument("<model>", "Model id or alias")
|
|
.action(async (model: string, _opts: unknown, command: Command) => {
|
|
const runtime = await loadModelsRuntime();
|
|
runtime.rejectAgentScopedModelCommand(command, "set");
|
|
await runtime.runModelsCommand(async () => {
|
|
const { modelsSetCommand } = await import("../commands/models/set.js");
|
|
await modelsSetCommand(model, runtime.defaultRuntime);
|
|
});
|
|
});
|
|
|
|
models
|
|
.command("set-image")
|
|
.description("Set the image model")
|
|
.argument("<model>", "Model id or alias")
|
|
.action(async (model: string, _opts: unknown, command: Command) => {
|
|
const runtime = await loadModelsRuntime();
|
|
runtime.rejectAgentScopedModelCommand(command, "set-image");
|
|
await runtime.runModelsCommand(async () => {
|
|
const { modelsSetImageCommand } = await import("../commands/models/set-image.js");
|
|
await modelsSetImageCommand(model, runtime.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, command: Command) => {
|
|
const runtime = await loadModelsRuntime();
|
|
runtime.rejectAgentScopedModelCommand(command, "aliases list");
|
|
await runtime.runModelsCommand(async () => {
|
|
const { modelsAliasesListCommand } = await loadModelsAliasesCommands();
|
|
await modelsAliasesListCommand(
|
|
{ ...opts, json: hasJsonOutput(opts) },
|
|
runtime.defaultRuntime,
|
|
);
|
|
});
|
|
});
|
|
|
|
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, _opts: unknown, command: Command) => {
|
|
const runtime = await loadModelsRuntime();
|
|
runtime.rejectAgentScopedModelCommand(command, "aliases add");
|
|
await runtime.runModelsCommand(async () => {
|
|
const { modelsAliasesAddCommand } = await loadModelsAliasesCommands();
|
|
await modelsAliasesAddCommand(alias, model, runtime.defaultRuntime);
|
|
});
|
|
});
|
|
|
|
aliases
|
|
.command("remove")
|
|
.description("Remove a model alias")
|
|
.argument("<alias>", "Alias name")
|
|
.action(async (alias: string, _opts: unknown, command: Command) => {
|
|
const runtime = await loadModelsRuntime();
|
|
runtime.rejectAgentScopedModelCommand(command, "aliases remove");
|
|
await runtime.runModelsCommand(async () => {
|
|
const { modelsAliasesRemoveCommand } = await loadModelsAliasesCommands();
|
|
await modelsAliasesRemoveCommand(alias, runtime.defaultRuntime);
|
|
});
|
|
});
|
|
|
|
const fallbackGroups = [
|
|
{
|
|
name: "fallbacks",
|
|
modelType: "model",
|
|
noun: "fallback",
|
|
article: "a",
|
|
key: "model",
|
|
label: "Fallbacks",
|
|
notFoundLabel: "Fallback",
|
|
clearedMessage: "Fallback list cleared.",
|
|
},
|
|
{
|
|
name: "image-fallbacks",
|
|
modelType: "image model",
|
|
noun: "image fallback",
|
|
article: "an",
|
|
key: "imageModel",
|
|
label: "Image fallbacks",
|
|
notFoundLabel: "Image fallback",
|
|
clearedMessage: "Image fallback list cleared.",
|
|
},
|
|
] as const;
|
|
|
|
for (const params of fallbackGroups) {
|
|
const { name, modelType, noun, article } = params;
|
|
const group = models.command(name).description(`Manage ${modelType} fallback list`);
|
|
|
|
group
|
|
.command("list")
|
|
.description(`List ${noun} models`)
|
|
.option("--json", "Output JSON", false)
|
|
.option("--plain", "Plain output", false)
|
|
.action(async (opts) => {
|
|
await withModelsRuntime(async ({ defaultRuntime }) => {
|
|
const { listFallbacksCommand } = await loadModelsFallbacksCommands();
|
|
await listFallbacksCommand(
|
|
params,
|
|
{ ...opts, json: hasJsonOutput(opts) },
|
|
defaultRuntime,
|
|
);
|
|
});
|
|
});
|
|
|
|
for (const [action, handler] of [
|
|
["add", "addFallbackCommand"],
|
|
["remove", "removeFallbackCommand"],
|
|
] as const) {
|
|
group
|
|
.command(action)
|
|
.description(`${action === "add" ? "Add" : "Remove"} ${article} ${noun} model`)
|
|
.argument("<model>", "Model id or alias")
|
|
.action(async (model: string) => {
|
|
await withModelsRuntime(async ({ defaultRuntime }) => {
|
|
const commands = await loadModelsFallbacksCommands();
|
|
await commands[handler](params, model, defaultRuntime);
|
|
});
|
|
});
|
|
}
|
|
|
|
group
|
|
.command("clear")
|
|
.description(`Clear all ${noun} models`)
|
|
.action(async () => {
|
|
await withModelsRuntime(async ({ defaultRuntime }) => {
|
|
const { clearFallbacksCommand } = await loadModelsFallbacksCommands();
|
|
await clearFallbacksCommand(params, defaultRuntime);
|
|
});
|
|
});
|
|
}
|
|
|
|
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("--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, command: Command) => {
|
|
const runtime = await loadModelsRuntime();
|
|
runtime.rejectAgentScopedModelCommand(command, "scan");
|
|
await runtime.runModelsCommand(async () => {
|
|
const { modelsScanCommand } = await import("../commands/models/scan.js");
|
|
await modelsScanCommand({ ...opts, json: hasJsonOutput(opts) }, runtime.defaultRuntime);
|
|
});
|
|
});
|
|
|
|
models.action(async (opts) => {
|
|
await withModelsRuntime(async ({ defaultRuntime }) => {
|
|
const { modelsStatusCommand } = await loadModelsStatusCommands();
|
|
await modelsStatusCommand(
|
|
{
|
|
json: Boolean(opts?.json || opts?.statusJson),
|
|
plain: Boolean(opts?.statusPlain),
|
|
agent: opts?.agent as string | undefined,
|
|
},
|
|
defaultRuntime,
|
|
);
|
|
});
|
|
});
|
|
|
|
const auth = models
|
|
.command("auth")
|
|
.description("Manage system/agent credentials on this machine");
|
|
auth.option("--agent <id>", "Agent id for auth commands");
|
|
auth.action(() => {
|
|
auth.help();
|
|
});
|
|
|
|
auth
|
|
.command("list")
|
|
.description("List saved auth profiles")
|
|
.option("--provider <id>", "Filter by provider id")
|
|
.option("--agent <id>", "Agent id (default: configured system agent)")
|
|
.option("--json", "Output JSON", false)
|
|
.action(async (opts, command) => {
|
|
await withModelsRuntime(async ({ defaultRuntime, resolveModelAgentOption }) => {
|
|
const agent = resolveModelAgentOption(command, opts);
|
|
const { modelsAuthListCommand } = await import("../commands/models/auth-list.js");
|
|
await modelsAuthListCommand(
|
|
{
|
|
provider: opts.provider as string | undefined,
|
|
agent,
|
|
json: hasJsonOutput(opts),
|
|
},
|
|
defaultRuntime,
|
|
);
|
|
});
|
|
});
|
|
|
|
auth
|
|
.command("add")
|
|
.description("Interactive auth helper (provider auth or paste token)")
|
|
.option("--agent <id>", "Agent id (default: configured default agent)")
|
|
.action(async (opts, command) => {
|
|
await withModelsRuntime(async ({ defaultRuntime, resolveModelAgentOption }) => {
|
|
const agent = resolveModelAgentOption(command, opts);
|
|
const { modelsAuthAddCommand } = await loadModelsAuthCommands();
|
|
await modelsAuthAddCommand({ agent }, defaultRuntime);
|
|
});
|
|
});
|
|
|
|
auth
|
|
.command("activate")
|
|
.description("Test a saved sign-in and use it for this agent")
|
|
.argument("<profileId>", "Saved sign-in id from models auth list")
|
|
.option("--agent <id>", "Agent id (default: the only configured agent)")
|
|
.action(async (profileId: string, opts, command) => {
|
|
await withModelsRuntime(async ({ defaultRuntime, resolveModelAgentOption }) => {
|
|
const agent = resolveModelAgentOption(command, opts);
|
|
const { modelsAuthActivateCommand } = await import("../commands/models/auth-activate.js");
|
|
await modelsAuthActivateCommand({ profileId, agent }, defaultRuntime);
|
|
});
|
|
});
|
|
|
|
auth
|
|
.command("logout")
|
|
.description("Remove a saved auth profile (see `models auth list` for ids)")
|
|
.argument("<profileId>", "Auth profile id (e.g. openai:manual)")
|
|
.option("--agent <id>", "Agent id (default: configured default agent)")
|
|
.option("--yes", "Skip the confirmation prompt", false)
|
|
.action(async (profileId: string, opts, command) => {
|
|
await withModelsRuntime(async ({ defaultRuntime, resolveModelAgentOption }) => {
|
|
const agent = resolveModelAgentOption(command, opts);
|
|
const { modelsAuthLogoutCommand } = await import("../commands/models/auth-logout.js");
|
|
await modelsAuthLogoutCommand(
|
|
{
|
|
profileId,
|
|
agent,
|
|
yes: Boolean(opts.yes),
|
|
},
|
|
defaultRuntime,
|
|
);
|
|
});
|
|
});
|
|
|
|
auth
|
|
.command("login")
|
|
.description("Sign in for system/agent use on this machine (OAuth/API key)")
|
|
.option("--agent <id>", "Agent id (default: configured default agent)")
|
|
.option("--provider <id>", "Provider id registered by a plugin")
|
|
.option("--method <id>", "Provider auth method id")
|
|
.option("--device-code", "Use the provider device-code auth method", false)
|
|
.option("--profile-id <id>", "Auth profile id override for single-profile login methods")
|
|
.option("--set-default", "Apply the provider's default model recommendation", false)
|
|
.option(
|
|
"--force",
|
|
"Remove existing profiles for the provider before logging in (use when a cached OAuth profile is stuck or you want to switch accounts)",
|
|
false,
|
|
)
|
|
.action(async (opts, command) => {
|
|
if (opts.deviceCode && typeof opts.method === "string" && opts.method !== "device-code") {
|
|
throw new Error(
|
|
"--device-code cannot be combined with --method unless method is device-code.",
|
|
);
|
|
}
|
|
await withModelsRuntime(async ({ defaultRuntime, resolveModelAgentOption }) => {
|
|
const agent = resolveModelAgentOption(command);
|
|
const { modelsAuthLoginCommand } = await loadModelsAuthCommands();
|
|
await modelsAuthLoginCommand(
|
|
{
|
|
provider: opts.provider as string | undefined,
|
|
method: opts.deviceCode ? "device-code" : (opts.method as string | undefined),
|
|
profileId: opts.profileId as string | undefined,
|
|
setDefault: Boolean(opts.setDefault),
|
|
force: Boolean(opts.force),
|
|
agent,
|
|
},
|
|
defaultRuntime,
|
|
);
|
|
});
|
|
});
|
|
|
|
auth
|
|
.command("setup-token")
|
|
.description("Run a provider CLI to create/sync a token (TTY required)")
|
|
.option("--agent <id>", "Agent id (default: configured default agent)")
|
|
.option("--provider <name>", "Provider id")
|
|
.option("--yes", "Skip confirmation", false)
|
|
.action(async (opts, command) => {
|
|
await withModelsRuntime(async ({ defaultRuntime, resolveModelAgentOption }) => {
|
|
const agent = resolveModelAgentOption(command);
|
|
const { modelsAuthSetupTokenCommand } = await loadModelsAuthCommands();
|
|
await modelsAuthSetupTokenCommand(
|
|
{
|
|
provider: opts.provider as string | undefined,
|
|
yes: Boolean(opts.yes),
|
|
agent,
|
|
},
|
|
defaultRuntime,
|
|
);
|
|
});
|
|
});
|
|
|
|
auth
|
|
.command("paste-token")
|
|
.description("Save a token in an auth profile and update config")
|
|
.option("--agent <id>", "Agent id (default: configured default agent)")
|
|
.requiredOption("--provider <name>", "Provider id (e.g. anthropic)")
|
|
.option("--profile-id <id>", "Auth profile id (default: <provider>:manual)")
|
|
.option(
|
|
"--expires-in <duration>",
|
|
"Optional expiry duration (e.g. 365d, 12h). Stored as absolute expiresAt.",
|
|
)
|
|
.action(async (opts, command) => {
|
|
await withModelsRuntime(async ({ defaultRuntime, resolveModelAgentOption }) => {
|
|
const agent = resolveModelAgentOption(command);
|
|
const { modelsAuthPasteTokenCommand } = await loadModelsAuthCommands();
|
|
await modelsAuthPasteTokenCommand(
|
|
{
|
|
provider: opts.provider as string | undefined,
|
|
profileId: opts.profileId as string | undefined,
|
|
expiresIn: opts.expiresIn as string | undefined,
|
|
agent,
|
|
},
|
|
defaultRuntime,
|
|
);
|
|
});
|
|
});
|
|
|
|
auth
|
|
.command("paste-api-key")
|
|
.description("Save an API key in an auth profile and update config")
|
|
.option("--agent <id>", "Agent id (default: configured default agent)")
|
|
.requiredOption("--provider <name>", "Provider id (e.g. openai)")
|
|
.option("--profile-id <id>", "Auth profile id (default: <provider>:manual)")
|
|
.action(async (opts, command) => {
|
|
await withModelsRuntime(async ({ defaultRuntime, resolveModelAgentOption }) => {
|
|
const agent = resolveModelAgentOption(command);
|
|
const { modelsAuthPasteApiKeyCommand } = await loadModelsAuthCommands();
|
|
await modelsAuthPasteApiKeyCommand(
|
|
{
|
|
provider: opts.provider as string | undefined,
|
|
profileId: opts.profileId as string | undefined,
|
|
agent,
|
|
},
|
|
defaultRuntime,
|
|
);
|
|
});
|
|
});
|
|
|
|
auth
|
|
.command("login-github-copilot")
|
|
.description("Login to GitHub Copilot via GitHub device flow (TTY required)")
|
|
.option("--agent <id>", "Agent id (default: configured default agent)")
|
|
.option("--yes", "Overwrite existing profile without prompting", false)
|
|
.action(async (opts, command) => {
|
|
await withModelsRuntime(async ({ defaultRuntime, resolveModelAgentOption }) => {
|
|
const agent = resolveModelAgentOption(command);
|
|
const { modelsAuthLoginCommand } = await loadModelsAuthCommands();
|
|
await modelsAuthLoginCommand(
|
|
{
|
|
provider: "github-copilot",
|
|
method: "device",
|
|
yes: Boolean(opts.yes),
|
|
agent,
|
|
},
|
|
defaultRuntime,
|
|
);
|
|
});
|
|
});
|
|
|
|
const order = auth.command("order").description("Manage per-agent auth profile order overrides");
|
|
|
|
order
|
|
.command("get")
|
|
.description("Show per-agent auth profile order override")
|
|
.requiredOption("--provider <name>", "Provider id (e.g. anthropic)")
|
|
.option("--agent <id>", "Agent id (default: configured system agent)")
|
|
.option("--json", "Output JSON", false)
|
|
.action(async (opts, command) => {
|
|
await withModelsRuntime(async ({ defaultRuntime, resolveModelAgentOption }) => {
|
|
const agent = resolveModelAgentOption(command, opts);
|
|
const { modelsAuthOrderGetCommand } = await loadModelsAuthOrderCommands();
|
|
await modelsAuthOrderGetCommand(
|
|
{
|
|
provider: opts.provider as string,
|
|
agent,
|
|
json: hasJsonOutput(opts),
|
|
},
|
|
defaultRuntime,
|
|
);
|
|
});
|
|
});
|
|
|
|
order
|
|
.command("set")
|
|
.description("Set per-agent auth profile order override")
|
|
.requiredOption("--provider <name>", "Provider id (e.g. anthropic)")
|
|
.option("--agent <id>", "Agent id (default: configured default agent)")
|
|
.argument("<profileIds...>", "Auth profile ids (e.g. anthropic:default)")
|
|
.action(async (profileIds: string[], opts, command) => {
|
|
await withModelsRuntime(async ({ defaultRuntime, resolveModelAgentOption }) => {
|
|
const agent = resolveModelAgentOption(command, opts);
|
|
const { modelsAuthOrderSetCommand } = await loadModelsAuthOrderCommands();
|
|
await modelsAuthOrderSetCommand(
|
|
{
|
|
provider: opts.provider as string,
|
|
agent,
|
|
order: profileIds,
|
|
},
|
|
defaultRuntime,
|
|
);
|
|
});
|
|
});
|
|
|
|
order
|
|
.command("clear")
|
|
.description("Clear per-agent auth profile order override")
|
|
.requiredOption("--provider <name>", "Provider id (e.g. anthropic)")
|
|
.option("--agent <id>", "Agent id (default: configured default agent)")
|
|
.action(async (opts, command) => {
|
|
await withModelsRuntime(async ({ defaultRuntime, resolveModelAgentOption }) => {
|
|
const agent = resolveModelAgentOption(command, opts);
|
|
const { modelsAuthOrderClearCommand } = await loadModelsAuthOrderCommands();
|
|
await modelsAuthOrderClearCommand(
|
|
{
|
|
provider: opts.provider as string,
|
|
agent,
|
|
},
|
|
defaultRuntime,
|
|
);
|
|
});
|
|
});
|
|
}
|