mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-10 23:00:42 +00:00
fix(cli): explain parser errors
This commit is contained in:
32
src/cli/program/error-output.test.ts
Normal file
32
src/cli/program/error-output.test.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { formatCliParseErrorOutput } from "./error-output.js";
|
||||
|
||||
describe("formatCliParseErrorOutput", () => {
|
||||
it("explains unknown commands with root help and plugin hints", () => {
|
||||
const output = formatCliParseErrorOutput("error: unknown command 'wat'\n", {
|
||||
argv: ["node", "openclaw", "wat"],
|
||||
});
|
||||
|
||||
expect(output).toContain('OpenClaw does not know the command "wat".');
|
||||
expect(output).toContain("openclaw --help");
|
||||
expect(output).toContain("openclaw plugins list");
|
||||
});
|
||||
|
||||
it("points unknown options at the active command help", () => {
|
||||
const output = formatCliParseErrorOutput("error: unknown option '--wat'\n", {
|
||||
argv: ["node", "openclaw", "channels", "status", "--wat"],
|
||||
});
|
||||
|
||||
expect(output).toContain('OpenClaw does not recognize option "--wat".');
|
||||
expect(output).toContain("openclaw channels status --help");
|
||||
});
|
||||
|
||||
it("points missing required arguments at command help", () => {
|
||||
const output = formatCliParseErrorOutput("error: missing required argument 'name'\n", {
|
||||
argv: ["node", "openclaw", "plugins", "install"],
|
||||
});
|
||||
|
||||
expect(output).toContain('Missing required argument "name".');
|
||||
expect(output).toContain("openclaw plugins install --help");
|
||||
});
|
||||
});
|
||||
95
src/cli/program/error-output.ts
Normal file
95
src/cli/program/error-output.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import { formatDocsLink } from "../../terminal/links.js";
|
||||
import { theme } from "../../terminal/theme.js";
|
||||
import { getCommandPathWithRootOptions } from "../argv.js";
|
||||
import { formatCliCommand } from "../command-format.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")}`;
|
||||
}
|
||||
|
||||
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)}.`),
|
||||
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),
|
||||
);
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import { replaceCliName, resolveCliName } from "../cli-name.js";
|
||||
import { CLI_LOG_LEVEL_VALUES, parseCliLogLevelOption } from "../log-level-option.js";
|
||||
import type { ProgramContext } from "./context.js";
|
||||
import { getCoreCliCommandsWithSubcommands } from "./core-command-descriptors.js";
|
||||
import { formatCliParseErrorOutput } from "./error-output.js";
|
||||
import { getSubCliCommandsWithSubcommands } from "./subcli-descriptors.js";
|
||||
|
||||
const CLI_NAME = resolveCliName();
|
||||
@@ -106,7 +107,7 @@ export function configureProgramHelp(program: Command, ctx: ProgramContext) {
|
||||
writeErr: (str) => {
|
||||
process.stderr.write(formatHelpOutput(str));
|
||||
},
|
||||
outputError: (str, write) => write(theme.error(str)),
|
||||
outputError: (str, write) => write(formatCliParseErrorOutput(str, { argv: process.argv })),
|
||||
});
|
||||
|
||||
if (
|
||||
|
||||
Reference in New Issue
Block a user