mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-24 10:51:15 +00:00
Summary: - The PR adds descriptor-backed CLI command suggestions for unknown root commands, wires them into Commander parse errors and early unowned-root diagnostics, and covers both paths with focused CLI tests. - PR surface: Source +104, Tests +71. Total +175 across 5 files. - Reproducibility: yes. for the behavior gap: current main's formatter and early unowned-root path emit generic diagnostics without closest-command hints, and the PR proof shows the after-fix CLI output. Automerge notes: - PR branch already contained follow-up commit before automerge: fix: suppress suggestions for plugin policy diagnostics - PR branch already contained follow-up commit before automerge: Merge remote-tracking branch 'origin/main' into fix/83999-cli-command… - PR branch already contained follow-up commit before automerge: test: align agent model expectations - PR branch already contained follow-up commit before automerge: test: restore unrelated agent test fixture Validation: - ClawSweeper review passed for headb98f5b59e6. - Required merge gates passed before the squash merge. Prepared head SHA:b98f5b59e6Review: https://github.com/openclaw/openclaw/pull/91345#issuecomment-4646215016 Co-authored-by: Glenn-Agent <glenn_agent@163.com>
100 lines
3.3 KiB
TypeScript
100 lines
3.3 KiB
TypeScript
// Friendly parse-error formatter for Commander errors and root CLI recovery hints.
|
|
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 { formatCliCommandSuggestions } from "./command-suggestions.js";
|
|
|
|
type FormatCliParseErrorOptions = {
|
|
argv?: 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?: { root?: boolean }): string {
|
|
if (options?.root || !argv) {
|
|
return formatCliCommand("openclaw --help");
|
|
}
|
|
const commandPath = getCommandPathWithRootOptions(argv, 2);
|
|
if (commandPath.length === 0) {
|
|
return formatCliCommand("openclaw --help");
|
|
}
|
|
return formatCliCommand(`openclaw ${commandPath.join(" ")} --help`);
|
|
}
|
|
|
|
function lines(...items: Array<string | undefined>): string {
|
|
return `${items.filter((item): item is string => Boolean(item)).join("\n")}\n`;
|
|
}
|
|
|
|
function formatHelpHint(argv: string[] | undefined, options?: { root?: boolean }): string {
|
|
return `${theme.muted("Try:")} ${theme.command(resolveHelpCommand(argv, options))}`;
|
|
}
|
|
|
|
function formatDocsHint(): string {
|
|
return `${theme.muted("Docs:")} ${formatDocsLink("/cli", "docs.openclaw.ai/cli")}`;
|
|
}
|
|
|
|
/** 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) {
|
|
const command = unknownCommand[1] ?? "";
|
|
return lines(
|
|
theme.error(`OpenClaw does not know the command ${quote(command)}.`),
|
|
formatCliCommandSuggestions(command),
|
|
formatHelpHint(options.argv, { root: true }),
|
|
`${theme.muted("Plugin command?")} ${theme.command(formatCliCommand("openclaw plugins list"))}`,
|
|
formatDocsHint(),
|
|
);
|
|
}
|
|
|
|
const unknownOption = message.match(/^unknown option ['"`](.+?)['"`]/i);
|
|
if (unknownOption) {
|
|
const option = unknownOption[1] ?? "";
|
|
return lines(
|
|
theme.error(`OpenClaw does not recognize option ${quote(option)}.`),
|
|
formatHelpHint(options.argv),
|
|
);
|
|
}
|
|
|
|
const missingArgument = message.match(/^missing required argument ['"`](.+?)['"`]/i);
|
|
if (missingArgument) {
|
|
const argument = missingArgument[1] ?? "";
|
|
return lines(
|
|
theme.error(`Missing required argument ${quote(argument)}.`),
|
|
formatHelpHint(options.argv),
|
|
);
|
|
}
|
|
|
|
const missingOption = message.match(/^required option ['"`](.+?)['"`] not specified/i);
|
|
if (missingOption) {
|
|
const option = missingOption[1] ?? "";
|
|
return lines(
|
|
theme.error(`Missing required option ${quote(option)}.`),
|
|
formatHelpHint(options.argv),
|
|
);
|
|
}
|
|
|
|
if (/^too many arguments\b/i.test(message)) {
|
|
return lines(theme.error("Too many arguments for this command."), formatHelpHint(options.argv));
|
|
}
|
|
|
|
return lines(
|
|
theme.error(`OpenClaw could not parse this command: ${message}`),
|
|
formatHelpHint(options.argv),
|
|
);
|
|
}
|