humanish 0.55.0 → 0.56.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/program.js CHANGED
@@ -142,6 +142,41 @@ function reportUnexpectedActionError(command, io, error) {
142
142
  }
143
143
  io.setExitCode(2);
144
144
  }
145
+ /** `unknown option '--x'` -> the sibling commands that DO declare `--x`. */
146
+ function commandsDeclaring(root, flag) {
147
+ const found = [];
148
+ const walk = (command, trail) => {
149
+ const names = [...trail, command.name()];
150
+ if (trail.length > 0 && command.options.some((option) => option.long === flag || option.short === flag)) {
151
+ found.push(names.slice(1).join(" "));
152
+ }
153
+ for (const child of command.commands)
154
+ walk(child, names);
155
+ };
156
+ walk(root, []);
157
+ return found;
158
+ }
159
+ /**
160
+ * Enrich commander's flag rejections with where the flag actually lives. A bare "unknown option"
161
+ * is accurate and unhelpful in the same way `no labs here yet` was: it reports a fact about this
162
+ * command and says nothing the reader can act on. Silence is preserved when no sibling has it —
163
+ * inventing a suggestion would be worse than none.
164
+ */
165
+ export function withSiblingFlagHint(text, root) {
166
+ const match = /unknown option '([^']+)'/.exec(text);
167
+ if (match === null)
168
+ return text;
169
+ const owners = commandsDeclaring(root, match[1]);
170
+ if (owners.length === 0)
171
+ return text;
172
+ // Truncation is REPORTED, never silent: a list that quietly drops owners would send a reader
173
+ // looking in the wrong place and think it had answered them.
174
+ const shown = owners.slice(0, 3);
175
+ const list = shown.map((owner) => `\`humanish ${owner}\``).join(", ");
176
+ const rest = owners.length - shown.length;
177
+ const tail = rest > 0 ? ` (and ${rest} more)` : "";
178
+ return `${text.replace(/\n+$/, "")}\n${match[1]} is an option of ${list}${tail}, not of this command.\n`;
179
+ }
145
180
  export function createProgram(io = {}) {
146
181
  const cliIo = { ...defaultIo, ...io };
147
182
  keyDiscoveryFn = io.keyDiscovery ?? discoverProviderKeys;
@@ -167,7 +202,14 @@ export function createProgram(io = {}) {
167
202
  .option("--json", "Print machine-readable JSON responses where supported.")
168
203
  .configureOutput({
169
204
  writeOut: (text) => cliIo.writeOut(text),
170
- writeErr: (text) => cliIo.writeErr(text)
205
+ writeErr: (text) => cliIo.writeErr(text),
206
+ // A rejected flag should name the command that WOULD have taken it. Found by a real
207
+ // first-contact study (labs/first-contact.yaml): a participant reached for
208
+ // `humanish run --no-open` by analogy with `lab run`, got a bare "unknown option", and
209
+ // filed it as a documentation mismatch. The flag is genuinely absent — `run` opens
210
+ // nothing — but "unknown" says that badly, because the reader's actual question is
211
+ // "then where does it live?".
212
+ outputError: (text, write) => write(withSiblingFlagHint(text, program))
171
213
  })
172
214
  .addHelpText("after", [
173
215
  "",