checkUnknownOptions() reports the parsed, camelCased key rather than the flag the user typed, so the error names something that was never on the command line.
$ buddy about --dry-run
Unknown option `--dryRun`
$ buddy about --no-such-flag
Unknown option `--suchFlag`The second one is the worse of the two: the no- prefix is gone as well, so the error names a flag that is neither what was typed nor a flag that exists.
Cause
dist/index.js, in checkUnknownOptions():
for (const name of Object.keys(options)) {
if (name !== "--" && !this.hasOption(name) && !globalCommand.hasOption(name)) {
const allOptions = [...globalCommand.options, ...this.options];
const allOptionNames = allOptions.flatMap(opt => opt.names);
const normalizedName = camelcaseOptionName(name.split(".")[0]);
const optionFlag = name.length > 1 ? `--${name}` : `-${name}`; // <- already camelCased
const suggestions = findSimilarCommands(normalizedName, allOptionNames);
let errorMsg = `Unknown option \`${optionFlag}\``;options is the parse result, so its keys have already been camelCased and had no- stripped by the negation handling. The line right above computes normalizedName for suggestion matching, which is correct for that job, but optionFlag is built from the transformed key instead of the original token.
Why it is worth fixing
An unknown-option error is read by someone who has just mistyped something, and its only job is to point at the thing they typed. Naming a different string sends them looking for a flag they never wrote. --no-such-flag reported as --suchFlag is the case where it stops being cosmetic: a reader cannot work backwards from that to their own input.
It also gets more visible as callers move flags out of the global set. In Stacks we just made --dry-run a per-command option rather than a process-wide one (stacksjs/stacks#2865), precisely so that clapp refuses it on the ~316 commands that never implemented it. That is the right behaviour and it works, but every one of those refusals now prints --dryRun.
Suggested fix
Keep the raw token alongside the parsed key and echo that. The argv is available at parse time; optionFlag just needs to come from the original rather than from Object.keys(options). Where the raw token is genuinely unavailable, reversing the transform (camelCase back to kebab-case) is still closer than the current output, though it cannot recover a stripped no-.
Reproduction
@stacksjs/clapp 0.2.16, Bun 1.4.3-canary.1, macOS
cli.command('about', 'Show info').action(() => {})
// then: about --dry-run -> Unknown option `--dryRun`
// about --no-such-flag -> Unknown option `--suchFlag`Exit code is 2 in both cases, which is correct; only the message names the wrong flag.