@ghostystudio/cli 0.32.2 → 0.33.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 +12 -2
- package/dist/index.js +140 -35
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -53,7 +53,7 @@ ghosty channels whatsapp link|unlink <id> --integration ID --answer on|off [--
|
|
|
53
53
|
ghosty channels whatsapp history <id> --integration ID [--days 7] [--dry-run] # its recent cards, messages and orders (no duplicates)
|
|
54
54
|
ghosty channels wa import <id> --from-file F | --from-dir DIR --own-number yes|no [--dry-run] # personal WhatsApp session, no QR
|
|
55
55
|
ghosty channels wa pair|status|pause <id> [--phone N]
|
|
56
|
-
ghosty channels wa groups ls|enable|disable|set|create <id> [group] [--role equipo|cliente] [--dry-run] # personal WhatsApp groups
|
|
56
|
+
ghosty channels wa groups ls|enable|disable|set|create <id> [group] [--role equipo|cliente] [--wake mention|all] [--dry-run] # personal WhatsApp groups
|
|
57
57
|
ghosty boards ls|create|assign|move-cards|rm <workspace|agent> … [--columns-from main] [--from A --to B --integration X --dry-run]
|
|
58
58
|
ghosty cards ls <id> [--q TEXT] [--limit 20] # recent cards on its sales board
|
|
59
59
|
ghosty cards show <id> <folio|phone|id> [--limit 20] # a card's thread, attachments and whether gs kept a copy
|
|
@@ -77,8 +77,9 @@ ghosty me memories ls|add|edit|rm
|
|
|
77
77
|
ghosty conversations archive|rm <id> --kind test|channel [--before 7d] [--dry-run]
|
|
78
78
|
ghosty agents docs dedupe [--dry-run] # your account: merge documents with the same content
|
|
79
79
|
# Ghosty staff
|
|
80
|
-
ghosty conversations ls --user EMAIL [--since 30d] · conversations show <id> <conv> --why
|
|
80
|
+
ghosty conversations ls --user EMAIL [--since 30d] · conversations show <id> <conv> --why|--tools
|
|
81
81
|
ghosty turns stats [--since 14d] [--all] · push log --user EMAIL [--since 2d]
|
|
82
|
+
ghosty agents doctor --user EMAIL [agent] [--turns 8] · usage --images [--plan free] [--by user]
|
|
82
83
|
ghosty novedades ls|create|set|test|send|rm [--title] [--body] [--image FILE] [--link] [--to] [--publish] [--at]
|
|
83
84
|
ghosty credentials ls|set|rm [provider] [--from-env VAR] # never as an argument
|
|
84
85
|
ghosty docs
|
|
@@ -96,6 +97,15 @@ Every command has its own help with examples: `ghosty <command> --help` or `ghos
|
|
|
96
97
|
|
|
97
98
|
Exit codes: `0` ok · `1` API error · `2` usage error · `3` not signed in.
|
|
98
99
|
|
|
100
|
+
### Changes in 0.33.0
|
|
101
|
+
|
|
102
|
+
- Staff: `agents doctor --user EMAIL` (their agents: doctor, latest turns and whether the app picked up each answer, plan/bag, boxes with duplicates flagged), `usage --images [--plan free] [--by user]` (image charges vs deliveries, real cost vs the table), `conversations show <agent> <conv> --tools` (every tool it ran, secrets masked, and the external domains it named).
|
|
103
|
+
- `agents ls --user --app` also lists their phones with the app build. `novedades ls` shows how many FCM delivered and rejected; announcement pushes show up in `push log`.
|
|
104
|
+
- `novedades create|set --min-version N`: if the app is older, its button reads «Actualizar». `turns stats` breaks down by channel (Teams and A2A included).
|
|
105
|
+
- `schedule add --conversation wa:<jid>` answers in that WhatsApp group/chat. `channels wa pair` warns that in Mexico `52…` and `521…` are different accounts.
|
|
106
|
+
- `channels wa groups set <agent> <group> --wake mention|all`: in `mention` (default when you turn a group on) the agent only answers when @-mentioned, named or replied to; the rest reaches it as context. `ls` shows the column.
|
|
107
|
+
- `channels wa groups enable|disable` now work (they were in the help but got rejected).
|
|
108
|
+
|
|
99
109
|
### Changes in 0.31.0
|
|
100
110
|
|
|
101
111
|
- `spaces ls [--user EMAIL]`; staff: `spaces join|leave <workspace>` and `spaces set <workspace> --combo sales` (`workspaces` is an alias). `boards create` warns when the workspace has no Sales (nobody would see the board).
|
package/dist/index.js
CHANGED
|
@@ -8367,7 +8367,7 @@ async function postLong(path, body, timeoutMs = 9e5) {
|
|
|
8367
8367
|
const req = httpsRequest(`${base}${path}`, {
|
|
8368
8368
|
method: "POST",
|
|
8369
8369
|
timeout: timeoutMs,
|
|
8370
|
-
headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.
|
|
8370
|
+
headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.33.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
|
|
8371
8371
|
}, (res) => {
|
|
8372
8372
|
let buf = "";
|
|
8373
8373
|
res.on("data", (c) => buf += c);
|
|
@@ -8409,7 +8409,7 @@ async function request(path, opts = {}, retried = false) {
|
|
|
8409
8409
|
method: opts.method ?? (opts.body !== void 0 || opts.raw !== void 0 ? "POST" : "GET"),
|
|
8410
8410
|
headers: {
|
|
8411
8411
|
authorization: `Bearer ${token}`,
|
|
8412
|
-
"user-agent": `ghosty-cli/${"0.
|
|
8412
|
+
"user-agent": `ghosty-cli/${"0.33.0"}`,
|
|
8413
8413
|
"accept-language": lang(),
|
|
8414
8414
|
...opts.body !== void 0 ? { "content-type": "application/json" } : {},
|
|
8415
8415
|
...opts.headers
|
|
@@ -9569,7 +9569,7 @@ async function call(path, v, init = {}) {
|
|
|
9569
9569
|
"x-ghosty-ts": ts,
|
|
9570
9570
|
"x-ghosty-sig": sig,
|
|
9571
9571
|
"content-type": "application/json",
|
|
9572
|
-
"user-agent": `ghosty-cli/${"0.
|
|
9572
|
+
"user-agent": `ghosty-cli/${"0.33.0"}`
|
|
9573
9573
|
},
|
|
9574
9574
|
body: method === "GET" ? void 0 : raw
|
|
9575
9575
|
});
|
|
@@ -10415,15 +10415,19 @@ function findGroup(groups, ref) {
|
|
|
10415
10415
|
throw usage(t(`No group "${ref}" for this agent.`, `Este agente no tiene el grupo "${ref}".`), groups.length ? `${t("Groups", "Grupos")}: ${groups.map((g) => g.name).join(", ")}` : t("Is its personal WhatsApp paired and in some group? See the agent's Channels page.", "\xBFSu WhatsApp personal est\xE1 pareado y en alg\xFAn grupo? Revisa la p\xE1gina Canales del agente."));
|
|
10416
10416
|
}
|
|
10417
10417
|
async function groupsCmd(args, v) {
|
|
10418
|
-
const [
|
|
10419
|
-
const
|
|
10418
|
+
const [rawSub, id, ...rest] = args;
|
|
10419
|
+
const sub = rawSub === "enable" || rawSub === "disable" ? "set" : rawSub;
|
|
10420
|
+
if (rawSub === "enable") v = { ...v, on: true };
|
|
10421
|
+
if (rawSub === "disable") v = { ...v, off: true };
|
|
10422
|
+
const ex = 'ghosty channels wa groups ls <agent-id> | set <agent-id> <group> [--role equipo|cliente] [--wake mention|all] [--on|--off] [--name "New name"] | create <agent-id> "Name" | inject <agent-id> <group> --text "\u2026" [--sender NAME]';
|
|
10420
10423
|
if (!id) throw usage(t("Missing agent id.", "Falta el id del agente."), `${t("Example", "Ejemplo")}: ${ex}`);
|
|
10421
10424
|
const path = `/api/v2/agents/${enc7(id)}/wa/groups`;
|
|
10422
10425
|
if (v.role !== void 0 && v.role !== "equipo" && v.role !== "cliente") throw usage(t("--role takes equipo or cliente.", "--role acepta equipo o cliente."));
|
|
10426
|
+
if (v.wake !== void 0 && v.wake !== "mention" && v.wake !== "all") throw usage(t("--wake takes mention or all.", "--wake acepta mention o all."));
|
|
10423
10427
|
if (!sub || sub === "ls" || sub === "list") {
|
|
10424
10428
|
const r2 = await api(`${path}${v.cached ? "?live=0" : ""}`);
|
|
10425
|
-
const rows = r2.groups.map((g2) => ({ name: g2.name, on: g2.enabled ? lavender("on") : dim("off"), role: g2.role, jid: g2.jid }));
|
|
10426
|
-
return out(r2, () => rows.length ? table(rows, ["name", "on", "role", "jid"]) : dim(t('No groups. Pair its personal WhatsApp in the web, or: ghosty channels wa groups create <agent-id> "Name"', 'Sin grupos. Parea su WhatsApp personal en la web, o: ghosty channels wa groups create <agent-id> "Nombre"')));
|
|
10429
|
+
const rows = r2.groups.map((g2) => ({ name: g2.name, on: g2.enabled ? lavender("on") : dim("off"), role: g2.role, wake: g2.wake ?? "all", jid: g2.jid }));
|
|
10430
|
+
return out(r2, () => rows.length ? table(rows, ["name", "on", "role", "wake", "jid"]) : dim(t('No groups. Pair its personal WhatsApp in the web, or: ghosty channels wa groups create <agent-id> "Name"', 'Sin grupos. Parea su WhatsApp personal en la web, o: ghosty channels wa groups create <agent-id> "Nombre"')));
|
|
10427
10431
|
}
|
|
10428
10432
|
if (sub === "create") {
|
|
10429
10433
|
const name2 = rest.join(" ").trim();
|
|
@@ -10432,6 +10436,14 @@ async function groupsCmd(args, v) {
|
|
|
10432
10436
|
return out(r2, () => `${lavender("\u2713")} ${t("Created", "Creado")} ${bold(name2)} (${v.role ?? "equipo"}, on)
|
|
10433
10437
|
${t("invite", "invitaci\xF3n")}: ${r2.inviteUrl}`);
|
|
10434
10438
|
}
|
|
10439
|
+
if (sub === "inject") {
|
|
10440
|
+
const ref2 = rest.join(" ").trim();
|
|
10441
|
+
const text = v.text?.trim();
|
|
10442
|
+
if (!ref2 || !text) throw usage(t("Missing group or --text.", "Falta el grupo o --text."), t("Example", "Ejemplo") + `: ghosty channels wa groups inject ${id} "Equipo" --text "lo que dec\xEDa el audio" --sender Brenda`);
|
|
10443
|
+
const g2 = findGroup((await api(`${path}?live=0`)).groups, ref2);
|
|
10444
|
+
const r2 = await api(`/api/v2/agents/${enc7(id)}/wa/inject`, { method: "POST", body: { jid: g2.jid, text, ...v.sender ? { senderName: v.sender } : {} } });
|
|
10445
|
+
return out(r2, () => `${lavender("\u2713")} ${t(`Injected into ${g2.name}: the agent answers there.`, `Reinyectado en ${g2.name}: el agente contesta ah\xED.`)}`);
|
|
10446
|
+
}
|
|
10435
10447
|
if (sub !== "set") throw usage(t(`Unknown groups subcommand: ${sub}.`, `Subcomando de groups desconocido: ${sub}.`), ex);
|
|
10436
10448
|
const ref = rest.join(" ").trim();
|
|
10437
10449
|
if (!ref) throw usage(t("Missing group (name or jid).", "Falta el grupo (nombre o jid)."), t("Example", "Ejemplo") + `: ghosty channels wa groups set ${id} "Pia-cotizadora" --role equipo --on`);
|
|
@@ -10441,16 +10453,17 @@ async function groupsCmd(args, v) {
|
|
|
10441
10453
|
const enabled = v.on ? true : v.off ? false : g.enabled;
|
|
10442
10454
|
const role = v.role ?? g.role;
|
|
10443
10455
|
const name = v.name?.trim() || void 0;
|
|
10444
|
-
const
|
|
10456
|
+
const wake = v.wake;
|
|
10457
|
+
const changes = [...wake && wake !== g.wake ? [`wake ${g.wake ?? "all"}\u2192${wake}`] : [], ...enabled !== g.enabled ? [`${g.enabled ? "on" : "off"}\u2192${enabled ? "on" : "off"}`] : [], ...role !== g.role ? [`${g.role}\u2192${role}`] : [], ...name && name !== g.name ? [`\xAB${g.name}\xBB\u2192\xAB${name}\xBB`] : []];
|
|
10445
10458
|
if (!changes.length) return out({ changed: 0, group: g }, () => dim(t(`Nothing to do: ${g.name} is already ${g.enabled ? "on" : "off"} as ${g.role}.`, `Nada que hacer: ${g.name} ya est\xE1 ${g.enabled ? "encendido" : "apagado"} como ${g.role}.`)));
|
|
10446
10459
|
const plan = `~ ${g.name}: ${changes.join(", ")}`;
|
|
10447
10460
|
if (v["dry-run"]) return out({ dryRun: true, plan: [plan] }, () => `${plan}
|
|
10448
10461
|
${dim(t("Dry run: nothing changed.", "Simulacro: no se cambi\xF3 nada."))}`);
|
|
10449
10462
|
if (enabled && !g.enabled) await confirm(`${bold(plan)}
|
|
10450
10463
|
${t(`The agent will start answering in this group as ${role}.`, `El agente empezar\xE1 a contestar en este grupo como ${role}.`)}`, { yes: v.yes, retry: `ghosty channels wa groups set ${id} "${ref}"${v.role ? ` --role ${v.role}` : ""} --on` });
|
|
10451
|
-
const r = await api(path, { method: "POST", body: { jid: g.jid, enabled, role, ...name ? { name } : {} } });
|
|
10464
|
+
const r = await api(path, { method: "POST", body: { jid: g.jid, enabled, role, ...wake ? { wake } : {}, ...name ? { name } : {} } });
|
|
10452
10465
|
const now = r.groups.find((x) => x.jid === g.jid);
|
|
10453
|
-
return out({ group: now ?? null, plan: [plan] }, () => `${lavender("\u2713")} ${now?.name ?? g.name}: ${now?.enabled ? "on" : "off"} ${t("as", "como")} ${now?.role ?? role}`);
|
|
10466
|
+
return out({ group: now ?? null, plan: [plan] }, () => `${lavender("\u2713")} ${now?.name ?? g.name}: ${now?.enabled ? "on" : "off"} ${t("as", "como")} ${now?.role ?? role} \xB7 wake ${now?.wake ?? wake ?? "all"}`);
|
|
10454
10467
|
}
|
|
10455
10468
|
var EB_EXPORT = (agentId) => `${(process.env.EASYBITS_URL ?? "https://www.easybits.cloud").replace(/\/+$/, "")}/api/v2/fleet-agents/${enc7(agentId)}/baileys/export`;
|
|
10456
10469
|
async function easybitsSession(agentId, keys) {
|
|
@@ -10488,6 +10501,10 @@ ${dim(serverText(s.reason))}` : ""}`);
|
|
|
10488
10501
|
if (sub === "pair") {
|
|
10489
10502
|
const phone = (v.phone ?? "").replace(/\D/g, "");
|
|
10490
10503
|
if (phone.length < 10) throw usage(t("Missing --phone (10-15 digits, with country code).", "Falta --phone (10-15 d\xEDgitos, con lada de pa\xEDs)."), t("Example", "Ejemplo") + `: ghosty channels wa pair ${id} --phone 5215512345678`);
|
|
10504
|
+
if (/^52\d{10}$/.test(phone) || /^521\d{10}$/.test(phone)) {
|
|
10505
|
+
const other = phone.startsWith("521") ? `52${phone.slice(3)}` : `521${phone.slice(2)}`;
|
|
10506
|
+
if (!globals.json) info(dim(t(`Mexico: +${phone} and +${other} are different WhatsApp accounts. Use the one shown in WhatsApp \u2192 Settings \u2192 your profile (newer numbers have no 1). If the code doesn't match, run: ghosty channels wa pause ${id} and pair again with --phone ${other}.`, `M\xE9xico: +${phone} y +${other} son cuentas distintas de WhatsApp. Usa el que sale en WhatsApp \u2192 Ajustes \u2192 tu perfil (los n\xFAmeros nuevos no llevan el 1). Si el c\xF3digo no empata: ghosty channels wa pause ${id} y vuelve a parear con --phone ${other}.`)));
|
|
10507
|
+
}
|
|
10491
10508
|
await api(base, { method: "POST", body: { phone } });
|
|
10492
10509
|
let s = { status: "connecting", phone: null, live: false, hasCreds: false };
|
|
10493
10510
|
for (let i = 0; i < 15 && !s.pairingCode && s.status !== "connected"; i++) {
|
|
@@ -10780,6 +10797,7 @@ var HELPS_ES = {
|
|
|
10780
10797
|
replace: [
|
|
10781
10798
|
["# the agent as a file; see ghosty apply --help", "# el agente como archivo; ver ghosty apply --help"],
|
|
10782
10799
|
["# your account: merge copies with the same content", "# tu cuenta: une las copias con el mismo contenido"],
|
|
10800
|
+
["# staff: their agents, latest turns, plan and boxes", "# staff: sus agentes, \xFAltimos turnos, plan y cajas"],
|
|
10783
10801
|
["# what it really has: effective toolsets, connectors connected + granted", "# lo que de verdad tiene: toolsets efectivos, conectores conectados + concedidos"]
|
|
10784
10802
|
],
|
|
10785
10803
|
flags: [
|
|
@@ -10799,6 +10817,8 @@ var HELPS_ES = {
|
|
|
10799
10817
|
["--voice-replies M", "set: auto = contesta con nota de voz cuando el cliente manda o pide audio; off = siempre texto"],
|
|
10800
10818
|
["--auto-stage on|off", "set: mueve solo las tarjetas del tablero tras cada turno del cliente (una llamada extra al modelo por turno)"],
|
|
10801
10819
|
["--verbose", "doctor: lista tambi\xE9n las revisiones que pasaron"],
|
|
10820
|
+
["--user EMAIL", "doctor (staff de Ghosty): los agentes de esa persona \u2014 doctor, \xFAltimos turnos (estado, segundos, error y \xABfetched\xBB: si su app pidi\xF3 el hilo despu\xE9s de que termin\xF3 el turno), plan/bolsa y cajas (marca duplicadas); queda en bit\xE1cora"],
|
|
10821
|
+
["--turns N", "doctor --user: cu\xE1ntos turnos recientes por agente (default 8, m\xE1x. 50)"],
|
|
10802
10822
|
["--write", "docs grant: el agente puede reemplazar el documento (default: s\xF3lo lectura)"],
|
|
10803
10823
|
["--no-deliver", "docs grant/add: puede leerlo pero nunca mand\xE1rselo a quien escribe"],
|
|
10804
10824
|
["--url URL | --file PATH", "docs add: de d\xF3nde sale el documento (URL p\xFAblica, hasta 25 MB, o un archivo local)"],
|
|
@@ -10827,7 +10847,7 @@ var HELPS_ES = {
|
|
|
10827
10847
|
["--prompt-mode M", "set: append (default: su prompt + la persona de Ghosty) o replace (s\xF3lo su prompt)"],
|
|
10828
10848
|
["--sponsor/--owner EMAIL", "ls: agentes de CUALQUIER cuenta que paga / de quien es ese correo (staff de Ghosty); luego se operan por id"],
|
|
10829
10849
|
["--ids", "ls: s\xF3lo los ids, uno por rengl\xF3n (para xargs)"],
|
|
10830
|
-
["--user EMAIL --app A", "ls (staff de Ghosty): la lista de esa persona tal como la recibe su app (tono, espacio, \xFAltimo uso); A = all|android|ios|desktop"]
|
|
10850
|
+
["--user EMAIL --app A", "ls (staff de Ghosty): la lista de esa persona tal como la recibe su app (tono, espacio, \xFAltimo uso) y sus tel\xE9fonos con la build de la app; A = all|android|ios|desktop"]
|
|
10831
10851
|
],
|
|
10832
10852
|
notes: [
|
|
10833
10853
|
"doctor sale con 1 cuando encuentra un problema, as\xED puede frenar un script o un CI.",
|
|
@@ -10922,20 +10942,21 @@ var HELPS_ES = {
|
|
|
10922
10942
|
},
|
|
10923
10943
|
chat: {
|
|
10924
10944
|
summary: "Conversa en streaming; sin mensaje es interactivo.",
|
|
10925
|
-
replace: [['["message"]', '["mensaje"]'], ["# staff: who sees it and why", "# staff: qui\xE9n lo ve y por qu\xE9"]],
|
|
10945
|
+
replace: [['["message"]', '["mensaje"]'], ["# staff: who sees it and why", "# staff: qui\xE9n lo ve y por qu\xE9"], ["# staff: what it ran and where it went", "# staff: qu\xE9 corri\xF3 y a d\xF3nde sali\xF3"]],
|
|
10926
10946
|
flags: [
|
|
10927
10947
|
["--media FILE", "chat: adjunta un archivo local (se repite)"],
|
|
10928
10948
|
["--file ID", "chat: adjunta de tus archivos (ghosty me files ls --q \u2026), separados por comas"],
|
|
10929
10949
|
["--kind K", "archive|rm: test (pruebas) o channel (clientes: s\xF3lo archivar, su memoria los sigue atendiendo)"],
|
|
10930
10950
|
["--before T", "archive|rm: s\xF3lo conversaciones sin mensajes desde T (7d, 12h o ISO)"],
|
|
10931
10951
|
["--user EMAIL", "ls/show (staff de Ghosty): las conversaciones de esa persona en cualquier agente; queda en bit\xE1cora"],
|
|
10932
|
-
["--why", "show (staff de Ghosty): clase, de qui\xE9n es, qu\xE9 turno la fij\xF3, y si el due\xF1o / su usuario la ven en su lista o por liga"]
|
|
10952
|
+
["--why", "show (staff de Ghosty): clase, de qui\xE9n es, qu\xE9 turno la fij\xF3, y si el due\xF1o / su usuario la ven en su lista o por liga"],
|
|
10953
|
+
["--tools", "show (staff de Ghosty): cada tool que corri\xF3 el agente con su comando/args (secretos enmascarados) y los dominios externos que nombr\xF3; nunca con un turno en marcha en ese hilo"]
|
|
10933
10954
|
]
|
|
10934
10955
|
},
|
|
10935
10956
|
schedule: {
|
|
10936
10957
|
summary: "Turnos que corren despu\xE9s, con el reloj propio del agente.",
|
|
10937
10958
|
replace: [['"what to do"', '"qu\xE9 hacer"'], ["# answers in that thread as if mentioned", "# contesta en ese hilo como si lo mencionaran"]],
|
|
10938
|
-
notes: ["--teams: el agente tiene que estar activo en el Teams de ese espacio y t\xFA ser miembro. El id del hilo es el del mensaje con que empieza."]
|
|
10959
|
+
notes: ["--teams: el agente tiene que estar activo en el Teams de ese espacio y t\xFA ser miembro. El id del hilo es el del mensaje con que empieza.", "--conversation waba:\u2026 | wa:<jid> | messenger:\u2026: la respuesta sale por ese canal (WABA s\xF3lo dentro de 24 h; WhatsApp personal si el n\xFAmero est\xE1 conectado). \xABOK\xBB nunca se manda."]
|
|
10939
10960
|
},
|
|
10940
10961
|
me: {
|
|
10941
10962
|
summary: "Tu cuenta: archivos, memorias y uso.",
|
|
@@ -10957,6 +10978,8 @@ var HELPS_ES = {
|
|
|
10957
10978
|
["-n, --dry-run", "imprime el plan (+ empieza a contestar, - deja de contestar) y no cambia nada"],
|
|
10958
10979
|
["--yes", "enable / groups enable: se salta la confirmaci\xF3n (obligatorio sin terminal)"],
|
|
10959
10980
|
["--role R", "groups: equipo (el equipo: escribe en bases de datos, tools de admin) o cliente (como cualquier cliente)"],
|
|
10981
|
+
["--wake mention|all", "groups set/enable: contesta todo, o s\xF3lo cuando lo mencionan o le responden"],
|
|
10982
|
+
["--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"],
|
|
10960
10983
|
["--cached", "groups ls: no le pide a WhatsApp la lista en vivo (m\xE1s r\xE1pido; puede faltar alg\xFAn grupo nuevo)"],
|
|
10961
10984
|
["--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)"],
|
|
10962
10985
|
["--list", "history --dry-run: CU\xC1LES conversaciones (tel\xE9fono, nombre, primer mensaje, mensajes, si vino de anuncio)"],
|
|
@@ -11074,6 +11097,8 @@ var HELPS_ES = {
|
|
|
11074
11097
|
usage: {
|
|
11075
11098
|
summary: "Tu plan y cu\xE1nto llevas usado esta semana y este mes. Con --workspace: turnos y tokens facturables de un espacio por agente y/o por d\xEDa. Con --boxes: sus cajas contra el tope.",
|
|
11076
11099
|
flags: [
|
|
11100
|
+
["--images", "(staff de Ghosty) cargos de im\xE1genes contra las que entregaron los agentes, por plan; costo guardado contra la tabla. Mucha diferencia = el medidor no est\xE1 reportando"],
|
|
11101
|
+
["--plan P", "--images: s\xF3lo free | pro | power | max | workspace"],
|
|
11077
11102
|
["--boxes", "las cajas del espacio contra su tope (spares, dormidas) y, por agente: conversaciones por caja, cajas, despiertas, spares, turnos corriendo ahora"],
|
|
11078
11103
|
["--workspace SLUG", "qu\xE9 espacio (due\xF1o o staff); con --boxes el default es el de ghosty use"],
|
|
11079
11104
|
["--by agent|day|agent,day", "--workspace: filas por agente (default), por d\xEDa (CDMX) o las dos \xB7 con --boxes: template = toda la flota por template (staff)"],
|
|
@@ -11112,11 +11137,11 @@ var HELPS_ES = {
|
|
|
11112
11137
|
},
|
|
11113
11138
|
push: {
|
|
11114
11139
|
summary: "Staff de Ghosty: qu\xE9 avisos (push) se le mandaron a alguien (fin de turno, permiso, escalar, documento\u2026), a qu\xE9 dispositivo y qu\xE9 contest\xF3 Apple/Google. Se registran desde el 1-oct y se guardan 30 d\xEDas.",
|
|
11115
|
-
notes: ["thread ense\xF1a la clase de la conversaci\xF3n: un \xABchannel\xBB ah\xED es que le lleg\xF3 el aviso de un cliente."]
|
|
11140
|
+
notes: ["thread ense\xF1a la clase de la conversaci\xF3n: un \xABchannel\xBB ah\xED es que le lleg\xF3 el aviso de un cliente.", "Tambi\xE9n salen los push de las novedades (kind novedad)."]
|
|
11116
11141
|
},
|
|
11117
11142
|
novedades: {
|
|
11118
11143
|
summary: "Staff de Ghosty: las novedades de la app Android. Cada una con su ilustraci\xF3n; mismos frenos que el admin: pru\xE9bala en tu tel\xE9fono primero, push de 9 a 21 h CDMX, 72 h entre env\xEDos a todos.",
|
|
11119
|
-
notes: ["--image se publica en img.ghosty.studio (WebP 1200 px). --to vac\xEDo = todos. send sin --at la manda ya (pregunta); --at '' quita la programaci\xF3n.", "--min-version N (versionCode de Android): si la app del tester es m\xE1s vieja, el bot\xF3n dice \xABActualizar\xBB y lanza la actualizaci\xF3n de Play desde la app; '' la quita."]
|
|
11144
|
+
notes: ["ls dice, de una ya enviada, cu\xE1ntos entreg\xF3 y rechaz\xF3 FCM (del push log, desde el 1-oct).", "--image se publica en img.ghosty.studio (WebP 1200 px). --to vac\xEDo = todos. send sin --at la manda ya (pregunta); --at '' quita la programaci\xF3n.", "--min-version N (versionCode de Android): si la app del tester es m\xE1s vieja, el bot\xF3n dice \xABActualizar\xBB y lanza la actualizaci\xF3n de Play desde la app; '' la quita."]
|
|
11120
11145
|
},
|
|
11121
11146
|
spaces: {
|
|
11122
11147
|
summary: "Espacios: lista los tuyos (o, staff, los de cualquiera) y operaciones del staff de Ghosty \u2014 entrar/salir del espacio de un cliente, cambiar su combo, mover \xABpagado hasta\xBB, renombrar su slug. Cada cambio queda en la bit\xE1cora del espacio.",
|
|
@@ -11148,6 +11173,7 @@ var HELPS = {
|
|
|
11148
11173
|
"ghosty agents set <agent-id> --engine E [--model M] --dry-run # what changing the engine does (sessions, machines, memory), without changing it",
|
|
11149
11174
|
"ghosty agents set <agent-id> --prompt-base <base-agent-id> --prompt-overlay overlay.json [--dry-run [--out FILE]] | --prompt-base none",
|
|
11150
11175
|
"ghosty agents doctor <agent-id> [--verbose]",
|
|
11176
|
+
"ghosty agents doctor --user <email> [agent-id] [--turns 8] # staff: their agents, latest turns, plan and boxes",
|
|
11151
11177
|
"ghosty agents docs ls|grant|revoke <agent-id> [doc\u2026] [--write] [--no-deliver] [--like AGENT]",
|
|
11152
11178
|
"ghosty agents docs add <agent-id> --url URL | --file PATH [--name N] [--description TEXT] [--grant read|write|no] [--no-deliver]",
|
|
11153
11179
|
"ghosty agents docs dedupe [<keep-id> <drop-id>] [--dry-run] # your account: merge copies with the same content",
|
|
@@ -11179,6 +11205,8 @@ var HELPS = {
|
|
|
11179
11205
|
["--voice-replies M", "set: auto = answer with a voice note when the customer sends or asks for audio; off = always text"],
|
|
11180
11206
|
["--auto-stage on|off", "set: move board cards by itself after each customer turn (one extra model call per turn)"],
|
|
11181
11207
|
["--verbose", "doctor: also list the checks that passed"],
|
|
11208
|
+
["--user EMAIL", "doctor (Ghosty staff): that person's agents \u2014 doctor, latest turns (state, seconds, error, and \xABfetched\xBB: whether their app asked for the thread after the turn ended), plan/bag and boxes (duplicates flagged); logged"],
|
|
11209
|
+
["--turns N", "doctor --user: how many recent turns per agent (default 8, max 50)"],
|
|
11182
11210
|
["--write", "docs grant: the agent may replace the document (default: read only)"],
|
|
11183
11211
|
["--no-deliver", "docs grant/add: it can read it but never send it to whoever writes"],
|
|
11184
11212
|
["--url URL | --file PATH", "docs add: where the document comes from (public URL, up to 25 MB, or a local file)"],
|
|
@@ -11207,7 +11235,7 @@ var HELPS = {
|
|
|
11207
11235
|
["--prompt-mode M", "set: append (default: its prompt + Ghosty's persona) or replace (its prompt only)"],
|
|
11208
11236
|
["--sponsor/--owner EMAIL", "ls: agents of ANY account paid by / owned by that email (Ghosty staff); then operate them by id"],
|
|
11209
11237
|
["--ids", "ls: only the ids, one per line (for xargs)"],
|
|
11210
|
-
["--user EMAIL --app A", "ls (Ghosty staff): that person's list exactly as their app gets it (tone, space, last use); A = all|android|ios|desktop"]
|
|
11238
|
+
["--user EMAIL --app A", "ls (Ghosty staff): that person's list exactly as their app gets it (tone, space, last use) plus their phones with the app build; A = all|android|ios|desktop"]
|
|
11211
11239
|
],
|
|
11212
11240
|
examples: [
|
|
11213
11241
|
"ghosty agents doctor cmu7pgbyr0001gee3ruk172mm",
|
|
@@ -11388,7 +11416,8 @@ var HELPS = {
|
|
|
11388
11416
|
"ghosty conversations show <agent-id> <conversation-id> [--limit 20]",
|
|
11389
11417
|
"ghosty conversations archive|rm <agent-id> --kind test|channel [--before 7d] [--dry-run] [--yes]",
|
|
11390
11418
|
"ghosty conversations ls --user <email> [--since 30d] [--surface android] # staff",
|
|
11391
|
-
"ghosty conversations show <agent-id> <conversation-id> --why # staff: who sees it and why"
|
|
11419
|
+
"ghosty conversations show <agent-id> <conversation-id> --why # staff: who sees it and why",
|
|
11420
|
+
"ghosty conversations show <agent-id> <conversation-id> --tools # staff: what it ran and where it went"
|
|
11392
11421
|
],
|
|
11393
11422
|
flags: [
|
|
11394
11423
|
["--media FILE", "chat: attach a local file (repeatable)"],
|
|
@@ -11396,7 +11425,8 @@ var HELPS = {
|
|
|
11396
11425
|
["--kind K", "archive|rm: test (test chats) or channel (customers: archive only, their memory keeps serving them)"],
|
|
11397
11426
|
["--before T", "archive|rm: only conversations with no message since T (7d, 12h or ISO)"],
|
|
11398
11427
|
["--user EMAIL", "ls/show (Ghosty staff): that person's conversations in any agent; logged"],
|
|
11399
|
-
["--why", "show (Ghosty staff): kind, whose it is, which turn set it, and whether the owner / its user see it in their list or by link"]
|
|
11428
|
+
["--why", "show (Ghosty staff): kind, whose it is, which turn set it, and whether the owner / its user see it in their list or by link"],
|
|
11429
|
+
["--tools", "show (Ghosty staff): every tool the agent ran with its command/args (secrets masked) and the external domains it named; never while a turn runs in that thread"]
|
|
11400
11430
|
],
|
|
11401
11431
|
examples: ['ghosty chat <id> "hola"', "ghosty chat <id>", 'ghosty chat <id> "p\xE1salo a PDF" --media foto.jpg', "ghosty conversations rm <id> --kind test --before 7d --dry-run", "ghosty conversations archive <id> --kind channel --before 30d", "ghosty conversations ls --user david@empresa.com --since 7d"],
|
|
11402
11432
|
learn: `${REF}#conversaciones`
|
|
@@ -11405,7 +11435,7 @@ var HELPS = {
|
|
|
11405
11435
|
summary: "Turns that run later, on the agent's own clock.",
|
|
11406
11436
|
usage: ['ghosty schedule add <agent-id> "what to do" --in 30m | --at ISO [--every 12h --until ISO] [--conversation ID] [--title T]', "ghosty schedule ls <agent-id>", "ghosty schedule rm <agent-id> <schedule-id>", 'ghosty schedule add <agent-id> "what to do" --teams <workspace>#<room>[/<thread-id>] --in 30m | --at ISO', "ghosty schedule ls|rm <agent-id> [id] --teams <workspace>"],
|
|
11407
11437
|
examples: ['ghosty schedule add <id> "revisa pedidos pendientes" --every 12h --in 1h', 'ghosty schedule add minighosty "Pres\xE9ntate al equipo" --teams descti#general/4812 --at 2026-10-01T09:00 # answers in that thread as if mentioned'],
|
|
11408
|
-
notes: ["--teams: the agent must be active in that workspace's Teams and you must be a member. The thread id is the id of the message that starts it."],
|
|
11438
|
+
notes: ["--teams: the agent must be active in that workspace's Teams and you must be a member. The thread id is the id of the message that starts it.", "--conversation waba:\u2026 | wa:<jid> | messenger:\u2026: the answer goes out through that channel (WABA only within 24 h; personal WhatsApp if the number is connected). \xABOK\xBB is never sent."],
|
|
11409
11439
|
learn: `${REF}#programar-turnos`
|
|
11410
11440
|
},
|
|
11411
11441
|
me: {
|
|
@@ -11448,12 +11478,15 @@ HELPS.channels = {
|
|
|
11448
11478
|
"ghosty channels wa groups ls <agent-id> [--cached]",
|
|
11449
11479
|
"ghosty channels wa groups enable|disable <agent-id> <group> [--role equipo|cliente] [--dry-run] [--yes]",
|
|
11450
11480
|
'ghosty channels wa groups set <agent-id> <group> [--role equipo|cliente] [--name "New name"] [--dry-run]',
|
|
11451
|
-
'ghosty channels wa groups create <agent-id> "Name" [--role equipo|cliente]'
|
|
11481
|
+
'ghosty channels wa groups create <agent-id> "Name" [--role equipo|cliente]',
|
|
11482
|
+
'ghosty channels wa groups inject <agent-id> <group> --text "\u2026" [--sender NAME]'
|
|
11452
11483
|
],
|
|
11453
11484
|
flags: [
|
|
11454
11485
|
["-n, --dry-run", "print the plan (+ starts answering, - stops) and change nothing"],
|
|
11455
11486
|
["--yes", "enable / groups enable: skip the confirmation (required without a terminal)"],
|
|
11456
11487
|
["--role R", "groups: equipo (the team: writes to databases, admin tools) or cliente (like any customer)"],
|
|
11488
|
+
["--wake mention|all", "groups set/enable: answer every message, or only when it's mentioned or replied to"],
|
|
11489
|
+
["--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"],
|
|
11457
11490
|
["--cached", "groups ls: don't ask WhatsApp for the live list (faster; may miss new groups)"],
|
|
11458
11491
|
["--ads-only on|off", "link: only conversations that come from an ad open a card (also alone, to change it on a linked number)"],
|
|
11459
11492
|
["--list", "history --dry-run: WHICH conversations (phone, name, first message, messages, from an ad)"],
|
|
@@ -11602,14 +11635,16 @@ HELPS.conversations = HELPS.chat;
|
|
|
11602
11635
|
HELPS.usage = {
|
|
11603
11636
|
...HELPS.me,
|
|
11604
11637
|
summary: "Your plan and how much you've used this week and month. With --workspace: a workspace's turns and billable tokens per agent and/or day. With --boxes: its boxes against its limit.",
|
|
11605
|
-
usage: ["ghosty usage", "ghosty me usage", "ghosty usage --workspace SLUG [--by agent|day|agent,day] [--days 14]", "ghosty usage --boxes [--workspace SLUG]"],
|
|
11638
|
+
usage: ["ghosty usage", "ghosty me usage", "ghosty usage --workspace SLUG [--by agent|day|agent,day] [--days 14]", "ghosty usage --boxes [--workspace SLUG]", "ghosty usage --images [--plan free] [--since 7d] [--by user] # staff"],
|
|
11606
11639
|
flags: [
|
|
11640
|
+
["--images", "(Ghosty staff) image charges vs images agents delivered, per plan; stored cost vs the price table. A big gap = the meter isn't reporting"],
|
|
11641
|
+
["--plan P", "--images: only free | pro | power | max | workspace"],
|
|
11607
11642
|
["--boxes", "the workspace's boxes against its limit (spares, asleep) and, per agent: conversations per box, boxes, awake, spares, turns running now"],
|
|
11608
11643
|
["--workspace SLUG", "which workspace (owner or staff); with --boxes the default is the one from ghosty use"],
|
|
11609
11644
|
["--by agent|day|agent,day", "--workspace: rows per agent (default), per day (CDMX) or both \xB7 with --boxes: template = the whole fleet by template (staff)"],
|
|
11610
11645
|
["--days N", "--workspace: how far back (default 14, max 92)"]
|
|
11611
11646
|
],
|
|
11612
|
-
examples: ["ghosty usage", "ghosty usage --json", "ghosty usage --workspace descti --by agent,day --days 14", "ghosty usage --boxes --workspace siiqtec"],
|
|
11647
|
+
examples: ["ghosty usage", "ghosty usage --json", "ghosty usage --workspace descti --by agent,day --days 14", "ghosty usage --boxes --workspace siiqtec", "ghosty usage --images --plan free --by user"],
|
|
11613
11648
|
notes: ["failed = turns that ended in error or were stopped. \xABno balance\xBB = messages that got no turn because the bag ran out (ghosty turns ls --workspace X --quota-hits lists them)."],
|
|
11614
11649
|
learn: `${REF}#uso`
|
|
11615
11650
|
};
|
|
@@ -11663,7 +11698,7 @@ HELPS.push = {
|
|
|
11663
11698
|
summary: "Ghosty staff: which pushes were sent to someone (turn end, permission, escalate, document\u2026), to which device and what Apple/Google answered. Logged since 2026-10-01, kept 30 days.",
|
|
11664
11699
|
usage: ["ghosty push log --user <email> [--since 2d] [--limit 100]"],
|
|
11665
11700
|
examples: ["ghosty push log --user brenda@siiqtec.com --since 1d"],
|
|
11666
|
-
notes: ["thread shows the conversation's kind: a \xABchannel\xBB there means a customer's push reached them."],
|
|
11701
|
+
notes: ["thread shows the conversation's kind: a \xABchannel\xBB there means a customer's push reached them.", "Announcement pushes appear too (kind novedad)."],
|
|
11667
11702
|
learn: `${REF}#soporte`
|
|
11668
11703
|
};
|
|
11669
11704
|
HELPS.novedades = {
|
|
@@ -11677,7 +11712,7 @@ HELPS.novedades = {
|
|
|
11677
11712
|
"ghosty novedades rm <id>"
|
|
11678
11713
|
],
|
|
11679
11714
|
examples: ['ghosty novedades create --title "PDF y audio" --body "Ahora\u2026" --image hero.png --link chats --publish', "ghosty novedades test <id>", 'ghosty novedades send <id> --at "2026-10-02T12:00"'],
|
|
11680
|
-
notes: ["--image is published to img.ghosty.studio (WebP 1200 px). --to empty = everyone. send without --at pushes now (asks); --at '' removes the schedule.", "--min-version N (Android versionCode): if the tester's app is older, the button reads \xABActualizar\xBB and starts Play's in-app update; '' removes it."],
|
|
11715
|
+
notes: ["ls shows, for a sent one, how many FCM delivered and rejected (from push log, since 2026-10-01).", "--image is published to img.ghosty.studio (WebP 1200 px). --to empty = everyone. send without --at pushes now (asks); --at '' removes the schedule.", "--min-version N (Android versionCode): if the tester's app is older, the button reads \xABActualizar\xBB and starts Play's in-app update; '' removes it."],
|
|
11681
11716
|
learn: `${REF}#soporte`
|
|
11682
11717
|
};
|
|
11683
11718
|
HELPS.spaces = {
|
|
@@ -12407,8 +12442,19 @@ ${table(rows, ["last", "agent", "conversation", "title", "kind", "listed", "turn
|
|
|
12407
12442
|
${dim(t("Read one: ghosty conversations show <agent> <conversation> --why \xB7 times in CDMX \xB7 listed = in their app list", "Leer uno: ghosty conversations show <agente> <conversaci\xF3n> --why \xB7 horas CDMX \xB7 listed = sale en su lista de la app"))}`);
|
|
12408
12443
|
}
|
|
12409
12444
|
async function supportThread(agent, sid, v) {
|
|
12410
|
-
const r = await api(`/api/v2/support/threads/${enc12(agent)}/${enc12(sid)}?tail=${enc12(v.limit ?? "40")}`);
|
|
12445
|
+
const r = await api(`/api/v2/support/threads/${enc12(agent)}/${enc12(sid)}?tail=${enc12(v.limit ?? "40")}${v.tools ? "&tools=1" : ""}`);
|
|
12411
12446
|
out(r, () => {
|
|
12447
|
+
if (v.tools && r.tools) {
|
|
12448
|
+
const x = r.tools;
|
|
12449
|
+
const lines = [
|
|
12450
|
+
`${bold(r.agent.name ?? r.agent.id)} \xB7 ${r.title ?? sid} ${dim(`(${t("from", "de")} ${x.source})`)}`,
|
|
12451
|
+
...x.note ? [dim(` ${serverText(x.note)}`)] : [],
|
|
12452
|
+
...x.tools.map((s) => `${dim(s.at ? cdmx(s.at) : " \xB7 ")} ${bold(s.tool)} ${s.input.replace(/\s+/g, " ").slice(0, 300)}${s.status && s.status !== "completed" ? dim(` [${s.status}]`) : ""}`),
|
|
12453
|
+
"",
|
|
12454
|
+
x.domains.length ? `${t("External domains", "Dominios externos")}: ${x.domains.map((d) => `${d.host} (${d.n})`).join(", ")}` : dim(t("No external domains named in tool inputs.", "Ninguna tool nombr\xF3 dominios externos."))
|
|
12455
|
+
];
|
|
12456
|
+
return lines.join("\n");
|
|
12457
|
+
}
|
|
12412
12458
|
const w = r.why;
|
|
12413
12459
|
const who2 = (x, label) => x ? ` ${label} ${x.email}: ${x.listed ? lavender(t("in their list/search/push", "en su lista/b\xFAsqueda/push")) : dim(t("not listed", "no sale en su lista"))} \xB7 ${t("by link", "por liga")}: ${x.open || t("no", "no")}` : "";
|
|
12414
12460
|
const why = [
|
|
@@ -12465,8 +12511,12 @@ ${table(rows, ["template", "boxes", "on", "asleep", "ram", "vcpu", "ram on"])}`)
|
|
|
12465
12511
|
async function appAgentsOf(email, client) {
|
|
12466
12512
|
const r = await api(`/api/v2/support/app-agents?user=${enc12(email)}${client && client !== "all" ? `&client=${enc12(client)}` : ""}`);
|
|
12467
12513
|
const rows = r.agentes.map((a) => ({ id: a.id, name: a.nombre, engine: a.motor ?? "", model: a.modelo ?? "", tone: a.tono ?? t("(by position)", "(por posici\xF3n)"), space: a.espacio?.tipo === "workspace" ? a.espacio.nombre : a.espacio?.tipo ?? "", last: a.ultimaActividad ? cdmx(a.ultimaActividad) : "" }));
|
|
12514
|
+
const phones = (r.devices ?? []).map((d) => `${d.platform} ${d.appVersion ?? ""}${d.appBuild ? ` (build ${d.appBuild})` : " (build ?)"} \xB7 ${t("seen", "visto")} ${cdmx(d.lastSeenAt)}`.replace(/\s+/g, " "));
|
|
12468
12515
|
out(r, () => `${bold(r.user)} ${dim(r.client ? t(`as the ${r.client} app sees it`, `como lo ve la app de ${r.client}`) : t("all apps", "todas las apps"))}
|
|
12469
|
-
${table(rows, ["id", "name", "engine", "model", "tone", "space", "last"])}`
|
|
12516
|
+
${table(rows, ["id", "name", "engine", "model", "tone", "space", "last"])}${phones.length ? `
|
|
12517
|
+
${dim(t("phones:", "tel\xE9fonos:"))}
|
|
12518
|
+
${phones.map((p) => ` ${p}`).join("\n")}` : `
|
|
12519
|
+
${dim(t("No phones registered for push.", "Sin tel\xE9fonos registrados para avisos."))}`}`);
|
|
12470
12520
|
}
|
|
12471
12521
|
async function pushLog(email, v) {
|
|
12472
12522
|
if (!email) throw usage(t("Missing --user.", "Falta --user."), "ghosty push log --user ana@empresa.com [--since 2d]");
|
|
@@ -12490,7 +12540,7 @@ async function novedadesCmd(sub, id, v) {
|
|
|
12490
12540
|
...v.publish ? { published: true } : v.draft ? { published: false } : {}
|
|
12491
12541
|
});
|
|
12492
12542
|
const line2 = (a) => `${bold(a.title)} ${dim(a.id)}
|
|
12493
|
-
${a.published ? lavender(t("published", "publicada")) : dim(t("draft", "borrador"))} \xB7 ${a.pushedAt ? t(`pushed ${cdmx(a.pushedAt)} (${a.pushCount})`, `enviada ${cdmx(a.pushedAt)} (${a.pushCount})`) : a.schedule ? t(`push scheduled ${cdmx(a.schedule)} CDMX`, `push programado ${cdmx(a.schedule)} CDMX`) : t("not pushed", "sin enviar")} \xB7 ${a.destinatarios ? t(`to ${a.destinatarios.split(",").length}`, `para ${a.destinatarios.split(",").length}`) : t("everyone", "todos")}${a.minVersionCode ? t(` \xB7 needs v${a.minVersionCode}+`, ` \xB7 requiere v${a.minVersionCode}+`) : ""}`;
|
|
12543
|
+
${a.published ? lavender(t("published", "publicada")) : dim(t("draft", "borrador"))} \xB7 ${a.pushedAt ? t(`pushed ${cdmx(a.pushedAt)} (${a.pushCount})`, `enviada ${cdmx(a.pushedAt)} (${a.pushCount})`) + (a.delivery ? ` \xB7 ${t(`${a.delivery.sent} delivered`, `${a.delivery.sent} entregados`)}${a.delivery.rejected ? red(t(` \xB7 ${a.delivery.rejected} rejected`, ` \xB7 ${a.delivery.rejected} rechazados`)) : ""}${a.delivery.noResponse ? dim(t(` \xB7 ${a.delivery.noResponse} no answer`, ` \xB7 ${a.delivery.noResponse} sin respuesta`)) : ""}` : "") : a.schedule ? t(`push scheduled ${cdmx(a.schedule)} CDMX`, `push programado ${cdmx(a.schedule)} CDMX`) : t("not pushed", "sin enviar")} \xB7 ${a.destinatarios ? t(`to ${a.destinatarios.split(",").length}`, `para ${a.destinatarios.split(",").length}`) : t("everyone", "todos")}${a.minVersionCode ? t(` \xB7 needs v${a.minVersionCode}+`, ` \xB7 requiere v${a.minVersionCode}+`) : ""}`;
|
|
12494
12544
|
switch (sub ?? "ls") {
|
|
12495
12545
|
case "ls":
|
|
12496
12546
|
case "list": {
|
|
@@ -12539,6 +12589,53 @@ ${dim(t(`Try it on your phone: ghosty novedades test ${r.id}`, `Pru\xE9bala en t
|
|
|
12539
12589
|
throw usage(t(`Unknown novedades subcommand: ${sub}.`, `Subcomando de novedades desconocido: ${sub}.`), ex);
|
|
12540
12590
|
}
|
|
12541
12591
|
}
|
|
12592
|
+
async function imageUsage(v) {
|
|
12593
|
+
const q = new URLSearchParams({ since: v.since ?? "7d", ...v.plan ? { plan: v.plan } : {}, ...v.by === "user" ? { by: "user" } : {} });
|
|
12594
|
+
const r = await api(`/api/v2/support/images?${q}`);
|
|
12595
|
+
const usd = (n) => n == null ? "" : `$${n.toFixed(3)}`;
|
|
12596
|
+
const cols = (x) => ({ charges: x.charges, delivered: x.delivered, hd: x.hd, byok: x.byok, usd: usd(x.usd), "table usd": usd(x.tableUsd), avg: usd(x.avgUsd) });
|
|
12597
|
+
out(r, () => {
|
|
12598
|
+
const gap = r.total.delivered > r.total.charges * 2 && r.total.delivered >= 5;
|
|
12599
|
+
return [
|
|
12600
|
+
`${bold(t(`${r.total.charges} charge(s) \xB7 ${r.total.delivered} image(s) delivered by agents`, `${r.total.charges} cargo(s) \xB7 ${r.total.delivered} imagen(es) entregadas por agentes`))} ${dim(`(${v.since ?? "7d"}${v.plan ? ` \xB7 ${v.plan}` : ""})`)}`,
|
|
12601
|
+
gap ? red(t(" \u26A0\uFE0F far more deliveries than charges: the meter is probably not reporting", " \u26A0\uFE0F muchas m\xE1s entregas que cargos: el medidor probablemente no est\xE1 reportando")) : "",
|
|
12602
|
+
"",
|
|
12603
|
+
table(r.byPlan.map((p) => ({ plan: p.plan, ...cols(p) })), ["plan", "charges", "delivered", "hd", "byok", "usd", "table usd", "avg"]),
|
|
12604
|
+
...r.byUser ? ["", table(r.byUser.map((u) => ({ user: u.user, plan: u.plan, ...cols(u), bag: u.budget ? `$${u.budget.usedMxn.toFixed(0)}/$${u.budget.limitMxn} MXN` : "" })), ["user", "plan", "charges", "delivered", "hd", "usd", "table usd", "bag"])] : [],
|
|
12605
|
+
dim(t("delivered = agent images in the library (includes forwarded photos: a ceiling) \xB7 usd = stored (OpenAI's real usage when sent) \xB7 table = IMAGE_COST_USD", "entregadas = im\xE1genes del agente en la biblioteca (incluye fotos reenviadas: es un techo) \xB7 usd = guardado (el usage real de OpenAI cuando llega) \xB7 tabla = IMAGE_COST_USD"))
|
|
12606
|
+
].filter((x) => x !== "").join("\n");
|
|
12607
|
+
});
|
|
12608
|
+
}
|
|
12609
|
+
async function userDoctor(email, agent, v) {
|
|
12610
|
+
const q = new URLSearchParams({ user: email, ...agent ? { agent } : {}, ...v.turns ? { turns: v.turns } : {} });
|
|
12611
|
+
const r = await api(`/api/v2/support/doctor?${q}`);
|
|
12612
|
+
out(r, () => {
|
|
12613
|
+
const lines = [bold(r.user)];
|
|
12614
|
+
if (r.plan) lines.push(dim(` plan ${r.plan.plan.name} \xB7 ${t("session", "sesi\xF3n")} ${r.plan.session.pct}% \xB7 ${t("week", "semana")} ${r.plan.week.pct}% \xB7 ${t("credit", "recarga")} $${r.plan.creditMxn} MXN`) + (r.plan.exhausted ? red(t(" \xB7 EXHAUSTED", " \xB7 AGOTADO")) : ""));
|
|
12615
|
+
for (const a of r.agents) {
|
|
12616
|
+
lines.push("", `${bold(a.name ?? a.id)} ${dim(`${a.id} \xB7 ${a.engine}${a.model ? ` \xB7 ${a.model}` : ""}${a.owner ? "" : t(" \xB7 shared", " \xB7 compartido")}`)}`);
|
|
12617
|
+
for (const c of a.checks) lines.push(` ${c.level === "error" ? red("\u2717") : "!"} ${serverText(c.title)}`);
|
|
12618
|
+
if (!a.checks.length) lines.push(dim(` \u2713 doctor (${a.okChecks})`));
|
|
12619
|
+
if (a.allowance) lines.push(dim(` ${t("bag", "bolsa")}: ${t("session", "sesi\xF3n")} $${a.allowance.session.usedMxn.toFixed(0)}/$${a.allowance.session.limitMxn} \xB7 ${t("week", "semana")} $${a.allowance.week.usedMxn.toFixed(0)}/$${a.allowance.week.limitMxn}`));
|
|
12620
|
+
const boxes = a.boxes.map((b) => `${b.id.slice(0, 11)} ${b.kind} ${b.state} ${b.memMb / 1024}GB`).join(" \xB7 ");
|
|
12621
|
+
lines.push(a.duplicateBoxes ? red(` ${t("DUPLICATE boxes", "cajas DUPLICADAS")}: ${boxes}`) : dim(` ${t("boxes", "cajas")}: ${boxes || t("none (the next turn starts one)", "ninguna (el siguiente turno levanta una)")}`));
|
|
12622
|
+
const rows = a.turns.map((x) => ({
|
|
12623
|
+
at: cdmx(x.startedAt),
|
|
12624
|
+
conversation: x.sessionId.slice(0, 20),
|
|
12625
|
+
from: x.surface ?? "",
|
|
12626
|
+
state: x.state === "error" ? red(x.state) : x.state,
|
|
12627
|
+
secs: x.seconds ?? "",
|
|
12628
|
+
// ¿La app pidió el hilo después del `done`? (registro desde el 1-oct)
|
|
12629
|
+
fetched: x.fetched === null ? "" : x.fetched === false ? red(t("never", "nunca")) : `+${x.fetched}s`,
|
|
12630
|
+
error: (x.error ?? "").slice(0, 60)
|
|
12631
|
+
}));
|
|
12632
|
+
if (rows.length) lines.push(table(rows, ["at", "conversation", "from", "state", "secs", "fetched", "error"]).split("\n").map((l) => ` ${l}`).join("\n"));
|
|
12633
|
+
}
|
|
12634
|
+
if (!r.agents.length) lines.push(dim(t("No agents.", "Sin agentes.")));
|
|
12635
|
+
lines.push("", dim(t("fetched = the app asked for the thread after the turn ended (tracked since 2026-10-01)", "fetched = la app pidi\xF3 el hilo despu\xE9s de que termin\xF3 el turno (se registra desde el 1-oct)")));
|
|
12636
|
+
return lines.join("\n");
|
|
12637
|
+
});
|
|
12638
|
+
}
|
|
12542
12639
|
|
|
12543
12640
|
// src/index.ts
|
|
12544
12641
|
var HELP_EN = `${bold("Usage:")} ghosty <command> [options]
|
|
@@ -12550,7 +12647,7 @@ ${bold("Session")}
|
|
|
12550
12647
|
whoami Show who you are signed in as
|
|
12551
12648
|
use [workspace] Default workspace for boards, agents create and agents ls [--clear]
|
|
12552
12649
|
completion zsh|bash|fish Tab completion for commands, flags, agents and workspaces
|
|
12553
|
-
usage Your plan and how much you've used this week and month [--boxes: boxes vs the workspace limit \xB7 --boxes --by template: the whole fleet (staff)]
|
|
12650
|
+
usage Your plan and how much you've used this week and month [--boxes: boxes vs the workspace limit \xB7 --boxes --by template: the whole fleet (staff) \xB7 --images [--plan free] [--by user]: image charges vs deliveries (staff)]
|
|
12554
12651
|
usage --workspace SLUG A workspace's turns and tokens per agent/day [--by agent,day] [--days 14]
|
|
12555
12652
|
plan apply [workspace] Apply a scheduled plan now, showing what changes [--dry-run]
|
|
12556
12653
|
engines ls Engines and models valid for --engine/--model [--models]
|
|
@@ -12611,7 +12708,7 @@ ${bold("Configure an agent")}
|
|
|
12611
12708
|
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]
|
|
12612
12709
|
channels wa pair <id> --phone N Pair a personal WhatsApp by code (no QR)
|
|
12613
12710
|
channels wa status|pause <id> Personal WhatsApp: how it is / pause it (keeps the session)
|
|
12614
|
-
channels wa groups ls|enable|disable|set|create <id> [group] Personal WhatsApp groups it answers, and as whom [--role equipo|cliente] [--name "New name"] [--dry-run]
|
|
12711
|
+
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]
|
|
12615
12712
|
boards ls|create|assign|move-cards|rm A workspace's boards: create (--columns-from), where an agent sends cards, move cards
|
|
12616
12713
|
cards ls <id> Recent cards on its sales board [--q TEXT] [--paused] [--limit 20]
|
|
12617
12714
|
cards show <id> <card> A card's thread: who wrote, text, attachments and whether gs kept a copy (folio, phone or id)
|
|
@@ -12678,7 +12775,7 @@ ${bold("Sesi\xF3n")}
|
|
|
12678
12775
|
whoami Muestra con qu\xE9 cuenta entraste
|
|
12679
12776
|
use [espacio] Espacio por default para boards, agents create y agents ls [--clear]
|
|
12680
12777
|
completion zsh|bash|fish Autocompletado con TAB: comandos, opciones, agentes y espacios
|
|
12681
|
-
usage Tu plan y cu\xE1nto llevas usado esta semana y este mes [--boxes: cajas contra el tope del espacio \xB7 --boxes --by template: toda la flota (staff)]
|
|
12778
|
+
usage Tu plan y cu\xE1nto llevas usado esta semana y este mes [--boxes: cajas contra el tope del espacio \xB7 --boxes --by template: toda la flota (staff) \xB7 --images [--plan free] [--by user]: cargos de im\xE1genes contra entregas (staff)]
|
|
12682
12779
|
usage --workspace SLUG Turnos y tokens de un espacio por agente/d\xEDa [--by agent,day] [--days 14]
|
|
12683
12780
|
plan apply [espacio] Aplica ya un plan programado, ense\xF1ando qu\xE9 cambia [--dry-run]
|
|
12684
12781
|
engines ls Motores y modelos v\xE1lidos para --engine/--model [--models]
|
|
@@ -12739,7 +12836,7 @@ ${bold("Configurar un agente")}
|
|
|
12739
12836
|
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]
|
|
12740
12837
|
channels wa pair <id> --phone N Vincula un WhatsApp personal con c\xF3digo (sin QR)
|
|
12741
12838
|
channels wa status|pause <id> WhatsApp personal: c\xF3mo est\xE1 / pausarlo (conserva la sesi\xF3n)
|
|
12742
|
-
channels wa groups ls|enable|disable|set|create <id> [grupo] Grupos de WhatsApp personal que contesta, y como qui\xE9n [--role equipo|cliente] [--name "Nombre nuevo"] [--dry-run]
|
|
12839
|
+
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]
|
|
12743
12840
|
boards ls|create|assign|move-cards|rm Los tableros de un espacio: crear (--columns-from), a d\xF3nde manda tarjetas un agente, mover tarjetas
|
|
12744
12841
|
cards ls <id> Tarjetas recientes de su tablero de ventas [--q TEXTO] [--paused] [--limit 20]
|
|
12745
12842
|
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)
|
|
@@ -12946,6 +13043,9 @@ async function main() {
|
|
|
12946
13043
|
before: { type: "string" },
|
|
12947
13044
|
recycle: { type: "boolean" },
|
|
12948
13045
|
role: { type: "string" },
|
|
13046
|
+
wake: { type: "string" },
|
|
13047
|
+
text: { type: "string" },
|
|
13048
|
+
sender: { type: "string" },
|
|
12949
13049
|
"from-file": { type: "string" },
|
|
12950
13050
|
"from-dir": { type: "string" },
|
|
12951
13051
|
"own-number": { type: "string" },
|
|
@@ -12987,6 +13087,9 @@ async function main() {
|
|
|
12987
13087
|
phones: { type: "string" },
|
|
12988
13088
|
combo: { type: "string" },
|
|
12989
13089
|
app: { type: "string" },
|
|
13090
|
+
images: { type: "boolean" },
|
|
13091
|
+
plan: { type: "string" },
|
|
13092
|
+
tools: { type: "boolean" },
|
|
12990
13093
|
ids: { type: "boolean" },
|
|
12991
13094
|
force: { type: "boolean" },
|
|
12992
13095
|
reenv: { type: "boolean" },
|
|
@@ -13003,7 +13106,7 @@ async function main() {
|
|
|
13003
13106
|
const tokens = splitTokens(pos, v);
|
|
13004
13107
|
globals.token = tokens.session;
|
|
13005
13108
|
globals.profile = v.profile;
|
|
13006
|
-
if (v.version) return out({ version: "0.
|
|
13109
|
+
if (v.version) return out({ version: "0.33.0" }, () => "0.33.0");
|
|
13007
13110
|
for (const n of normalizeArgs(pos, v)) if (!globals.json) info(dim(n));
|
|
13008
13111
|
const context = (await readConfig()).workspace;
|
|
13009
13112
|
if (!explicitToken()?.startsWith("gat_")) {
|
|
@@ -13063,6 +13166,7 @@ async function main() {
|
|
|
13063
13166
|
if (sub === "share" || sub === "unshare" || sub === "shares") return agentsShare(sub, a1, a2, v);
|
|
13064
13167
|
if (sub === "set") return agentsSet(a1, v);
|
|
13065
13168
|
if (sub === "rm" || sub === "delete") return agentsRm(a1, v);
|
|
13169
|
+
if (sub === "doctor" && v.user) return userDoctor(v.user, a1, v);
|
|
13066
13170
|
if (sub === "doctor") return agentsDoctor(a1, v);
|
|
13067
13171
|
if (sub === "docs" || sub === "documents") return agentDocsCmd(a1, a2, pos.slice(4).filter((x) => x !== void 0), v);
|
|
13068
13172
|
if (sub === "restart") return agentsRestart(a1);
|
|
@@ -13107,7 +13211,7 @@ async function main() {
|
|
|
13107
13211
|
case "conversations":
|
|
13108
13212
|
if ((sub === "ls" || sub === "list") && v.user) return supportThreads(v.user, v);
|
|
13109
13213
|
if (sub === "ls" || sub === "list") return conversationsLs(a1);
|
|
13110
|
-
if (sub === "show" && (v.why || v.user)) return supportThread(a1, a2, v);
|
|
13214
|
+
if (sub === "show" && (v.why || v.user || v.tools)) return supportThread(a1, a2, v);
|
|
13111
13215
|
if (sub === "show") return conversationsShow(a1, a2, v);
|
|
13112
13216
|
if (sub === "archive" || sub === "rm") return cleanupConversations(sub, a1, v);
|
|
13113
13217
|
throw usage(t(`Unknown conversations subcommand: ${sub ?? "(none)"}.`, `Subcomando de conversations desconocido: ${sub ?? "(ninguno)"}.`), t("Try: ghosty conversations ls <agent-id> \xB7 ghosty conversations show <agent-id> <conversation-id>", "Prueba: ghosty conversations ls <agent-id> \xB7 ghosty conversations show <agent-id> <conversation-id>"));
|
|
@@ -13116,6 +13220,7 @@ async function main() {
|
|
|
13116
13220
|
case "partner":
|
|
13117
13221
|
return partnerCmd(sub, a1, a2, { ...v, token: tokens.turn });
|
|
13118
13222
|
case "usage":
|
|
13223
|
+
if (v.images) return imageUsage(v);
|
|
13119
13224
|
if (v.boxes && v.by === "template") return fleetBoxes();
|
|
13120
13225
|
if (v.boxes) return boxesUsage(v.workspace ?? context);
|
|
13121
13226
|
if (v.workspace || v.by || v.days) return workspaceUsage(v.workspace ?? context, v);
|
|
@@ -13149,7 +13254,7 @@ async function main() {
|
|
|
13149
13254
|
throw unknown(cmd, cmd, COMMANDS, "ghosty --help");
|
|
13150
13255
|
}
|
|
13151
13256
|
}
|
|
13152
|
-
var notice = updateNotice("0.
|
|
13257
|
+
var notice = updateNotice("0.33.0").catch(() => null);
|
|
13153
13258
|
main().then(
|
|
13154
13259
|
async () => {
|
|
13155
13260
|
const n = await notice;
|
package/package.json
CHANGED