kooni-bot 0.1.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 +197 -0
- package/package.json +15 -0
package/bin/kooni.js
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// kooni-bot — instala y actualiza bots de IA de Kooni en TU propia infra, en un
|
|
3
|
+
// comando. Derivado de Forja (MIT, © Horizontes IA). Bilingüe (ES/EN).
|
|
4
|
+
//
|
|
5
|
+
// npx kooni-bot init [dir] → descarga el template, configura y despliega
|
|
6
|
+
// npx kooni-bot update [dir] → trae la versión nueva SIN perder tu config
|
|
7
|
+
// npx kooni-bot version → versión del CLI
|
|
8
|
+
//
|
|
9
|
+
// El template vive en el repo público del dueño (GitHub). El CLI lo descarga
|
|
10
|
+
// como tarball (como forja), NO clona git, así funciona en Mac/Linux/Windows.
|
|
11
|
+
// Kooni es open source (MIT): NO valida licencia contra servidores — el tier
|
|
12
|
+
// Pro se activa con un código local en el panel (ver docs del proyecto).
|
|
13
|
+
import { createInterface } from "node:readline/promises";
|
|
14
|
+
import { stdin as input, stdout as output } from "node:process";
|
|
15
|
+
import { mkdirSync, writeFileSync, readFileSync, existsSync, rmSync, chmodSync, readdirSync } from "node:fs";
|
|
16
|
+
import { join } from "node:path";
|
|
17
|
+
import { execFileSync } from "node:child_process";
|
|
18
|
+
|
|
19
|
+
const REPO = process.env.KOONI_REPO || "iamnocodeveloper/kooni-bot";
|
|
20
|
+
const BRANCH = process.env.KOONI_BRANCH || "main";
|
|
21
|
+
const TARBALL = `https://codeload.github.com/${REPO}/tar.gz/refs/heads/${BRANCH}`;
|
|
22
|
+
|
|
23
|
+
// El tar de Windows (MSYS) confunde las rutas C:\... con host remoto. Normaliza
|
|
24
|
+
// a rutas POSIX (/c/...) para que tar las entienda.
|
|
25
|
+
function posixPath(p) {
|
|
26
|
+
if (process.platform !== "win32") return p;
|
|
27
|
+
return p.replace(/\\/g, "/").replace(/^([A-Za-z]):\//, "/$1/");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const LANG = process.env.KOONI_LANG === "en" ? "en" : "es";
|
|
31
|
+
const I = {
|
|
32
|
+
es: {
|
|
33
|
+
init: "Instala un bot de Kooni en TU Cloudflare (en un comando).",
|
|
34
|
+
update: "Trae la versión nueva del template SIN perder tu configuración ni tus datos.",
|
|
35
|
+
download: (u) => `Bajando el template de Kooni… (${u})`,
|
|
36
|
+
ok: (s) => `✓ ${s}`,
|
|
37
|
+
err: (m) => `✗ ${m}`,
|
|
38
|
+
extractDir: "directorio",
|
|
39
|
+
whichDir: "¿En qué carpeta lo instalo? (vacío = actual)",
|
|
40
|
+
runInit: "El template está listo. Ahora configura tu bot:",
|
|
41
|
+
updateDone: "Actualizado. Revisa que nada en member/ cambió.",
|
|
42
|
+
needDir: "No encuentro una instalación de Kooni en:",
|
|
43
|
+
},
|
|
44
|
+
en: {
|
|
45
|
+
init: "Install a Kooni bot on YOUR Cloudflare (one command).",
|
|
46
|
+
update: "Fetch the new template version WITHOUT losing your config or data.",
|
|
47
|
+
download: (u) => `Downloading the Kooni template… (${u})`,
|
|
48
|
+
ok: (s) => `✓ ${s}`,
|
|
49
|
+
err: (m) => `✗ ${m}`,
|
|
50
|
+
extractDir: "directory",
|
|
51
|
+
whichDir: "Which folder should I install it in? (empty = current)",
|
|
52
|
+
runInit: "Template ready. Now configure your bot:",
|
|
53
|
+
updateDone: "Updated. Make sure nothing in member/ changed.",
|
|
54
|
+
needDir: "No Kooni install found in:",
|
|
55
|
+
},
|
|
56
|
+
}[LANG];
|
|
57
|
+
|
|
58
|
+
const t = (es, en) => (LANG === "es" ? es : en);
|
|
59
|
+
|
|
60
|
+
function help() {
|
|
61
|
+
console.log(`
|
|
62
|
+
kooni-bot — ${t("instala tu asistente de IA en Cloudflare", "install your AI assistant on Cloudflare")}
|
|
63
|
+
|
|
64
|
+
npx kooni-bot init [dir] ${t("instala (descarga template + config + deploy)", "install (download template + config + deploy)")}
|
|
65
|
+
npx kooni-bot update [dir] ${t("actualiza sin perder tu configuración", "update without losing config")}
|
|
66
|
+
npx kooni-bot version ${t("versión del CLI", "CLI version")}
|
|
67
|
+
`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function ask(q, dflt = "") {
|
|
71
|
+
const rl = createInterface({ input, output });
|
|
72
|
+
const ans = await rl.question(dflt ? `${q} [${dflt}] ` : `${q} `);
|
|
73
|
+
rl.close();
|
|
74
|
+
return ans.trim() || dflt;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function download(url, dest) {
|
|
78
|
+
const res = await fetch(url);
|
|
79
|
+
if (!res.ok) {
|
|
80
|
+
throw new Error(t(`no pude bajar el template (HTTP ${res.status}). Revisa tu internet o el repo ${REPO}.`, `could not download template (HTTP ${res.status}). Check internet or repo ${REPO}.`));
|
|
81
|
+
}
|
|
82
|
+
const buf = Buffer.from(await res.arrayBuffer());
|
|
83
|
+
writeFileSync(dest, buf);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function extractFresh(tgz, dir) {
|
|
87
|
+
mkdirSync(dir, { recursive: true });
|
|
88
|
+
// El tarball de GitHub trae una carpeta raíz (kooni-bot-main/). Extraemos a
|
|
89
|
+
// una carpeta temporal y movemos el contenido (más robusto en Windows que
|
|
90
|
+
// --strip-components con el tar nativo).
|
|
91
|
+
const tmp = join(dir, ".kooni-extract");
|
|
92
|
+
mkdirSync(tmp, { recursive: true });
|
|
93
|
+
execFileSync("tar", ["-xzf", posixPath(tgz), "-C", posixPath(tmp)]);
|
|
94
|
+
const root = readdirSync(tmp)[0];
|
|
95
|
+
const src = join(tmp, root);
|
|
96
|
+
const entries = readdirSync(src);
|
|
97
|
+
for (const e of entries) {
|
|
98
|
+
const from = join(src, e);
|
|
99
|
+
const to = join(dir, e);
|
|
100
|
+
try { rmSync(to, { recursive: true, force: true }); } catch {}
|
|
101
|
+
// moveSync manual: rename falla entre unidades en Windows, copiamos.
|
|
102
|
+
execFileSync("cp", ["-r", from, dir]);
|
|
103
|
+
}
|
|
104
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
105
|
+
rmSync(tgz, { force: true });
|
|
106
|
+
writeFileSync(join(dir, ".kooni-version"), "cli-0.1.0");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function extractOver(tgz, dir) {
|
|
110
|
+
// Actualización: NO pisar la config del miembro ni los datos.
|
|
111
|
+
const tmp = join(dir, ".kooni-extract");
|
|
112
|
+
mkdirSync(tmp, { recursive: true });
|
|
113
|
+
execFileSync("tar", ["-xzf", tgz, "-C", tmp]);
|
|
114
|
+
const root = readdirSync(tmp)[0];
|
|
115
|
+
const src = join(tmp, root);
|
|
116
|
+
const EXCLUDE = ["member", "wrangler.toml", ".dev.vars", ".dev.vars.example", ".dev.vars.local", ".env", ".env.example", ".bot-state.json", ".bot-setup.json", ".git", ".kooni-extract"];
|
|
117
|
+
const entries = readdirSync(src);
|
|
118
|
+
for (const e of entries) {
|
|
119
|
+
if (EXCLUDE.includes(e)) continue;
|
|
120
|
+
const from = join(src, e);
|
|
121
|
+
const to = join(dir, e);
|
|
122
|
+
try { rmSync(to, { recursive: true, force: true }); } catch {}
|
|
123
|
+
execFileSync("cp", ["-r", from, dir]);
|
|
124
|
+
}
|
|
125
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
126
|
+
rmSync(tgz, { force: true });
|
|
127
|
+
writeFileSync(join(dir, ".kooni-version"), "cli-0.1.0");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function isKooni(dir) {
|
|
131
|
+
return existsSync(join(dir, "package.json")) && (existsSync(join(dir, "member")) || existsSync(join(dir, "src/index.ts")));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function cmdInit() {
|
|
135
|
+
const dirArg = process.argv[3] || "";
|
|
136
|
+
let dir = dirArg ? join(process.cwd(), dirArg) : process.cwd();
|
|
137
|
+
if (!dirArg) {
|
|
138
|
+
const ans = await ask(t("¿En qué carpeta lo instalo? (vacío = actual)", "Install in which folder? (empty = current)"), "kooni-bot");
|
|
139
|
+
dir = ans === "kooni-bot" ? join(process.cwd(), "kooni-bot") : join(process.cwd(), ans);
|
|
140
|
+
}
|
|
141
|
+
mkdirSync(dir, { recursive: true });
|
|
142
|
+
if (!isKooni(dir) && existsSync(join(dir, "package.json"))) {
|
|
143
|
+
console.log(I.err(t("la carpeta ya tiene un proyecto. Usa `kooni-bot update` en su lugar.", "folder already has a project. Use `kooni-bot update` instead.")));
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
const tgz = join(dir, ".kooni-template.tgz");
|
|
147
|
+
console.log(I.download(TARBALL));
|
|
148
|
+
await download(TARBALL, tgz);
|
|
149
|
+
extractFresh(tgz, dir);
|
|
150
|
+
console.log(I.ok(t("template descargado.", "template downloaded.")));
|
|
151
|
+
|
|
152
|
+
// Instalar dependencias y correr el instalador (configura slug, secrets, deploy).
|
|
153
|
+
console.log(I.runInit);
|
|
154
|
+
const installer = process.platform === "win32" ? "scripts/kooni-init.ps1" : "scripts/kooni-init.sh";
|
|
155
|
+
const cmd = process.platform === "win32" ? "powershell" : "bash";
|
|
156
|
+
const script = join(dir, installer);
|
|
157
|
+
if (!existsSync(script)) {
|
|
158
|
+
console.log(I.err(t("no encontré el instalador en el template.", "installer not found in template.")));
|
|
159
|
+
process.exit(1);
|
|
160
|
+
}
|
|
161
|
+
try {
|
|
162
|
+
execFileSync(cmd, [script, "deploy"], { cwd: dir, stdio: "inherit" });
|
|
163
|
+
} catch {
|
|
164
|
+
console.log(I.err(t("el instalador falló. Revisa los mensajes de arriba.", "installer failed. Check messages above.")));
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
167
|
+
console.log(I.ok(t("¡Bot listo! Tu panel: https://<slug>.workers.dev/admin", "Bot ready! Your panel: https://<slug>.workers.dev/admin")));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async function cmdUpdate() {
|
|
171
|
+
const dir = join(process.cwd(), process.argv[3] || "");
|
|
172
|
+
if (!isKooni(dir)) {
|
|
173
|
+
console.log(I.err(`${I.needDir} ${dir}`));
|
|
174
|
+
process.exit(1);
|
|
175
|
+
}
|
|
176
|
+
const tgz = join(dir, ".kooni-template.tgz");
|
|
177
|
+
console.log(I.download(TARBALL));
|
|
178
|
+
await download(TARBALL, tgz);
|
|
179
|
+
extractOver(tgz, dir);
|
|
180
|
+
console.log(I.ok(I.updateDone));
|
|
181
|
+
console.log(t("Luego corre: pnpm run deploy (o bash scripts/kooni-init.sh deploy)", "Then run: pnpm run deploy (or bash scripts/kooni-init.sh deploy)"));
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async function main() {
|
|
185
|
+
const cmd = process.argv[2] || "help";
|
|
186
|
+
if (cmd === "help" || cmd === "--help" || cmd === "-h") return help();
|
|
187
|
+
if (cmd === "version" || cmd === "--version" || cmd === "-v") { console.log("kooni-bot 0.1.0"); return; }
|
|
188
|
+
if (cmd === "init") return cmdInit();
|
|
189
|
+
if (cmd === "update") return cmdUpdate();
|
|
190
|
+
console.log(t(`comando desconocido: ${cmd}`, `unknown command: ${cmd}`));
|
|
191
|
+
help();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
main().catch((e) => {
|
|
195
|
+
console.error(I.err(e?.message || e));
|
|
196
|
+
process.exit(1);
|
|
197
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "kooni-bot",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Kooni — instala tu asistente de IA multicanal (WhatsApp, Instagram, Messenger, Telegram) en TU Cloudflare, en un comando.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"kooni-bot": "bin/kooni.js"
|
|
9
|
+
},
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">=18"
|
|
12
|
+
},
|
|
13
|
+
"keywords": ["chatbot", "ai", "whatsapp", "instagram", "telegram", "cloudflare", "kooni"],
|
|
14
|
+
"files": ["bin"]
|
|
15
|
+
}
|