fix(cli): improve terminal error guidance

This commit is contained in:
Vincent Koc
2026-05-10 09:57:20 +08:00
parent 6811ef058b
commit be1c38e692
23 changed files with 178 additions and 50 deletions

View File

@@ -353,7 +353,7 @@ describe("channel-auth", () => {
});
await expect(runChannelLogin({ channel: "whatsapp" }, runtime)).rejects.toThrow(
'Channel "whatsapp" does not support login.',
'Channel "whatsapp" does not support login. Run `openclaw channels status --channel whatsapp` to inspect supported actions.',
);
});
@@ -564,7 +564,7 @@ describe("channel-auth", () => {
});
await expect(runChannelLogout({ channel: "whatsapp" }, runtime)).rejects.toThrow(
'Channel "whatsapp" does not support logout.',
'Channel "whatsapp" does not support logout. Run `openclaw channels status --channel whatsapp` to inspect supported actions.',
);
});
});

View File

@@ -16,6 +16,7 @@ import { normalizeOptionalString } from "../shared/string-coerce.js";
import { sanitizeForLog } from "../terminal/ansi.js";
import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../utils/message-channel.js";
import { formatCliCommand } from "./command-format.js";
import { formatUnsupportedChannelActionMessage } from "./error-format.js";
import { commitConfigWithPendingPluginInstalls } from "./plugins-install-record-commit.js";
type ChannelAuthOptions = {
@@ -118,7 +119,11 @@ async function resolveChannelPluginForMode(
const plugin = resolved.plugin;
if (!plugin || !supportsChannelAuthMode(plugin, mode)) {
throw new Error(
`Channel "${channelId}" does not support ${mode}. Run ${formatCliCommand("openclaw channels status --channel " + channelId)} for its supported actions.`,
formatUnsupportedChannelActionMessage({
channel: channelId,
action: mode,
inspectCommand: "openclaw channels status --channel " + channelId,
}),
);
}
return {
@@ -227,7 +232,13 @@ export async function runChannelLogin(
}
const login = plugin.auth?.login;
if (!login) {
throw new Error(`Channel "${channelInput}" does not support login.`);
throw new Error(
formatUnsupportedChannelActionMessage({
channel: channelInput,
action: "login",
inspectCommand: "openclaw channels status --channel " + channelInput,
}),
);
}
// Auth-only flow: do not mutate channel config here.
setVerbose(Boolean(opts.verbose));
@@ -270,7 +281,13 @@ export async function runChannelLogout(
}
const logoutAccount = plugin.gateway?.logoutAccount;
if (!logoutAccount) {
throw new Error(`Channel "${channelInput}" does not support logout.`);
throw new Error(
formatUnsupportedChannelActionMessage({
channel: channelInput,
action: "logout",
inspectCommand: "openclaw channels status --channel " + channelInput,
}),
);
}
// Prefer the live gateway so logout also stops any active channel runtime.
const { accountId } = resolveAccountContext(plugin, opts, cfg);

View File

@@ -23,6 +23,7 @@ import {
import { defaultRuntime } from "../../runtime.js";
import { normalizeOptionalString } from "../../shared/string-coerce.js";
import { formatCliCommand } from "../command-format.js";
import { formatInvalidConfigPort, formatInvalidPortOption } from "../error-format.js";
import { buildDaemonServiceSnapshot, installDaemonServiceAndEmit } from "./response.js";
import {
createDaemonInstallActionContext,
@@ -94,12 +95,12 @@ export async function runDaemonInstall(opts: DaemonInstallOptions) {
const cfg = configSnapshot.valid ? configSnapshot.sourceConfig : configSnapshot.config;
const portOverride = parsePort(opts.port);
if (opts.port !== undefined && portOverride === null) {
fail("Invalid --port. Use a port number from 1 to 65535, for example 18789.");
fail(formatInvalidPortOption("--port"));
return;
}
const port = portOverride ?? resolveGatewayPort(cfg);
if (!Number.isFinite(port) || port <= 0 || port > 65_535) {
fail("Invalid Gateway port in config. Set gateway.port to a number from 1 to 65535.");
fail(formatInvalidConfigPort("gateway.port"));
return;
}
const runtimeRaw = opts.runtime ? opts.runtime : DEFAULT_GATEWAY_DAEMON_RUNTIME;

71
src/cli/error-format.ts Normal file
View File

@@ -0,0 +1,71 @@
import { formatCliCommand } from "./command-format.js";
const DEFAULT_GATEWAY_PORT_EXAMPLE = 18789;
export function formatPortRangeHint(example = DEFAULT_GATEWAY_PORT_EXAMPLE): string {
return `Use a port number from 1 to 65535, for example ${example}.`;
}
export function formatInvalidPortOption(
option: string,
example = DEFAULT_GATEWAY_PORT_EXAMPLE,
): string {
return `Invalid ${option}. ${formatPortRangeHint(example)}`;
}
export function formatInvalidConfigPort(
path: string,
example = DEFAULT_GATEWAY_PORT_EXAMPLE,
): string {
return `Invalid ${path} in config. Set ${path} to a number from 1 to 65535, or pass --port ${example}.`;
}
export function formatUnknownChannelMessage(params: {
channel: string;
listCommand?: string;
purpose?: string;
}): string {
const purpose = params.purpose ? ` for ${params.purpose}` : "";
const listCommand = params.listCommand ?? "openclaw channels list --all";
return `Unknown channel "${params.channel}"${purpose}. Run ${formatCliCommand(
listCommand,
)} to see configured and installable channels.`;
}
export function formatUnsupportedChannelActionMessage(params: {
channel: string;
action: string;
inspectCommand?: string;
}): string {
const inspectCommand =
params.inspectCommand ?? `openclaw channels capabilities --channel ${params.channel}`;
return `Channel "${params.channel}" does not support ${params.action}. Run ${formatCliCommand(
inspectCommand,
)} to inspect supported actions.`;
}
export function formatLookupMiss(params: {
noun: string;
value: string;
listCommand: string;
valueLabel?: string;
}): string {
const valueLabel = params.valueLabel ?? params.noun.toLowerCase();
return `${params.noun} not found: ${params.value}. Run ${formatCliCommand(
params.listCommand,
)} to see recent ${valueLabel}s.`;
}
export function formatMissingPluginMessage(params: {
id: string;
listCommand?: string;
includeSearch?: boolean;
}): string {
const listCommand = params.listCommand ?? "openclaw plugins list";
const searchHint = params.includeSearch
? `, or ${formatCliCommand("openclaw plugins search " + params.id)} to look for installable plugins`
: "";
return `Plugin not found: ${params.id}. Run ${formatCliCommand(
listCommand,
)} to see installed plugins${searchHint}.`;
}

View File

@@ -34,6 +34,7 @@ import {
} from "../../shared/string-coerce.js";
import { formatCliCommand } from "../command-format.js";
import { inheritOptionFromParent } from "../command-options.js";
import { formatInvalidConfigPort, formatInvalidPortOption } from "../error-format.js";
import { withProgress } from "../progress.js";
import { parsePort } from "../shared/parse-port.js";
import { installQaParentWatchdog } from "./qa-parent-watchdog.js";
@@ -520,15 +521,15 @@ async function runGatewayCommand(opts: GatewayRunOpts) {
});
const portOverride = parsePort(opts.port);
if (opts.port !== undefined && portOverride === null) {
defaultRuntime.error("Invalid --port. Use a positive port number, for example 3000.");
defaultRuntime.error(formatInvalidPortOption("--port"));
defaultRuntime.exit(1);
return;
}
const port = portOverride ?? resolveGatewayPort(cfg);
if (!Number.isFinite(port) || port <= 0) {
defaultRuntime.error(
`Gateway port is invalid in config. Set it with ${formatCliCommand("openclaw config set gateway.port 3000")} or pass --port 3000.`,
);
if (!Number.isFinite(port) || port <= 0 || port > 65_535) {
defaultRuntime.error(formatInvalidConfigPort("gateway.port"));
defaultRuntime.exit(1);
return;
}
const { formatFutureConfigActionBlock, resolveFutureConfigActionBlock } =
await import("../../config/future-version-guard.js");

View File

@@ -34,6 +34,7 @@ import {
parsePort,
resolveRuntimeStatusColor,
} from "../daemon-cli/shared.js";
import { formatInvalidConfigPort, formatInvalidPortOption } from "../error-format.js";
type NodeDaemonInstallOptions = {
host?: string;
@@ -95,7 +96,11 @@ export async function runNodeDaemonInstall(opts: NodeDaemonInstallOptions) {
const config = await loadNodeHostConfig();
const { host, port } = resolveNodeDefaults(opts, config);
if (!Number.isFinite(port ?? Number.NaN) || (port ?? 0) <= 0 || (port ?? 0) > 65_535) {
fail("Invalid node host port. Use a port number from 1 to 65535.");
fail(
opts.port !== undefined
? formatInvalidPortOption("--port")
: formatInvalidConfigPort("node.gateway.port"),
);
return;
}

View File

@@ -46,7 +46,9 @@ function parseChannel(raw: unknown, channels: PairingChannel[]): PairingChannel
if (/^[a-z][a-z0-9_-]{0,63}$/.test(value)) {
return value as PairingChannel;
}
throw new Error(`Invalid channel "${value}". Use lowercase letters, numbers, "_" or "-".`);
throw new Error(
`Invalid channel "${value}". Use lowercase letters, numbers, "_" or "-", for example "telegram".`,
);
}
async function notifyApproved(channel: PairingChannel, id: string) {

View File

@@ -189,7 +189,7 @@ describe("plugins cli policy mutations", () => {
);
expect(runtimeErrors).toContain(
"Plugin not found: missing-plugin. Run `openclaw plugins list` to see installed plugins.",
"Plugin not found: missing-plugin. Run `openclaw plugins list` to see installed plugins, or `openclaw plugins search missing-plugin` to look for installable plugins.",
);
expect(enablePluginInConfig).not.toHaveBeenCalled();
expect(writeConfigFile).not.toHaveBeenCalled();

View File

@@ -11,6 +11,7 @@ import { defaultRuntime } from "../runtime.js";
import { formatDocsLink } from "../terminal/links.js";
import { theme } from "../terminal/theme.js";
import { shortenHomeInString } from "../utils.js";
import { formatMissingPluginMessage } from "./error-format.js";
import type { PluginInspectOptions } from "./plugins-inspect-command.js";
import type { PluginsListOptions } from "./plugins-list-command.js";
import { applyParentDefaultHelpAction } from "./program/parent-default-help.js";
@@ -58,9 +59,7 @@ function formatRegistryState(state: "missing" | "fresh" | "stale"): string {
}
function reportMissingPlugin(id: string) {
defaultRuntime.error(
`Plugin not found: ${id}. Run \`openclaw plugins list\` to see installed plugins.`,
);
defaultRuntime.error(formatMissingPluginMessage({ id, includeSearch: true }));
return defaultRuntime.exit(1);
}

View File

@@ -9,6 +9,7 @@ import { getTerminalTableWidth, renderTable } from "../terminal/table.js";
import { theme } from "../terminal/theme.js";
import { shortenHomeInString, shortenHomePath } from "../utils.js";
import { formatCliCommand } from "./command-format.js";
import { formatMissingPluginMessage } from "./error-format.js";
import { quietPluginJsonLogger } from "./plugins-command-helpers.js";
export type PluginInspectOptions = {
@@ -233,9 +234,7 @@ export async function runPluginsInspectCommand(
);
const targetPlugin = snapshotReport.plugins.find((entry) => entry.id === id || entry.name === id);
if (!targetPlugin) {
defaultRuntime.error(
`Plugin not found: ${id}. Run ${formatCliCommand("openclaw plugins list")} to see installed plugins.`,
);
defaultRuntime.error(formatMissingPluginMessage({ id, includeSearch: true }));
return defaultRuntime.exit(1);
}
const report = runtimeInspect
@@ -258,7 +257,7 @@ export async function runPluginsInspectCommand(
});
if (!inspect) {
defaultRuntime.error(
`Plugin not found: ${id}. Run ${formatCliCommand("openclaw plugins list --json")} to inspect raw discovery state.`,
formatMissingPluginMessage({ id, listCommand: "openclaw plugins list --json" }),
);
return defaultRuntime.exit(1);
}

View File

@@ -3,6 +3,7 @@ import { resolveChannelDefaultAccountId } from "../channels/plugins/helpers.js";
import { getLoadedChannelPlugin } from "../channels/plugins/index.js";
import type { ChannelId } from "../channels/plugins/types.public.js";
import { normalizeChannelId as normalizeBundledChannelId } from "../channels/registry.js";
import { formatUnknownChannelMessage } from "../cli/error-format.js";
import { isRouteBinding, listRouteBindings } from "../config/bindings.js";
import type { AgentRouteBinding } from "../config/types.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
@@ -313,12 +314,14 @@ export function parseBindingSpecs(params: {
const [channelRaw, accountRaw] = trimmed.split(":", 2);
const channel = normalizeBindingChannelId(channelRaw, params.config);
if (!channel) {
errors.push(`Unknown channel "${channelRaw}".`);
errors.push(formatUnknownChannelMessage({ channel: channelRaw }));
continue;
}
let accountId: string | undefined = accountRaw?.trim();
if (accountRaw !== undefined && !accountId) {
errors.push(`Invalid binding "${trimmed}" (empty account id).`);
errors.push(
`Invalid binding "${trimmed}". Account id is empty. Use <channel>:<account>, for example telegram:default.`,
);
continue;
}
accountId = resolveBindingAccountId({

View File

@@ -6,6 +6,11 @@ import { moveSingleAccountChannelSectionToDefaultAccount } from "../../channels/
import type { ChannelSetupPlugin } from "../../channels/plugins/setup-wizard-types.js";
import type { ChannelPlugin } from "../../channels/plugins/types.plugin.js";
import type { ChannelId, ChannelSetupInput } from "../../channels/plugins/types.public.js";
import { formatCliCommand } from "../../cli/command-format.js";
import {
formatUnknownChannelMessage,
formatUnsupportedChannelActionMessage,
} from "../../cli/error-format.js";
import { commitConfigWithPendingPluginInstalls } from "../../cli/plugins-install-record-commit.js";
import { refreshPluginRegistryAfterConfigMutation } from "../../cli/plugins-registry-refresh.js";
import type { OpenClawConfig } from "../../config/config.js";
@@ -345,7 +350,7 @@ export async function channelsAddCommand(
if (!channel) {
const hint = catalogEntry
? `Plugin ${catalogEntry.meta.label} could not be loaded after install. Run openclaw doctor --fix, then retry openclaw channels add.`
: `Unknown channel: ${rawChannel}. Run openclaw channels list --all to see configured and installable channels.`;
: formatUnknownChannelMessage({ channel: rawChannel });
runtime.error(hint);
runtime.exit(1);
return;
@@ -354,7 +359,10 @@ export async function channelsAddCommand(
const plugin = await loadScopedPlugin(channel, catalogEntry?.pluginId);
if (!plugin?.setup?.applyAccountConfig) {
runtime.error(
`Channel ${channel} does not support non-interactive add. Run openclaw channels add with no flags for guided setup, or openclaw channels list --all to inspect available channels.`,
`${formatUnsupportedChannelActionMessage({
channel,
action: "non-interactive add",
})} Run ${formatCliCommand("openclaw channels add")} with no flags for guided setup.`,
);
runtime.exit(1);
return;

View File

@@ -11,6 +11,7 @@ import type {
ChannelPlugin,
} from "../../channels/plugins/types.public.js";
import { formatCliCommand } from "../../cli/command-format.js";
import { formatUnknownChannelMessage } from "../../cli/error-format.js";
import { commitConfigWithPendingPluginInstalls } from "../../cli/plugins-install-record-commit.js";
import { refreshPluginRegistryAfterConfigMutation } from "../../cli/plugins-registry-refresh.js";
import {
@@ -296,11 +297,7 @@ export async function channelsCapabilitiesCommand(
})();
if (!selected || selected.length === 0) {
runtime.error(
danger(
`Unknown channel "${rawChannel}". Run ${formatCliCommand("openclaw channels list")} to see installed channels.`,
),
);
runtime.error(danger(formatUnknownChannelMessage({ channel: rawChannel })));
runtime.exit(1);
return;
}

View File

@@ -3,6 +3,10 @@ import { getChannelPlugin, normalizeChannelId } from "../../channels/plugins/ind
import { listReadOnlyChannelPluginsForConfig } from "../../channels/plugins/read-only.js";
import type { ChannelPlugin } from "../../channels/plugins/types.plugin.js";
import { formatCliCommand } from "../../cli/command-format.js";
import {
formatUnknownChannelMessage,
formatUnsupportedChannelActionMessage,
} from "../../cli/error-format.js";
import { commitConfigWithPendingPluginInstalls } from "../../cli/plugins-install-record-commit.js";
import { refreshPluginRegistryAfterConfigMutation } from "../../cli/plugins-registry-refresh.js";
import { replaceConfigFile, type OpenClawConfig } from "../../config/config.js";
@@ -159,9 +163,7 @@ export async function channelsRemoveCommand(
}
const resolvedChannel = resolvedPluginState?.channelId ?? channel;
if (!resolvedChannel) {
runtime.error(
`Unknown channel "${rawChannel}". Run ${formatCliCommand("openclaw channels list")} to see supported channels.`,
);
runtime.error(formatUnknownChannelMessage({ channel: rawChannel }));
runtime.exit(1);
return;
}
@@ -175,7 +177,7 @@ export async function channelsRemoveCommand(
runtime.exit(1);
return;
}
runtime.error(`Unknown channel "${resolvedChannel}".`);
runtime.error(formatUnknownChannelMessage({ channel: resolvedChannel }));
runtime.exit(1);
return;
}
@@ -197,7 +199,7 @@ export async function channelsRemoveCommand(
if (deleteConfig) {
if (!plugin.config.deleteAccount) {
runtime.error(
`Channel "${channel}" does not support delete. Use disable/remove without --delete.`,
`${formatUnsupportedChannelActionMessage({ channel, action: "delete" })} Use ${formatCliCommand("openclaw channels remove --channel " + channel)} to disable it without deleting config.`,
);
runtime.exit(1);
return;
@@ -214,7 +216,7 @@ export async function channelsRemoveCommand(
} else {
if (!plugin.config.setAccountEnabled) {
runtime.error(
`Channel "${channel}" does not support disable. Use --delete only if you want to remove config.`,
`${formatUnsupportedChannelActionMessage({ channel, action: "disable" })} Use ${formatCliCommand("openclaw channels remove --channel " + channel + " --delete")} only if you want to remove config.`,
);
runtime.exit(1);
return;

View File

@@ -6,6 +6,7 @@ import type {
import { resolveCommandConfigWithSecrets } from "../../cli/command-config-resolution.js";
import { formatCliCommand } from "../../cli/command-format.js";
import { getChannelsCommandSecretTargetIds } from "../../cli/command-secret-targets.js";
import { formatUnsupportedChannelActionMessage } from "../../cli/error-format.js";
import { commitConfigWithPendingPluginInstalls } from "../../cli/plugins-install-record-commit.js";
import { refreshPluginRegistryAfterConfigMutation } from "../../cli/plugins-registry-refresh.js";
import {
@@ -197,7 +198,10 @@ export async function channelsResolveCommand(opts: ChannelsResolveOptions, runti
if (!plugin?.resolver?.resolveTargets) {
const channelText = selection.channel ?? explicitChannel ?? "";
throw new Error(
`Channel ${channelText} does not support resolve. Run ${formatCliCommand("openclaw channels capabilities --channel " + channelText)} to inspect supported actions.`,
formatUnsupportedChannelActionMessage({
channel: channelText,
action: "resolve",
}),
);
}
const preferredKind = resolvePreferredKind(opts.kind);

View File

@@ -1,3 +1,4 @@
import { formatPortRangeHint } from "../cli/error-format.js";
import { resolveGatewayPort } from "../config/config.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { isValidEnvSecretRefId, type SecretInput } from "../config/types.secrets.js";
@@ -29,7 +30,7 @@ type GatewayTokenInputMode = "plaintext" | "ref";
function validateGatewayPortInput(value: unknown): string | undefined {
const port = Number(typeof value === "string" ? value.trim() : value);
if (!Number.isInteger(port) || port < 1 || port > 65_535) {
return "Use a port number from 1 to 65535, for example 18789.";
return formatPortRangeHint();
}
return undefined;
}

View File

@@ -3,6 +3,7 @@ import nodePath from "node:path";
import { isDeepStrictEqual } from "node:util";
import { describeCodexNativeWebSearch } from "../agents/codex-native-web-search.shared.js";
import { formatCliCommand } from "../cli/command-format.js";
import { formatPortRangeHint } from "../cli/error-format.js";
import { commitConfigWithPendingPluginInstalls } from "../cli/plugins-install-record-commit.js";
import { readConfigFileSnapshot, resolveGatewayPort } from "../config/config.js";
import { logConfigUpdated } from "../config/logging.js";
@@ -64,7 +65,7 @@ const setupPluginConfigModuleLoader = createLazyImportLoader<SetupPluginConfigMo
function validateGatewayPortInput(value: unknown): string | undefined {
const port = Number(typeof value === "string" ? value.trim() : value);
if (!Number.isInteger(port) || port < 1 || port > 65_535) {
return "Use a port number from 1 to 65535, for example 18789.";
return formatPortRangeHint();
}
return undefined;
}

View File

@@ -2,6 +2,7 @@ import { resolveDefaultAgentId } from "../agents/agent-scope.js";
import { CHANNEL_MESSAGE_ACTION_NAMES } from "../channels/plugins/message-action-names.js";
import type { ChannelMessageActionName } from "../channels/plugins/types.public.js";
import { resolveCommandConfigWithSecrets } from "../cli/command-config-resolution.js";
import { formatCliCommand } from "../cli/command-format.js";
import { getScopedChannelsCommandSecretTargets } from "../cli/command-secret-targets.js";
import { resolveMessageSecretScope } from "../cli/message-secret-scope.js";
import { createOutboundSendDeps, type CliDeps } from "../cli/outbound-send-deps.js";
@@ -58,7 +59,11 @@ export async function messageCommand(
(name) => normalizeLowercaseStringOrEmpty(name) === normalizedActionInput,
);
if (!actionMatch) {
throw new Error(`Unknown message action: ${actionInput}`);
throw new Error(
`Unknown message action "${actionInput}". Use one of ${CHANNEL_MESSAGE_ACTION_NAMES.join(
", ",
)}. Example: ${formatCliCommand("openclaw message send --channel <channel> --target <id> --text <message>")}.`,
);
}
const action = actionMatch as ChannelMessageActionName;

View File

@@ -1,4 +1,5 @@
import { formatCliCommand } from "../../../cli/command-format.js";
import { formatInvalidPortOption } from "../../../cli/error-format.js";
import type { OpenClawConfig } from "../../../config/types.openclaw.js";
import { isValidEnvSecretRefId, resolveSecretInputRef } from "../../../config/types.secrets.js";
import type { RuntimeEnv } from "../../../runtime.js";
@@ -23,8 +24,13 @@ export function applyNonInteractiveGatewayConfig(params: {
const { opts, runtime } = params;
const hasGatewayPort = opts.gatewayPort !== undefined;
if (hasGatewayPort && (!Number.isFinite(opts.gatewayPort) || (opts.gatewayPort ?? 0) <= 0)) {
runtime.error("Invalid --gateway-port. Use a positive port number, for example 3000.");
if (
hasGatewayPort &&
(!Number.isFinite(opts.gatewayPort) ||
(opts.gatewayPort ?? 0) <= 0 ||
opts.gatewayPort > 65_535)
) {
runtime.error(formatInvalidPortOption("--gateway-port"));
runtime.exit(1);
return null;
}

View File

@@ -316,7 +316,7 @@ describe("sessionsCommand", () => {
const { runtime, errors } = makeRuntime();
await expect(sessionsCommand({ store, active: "0" }, runtime)).rejects.toThrow("exit 1");
expect(errors[0]).toContain("--active must be a positive integer");
expect(errors[0]).toContain("--active must be a positive number of minutes");
fs.rmSync(store);
});
@@ -334,7 +334,7 @@ describe("sessionsCommand", () => {
const { runtime, errors } = makeRuntime();
await expect(sessionsCommand({ store, limit: "0" }, runtime)).rejects.toThrow("exit 1");
expect(errors[0]).toContain('--limit must be a positive integer or "all"');
expect(errors[0]).toContain('--limit must be a positive integer or "all", for example');
fs.rmSync(store);
});

View File

@@ -234,7 +234,7 @@ export async function sessionsCommand(
if (opts.active !== undefined) {
const parsed = Number.parseInt(opts.active, 10);
if (Number.isNaN(parsed) || parsed <= 0) {
runtime.error("--active must be a positive integer (minutes)");
runtime.error("--active must be a positive number of minutes, for example --active 30.");
runtime.exit(1);
return;
}
@@ -243,7 +243,7 @@ export async function sessionsCommand(
const limit = parseSessionsLimit(opts.limit);
if (limit === null) {
runtime.error('--limit must be a positive integer or "all"');
runtime.error('--limit must be a positive integer or "all", for example --limit 25.');
runtime.exit(1);
return;
}

View File

@@ -1,5 +1,5 @@
import fs from "node:fs";
import { formatCliCommand } from "../cli/command-format.js";
import { formatLookupMiss } from "../cli/error-format.js";
import { getRuntimeConfig } from "../config/config.js";
import {
loadSessionStore,
@@ -58,7 +58,12 @@ const SESSION_REGISTRY_RETENTION_MS = 7 * 24 * 60 * 60_000;
const info = theme.info;
function formatTaskLookupMiss(lookup: string): string {
return `Task not found: ${lookup}. Run ${formatCliCommand("openclaw tasks list")} to see recent task ids.`;
return formatLookupMiss({
noun: "Task",
value: lookup,
listCommand: "openclaw tasks list",
valueLabel: "task id",
});
}
async function loadTaskCancelConfig() {

View File

@@ -1,3 +1,4 @@
import { formatPortRangeHint } from "../cli/error-format.js";
import {
normalizeGatewayTokenInput,
randomToken,
@@ -55,7 +56,7 @@ function normalizeWizardTextInput(value: unknown): string {
function validateGatewayPortInput(value: unknown): string | undefined {
const port = Number(normalizeWizardTextInput(value));
if (!Number.isInteger(port) || port < 1 || port > 65_535) {
return "Use a port number from 1 to 65535, for example 18789.";
return formatPortRangeHint();
}
return undefined;
}