@retasc/cli 1.6.1 → 1.6.2

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.
@@ -11,7 +11,13 @@ function isInteractive() {
11
11
  async function ask(question) {
12
12
  const rl = createInterface({ input: stdin, output: stdout });
13
13
  try {
14
- return (await rl.question(question)).trim();
14
+ // readline's question() NEVER settles when stdin hits EOF (Ctrl-D, or a
15
+ // closed pipe) — it silently drops the callback rather than resolving ""
16
+ // or throwing, so a bare `await` here wedges the CLI with no output and no
17
+ // exit. Race the close event so callers fail fast instead (RTSC-269).
18
+ const closed = new Promise((_, reject) => rl.once("close", () => reject(new Error("input closed"))));
19
+ closed.catch(() => { }); // the normal path closes rl too; that loser rejection is expected
20
+ return (await Promise.race([rl.question(question), closed])).trim();
15
21
  }
16
22
  finally {
17
23
  rl.close();
@@ -25,18 +31,37 @@ async function confirm(question, assumeYes) {
25
31
  const a = (await ask(`${question} [y/N] `)).toLowerCase();
26
32
  return a === "y" || a === "yes";
27
33
  }
28
- /** Pick from a list interactively, or return undefined to fall through to create. */
29
- async function pick(label, items, render) {
34
+ /**
35
+ * Pick from a list interactively. Returns the chosen item, or undefined when the
36
+ * user explicitly picks the trailing "create new" option.
37
+ *
38
+ * Invalid input re-prompts rather than falling through: undefined used to mean
39
+ * BOTH "chose create-new" and "typed garbage", so a typo like `1)` silently
40
+ * created a new org/project instead of selecting option 1 (RTSC-269).
41
+ *
42
+ * The answer must be plain digits. Number() alone accepts "0x4"/"4e0"/"+4"/
43
+ * "4.0", which would route those straight to create-new — the same
44
+ * non-canonical-input-hits-the-destructive-branch bug in a new coat.
45
+ */
46
+ export async function pick(label, items, render, askFn = ask) {
30
47
  if (!items.length)
31
48
  return undefined;
32
49
  console.log(`\n${label}:`);
33
50
  items.forEach((it, i) => console.log(` ${i + 1}) ${render(it)}`));
34
- console.log(` ${items.length + 1}) + create new`);
35
- const a = await ask("Choose a number: ");
36
- const n = Number(a);
37
- if (Number.isInteger(n) && n >= 1 && n <= items.length)
38
- return items[n - 1];
39
- return undefined; // create-new (or invalid → treated as create)
51
+ const createNew = items.length + 1;
52
+ console.log(` ${createNew}) + create new`);
53
+ for (let attempt = 0; attempt < 3; attempt++) {
54
+ const a = await askFn("Choose a number: ");
55
+ if (/^\d+$/.test(a)) {
56
+ const n = Number(a);
57
+ if (n >= 1 && n <= items.length)
58
+ return items[n - 1];
59
+ if (n === createNew)
60
+ return undefined;
61
+ }
62
+ console.log(`Please enter a number between 1 and ${createNew}.`);
63
+ }
64
+ throw new Error("no valid choice — aborting");
40
65
  }
41
66
  export async function bindAction(opts) {
42
67
  const cfg = loadConfig();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@retasc/cli",
3
- "version": "1.6.1",
3
+ "version": "1.6.2",
4
4
  "description": "Retasc CLI — sign in with GitHub, create projects, mint agent API keys, and wire your agent to the Retasc MCP server in one command.",
5
5
  "type": "module",
6
6
  "bin": {