diff --git a/packages/cli/src/utils/reject-unknown-flags.test.ts b/packages/cli/src/utils/reject-unknown-flags.test.ts index 7900c11f3b..31d288461f 100644 --- a/packages/cli/src/utils/reject-unknown-flags.test.ts +++ b/packages/cli/src/utils/reject-unknown-flags.test.ts @@ -112,6 +112,37 @@ describe("assertKnownFlags", () => { ); }); + // fails without the fix: the list sent agents to hidden rename shims that only print an error. + it("leaves hidden flags out of the list, as --help does", () => { + const init = { + args: { + example: { type: "string" }, + template: { type: "string", hidden: true, description: "[renamed] Use --example instead." }, + agent: { type: "boolean", hidden: true }, + }, + } as unknown as CommandDef; + expect(() => assertKnownFlags(init, ["--demo"], "init")).toThrow( + expect.objectContaining({ + hint: "Its flags: --example. Run 'hyperframes init --help' for what each does.", + }), + ); + }); + + // fails without the fix: a typo near a hidden shim was told to use the shim. + it("never suggests a hidden flag for a typo", () => { + const init = { + args: { + example: { type: "string" }, + template: { type: "string", hidden: true, description: "[renamed] Use --example instead." }, + }, + } as unknown as CommandDef; + expect(() => assertKnownFlags(init, ["--templat", "x"], "init")).toThrow( + expect.objectContaining({ + hint: "Its flags: --example. Run 'hyperframes init --help' for what each does.", + }), + ); + }); + it("points --search at the query flag", () => { const catalog = { args: { words: { type: "positional" }, query: { type: "string" }, json: { type: "boolean" } }, diff --git a/packages/cli/src/utils/reject-unknown-flags.ts b/packages/cli/src/utils/reject-unknown-flags.ts index 0fee2b7012..7b98fd0717 100644 --- a/packages/cli/src/utils/reject-unknown-flags.ts +++ b/packages/cli/src/utils/reject-unknown-flags.ts @@ -23,10 +23,10 @@ function nameVariants(name: string): string[] { return [name, kebabCase(name), camel]; } -function declaredLongFlags(args: ArgsDef | undefined): string[] { +function visibleLongFlags(args: ArgsDef | undefined): string[] { return Object.entries(args ?? {}) - .filter(([, def]) => def?.type !== "positional") - .map(([name]) => `--${kebabCase(name)}`); + .filter(([, def]) => def?.type !== "positional" && !(def && "hidden" in def && def.hidden)) + .map(([name]) => kebabCase(name)); } // Every spelling of one declared arg: name variants plus aliases. `def` may be undefined. @@ -86,12 +86,7 @@ const FLAG_SYNONYMS: Readonly> = { search: ["query"], }; -function unknownFlagError( - flag: string, - known: Set, - declared: string[], - path: string, -): CliUsageError { +function unknownFlagError(flag: string, visible: string[], path: string): CliUsageError { const command = `hyperframes ${path ? `${path} ` : ""}`.trim(); if (flag === "--json") { return new CliUsageError(`${command} has no JSON output.`, { @@ -99,14 +94,13 @@ function unknownFlagError( hint: "Run it without --json.", }); } - const longNames = [...known].filter((name) => name.length > 1); const name = flag.replace(/^--?/, ""); const match = - FLAG_SYNONYMS[name]?.find((s) => known.has(s)) ?? - (flag.startsWith("--") ? closestMatch(name, longNames) : undefined); + FLAG_SYNONYMS[name]?.find((s) => visible.includes(s)) ?? + (flag.startsWith("--") ? closestMatch(name, visible) : undefined); const hint = [ match && `Did you mean --${match}?`, - declared.length > 0 && `Its flags: ${declared.join(", ")}.`, + visible.length > 0 && `Its flags: ${visible.map((f) => `--${f}`).join(", ")}.`, `Run 'hyperframes ${path ? `${path} ` : ""}--help' for what each does.`, ] .filter(Boolean) @@ -130,7 +124,7 @@ export function assertKnownFlags(cmd: CommandDef, rawArgs: string[], pa for (const tok of rawArgs) { if (tok === "--") break; const bad = unknownFlagIn(tok, known); - if (bad) throw unknownFlagError(bad, known, declaredLongFlags(args), path); + if (bad) throw unknownFlagError(bad, visibleLongFlags(args), path); } }