fix(cli): clarify operator command errors

This commit is contained in:
Vincent Koc
2026-05-09 08:10:43 +08:00
parent b2645c7354
commit 858a4471bd
6 changed files with 65 additions and 19 deletions

View File

@@ -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;
}

View File

@@ -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<void> {
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;
}

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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 <key>, or --agent <id>");
runtime.error(
`Choose the sandbox scope: --all, --session <key>, or --agent <id>. 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 };
}
}

View File

@@ -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<boolean> {
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<boolean> {
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<boolean> {
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;
}