@ghostystudio/cli 0.23.0 → 0.24.1
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/README.md +14 -0
- package/dist/index.js +157 -31
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -90,6 +90,20 @@ Exit codes: `0` ok · `1` API error · `2` usage error · `3` not signed in.
|
|
|
90
90
|
- `try --replay FILE --out DIR`: replays real conversations from your machine. The report is written after every step, survives Ghosty deploys (it honors `Retry-After`), and `--resume` continues a partial run.
|
|
91
91
|
- `agents doctor` speaks English like the rest of the CLI; `channels whatsapp ls` returns a full URL.
|
|
92
92
|
|
|
93
|
+
### Changes in 0.24.1
|
|
94
|
+
|
|
95
|
+
- `dbs rename` and `dbs rm` also work for Ghosty staff (migrations), not only the database's owner.
|
|
96
|
+
|
|
97
|
+
### Changes in 0.24.0
|
|
98
|
+
|
|
99
|
+
- `dbs rename <agent> <db> <new> [--dry-run]`: renames the database and rewrites every prompt, env value and SKILL.md that names it (the dry run lists them).
|
|
100
|
+
- `dbs ls --orphans` (databases no agent has) and `dbs rm <agent> <db> [--dry-run] [--yes]` (deletes the whole database; owner only, same "Not deleted" error as the web).
|
|
101
|
+
- `dbs import` with replace warns about rows that exist only in gs (a database shared with agents already writing there) before deleting them.
|
|
102
|
+
- `channels whatsapp integrations <agent>`: the owner's WhatsApp Business numbers in Formmy (number, name, who answers today, Integration id).
|
|
103
|
+
- `channels whatsapp history --dry-run` works before `link`, to choose `--days` before the cutover.
|
|
104
|
+
- `turns ls --skipped`: WhatsApp Business customer messages that did not open a turn, and why (operator active, records only, paused, reaction, channel off, no balance).
|
|
105
|
+
- `agents create --prompt-file` says which prompt it applied instead of asking for one.
|
|
106
|
+
|
|
93
107
|
### Changes in 0.22.0
|
|
94
108
|
|
|
95
109
|
- `try --waba` archives its test card when it ends (`--keep` leaves it) and auto-stage no longer runs on tests.
|
package/dist/index.js
CHANGED
|
@@ -7594,6 +7594,12 @@ var SERVER_MESSAGES = [
|
|
|
7594
7594
|
// — Bases de datos
|
|
7595
7595
|
["no hay una base \xAB{}\xBB", `there's no database "{}"`],
|
|
7596
7596
|
["Ya tienes una base con ese nombre.", "You already have a database with that name."],
|
|
7597
|
+
["La base ya se llama {}.", "The database is already called {}."],
|
|
7598
|
+
["No se borr\xF3: s\xF3lo quien es due\xF1o de la base puede borrarla.", "Not deleted: only the database's owner can delete it."],
|
|
7599
|
+
["No se renombr\xF3: s\xF3lo quien es due\xF1o de la base puede renombrarla.", "Not renamed: only the database's owner can rename it."],
|
|
7600
|
+
["falta name (el nombre nuevo)", "missing name (the new name)"],
|
|
7601
|
+
["s\xF3lo el due\xF1o, un admin del agente o staff ven los n\xFAmeros de Formmy", "only the owner, an admin of the agent or staff can see the Formmy numbers"],
|
|
7602
|
+
["no pude leer sus turnos", "couldn't read its turns"],
|
|
7597
7603
|
["Tu plan incluye {} base{} de datos.", "Your plan includes {} database{}."],
|
|
7598
7604
|
["El nombre lleva letras min\xFAsculas, n\xFAmeros, guiones o guiones bajos (m\xE1x. 64).", "The name takes lowercase letters, numbers, hyphens or underscores (max 64)."],
|
|
7599
7605
|
["Nombre de tabla inv\xE1lido (letras, n\xFAmeros y _).", "Invalid table name (letters, numbers and _)."],
|
|
@@ -8261,7 +8267,7 @@ async function postLong(path, body, timeoutMs = 9e5) {
|
|
|
8261
8267
|
const req = httpsRequest(`${base}${path}`, {
|
|
8262
8268
|
method: "POST",
|
|
8263
8269
|
timeout: timeoutMs,
|
|
8264
|
-
headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.
|
|
8270
|
+
headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.24.1"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
|
|
8265
8271
|
}, (res) => {
|
|
8266
8272
|
let buf = "";
|
|
8267
8273
|
res.on("data", (c) => buf += c);
|
|
@@ -8303,7 +8309,7 @@ async function request(path, opts = {}, retried = false) {
|
|
|
8303
8309
|
method: opts.method ?? (opts.body !== void 0 || opts.raw !== void 0 ? "POST" : "GET"),
|
|
8304
8310
|
headers: {
|
|
8305
8311
|
authorization: `Bearer ${token}`,
|
|
8306
|
-
"user-agent": `ghosty-cli/${"0.
|
|
8312
|
+
"user-agent": `ghosty-cli/${"0.24.1"}`,
|
|
8307
8313
|
"accept-language": lang(),
|
|
8308
8314
|
...opts.body !== void 0 ? { "content-type": "application/json" } : {},
|
|
8309
8315
|
...opts.headers
|
|
@@ -8564,9 +8570,10 @@ ${dim(t("Not copied: prompt, env, channels. Dry run: nothing created.", "No se c
|
|
|
8564
8570
|
const r = await api("/api/v2/me/agents", { method: "POST", body });
|
|
8565
8571
|
const where = r.workspace ? `
|
|
8566
8572
|
${t("workspace", "espacio")}: ${r.workspace}${r.handle ? ` \xB7 @${r.handle} ${t("in Teams", "en Teams")}` : dim(t(" \xB7 not active in Teams", " \xB7 no est\xE1 activo en Teams"))}` : "";
|
|
8573
|
+
const promptLine = typeof body.prompt === "string" ? dim(t(` prompt: ${v["prompt-file"] ?? "--prompt"}, ${body.prompt.length.toLocaleString("en-US")} chars`, ` prompt: ${v["prompt-file"] ?? "--prompt"}, ${body.prompt.length.toLocaleString("en-US")} caracteres`)) : dim(t(" Now give it its own prompt: ghosty agents set ", " Ahora dale su propio prompt: ghosty agents set ") + r.id + " --prompt-file PROMPT.md");
|
|
8567
8574
|
const copied = r.copied?.length ? `
|
|
8568
8575
|
${r.copied.map((l) => ` ${serverText(l)}`).join("\n")}
|
|
8569
|
-
${
|
|
8576
|
+
${promptLine}` : "";
|
|
8570
8577
|
out(r, () => `${lavender("\u2713")} ${t("Created", "Creado")} ${r.name} ${dim(`(${r.engine} \xB7 ${r.model ?? t("default", "por defecto")})`)}
|
|
8571
8578
|
id: ${r.id}${where}${copied}`);
|
|
8572
8579
|
}
|
|
@@ -9297,7 +9304,7 @@ async function call(path, v, init = {}) {
|
|
|
9297
9304
|
"x-ghosty-ts": ts,
|
|
9298
9305
|
"x-ghosty-sig": sig,
|
|
9299
9306
|
"content-type": "application/json",
|
|
9300
|
-
"user-agent": `ghosty-cli/${"0.
|
|
9307
|
+
"user-agent": `ghosty-cli/${"0.24.1"}`
|
|
9301
9308
|
},
|
|
9302
9309
|
body: method === "GET" ? void 0 : raw
|
|
9303
9310
|
});
|
|
@@ -9507,6 +9514,17 @@ function importWarnings(ws) {
|
|
|
9507
9514
|
${lines.join("\n")}
|
|
9508
9515
|
${dim(t(`Keep them permanent: add --rehost-images ${flag}`, `Para que sigan permanentes: agrega --rehost-images ${flag}`))}`;
|
|
9509
9516
|
}
|
|
9517
|
+
function gsOnlyWarnings(gs) {
|
|
9518
|
+
if (!gs?.length) return "";
|
|
9519
|
+
const lines = gs.map(
|
|
9520
|
+
(g) => red(
|
|
9521
|
+
g.key ? t(` ! ${g.table}: ${g.rows} row(s) exist only in gs and would be deleted (${g.key}: ${g.sample.join(", ")})`, ` ! ${g.table}: ${g.rows} fila(s) s\xF3lo existen en gs y se borrar\xEDan (${g.key}: ${g.sample.join(", ")})`) : t(` ! ${g.table}: ${g.rows} more row(s) in gs than in EasyBits would be deleted`, ` ! ${g.table}: ${g.rows} fila(s) m\xE1s en gs que en EasyBits se borrar\xEDan`)
|
|
9522
|
+
)
|
|
9523
|
+
);
|
|
9524
|
+
return `
|
|
9525
|
+
${lines.join("\n")}
|
|
9526
|
+
${dim(t("Other agents may be writing there already: leave those tables out (--tables) or use --append.", "Puede que otros agentes ya escriban ah\xED: deja fuera esas tablas (--tables) o usa --append."))}`;
|
|
9527
|
+
}
|
|
9510
9528
|
function planText(plan) {
|
|
9511
9529
|
return plan.map((p) => p.action === "create" ? t(` + ${p.table} create, ${p.sourceRows} row(s)`, ` + ${p.table} crear, ${p.sourceRows} fila(s)`) : t(` ~ ${p.table} ${p.action} ${p.targetRows} \u2192 ${p.resultRows} row(s)`, ` ~ ${p.table} ${p.action === "replace" ? "reemplazar" : "agregar"} ${p.targetRows} \u2192 ${p.resultRows} fila(s)`)).join("\n");
|
|
9512
9530
|
}
|
|
@@ -9520,10 +9538,10 @@ async function dbsImport(id, db, v) {
|
|
|
9520
9538
|
const path = agentPath2(id, `/dbs/${enc6(db)}/import`);
|
|
9521
9539
|
const dry = await api(path, { body: { ...body, dryRun: true } });
|
|
9522
9540
|
if (v["dry-run"]) return out(dry, () => `${dry.db} \u2190 easybits:${m[1]}
|
|
9523
|
-
${planText(dry.plan)}${importWarnings(dry.warnings)}
|
|
9541
|
+
${planText(dry.plan)}${importWarnings(dry.warnings)}${gsOnlyWarnings(dry.gsOnly)}
|
|
9524
9542
|
${dim(t("Dry run: nothing changed.", "Simulaci\xF3n: no cambi\xF3 nada."))}`);
|
|
9525
9543
|
const destructive = dry.plan.some((p) => p.action === "replace" && p.targetRows > 0);
|
|
9526
|
-
if (destructive) await confirm(`${planText(dry.plan)}${importWarnings(dry.warnings)}
|
|
9544
|
+
if (destructive) await confirm(`${planText(dry.plan)}${importWarnings(dry.warnings)}${gsOnlyWarnings(dry.gsOnly)}
|
|
9527
9545
|
${t(`Replace the rows above in ${dry.db}?`, `\xBFReemplazar las filas de arriba en ${dry.db}?`)}`, { yes: v.yes, retry: `ghosty dbs import ${id} ${db} --from ${v.from}${v.tables ? ` --tables ${v.tables}` : ""}` });
|
|
9528
9546
|
const r = await api(path, { body });
|
|
9529
9547
|
const img = r.rehosted ? `
|
|
@@ -9565,6 +9583,38 @@ ${total.failed.map((f) => ` ${f.url} ${dim(f.error)}`).join("\n")}` : ""
|
|
|
9565
9583
|
].filter(Boolean).join("\n")
|
|
9566
9584
|
);
|
|
9567
9585
|
}
|
|
9586
|
+
async function dbsRename(id, db, name, dryRun) {
|
|
9587
|
+
const r = await api(agentPath2(id, `/dbs/${enc6(db)}/rename`), { body: { name, dryRun } });
|
|
9588
|
+
const refs = r.references.flatMap((x) => [
|
|
9589
|
+
` ${bold(x.agent)} ${x.where}${x.derived ? dim(t(" (derived: follows its base)", " (derivado: sigue a su base)")) : ""}`,
|
|
9590
|
+
...x.refs.slice(0, 3).map((l) => dim(` ${x.where === "prompt" || x.where.startsWith("skill:") ? `${l.line}: ` : ""}${l.text}`)),
|
|
9591
|
+
...x.refs.length > 3 ? [dim(t(` \u2026 +${x.refs.length - 3} more`, ` \u2026 +${x.refs.length - 3} m\xE1s`))] : []
|
|
9592
|
+
]);
|
|
9593
|
+
const head = `${r.from} \u2192 ${r.to}`;
|
|
9594
|
+
const refsText = refs.length ? `
|
|
9595
|
+
${t(`It is named in ${r.references.length} place(s):`, `La nombran ${r.references.length} lugar(es):`)}
|
|
9596
|
+
${refs.join("\n")}` : `
|
|
9597
|
+
${dim(t("No prompt, env or skill names it.", "Ning\xFAn prompt, env ni skill la nombra."))}`;
|
|
9598
|
+
if (r.dryRun) return out(r, () => `${head}${refsText}
|
|
9599
|
+
${dim(t("Dry run: nothing changed. Without --dry-run those mentions are rewritten too.", "Simulaci\xF3n: no cambi\xF3 nada. Sin --dry-run tambi\xE9n se reescriben esas menciones."))}`);
|
|
9600
|
+
const failed = r.failed.map((f) => red(` \u2717 ${f.agentId} ${f.where}: ${serverText(f.error)}`));
|
|
9601
|
+
return out(r, () => [`${lavender("\u2713")} ${head}. ${t(`${r.rewritten.length} mention(s) rewritten.`, `${r.rewritten.length} menci\xF3n(es) reescritas.`)}`, ...failed, dim(`${t("Undo", "Deshacer")}: ghosty dbs rename ${id} ${r.to} ${r.from}`)].join("\n"));
|
|
9602
|
+
}
|
|
9603
|
+
async function dbsRm(id, db, v) {
|
|
9604
|
+
const path = agentPath2(id, `/dbs/${enc6(db)}/delete`);
|
|
9605
|
+
const dry = await api(path, { body: { dryRun: true } });
|
|
9606
|
+
const rows = dry.tables.reduce((n, x) => n + x.rows, 0);
|
|
9607
|
+
const plan = [
|
|
9608
|
+
`${bold(dry.name)}: ${t(`${dry.tables.length} table(s), ${rows} row(s)`, `${dry.tables.length} tabla(s), ${rows} fila(s)`)}`,
|
|
9609
|
+
dry.agents.length ? red(t(` ! granted to ${dry.agents.length} agent(s), they lose it: ${dry.agents.map((a) => a.name).join(", ")}`, ` ! concedida a ${dry.agents.length} agente(s), la pierden: ${dry.agents.map((a) => a.name).join(", ")}`)) : dim(t(" no agent has it granted", " ning\xFAn agente la tiene concedida"))
|
|
9610
|
+
].join("\n");
|
|
9611
|
+
if (v["dry-run"]) return out(dry, () => `${plan}
|
|
9612
|
+
${dim(t("Dry run: nothing changed.", "Simulaci\xF3n: no cambi\xF3 nada."))}`);
|
|
9613
|
+
await confirm(`${plan}
|
|
9614
|
+
${t(`Delete ${dry.name} for good? This cannot be undone.`, `\xBFBorrar ${dry.name} para siempre? No se puede deshacer.`)}`, { yes: v.yes, expect: dry.name, retry: `ghosty dbs rm ${id} ${db}` });
|
|
9615
|
+
const r = await api(path, { body: {} });
|
|
9616
|
+
return out(r, () => `${lavender("\u2713")} ${t(`${r.name} deleted.`, `${r.name} borrada.`)}`);
|
|
9617
|
+
}
|
|
9568
9618
|
async function dbsCmd(sub, id, db, rest, v) {
|
|
9569
9619
|
const ex = "ghosty dbs ls <agent-id>";
|
|
9570
9620
|
id = need2(id, t("agent id", "el id del agente"), ex);
|
|
@@ -9572,8 +9622,11 @@ async function dbsCmd(sub, id, db, rest, v) {
|
|
|
9572
9622
|
case "ls":
|
|
9573
9623
|
case "list": {
|
|
9574
9624
|
const r = await api(agentPath2(id, "/dbs"));
|
|
9575
|
-
const
|
|
9576
|
-
|
|
9625
|
+
const dbs = v.orphans ? r.dbs.filter((d) => d.agents === 0) : r.dbs;
|
|
9626
|
+
const rows = dbs.map((d) => ({ name: d.name, access: d.granted ? "granted" : "\u2014", agents: d.agents ?? "", "writes from customers": d.granted ? d.externalWriteTables.join(", ") || "(none)" : "", "own rows": d.granted ? Object.keys(d.externalRowScope ?? {}).join(", ") : "", id: d.id }));
|
|
9627
|
+
if (v.orphans) return out(dbs, () => rows.length ? `${table(rows, ["name", "agents", "id"])}
|
|
9628
|
+
${dim(`${t("Delete one", "Borrar una")}: ghosty dbs rm ${id} <db> --dry-run`)}` : dim(t("No orphan databases: every one is granted to some agent.", "Sin bases hu\xE9rfanas: todas est\xE1n concedidas a alg\xFAn agente.")));
|
|
9629
|
+
return out(dbs, () => rows.length ? table(rows, ["name", "access", "agents", "writes from customers", "own rows", "id"]) : dim(t("This account has no databases yet: create one at /app/dbs.", "Esta cuenta todav\xEDa no tiene bases: crea una en /app/dbs.")));
|
|
9577
9630
|
}
|
|
9578
9631
|
case "tables": {
|
|
9579
9632
|
db = need2(db, t("database (id or name)", "la base (id o nombre)"), "ghosty dbs tables <agent-id> <db>");
|
|
@@ -9631,13 +9684,23 @@ ${dim(`${t(`${rows.length} row(s)`, `${rows.length} fila(s)`)} \xB7 ${Math.round
|
|
|
9631
9684
|
db = need2(db, t("database (id or name)", "la base (id o nombre)"), "ghosty dbs rehost <agent-id> <db> --table T --column C");
|
|
9632
9685
|
return dbsRehost(id, db, v);
|
|
9633
9686
|
}
|
|
9687
|
+
case "rename": {
|
|
9688
|
+
const ex2 = "ghosty dbs rename <agent-id> <db> <new-name> [--dry-run]";
|
|
9689
|
+
db = need2(db, t("database (id or name)", "la base (id o nombre)"), ex2);
|
|
9690
|
+
return dbsRename(id, db, need2(rest[0], t("new name", "el nombre nuevo"), ex2), !!v["dry-run"]);
|
|
9691
|
+
}
|
|
9692
|
+
case "rm":
|
|
9693
|
+
case "delete": {
|
|
9694
|
+
db = need2(db, t("database (id or name)", "la base (id o nombre)"), "ghosty dbs rm <agent-id> <db> [--dry-run] [--yes]");
|
|
9695
|
+
return dbsRm(id, db, v);
|
|
9696
|
+
}
|
|
9634
9697
|
case "revoke": {
|
|
9635
9698
|
db = need2(db, t("database (id or name)", "la base (id o nombre)"), "ghosty dbs revoke <agent-id> <db>");
|
|
9636
9699
|
const r = await api(agentPath2(id, `/dbs/${enc6(db)}`), { method: "DELETE" });
|
|
9637
9700
|
return out(r, () => `${lavender("\u2713")} ${r.name} ${t("revoked.", "revocada.")} ${dim(`${t("Undo", "Deshacer")}: ghosty dbs grant ${id} ${r.name}`)}`);
|
|
9638
9701
|
}
|
|
9639
9702
|
default:
|
|
9640
|
-
throw usage(t(`Unknown dbs subcommand: ${sub}.`, `Subcomando de dbs desconocido: ${sub}.`), t("Try", "Prueba") + ": ghosty dbs ls | tables | query | import | rehost | grant | revoke (ghosty dbs --help)");
|
|
9703
|
+
throw usage(t(`Unknown dbs subcommand: ${sub}.`, `Subcomando de dbs desconocido: ${sub}.`), t("Try", "Prueba") + ": ghosty dbs ls | tables | query | import | rehost | grant | revoke | rename | rm (ghosty dbs --help)");
|
|
9641
9704
|
}
|
|
9642
9705
|
}
|
|
9643
9706
|
async function agentDocsCmd(sub, id, docs, v) {
|
|
@@ -9691,10 +9754,32 @@ async function agentDocsCmd(sub, id, docs, v) {
|
|
|
9691
9754
|
throw usage(t(`Unknown agents docs subcommand: ${sub}.`, `Subcomando de agents docs desconocido: ${sub}.`), t("Try", "Prueba") + ": ghosty agents docs ls | add | grant | revoke");
|
|
9692
9755
|
}
|
|
9693
9756
|
}
|
|
9757
|
+
function skipReason(r) {
|
|
9758
|
+
const es = {
|
|
9759
|
+
manual_mode: ["operator active in Formmy (manual mode)", "operador activo en Formmy (modo manual)"],
|
|
9760
|
+
records_only: ["number records only (link --answer off)", "el n\xFAmero s\xF3lo registra (link --answer off)"],
|
|
9761
|
+
paused: ["conversation paused on the board", "conversaci\xF3n pausada en el tablero"],
|
|
9762
|
+
reaction: ["only a reaction", "s\xF3lo una reacci\xF3n"],
|
|
9763
|
+
channel_off: ["WhatsApp channel off for this agent", "canal de WhatsApp apagado en este agente"],
|
|
9764
|
+
no_quota: ["out of balance", "sin saldo"]
|
|
9765
|
+
};
|
|
9766
|
+
const pair = es[r];
|
|
9767
|
+
return pair ? t(pair[0], pair[1]) : r;
|
|
9768
|
+
}
|
|
9769
|
+
async function turnsSkipped(id, v) {
|
|
9770
|
+
const q = new URLSearchParams({ skipped: "1", limit: v.limit ?? "20" });
|
|
9771
|
+
if (v.since) q.set("since", v.since);
|
|
9772
|
+
const r = await api(agentPath2(id, `/turns?${q}`));
|
|
9773
|
+
const rows = r.skipped.map((m) => ({ at: m.at.slice(0, 16).replace("T", " "), reason: skipReason(m.reason), phone: m.phone, message: (m.preview ?? "").slice(0, 60) }));
|
|
9774
|
+
const tally = Object.entries(r.counts).map(([k, n]) => `${skipReason(k)} ${n}`).join(" \xB7 ");
|
|
9775
|
+
out(r, () => rows.length ? `${table(rows, ["at", "reason", "phone", "message"])}
|
|
9776
|
+
${dim(tally)}` : dim(t(`Every customer message${v.since ? ` since ${v.since}` : ""} opened a turn (or none arrived: check ghosty channels whatsapp ls ${id}, "formmy forwards").`, `Todo mensaje de cliente${v.since ? ` desde ${v.since}` : ""} abri\xF3 turno (o no lleg\xF3 ninguno: revisa ghosty channels whatsapp ls ${id}, \xABformmy forwards\xBB).`)));
|
|
9777
|
+
}
|
|
9694
9778
|
async function turnsCmd(sub, id, turnId, v) {
|
|
9695
9779
|
if (sub === "show") return turnShow(id, turnId, !!v.full);
|
|
9696
9780
|
if (sub && sub !== "ls" && sub !== "list") throw usage(t(`Unknown turns subcommand: ${sub}.`, `Subcomando de turns desconocido: ${sub}.`), t("Try", "Prueba") + ": ghosty turns ls <agent-id> --errors --since 1h \xB7 ghosty turns show <agent-id> [turn-id|last]");
|
|
9697
9781
|
id = need2(id, t("agent id", "el id del agente"), "ghosty turns ls <agent-id> --errors --since 1h");
|
|
9782
|
+
if (v.skipped) return turnsSkipped(id, v);
|
|
9698
9783
|
const q = new URLSearchParams();
|
|
9699
9784
|
const status = v.errors ? "error" : v.status;
|
|
9700
9785
|
if (status) q.set("state", status);
|
|
@@ -9926,6 +10011,7 @@ async function channelsCmd(kind, sub, id, ref, v) {
|
|
|
9926
10011
|
if (!id) throw usage(t("Missing agent id.", "Falta el id del agente."), t("Example", "Ejemplo") + ": ghosty channels whatsapp ls <agent-id>");
|
|
9927
10012
|
if (sub === "link" || sub === "unlink") return linkCmd(sub, id, v);
|
|
9928
10013
|
if (sub === "history") return historyCmd(id, v);
|
|
10014
|
+
if (sub === "integrations") return integrationsCmd(id);
|
|
9929
10015
|
if (sub === "import" || sub === "status" || sub === "pause" || sub === "pair") return waCmd(sub, id, v);
|
|
9930
10016
|
if (sub === "enable" && !v["dry-run"]) requireYesIfHeadless(v.yes, `ghosty channels whatsapp enable ${id} ${ref ?? "<number>"}`);
|
|
9931
10017
|
const path = `/api/v2/agents/${enc7(id)}/whatsapp`;
|
|
@@ -9941,7 +10027,7 @@ async function channelsCmd(kind, sub, id, ref, v) {
|
|
|
9941
10027
|
}));
|
|
9942
10028
|
return out(cur, () => rows.length ? table(rows, ["number", "answers", "formmy forwards", "id"]) : dim(t(`No WhatsApp Business numbers yet. Pair one at ${cur.salesUrl}`, `Todav\xEDa no hay n\xFAmeros de WhatsApp Business. Parea uno en ${cur.salesUrl}`)));
|
|
9943
10029
|
}
|
|
9944
|
-
if (sub !== "enable" && sub !== "disable") throw usage(t(`Unknown subcommand: ${sub}.`, `Subcomando desconocido: ${sub}.`), t("Try", "Prueba") + ": ghosty channels whatsapp ls | enable | disable | link | unlink | history \xB7 ghosty channels wa import | status | pause | groups");
|
|
10030
|
+
if (sub !== "enable" && sub !== "disable") throw usage(t(`Unknown subcommand: ${sub}.`, `Subcomando desconocido: ${sub}.`), t("Try", "Prueba") + ": ghosty channels whatsapp ls | enable | disable | integrations | link | unlink | history \xB7 ghosty channels wa import | status | pause | groups");
|
|
9945
10031
|
if (!ref) throw usage(t("Missing number.", "Falta el n\xFAmero."), t("Example", "Ejemplo") + `: ghosty channels whatsapp ${sub} ${id} +52 55 1234 5678`);
|
|
9946
10032
|
const n = find(cur.numbers, ref);
|
|
9947
10033
|
const on = sub === "enable";
|
|
@@ -10064,6 +10150,23 @@ function bareMediaFlag(argv) {
|
|
|
10064
10150
|
if (argv[0] !== "channels" || !argv.includes("history")) return argv;
|
|
10065
10151
|
return argv.map((a, i) => a === "--media" && (argv[i + 1] === void 0 || argv[i + 1].startsWith("-")) ? "--media=" : a);
|
|
10066
10152
|
}
|
|
10153
|
+
async function integrationsCmd(id) {
|
|
10154
|
+
const r = await api(`/api/v2/agents/${enc7(id)}/whatsapp/integrations`);
|
|
10155
|
+
const who2 = { gs: "gs", easybits: "EasyBits", droplet: "droplet", formmy: "Formmy", other: t("other", "otro") };
|
|
10156
|
+
const rows = r.integrations.map((i) => ({
|
|
10157
|
+
number: i.phoneNumber ? `+${i.phoneNumber}` : dim("?"),
|
|
10158
|
+
name: i.displayName ?? "",
|
|
10159
|
+
"answered by": `${who2[i.answeredBy] ?? i.answeredBy}${i.gsAgent ? ` (${i.thisAgent ? lavender(t("this agent", "este agente")) : i.gsAgent.name})` : ""}${i.isActive ? "" : dim(t(" \xB7 inactive", " \xB7 inactiva"))}`,
|
|
10160
|
+
"formmy forwards": i.forwards ? t("yes", "s\xED") : dim("no"),
|
|
10161
|
+
"formmy agent": i.agent ?? "",
|
|
10162
|
+
integration: i.integrationId
|
|
10163
|
+
}));
|
|
10164
|
+
return out(
|
|
10165
|
+
r,
|
|
10166
|
+
() => rows.length ? `${table(rows, ["number", "name", "answered by", "formmy forwards", "formmy agent", "integration"])}
|
|
10167
|
+
${dim(`${t("Next", "Siguiente")}: ghosty channels whatsapp link ${id} --integration <integration> --answer on|off --dry-run`)}` : dim(t(`${r.ownerEmail} has no WhatsApp Business numbers in Formmy.`, `${r.ownerEmail} no tiene n\xFAmeros de WhatsApp Business en Formmy.`))
|
|
10168
|
+
);
|
|
10169
|
+
}
|
|
10067
10170
|
async function historyCmd(id, v) {
|
|
10068
10171
|
if (!v.integration) throw usage(t("Missing --integration.", "Falta --integration."), t("Example", "Ejemplo") + `: ghosty channels whatsapp history ${id} --integration <formmy-integration-id> --days 7 --dry-run`);
|
|
10069
10172
|
const days = Number(v.days ?? 7);
|
|
@@ -10093,9 +10196,10 @@ ${dim(v["dry-run"] ? t("Dry run: nothing copied.", "Simulacro: no se copi\xF3 na
|
|
|
10093
10196
|
}
|
|
10094
10197
|
return out({ ...sum2, conversations: done2 }, () => `${lavender("\u2713")} ${t(`${sum2.mediaCopied} file(s) copied`, `${sum2.mediaCopied} archivo(s) copiado(s)`)}${sum2.mediaFailed ? lavender(t(`, ${sum2.mediaFailed} failed (Formmy may have deleted them already)`, `, ${sum2.mediaFailed} fallaron (Formmy pudo haberlos borrado ya)`)) : ""}`);
|
|
10095
10198
|
}
|
|
10199
|
+
const dest = c.linked === false ? t("(not linked yet: link it before importing)", "(sin enlazar todav\xEDa: enl\xE1zalo antes de importar)") : `\u2192 ${t("board", "tablero")} ${c.board}`;
|
|
10096
10200
|
const plan = t(
|
|
10097
|
-
`${c.conversations} conversation(s), ${c.messages} message(s), ${c.orders} order(s) from the last ${days} day(s)${c.phoneNumber ? ` of +${c.phoneNumber}` : ""}
|
|
10098
|
-
`${c.conversations} conversaci\xF3n(es), ${c.messages} mensaje(s), ${c.orders} pedido(s) de los \xFAltimos ${days} d\xEDa(s)${c.phoneNumber ? ` de +${c.phoneNumber}` : ""}
|
|
10201
|
+
`${c.conversations} conversation(s), ${c.messages} message(s), ${c.orders} order(s) from the last ${days} day(s)${c.phoneNumber ? ` of +${c.phoneNumber}` : ""} ${dest}`,
|
|
10202
|
+
`${c.conversations} conversaci\xF3n(es), ${c.messages} mensaje(s), ${c.orders} pedido(s) de los \xFAltimos ${days} d\xEDa(s)${c.phoneNumber ? ` de +${c.phoneNumber}` : ""} ${dest}`
|
|
10099
10203
|
);
|
|
10100
10204
|
const missingHint = c.mediaMissing ? `
|
|
10101
10205
|
${lavender(t(`${c.mediaMissing} file(s) already imported have no copy in gs: add --media to copy them (Formmy keeps them 7 days).`, `${c.mediaMissing} archivo(s) ya importado(s) no tienen copia en gs: agrega --media para copiarlos (Formmy los guarda 7 d\xEDas).`))}` : "";
|
|
@@ -10295,6 +10399,7 @@ var HELPS_ES = {
|
|
|
10295
10399
|
summary: "Bases de datos que un agente puede usar, y qu\xE9 puede escribir desde canales de clientes.",
|
|
10296
10400
|
replace: [["# reads $EASYBITS_API_KEY", "# lee $EASYBITS_API_KEY"]],
|
|
10297
10401
|
flags: [
|
|
10402
|
+
["--orphans", "ls: s\xF3lo las bases que ning\xFAn agente tiene concedidas (candidatas a rm)"],
|
|
10298
10403
|
["--arg V", "query: valor para un marcador ? (repetible)"],
|
|
10299
10404
|
["--write", "query: permite INSERT/UPDATE/DELETE/DDL (default: s\xF3lo lectura)"],
|
|
10300
10405
|
["--external-write t1,t2", 'grant: tablas que puede escribir desde WhatsApp/Messenger ("" = ninguna)'],
|
|
@@ -10305,11 +10410,14 @@ var HELPS_ES = {
|
|
|
10305
10410
|
["--rehost-images t.col,\u2026", "import: copia las ligas de imagen de esas columnas al almacenamiento p\xFAblico de Ghosty (ligas permanentes en img.ghosty.studio)"],
|
|
10306
10411
|
["--table T --column C", "rehost: la columna de ligas de imagen (p. ej. catalogo imagen_url) que se pasa a ligas permanentes en img.ghosty.studio; idempotente, una liga que falla se queda como estaba"],
|
|
10307
10412
|
["--key-column K --dir DIR", "photos put: cada archivo de DIR se llama como la clave de su fila (SKU-123.jpg \u2192 la fila donde K = SKU-123); va al almacenamiento permanente y su liga a --column (default imagen_url)"],
|
|
10308
|
-
["-n, --dry-run", "import: imprime el plan (+ crear, ~ reemplazar N \u2192 M filas), no cambia nada \xB7 photos put: lista las claves sin fila y los archivos que no son imagen"]
|
|
10413
|
+
["-n, --dry-run", "import: imprime el plan (+ crear, ~ reemplazar N \u2192 M filas) y las filas que s\xF3lo existen en gs, no cambia nada \xB7 photos put: lista las claves sin fila y los archivos que no son imagen \xB7 rename: lista los prompts, env y skills que la nombran \xB7 rm: tablas, filas y agentes que la perder\xEDan"],
|
|
10414
|
+
["--yes", "import, rm: se salta la confirmaci\xF3n (obligatorio sin terminal)"]
|
|
10309
10415
|
],
|
|
10310
10416
|
notes: [
|
|
10311
10417
|
"<db> es el id de la base o su nombre. Desde canales del due\xF1o el agente lee y escribe todo lo concedido.",
|
|
10312
|
-
"tables dice, por columna de fotos/ligas, cu\xE1ntas son permanentes (img.ghosty.studio), de fuera o vac\xEDas; import avisa antes de reemplazar ligas permanentes por ligas de fuera."
|
|
10418
|
+
"tables dice, por columna de fotos/ligas, cu\xE1ntas son permanentes (img.ghosty.studio), de fuera o vac\xEDas; import avisa antes de reemplazar ligas permanentes por ligas de fuera.",
|
|
10419
|
+
"import con replace avisa de las filas escritas en gs que EasyBits no tiene (una base compartida con agentes que ya corren en gs) y pregunta antes de borrarlas.",
|
|
10420
|
+
"rename tambi\xE9n reescribe cada prompt, valor de env y SKILL.md que nombra la base; un prompt derivado sigue a su base. rm borra la base misma (revoke s\xF3lo le quita el acceso a este agente); las dos son s\xF3lo del due\xF1o (o del staff de Ghosty)."
|
|
10313
10421
|
]
|
|
10314
10422
|
},
|
|
10315
10423
|
turns: {
|
|
@@ -10317,12 +10425,13 @@ var HELPS_ES = {
|
|
|
10317
10425
|
replace: [["# last turn: each tool, its seconds, output tokens", "# \xFAltimo turno: cada tool, sus segundos, tokens de salida"]],
|
|
10318
10426
|
flags: [
|
|
10319
10427
|
["--errors", "s\xF3lo turnos fallidos (igual que --status error)"],
|
|
10428
|
+
["--skipped", "ls: mensajes de clientes de WhatsApp Business que NO abrieron turno, y por qu\xE9 (operador activo, s\xF3lo registra, pausa, reacci\xF3n, canal apagado, sin saldo)"],
|
|
10320
10429
|
["--status S", "running | done | stopped | error"],
|
|
10321
10430
|
["--since T", "30m, 1h, 2d o una fecha ISO"],
|
|
10322
10431
|
["--limit N", "m\xE1ximo de filas (default 20, m\xE1x. 500)"],
|
|
10323
10432
|
["--full", "show: el mensaje que entr\xF3 y la respuesta completos (default: primeros 600 caracteres)"]
|
|
10324
10433
|
],
|
|
10325
|
-
notes: ["show imprime lo que entr\xF3 y lo que sali\xF3 en cualquier motor o canal (grupos de WhatsApp tambi\xE9n); la l\xEDnea de tiempo lee el transcript del worker, as\xED que s\xF3lo motores del pool (no ACP/goose)."]
|
|
10434
|
+
notes: ["--skipped guarda 30 d\xEDas y s\xF3lo ve lo que Formmy reenvi\xF3: si Formmy no reenv\xEDa (ghosty channels whatsapp ls, \xABformmy forwards\xBB), a gs nunca le llega el mensaje.", "show imprime lo que entr\xF3 y lo que sali\xF3 en cualquier motor o canal (grupos de WhatsApp tambi\xE9n); la l\xEDnea de tiempo lee el transcript del worker, as\xED que s\xF3lo motores del pool (no ACP/goose)."]
|
|
10326
10435
|
},
|
|
10327
10436
|
files: { summary: "Archivos en la m\xE1quina del agente." },
|
|
10328
10437
|
mcp: { summary: "Servidores MCP de un agente. set REEMPLAZA la lista entera." },
|
|
@@ -10391,7 +10500,8 @@ var HELPS_ES = {
|
|
|
10391
10500
|
"Apagarlo sigue registrando los mensajes; nadie los contesta.",
|
|
10392
10501
|
"Los grupos son del WhatsApp PERSONAL emparejado al agente (no de WhatsApp Business). <group> es su nombre o su jid.",
|
|
10393
10502
|
"link mueve un n\xFAmero que ya vive en Formmy (EasyBits, un droplet) sin rehacer el alta de Meta; unlink lo regresa. S\xF3lo el due\xF1o, un admin del agente o el staff de Ghosty.",
|
|
10394
|
-
"
|
|
10503
|
+
"integrations lista los n\xFAmeros de WhatsApp Business del due\xF1o en Formmy (n\xFAmero, nombre, qui\xE9n contesta hoy: gs, EasyBits, un droplet o Formmy) con el id de la Integration que piden link e history.",
|
|
10504
|
+
"history trae al tablero las tarjetas, mensajes y pedidos recientes del n\xFAmero desde Formmy (enl\xE1zalo antes; --dry-run funciona sin enlazar, para decidir --days). Repetirlo nunca duplica: c\xF3rrelo antes del corte y otra vez justo en el corte. Los archivos (fotos, PDF, audios) tambi\xE9n se copian a gs: Formmy los borra a los 7 d\xEDas, as\xED que trae el n\xFAmero dentro de esa semana; una copia que fall\xF3 se reintenta con --media.",
|
|
10395
10505
|
"import: apaga primero el socket de EasyBits SIN logout (dos sockets se tumban entre s\xED; un logout invalida las llaves). Borra el archivo despu\xE9s."
|
|
10396
10506
|
]
|
|
10397
10507
|
},
|
|
@@ -10603,16 +10713,19 @@ var HELPS = {
|
|
|
10603
10713
|
dbs: {
|
|
10604
10714
|
summary: "Databases an agent can use, and what it may write from customer channels.",
|
|
10605
10715
|
usage: [
|
|
10606
|
-
"ghosty dbs ls <agent-id>",
|
|
10716
|
+
"ghosty dbs ls <agent-id> [--orphans]",
|
|
10607
10717
|
"ghosty dbs tables <agent-id> <db>",
|
|
10608
10718
|
'ghosty dbs query <agent-id> <db> "SQL" [--arg V]\u2026 [--write]',
|
|
10609
10719
|
"ghosty dbs import <agent-id> <db> --from easybits:<db-id> [--tables a,b] [--append] [--rehost-images t.col,\u2026] [--dry-run] [--yes]",
|
|
10610
10720
|
"ghosty dbs rehost <agent-id> <db> --table T --column C [--dry-run]",
|
|
10611
10721
|
"ghosty dbs photos put <agent-id> <db> --table T --key-column K --dir DIR [--column C] [--dry-run]",
|
|
10612
10722
|
"ghosty dbs grant <agent-id> <db> [--external-write t1,t2] [--external-rows t:col+col,\u2026]",
|
|
10613
|
-
"ghosty dbs revoke <agent-id> <db>"
|
|
10723
|
+
"ghosty dbs revoke <agent-id> <db>",
|
|
10724
|
+
"ghosty dbs rename <agent-id> <db> <new-name> [--dry-run]",
|
|
10725
|
+
"ghosty dbs rm <agent-id> <db> [--dry-run] [--yes]"
|
|
10614
10726
|
],
|
|
10615
10727
|
flags: [
|
|
10728
|
+
["--orphans", "ls: only databases no agent has granted (candidates for rm)"],
|
|
10616
10729
|
["--arg V", "query: value for a ? placeholder (repeatable)"],
|
|
10617
10730
|
["--write", "query: allow INSERT/UPDATE/DELETE/DDL (default: read only)"],
|
|
10618
10731
|
["--external-write t1,t2", 'grant: tables it may write from WhatsApp/Messenger ("" = none)'],
|
|
@@ -10623,7 +10736,8 @@ var HELPS = {
|
|
|
10623
10736
|
["--rehost-images t.col,\u2026", "import: copy the image links in those columns to Ghosty's public storage (permanent links at img.ghosty.studio)"],
|
|
10624
10737
|
["--table T --column C", "rehost: the column of image links (e.g. catalogo imagen_url) to move to permanent links at img.ghosty.studio; idempotent, a link that fails stays as it was"],
|
|
10625
10738
|
["--key-column K --dir DIR", "photos put: each file in DIR is named after its row's key (SKU-123.jpg \u2192 the row where K = SKU-123); it goes to permanent storage and its link to --column (default imagen_url)"],
|
|
10626
|
-
["-n, --dry-run", "import: print the plan (+ create, ~ replace N \u2192 M rows), change nothing \xB7 photos put: list keys with no row and files that aren't images"]
|
|
10739
|
+
["-n, --dry-run", "import: print the plan (+ create, ~ replace N \u2192 M rows) and the rows that exist only in gs, change nothing \xB7 photos put: list keys with no row and files that aren't images \xB7 rename: list the prompts, env and skills that name it \xB7 rm: tables, rows and agents that would lose it"],
|
|
10740
|
+
["--yes", "import, rm: skip the confirmation (required when not interactive)"]
|
|
10627
10741
|
],
|
|
10628
10742
|
examples: [
|
|
10629
10743
|
"ghosty dbs ls <id>",
|
|
@@ -10632,26 +10746,31 @@ var HELPS = {
|
|
|
10632
10746
|
"ghosty dbs grant <id> totequim --external-write cotizaciones,folio",
|
|
10633
10747
|
"ghosty dbs grant <id> totequim --external-rows clientes:telefono+tel_secundario,cotizaciones:cliente_tel",
|
|
10634
10748
|
"ghosty dbs import <id> totequim --from easybits:6a10c84c\u2026 --dry-run # reads $EASYBITS_API_KEY",
|
|
10635
|
-
"ghosty dbs photos put <id> siiqtec --table catalogo --key-column sku --dir fotos/ --dry-run"
|
|
10749
|
+
"ghosty dbs photos put <id> siiqtec --table catalogo --key-column sku --dir fotos/ --dry-run",
|
|
10750
|
+
"ghosty dbs rename <id> totequim siiqtec --dry-run",
|
|
10751
|
+
"ghosty dbs ls <id> --orphans"
|
|
10636
10752
|
],
|
|
10637
10753
|
notes: [
|
|
10638
10754
|
"<db> is the database id or its name. From owner channels the agent reads and writes everything granted.",
|
|
10639
|
-
"tables shows, per photo/link column, how many links are permanent (img.ghosty.studio), external or empty; import warns before replacing permanent links with external ones."
|
|
10755
|
+
"tables shows, per photo/link column, how many links are permanent (img.ghosty.studio), external or empty; import warns before replacing permanent links with external ones.",
|
|
10756
|
+
"import with replace warns about rows written in gs that EasyBits doesn't have (a database shared with agents already running in gs) and asks before deleting them.",
|
|
10757
|
+
"rename also rewrites every prompt, env value and SKILL.md that names the database; a derived prompt follows its base. rm deletes the database itself (revoke only removes this agent's access); only its owner (or Ghosty staff) can do either."
|
|
10640
10758
|
],
|
|
10641
10759
|
learn: `${REF}#bases-de-datos`
|
|
10642
10760
|
},
|
|
10643
10761
|
turns: {
|
|
10644
10762
|
summary: "An agent's recent turns: what answered, what failed and why, and where one turn's time went.",
|
|
10645
|
-
usage: ["ghosty turns ls <agent-id> [--errors | --status STATE] [--since 1h] [--limit N]", "ghosty turns show <agent-id> [turn-id | last] [--full]"],
|
|
10763
|
+
usage: ["ghosty turns ls <agent-id> [--errors | --status STATE | --skipped] [--since 1h] [--limit N]", "ghosty turns show <agent-id> [turn-id | last] [--full]"],
|
|
10646
10764
|
flags: [
|
|
10647
10765
|
["--errors", "only failed turns (same as --status error)"],
|
|
10766
|
+
["--skipped", "ls: WhatsApp Business customer messages that did NOT open a turn, and why (operator active, records only, paused, reaction, channel off, no balance)"],
|
|
10648
10767
|
["--status S", "running | done | stopped | error"],
|
|
10649
10768
|
["--since T", "30m, 1h, 2d or an ISO date"],
|
|
10650
10769
|
["--limit N", "max rows (default 20, max 500)"],
|
|
10651
10770
|
["--full", "show: the whole message that came in and the whole answer (default: first 600 chars)"]
|
|
10652
10771
|
],
|
|
10653
|
-
examples: ["ghosty turns ls <id> --errors --since 1h", "ghosty turns ls <id> --json --limit 100", "ghosty turns show <id> # last turn: each tool, its seconds, output tokens"],
|
|
10654
|
-
notes: ["show prints what came in and what went out on any engine or channel (WhatsApp groups too); the timeline reads the worker's transcript, so pool engines only (not ACP/goose)."],
|
|
10772
|
+
examples: ["ghosty turns ls <id> --errors --since 1h", "ghosty turns ls <id> --skipped --since 1d", "ghosty turns ls <id> --json --limit 100", "ghosty turns show <id> # last turn: each tool, its seconds, output tokens"],
|
|
10773
|
+
notes: ["--skipped keeps 30 days and only sees what Formmy forwarded: if Formmy doesn't forward (ghosty channels whatsapp ls, \xABformmy forwards\xBB), gs never gets the message.", "show prints what came in and what went out on any engine or channel (WhatsApp groups too); the timeline reads the worker's transcript, so pool engines only (not ACP/goose)."],
|
|
10655
10774
|
learn: `${REF}#turnos`
|
|
10656
10775
|
},
|
|
10657
10776
|
files: {
|
|
@@ -10741,6 +10860,7 @@ HELPS.channels = {
|
|
|
10741
10860
|
"ghosty channels whatsapp ls <agent-id>",
|
|
10742
10861
|
"ghosty channels whatsapp enable <agent-id> <number> [--dry-run] [--yes]",
|
|
10743
10862
|
"ghosty channels whatsapp disable <agent-id> <number> [--dry-run]",
|
|
10863
|
+
"ghosty channels whatsapp integrations <agent-id>",
|
|
10744
10864
|
"ghosty channels whatsapp link <agent-id> --integration <formmy-id> --answer on|off [--forward] [--dry-run] [--yes]",
|
|
10745
10865
|
"ghosty channels whatsapp unlink <agent-id> --integration <formmy-id> [--dry-run] [--yes]",
|
|
10746
10866
|
"ghosty channels whatsapp history <agent-id> --integration <formmy-id> [--days 7] [--media] [--dry-run] [--yes]",
|
|
@@ -10774,6 +10894,7 @@ HELPS.channels = {
|
|
|
10774
10894
|
"ghosty channels whatsapp disable <id> 5512345678",
|
|
10775
10895
|
"ghosty channels wa groups ls <id>",
|
|
10776
10896
|
'ghosty channels wa groups enable <id> "Pia-cotizadora" --role equipo --dry-run',
|
|
10897
|
+
"ghosty channels whatsapp integrations <id>",
|
|
10777
10898
|
"ghosty channels whatsapp link <id> --integration 6a3af395\u2026 --answer off --dry-run",
|
|
10778
10899
|
"ghosty channels whatsapp history <id> --integration 6a3af395\u2026 --days 7 --dry-run",
|
|
10779
10900
|
"ghosty channels whatsapp history <id> --integration 6a3af395\u2026 --days 7 --media",
|
|
@@ -10784,7 +10905,8 @@ HELPS.channels = {
|
|
|
10784
10905
|
"Disabling keeps logging messages; nobody answers them.",
|
|
10785
10906
|
"Groups belong to the PERSONAL WhatsApp paired to the agent (not WhatsApp Business). <group> is its name or its jid.",
|
|
10786
10907
|
"link moves a number that already lives in Formmy (EasyBits, a droplet) without redoing Meta's signup; unlink sends it back. Only the owner, an admin of the agent or Ghosty staff.",
|
|
10787
|
-
"
|
|
10908
|
+
"integrations lists the owner's WhatsApp Business numbers in Formmy (number, name, who answers today: gs, EasyBits, a droplet or Formmy) with the Integration id that link and history take.",
|
|
10909
|
+
"history brings the number's recent cards, messages and orders from Formmy to the board (link it first; --dry-run works before linking, to choose --days). Re-running never duplicates: run it before the cutover and again right at it. Files (photos, PDFs, audio) are copied to gs too: Formmy deletes them after 7 days, so bring the number within that week; a copy that failed is retried with --media.",
|
|
10788
10910
|
"import: turn off the EasyBits socket first WITHOUT logout (two sockets knock each other off; a logout invalidates the keys). Delete the file afterwards."
|
|
10789
10911
|
],
|
|
10790
10912
|
learn: `${REF}#canales-de-whatsapp`
|
|
@@ -11320,10 +11442,11 @@ ${bold("Configure an agent")}
|
|
|
11320
11442
|
skills house <id> Ghosty's skills in bulk [--only a,b | --enable a,b | --disable a,b] [--dry-run]
|
|
11321
11443
|
skills ls|get|add|rm <id> [slug] Skills (add from catalog, --file SKILL.md or --dir FOLDER; get lists, --out DIR saves; rm keeps a copy)
|
|
11322
11444
|
skills templates <id> pdf-doc templates it sees: its own and Ghosty's, and which folder wins
|
|
11323
|
-
dbs ls|tables|query|import|rehost|grant|revoke <id> [db] Databases it uses [--write] [--external-write t1,t2] [--external-rows t:col] [--from easybits:ID --dry-run] [--table T --column C]
|
|
11445
|
+
dbs ls|tables|query|import|rehost|grant|revoke|rename|rm <id> [db] Databases it uses [--write] [--external-write t1,t2] [--external-rows t:col] [--from easybits:ID --dry-run] [--table T --column C]
|
|
11324
11446
|
dbs photos put <id> <db> --table T --key-column K --dir DIR Loose photos to a catalog (SKU-123.jpg \u2192 its row), permanent links [--column C] [--dry-run]
|
|
11325
11447
|
mcp get|set <id> MCP servers [--file servers.json]
|
|
11326
11448
|
channels whatsapp ls|enable|disable <id> [number] Which WhatsApp Business numbers it answers [--dry-run]
|
|
11449
|
+
channels whatsapp integrations <id> Its owner's WhatsApp Business numbers in Formmy (number, who answers today, Integration id)
|
|
11327
11450
|
channels whatsapp link|unlink <id> --integration ID --answer on|off Bring a WhatsApp Business number from Formmy/EasyBits to this agent, or send it back [--forward] [--dry-run]
|
|
11328
11451
|
channels whatsapp history <id> --integration ID [--days 7] Bring its recent cards, messages, orders and files from Formmy (no duplicates; Formmy keeps files 7 days) [--media] [--dry-run]
|
|
11329
11452
|
channels wa import <id> --from-file F | --from-dir DIR --own-number yes|no Move a personal WhatsApp session here, no QR [--dry-run] [--replace] [--no-connect]
|
|
@@ -11342,7 +11465,7 @@ ${bold("Talk to an agent")}
|
|
|
11342
11465
|
chat <id> ["message"] Stream a conversation (no message = interactive) [--conversation ID]
|
|
11343
11466
|
conversations ls <id> List conversations
|
|
11344
11467
|
conversations show <id> <conversation> What was said [--limit 20] (0 = all)
|
|
11345
|
-
turns ls <id> Recent turns and why they failed [--errors] [--since 1h] [--limit N]
|
|
11468
|
+
turns ls <id> Recent turns and why they failed [--errors] [--skipped] [--since 1h] [--limit N]
|
|
11346
11469
|
turns show <id> [turn] What came in, what it answered, and where the time went (default: last) [--full]
|
|
11347
11470
|
|
|
11348
11471
|
${bold("Schedule turns")} ${dim("(each run lands in a conversation and notifies your phone; an answer of exactly OK stays silent)")}
|
|
@@ -11415,10 +11538,11 @@ ${bold("Configurar un agente")}
|
|
|
11415
11538
|
skills house <id> Las skills de Ghosty en bloque [--only a,b | --enable a,b | --disable a,b] [--dry-run]
|
|
11416
11539
|
skills ls|get|add|rm <id> [slug] Skills (add del cat\xE1logo, --file SKILL.md o --dir CARPETA; get lista, --out DIR guarda; rm deja copia)
|
|
11417
11540
|
skills templates <id> Plantillas de pdf-doc que ve: las suyas y las de Ghosty, y qu\xE9 carpeta gana
|
|
11418
|
-
dbs ls|tables|query|import|rehost|grant|revoke <id> [db] Bases de datos que usa [--write] [--external-write t1,t2] [--external-rows t:col] [--from easybits:ID --dry-run] [--table T --column C]
|
|
11541
|
+
dbs ls|tables|query|import|rehost|grant|revoke|rename|rm <id> [db] Bases de datos que usa [--write] [--external-write t1,t2] [--external-rows t:col] [--from easybits:ID --dry-run] [--table T --column C]
|
|
11419
11542
|
dbs photos put <id> <db> --table T --key-column K --dir DIR Fotos sueltas a un cat\xE1logo (SKU-123.jpg \u2192 su fila), ligas permanentes [--column C] [--dry-run]
|
|
11420
11543
|
mcp get|set <id> Servidores MCP [--file servers.json]
|
|
11421
11544
|
channels whatsapp ls|enable|disable <id> [n\xFAmero] Qu\xE9 n\xFAmeros de WhatsApp Business contesta [--dry-run]
|
|
11545
|
+
channels whatsapp integrations <id> Los n\xFAmeros de WhatsApp Business de su due\xF1o en Formmy (n\xFAmero, qui\xE9n contesta hoy, id de la Integration)
|
|
11422
11546
|
channels whatsapp link|unlink <id> --integration ID --answer on|off Trae un n\xFAmero de WhatsApp Business de Formmy/EasyBits a este agente, o devu\xE9lvelo [--forward] [--dry-run]
|
|
11423
11547
|
channels whatsapp history <id> --integration ID [--days 7] Trae de Formmy sus tarjetas, mensajes, pedidos y archivos recientes (sin duplicar; Formmy guarda los archivos 7 d\xEDas) [--media] [--dry-run]
|
|
11424
11548
|
channels wa import <id> --from-file F | --from-dir DIR --own-number yes|no Mueve aqu\xED una sesi\xF3n de WhatsApp personal, sin QR [--dry-run] [--replace] [--no-connect]
|
|
@@ -11437,7 +11561,7 @@ ${bold("Hablar con un agente")}
|
|
|
11437
11561
|
chat <id> ["mensaje"] Conversaci\xF3n en streaming (sin mensaje = interactivo) [--conversation ID]
|
|
11438
11562
|
conversations ls <id> Lista conversaciones
|
|
11439
11563
|
conversations show <id> <conversaci\xF3n> Qu\xE9 se dijo [--limit 20] (0 = todo)
|
|
11440
|
-
turns ls <id> Turnos recientes y por qu\xE9 fallaron [--errors] [--since 1h] [--limit N]
|
|
11564
|
+
turns ls <id> Turnos recientes y por qu\xE9 fallaron [--errors] [--skipped] [--since 1h] [--limit N]
|
|
11441
11565
|
turns show <id> [turno] Qu\xE9 lleg\xF3, qu\xE9 contest\xF3 y en qu\xE9 se fue el tiempo (default: el \xFAltimo) [--full]
|
|
11442
11566
|
|
|
11443
11567
|
${bold("Turnos programados")} ${dim("(cada corrida cae en una conversaci\xF3n y avisa a tu tel\xE9fono; una respuesta de exactamente OK no avisa)")}
|
|
@@ -11568,6 +11692,8 @@ async function main() {
|
|
|
11568
11692
|
"as-formmy": { type: "boolean" },
|
|
11569
11693
|
"key-column": { type: "string" },
|
|
11570
11694
|
"unset-env": { type: "string" },
|
|
11695
|
+
orphans: { type: "boolean" },
|
|
11696
|
+
skipped: { type: "boolean" },
|
|
11571
11697
|
keep: { type: "boolean" },
|
|
11572
11698
|
boxes: { type: "boolean" },
|
|
11573
11699
|
like: { type: "string" },
|
|
@@ -11629,7 +11755,7 @@ async function main() {
|
|
|
11629
11755
|
const tokens = splitTokens(pos, v);
|
|
11630
11756
|
globals.token = tokens.session;
|
|
11631
11757
|
globals.profile = v.profile;
|
|
11632
|
-
if (v.version) return out({ version: "0.
|
|
11758
|
+
if (v.version) return out({ version: "0.24.1" }, () => "0.24.1");
|
|
11633
11759
|
for (const n of normalizeArgs(pos, v)) if (!globals.json) info(dim(n));
|
|
11634
11760
|
const context = (await readConfig()).workspace;
|
|
11635
11761
|
if (!explicitToken()?.startsWith("gat_")) {
|
|
@@ -11748,7 +11874,7 @@ async function main() {
|
|
|
11748
11874
|
throw unknown(cmd, cmd, COMMANDS, "ghosty --help");
|
|
11749
11875
|
}
|
|
11750
11876
|
}
|
|
11751
|
-
var notice = updateNotice("0.
|
|
11877
|
+
var notice = updateNotice("0.24.1").catch(() => null);
|
|
11752
11878
|
main().then(
|
|
11753
11879
|
async () => {
|
|
11754
11880
|
const n = await notice;
|
package/package.json
CHANGED