@ghostystudio/cli 0.87.0 → 0.89.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 +8 -0
- package/dist/index.js +123 -39
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -153,6 +153,14 @@ Every command has its own help with examples: `ghosty <command> --help` or `ghos
|
|
|
153
153
|
|
|
154
154
|
Exit codes: `0` ok · `1` API error · `2` usage error · `3` not signed in.
|
|
155
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
|
+
|
|
156
164
|
### Changes in 0.86.0
|
|
157
165
|
|
|
158
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.89.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.89.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.89.0"}`
|
|
11241
11282
|
},
|
|
11242
11283
|
body: method === "GET" ? void 0 : raw
|
|
11243
11284
|
});
|
|
@@ -12085,6 +12126,8 @@ 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
|
+
contact_off: ["the agent is turned off for this contact (whatsapp contacts --off)", "el agente est\xE1 apagado para este contacto (whatsapp contacts --off)"],
|
|
12130
|
+
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
12131
|
partner_token: ["partner did not issue a turn token", "el partner no dio token de turno"],
|
|
12089
12132
|
partner_busy: ["partner at its concurrent-turn limit", "el partner lleg\xF3 a su tope de turnos a la vez"]
|
|
12090
12133
|
};
|
|
@@ -12588,6 +12631,7 @@ async function channelsCmd(kind, sub, id, ref, v) {
|
|
|
12588
12631
|
if (!id) throw usage(t("Missing agent id.", "Falta el id del agente."), t("Example", "Ejemplo") + ": ghosty channels whatsapp ls <agent-id>");
|
|
12589
12632
|
if (sub === "link" || sub === "unlink") return linkCmd(sub, id, v);
|
|
12590
12633
|
if (sub === "history") return historyCmd(id, v);
|
|
12634
|
+
if (sub === "contacts") return contactsCmd(id, v);
|
|
12591
12635
|
if (sub === "integrations" && v.orphans) return linksCmd(id, "waba", true);
|
|
12592
12636
|
if (sub === "integrations") return integrationsCmd(id, v.email);
|
|
12593
12637
|
if (sub === "import" || sub === "status" || sub === "pause" || sub === "pair") return waCmd2(sub, id, v);
|
|
@@ -12602,10 +12646,11 @@ async function channelsCmd(kind, sub, id, ref, v) {
|
|
|
12602
12646
|
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)")),
|
|
12603
12647
|
"formmy forwards": fwd(n2),
|
|
12604
12648
|
"ads only": n2.adsOnly ? lavender(t("yes", "s\xED")) : "",
|
|
12649
|
+
"new only": n2.newOnly ? lavender(t("yes", "s\xED")) : "",
|
|
12605
12650
|
board: n2.board ? n2.board.name : dim(t("the agent's", "el del agente")),
|
|
12606
12651
|
id: n2.id
|
|
12607
12652
|
}));
|
|
12608
|
-
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}`)));
|
|
12653
|
+
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}`)));
|
|
12609
12654
|
}
|
|
12610
12655
|
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");
|
|
12611
12656
|
if (!ref) throw usage(t("Missing number.", "Falta el n\xFAmero."), t("Example", "Ejemplo") + `: ghosty channels whatsapp ${sub} ${id} +52 55 1234 5678`);
|
|
@@ -12768,21 +12813,24 @@ async function linkCmd(sub, id, v) {
|
|
|
12768
12813
|
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`);
|
|
12769
12814
|
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."));
|
|
12770
12815
|
const adsOnly = v["ads-only"] === void 0 ? void 0 : v["ads-only"] === "on";
|
|
12816
|
+
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."));
|
|
12817
|
+
const newOnly = v["new-only"] === void 0 ? void 0 : v["new-only"] === "on";
|
|
12818
|
+
const newOnlyFlag = v["new-only"] ? ` --new-only ${v["new-only"]}` : "";
|
|
12771
12819
|
const board = v.board === void 0 ? void 0 : /^(agent|agente)$/i.test(v.board) ? null : v.board;
|
|
12772
12820
|
const boardFlag = v.board !== void 0 ? ` --board ${JSON.stringify(v.board)}` : "";
|
|
12773
|
-
if (sub === "link" && (adsOnly !== void 0 || board !== void 0) && v.answer === void 0) {
|
|
12821
|
+
if (sub === "link" && (adsOnly !== void 0 || newOnly !== void 0 || board !== void 0) && v.answer === void 0) {
|
|
12774
12822
|
const path3 = `/api/v2/agents/${enc11(id)}/whatsapp/link`;
|
|
12775
|
-
const r2 = await api(path3, { method: "POST", body: { integrationId: v.integration, ...adsOnly !== void 0 ? { adsOnly } : {}, ...board !== void 0 ? { board } : {}, ...v["dry-run"] ? { dryRun: true } : {} } });
|
|
12823
|
+
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 } : {} } });
|
|
12776
12824
|
return out(r2, () => `${r2.plan.map((x) => serverText(x)).join("\n")}${v["dry-run"] ? `
|
|
12777
|
-
${dim(applyHint(`ghosty channels whatsapp link ${id} --integration ${v.integration}${v["ads-only"] ? ` --ads-only ${v["ads-only"]}` : ""}${boardFlag}`))}` : ""}`);
|
|
12825
|
+
${dim(applyHint(`ghosty channels whatsapp link ${id} --integration ${v.integration}${v["ads-only"] ? ` --ads-only ${v["ads-only"]}` : ""}${newOnlyFlag}${boardFlag}`))}` : ""}`);
|
|
12778
12826
|
}
|
|
12779
12827
|
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)"));
|
|
12780
12828
|
const path2 = `/api/v2/agents/${enc11(id)}/whatsapp/link`;
|
|
12781
12829
|
if (v["partner-agent"] && !v["partner-tenant"]) throw usage(t("--partner-agent needs --partner-tenant <externalId>.", "--partner-agent necesita --partner-tenant <externalId>."));
|
|
12782
12830
|
const partner = v["partner-tenant"] ? { partnerExternalId: v["partner-tenant"], ...v["partner-agent"] ? { partnerAgentSlug: v["partner-agent"] } : {} } : {};
|
|
12783
|
-
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 } };
|
|
12831
|
+
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 } };
|
|
12784
12832
|
const partnerFlags = v["partner-tenant"] ? ` --partner-tenant ${v["partner-tenant"]}${v["partner-agent"] ? ` --partner-agent ${v["partner-agent"]}` : ""}` : "";
|
|
12785
|
-
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}` : ""}`;
|
|
12833
|
+
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}` : ""}`;
|
|
12786
12834
|
const plan = await api(path2, { method: "POST", body: { ...body, dryRun: true } });
|
|
12787
12835
|
const text = `${plan.plan.map((x) => serverText(x)).join("\n")}
|
|
12788
12836
|
${dim(`Formmy: ${plan.formmy.before.url ?? "null"} \u2192 ${plan.formmy.after.url ?? "null"} (${t("as", "como")} ${plan.formmy.by})`)}`;
|
|
@@ -12951,6 +12999,23 @@ ${t(`Cards appear on the team's board (no "new lead" alerts).`, "Las tarjetas ap
|
|
|
12951
12999
|
}
|
|
12952
13000
|
out({ ...sum, conversations: done }, () => `${lavender("\u2713")} ${t(`${sum.newCards} new card(s), ${sum.updatedCards} updated, ${sum.messages} message(s), ${sum.orders} order(s)`, `${sum.newCards} tarjeta(s) nueva(s), ${sum.updatedCards} actualizada(s), ${sum.messages} mensaje(s), ${sum.orders} pedido(s)`)}${files(sum)}${sum.suppressed ? dim(t(` \xB7 ${sum.suppressed} bot draft(s) the customer never saw, skipped`, ` \xB7 se omitieron ${sum.suppressed} borrador(es) del bot que el cliente nunca vio`)) : ""}${pausedLine(sum, id)}`);
|
|
12953
13001
|
}
|
|
13002
|
+
async function contactsCmd(id, v) {
|
|
13003
|
+
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 contacts <agent-id> --integration <id> [--new-only on|off] [--mute PHONE] [--unmute PHONE] [--seed]");
|
|
13004
|
+
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."));
|
|
13005
|
+
if (v.mute && v.unmute) throw usage(t("Use --mute or --unmute, not both.", "Usa --mute o --unmute, no los dos."));
|
|
13006
|
+
const path2 = `/api/v2/agents/${enc11(id)}/whatsapp/contacts`;
|
|
13007
|
+
const contact = v.mute ?? v.unmute;
|
|
13008
|
+
const write = v["new-only"] !== void 0 || contact !== void 0 || v.seed;
|
|
13009
|
+
const r = write ? await api(path2, { method: "POST", body: { number: v.integration, ...v["new-only"] !== void 0 ? { newOnly: v["new-only"] === "on" } : {}, ...contact !== void 0 ? { contact, off: !!v.mute } : {}, ...v.seed ? { seed: true } : {} } }) : await api(`${path2}?number=${enc11(v.integration)}`);
|
|
13010
|
+
return out(r, () => {
|
|
13011
|
+
const lines = [
|
|
13012
|
+
`${t("New contacts only", "S\xF3lo contactos nuevos")}: ${r.newOnly ? lavender(t("on", "encendido")) : t("off", "apagado")}${r.newSince ? dim(` (${t("since", "desde")} ${r.newSince})`) : ""}`,
|
|
13013
|
+
`${t("Known contacts", "Contactos conocidos")}: ${r.known}${r.seededAt ? dim(` (${t("brought from Formmy", "tra\xEDdos de Formmy")} ${r.seededAt})`) : dim(t(" (still waiting for the WhatsApp history)", " (todav\xEDa esperando el historial de WhatsApp)"))}`,
|
|
13014
|
+
`${t("Contacts without the agent", "Contactos sin agente")}: ${r.off.length ? r.off.map((o) => o.name ? `${o.name} ${o.key}` : o.key).join(", ") : dim("\u2014")}`
|
|
13015
|
+
];
|
|
13016
|
+
return lines.join("\n");
|
|
13017
|
+
});
|
|
13018
|
+
}
|
|
12954
13019
|
|
|
12955
13020
|
// src/commands/board.ts
|
|
12956
13021
|
init_api();
|
|
@@ -13443,7 +13508,11 @@ var HELPS_ES = {
|
|
|
13443
13508
|
],
|
|
13444
13509
|
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."]
|
|
13445
13510
|
},
|
|
13446
|
-
files: {
|
|
13511
|
+
files: {
|
|
13512
|
+
summary: "Archivos en la m\xE1quina del agente.",
|
|
13513
|
+
flags: [["--session ID", "agentes de pool: la carpeta de esa conversaci\xF3n en su caja (adjuntos, lo que hizo) en vez de su conocimiento"]],
|
|
13514
|
+
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."]
|
|
13515
|
+
},
|
|
13447
13516
|
mcp: {
|
|
13448
13517
|
summary: "Servidores MCP de un agente. set REEMPLAZA la lista entera; connect agrega uno remoto con su URL.",
|
|
13449
13518
|
replace: [["# one remote server, OAuth in the browser if it asks", "# un servidor remoto; OAuth en el navegador si lo pide"]],
|
|
@@ -13575,14 +13644,15 @@ var HELPS_ES = {
|
|
|
13575
13644
|
flags: [
|
|
13576
13645
|
["--out DIR", "export: carpeta destino (default ghosty-archivos); get: archivo donde guardarlo"],
|
|
13577
13646
|
["--kind K", "ls, export: s\xF3lo image | video | audio | document | other"],
|
|
13578
|
-
["-n, --dry-run", "export: cu\xE1ntos archivos y MB por carpeta, sin bajar nada"],
|
|
13647
|
+
["-n, --dry-run", "export: cu\xE1ntos archivos y MB por carpeta, sin bajar nada; rm: qu\xE9 borrar\xEDa"],
|
|
13648
|
+
["--q TEXT", "ls, rm: busca por nombre"],
|
|
13579
13649
|
["--rev N", "docs: trabaja sobre la versi\xF3n N (default: la \xFAltima; get las lista)"],
|
|
13580
13650
|
["--format pdf|md", "docs export: PDF (tama\xF1o carta, default) o Markdown"],
|
|
13581
13651
|
["--language L", "docs check: es (default), en-US, pt-BR o fr"],
|
|
13582
13652
|
["--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"]
|
|
13583
13653
|
],
|
|
13584
13654
|
notes: [
|
|
13585
|
-
"<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).",
|
|
13655
|
+
"<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.",
|
|
13586
13656
|
"Las memorias viajan a tus agentes s\xF3lo en tus chats personales (app, /c, notch), nunca a WhatsApp, Messenger ni demos.",
|
|
13587
13657
|
"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."
|
|
13588
13658
|
]
|
|
@@ -13618,6 +13688,8 @@ var HELPS_ES = {
|
|
|
13618
13688
|
["--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"],
|
|
13619
13689
|
["--cached", "groups ls: no le pide a WhatsApp la lista en vivo (m\xE1s r\xE1pido; puede faltar alg\xFAn grupo nuevo)"],
|
|
13620
13690
|
["--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)"],
|
|
13691
|
+
["--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)"],
|
|
13692
|
+
["contacts <id> --integration ID", "s\xF3lo contactos nuevos y contactos sin agente: --new-only on|off, --mute TEL (el agente nunca le contesta a ese n\xFAmero), --unmute TEL, --seed (vuelve a traer los contactos conocidos de WhatsApp)"],
|
|
13621
13693
|
["--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"],
|
|
13622
13694
|
["--list", "history --dry-run: CU\xC1LES conversaciones (tel\xE9fono, nombre, primer mensaje, mensajes, si vino de anuncio)"],
|
|
13623
13695
|
["--only-ads / --phones a,b", "history: importa (o lista) s\xF3lo las de anuncio y/o esos tel\xE9fonos"],
|
|
@@ -14237,8 +14309,10 @@ var HELPS = {
|
|
|
14237
14309
|
},
|
|
14238
14310
|
files: {
|
|
14239
14311
|
summary: "Files in the agent's machine.",
|
|
14240
|
-
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]"],
|
|
14241
|
-
examples: ["ghosty files put <id> knowledge/precios.md --file precios.md", "cat notas.md | ghosty files put <id> notas.md"],
|
|
14312
|
+
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>]"],
|
|
14313
|
+
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>"],
|
|
14314
|
+
flags: [["--session ID", "pool agents: that conversation's folder in its box (attachments, what it made) instead of its knowledge files"]],
|
|
14315
|
+
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."],
|
|
14242
14316
|
learn: `${REF}#archivos`
|
|
14243
14317
|
},
|
|
14244
14318
|
mcp: {
|
|
@@ -14411,7 +14485,8 @@ var HELPS = {
|
|
|
14411
14485
|
"ghosty me files ls [--q TEXT] [--kind image|video|audio|document|other] [--cursor C]",
|
|
14412
14486
|
"ghosty me files get <file-id> [--out FILE]",
|
|
14413
14487
|
"ghosty me files export [--out DIR] [--kind K] [--dry-run]",
|
|
14414
|
-
"ghosty me files upload <file>
|
|
14488
|
+
"ghosty me files upload <file>",
|
|
14489
|
+
"ghosty me files rm <file-id>[,<file-id>\u2026] [--q TEXT] [--dry-run] [--yes]",
|
|
14415
14490
|
'ghosty me memories ls | add "text" | edit <id> "text" | rm <id>',
|
|
14416
14491
|
"ghosty me docs ls [--q TEXT] # your editable docs (id, title, conversation)",
|
|
14417
14492
|
"ghosty me docs get <doc> [--rev N] # an editable doc an agent wrote (/app/docs/<id>): title and versions",
|
|
@@ -14421,18 +14496,19 @@ var HELPS = {
|
|
|
14421
14496
|
"ghosty me docs restore <doc> <n> # version n back as a new version",
|
|
14422
14497
|
"ghosty usage"
|
|
14423
14498
|
],
|
|
14424
|
-
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"],
|
|
14499
|
+
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"],
|
|
14425
14500
|
flags: [
|
|
14426
14501
|
["--out DIR", "export: destination folder (default ghosty-archivos); get: file to save"],
|
|
14427
14502
|
["--kind K", "ls, export: only image | video | audio | document | other"],
|
|
14428
|
-
["-n, --dry-run", "export: how many files and MB per folder, without downloading"],
|
|
14503
|
+
["-n, --dry-run", "export: how many files and MB per folder, without downloading; rm: what it would delete"],
|
|
14504
|
+
["--q TEXT", "ls, rm: search by name"],
|
|
14429
14505
|
["--rev N", "docs: work on version N (default: the latest; get lists them)"],
|
|
14430
14506
|
["--format pdf|md", "docs export: PDF (letter size, default) or Markdown"],
|
|
14431
14507
|
["--language L", "docs check: es (default), en-US, pt-BR or fr"],
|
|
14432
14508
|
["--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"]
|
|
14433
14509
|
],
|
|
14434
14510
|
notes: [
|
|
14435
|
-
"<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).",
|
|
14511
|
+
"<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.",
|
|
14436
14512
|
"Memories travel to your agents only in your personal chats (app, /c, notch), never to WhatsApp, Messenger or demo threads.",
|
|
14437
14513
|
"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."
|
|
14438
14514
|
],
|
|
@@ -14505,6 +14581,8 @@ HELPS.channels = {
|
|
|
14505
14581
|
["--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"],
|
|
14506
14582
|
["--cached", "groups ls: don't ask WhatsApp for the live list (faster; may miss new groups)"],
|
|
14507
14583
|
["--ads-only on|off", "link: only conversations that come from an ad open a card (also alone, to change it on a linked number)"],
|
|
14584
|
+
["--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)"],
|
|
14585
|
+
["contacts <id> --integration ID", "new contacts only and contacts without the agent: --new-only on|off, --mute PHONE (the agent never answers that number), --unmute PHONE, --seed (bring the known contacts from WhatsApp again)"],
|
|
14508
14586
|
["--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"],
|
|
14509
14587
|
["--list", "history --dry-run: WHICH conversations (phone, name, first message, messages, from an ad)"],
|
|
14510
14588
|
["--only-ads / --phones a,b", "history: import (or list) only the ones from an ad and/or those phones"],
|
|
@@ -16985,7 +17063,8 @@ ${bold("Configure an agent")}
|
|
|
16985
17063
|
channels messenger status <id> Each page: who answers, last webhook and token health (asks Meta now)
|
|
16986
17064
|
channels messenger on|off <id> <page> Answer a page with this agent, or stop (off keeps logging to the board) [--take --dry-run --yes]
|
|
16987
17065
|
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]
|
|
16988
|
-
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]
|
|
17066
|
+
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]
|
|
17067
|
+
channels whatsapp contacts <id> --integration ID New contacts only (the agent skips the people you already talk to) and contacts without the agent [--new-only on|off] [--mute PHONE] [--unmute PHONE] [--seed]
|
|
16989
17068
|
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]
|
|
16990
17069
|
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]
|
|
16991
17070
|
channels wa pair <id> --phone N Pair a personal WhatsApp by code (no QR)
|
|
@@ -17203,7 +17282,8 @@ ${bold("Configurar un agente")}
|
|
|
17203
17282
|
channels messenger status <id> Cada p\xE1gina: qui\xE9n contesta, \xFAltimo webhook y salud del token (le pregunta a Meta en el momento)
|
|
17204
17283
|
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]
|
|
17205
17284
|
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]
|
|
17206
|
-
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]
|
|
17285
|
+
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]
|
|
17286
|
+
channels whatsapp contacts <id> --integration ID S\xF3lo contactos nuevos (el agente no le contesta a tu gente de siempre) y contactos sin agente [--new-only on|off] [--mute TEL] [--unmute TEL] [--seed]
|
|
17207
17287
|
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]
|
|
17208
17288
|
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]
|
|
17209
17289
|
channels wa pair <id> --phone N Vincula un WhatsApp personal con c\xF3digo (sin QR)
|
|
@@ -17586,6 +17666,10 @@ async function main() {
|
|
|
17586
17666
|
publish: { type: "boolean" },
|
|
17587
17667
|
draft: { type: "boolean" },
|
|
17588
17668
|
"ads-only": { type: "string" },
|
|
17669
|
+
"new-only": { type: "string" },
|
|
17670
|
+
mute: { type: "string" },
|
|
17671
|
+
unmute: { type: "string" },
|
|
17672
|
+
seed: { type: "boolean" },
|
|
17589
17673
|
"partner-tenant": { type: "string" },
|
|
17590
17674
|
ref: { type: "string", multiple: true },
|
|
17591
17675
|
"style-ref": { type: "string", multiple: true },
|
|
@@ -17628,7 +17712,7 @@ async function main() {
|
|
|
17628
17712
|
const tokens = splitTokens(pos, v);
|
|
17629
17713
|
globals.token = tokens.session;
|
|
17630
17714
|
globals.profile = v.profile;
|
|
17631
|
-
if (v.version) return out({ version: "0.
|
|
17715
|
+
if (v.version) return out({ version: "0.89.0" }, () => "0.89.0");
|
|
17632
17716
|
for (const n of normalizeArgs(pos, v)) if (!globals.json) info(dim(n));
|
|
17633
17717
|
const context = (await readConfig()).workspace;
|
|
17634
17718
|
if (!explicitToken()?.startsWith("gat_")) {
|
|
@@ -17816,7 +17900,7 @@ async function main() {
|
|
|
17816
17900
|
}
|
|
17817
17901
|
return planCmd(sub, a1, v, context);
|
|
17818
17902
|
case "me":
|
|
17819
|
-
if (sub === "files") return meFiles(a1, a2, v);
|
|
17903
|
+
if (sub === "files") return meFiles(a1, a2, v, pos.slice(4));
|
|
17820
17904
|
if (sub === "memories" || sub === "memory") return meMemories(a1, a2, pos[4]);
|
|
17821
17905
|
if (sub === "usage") return meUsage();
|
|
17822
17906
|
if (sub === "docs") return meDocs(a1, a2, pos[4], v);
|
|
@@ -17831,10 +17915,10 @@ async function main() {
|
|
|
17831
17915
|
throw unknown(cmd, cmd, COMMANDS, "ghosty --help");
|
|
17832
17916
|
}
|
|
17833
17917
|
}
|
|
17834
|
-
var notice = updateNotice("0.
|
|
17918
|
+
var notice = updateNotice("0.89.0").catch(() => null);
|
|
17835
17919
|
main().then(
|
|
17836
17920
|
async () => {
|
|
17837
|
-
const n = await notice;
|
|
17921
|
+
const n = process.stderr.isTTY ? await notice : null;
|
|
17838
17922
|
if (n) info(dim(n));
|
|
17839
17923
|
return exitAfterFlush(EXIT.OK);
|
|
17840
17924
|
},
|
package/package.json
CHANGED