From 858a4471bd39c823a871538cf8116a21613bad97 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sat, 9 May 2026 08:10:43 +0800 Subject: [PATCH] fix(cli): clarify operator command errors --- src/commands/channels/capabilities.ts | 19 ++++++++++++++++--- src/commands/commitments.ts | 13 ++++++++++--- src/commands/configure.commands.ts | 3 ++- src/commands/onboard.ts | 8 ++++++-- src/commands/sandbox.ts | 16 ++++++++++++---- src/commands/uninstall.ts | 25 +++++++++++++++++++------ 6 files changed, 65 insertions(+), 19 deletions(-) diff --git a/src/commands/channels/capabilities.ts b/src/commands/channels/capabilities.ts index 70e2a5a73b2..6ac3079664c 100644 --- a/src/commands/channels/capabilities.ts +++ b/src/commands/channels/capabilities.ts @@ -10,6 +10,7 @@ import type { ChannelCapabilitiesDisplayLine, ChannelPlugin, } from "../../channels/plugins/types.public.js"; +import { formatCliCommand } from "../../cli/command-format.js"; import { commitConfigWithPendingPluginInstalls } from "../../cli/plugins-install-record-commit.js"; import { refreshPluginRegistryAfterConfigMutation } from "../../cli/plugins-registry-refresh.js"; import { @@ -229,12 +230,20 @@ export async function channelsCapabilitiesCommand( const rawTarget = normalizeOptionalString(opts.target) ?? ""; if (opts.account && (!rawChannel || rawChannel === "all")) { - runtime.error(danger("--account requires a specific --channel.")); + runtime.error( + danger( + `--account requires a specific --channel. Run ${formatCliCommand("openclaw channels list")} to choose one.`, + ), + ); runtime.exit(1); return; } if (rawTarget && (!rawChannel || rawChannel === "all")) { - runtime.error(danger("--target requires a specific --channel.")); + runtime.error( + danger( + `--target requires a specific --channel. Run ${formatCliCommand("openclaw channels list")} to choose one.`, + ), + ); runtime.exit(1); return; } @@ -287,7 +296,11 @@ export async function channelsCapabilitiesCommand( })(); if (!selected || selected.length === 0) { - runtime.error(danger(`Unknown channel "${rawChannel}".`)); + runtime.error( + danger( + `Unknown channel "${rawChannel}". Run ${formatCliCommand("openclaw channels list")} to see installed channels.`, + ), + ); runtime.exit(1); return; } diff --git a/src/commands/commitments.ts b/src/commands/commitments.ts index a6734acef20..16ab5140f0f 100644 --- a/src/commands/commitments.ts +++ b/src/commands/commitments.ts @@ -1,3 +1,4 @@ +import { formatCliCommand } from "../cli/command-format.js"; import { listCommitments, markCommitmentsStatus, @@ -35,7 +36,9 @@ function parseStatus(raw: string | undefined, runtime: RuntimeEnv): CommitmentSt if (STATUS_VALUES.has(status as CommitmentStatus)) { return status as CommitmentStatus; } - runtime.error(`Unknown commitment status: ${status}`); + runtime.error( + `Unknown commitment status: ${status}. Use one of: ${Array.from(STATUS_VALUES).join(", ")}.`, + ); runtime.exit(1); return undefined; } @@ -126,7 +129,9 @@ export async function commitmentsListCommand( runtime.log(info(`Agent filter: ${opts.agent}`)); } if (commitments.length === 0) { - runtime.log("No commitments found."); + runtime.log( + `No commitments found. Run ${formatCliCommand("openclaw commitments --all")} to include dismissed and expired commitments.`, + ); return; } for (const line of formatRows(commitments, isRich())) { @@ -140,7 +145,9 @@ export async function commitmentsDismissCommand( ): Promise { const ids = opts.ids.map((id) => id.trim()).filter(Boolean); if (ids.length === 0) { - runtime.error("At least one commitment id is required."); + runtime.error( + `At least one commitment id is required. Run ${formatCliCommand("openclaw commitments list")} to choose one.`, + ); runtime.exit(1); return; } diff --git a/src/commands/configure.commands.ts b/src/commands/configure.commands.ts index 9b08dc36df2..4eb8edc2099 100644 --- a/src/commands/configure.commands.ts +++ b/src/commands/configure.commands.ts @@ -1,3 +1,4 @@ +import { formatCliCommand } from "../cli/command-format.js"; import type { RuntimeEnv } from "../runtime.js"; import { defaultRuntime } from "../runtime.js"; import type { WizardSection } from "./configure.shared.js"; @@ -27,7 +28,7 @@ export async function configureCommandFromSectionsArg( if (invalid.length > 0) { runtime.error( - `Invalid --section: ${invalid.join(", ")}. Expected one of: ${CONFIGURE_WIZARD_SECTIONS.join(", ")}.`, + `Invalid --section: ${invalid.join(", ")}. Expected one of: ${CONFIGURE_WIZARD_SECTIONS.join(", ")}. Run ${formatCliCommand("openclaw configure")} without --section to use the full wizard.`, ); runtime.exit(1); return; diff --git a/src/commands/onboard.ts b/src/commands/onboard.ts index e8184b6f94c..8076a36a1e5 100644 --- a/src/commands/onboard.ts +++ b/src/commands/onboard.ts @@ -50,13 +50,17 @@ export async function setupWizardCommand( normalizedOpts.secretInputMode !== "plaintext" && // pragma: allowlist secret normalizedOpts.secretInputMode !== "ref" // pragma: allowlist secret ) { - 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 the interactive setup.`, + ); runtime.exit(1); return; } if (normalizedOpts.resetScope && !VALID_RESET_SCOPES.has(normalizedOpts.resetScope)) { - runtime.error('Invalid --reset-scope. Use "config", "config+creds+sessions", or "full".'); + runtime.error( + `Invalid --reset-scope. Use "config", "config+creds+sessions", or "full". Run ${formatCliCommand("openclaw onboard --reset --reset-scope config")} for a config-only reset.`, + ); runtime.exit(1); return; } diff --git a/src/commands/sandbox.ts b/src/commands/sandbox.ts index e2f9df194aa..50d9c30e7e3 100644 --- a/src/commands/sandbox.ts +++ b/src/commands/sandbox.ts @@ -7,6 +7,8 @@ import { type SandboxBrowserInfo, type SandboxContainerInfo, } from "../agents/sandbox.js"; +import { formatCliCommand } from "../cli/command-format.js"; +import { formatErrorMessage } from "../infra/errors.js"; import { type RuntimeEnv, writeRuntimeJson } from "../runtime.js"; import { displayBrowsers, @@ -74,7 +76,9 @@ export async function sandboxRecreateCommand( const filtered = await fetchAndFilterContainers(opts); if (filtered.containers.length + filtered.browsers.length === 0) { - runtime.log("No sandbox runtimes found matching the criteria."); + runtime.log( + `No sandbox runtimes found matching the criteria. Run ${formatCliCommand("openclaw sandbox list")} to inspect active runtimes.`, + ); return; } @@ -97,14 +101,16 @@ export async function sandboxRecreateCommand( function validateRecreateOptions(opts: SandboxRecreateOptions, runtime: RuntimeEnv): boolean { if (!opts.all && !opts.session && !opts.agent) { - runtime.error("Please specify --all, --session , or --agent "); + runtime.error( + `Choose the sandbox scope: --all, --session , or --agent . Run ${formatCliCommand("openclaw sandbox list")} to inspect active runtimes first.`, + ); runtime.exit(1); return false; } const exclusiveCount = [opts.all, opts.session, opts.agent].filter(Boolean).length; if (exclusiveCount > 1) { - runtime.error("Please specify only one of: --all, --session, --agent"); + runtime.error("Choose only one sandbox scope: --all, --session, or --agent."); runtime.exit(1); return false; } @@ -194,7 +200,9 @@ async function removeContainer( runtime.log(`✓ Removed ${containerName}`); return { success: true }; } catch (err) { - runtime.error(`✗ Failed to remove ${containerName}: ${String(err)}`); + runtime.error( + `Failed to remove ${containerName}: ${formatErrorMessage(err)}. Run ${formatCliCommand("openclaw sandbox list")} to inspect what remains.`, + ); return { success: false }; } } diff --git a/src/commands/uninstall.ts b/src/commands/uninstall.ts index 5f03eb1cefa..7d307de6127 100644 --- a/src/commands/uninstall.ts +++ b/src/commands/uninstall.ts @@ -3,6 +3,7 @@ import { cancel, confirm, isCancel, multiselect } from "@clack/prompts"; import { formatCliCommand } from "../cli/command-format.js"; import { isNixMode } from "../config/config.js"; import { resolveGatewayService } from "../daemon/service.js"; +import { formatErrorMessage } from "../infra/errors.js"; import type { RuntimeEnv } from "../runtime.js"; import { stylePromptHint, stylePromptMessage, stylePromptTitle } from "../terminal/prompt-style.js"; import { resolveHomeDir } from "../utils.js"; @@ -54,7 +55,9 @@ function buildScopeSelection(opts: UninstallOptions): { async function stopAndUninstallService(runtime: RuntimeEnv): Promise { if (isNixMode) { - runtime.error("Nix mode detected; service uninstall is disabled."); + runtime.error( + `Nix mode detected; service uninstall is disabled. Manage the service through your Nix profile instead, then run ${formatCliCommand("openclaw status")} to verify.`, + ); return false; } const service = resolveGatewayService(); @@ -62,7 +65,9 @@ async function stopAndUninstallService(runtime: RuntimeEnv): Promise { try { loaded = await service.isLoaded({ env: process.env }); } catch (err) { - runtime.error(`Gateway service check failed: ${String(err)}`); + runtime.error( + `Gateway service check failed: ${formatErrorMessage(err)}. Run ${formatCliCommand("openclaw gateway status --deep")} for service diagnostics.`, + ); return false; } if (!loaded) { @@ -72,13 +77,17 @@ async function stopAndUninstallService(runtime: RuntimeEnv): Promise { try { await service.stop({ env: process.env, stdout: process.stdout }); } catch (err) { - runtime.error(`Gateway stop failed: ${String(err)}`); + runtime.error( + `Gateway stop failed: ${formatErrorMessage(err)}. Run ${formatCliCommand("openclaw gateway status --deep")} before retrying uninstall.`, + ); } try { await service.uninstall({ env: process.env, stdout: process.stdout }); return true; } catch (err) { - runtime.error(`Gateway uninstall failed: ${String(err)}`); + runtime.error( + `Gateway uninstall failed: ${formatErrorMessage(err)}. Run ${formatCliCommand("openclaw gateway status --deep")} for the service state.`, + ); return false; } } @@ -101,14 +110,18 @@ export async function uninstallCommand(runtime: RuntimeEnv, opts: UninstallOptio const { scopes, hadExplicit } = buildScopeSelection(opts); const interactive = !opts.nonInteractive; if (!interactive && !opts.yes) { - runtime.error("Non-interactive mode requires --yes."); + runtime.error( + `Non-interactive uninstall requires --yes. Preview first with ${formatCliCommand("openclaw uninstall --dry-run --all")}.`, + ); runtime.exit(1); return; } if (!hadExplicit) { if (!interactive) { - runtime.error("Non-interactive mode requires explicit scopes (use --all)."); + runtime.error( + `Non-interactive uninstall requires explicit scopes. Use --all, or choose scopes such as --service --state.`, + ); runtime.exit(1); return; }