@ghostystudio/cli 0.32.2 → 0.33.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +12 -2
  2. package/dist/index.js +140 -35
  3. 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.32.2"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
8370
+ headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.33.1"}`, "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.32.2"}`,
8412
+ "user-agent": `ghosty-cli/${"0.33.1"}`,
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.32.2"}`
9572
+ "user-agent": `ghosty-cli/${"0.33.1"}`
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 [sub, id, ...rest] = args;
10419
- const ex = 'ghosty channels wa groups ls <agent-id> | set <agent-id> <group> [--role equipo|cliente] [--on|--off] [--name "New name"] | create <agent-id> "Name"';
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 changes = [...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`] : []];
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(2)}/$${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.32.2" }, () => "0.32.2");
13109
+ if (v.version) return out({ version: "0.33.1" }, () => "0.33.1");
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.32.2").catch(() => null);
13257
+ var notice = updateNotice("0.33.1").catch(() => null);
13153
13258
  main().then(
13154
13259
  async () => {
13155
13260
  const n = await notice;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghostystudio/cli",
3
- "version": "0.32.2",
3
+ "version": "0.33.1",
4
4
  "description": "Official Ghosty Studio CLI — configure, test and chat with your agents from the terminal. Built for humans and coding agents.",
5
5
  "type": "module",
6
6
  "bin": {