@ghostystudio/cli 0.86.0 → 0.88.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/README.md +10 -0
- package/dist/index.js +194 -53
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -74,6 +74,8 @@ ghosty channels whatsapp ls|enable|disable <id> [number] [--dry-run]
|
|
|
74
74
|
ghosty channels whatsapp link|unlink <id> --integration ID --answer on|off [--forward] [--dry-run] # bring a WABA number from Formmy
|
|
75
75
|
ghosty channels whatsapp history <id> --integration ID [--days 7] [--dry-run] # its recent cards, messages and orders (no duplicates)
|
|
76
76
|
ghosty channels whatsapp|messenger connect <id> # opens Channels to connect with Meta (browser sign-in)
|
|
77
|
+
ghosty channels messenger status <id> # each page: who answers, last webhook, token health
|
|
78
|
+
ghosty channels messenger on|off <id> <page> [--take] # this agent answers the page, or stops (off keeps logging)
|
|
77
79
|
ghosty channels mteams connect <id> --app-id <guid> --tenant <guid> # Microsoft Teams bot (secret in GHOSTY_MTEAMS_SECRET); ls shows endpoint and last problem
|
|
78
80
|
ghosty channels wa import <id> --from-file F | --from-dir DIR --own-number yes|no [--dry-run] # personal WhatsApp session, no QR
|
|
79
81
|
ghosty channels wa pair|status|pause <id> [--phone N]
|
|
@@ -151,6 +153,14 @@ Every command has its own help with examples: `ghosty <command> --help` or `ghos
|
|
|
151
153
|
|
|
152
154
|
Exit codes: `0` ok · `1` API error · `2` usage error · `3` not signed in.
|
|
153
155
|
|
|
156
|
+
### Changes in 0.88.0
|
|
157
|
+
|
|
158
|
+
- `me files rm` takes several ids (`a,b` or `a b`) and `--q TEXT`, lists what it will delete, asks first (without a terminal it needs `--yes`; before, it deleted without asking) and `--dry-run` only shows the list.
|
|
159
|
+
- `files ls|get|rm <agent> --session <conversation>`: on a pool agent, that conversation's folder in its box (attachments, what it made). Never wakes a paused box.
|
|
160
|
+
- `files` and `me files` point to each other in their help, errors and empty listings.
|
|
161
|
+
- The «Update available» notice only shows to a person at a terminal; agents and scripts see it only in the unknown-option error.
|
|
162
|
+
- gs: a file that looks like a key (cookies.txt, `.env`, private key) is used in the turn but not saved to the library; `POST /api/v2/me/files` answers `422 secret_file`.
|
|
163
|
+
|
|
154
164
|
### Changes in 0.86.0
|
|
155
165
|
|
|
156
166
|
- `ghosty tts "text" [--voice em_santa|em_alex|ef_dora] [--out FILE]`: hear the house voice (Kokoro, Mexican Spanish, with Ghosty's pronunciation lexicon) say a text, saved as a .wav. Up to 400 characters.
|
package/dist/index.js
CHANGED
|
@@ -1175,7 +1175,7 @@ async function postLong(path2, body, timeoutMs = 9e5) {
|
|
|
1175
1175
|
const req = httpsRequest(`${base}${path2}`, {
|
|
1176
1176
|
method: "POST",
|
|
1177
1177
|
timeout: timeoutMs,
|
|
1178
|
-
headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.
|
|
1178
|
+
headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.88.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
|
|
1179
1179
|
}, (res) => {
|
|
1180
1180
|
let buf = "";
|
|
1181
1181
|
res.on("data", (c) => buf += c);
|
|
@@ -1217,7 +1217,7 @@ async function request(path2, opts = {}, retried = false) {
|
|
|
1217
1217
|
method: opts.method ?? (opts.body !== void 0 || opts.raw !== void 0 ? "POST" : "GET"),
|
|
1218
1218
|
headers: {
|
|
1219
1219
|
authorization: `Bearer ${token}`,
|
|
1220
|
-
"user-agent": `ghosty-cli/${"0.
|
|
1220
|
+
"user-agent": `ghosty-cli/${"0.88.0"}`,
|
|
1221
1221
|
"accept-language": lang(),
|
|
1222
1222
|
...opts.body !== void 0 ? { "content-type": "application/json" } : {},
|
|
1223
1223
|
...opts.headers
|
|
@@ -10047,21 +10047,25 @@ async function tryTurn(id, text, v) {
|
|
|
10047
10047
|
${dim(`(${t("warning", "aviso")}: ${serverText(r.error)})`)}` : ""));
|
|
10048
10048
|
}
|
|
10049
10049
|
async function filesCmd(sub, id, path2, v) {
|
|
10050
|
-
const ex = "ghosty files ls <agent-id> | get <agent-id> <path> | put <agent-id> <path> --file <local> | rm <agent-id> <path>";
|
|
10050
|
+
const ex = "ghosty files ls <agent-id> [--session <id>] | get <agent-id> <path> | put <agent-id> <path> --file <local> | rm <agent-id> <path>";
|
|
10051
10051
|
id = need(id, "agent id", ex);
|
|
10052
10052
|
const p = (path2 ?? "").replace(/^\/+/, "");
|
|
10053
|
+
const qs = v.session ? `?session=${encodeURIComponent(v.session)}` : "";
|
|
10054
|
+
const en = v.session ? ` --session ${v.session}` : "";
|
|
10053
10055
|
switch (sub) {
|
|
10054
10056
|
case "ls": {
|
|
10055
|
-
const r = await api(agentPath(id, `/files/${p}`));
|
|
10057
|
+
const r = await api(agentPath(id, `/files/${p}${qs}`));
|
|
10056
10058
|
const cols = r.files?.length ? Object.keys(r.files[0]).slice(0, 4) : ["path"];
|
|
10059
|
+
const hint = r.files?.length ? "" : `
|
|
10060
|
+
${dim(t("Looking for your account's files? ghosty me files ls", "\xBFBuscas los archivos de tu cuenta? ghosty me files ls"))}`;
|
|
10057
10061
|
out(r, () => `${dim(r.dir)}
|
|
10058
10062
|
${table(r.files ?? [], cols)}${r.note ? `
|
|
10059
|
-
${dim(r.note)}` : ""}`);
|
|
10063
|
+
${dim(r.note)}` : ""}${hint}`);
|
|
10060
10064
|
return;
|
|
10061
10065
|
}
|
|
10062
10066
|
case "get": {
|
|
10063
10067
|
need(path2, "path", ex);
|
|
10064
|
-
const res = await request(agentPath(id, `/files/${p}`));
|
|
10068
|
+
const res = await request(agentPath(id, `/files/${p}${qs}`));
|
|
10065
10069
|
const bytes = Buffer.from(await res.arrayBuffer());
|
|
10066
10070
|
if (v.out) {
|
|
10067
10071
|
await writeFile3(v.out, bytes);
|
|
@@ -10078,12 +10082,13 @@ ${dim(r.note)}` : ""}`);
|
|
|
10078
10082
|
}
|
|
10079
10083
|
case "rm": {
|
|
10080
10084
|
need(path2, "path", ex);
|
|
10081
|
-
await confirm(t(`Delete ${p} from the agent's machine?`, `\xBFBorrar ${p} de la m\xE1quina del agente?`), { yes: v.yes, retry: `ghosty files rm ${id} ${p}` });
|
|
10082
|
-
out(await api(agentPath(id, `/files/${p}`), { method: "DELETE" }) ?? { deleted: p }, () => `${lavender("\u2713")} ${t("Deleted", "Borrado")} ${p}`);
|
|
10085
|
+
await confirm(t(`Delete ${p} from the agent's machine?`, `\xBFBorrar ${p} de la m\xE1quina del agente?`), { yes: v.yes, retry: `ghosty files rm ${id} ${p}${en}` });
|
|
10086
|
+
out(await api(agentPath(id, `/files/${p}${qs}`), { method: "DELETE" }) ?? { deleted: p }, () => `${lavender("\u2713")} ${t("Deleted", "Borrado")} ${p}`);
|
|
10083
10087
|
return;
|
|
10084
10088
|
}
|
|
10085
10089
|
default:
|
|
10086
|
-
throw usage(t(`Unknown files subcommand: ${sub ?? "(none)"}.`, `Subcomando de files desconocido: ${sub ?? "(ninguno)"}.`), ex
|
|
10090
|
+
throw usage(t(`Unknown files subcommand: ${sub ?? "(none)"}.`, `Subcomando de files desconocido: ${sub ?? "(ninguno)"}.`), `${ex}
|
|
10091
|
+
${t("Your account's files: ghosty me files ls", "Los archivos de tu cuenta: ghosty me files ls")}`);
|
|
10087
10092
|
}
|
|
10088
10093
|
}
|
|
10089
10094
|
async function readStdin() {
|
|
@@ -10858,8 +10863,46 @@ ${red(t(`${failed.length} failed:`, `${failed.length} fallaron:`))}
|
|
|
10858
10863
|
${failed.map((x) => ` ${x.name} ${dim(`${x.id} \xB7 ${x.error}`)}`).join("\n")}` : "")
|
|
10859
10864
|
);
|
|
10860
10865
|
}
|
|
10861
|
-
|
|
10862
|
-
|
|
10866
|
+
function fileIds(args) {
|
|
10867
|
+
return [...new Set(args.flatMap((a) => (a ?? "").split(",")).map((x) => x.trim()).filter(Boolean))];
|
|
10868
|
+
}
|
|
10869
|
+
async function meFilesRm(ids, v, ex) {
|
|
10870
|
+
if (!ids.length && !v.q) throw usage(t("Missing file id (or --q TEXT).", "Falta el id del archivo (o --q TEXTO)."), ex);
|
|
10871
|
+
let rows = ids.map((id) => ({ id }));
|
|
10872
|
+
if (v.q) {
|
|
10873
|
+
const q = new URLSearchParams({ q: v.q, limit: "100", ...v.kind ? { kind: v.kind } : {} });
|
|
10874
|
+
const r = await api(`/api/v2/me/files?${q}`);
|
|
10875
|
+
rows = [...rows, ...r.files.filter((f) => !ids.includes(f.id)).map((f) => ({ id: f.id, name: f.name, size: f.size, created: f.createdAt.slice(0, 10) }))];
|
|
10876
|
+
}
|
|
10877
|
+
const retry = `ghosty me files rm ${[...ids, ...v.q ? [`--q "${v.q}"`] : []].join(" ")}`;
|
|
10878
|
+
const lista = table(rows, v.q ? ["id", "name", "size", "created"] : ["id"]);
|
|
10879
|
+
if (!rows.length) return out({ deleted: [], failed: [] }, () => dim(t("Nothing matches.", "No hay nada que coincida.")));
|
|
10880
|
+
if (v["dry-run"]) return out({ wouldDelete: rows }, () => `${lista}
|
|
10881
|
+
${dim(t("Dry run: nothing deleted.", "Simulaci\xF3n: no se borr\xF3 nada."))}
|
|
10882
|
+
${dim(applyHint(retry))}`);
|
|
10883
|
+
await confirm(`${lista}
|
|
10884
|
+
${t(`Delete ${rows.length} file(s)?`, `\xBFBorro ${rows.length} archivo(s)?`)}`, { yes: v.yes, retry });
|
|
10885
|
+
const deleted = [];
|
|
10886
|
+
const failed = [];
|
|
10887
|
+
for (const [i, f] of rows.entries()) {
|
|
10888
|
+
try {
|
|
10889
|
+
await api(`/api/v2/me/files/${enc4(f.id)}`, { method: "DELETE" });
|
|
10890
|
+
deleted.push(f.id);
|
|
10891
|
+
if (rows.length > 1) info(dim(`${i + 1}/${rows.length} ${f.name ?? f.id}`));
|
|
10892
|
+
} catch (e) {
|
|
10893
|
+
failed.push({ id: f.id, error: e instanceof Error ? e.message : String(e) });
|
|
10894
|
+
}
|
|
10895
|
+
}
|
|
10896
|
+
out(
|
|
10897
|
+
{ deleted, failed },
|
|
10898
|
+
() => `${lavender("\u2713")} ${t(`Deleted ${deleted.length}`, `Borrados ${deleted.length}`)}` + (failed.length ? `
|
|
10899
|
+
${red(t(`${failed.length} failed:`, `${failed.length} fallaron:`))}
|
|
10900
|
+
${failed.map((x) => ` ${x.id} ${dim(x.error)}`).join("\n")}` : "")
|
|
10901
|
+
);
|
|
10902
|
+
if (failed.length) throw new CliError(t(`${failed.length} file(s) not deleted.`, `${failed.length} archivo(s) sin borrar.`), EXIT.API);
|
|
10903
|
+
}
|
|
10904
|
+
async function meFiles(sub, arg, v, rest = []) {
|
|
10905
|
+
const ex = "ghosty me files ls [--q TEXT] [--kind image|video|audio|document|other] [--cursor C] | get <file-id> [--out FILE] | export [--out DIR] | upload <file> | rm <file-id>[,<file-id>\u2026] [--q TEXT] [--dry-run]";
|
|
10863
10906
|
switch (sub) {
|
|
10864
10907
|
case "export":
|
|
10865
10908
|
case "download":
|
|
@@ -10909,13 +10952,11 @@ ${dim(t(`${(r.storage.usedBytes / 1e9).toFixed(2)} GB of ${r.storage.limitGb} GB
|
|
|
10909
10952
|
out(r, () => `${lavender("\u2713")} ${t("Uploaded", "Subido")} ${r.name} (${r.id})`);
|
|
10910
10953
|
return;
|
|
10911
10954
|
}
|
|
10912
|
-
case "rm":
|
|
10913
|
-
|
|
10914
|
-
out(await api(`/api/v2/me/files/${enc4(arg)}`, { method: "DELETE" }) ?? { deleted: arg }, () => `${lavender("\u2713")} ${t("Deleted", "Borrado")} ${arg}`);
|
|
10915
|
-
return;
|
|
10916
|
-
}
|
|
10955
|
+
case "rm":
|
|
10956
|
+
return meFilesRm(fileIds([arg, ...rest]), v, ex);
|
|
10917
10957
|
default:
|
|
10918
|
-
throw usage(t(`Unknown files subcommand: ${sub ?? "(none)"}.`, `Subcomando de files desconocido: ${sub ?? "(ninguno)"}.`), ex
|
|
10958
|
+
throw usage(t(`Unknown files subcommand: ${sub ?? "(none)"}.`, `Subcomando de files desconocido: ${sub ?? "(ninguno)"}.`), `${ex}
|
|
10959
|
+
${t("Files in an agent's machine: ghosty files ls <agent>", "Archivos en la m\xE1quina de un agente: ghosty files ls <agente>")}`);
|
|
10919
10960
|
}
|
|
10920
10961
|
}
|
|
10921
10962
|
async function meUsage() {
|
|
@@ -11237,7 +11278,7 @@ async function call(path2, v, init = {}) {
|
|
|
11237
11278
|
"x-ghosty-ts": ts,
|
|
11238
11279
|
"x-ghosty-sig": sig,
|
|
11239
11280
|
"content-type": "application/json",
|
|
11240
|
-
"user-agent": `ghosty-cli/${"0.
|
|
11281
|
+
"user-agent": `ghosty-cli/${"0.88.0"}`
|
|
11241
11282
|
},
|
|
11242
11283
|
body: method === "GET" ? void 0 : raw
|
|
11243
11284
|
});
|
|
@@ -12085,6 +12126,7 @@ function skipReason(r) {
|
|
|
12085
12126
|
channel_off: ["WhatsApp channel off for this agent", "canal de WhatsApp apagado en este agente"],
|
|
12086
12127
|
no_quota: ["out of balance", "sin saldo"],
|
|
12087
12128
|
own_number: ["sent by another of our agent numbers (bot loop guard)", "lo mand\xF3 otro n\xFAmero nuestro con agente (guarda de bucle)"],
|
|
12129
|
+
known_contact: ["contact wrote before --new-only was turned on (only new contacts get the agent)", "el contacto ya escrib\xEDa antes de --new-only (el agente s\xF3lo contesta a contactos nuevos)"],
|
|
12088
12130
|
partner_token: ["partner did not issue a turn token", "el partner no dio token de turno"],
|
|
12089
12131
|
partner_busy: ["partner at its concurrent-turn limit", "el partner lleg\xF3 a su tope de turnos a la vez"]
|
|
12090
12132
|
};
|
|
@@ -12579,7 +12621,9 @@ async function channelsCmd(kind, sub, id, ref, v) {
|
|
|
12579
12621
|
}
|
|
12580
12622
|
if (kind === "messenger" || kind === "fb") {
|
|
12581
12623
|
if (!id) throw usage(t("Missing agent id.", "Falta el id del agente."), t("Example", "Ejemplo") + ": ghosty channels messenger ls <agent-id> [--orphans]");
|
|
12582
|
-
if (sub
|
|
12624
|
+
if (sub === "status") return messengerStatusCmd(id);
|
|
12625
|
+
if (sub === "on" || sub === "off") return messengerToggleCmd(id, ref, sub === "on", v);
|
|
12626
|
+
if (sub && sub !== "ls" && sub !== "list") throw usage(t(`Unknown subcommand: ${sub}.`, `Subcomando desconocido: ${sub}.`), t("Try", "Prueba") + ": ghosty channels messenger ls|status|on|off <agent-id> [page]");
|
|
12583
12627
|
return linksCmd(id, "messenger", v.orphans);
|
|
12584
12628
|
}
|
|
12585
12629
|
if (kind !== "whatsapp" && kind !== "wa") throw usage(t(`Unknown channel: ${kind ?? "(none)"}.`, `Canal desconocido: ${kind ?? "(ninguno)"}.`), t("Try", "Prueba") + ": ghosty channels whatsapp ls <agent-id>");
|
|
@@ -12600,10 +12644,11 @@ async function channelsCmd(kind, sub, id, ref, v) {
|
|
|
12600
12644
|
answers: n2.mine ? lavender(t("this agent", "este agente")) : n2.recordsOnly ? t("this agent (records only)", "este agente (s\xF3lo registra)") : n2.takenByOther ? t("another agent", "otro agente") : dim(t("nobody (silent)", "nadie (en silencio)")),
|
|
12601
12645
|
"formmy forwards": fwd(n2),
|
|
12602
12646
|
"ads only": n2.adsOnly ? lavender(t("yes", "s\xED")) : "",
|
|
12647
|
+
"new only": n2.newOnly ? lavender(t("yes", "s\xED")) : "",
|
|
12603
12648
|
board: n2.board ? n2.board.name : dim(t("the agent's", "el del agente")),
|
|
12604
12649
|
id: n2.id
|
|
12605
12650
|
}));
|
|
12606
|
-
return out(cur, () => rows.length ? table(rows, ["number", "answers", "formmy forwards", "ads only", "board", "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}`)));
|
|
12651
|
+
return out(cur, () => rows.length ? table(rows, ["number", "answers", "formmy forwards", "ads only", "new only", "board", "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}`)));
|
|
12607
12652
|
}
|
|
12608
12653
|
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");
|
|
12609
12654
|
if (!ref) throw usage(t("Missing number.", "Falta el n\xFAmero."), t("Example", "Ejemplo") + `: ghosty channels whatsapp ${sub} ${id} +52 55 1234 5678`);
|
|
@@ -12766,21 +12811,24 @@ async function linkCmd(sub, id, v) {
|
|
|
12766
12811
|
if (!id || !v.integration) throw usage(t("Missing agent id or --integration.", "Falta el id del agente o --integration."), t("Example", "Ejemplo") + `: ghosty channels whatsapp ${sub} <agent-id> --integration <formmy-integration-id> --answer off --dry-run`);
|
|
12767
12812
|
if (v["ads-only"] !== void 0 && v["ads-only"] !== "on" && v["ads-only"] !== "off") throw usage(t("--ads-only takes on or off.", "--ads-only acepta on u off."));
|
|
12768
12813
|
const adsOnly = v["ads-only"] === void 0 ? void 0 : v["ads-only"] === "on";
|
|
12814
|
+
if (v["new-only"] !== void 0 && v["new-only"] !== "on" && v["new-only"] !== "off") throw usage(t("--new-only takes on or off.", "--new-only acepta on u off."));
|
|
12815
|
+
const newOnly = v["new-only"] === void 0 ? void 0 : v["new-only"] === "on";
|
|
12816
|
+
const newOnlyFlag = v["new-only"] ? ` --new-only ${v["new-only"]}` : "";
|
|
12769
12817
|
const board = v.board === void 0 ? void 0 : /^(agent|agente)$/i.test(v.board) ? null : v.board;
|
|
12770
12818
|
const boardFlag = v.board !== void 0 ? ` --board ${JSON.stringify(v.board)}` : "";
|
|
12771
|
-
if (sub === "link" && (adsOnly !== void 0 || board !== void 0) && v.answer === void 0) {
|
|
12819
|
+
if (sub === "link" && (adsOnly !== void 0 || newOnly !== void 0 || board !== void 0) && v.answer === void 0) {
|
|
12772
12820
|
const path3 = `/api/v2/agents/${enc11(id)}/whatsapp/link`;
|
|
12773
|
-
const r2 = await api(path3, { method: "POST", body: { integrationId: v.integration, ...adsOnly !== void 0 ? { adsOnly } : {}, ...board !== void 0 ? { board } : {}, ...v["dry-run"] ? { dryRun: true } : {} } });
|
|
12821
|
+
const r2 = await api(path3, { method: "POST", body: { integrationId: v.integration, ...adsOnly !== void 0 ? { adsOnly } : {}, ...newOnly !== void 0 ? { newOnly } : {}, ...board !== void 0 ? { board } : {}, ...v["dry-run"] ? { dryRun: true } : {} } });
|
|
12774
12822
|
return out(r2, () => `${r2.plan.map((x) => serverText(x)).join("\n")}${v["dry-run"] ? `
|
|
12775
|
-
${dim(applyHint(`ghosty channels whatsapp link ${id} --integration ${v.integration}${v["ads-only"] ? ` --ads-only ${v["ads-only"]}` : ""}${boardFlag}`))}` : ""}`);
|
|
12823
|
+
${dim(applyHint(`ghosty channels whatsapp link ${id} --integration ${v.integration}${v["ads-only"] ? ` --ads-only ${v["ads-only"]}` : ""}${newOnlyFlag}${boardFlag}`))}` : ""}`);
|
|
12776
12824
|
}
|
|
12777
12825
|
if (sub === "link" && v.answer !== "on" && v.answer !== "off") throw usage(t("Missing --answer on|off.", "Falta --answer on|off."), t("on = this agent answers real customers \xB7 off = it only records (add --forward so gs still sees the conversations)", "on = este agente contesta a clientes reales \xB7 off = s\xF3lo registra (agrega --forward para que gs siga viendo las conversaciones)"));
|
|
12778
12826
|
const path2 = `/api/v2/agents/${enc11(id)}/whatsapp/link`;
|
|
12779
12827
|
if (v["partner-agent"] && !v["partner-tenant"]) throw usage(t("--partner-agent needs --partner-tenant <externalId>.", "--partner-agent necesita --partner-tenant <externalId>."));
|
|
12780
12828
|
const partner = v["partner-tenant"] ? { partnerExternalId: v["partner-tenant"], ...v["partner-agent"] ? { partnerAgentSlug: v["partner-agent"] } : {} } : {};
|
|
12781
|
-
const body = { integrationId: v.integration, ...sub === "unlink" ? { unlink: true } : { answer: v.answer === "on", ...v.forward ? { forward: true } : {}, ...adsOnly !== void 0 ? { adsOnly } : {}, ...board !== void 0 ? { board } : {}, ...partner } };
|
|
12829
|
+
const body = { integrationId: v.integration, ...sub === "unlink" ? { unlink: true } : { answer: v.answer === "on", ...v.forward ? { forward: true } : {}, ...adsOnly !== void 0 ? { adsOnly } : {}, ...newOnly !== void 0 ? { newOnly } : {}, ...board !== void 0 ? { board } : {}, ...partner } };
|
|
12782
12830
|
const partnerFlags = v["partner-tenant"] ? ` --partner-tenant ${v["partner-tenant"]}${v["partner-agent"] ? ` --partner-agent ${v["partner-agent"]}` : ""}` : "";
|
|
12783
|
-
const cmd = `ghosty channels whatsapp ${sub} ${id} --integration ${v.integration}${sub === "link" ? ` --answer ${v.answer}${v.forward ? " --forward" : ""}${v["ads-only"] ? ` --ads-only ${v["ads-only"]}` : ""}${boardFlag}${partnerFlags}` : ""}`;
|
|
12831
|
+
const cmd = `ghosty channels whatsapp ${sub} ${id} --integration ${v.integration}${sub === "link" ? ` --answer ${v.answer}${v.forward ? " --forward" : ""}${v["ads-only"] ? ` --ads-only ${v["ads-only"]}` : ""}${newOnlyFlag}${boardFlag}${partnerFlags}` : ""}`;
|
|
12784
12832
|
const plan = await api(path2, { method: "POST", body: { ...body, dryRun: true } });
|
|
12785
12833
|
const text = `${plan.plan.map((x) => serverText(x)).join("\n")}
|
|
12786
12834
|
${dim(`Formmy: ${plan.formmy.before.url ?? "null"} \u2192 ${plan.formmy.after.url ?? "null"} (${t("as", "como")} ${plan.formmy.by})`)}`;
|
|
@@ -12821,6 +12869,41 @@ async function pageWithRetry(fetchPage, where, waitMs = (n) => 5e3 * n, tries =
|
|
|
12821
12869
|
}
|
|
12822
12870
|
}
|
|
12823
12871
|
var lastCell = (l) => l ? `${l.agentName ?? l.agentId} ${dim(l.at.slice(0, 16).replace("T", " "))}` : dim("\u2014");
|
|
12872
|
+
function findPage(pages, ref) {
|
|
12873
|
+
const hit = pages.find((p) => p.pageId === ref) ?? pages.find((p) => (p.name ?? "").toLowerCase() === ref.toLowerCase());
|
|
12874
|
+
if (!hit) throw usage(t(`No Messenger page "${ref}" for this agent.`, `Este agente no tiene la p\xE1gina de Messenger "${ref}".`), pages.length ? `${t("Available", "Disponibles")}: ${pages.map((p) => p.name ?? p.pageId).join(", ")}` : t("Connect one first in the web (the agent's Channels page).", "Primero conecta una en la web (p\xE1gina Canales del agente)."));
|
|
12875
|
+
return hit;
|
|
12876
|
+
}
|
|
12877
|
+
var ago = (iso) => {
|
|
12878
|
+
if (!iso) return dim(t("never", "nunca"));
|
|
12879
|
+
const h = (Date.now() - new Date(iso).getTime()) / 36e5;
|
|
12880
|
+
return h < 1 ? t("< 1 h ago", "hace < 1 h") : h < 48 ? t(`${Math.round(h)} h ago`, `hace ${Math.round(h)} h`) : t(`${Math.round(h / 24)} d ago`, `hace ${Math.round(h / 24)} d`);
|
|
12881
|
+
};
|
|
12882
|
+
async function messengerStatusCmd(id) {
|
|
12883
|
+
const r = await api(`/api/v2/agents/${enc11(id)}/messenger?health=1`);
|
|
12884
|
+
const health = (p) => !p.health ? dim("?") : p.health.ok ? p.health.subscribed ? t("ok", "ok") : lavender(t("not subscribed: reconnect it", "sin suscripci\xF3n: recon\xE9ctala")) : lavender(p.health.code === 190 ? t("token dead: reconnect it", "token muerto: recon\xE9ctala") : p.health.message);
|
|
12885
|
+
const rows = r.pages.map((p) => ({
|
|
12886
|
+
page: p.name ?? p.pageId,
|
|
12887
|
+
answers: p.answers === "this" ? lavender(t("this agent", "este agente")) : p.answers === "other" ? t("another agent", "otro agente") : dim(t("nobody (records only)", "nadie (s\xF3lo registra)")),
|
|
12888
|
+
"last webhook": ago(p.lastWebhookAt),
|
|
12889
|
+
health: health(p),
|
|
12890
|
+
id: p.pageId
|
|
12891
|
+
}));
|
|
12892
|
+
return out(r, () => rows.length ? table(rows, ["page", "answers", "last webhook", "health", "id"]) : dim(t("No Messenger pages yet. Connect one in the agent's Channels page.", "Todav\xEDa no hay p\xE1ginas de Messenger. Conecta una en la p\xE1gina Canales del agente.")));
|
|
12893
|
+
}
|
|
12894
|
+
async function messengerToggleCmd(id, ref, on, v) {
|
|
12895
|
+
if (!ref) throw usage(t("Missing the page (id or name).", "Falta la p\xE1gina (id o nombre)."), t("Example", "Ejemplo") + `: ghosty channels messenger ${on ? "on" : "off"} ${id} <page-id>`);
|
|
12896
|
+
const cur = await api(`/api/v2/agents/${enc11(id)}/messenger`);
|
|
12897
|
+
const page = findPage(cur.pages, ref);
|
|
12898
|
+
const label = page.name ?? page.pageId;
|
|
12899
|
+
const plan = on ? page.answers === "other" && !v.take ? null : t(`This agent will answer the Messenger and comments of "${label}" (real customers).`, `Este agente va a contestar el Messenger y los comentarios de \xAB${label}\xBB (clientes reales).`) : t(`Nobody will answer "${label}"; messages keep landing on the board.`, `Nadie va a contestar \xAB${label}\xBB; los mensajes se siguen registrando en el tablero.`);
|
|
12900
|
+
if (!plan) throw usage(t(`Another agent answers "${label}".`, `Otro agente contesta \xAB${label}\xBB.`), t("Add --take to move it to this agent.", "Agrega --take para pasarla a este agente."));
|
|
12901
|
+
if (v["dry-run"]) return out({ dryRun: true, pageId: page.pageId, on }, () => `${plan}
|
|
12902
|
+
${dim(t("(dry run: nothing changed)", "(dry run: no se cambi\xF3 nada)"))}`);
|
|
12903
|
+
if (on) await confirm(`${plan} ${t("Continue?", "\xBFSeguir?")}`, { yes: v.yes, retry: `ghosty channels messenger on ${id} ${page.pageId}${v.take ? " --take" : ""}` });
|
|
12904
|
+
const r = await api(`/api/v2/agents/${enc11(id)}/messenger`, { method: "POST", body: { pageId: page.pageId, on, ...v.take ? { take: true } : {} } });
|
|
12905
|
+
return out(r, () => on ? t(`Now answering "${label}".`, `Ya contesta \xAB${label}\xBB.`) : t(`No longer answering "${label}"; messages are still logged.`, `Ya no contesta \xAB${label}\xBB; los mensajes se siguen registrando.`));
|
|
12906
|
+
}
|
|
12824
12907
|
async function linksCmd(id, kind, orphans) {
|
|
12825
12908
|
const r = await api(`/api/v2/agents/${enc11(id)}/channels/links?kind=${kind}${orphans ? "&orphans=1" : ""}`);
|
|
12826
12909
|
const now = (l) => l.answersNow ? l.answersNow.agentName ?? l.answersNow.agentId : l.recordsOnly ? t("nobody (records only)", "nadie (s\xF3lo registra)") : l.via === "easybits" ? "EasyBits" : l.via === "partner" ? "partner" : lavender(t("nobody", "nadie"));
|
|
@@ -13074,11 +13157,13 @@ init_api();
|
|
|
13074
13157
|
init_util();
|
|
13075
13158
|
init_i18n();
|
|
13076
13159
|
var enc12 = encodeURIComponent;
|
|
13077
|
-
|
|
13160
|
+
var FOLLOWUP_ACTIONS = /* @__PURE__ */ new Set(["on", "off", "template", "dry-run"]);
|
|
13161
|
+
async function boardsCmd(sub, a1, a2, v, context, a3) {
|
|
13078
13162
|
if (context && sub !== "assign") {
|
|
13079
13163
|
if ((sub === "ls" || sub === "list" || !sub || sub === "move-cards") && !a1) a1 = context;
|
|
13080
13164
|
else if ((sub === "create" || sub === "rm") && a1 && !a2) [a1, a2] = [context, a1];
|
|
13081
13165
|
else if (sub === "access" && !a2) [a1, a2] = [context, a1 ?? "main"];
|
|
13166
|
+
else if (sub === "followups" && (!a1 || FOLLOWUP_ACTIONS.has(a1))) [a1, a2, a3] = [context, void 0, a1];
|
|
13082
13167
|
}
|
|
13083
13168
|
switch (sub ?? "ls") {
|
|
13084
13169
|
case "ls":
|
|
@@ -13145,8 +13230,37 @@ ${table(r.people.map((p) => ({ email: p.email, until: fmtUntil(p.until) })), ["e
|
|
|
13145
13230
|
return [head + people, ...changes].join("\n");
|
|
13146
13231
|
});
|
|
13147
13232
|
}
|
|
13233
|
+
// Seguimiento de cotizaciones del tablero: estado, prender/apagar, plantilla y prueba sin enviar.
|
|
13234
|
+
case "followups": {
|
|
13235
|
+
if (!a1) throw usage(t("Missing workspace.", "Falta el espacio."), t("Example", "Ejemplo") + ": ghosty boards followups <workspace> [board|main] [on|off|template|dry-run]");
|
|
13236
|
+
if (a2 && FOLLOWUP_ACTIONS.has(a2) && !a3) [a2, a3] = [void 0, a2];
|
|
13237
|
+
const board = a2 ?? "main";
|
|
13238
|
+
const path2 = `/api/v2/workspaces/${enc12(a1)}/boards/followups`;
|
|
13239
|
+
const steps = v.steps ? v.steps.split(",").map((x) => Number(x.trim())).filter((n) => n > 0) : void 0;
|
|
13240
|
+
const minutes = v.minutes === void 0 ? void 0 : v.minutes === "on";
|
|
13241
|
+
if (a3 === "dry-run") {
|
|
13242
|
+
const r2 = await api(path2, { method: "POST", body: { board, intent: "dry-run" } });
|
|
13243
|
+
return out(r2, () => r2.items.length ? table(r2.items.map((i) => ({ folio: i.folio, toque: i.step + 1, action: i.action, detail: i.detail })), ["folio", "toque", "action", "detail"]) : dim(t("Nothing due right now.", "Nada que toque ahora.")));
|
|
13244
|
+
}
|
|
13245
|
+
if (a3 === "template") {
|
|
13246
|
+
const r2 = await api(path2, { method: "POST", body: { board, intent: "create-template" } });
|
|
13247
|
+
return out(r2, () => t("Template sent to Meta for review (utility).", "Plantilla enviada a revisi\xF3n de Meta (utilidad)."));
|
|
13248
|
+
}
|
|
13249
|
+
if (a3 === "on" || a3 === "off" || steps || minutes !== void 0) {
|
|
13250
|
+
await api(path2, { method: "POST", body: { board, intent: "set", ...a3 ? { enabled: a3 === "on" } : {}, ...steps ? { steps } : {}, ...minutes !== void 0 ? { testMinutes: minutes } : {} } });
|
|
13251
|
+
} else if (a3) throw usage(t(`Unknown action: ${a3}.`, `Acci\xF3n desconocida: ${a3}.`), "on | off | template | dry-run");
|
|
13252
|
+
const r = await api(`${path2}?board=${enc12(board)}`);
|
|
13253
|
+
return out(r, () => {
|
|
13254
|
+
const c = r.config;
|
|
13255
|
+
const head = `\xAB${r.board}\xBB: ${c.enabled ? lavender(t("on", "prendido")) : t("off", "apagado")} \xB7 ${t("touches at", "toques a los")} ${c.steps.join(", ")} ${c.testMinutes ? "min" : t("days", "d\xEDas")} \xB7 ${c.template} ${dim(`(${r.template.status ?? r.template.error ?? t("not in Meta", "sin dar de alta en Meta")})`)}`;
|
|
13256
|
+
const s2 = r.stats;
|
|
13257
|
+
const nums = `${s2.active} ${t("active", "en curso")} \xB7 ${s2.sent} ${t("sent", "enviados")} \xB7 ${s2.replied} ${t("replied", "contestaron")} \xB7 ${s2.paid} ${t("paid/closed", "pagaron o cerraron")} \xB7 ${s2.done} ${t("no reply", "sin respuesta")}`;
|
|
13258
|
+
const rows = r.recent.map((x) => ({ folio: x.folio, name: x.nombre ?? "", step: x.step, status: x.stop_reason ? `${x.status}: ${x.stop_reason}` : x.status, next: x.next_at ? new Date(x.next_at * 1e3).toISOString().slice(0, 16) : "" }));
|
|
13259
|
+
return [head, nums, rows.length ? table(rows, ["folio", "name", "step", "status", "next"]) : ""].filter(Boolean).join("\n");
|
|
13260
|
+
});
|
|
13261
|
+
}
|
|
13148
13262
|
default:
|
|
13149
|
-
throw usage(t(`Unknown boards subcommand: ${sub}.`, `Subcomando de boards desconocido: ${sub}.`), t("Try", "Prueba") + ": ghosty boards ls | create | assign | move-cards | access | rm");
|
|
13263
|
+
throw usage(t(`Unknown boards subcommand: ${sub}.`, `Subcomando de boards desconocido: ${sub}.`), t("Try", "Prueba") + ": ghosty boards ls | create | assign | move-cards | access | followups | rm");
|
|
13150
13264
|
}
|
|
13151
13265
|
}
|
|
13152
13266
|
|
|
@@ -13375,7 +13489,11 @@ var HELPS_ES = {
|
|
|
13375
13489
|
],
|
|
13376
13490
|
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).", "jobs es s\xF3lo lectura: las \xFAltimas 24 h de encargos en segundo plano de TUS chats personales, y si cada uno ya le lleg\xF3 al agente. Se detienen con turns cancel; lo que se repite a una hora es ghosty schedule add --every."]
|
|
13377
13491
|
},
|
|
13378
|
-
files: {
|
|
13492
|
+
files: {
|
|
13493
|
+
summary: "Archivos en la m\xE1quina del agente.",
|
|
13494
|
+
flags: [["--session ID", "agentes de pool: la carpeta de esa conversaci\xF3n en su caja (adjuntos, lo que hizo) en vez de su conocimiento"]],
|
|
13495
|
+
notes: ["Los archivos de TU cuenta (lo que subiste y lo que entregaron los agentes) son ghosty me files, no esto.", "--session nunca despierta una caja dormida; una caja reciclada vuelve vac\xEDa."]
|
|
13496
|
+
},
|
|
13379
13497
|
mcp: {
|
|
13380
13498
|
summary: "Servidores MCP de un agente. set REEMPLAZA la lista entera; connect agrega uno remoto con su URL.",
|
|
13381
13499
|
replace: [["# one remote server, OAuth in the browser if it asks", "# un servidor remoto; OAuth en el navegador si lo pide"]],
|
|
@@ -13507,14 +13625,15 @@ var HELPS_ES = {
|
|
|
13507
13625
|
flags: [
|
|
13508
13626
|
["--out DIR", "export: carpeta destino (default ghosty-archivos); get: archivo donde guardarlo"],
|
|
13509
13627
|
["--kind K", "ls, export: s\xF3lo image | video | audio | document | other"],
|
|
13510
|
-
["-n, --dry-run", "export: cu\xE1ntos archivos y MB por carpeta, sin bajar nada"],
|
|
13628
|
+
["-n, --dry-run", "export: cu\xE1ntos archivos y MB por carpeta, sin bajar nada; rm: qu\xE9 borrar\xEDa"],
|
|
13629
|
+
["--q TEXT", "ls, rm: busca por nombre"],
|
|
13511
13630
|
["--rev N", "docs: trabaja sobre la versi\xF3n N (default: la \xFAltima; get las lista)"],
|
|
13512
13631
|
["--format pdf|md", "docs export: PDF (tama\xF1o carta, default) o Markdown"],
|
|
13513
13632
|
["--language L", "docs check: es (default), en-US, pt-BR o fr"],
|
|
13514
13633
|
["--block N / --segment S", "docs tts: la posici\xF3n del bloque (desde 0) y, si quieres, la frase dentro de \xE9l; la salida dice cu\xE1ntas frases tiene"]
|
|
13515
13634
|
],
|
|
13516
13635
|
notes: [
|
|
13517
|
-
"<doc> es el id del documento o la liga que da el agente (https://www.ghosty.studio/app/docs/<id>). No es lo mismo que agents docs (archivos que lee un agente) ni que ghosty docs (abre esta documentaci\xF3n).",
|
|
13636
|
+
"<doc> es el id del documento o la liga que da el agente (https://www.ghosty.studio/app/docs/<id>). No es lo mismo que agents docs (archivos que lee un agente) ni que ghosty docs (abre esta documentaci\xF3n). me files es la biblioteca de tu cuenta; los archivos dentro de la m\xE1quina de un agente son ghosty files.",
|
|
13518
13637
|
"Las memorias viajan a tus agentes s\xF3lo en tus chats personales (app, /c, notch), nunca a WhatsApp, Messenger ni demos.",
|
|
13519
13638
|
"export baja TODO lo de tu cuenta: documentos, archivos de tus chats y de los canales de tus agentes (lo que mandaron los clientes). Lo ya bajado con el mismo tama\xF1o se salta, as\xED que repetirlo s\xF3lo trae lo nuevo."
|
|
13520
13639
|
]
|
|
@@ -13550,6 +13669,7 @@ var HELPS_ES = {
|
|
|
13550
13669
|
["--text T / --sender N", "groups inject (staff): un mensaje que se perdi\xF3 (grupo apagado, deploy) entra como si N lo hubiera escrito ah\xED, y el agente contesta en el grupo"],
|
|
13551
13670
|
["--cached", "groups ls: no le pide a WhatsApp la lista en vivo (m\xE1s r\xE1pido; puede faltar alg\xFAn grupo nuevo)"],
|
|
13552
13671
|
["--ads-only on|off", "link: s\xF3lo las conversaciones que vienen de un anuncio abren tarjeta (tambi\xE9n solo, para cambiarlo en un n\xFAmero ya ligado)"],
|
|
13672
|
+
["--new-only on|off", "link: el agente s\xF3lo contesta a contactos que escriben por primera vez desde ahora; los de antes quedan en el hilo para ti (tambi\xE9n solo)"],
|
|
13553
13673
|
["--board NAME|agent", "link: este n\xFAmero llena su propio tablero de ventas (nombre, id o main) en vez del del agente; las tools del CRM del agente trabajan ese tablero en los turnos de este n\xFAmero. agent = vuelve al del agente. Tambi\xE9n solo, en un n\xFAmero ya ligado"],
|
|
13554
13674
|
["--list", "history --dry-run: CU\xC1LES conversaciones (tel\xE9fono, nombre, primer mensaje, mensajes, si vino de anuncio)"],
|
|
13555
13675
|
["--only-ads / --phones a,b", "history: importa (o lista) s\xF3lo las de anuncio y/o esos tel\xE9fonos"],
|
|
@@ -13645,13 +13765,16 @@ var HELPS_ES = {
|
|
|
13645
13765
|
["--from / --to B", "move-cards: tableros por nombre, id o main"],
|
|
13646
13766
|
["--integration ID | --phone N | --all", "move-cards: qu\xE9 tarjetas (el filtro es obligatorio)"],
|
|
13647
13767
|
["-n, --dry-run", "move-cards: cuenta y muestra una muestra, no cambia nada"],
|
|
13648
|
-
["--yes", "move-cards: se salta la confirmaci\xF3n (obligatorio sin terminal)"]
|
|
13768
|
+
["--yes", "move-cards: se salta la confirmaci\xF3n (obligatorio sin terminal)"],
|
|
13769
|
+
["--steps 2,7,14", "followups: d\xEDas despu\xE9s de la cotizaci\xF3n de cada toque (hasta 5)"],
|
|
13770
|
+
["--minutes on|off", "followups (staff): los pasos cuentan en minutos, para probar sin esperar d\xEDas"]
|
|
13649
13771
|
],
|
|
13650
13772
|
notes: [
|
|
13651
13773
|
"assign cambia a d\xF3nde van las tarjetas NUEVAS; move-cards trae las que ya existen (hilo, pedidos, actividad) con folio nuevo. Una tarjeta cuya conversaci\xF3n ya est\xE1 en el tablero destino se salta.",
|
|
13652
13774
|
"Leer requiere ser miembro; cambiar requiere ser due\xF1o o staff del espacio.",
|
|
13653
13775
|
"rm s\xF3lo borra un tablero vac\xEDo que no sea el principal.",
|
|
13654
|
-
"access sin flags dice qui\xE9n ve el tablero; due\xF1o y staff ven todos. Por qu\xE9 alguien lo ve: ghosty access why <correo> --workspace <espacio> --board <tablero>."
|
|
13776
|
+
"access sin flags dice qui\xE9n ve el tablero; due\xF1o y staff ven todos. Por qu\xE9 alguien lo ve: ghosty access why <correo> --workspace <espacio> --board <tablero>.",
|
|
13777
|
+
"followups: cada cotizaci\xF3n que manda el agente recibe hasta 3 toques por WhatsApp (2, 7 y 14 d\xEDas) escritos con la pl\xE1tica; paran si el cliente contesta, paga o alguien toma la pl\xE1tica. Fuera de la ventana de 24 h usa la plantilla de utilidad (template = mandarla a Meta). Cambiarlo pide el mismo permiso que mandar campa\xF1as."
|
|
13655
13778
|
]
|
|
13656
13779
|
},
|
|
13657
13780
|
cards: {
|
|
@@ -14166,8 +14289,10 @@ var HELPS = {
|
|
|
14166
14289
|
},
|
|
14167
14290
|
files: {
|
|
14168
14291
|
summary: "Files in the agent's machine.",
|
|
14169
|
-
usage: ["ghosty files ls <agent-id> [dir]", "ghosty files get <agent-id> <path> [--out LOCAL]", "ghosty files put <agent-id> <path> [--file LOCAL | < stdin]", "ghosty files rm <agent-id> <path> [--yes]"],
|
|
14170
|
-
examples: ["ghosty files put <id> knowledge/precios.md --file precios.md", "cat notas.md | ghosty files put <id> notas.md"],
|
|
14292
|
+
usage: ["ghosty files ls <agent-id> [dir] [--session <id>]", "ghosty files get <agent-id> <path> [--out LOCAL] [--session <id>]", "ghosty files put <agent-id> <path> [--file LOCAL | < stdin]", "ghosty files rm <agent-id> <path> [--yes] [--session <id>]"],
|
|
14293
|
+
examples: ["ghosty files put <id> knowledge/precios.md --file precios.md", "cat notas.md | ghosty files put <id> notas.md", "ghosty files ls <id> adjuntos --session <conversation-id>"],
|
|
14294
|
+
flags: [["--session ID", "pool agents: that conversation's folder in its box (attachments, what it made) instead of its knowledge files"]],
|
|
14295
|
+
notes: ["The files in YOUR account (what you uploaded and what agents delivered) are ghosty me files, not this.", "--session never wakes a paused box; a recycled box comes back empty."],
|
|
14171
14296
|
learn: `${REF}#archivos`
|
|
14172
14297
|
},
|
|
14173
14298
|
mcp: {
|
|
@@ -14340,7 +14465,8 @@ var HELPS = {
|
|
|
14340
14465
|
"ghosty me files ls [--q TEXT] [--kind image|video|audio|document|other] [--cursor C]",
|
|
14341
14466
|
"ghosty me files get <file-id> [--out FILE]",
|
|
14342
14467
|
"ghosty me files export [--out DIR] [--kind K] [--dry-run]",
|
|
14343
|
-
"ghosty me files upload <file>
|
|
14468
|
+
"ghosty me files upload <file>",
|
|
14469
|
+
"ghosty me files rm <file-id>[,<file-id>\u2026] [--q TEXT] [--dry-run] [--yes]",
|
|
14344
14470
|
'ghosty me memories ls | add "text" | edit <id> "text" | rm <id>',
|
|
14345
14471
|
"ghosty me docs ls [--q TEXT] # your editable docs (id, title, conversation)",
|
|
14346
14472
|
"ghosty me docs get <doc> [--rev N] # an editable doc an agent wrote (/app/docs/<id>): title and versions",
|
|
@@ -14350,18 +14476,19 @@ var HELPS = {
|
|
|
14350
14476
|
"ghosty me docs restore <doc> <n> # version n back as a new version",
|
|
14351
14477
|
"ghosty usage"
|
|
14352
14478
|
],
|
|
14353
|
-
examples: ["ghosty me files ls --kind document", "ghosty me files ls --q contrato", "ghosty me files get <id> --out contrato.pdf", "ghosty me files export --dry-run", "ghosty me files export --out respaldo/ # documents/, conversations/, library/", 'ghosty chat <agent> "res\xFAmelo" --file <id>', 'ghosty me memories add "I prefer short answers"', "ghosty me docs export https://www.ghosty.studio/app/docs/<id> --format md > propuesta.md", "ghosty me docs check <id> --json"],
|
|
14479
|
+
examples: ["ghosty me files ls --kind document", "ghosty me files ls --q contrato", "ghosty me files rm --q cookies --dry-run", "ghosty me files get <id> --out contrato.pdf", "ghosty me files export --dry-run", "ghosty me files export --out respaldo/ # documents/, conversations/, library/", 'ghosty chat <agent> "res\xFAmelo" --file <id>', 'ghosty me memories add "I prefer short answers"', "ghosty me docs export https://www.ghosty.studio/app/docs/<id> --format md > propuesta.md", "ghosty me docs check <id> --json"],
|
|
14354
14480
|
flags: [
|
|
14355
14481
|
["--out DIR", "export: destination folder (default ghosty-archivos); get: file to save"],
|
|
14356
14482
|
["--kind K", "ls, export: only image | video | audio | document | other"],
|
|
14357
|
-
["-n, --dry-run", "export: how many files and MB per folder, without downloading"],
|
|
14483
|
+
["-n, --dry-run", "export: how many files and MB per folder, without downloading; rm: what it would delete"],
|
|
14484
|
+
["--q TEXT", "ls, rm: search by name"],
|
|
14358
14485
|
["--rev N", "docs: work on version N (default: the latest; get lists them)"],
|
|
14359
14486
|
["--format pdf|md", "docs export: PDF (letter size, default) or Markdown"],
|
|
14360
14487
|
["--language L", "docs check: es (default), en-US, pt-BR or fr"],
|
|
14361
14488
|
["--block N / --segment S", "docs tts: the block's position (from 0) and, optionally, the sentence inside it; the output says how many sentences it has"]
|
|
14362
14489
|
],
|
|
14363
14490
|
notes: [
|
|
14364
|
-
"<doc> is the doc's id or the link the agent gives (https://www.ghosty.studio/app/docs/<id>). Not the same as agents docs (files an agent reads) or ghosty docs (opens this documentation).",
|
|
14491
|
+
"<doc> is the doc's id or the link the agent gives (https://www.ghosty.studio/app/docs/<id>). Not the same as agents docs (files an agent reads) or ghosty docs (opens this documentation). me files is your account library; the files inside an agent's machine are ghosty files.",
|
|
14365
14492
|
"Memories travel to your agents only in your personal chats (app, /c, notch), never to WhatsApp, Messenger or demo threads.",
|
|
14366
14493
|
"export brings EVERYTHING in your account: documents, files from your chats and from your agents' channels (what customers sent). A file already downloaded with the same size is skipped, so rerunning it only brings what's new."
|
|
14367
14494
|
],
|
|
@@ -14411,6 +14538,8 @@ HELPS.channels = {
|
|
|
14411
14538
|
"ghosty channels whatsapp disable <agent-id> <number> [--dry-run]",
|
|
14412
14539
|
"ghosty channels whatsapp integrations <agent-id> [--email OTHER] [--orphans]",
|
|
14413
14540
|
"ghosty channels messenger ls <agent-id> [--orphans]",
|
|
14541
|
+
"ghosty channels messenger status <agent-id> # who answers each page, last webhook, token health",
|
|
14542
|
+
"ghosty channels messenger on|off <agent-id> <page> [--take] [--dry-run] [--yes]",
|
|
14414
14543
|
"ghosty channels whatsapp|messenger connect <agent-id> [--no-open] # opens Channels: Meta sign-in in the browser",
|
|
14415
14544
|
"ghosty channels whatsapp link <agent-id> --integration <formmy-id> --answer on|off [--forward] [--dry-run] [--yes]",
|
|
14416
14545
|
"ghosty channels whatsapp unlink <agent-id> --integration <formmy-id> [--dry-run] [--yes]",
|
|
@@ -14432,6 +14561,7 @@ HELPS.channels = {
|
|
|
14432
14561
|
["--text T / --sender N", "groups inject (staff): a message that got lost (group off, deploy) enters as if N wrote it there, and the agent answers in the group"],
|
|
14433
14562
|
["--cached", "groups ls: don't ask WhatsApp for the live list (faster; may miss new groups)"],
|
|
14434
14563
|
["--ads-only on|off", "link: only conversations that come from an ad open a card (also alone, to change it on a linked number)"],
|
|
14564
|
+
["--new-only on|off", "link: the agent answers only contacts writing for the first time from now on; earlier contacts stay in the thread for you (also alone)"],
|
|
14435
14565
|
["--board NAME|agent", "link: this number fills its own sales board (name, id or main) instead of the agent's; the agent's CRM tools work that board on this number's turns. agent = back to the agent's board. Also alone, on a linked number"],
|
|
14436
14566
|
["--list", "history --dry-run: WHICH conversations (phone, name, first message, messages, from an ad)"],
|
|
14437
14567
|
["--only-ads / --phones a,b", "history: import (or list) only the ones from an ad and/or those phones"],
|
|
@@ -14545,7 +14675,8 @@ HELPS.boards = {
|
|
|
14545
14675
|
"ghosty boards assign <agent-id> <board|main>",
|
|
14546
14676
|
"ghosty boards move-cards <workspace> --from <board> --to <board> [--integration ID | --phone N | --all] [--dry-run] [--yes]",
|
|
14547
14677
|
'ghosty boards rm <workspace> "Name" [--yes]',
|
|
14548
|
-
"ghosty boards access <workspace> [board|main] [--add EMAIL] [--remove EMAIL] [--restrict] [--everyone] [--until 2026-10-28|14d]"
|
|
14678
|
+
"ghosty boards access <workspace> [board|main] [--add EMAIL] [--remove EMAIL] [--restrict] [--everyone] [--until 2026-10-28|14d]",
|
|
14679
|
+
"ghosty boards followups <workspace> [board|main] [on|off|template|dry-run] [--steps 2,7,14] [--minutes on|off]"
|
|
14549
14680
|
],
|
|
14550
14681
|
flags: [
|
|
14551
14682
|
["--add / --remove EMAIL", "access: add to or take off the board's \xABonly these people\xBB list (repeatable or comma-separated; only workspace members)"],
|
|
@@ -14556,10 +14687,12 @@ HELPS.boards = {
|
|
|
14556
14687
|
["--from / --to B", "move-cards: boards by name, id or main"],
|
|
14557
14688
|
["--integration ID | --phone N | --all", "move-cards: which cards (a filter is required)"],
|
|
14558
14689
|
["-n, --dry-run", "move-cards: count and show a sample, change nothing"],
|
|
14559
|
-
["--yes", "move-cards: skip the confirmation (required without a terminal)"]
|
|
14690
|
+
["--yes", "move-cards: skip the confirmation (required without a terminal)"],
|
|
14691
|
+
["--steps 2,7,14", "followups: days after the quote for each touch (up to 5)"],
|
|
14692
|
+
["--minutes on|off", "followups (staff): count the steps in minutes, to test without waiting days"]
|
|
14560
14693
|
],
|
|
14561
|
-
examples: ["ghosty boards ls siiqtec", 'ghosty boards create siiqtec "Siiqtec - Ixmiquilpan" --columns-from main', 'ghosty boards assign <agent-id> "Siiqtec - Ixmiquilpan"', 'ghosty boards move-cards siiqtec --from main --to "Siiqtec - Ixmiquilpan" --integration 6a3b02ad\u2026 --dry-run'],
|
|
14562
|
-
notes: ["assign changes where NEW cards go; move-cards brings the existing ones (thread, orders, activity) with a new folio. A card whose conversation is already on the target board is skipped.", "Reading needs membership; changing needs owner or staff of the workspace.", "rm only deletes an empty board that is not the main one.", "access without flags shows who sees the board; owner and staff always see every board. Why someone sees it: ghosty access why <email> --workspace <ws> --board <board>."],
|
|
14694
|
+
examples: ["ghosty boards ls siiqtec", "ghosty boards followups siiqtec main template", "ghosty boards followups siiqtec main on", 'ghosty boards create siiqtec "Siiqtec - Ixmiquilpan" --columns-from main', 'ghosty boards assign <agent-id> "Siiqtec - Ixmiquilpan"', 'ghosty boards move-cards siiqtec --from main --to "Siiqtec - Ixmiquilpan" --integration 6a3b02ad\u2026 --dry-run'],
|
|
14695
|
+
notes: ["assign changes where NEW cards go; move-cards brings the existing ones (thread, orders, activity) with a new folio. A card whose conversation is already on the target board is skipped.", "Reading needs membership; changing needs owner or staff of the workspace.", "rm only deletes an empty board that is not the main one.", "access without flags shows who sees the board; owner and staff always see every board. Why someone sees it: ghosty access why <email> --workspace <ws> --board <board>.", "followups: every quote the agent sends gets up to 3 WhatsApp touches (2, 7 and 14 days) written from the conversation; they stop when the customer replies, pays or someone takes the chat. Outside the 24 h window it uses the utility template (template = submit it to Meta). Changing it takes the same permission as sending campaigns."],
|
|
14563
14696
|
learn: `${REF}#tablero`
|
|
14564
14697
|
};
|
|
14565
14698
|
HELPS.cards = {
|
|
@@ -16906,14 +17039,16 @@ ${bold("Configure an agent")}
|
|
|
16906
17039
|
channels whatsapp|messenger connect <id> Opens the agent's Channels page to connect a number or page with Meta (browser sign-in) [--no-open]
|
|
16907
17040
|
channels whatsapp integrations <id> Its owner's WhatsApp Business numbers in Formmy (number, who answers today, who answered last, Integration id) [--orphans: answered by nobody but with turns]
|
|
16908
17041
|
channels messenger ls <id> The workspace's Messenger pages: who answers now and who answered last [--orphans]
|
|
17042
|
+
channels messenger status <id> Each page: who answers, last webhook and token health (asks Meta now)
|
|
17043
|
+
channels messenger on|off <id> <page> Answer a page with this agent, or stop (off keeps logging to the board) [--take --dry-run --yes]
|
|
16909
17044
|
channels mteams ls|connect|zip|on|off|rm <id> [bot] Microsoft Teams bot (Azure Bot in the client's tenant) [--app-id --tenant --secret|GHOSTY_MTEAMS_SECRET --name --out]
|
|
16910
|
-
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] [--ads-only on|off] [--board NAME|agent] [--dry-run]
|
|
17045
|
+
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] [--ads-only on|off] [--new-only on|off] [--board NAME|agent] [--dry-run]
|
|
16911
17046
|
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 [--list]] [--only-ads] [--phones a,b] [--column NAME]
|
|
16912
17047
|
channels wa import <id> --from easybits:ID | --from-file F | --from-dir DIR --own-number yes|no Move a personal WhatsApp session here, no QR [--dry-run] [--replace] [--no-connect]
|
|
16913
17048
|
channels wa pair <id> --phone N Pair a personal WhatsApp by code (no QR)
|
|
16914
17049
|
channels wa status|pause <id> Personal WhatsApp: how it is / pause it (keeps the session)
|
|
16915
17050
|
channels wa groups ls|enable|disable|set|create <id> [group] Personal WhatsApp groups it answers, and as whom [--role equipo|cliente] [--wake mention|all] [--name "New name"] [--dry-run]
|
|
16916
|
-
boards ls|create|assign|move-cards|access|rm A workspace's boards: create (--columns-from), where an agent sends cards, move cards, who sees each one
|
|
17051
|
+
boards ls|create|assign|move-cards|access|followups|rm A workspace's boards: create (--columns-from), where an agent sends cards, move cards, who sees each one, quote follow-ups
|
|
16917
17052
|
cards ls <id> Recent cards on its sales board [--q TEXT] [--paused] [--limit 20]
|
|
16918
17053
|
cards show <id> <card> A card's thread: who wrote, text, attachments and whether gs kept a copy (folio, phone or id)
|
|
16919
17054
|
cards archive <id> Archive cards in bulk (reversible) --integration X | --column X | --before 30d [--dry-run]
|
|
@@ -16936,7 +17071,7 @@ ${bold("Sites")} ${dim("(sites your agents publish at <slug>.ghosty.page)")}
|
|
|
16936
17071
|
sites pin <slug|id> Pin it: never pauses, no banner ($49 USD/yr or $5/mo, includes up to 3 sites) [--interval year|month] [--currency usd|mxn] [--add-pack: 3 more]
|
|
16937
17072
|
sites unpin <slug|id> Release its slot (back to the Free rule: 30 days + notice)
|
|
16938
17073
|
sites search <slug|id> How Google finds it: clicks, impressions, searches, indexed or not [--days 28]
|
|
16939
|
-
sites domain add|verify|rm <slug> <dom> Custom domain for
|
|
17074
|
+
sites domain add|verify|rm <slug> <dom> Custom domain for a site or app (DNS record \u2192 verify \u2192 HTTPS) [ls <slug>: list]
|
|
16940
17075
|
sites pins Your pinned-sites subscription and slots [--cancel | --resume]
|
|
16941
17076
|
sites publish <file.html|folder> Publish a house site (staff): no owner, no expiry, no banner [--title] [--slug S] [--site <slug|id>: update that one]
|
|
16942
17077
|
sites slug <slug|id> <new-slug> Change a site's slug (staff)
|
|
@@ -17122,14 +17257,16 @@ ${bold("Configurar un agente")}
|
|
|
17122
17257
|
channels whatsapp|messenger connect <id> Abre la p\xE1gina Canales del agente para conectar un n\xFAmero o una p\xE1gina con Meta (inicio de sesi\xF3n en el navegador) [--no-open]
|
|
17123
17258
|
channels whatsapp integrations <id> Los n\xFAmeros de WhatsApp Business de su due\xF1o en Formmy (n\xFAmero, qui\xE9n contesta hoy, qui\xE9n contest\xF3 al \xFAltimo, id de la Integration) [--orphans: sin qui\xE9n conteste pero con turnos]
|
|
17124
17259
|
channels messenger ls <id> Las p\xE1ginas de Messenger del espacio: qui\xE9n contesta hoy y qui\xE9n contest\xF3 al \xFAltimo [--orphans]
|
|
17260
|
+
channels messenger status <id> Cada p\xE1gina: qui\xE9n contesta, \xFAltimo webhook y salud del token (le pregunta a Meta en el momento)
|
|
17261
|
+
channels messenger on|off <id> <p\xE1gina> Que este agente conteste una p\xE1gina, o que deje de hacerlo (off sigue registrando en el tablero) [--take --dry-run --yes]
|
|
17125
17262
|
channels mteams ls|connect|zip|on|off|rm <id> [bot] Bot de Microsoft Teams (Azure Bot en el tenant del cliente) [--app-id --tenant --secret|GHOSTY_MTEAMS_SECRET --name --out]
|
|
17126
|
-
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] [--ads-only on|off] [--board NOMBRE|agent] [--dry-run]
|
|
17263
|
+
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] [--ads-only on|off] [--new-only on|off] [--board NOMBRE|agent] [--dry-run]
|
|
17127
17264
|
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 [--list]] [--only-ads] [--phones a,b] [--column NOMBRE]
|
|
17128
17265
|
channels wa import <id> --from easybits: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]
|
|
17129
17266
|
channels wa pair <id> --phone N Vincula un WhatsApp personal con c\xF3digo (sin QR)
|
|
17130
17267
|
channels wa status|pause <id> WhatsApp personal: c\xF3mo est\xE1 / pausarlo (conserva la sesi\xF3n)
|
|
17131
17268
|
channels wa groups ls|enable|disable|set|create <id> [grupo] Grupos de WhatsApp personal que contesta, y como qui\xE9n [--role equipo|cliente] [--wake mention|all] [--name "Nombre nuevo"] [--dry-run]
|
|
17132
|
-
boards ls|create|assign|move-cards|access|rm Los tableros de un espacio: crear (--columns-from), a d\xF3nde manda tarjetas un agente, mover tarjetas, qui\xE9n ve cada uno
|
|
17269
|
+
boards ls|create|assign|move-cards|access|followups|rm Los tableros de un espacio: crear (--columns-from), a d\xF3nde manda tarjetas un agente, mover tarjetas, qui\xE9n ve cada uno, seguimiento de cotizaciones
|
|
17133
17270
|
cards ls <id> Tarjetas recientes de su tablero de ventas [--q TEXTO] [--paused] [--limit 20]
|
|
17134
17271
|
cards show <id> <tarjeta> El hilo de una tarjeta: qui\xE9n escribi\xF3, texto, adjuntos y si gs guard\xF3 copia (folio, tel\xE9fono o id)
|
|
17135
17272
|
cards archive <id> Archiva tarjetas en bloque (reversible) --integration X | --column X | --before 30d [--dry-run]
|
|
@@ -17152,7 +17289,7 @@ ${bold("Sitios")} ${dim("(los sitios que publican tus agentes en <slug>.ghosty.
|
|
|
17152
17289
|
sites pin <slug|id> F\xEDjalo: no se pausa y va sin franja ($49 USD al a\xF1o o $5 al mes, incluye hasta 3 sitios) [--interval year|month] [--currency usd|mxn] [--add-pack: 3 m\xE1s]
|
|
17153
17290
|
sites unpin <slug|id> Suelta su lugar (vuelve a la regla Gratis: 30 d\xEDas + aviso)
|
|
17154
17291
|
sites pins Tu suscripci\xF3n de Sitios fijos y sus lugares [--cancel | --resume]
|
|
17155
|
-
sites domain add|verify|rm <slug> <dom> Dominio propio para
|
|
17292
|
+
sites domain add|verify|rm <slug> <dom> Dominio propio para un sitio o app (registro DNS \u2192 verificar \u2192 HTTPS) [ls <slug>: lista]
|
|
17156
17293
|
sites publish <archivo.html|carpeta> Publica un sitio de la casa (staff): sin due\xF1o, sin caducidad, sin franja [--title] [--slug S] [--site <slug|id>: actualiza \xE9se]
|
|
17157
17294
|
sites slug <slug|id> <slug-nuevo> Cambia el slug de un sitio (staff)
|
|
17158
17295
|
|
|
@@ -17370,6 +17507,7 @@ async function main() {
|
|
|
17370
17507
|
"key-column": { type: "string" },
|
|
17371
17508
|
"unset-env": { type: "string" },
|
|
17372
17509
|
orphans: { type: "boolean" },
|
|
17510
|
+
take: { type: "boolean" },
|
|
17373
17511
|
skipped: { type: "boolean" },
|
|
17374
17512
|
paused: { type: "boolean" },
|
|
17375
17513
|
email: { type: "string" },
|
|
@@ -17386,6 +17524,8 @@ async function main() {
|
|
|
17386
17524
|
enable: { type: "string" },
|
|
17387
17525
|
disable: { type: "string" },
|
|
17388
17526
|
"columns-from": { type: "string" },
|
|
17527
|
+
steps: { type: "string" },
|
|
17528
|
+
minutes: { type: "string" },
|
|
17389
17529
|
add: { type: "string", multiple: true },
|
|
17390
17530
|
remove: { type: "string", multiple: true },
|
|
17391
17531
|
everyone: { type: "boolean" },
|
|
@@ -17503,6 +17643,7 @@ async function main() {
|
|
|
17503
17643
|
publish: { type: "boolean" },
|
|
17504
17644
|
draft: { type: "boolean" },
|
|
17505
17645
|
"ads-only": { type: "string" },
|
|
17646
|
+
"new-only": { type: "string" },
|
|
17506
17647
|
"partner-tenant": { type: "string" },
|
|
17507
17648
|
ref: { type: "string", multiple: true },
|
|
17508
17649
|
"style-ref": { type: "string", multiple: true },
|
|
@@ -17545,7 +17686,7 @@ async function main() {
|
|
|
17545
17686
|
const tokens = splitTokens(pos, v);
|
|
17546
17687
|
globals.token = tokens.session;
|
|
17547
17688
|
globals.profile = v.profile;
|
|
17548
|
-
if (v.version) return out({ version: "0.
|
|
17689
|
+
if (v.version) return out({ version: "0.88.0" }, () => "0.88.0");
|
|
17549
17690
|
for (const n of normalizeArgs(pos, v)) if (!globals.json) info(dim(n));
|
|
17550
17691
|
const context = (await readConfig()).workspace;
|
|
17551
17692
|
if (!explicitToken()?.startsWith("gat_")) {
|
|
@@ -17652,7 +17793,7 @@ async function main() {
|
|
|
17652
17793
|
case "board":
|
|
17653
17794
|
return boardCmd(sub, a1, v, a2);
|
|
17654
17795
|
case "boards":
|
|
17655
|
-
return boardsCmd(sub, a1, a2, v, context);
|
|
17796
|
+
return boardsCmd(sub, a1, a2, v, context, pos[4]);
|
|
17656
17797
|
case "channels":
|
|
17657
17798
|
if (a1 === "connect" && ["whatsapp", "wa", "messenger", "fb"].includes(sub ?? "")) {
|
|
17658
17799
|
if (!a2) throw usage(t("Missing agent id.", "Falta el id del agente."), `ghosty channels ${sub} connect <agent-id>`);
|
|
@@ -17733,7 +17874,7 @@ async function main() {
|
|
|
17733
17874
|
}
|
|
17734
17875
|
return planCmd(sub, a1, v, context);
|
|
17735
17876
|
case "me":
|
|
17736
|
-
if (sub === "files") return meFiles(a1, a2, v);
|
|
17877
|
+
if (sub === "files") return meFiles(a1, a2, v, pos.slice(4));
|
|
17737
17878
|
if (sub === "memories" || sub === "memory") return meMemories(a1, a2, pos[4]);
|
|
17738
17879
|
if (sub === "usage") return meUsage();
|
|
17739
17880
|
if (sub === "docs") return meDocs(a1, a2, pos[4], v);
|
|
@@ -17748,10 +17889,10 @@ async function main() {
|
|
|
17748
17889
|
throw unknown(cmd, cmd, COMMANDS, "ghosty --help");
|
|
17749
17890
|
}
|
|
17750
17891
|
}
|
|
17751
|
-
var notice = updateNotice("0.
|
|
17892
|
+
var notice = updateNotice("0.88.0").catch(() => null);
|
|
17752
17893
|
main().then(
|
|
17753
17894
|
async () => {
|
|
17754
|
-
const n = await notice;
|
|
17895
|
+
const n = process.stderr.isTTY ? await notice : null;
|
|
17755
17896
|
if (n) info(dim(n));
|
|
17756
17897
|
return exitAfterFlush(EXIT.OK);
|
|
17757
17898
|
},
|
package/package.json
CHANGED