kooni-bot 0.2.5 → 0.2.8
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 +177 -44
- 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.
|
|
25
|
+
const CLI_VERSION = "0.2.8";
|
|
26
26
|
|
|
27
27
|
const REPO = process.env.KOONI_REPO || "iamnocodeveloper/kooni-bot";
|
|
28
28
|
const BRANCH = process.env.KOONI_BRANCH || "main";
|
|
@@ -33,6 +33,7 @@ const CHECKIN_URL = process.env.KOONI_CHECKIN_URL || "https://f5gacw7g.function2
|
|
|
33
33
|
|
|
34
34
|
const CFG_DIR = join(homedir(), ".kooni");
|
|
35
35
|
const CFG_FILE = join(CFG_DIR, "config.json");
|
|
36
|
+
const INSTALLS_FILE = join(CFG_DIR, "installs.json");
|
|
36
37
|
const MARKER = ".kooni-bot.json";
|
|
37
38
|
const SKILL_DIR = join(homedir(), ".claude", "skills", "kooni");
|
|
38
39
|
|
|
@@ -250,6 +251,26 @@ function banner() {
|
|
|
250
251
|
function loadCfg() { try { return JSON.parse(readFileSync(CFG_FILE, "utf8")); } catch { return {}; } }
|
|
251
252
|
function saveCfg(o) { mkdirSync(CFG_DIR, { recursive: true }); writeFileSync(CFG_FILE, JSON.stringify(o, null, 2)); }
|
|
252
253
|
|
|
254
|
+
// Registro local de instalaciones (~/.kooni/installs.json): cada instalación de
|
|
255
|
+
// esta computadora queda identificada por su carpeta real + uid de Cloudflare.
|
|
256
|
+
// Sirve para que `deploy`/`update`/`doctor` sepan cuál elegir si hay varias.
|
|
257
|
+
function loadInstalls() {
|
|
258
|
+
try { return JSON.parse(readFileSync(INSTALLS_FILE, "utf8")); } catch { return []; }
|
|
259
|
+
}
|
|
260
|
+
function saveInstalls(list) {
|
|
261
|
+
mkdirSync(CFG_DIR, { recursive: true });
|
|
262
|
+
writeFileSync(INSTALLS_FILE, JSON.stringify(list, null, 2));
|
|
263
|
+
}
|
|
264
|
+
function recordInstall(dir, meta) {
|
|
265
|
+
const real = realpathSync(dir);
|
|
266
|
+
const list = loadInstalls().filter((x) => x && x.dir !== real);
|
|
267
|
+
list.push({ dir: real, ...meta, updatedAt: new Date().toISOString() });
|
|
268
|
+
saveInstalls(list);
|
|
269
|
+
}
|
|
270
|
+
function listInstalls() {
|
|
271
|
+
return loadInstalls().filter((x) => x && x.dir && existsSync(x.dir));
|
|
272
|
+
}
|
|
273
|
+
|
|
253
274
|
// ── flags / interacción ──────────────────────────────────────────────────────
|
|
254
275
|
function parseFlags(args) {
|
|
255
276
|
const flags = {};
|
|
@@ -461,10 +482,9 @@ function backupBeforeUpdate(dir, version) {
|
|
|
461
482
|
}
|
|
462
483
|
|
|
463
484
|
// ── markers / detección ──────────────────────────────────────────────────────
|
|
464
|
-
function writeMarker(dir,
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
}, null, 2));
|
|
485
|
+
function writeMarker(dir, meta) {
|
|
486
|
+
const prev = readMarker(dir) || {};
|
|
487
|
+
writeFileSync(join(dir, MARKER), JSON.stringify({ ...prev, ...meta, updatedAt: new Date().toISOString() }, null, 2));
|
|
468
488
|
}
|
|
469
489
|
|
|
470
490
|
function readMarker(dir) {
|
|
@@ -482,9 +502,28 @@ function isKooni(dir) {
|
|
|
482
502
|
return existsSync(join(dir, "package.json")) && (existsSync(join(dir, "member")) || existsSync(join(dir, "src", "index.ts")));
|
|
483
503
|
}
|
|
484
504
|
|
|
485
|
-
|
|
505
|
+
// Elige la instalación sobre la que actuar. `arg` es una ruta explícita (gana).
|
|
506
|
+
// Si no hay ruta, prioriza el cwd; después el registro local `installs.json`;
|
|
507
|
+
// y como último recurso escanea los hijos del cwd. Devuelve la ruta o null.
|
|
508
|
+
async function resolveBotDir(arg, rl) {
|
|
486
509
|
if (arg && isKooni(arg)) return arg;
|
|
487
510
|
if (isKooni(process.cwd())) return process.cwd();
|
|
511
|
+
|
|
512
|
+
const registered = listInstalls();
|
|
513
|
+
if (registered.length === 1) return registered[0].dir;
|
|
514
|
+
if (registered.length > 1) {
|
|
515
|
+
if (!interactive()) {
|
|
516
|
+
console.log(C.yellow("\n " + m("Hay varias instalaciones de Kooni. Pasa la carpeta explícita:", "Multiple Kooni installs found. Pass an explicit folder:")));
|
|
517
|
+
registered.forEach((x) => console.log(" " + C.cyan(`npx kooni-bot <comando> "${x.dir}"`)));
|
|
518
|
+
process.exit(1);
|
|
519
|
+
}
|
|
520
|
+
const idx = await select(rl, m("¿Cuál instalación?", "Which install?"), registered.map((x) => ({
|
|
521
|
+
key: x.dir,
|
|
522
|
+
label: `${x.slug || basename(x.dir)} · ${x.dir}`,
|
|
523
|
+
})));
|
|
524
|
+
return registered[idx]?.dir || null;
|
|
525
|
+
}
|
|
526
|
+
|
|
488
527
|
for (const e of readdirSync(process.cwd())) {
|
|
489
528
|
try {
|
|
490
529
|
const p = join(process.cwd(), e);
|
|
@@ -559,7 +598,7 @@ function sanitizeSlug(s) {
|
|
|
559
598
|
return String(s || "mi-negocio").toLowerCase().replace(/ /g, "-").replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "") || "mi-negocio";
|
|
560
599
|
}
|
|
561
600
|
|
|
562
|
-
function stampWrangler(dir, answers) {
|
|
601
|
+
function stampWrangler(dir, answers, botUid) {
|
|
563
602
|
const wt = join(dir, "wrangler.toml");
|
|
564
603
|
const example = join(dir, "wrangler.toml.example");
|
|
565
604
|
// El template distribuye wrangler.toml.example; init genera el wrangler.toml real.
|
|
@@ -569,15 +608,21 @@ function stampWrangler(dir, answers) {
|
|
|
569
608
|
if (!existsSync(wt)) return null;
|
|
570
609
|
let s = readFileSync(wt, "utf8");
|
|
571
610
|
const slug = answers.slug;
|
|
572
|
-
//
|
|
573
|
-
//
|
|
574
|
-
|
|
575
|
-
|
|
611
|
+
// Identidad ÚNICA por instalación: cada bot genera su propio uid de 6 chars y
|
|
612
|
+
// lo usa en worker/D1/Vectorize. Así dos instalaciones en la MISMA cuenta de
|
|
613
|
+
// Cloudflare nunca comparten datos. Si el wrangler.toml ya trae un uid (reinstall
|
|
614
|
+
// en la misma carpeta), se reutiliza.
|
|
615
|
+
const existingUid = (s.match(/name\s*=\s*"kooni-bot-.+-([a-f0-9]{6})"/) || [])[1];
|
|
616
|
+
const uid = existingUid || botUid || randomUUID().replace(/-/g, "").slice(0, 6);
|
|
617
|
+
const resId = slug.replace(/-/g, "_");
|
|
618
|
+
const dbName = `kooni_${resId}_${uid}_db`;
|
|
619
|
+
const kbName = `kooni_${resId}_${uid}_kb`;
|
|
620
|
+
const workerName = `kooni-bot-${slug}-${uid}`;
|
|
576
621
|
const R = REGIONS[answers.lang] || REGIONS["es-MX"];
|
|
577
622
|
|
|
578
623
|
const set = (re, val) => { s = s.replace(re, val); };
|
|
579
624
|
|
|
580
|
-
set(/^name\s*=\s*"[^"]*"/m, `name = "
|
|
625
|
+
set(/^name\s*=\s*"[^"]*"/m, `name = "${workerName}"`);
|
|
581
626
|
set(/BOT_NAME\s*=\s*"[^"]*"/g, `BOT_NAME = "${String(answers.botName).replace(/"/g, "'")}"`);
|
|
582
627
|
set(/BUSINESS_NAME\s*=\s*"[^"]*"/g, `BUSINESS_NAME = "${String(answers.businessName).replace(/"/g, "'")}"`);
|
|
583
628
|
set(/BOT_LANGUAGE\s*=\s*"[^"]*"/g, `BOT_LANGUAGE = "${answers.lang}"`);
|
|
@@ -597,6 +642,13 @@ function stampWrangler(dir, answers) {
|
|
|
597
642
|
return new RegExp(`^\\s*${key}\\s*=`, "m").test(varsMatch[0]);
|
|
598
643
|
};
|
|
599
644
|
|
|
645
|
+
// Identidad de instalación para ligar licencias (ver src/license.ts + limits.ts).
|
|
646
|
+
if (hasInVars("BOT_INSTANCE_ID")) {
|
|
647
|
+
set(/BOT_INSTANCE_ID\s*=\s*"[^"]*"/g, `BOT_INSTANCE_ID = "${uid}"`);
|
|
648
|
+
} else {
|
|
649
|
+
s = s.replace(/^(\s*\[vars\][^\n]*\n)/m, `$1BOT_INSTANCE_ID = "${uid}"\n`);
|
|
650
|
+
}
|
|
651
|
+
|
|
600
652
|
if (answers.provider !== "anthropic") {
|
|
601
653
|
if (hasInVars("LLM_PROVIDER")) {
|
|
602
654
|
set(/LLM_PROVIDER\s*=\s*"[^"]*"/g, `LLM_PROVIDER = "${answers.provider}"`);
|
|
@@ -617,7 +669,7 @@ function stampWrangler(dir, answers) {
|
|
|
617
669
|
}
|
|
618
670
|
|
|
619
671
|
writeFileSync(wt, s);
|
|
620
|
-
return { dbName, kbName, tz: R.tz, currency: R.currency };
|
|
672
|
+
return { uid, dbName, kbName, workerName, tz: R.tz, currency: R.currency };
|
|
621
673
|
}
|
|
622
674
|
|
|
623
675
|
function renderMemberConfig(answers, meta) {
|
|
@@ -696,6 +748,7 @@ function collectAnswers(flags) {
|
|
|
696
748
|
// `onboarding()` lo pregunte (interactivo) o use el default (no-interactivo).
|
|
697
749
|
return {
|
|
698
750
|
slug: flags.slug ? sanitizeSlug(flags.slug) : undefined,
|
|
751
|
+
uid: String(flags.uid || "").trim().toLowerCase().replace(/[^a-z0-9]/g, "").slice(0, 6) || undefined,
|
|
699
752
|
businessName: String(flags.negocio || flags.nombre || flags.name || "").trim() || undefined,
|
|
700
753
|
botName: String(flags["bot-name"] || "").trim() || undefined,
|
|
701
754
|
lang: flags.lang ? normBotLang(flags.lang) : undefined,
|
|
@@ -821,10 +874,11 @@ async function deployBot(dir, { flags = {}, rl } = {}) {
|
|
|
821
874
|
wrangler(dir, ["login"]);
|
|
822
875
|
console.log(" " + C.green("✓") + " " + t().loginOk);
|
|
823
876
|
|
|
824
|
-
// recursos (nombres
|
|
877
|
+
// recursos (nombres ÚNICOS por instalación, ya estampados en wrangler.toml)
|
|
825
878
|
console.log("\n " + C.dim(t().creatingResources));
|
|
826
|
-
const
|
|
827
|
-
const
|
|
879
|
+
const wtRaw = readFileSync(wt, "utf8");
|
|
880
|
+
const dbName = (wtRaw.match(/database_name\s*=\s*"([^"]+)"/) || [])[1] || "kooni_db";
|
|
881
|
+
const kbName = (wtRaw.match(/index_name\s*=\s*"([^"]+)"/) || [])[1] || "kooni_kb";
|
|
828
882
|
|
|
829
883
|
let d1Id = "";
|
|
830
884
|
try {
|
|
@@ -904,7 +958,7 @@ async function deployBot(dir, { flags = {}, rl } = {}) {
|
|
|
904
958
|
console.log(" " + C.green("✓") + " " + m("dependencias listas", "dependencies ready"));
|
|
905
959
|
|
|
906
960
|
console.log(" " + C.dim(t().migrations));
|
|
907
|
-
|
|
961
|
+
wrangler(dir, ["d1", "execute", dbName, "--file=src/db/schema.sql", "--remote"]);
|
|
908
962
|
console.log(" " + C.green("✓") + " " + m("migraciones aplicadas", "migrations applied"));
|
|
909
963
|
|
|
910
964
|
console.log(" " + C.dim(t().deploying));
|
|
@@ -925,6 +979,8 @@ async function deployBot(dir, { flags = {}, rl } = {}) {
|
|
|
925
979
|
} else {
|
|
926
980
|
console.log(C.yellow(" ⚠ " + m("no se detectó la URL del worker — revisa la salida del deploy", "couldn't detect the worker URL — check the deploy output")));
|
|
927
981
|
}
|
|
982
|
+
// Persistir identidad de Cloudflare en el marker (para update/doctor/selector).
|
|
983
|
+
writeMarker(dir, { databaseId: d1Id, workerUrl: url || undefined, dbName, kbName });
|
|
928
984
|
return url;
|
|
929
985
|
}
|
|
930
986
|
|
|
@@ -932,14 +988,19 @@ async function deployBot(dir, { flags = {}, rl } = {}) {
|
|
|
932
988
|
async function checkin(dir, answers, version) {
|
|
933
989
|
if (process.env.KOONI_NO_CHECKIN === "1" || process.env.KOONI_SILENT === "1") return;
|
|
934
990
|
try {
|
|
935
|
-
const
|
|
991
|
+
const marker = readMarker(dir) || {};
|
|
992
|
+
const slug = answers.slug || marker.slug || basename(dir);
|
|
936
993
|
await fetchTimeout(CHECKIN_URL, {
|
|
937
994
|
method: "POST",
|
|
938
995
|
headers: { "Content-Type": "application/json" },
|
|
939
996
|
body: JSON.stringify({
|
|
940
997
|
email: answers.email || undefined,
|
|
941
998
|
slug,
|
|
942
|
-
|
|
999
|
+
uid: marker.uid,
|
|
1000
|
+
workerName: marker.workerName,
|
|
1001
|
+
dbName: marker.dbName,
|
|
1002
|
+
kbName: marker.kbName,
|
|
1003
|
+
workerUrl: marker.workerUrl || `https://kooni-bot-${slug}.workers.dev`,
|
|
943
1004
|
cliVersion: CLI_VERSION,
|
|
944
1005
|
botVersion: version,
|
|
945
1006
|
tier: answers.tier,
|
|
@@ -973,9 +1034,14 @@ constructor. No hay licencia ni servidor de Horizontes: el tier free/pro se cont
|
|
|
973
1034
|
- \`npx kooni-bot init [dir]\` — descarga el template, configura (idioma, negocio, cerebro) y ofrece desplegar.
|
|
974
1035
|
- \`npx kooni-bot deploy [dir]\` — provisiona Cloudflare (login, D1/Vectorize/R2, secrets, migraciones, deploy).
|
|
975
1036
|
- \`npx kooni-bot update [dir]\` — trae la versión nueva conservando \`member/\`, \`wrangler.toml\` y datos.
|
|
1037
|
+
- \`npx kooni-bot update --all\` — actualiza TODAS las instalaciones registradas en esta computadora.
|
|
976
1038
|
- \`npx kooni-bot doctor [dir]\` — diagnostica el bot instalado.
|
|
977
1039
|
- \`npx kooni-bot version\`.
|
|
978
1040
|
|
|
1041
|
+
## Conexión de canales (DESDE el panel, sin redeploy)
|
|
1042
|
+
- Telegram y Zernio se conectan pegando su token/API key en \`/admin/conexiones\`. Se guardan en D1 (\`settings\`) y el canal se pone verde al instante, SIN \`wrangler secret put\` ni redeploy.
|
|
1043
|
+
- La URL del webhook de cada canal se muestra en su propia card (con botón copiar).
|
|
1044
|
+
|
|
979
1045
|
## Regla de oro (memorízala)
|
|
980
1046
|
| Carpeta / archivo | Qué pasa al actualizar |
|
|
981
1047
|
|---|---|
|
|
@@ -1079,7 +1145,7 @@ async function cmdInit(flags, rest) {
|
|
|
1079
1145
|
const answers = collectAnswers(flags);
|
|
1080
1146
|
await onboarding(rl, answers, basename(dir));
|
|
1081
1147
|
|
|
1082
|
-
const meta = stampWrangler(dir, answers);
|
|
1148
|
+
const meta = stampWrangler(dir, answers, answers.uid);
|
|
1083
1149
|
const kbToken = "kooni-reindex-" + randomUUID().replace(/-/g, "").slice(0, 12);
|
|
1084
1150
|
if (existsSync(join(dir, "member"))) {
|
|
1085
1151
|
const region = REGIONS[answers.lang] || REGIONS["es-MX"];
|
|
@@ -1090,9 +1156,26 @@ async function cmdInit(flags, rest) {
|
|
|
1090
1156
|
}));
|
|
1091
1157
|
}
|
|
1092
1158
|
writeDevVars(dir, answers, kbToken);
|
|
1093
|
-
writeMarker(dir, {
|
|
1159
|
+
writeMarker(dir, {
|
|
1160
|
+
slug: answers.slug,
|
|
1161
|
+
version,
|
|
1162
|
+
lang: L,
|
|
1163
|
+
uid: (meta && meta.uid) || answers.uid,
|
|
1164
|
+
workerName: meta && meta.workerName,
|
|
1165
|
+
dbName: meta && meta.dbName,
|
|
1166
|
+
kbName: meta && meta.kbName,
|
|
1167
|
+
});
|
|
1094
1168
|
console.log("\n " + C.green("✓") + " " + t().configDone);
|
|
1095
1169
|
|
|
1170
|
+
// Registrar la instalación local para el selector multi-bot.
|
|
1171
|
+
recordInstall(dir, {
|
|
1172
|
+
slug: answers.slug,
|
|
1173
|
+
uid: (meta && meta.uid) || answers.uid,
|
|
1174
|
+
workerName: meta && meta.workerName,
|
|
1175
|
+
dbName: meta && meta.dbName,
|
|
1176
|
+
kbName: meta && meta.kbName,
|
|
1177
|
+
});
|
|
1178
|
+
|
|
1096
1179
|
// deploy (si no lo deshabilitan)
|
|
1097
1180
|
if (!flags["no-deploy"] && !process.env.KOONI_NO_DEPLOY) {
|
|
1098
1181
|
const deployFlags = { ...flags, brainKey: answers.brainKey, "api-key": answers.apiKey || "" };
|
|
@@ -1122,11 +1205,11 @@ async function cmdDeploy(flags, rest) {
|
|
|
1122
1205
|
banner();
|
|
1123
1206
|
installAgentSkill(flags);
|
|
1124
1207
|
|
|
1125
|
-
const dir = resolveBotDir(rest[0]);
|
|
1126
|
-
if (!dir) { console.log(" " + C.red(t().needDir) + " " + (rest[0] || process.cwd()) + "\n"); process.exit(1); }
|
|
1127
|
-
|
|
1128
1208
|
const rl = createInterface({ input, output });
|
|
1129
1209
|
try {
|
|
1210
|
+
const dir = await resolveBotDir(rest[0], rl);
|
|
1211
|
+
if (!dir) { console.log(" " + C.red(t().needDir) + " " + (rest[0] || process.cwd()) + "\n"); process.exit(1); }
|
|
1212
|
+
|
|
1130
1213
|
// provider para elegir el secret correcto
|
|
1131
1214
|
let brainKey = "claude";
|
|
1132
1215
|
try {
|
|
@@ -1144,24 +1227,16 @@ async function cmdDeploy(flags, rest) {
|
|
|
1144
1227
|
}
|
|
1145
1228
|
}
|
|
1146
1229
|
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
if (flags.lang === "en" || cfg.lang === "en") L = "en";
|
|
1151
|
-
banner();
|
|
1152
|
-
installAgentSkill(flags);
|
|
1153
|
-
|
|
1154
|
-
const dir = resolveBotDir(rest[0]);
|
|
1155
|
-
if (!dir) { console.log(" " + C.red(t().needDir) + " " + (rest[0] || process.cwd()) + "\n"); process.exit(1); }
|
|
1156
|
-
|
|
1230
|
+
// Actualiza UNA instalación. Devuelve un resumen; NO hace process.exit (para que
|
|
1231
|
+
// `--all` pueda recorrer varias sin cortar el proceso).
|
|
1232
|
+
async function updateOne(dir, flags = {}) {
|
|
1157
1233
|
const marker = readMarker(dir) || {};
|
|
1234
|
+
const slug = marker.slug || basename(dir);
|
|
1158
1235
|
const current = marker.version || readPkgVersion(dir) || "0.0.0";
|
|
1159
1236
|
|
|
1160
1237
|
const tgz = join(dir, ".kooni-template.tgz");
|
|
1161
|
-
process.stdout.write(C.dim(" " + t().updRevalidating + "\n"));
|
|
1162
1238
|
await downloadTemplate(tgz);
|
|
1163
1239
|
|
|
1164
|
-
// versión nueva desde el tarball
|
|
1165
1240
|
const tmp = join(dir, ".kooni-extract");
|
|
1166
1241
|
mkdirSync(tmp, { recursive: true });
|
|
1167
1242
|
const src = extractToTemp(tgz, tmp);
|
|
@@ -1176,29 +1251,26 @@ async function cmdUpdate(flags, rest) {
|
|
|
1176
1251
|
if (!verLt(current, next)) {
|
|
1177
1252
|
rmSync(tmp, { recursive: true, force: true });
|
|
1178
1253
|
rmSync(tgz, { force: true });
|
|
1179
|
-
|
|
1180
|
-
return;
|
|
1254
|
+
return { dir, slug, updated: false, from: current, to: next };
|
|
1181
1255
|
}
|
|
1182
1256
|
|
|
1183
|
-
// respaldo + extraer sobre la instalación
|
|
1184
1257
|
const backupPath = backupBeforeUpdate(dir, current);
|
|
1185
1258
|
extractOver(tgz, dir);
|
|
1186
|
-
writeMarker(dir, { slug
|
|
1259
|
+
writeMarker(dir, { slug, version: next, lang: marker.lang || L });
|
|
1187
1260
|
|
|
1188
1261
|
console.log(" " + C.green("✓") + " " + t().updDone(next));
|
|
1189
1262
|
if (backupPath) console.log(" " + C.dim(t().updBackup(backupPath.slice(dir.length + 1))));
|
|
1190
1263
|
console.log(" " + C.dim(t().updPreserved));
|
|
1191
1264
|
console.log(" " + C.dim(t().updReplaced));
|
|
1192
1265
|
|
|
1193
|
-
// dependencias + migraciones + reindex + deploy
|
|
1194
1266
|
console.log("\n " + C.dim(t().installing));
|
|
1195
1267
|
runPnpm(dir, ["install"]);
|
|
1196
|
-
|
|
1268
|
+
const dbName = marker.dbName || (readFileSync(join(dir, "wrangler.toml"), "utf8").match(/database_name\s*=\s*"([^"]+)"/) || [])[1] || "kooni_db";
|
|
1269
|
+
try { wrangler(dir, ["d1", "execute", dbName, "--file=src/db/schema.sql", "--remote"]); } catch { /* best-effort */ }
|
|
1197
1270
|
try { runPnpm(dir, ["kb:reindex"]); } catch { /* best-effort */ }
|
|
1198
1271
|
console.log(" " + C.dim(t().deploying));
|
|
1199
1272
|
try { runPnpm(dir, ["run", "deploy"]); } catch { /* el deploy-check imprime el detalle */ }
|
|
1200
1273
|
|
|
1201
|
-
// reindex del worker si hay estado con URL
|
|
1202
1274
|
try {
|
|
1203
1275
|
const st = JSON.parse(readFileSync(join(dir, ".bot-state.json"), "utf8"));
|
|
1204
1276
|
if (st.worker_url) {
|
|
@@ -1208,6 +1280,60 @@ async function cmdUpdate(flags, rest) {
|
|
|
1208
1280
|
}
|
|
1209
1281
|
} catch {}
|
|
1210
1282
|
|
|
1283
|
+
return { dir, slug, updated: true, from: current, to: next };
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
async function cmdUpdate(flags, rest) {
|
|
1287
|
+
const cfg = loadCfg();
|
|
1288
|
+
ASSUME_YES = !!(flags.yes || process.env.KOONI_YES);
|
|
1289
|
+
if (flags.lang === "en" || cfg.lang === "en") L = "en";
|
|
1290
|
+
banner();
|
|
1291
|
+
installAgentSkill(flags);
|
|
1292
|
+
|
|
1293
|
+
// --all: actualiza todas las instalaciones registradas en ~/.kooni/installs.json.
|
|
1294
|
+
if (flags.all) {
|
|
1295
|
+
const dirs = listInstalls().map((x) => x.dir);
|
|
1296
|
+
if (dirs.length === 0) {
|
|
1297
|
+
console.log(" " + C.red(t().needDir) + " (sin instalaciones registradas)\n");
|
|
1298
|
+
process.exit(1);
|
|
1299
|
+
}
|
|
1300
|
+
console.log(" " + C.dim(m(`Actualizando ${dirs.length} instalaciones…`, `Updating ${dirs.length} installs…`)) + "\n");
|
|
1301
|
+
const results = [];
|
|
1302
|
+
for (const d of dirs) {
|
|
1303
|
+
const marker = readMarker(d) || {};
|
|
1304
|
+
const slug = marker.slug || basename(d);
|
|
1305
|
+
console.log(C.b("\n ◇ " + slug));
|
|
1306
|
+
try {
|
|
1307
|
+
const r = await updateOne(d, flags);
|
|
1308
|
+
results.push(r);
|
|
1309
|
+
console.log(r.updated
|
|
1310
|
+
? " " + C.green(`✓ ${slug}: ${r.from} → ${r.to}`)
|
|
1311
|
+
: " " + C.green(`✓ ${slug}: ${t().updUpToDate} (v${r.to})`));
|
|
1312
|
+
} catch (e) {
|
|
1313
|
+
results.push({ dir: d, slug, error: e.message || String(e) });
|
|
1314
|
+
console.log(" " + C.red(`✗ ${slug}: ${e.message || e}`));
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
console.log("");
|
|
1318
|
+
return;
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
const rl = createInterface({ input, output });
|
|
1322
|
+
let dir;
|
|
1323
|
+
try {
|
|
1324
|
+
dir = await resolveBotDir(rest[0], rl);
|
|
1325
|
+
} finally {
|
|
1326
|
+
rl.close();
|
|
1327
|
+
}
|
|
1328
|
+
if (!dir) { console.log(" " + C.red(t().needDir) + " " + (rest[0] || process.cwd()) + "\n"); process.exit(1); }
|
|
1329
|
+
|
|
1330
|
+
const marker = readMarker(dir) || {};
|
|
1331
|
+
const slug = marker.slug || basename(dir);
|
|
1332
|
+
console.log(C.b("\n ◇ " + slug));
|
|
1333
|
+
const r = await updateOne(dir, flags);
|
|
1334
|
+
if (!r.updated) {
|
|
1335
|
+
console.log(" " + C.green("✓") + " " + t().updUpToDate + " (v" + r.to + ")\n");
|
|
1336
|
+
}
|
|
1211
1337
|
console.log("");
|
|
1212
1338
|
}
|
|
1213
1339
|
|
|
@@ -1220,7 +1346,13 @@ async function cmdDoctor(flags, rest) {
|
|
|
1220
1346
|
const warn = (s, h) => { console.log(" " + C.yellow("⚠") + " " + s); if (h) console.log(" " + C.dim(h)); };
|
|
1221
1347
|
const bad = (s, h) => { console.log(" " + C.red("✗") + " " + s); if (h) console.log(" " + C.dim(h)); };
|
|
1222
1348
|
|
|
1223
|
-
const
|
|
1349
|
+
const rl = createInterface({ input, output });
|
|
1350
|
+
let dir;
|
|
1351
|
+
try {
|
|
1352
|
+
dir = await resolveBotDir(rest[0], rl);
|
|
1353
|
+
} finally {
|
|
1354
|
+
rl.close();
|
|
1355
|
+
}
|
|
1224
1356
|
if (!dir) { bad(t().needDir + " " + (rest[0] || process.cwd())); process.exit(1); }
|
|
1225
1357
|
ok(m("Bot encontrado en ", "Bot found in ") + C.cyan(dir));
|
|
1226
1358
|
|
|
@@ -1279,6 +1411,7 @@ ${C.cyan("kooni-bot")} — ${t().helpIntro}
|
|
|
1279
1411
|
${C.cyan("npx kooni-bot init [dir]")} ${m("instala (descarga template + config + deploy)", "install (download template + config + deploy)")}
|
|
1280
1412
|
${C.cyan("npx kooni-bot deploy [dir]")} ${m("provisiona Cloudflare y publica el worker", "provision Cloudflare and publish the worker")}
|
|
1281
1413
|
${C.cyan("npx kooni-bot update [dir]")} ${m("actualiza sin perder tu configuración", "update without losing config")}
|
|
1414
|
+
${C.cyan("npx kooni-bot update --all")} ${m("actualiza TODAS las instalaciones registradas", "update ALL registered installs")}
|
|
1282
1415
|
${C.cyan("npx kooni-bot doctor [dir]")} ${m("diagnóstico del bot instalado", "diagnose the installed bot")}
|
|
1283
1416
|
${C.cyan("npx kooni-bot version")} ${m("versión del CLI", "CLI version")}
|
|
1284
1417
|
|
package/package.json
CHANGED