mirror of
https://github.com/moltbot/moltbot.git
synced 2026-05-13 23:56:07 +00:00
fix(cli): improve terminal error guidance
This commit is contained in:
@@ -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.',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
71
src/cli/error-format.ts
Normal 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}.`;
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user