premanmcp 0.16.2 → 0.16.3

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.
@@ -522,10 +522,31 @@ async function askStep(question, { assumeYes, canGoBack, defaultYes = true }) {
522
522
  if (assumeYes) return fallback;
523
523
  const shown = defaultYes ? "Y/n" : "y/N";
524
524
  const hint = canGoBack ? `[${shown}/b]` : `[${shown}]`;
525
- const answer = (await promptText(`${question} ${hint}: `)).trim().toLowerCase();
526
- if (canGoBack && (answer === "b" || answer === "back")) return "back";
527
- if (answer === "") return fallback;
528
- return answer === "y" || answer === "yes" ? "yes" : "no";
525
+ // Anything that was not yes, no or a usable back used to fall through to no.
526
+ // On the repository question that is the most expensive misreading in the
527
+ // walk: "b" typed at the one step with nothing behind it, or a plain "sure",
528
+ // disconnected the repo and explained itself in a line that had already
529
+ // scrolled past by the time the next question drew.
530
+ //
531
+ // Bounded, and empty still takes the default, so a closed stdin ends the
532
+ // question rather than starting a spin.
533
+ for (let attempt = 0; attempt < 3; attempt += 1) {
534
+ const answer = (await promptText(`${question} ${hint}: `)).trim().toLowerCase();
535
+ if (answer === "") return fallback;
536
+ if (answer === "y" || answer === "yes") return "yes";
537
+ if (answer === "n" || answer === "no") return "no";
538
+ if (answer === "b" || answer === "back") {
539
+ if (canGoBack) return "back";
540
+ // Offered nowhere, so this is somebody guessing. Say why it did nothing
541
+ // rather than silently spending their answer on the opposite of it.
542
+ process.stdout.write(" Nothing to go back to yet.\n");
543
+ continue;
544
+ }
545
+ process.stdout.write(
546
+ ` Please answer y or n${canGoBack ? ", or b to go back" : ""}.\n`
547
+ );
548
+ }
549
+ return fallback;
529
550
  }
530
551
 
531
552
  /**
package/bin/shared.js CHANGED
@@ -294,9 +294,28 @@ export function openUrl(url) {
294
294
  }
295
295
 
296
296
  export async function promptText(question) {
297
+ // A stream that has already ended will never deliver a line, and readline does
298
+ // not emit 'close' for an 'end' that happened before the interface existed --
299
+ // so the second prompt after stdin ran out resolved nothing, kept nothing
300
+ // alive, and the process simply exited mid-walk with a zero status.
301
+ if (process.stdin.readableEnded || process.stdin.destroyed) {
302
+ // Still shown, and still terminated, so the transcript reads the way it
303
+ // would have if somebody had pressed Enter.
304
+ process.stdout.write(`${question}\n`);
305
+ return "";
306
+ }
297
307
  const rl = createInterface({ input: process.stdin, output: process.stdout });
298
308
  try {
299
- return (await rl.question(question)).trim();
309
+ // An exhausted or closed stdin never answers, and rl.question waits for an
310
+ // answer that cannot come. That is how a run given fewer answers than it had
311
+ // questions -- `onboard < /dev/null`, a script whose here-doc ran out, a
312
+ // pipe whose writer exited -- stopped dead on a prompt instead of finishing
313
+ // on its defaults. Empty is what every caller already treats as "no answer".
314
+ const answer = await Promise.race([
315
+ rl.question(question),
316
+ new Promise((resolve) => rl.once("close", () => resolve(""))),
317
+ ]);
318
+ return answer.trim();
300
319
  } finally {
301
320
  rl.close();
302
321
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "premanmcp",
3
- "version": "0.16.2",
3
+ "version": "0.16.3",
4
4
  "description": "Turn APIs into agent-callable MCP tools with auth, testing, and audit logs",
5
5
  "type": "module",
6
6
  "bin": {