kooni-bot 0.2.16 → 0.3.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.
Files changed (2) hide show
  1. package/bin/kooni.js +43 -28
  2. package/package.json +1 -1
package/bin/kooni.js CHANGED
@@ -19,10 +19,10 @@ import { realpathSync } from "node:fs";
19
19
  import { join, basename } from "node:path";
20
20
  import { homedir } from "node:os";
21
21
  import { execFileSync, spawnSync } from "node:child_process";
22
- import { randomUUID, createHmac } from "node:crypto";
22
+ import { randomUUID, createPublicKey, verify as edVerify } from "node:crypto";
23
23
  import { fileURLToPath } from "node:url";
24
24
 
25
- const CLI_VERSION = "0.2.16";
25
+ const CLI_VERSION = "0.2.17";
26
26
 
27
27
  const REPO = process.env.KOONI_REPO || "iamnocodeveloper/kooni-bot";
28
28
  const BRANCH = process.env.KOONI_BRANCH || "main";
@@ -37,12 +37,20 @@ const INSTALLS_FILE = join(CFG_DIR, "installs.json");
37
37
  const MARKER = ".kooni-bot.json";
38
38
  const SKILL_DIR = join(homedir(), ".claude", "skills", "kooni");
39
39
 
40
- // Llave maestra de licencias (HMAC de los códigos KOONI-PRO-…). DEBE coincidir
41
- // con el secret LICENSE_MASTER_KEY del panel de licencias (InsForge kooni-licencias)
42
- // para que los códigos que genera el panel validen en TODAS las instalaciones.
43
- // Tradeoff conocido de la validación local HMAC: la llave viaja en cada worker.
44
- // La mejora recomendada (firma asimétrica, pública en el worker) está en PLAN.md.
45
- const LICENSE_MASTER_KEY = process.env.KOONI_LICENSE_MASTER_KEY || "KqQGmLK7yMl-MISS2JtMd0bCY5lws_a926ksFCIkZkk";
40
+ // Clave PÚBLICA de licencias (Ed25519). Verifica códigos KOONI-PRO-V2-… pero NO
41
+ // puede firmarlos: es segura de publicar en npm — ese es el punto de la v2.
42
+ // DEBE ser la misma que está embebida en src/license.ts del bot. La PRIVADA vive
43
+ // solo en el panel de licencias (InsForge, secret LICENSE_PRIVATE_KEY).
44
+ //
45
+ // v2 (2026-09-01): reemplaza a LICENSE_MASTER_KEY (HMAC), que viajaba en este
46
+ // mismo archivo público y permitía falsificar licencias — hallazgo S2 del PLAN.
47
+ const LICENSE_PUBLIC_KEY = process.env.KOONI_LICENSE_PUBLIC_KEY || "MCowBQYDK2VwAyEAqpP9OBrju8ebMWjQM4uYLsUV5yqWG8k8ieozT8Me8EQ=";
48
+
49
+ // Token compartido de registro/uso (X-Kooni-Token). DEBE coincidir con el secret
50
+ // REGISTER_TOKEN del panel de licencias (InsForge). Es "seguridad por oscuridad"
51
+ // (vive en el CLI público y en cada worker — ver tarea S4 en PLAN.md): el blindaje
52
+ // real lo dan la validación de formato y el rechazo de uids desconocidos.
53
+ const KOONI_REGISTER_TOKEN = process.env.KOONI_REGISTER_TOKEN || "1f8740b841b71f21eba510eb12b8e6870b5949a916cf1162";
46
54
 
47
55
  // ── color ─────────────────────────────────────────────────────────────────────
48
56
  const C = {
@@ -664,6 +672,13 @@ function stampWrangler(dir, answers, botUid) {
664
672
  s = s.replace(/^(\s*\[vars\][^\n]*\n)/m, `$1BOT_INSTANCE_ID = "${uid}"\n`);
665
673
  }
666
674
 
675
+ // Token compartido de registro/uso (panel de licencias del dueño).
676
+ if (hasInVars("KOONI_REGISTER_TOKEN")) {
677
+ set(/KOONI_REGISTER_TOKEN\s*=\s*"[^"]*"/g, `KOONI_REGISTER_TOKEN = "${KOONI_REGISTER_TOKEN}"`);
678
+ } else {
679
+ s = s.replace(/^(\s*\[vars\][^\n]*\n)/m, `$1KOONI_REGISTER_TOKEN = "${KOONI_REGISTER_TOKEN}"\n`);
680
+ }
681
+
667
682
  if (answers.provider !== "anthropic") {
668
683
  if (hasInVars("LLM_PROVIDER")) {
669
684
  set(/LLM_PROVIDER\s*=\s*"[^"]*"/g, `LLM_PROVIDER = "${answers.provider}"`);
@@ -751,9 +766,10 @@ function writeDevVars(dir, answers, kbToken) {
751
766
  lines.push("# " + answers.secret + "=<tu-api-key> ← para wrangler dev local, pégalo aquí (o usa el panel)");
752
767
  lines.push(`DASHBOARD_PASSWORD=${answers.dashPassword || "kooni-local-password"}`);
753
768
  lines.push(`KB_REINDEX_TOKEN=${kbToken}`);
754
- // Llave maestra de licencias: igual en todas las instalaciones (debe coincidir
755
- // con la del panel de licencias para que los códigos KOONI-PRO validen).
756
- lines.push(`LICENSE_MASTER_KEY=${LICENSE_MASTER_KEY}`);
769
+ // (v2) Ya NO se escribe ninguna llave de licencias: el bot verifica con la
770
+ // clave pública que trae embebida y no necesita ningún secret para eso.
771
+ // Token compartido de registro/uso (X-Kooni-Token para el panel de licencias).
772
+ lines.push(`KOONI_REGISTER_TOKEN=${KOONI_REGISTER_TOKEN}`);
757
773
  writeFileSync(join(dir, ".dev.vars"), lines.join("\n") + "\n");
758
774
  }
759
775
 
@@ -887,7 +903,7 @@ async function onboarding(rl, answers, defaultDir) {
887
903
  : !!(flags.license || answers.licenseCode);
888
904
  if (wantsPro) {
889
905
  const code = answers.licenseCode || (interactive()
890
- ? await ask(rl, m("Pega tu código KOONI-PRO-…:", "Paste your KOONI-PRO-… code:"), undefined)
906
+ ? await ask(rl, m("Pega tu código KOONI-PRO-V2-…:", "Paste your KOONI-PRO-V2-… code:"), undefined)
891
907
  : String(flags.license || "").trim());
892
908
  if (code && verifyKooniLicense(code)) {
893
909
  answers.licenseCode = code.trim();
@@ -901,12 +917,13 @@ async function onboarding(rl, answers, defaultDir) {
901
917
  }
902
918
 
903
919
  // ── licencia Pro ─────────────────────────────────────────────────────────────
904
- // Valida un código KOONI-PRO-… localmente (mismo formato que src/license.ts del
905
- // bot): HMAC-SHA256(payload, LICENSE_MASTER_KEY). No exige que esté ligado a
906
- // esta instalación (eso lo revalida el panel al leerlo).
920
+ // Valida un código KOONI-PRO-V2-… localmente (mismo formato que src/license.ts
921
+ // del bot): firma Ed25519 verificada con la clave PÚBLICA. No exige que esté
922
+ // ligado a esta instalación (eso lo revalida el panel al leerlo).
923
+ // Sin red: la verificación es puramente local, igual que antes.
907
924
  function verifyKooniLicense(code) {
908
925
  const trimmed = String(code || "").trim();
909
- const prefix = "KOONI-PRO-";
926
+ const prefix = "KOONI-PRO-V2-";
910
927
  if (!trimmed.startsWith(prefix)) return false;
911
928
  const rest = trimmed.slice(prefix.length);
912
929
  const dot = rest.lastIndexOf(".");
@@ -914,8 +931,12 @@ function verifyKooniLicense(code) {
914
931
  const enc = rest.slice(0, dot);
915
932
  const sig = rest.slice(dot + 1);
916
933
  try {
917
- const calc = createHmac("sha256", LICENSE_MASTER_KEY).update(enc).digest("hex");
918
- if (calc !== sig) return false;
934
+ const pub = createPublicKey({
935
+ key: Buffer.from(LICENSE_PUBLIC_KEY, "base64"),
936
+ format: "der",
937
+ type: "spki",
938
+ });
939
+ if (!edVerify(null, Buffer.from(enc, "utf8"), pub, Buffer.from(sig, "hex"))) return false;
919
940
  const payload = JSON.parse(Buffer.from(enc, "base64url").toString("utf8"));
920
941
  if (payload.kind === "monthly" && payload.expiry && Date.now() > payload.expiry) return false;
921
942
  return true;
@@ -1168,14 +1189,8 @@ async function deployBot(dir, { flags = {}, rl } = {}) {
1168
1189
  }
1169
1190
  }
1170
1191
 
1171
- // Llave maestra de licencias: TODAS las instalaciones la llevan, para que los
1172
- // códigos KOONI-PRO-… generados desde el panel validen aquí.
1173
- if (LICENSE_MASTER_KEY && LICENSE_MASTER_KEY !== "KqQGmLK7yMl-MISS2JtMd0bCY5lws_a926ksFCIkZkk") {
1174
- try {
1175
- wrangler(dir, ["secret", "put", "LICENSE_MASTER_KEY"], { input: LICENSE_MASTER_KEY, capture: true });
1176
- console.log(" " + C.green("✓") + " " + t().secretOk("LICENSE_MASTER_KEY"));
1177
- } catch {}
1178
- }
1192
+ // (v2) No hay secret de licencias que instalar: el worker verifica los códigos
1193
+ // con la clave PÚBLICA embebida en su propio código. Una llave menos viajando.
1179
1194
 
1180
1195
  // dependencias + migraciones + deploy (progreso visible en vivo)
1181
1196
  console.log("\n " + C.dim(t().installing));
@@ -1251,7 +1266,7 @@ async function checkin(dir, answers, version) {
1251
1266
  const slug = answers.slug || marker.slug || basename(dir);
1252
1267
  await fetchTimeout(CHECKIN_URL, {
1253
1268
  method: "POST",
1254
- headers: { "Content-Type": "application/json" },
1269
+ headers: { "Content-Type": "application/json", "X-Kooni-Token": KOONI_REGISTER_TOKEN },
1255
1270
  body: JSON.stringify({
1256
1271
  email: answers.email || undefined,
1257
1272
  slug,
@@ -1700,7 +1715,7 @@ ${C.dim(" Flags de init (modo no-interactivo, para agentes):")}
1700
1715
  ${C.dim(" --yes --slug <slug> --negocio <nombre> --bot-name <nombre> --lang es-MX|es-ES|en|pt-BR")}
1701
1716
  ${C.dim(" --tier free|pro --cerebro claude|chatgpt|grok|gateway --base-url <url>")}
1702
1717
  ${C.dim(" --que --ofrece --horario --ubicacion --telefono --web --pagos --faq --reglas --tono")}
1703
- ${C.dim(" --no-deploy --no-agent-skill --email <correo> --license <codigo-KOONI-PRO>")}
1718
+ ${C.dim(" --no-deploy --no-agent-skill --email <correo> --license <codigo-KOONI-PRO-V2>")}
1704
1719
  `);
1705
1720
  }
1706
1721
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kooni-bot",
3
- "version": "0.2.16",
3
+ "version": "0.3.0",
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",