From b947c53d13ffbf4e259d7eb703d8c6a63da72b4f Mon Sep 17 00:00:00 2001 From: Miguel Angel Simon Sierra Date: Sat, 14 Nov 2026 10:36:18 -0500 Subject: [PATCH 1/2] fix(cli): an unknown flag's list leaves out hidden rename shims The list pointed agents at init's --template, --video-legacy and --agent, which only print an error. It now shows the flags --help shows. --- .../cli/src/utils/reject-unknown-flags.test.ts | 16 ++++++++++++++++ packages/cli/src/utils/reject-unknown-flags.ts | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/utils/reject-unknown-flags.test.ts b/packages/cli/src/utils/reject-unknown-flags.test.ts index 7900c11f3b..dd2aa15f62 100644 --- a/packages/cli/src/utils/reject-unknown-flags.test.ts +++ b/packages/cli/src/utils/reject-unknown-flags.test.ts @@ -112,6 +112,22 @@ 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.", + }), + ); + }); + 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..fc5178a836 100644 --- a/packages/cli/src/utils/reject-unknown-flags.ts +++ b/packages/cli/src/utils/reject-unknown-flags.ts @@ -25,7 +25,7 @@ function nameVariants(name: string): string[] { function declaredLongFlags(args: ArgsDef | undefined): string[] { return Object.entries(args ?? {}) - .filter(([, def]) => def?.type !== "positional") + .filter(([, def]) => def?.type !== "positional" && !(def && "hidden" in def && def.hidden)) .map(([name]) => `--${kebabCase(name)}`); } From fc4413344f995973264b84637a8917358b08b0fb Mon Sep 17 00:00:00 2001 From: Miguel Angel Simon Sierra Date: Sat, 14 Nov 2026 10:46:00 -0500 Subject: [PATCH 2/2] fix(cli): an unknown flag never suggests a hidden rename shim The did-you-mean search still used every spelling, so '--templat' on init was pointed at the hidden --template. It now uses the same visible flags the list shows. --- .../src/utils/reject-unknown-flags.test.ts | 15 ++++++++++++++ .../cli/src/utils/reject-unknown-flags.ts | 20 +++++++------------ 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/packages/cli/src/utils/reject-unknown-flags.test.ts b/packages/cli/src/utils/reject-unknown-flags.test.ts index dd2aa15f62..31d288461f 100644 --- a/packages/cli/src/utils/reject-unknown-flags.test.ts +++ b/packages/cli/src/utils/reject-unknown-flags.test.ts @@ -128,6 +128,21 @@ describe("assertKnownFlags", () => { ); }); + // 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 fc5178a836..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" && !(def && "hidden" in def && def.hidden)) - .map(([name]) => `--${kebabCase(name)}`); + .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); } }