kooni-bot 0.2.2 → 0.2.4

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.
Files changed (2) hide show
  1. package/bin/kooni.js +33 -29
  2. package/package.json +1 -1
package/bin/kooni.js CHANGED
@@ -22,7 +22,7 @@ import { execFileSync, spawnSync } from "node:child_process";
22
22
  import { randomUUID } from "node:crypto";
23
23
  import { fileURLToPath } from "node:url";
24
24
 
25
- const CLI_VERSION = "0.2.2";
25
+ const CLI_VERSION = "0.2.4";
26
26
 
27
27
  const REPO = process.env.KOONI_REPO || "iamnocodeveloper/kooni-bot";
28
28
  const BRANCH = process.env.KOONI_BRANCH || "main";
@@ -339,27 +339,30 @@ async function ask(rl, q, val) {
339
339
  return (await rl.question("\n " + C.b(q) + "\n " + C.cyan("› "))).trim();
340
340
  }
341
341
 
342
- // Input oculto para API keys / contraseñas. Usa una interfaz readline DEDICADA
343
- // (no el rl compartido) con máscara de salida para no chocar con los listeners
344
- // del selector y soportar pegado. Devuelve "" si no hay TTY.
345
- async function promptSecret(q) {
342
+ // Input oculto para API keys / contraseñas. Reutiliza la MISMA interfaz `rl`
343
+ // compartida (crear una segunda readline sobre el mismo stdin competía por los
344
+ // datos y nunca recibía Enter el proceso se quedaba colgado). Enmascara el eco
345
+ // parcheando temporalmente `_writeToOutput`: deja pasar solo los saltos de línea.
346
+ async function promptSecret(rl, q) {
346
347
  if (!interactive()) return "";
347
- const rl = createInterface({ input, output, terminal: true });
348
- const answer = await new Promise((resolve) => {
349
- // Enmascara lo que teclea el usuario: solo deja pasar el prompt y los saltos
350
- // de línea (nunca la tecla), para que la API key quede oculta.
351
- const orig = rl._writeToOutput;
352
- rl._writeToOutput = (s) => {
353
- if (s === "\r\n" || s === "\n" || s === "\r") process.stdout.write(s);
354
- else if (typeof orig === "function" && s.includes(" ")) orig.call(rl, s);
355
- };
356
- rl.question("\n " + C.b(q) + "\n " + C.cyan("› "), (a) => {
357
- rl.close();
358
- resolve(a);
359
- });
360
- });
361
- process.stdout.write("\n");
362
- return answer.trim();
348
+ process.stdout.write("\n " + C.b(q) + "\n " + C.cyan("› "));
349
+
350
+ const orig = rl._writeToOutput;
351
+ let masking = true;
352
+ rl._writeToOutput = (s) => {
353
+ if (!masking) return typeof orig === "function" ? orig.call(rl, s) : process.stdout.write(s);
354
+ // Enmascara lo tecleado; solo deja los saltos de línea.
355
+ if (s === "\r\n" || s === "\n" || s === "\r") process.stdout.write(s);
356
+ };
357
+
358
+ try {
359
+ const answer = await rl.question("");
360
+ return answer.trim();
361
+ } finally {
362
+ rl._writeToOutput = orig;
363
+ masking = false;
364
+ process.stdout.write("\n");
365
+ }
363
366
  }
364
367
 
365
368
  async function confirm(rl, q) {
@@ -496,9 +499,11 @@ const isWin = process.platform === "win32";
496
499
 
497
500
  // Ejecuta un binario de forma segura en Windows/macOS/Linux:
498
501
  // - Unix/mac: `spawnSync(file, args)` directo.
499
- // - Windows: `npx`/`pnpm` son shims `.cmd`, que NO se pueden spawnear directo
500
- // (EINVAL). Se lanzan con `cmd.exe /c <file> <args...>` pasando los argumentos
501
- // como lista separada (nada de `shell: true` ni concatenación sin DEP0190).
502
+ // - Windows: `npx`/`pnpm` son shims, pero `cmd.exe /c` los resuelve SIN la
503
+ // extensión `.cmd` (probado: `cmd.exe /c pnpm --version` funciona, mientras
504
+ // que `pnpm.cmd` explícito falla con "no se reconoce"). Así que aquí NO se
505
+ // mapea a `.cmd`: se lanza `cmd.exe /c <file> <args...>` con args separados.
506
+ // Nada de `shell: true` ni concatenación → sin EINVAL ni DEP0190.
502
507
  // - `stdio` hereda por defecto (progreso en vivo); `capture` captura para parsear.
503
508
  function run(file, args = [], opts = {}) {
504
509
  const cwd = opts.cwd;
@@ -509,15 +514,14 @@ function run(file, args = [], opts = {}) {
509
514
  let argv = args;
510
515
 
511
516
  if (isWin) {
512
- const shim = { npx: "npx.cmd", pnpm: "pnpm.cmd" }[file] || file;
513
517
  cmd = "cmd.exe";
514
- argv = ["/d", "/c", shim, ...args];
518
+ argv = ["/d", "/c", file, ...args];
515
519
  }
516
520
 
517
521
  const r = spawnSync(cmd, argv, { cwd, input, stdio, encoding: "utf8" });
518
522
  if (r.error) throw r.error;
519
523
  if (r.status !== 0) {
520
- const err = new Error(`Command failed: ${[cmd, ...args].join(" ")}`);
524
+ const err = new Error(`Command failed: ${[file, ...args].join(" ")}`);
521
525
  err.stdout = r.stdout;
522
526
  err.stderr = r.stderr;
523
527
  err.status = r.status;
@@ -746,7 +750,7 @@ async function onboarding(rl, answers, defaultDir) {
746
750
  // La API key se pide aquí, apenas se elige el cerebro — y se conserva para el
747
751
  // deploy (se guarda como secret sin mostrarla). En no-interactivo se salta.
748
752
  if (!answers.apiKey) {
749
- answers.apiKey = await promptSecret(t().qApiKey(BRAINS[answers.brainKey].label));
753
+ answers.apiKey = await promptSecret(rl, t().qApiKey(BRAINS[answers.brainKey].label));
750
754
  }
751
755
 
752
756
  answers.what = answers.what ?? (await ask(rl, t().qWhat, undefined) || "");
@@ -873,7 +877,7 @@ async function deployBot(dir, { flags = {}, rl } = {}) {
873
877
  }
874
878
 
875
879
  let dash = "";
876
- if (interactive()) dash = await promptSecret(m("Elige una contraseña para el panel /admin (usuario: admin):", "Choose a password for the /admin dashboard (user: admin):"));
880
+ if (interactive()) dash = await promptSecret(rl, m("Elige una contraseña para el panel /admin (usuario: admin):", "Choose a password for the /admin dashboard (user: admin):"));
877
881
  if (!dash) dash = "kooni-local-password";
878
882
  wrangler(dir, ["secret", "put", "DASHBOARD_PASSWORD"], { input: dash, capture: true });
879
883
  console.log(" " + C.green("✓") + " " + t().secretOk("DASHBOARD_PASSWORD"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kooni-bot",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "Kooni — instala tu asistente de IA multicanal (WhatsApp, Instagram, Messenger, Telegram) en TU Cloudflare, en un comando.",
5
5
  "license": "MIT",
6
6
  "type": "module",