From 346e327586eee54b058d73c12e710db178e00097 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sun, 10 May 2026 05:40:47 +0800 Subject: [PATCH] fix(cli): guide onboarding option errors --- src/cli/gateway-cli/run.ts | 4 ++-- src/commands/configure.gateway-auth.ts | 5 ++++- src/commands/models/aliases.ts | 5 ++++- src/commands/models/fallbacks-shared.ts | 11 ++++++++++- src/commands/models/scan.ts | 5 ++++- src/commands/onboard-non-interactive/api-keys.ts | 5 ++++- .../onboard-non-interactive/local/auth-choice.ts | 5 ++++- 7 files changed, 32 insertions(+), 8 deletions(-) diff --git a/src/cli/gateway-cli/run.ts b/src/cli/gateway-cli/run.ts index 49275ff6c8c..6690eb51943 100644 --- a/src/cli/gateway-cli/run.ts +++ b/src/cli/gateway-cli/run.ts @@ -477,7 +477,7 @@ async function runGatewayCommand(opts: GatewayRunOpts) { wsLogRaw !== "compact" && wsLogRaw !== "full" ) { - defaultRuntime.error('Invalid --ws-log (use "auto", "full", "compact")'); + defaultRuntime.error('Invalid --ws-log. Use "auto", "full", or "compact".'); defaultRuntime.exit(1); } setGatewayWsLogStyle(wsLogStyle); @@ -560,7 +560,7 @@ async function runGatewayCommand(opts: GatewayRunOpts) { toOptionString(opts.bind) ?? cfg.gateway?.bind, ); if (bindExplicitRawStr !== undefined && !VALID_BIND_MODES.has(bindExplicitRawStr)) { - defaultRuntime.error('Invalid --bind (use "loopback", "lan", "tailnet", "auto", or "custom")'); + defaultRuntime.error('Invalid --bind. Use "loopback", "lan", "tailnet", "auto", or "custom".'); defaultRuntime.exit(1); return; } diff --git a/src/commands/configure.gateway-auth.ts b/src/commands/configure.gateway-auth.ts index 4156d210d12..b1fec347593 100644 --- a/src/commands/configure.gateway-auth.ts +++ b/src/commands/configure.gateway-auth.ts @@ -1,5 +1,6 @@ import { ensureAuthProfileStore } from "../agents/auth-profiles.js"; import { resolveDefaultAgentWorkspaceDir } from "../agents/workspace.js"; +import { formatCliCommand } from "../cli/command-format.js"; import type { OpenClawConfig, GatewayAuthConfig } from "../config/config.js"; import { isSecretRef, type SecretInput } from "../config/types.secrets.js"; import type { RuntimeEnv } from "../runtime.js"; @@ -161,7 +162,9 @@ export function buildGatewayAuthConfig(params: { } if (params.mode === "trusted-proxy") { if (!params.trustedProxy) { - throw new Error("trustedProxy config is required when mode is trusted-proxy"); + throw new Error( + `trustedProxy config is required when mode is trusted-proxy. Run ${formatCliCommand("openclaw configure --section gateway")} to configure Gateway auth interactively.`, + ); } return { ...base, mode: "trusted-proxy", trustedProxy: params.trustedProxy }; } diff --git a/src/commands/models/aliases.ts b/src/commands/models/aliases.ts index c265b13fadf..84e310537a2 100644 --- a/src/commands/models/aliases.ts +++ b/src/commands/models/aliases.ts @@ -1,3 +1,4 @@ +import { formatCliCommand } from "../../cli/command-format.js"; import { logConfigUpdated } from "../../config/logging.js"; import { type RuntimeEnv, writeRuntimeJson } from "../../runtime.js"; import { loadModelsConfig } from "./load-config.js"; @@ -95,7 +96,9 @@ export async function modelsAliasesRemoveCommand(aliasRaw: string, runtime: Runt } } if (!found) { - throw new Error(`Alias not found: ${alias}`); + throw new Error( + `Alias not found: ${alias}. Run ${formatCliCommand("openclaw models aliases list")} to see configured aliases.`, + ); } return { ...cfg, diff --git a/src/commands/models/fallbacks-shared.ts b/src/commands/models/fallbacks-shared.ts index 9e1100ddb29..f344376930e 100644 --- a/src/commands/models/fallbacks-shared.ts +++ b/src/commands/models/fallbacks-shared.ts @@ -1,4 +1,5 @@ import { buildModelAliasIndex, resolveModelRefFromString } from "../../agents/model-selection.js"; +import { formatCliCommand } from "../../cli/command-format.js"; import { logConfigUpdated } from "../../config/logging.js"; import { resolveAgentModelFallbackValues, toAgentModelListLike } from "../../config/model-input.js"; import type { AgentModelEntryConfig } from "../../config/types.agent-defaults.js"; @@ -18,6 +19,12 @@ import { type DefaultsFallbackKey = "model" | "imageModel"; +function listCommandForFallbackKey(key: DefaultsFallbackKey): string { + return key === "imageModel" + ? "openclaw models image-fallbacks list" + : "openclaw models fallbacks list"; +} + function getFallbacks(cfg: OpenClawConfig, key: DefaultsFallbackKey): string[] { return resolveAgentModelFallbackValues(cfg.agents?.defaults?.[key]); } @@ -133,7 +140,9 @@ export async function removeFallbackCommand( }); if (filtered.length === existing.length) { - throw new Error(`${params.notFoundLabel} not found: ${targetKey}`); + throw new Error( + `${params.notFoundLabel} not found: ${targetKey}. Run ${formatCliCommand(listCommandForFallbackKey(params.key))} to see configured fallbacks.`, + ); } return patchDefaultsFallbacks(cfg, { key: params.key, fallbacks: filtered }); diff --git a/src/commands/models/scan.ts b/src/commands/models/scan.ts index f5d72215093..0c7484bae82 100644 --- a/src/commands/models/scan.ts +++ b/src/commands/models/scan.ts @@ -2,6 +2,7 @@ import { cancel, multiselect as clackMultiselect, isCancel } from "@clack/prompt import { getEnvApiKey } from "@mariozechner/pi-ai"; import { resolveApiKeyForProvider } from "../../agents/model-auth.js"; import { type ModelScanResult, scanOpenRouterModels } from "../../agents/model-scan.js"; +import { formatCliCommand } from "../../cli/command-format.js"; import { withProgressTotals } from "../../cli/progress.js"; import { logConfigUpdated } from "../../config/logging.js"; import { toAgentModelListLike } from "../../config/model-input.js"; @@ -266,7 +267,9 @@ export async function modelsScanCommand( const toolOk = results.filter((entry) => entry.tool.ok); if (toolOk.length === 0) { - throw new Error("No tool-capable OpenRouter free models found."); + throw new Error( + `No tool-capable OpenRouter free models found. Try ${formatCliCommand("openclaw models scan --no-probe")} to inspect metadata-only candidates, or configure OPENROUTER_API_KEY before probing.`, + ); } const sorted = sortScanResults(results); diff --git a/src/commands/onboard-non-interactive/api-keys.ts b/src/commands/onboard-non-interactive/api-keys.ts index 164dddffa27..aa16913affe 100644 --- a/src/commands/onboard-non-interactive/api-keys.ts +++ b/src/commands/onboard-non-interactive/api-keys.ts @@ -4,6 +4,7 @@ import { resolveAuthProfileOrder, } from "../../agents/auth-profiles.js"; import { resolveEnvApiKey } from "../../agents/model-auth.js"; +import { formatCliCommand } from "../../cli/command-format.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; import type { RuntimeEnv } from "../../runtime.js"; import { normalizeOptionalSecretInput } from "../../utils/normalize-secret-input.js"; @@ -134,7 +135,9 @@ export async function resolveNonInteractiveApiKey(params: { const profileHint = params.allowProfile === false ? "" : `, or existing ${params.provider} API-key profile`; - params.runtime.error(`Missing ${params.flagName} (or ${params.envVar} in env${profileHint}).`); + params.runtime.error( + `Missing ${params.flagName} (or ${params.envVar} in env${profileHint}). Export ${params.envVar}, pass ${params.flagName}, or run ${formatCliCommand("openclaw onboard")} for interactive setup.`, + ); params.runtime.exit(1); return null; } diff --git a/src/commands/onboard-non-interactive/local/auth-choice.ts b/src/commands/onboard-non-interactive/local/auth-choice.ts index f56d7995798..7de0a0e075b 100644 --- a/src/commands/onboard-non-interactive/local/auth-choice.ts +++ b/src/commands/onboard-non-interactive/local/auth-choice.ts @@ -1,4 +1,5 @@ import type { ApiKeyCredential } from "../../../agents/auth-profiles/types.js"; +import { formatCliCommand } from "../../../cli/command-format.js"; import type { OpenClawConfig } from "../../../config/types.openclaw.js"; import type { SecretInput } from "../../../config/types.secrets.js"; import { formatErrorMessage } from "../../../infra/errors.js"; @@ -42,7 +43,9 @@ export async function applyNonInteractiveAuthChoice(params: { let nextConfig = params.nextConfig; const requestedSecretInputMode = normalizeSecretInputModeInput(opts.secretInputMode); if (opts.secretInputMode && !requestedSecretInputMode) { - runtime.error('Invalid --secret-input-mode. Use "plaintext" or "ref".'); + runtime.error( + `Invalid --secret-input-mode. Use "plaintext" or "ref", or run ${formatCliCommand("openclaw onboard")} for interactive setup.`, + ); runtime.exit(1); return null; }