// Friendly parse-error formatter for Commander errors and root CLI recovery hints. import { stripAnsi } from "../../../packages/terminal-core/src/ansi.js"; import { formatDocsLink } from "../../../packages/terminal-core/src/links.js"; import { theme } from "../../../packages/terminal-core/src/theme.js"; import { getCommandPathWithRootOptions } from "../argv.js"; import { formatCliCommand } from "../command-format.js"; import { ExpectedCliError } from "../failure-output.js"; import { formatCliCommandSuggestions } from "./command-suggestions.js"; type FormatCliParseErrorOptions = { argv?: string[]; commandPath?: string[]; commandNames?: readonly string[]; }; function stripCommanderErrorPrefix(raw: string): string { return raw .trim() .replace(/^error:\s*/i, "") .trim(); } function quote(value: string): string { return `"${value}"`; } function resolveHelpCommand( argv: string[] | undefined, options?: { commandPath?: string[] }, ): string { const commandPath = options?.commandPath ?? (argv ? getCommandPathWithRootOptions(argv, 2) : []); if (commandPath.length === 0) { return formatCliCommand("openclaw --help"); } return formatCliCommand(`openclaw ${commandPath.join(" ")} --help`); } function lines(...items: Array): string { return `${items.filter((item): item is string => Boolean(item)).join("\n")}\n`; } function formatHelpHint(argv: string[] | undefined, options?: { commandPath?: string[] }): string { const command = resolveHelpCommand(argv, options); return `${theme.muted("Try:")} ${theme.command(command)}`; } function formatDocsHint(): string { return `${theme.muted("Docs:")} ${formatDocsLink("/cli", "docs.openclaw.ai/cli")}`; } function formatCliMachineOutput(humanOutput: string): string { const docs = `Docs: ${formatDocsLink("/cli", "docs.openclaw.ai/cli", { force: false })}`; return stripAnsi(humanOutput).replace(/^Docs:.*$/mu, docs); } function formatUnknownCommandMessage(command: string, commandPath: readonly string[]): string { return commandPath.length > 0 ? `OpenClaw ${commandPath.join(" ")} has no command ${quote(command)}.` : `OpenClaw does not know the command ${quote(command)}.`; } function formatCliUnknownCommandOutput( command: string, options: FormatCliParseErrorOptions = {}, ): string { const commandPath = options.commandPath ?? []; const hasParentCommand = commandPath.length > 0; return lines( theme.error(formatUnknownCommandMessage(command, commandPath)), formatCliCommandSuggestions(command, commandPath, options.commandNames), formatHelpHint(options.argv, { commandPath }), hasParentCommand ? undefined : `${theme.muted("Plugin command?")} ${theme.command(formatCliCommand("openclaw plugins list"))}`, formatDocsHint(), ); } export function createCliParseError( raw: string, options: FormatCliParseErrorOptions = {}, errorOptions: { humanOutputWritten?: boolean } = {}, ): ExpectedCliError { const message = stripCommanderErrorPrefix(raw); const unknownCommand = message.match(/^unknown command ['"`](.+?)['"`]/i); if (unknownCommand) { const command = unknownCommand[1] ?? ""; const commandPath = options.commandPath ?? []; const humanOutput = formatCliUnknownCommandOutput(command, options); return new ExpectedCliError({ message: formatUnknownCommandMessage(command, commandPath), humanOutput, humanOutputWritten: errorOptions.humanOutputWritten, machineOutput: formatCliMachineOutput(humanOutput), }); } const humanOutput = formatCliParseErrorOutput(raw, options); return new ExpectedCliError({ message, humanOutput, humanOutputWritten: errorOptions.humanOutputWritten, machineOutput: formatCliMachineOutput(humanOutput), }); } export function createCliUnknownCommandError( command: string, options: FormatCliParseErrorOptions = {}, ): ExpectedCliError { const commandPath = options.commandPath ?? []; const humanOutput = formatCliUnknownCommandOutput(command, options); return new ExpectedCliError({ message: formatUnknownCommandMessage(command, commandPath), humanOutput, machineOutput: formatCliMachineOutput(humanOutput), }); } function formatOrdinaryCliParseErrorMessage(message: string): string { const unknownOption = message.match(/^unknown option ['"`](.+?)['"`]/i); if (unknownOption) { const option = unknownOption[1] ?? ""; return `OpenClaw does not recognize option ${quote(option)}.`; } const missingArgument = message.match(/^missing required argument ['"`](.+?)['"`]/i); if (missingArgument) { const argument = missingArgument[1] ?? ""; return `Missing required argument ${quote(argument)}.`; } const missingOption = message.match(/^required option ['"`](.+?)['"`] not specified/i); if (missingOption) { const option = missingOption[1] ?? ""; return `Missing required option ${quote(option)}.`; } if (/^too many arguments\b/i.test(message)) { return "Too many arguments for this command."; } return `OpenClaw could not parse this command: ${message}`; } /** Convert Commander parse errors into OpenClaw-specific help and docs guidance. */ export function formatCliParseErrorOutput( raw: string, options: FormatCliParseErrorOptions = {}, ): string { const message = stripCommanderErrorPrefix(raw); const unknownCommand = message.match(/^unknown command ['"`](.+?)['"`]/i); if (unknownCommand) { return formatCliUnknownCommandOutput(unknownCommand[1] ?? "", options); } const output = formatOrdinaryCliParseErrorMessage(message); return lines( theme.error(output), formatHelpHint(options.argv, { commandPath: options.commandPath }), ); }