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.
- package/bin/kooni.js +43 -28
- 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,
|
|
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.
|
|
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
|
-
//
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
|
|
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
|
-
//
|
|
755
|
-
//
|
|
756
|
-
|
|
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
|
|
905
|
-
// bot):
|
|
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
|
|
918
|
-
|
|
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
|
-
//
|
|
1172
|
-
//
|
|
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