@ghostystudio/cli 0.30.0 → 0.32.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.
Files changed (3) hide show
  1. package/README.md +9 -0
  2. package/dist/index.js +210 -56
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -73,6 +73,7 @@ ghosty schedule rm <id> <schedule-id>
73
73
  ghosty schedule add|ls|rm <id> … --teams <workspace>#<room>[/<thread-id>] # in a Teams room/thread: it answers there as if mentioned
74
74
 
75
75
  ghosty me files ls|get|upload|rm [--q TEXT] [--kind image|video|audio|document|other] [--cursor C]
76
+ ghosty me memories ls|add|edit|rm [--agent A]
76
77
  ghosty conversations archive|rm <id> --kind test|channel [--before 7d] [--dry-run]
77
78
  ghosty agents docs dedupe [--dry-run] # your account: merge documents with the same content
78
79
  # Ghosty staff
@@ -95,6 +96,14 @@ Every command has its own help with examples: `ghosty <command> --help` or `ghos
95
96
 
96
97
  Exit codes: `0` ok · `1` API error · `2` usage error · `3` not signed in.
97
98
 
99
+ ### Changes in 0.31.0
100
+
101
+ - `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).
102
+ - `channels whatsapp history --dry-run --list` (which conversations, whether from an ad), `--only-ads`, `--phones a,b`, `--column NAME`; `channels whatsapp link --ads-only on|off` (also alone, on a linked number) and `whatsapp ls` shows it.
103
+ - After a `--dry-run`, the suggested command already carries `--yes` when there's no terminal. A 404 says it may be missing access.
104
+ - Staff: `agents ls --user EMAIL --app all|android|ios|desktop` (their list as their app gets it), `usage --boxes --by template` (whole fleet).
105
+ - The «new version» notice checks npm every hour (it was once a day).
106
+
98
107
  ### Changes in 0.30.0
99
108
 
100
109
  - Support (Ghosty staff): `conversations ls --user EMAIL`, `conversations show <agent> <conv> --why` (who sees it and why), `turns stats` (real failure rate), `push log --user EMAIL`, `novedades ls|create|set|test|send|rm` (Android app announcements, `--image FILE`).
package/dist/index.js CHANGED
@@ -8044,6 +8044,27 @@ var SERVER_MESSAGES = [
8044
8044
  ["Esta novedad ya se mand\xF3.", "This announcement was already sent."],
8045
8045
  ["no hay novedad {}", "no announcement {}"],
8046
8046
  ["lista, b\xFAsqueda y push: s\xF3lo kind=personal de quien lo abri\xF3; por liga: el suyo, y el due\xF1o/admin lee los de canal y prueba", "list, search and push: only kind=personal of whoever opened it; by link: their own, and the owner/admin reads channel and test threads"],
8047
+ ["los espacios de otra persona: s\xF3lo el staff de Ghosty", "someone else's workspaces: Ghosty staff only"],
8048
+ ["entrar al espacio de un cliente: s\xF3lo el staff de Ghosty", "entering a client's workspace: Ghosty staff only"],
8049
+ ["cambiar el combo de un espacio: s\xF3lo el staff de Ghosty", "changing a workspace's combo: Ghosty staff only"],
8050
+ ["ver la app de otra persona: s\xF3lo el staff de Ghosty", "seeing someone else's app: Ghosty staff only"],
8051
+ ["las cajas de la flota: s\xF3lo el staff de Ghosty", "the fleet's boxes: Ghosty staff only"],
8052
+ ["combo: {}", "combo: {}"],
8053
+ ["{} no trae Ventas (combo {}): nadie va a ver este tablero; staff: ghosty spaces set {} --combo sales", "{} has no Sales (combo {}): nobody will see this board; staff: ghosty spaces set {} --combo sales"],
8054
+ ["no hay columna \xAB{}\xBB en ese tablero; hay: {}", "no column \xAB{}\xBB on that board; there are: {}"],
8055
+ ["~ WabaLink {}: s\xF3lo anuncios {} \u2192 {}", "~ WabaLink {}: ads only {} \u2192 {}"],
8056
+ [" s\xF3lo anuncios: on (s\xF3lo abre tarjeta lo que viene de un anuncio)", " ads only: on (only what comes from an ad opens a card)"],
8057
+ [" s\xF3lo anuncios: off", " ads only: off"],
8058
+ // — Memorias e hilo largo
8059
+ ["no existe esa memoria (revisa memory_list)", "that memory doesn't exist (check memory_list)"],
8060
+ ["no existe esa memoria", "that memory doesn't exist"],
8061
+ ["la memoria est\xE1 vac\xEDa", "the memory is empty"],
8062
+ ["una memoria cabe en {} caracteres", "a memory fits in {} characters"],
8063
+ ["ya hay {} memorias; borra alguna antes de guardar otra", "there are already {} memories; delete one before saving another"],
8064
+ ["no se pudo guardar la memoria", "couldn't save the memory"],
8065
+ ["las memorias s\xF3lo se tocan en el chat personal de la persona", "memories can only be changed in the person's personal chat"],
8066
+ ["la persona la ve y la edita en Memorias de su app", "the person sees and edits it in Memories in their app"],
8067
+ ["No pude abrir el chat nuevo: {}", "Couldn't open the new chat: {}"],
8047
8068
  // — Infra interna (casi nunca llega al CLI, pero por si acaso)
8048
8069
  ["SESSION_SECRET ausente en producci\xF3n \u2014 las cookies quedar\xEDan firmadas con un valor p\xFAblico. Inyectarlo con ./scripts/set_gs_secret.sh SESSION_SECRET (openssl rand -hex 32).", "SESSION_SECRET missing in production \u2014 cookies would be signed with a public value. Inject it with ./scripts/set_gs_secret.sh SESSION_SECRET (openssl rand -hex 32)."],
8049
8070
  ["sqld: fetch sin namespace", "sqld: fetch without namespace"],
@@ -8346,7 +8367,7 @@ async function postLong(path, body, timeoutMs = 9e5) {
8346
8367
  const req = httpsRequest(`${base}${path}`, {
8347
8368
  method: "POST",
8348
8369
  timeout: timeoutMs,
8349
- headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.30.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
8370
+ headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.32.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
8350
8371
  }, (res) => {
8351
8372
  let buf = "";
8352
8373
  res.on("data", (c) => buf += c);
@@ -8388,7 +8409,7 @@ async function request(path, opts = {}, retried = false) {
8388
8409
  method: opts.method ?? (opts.body !== void 0 || opts.raw !== void 0 ? "POST" : "GET"),
8389
8410
  headers: {
8390
8411
  authorization: `Bearer ${token}`,
8391
- "user-agent": `ghosty-cli/${"0.30.0"}`,
8412
+ "user-agent": `ghosty-cli/${"0.32.0"}`,
8392
8413
  "accept-language": lang(),
8393
8414
  ...opts.body !== void 0 ? { "content-type": "application/json" } : {},
8394
8415
  ...opts.headers
@@ -8415,7 +8436,7 @@ async function toError(res) {
8415
8436
  if (res.status === 401) return new CliError(t("Your session is not valid anymore.", "Tu sesi\xF3n ya no es v\xE1lida."), EXIT.AUTH, t("Run: ghosty login", "Corre: ghosty login"));
8416
8437
  if (res.status === 403) return new CliError(t(`Not allowed: ${detail}`, `No permitido: ${detail}`), EXIT.API, t("If the agent was shared with you, its workspace (board, channels, numbers) stays with the owner: ask them to add you to that workspace. Otherwise run: ghosty login", "Si el agente se comparti\xF3 contigo, su espacio (tablero, canales, n\xFAmeros) sigue siendo del due\xF1o: p\xEDdele que te agregue a ese espacio. Si no, corre: ghosty login"));
8417
8438
  if (res.status === 404 && (body.message || body.error)) return new CliError(detail, EXIT.API);
8418
- if (res.status === 404) return new CliError(t("Not found.", "No se encontr\xF3."), EXIT.API, t("Check the agent id (ghosty agents ls) or that your token belongs to that agent.", "Revisa el id del agente (ghosty agents ls) o que tu token sea de ese agente."));
8439
+ if (res.status === 404) return new CliError(t("Not found \u2014 or you don't have access to it.", "No se encontr\xF3, o no tienes acceso."), EXIT.API, t("Check the agent id (ghosty agents ls) or that your token belongs to that agent. Ghosty staff on a client's workspace: ghosty spaces join <workspace>", "Revisa el id del agente (ghosty agents ls) o que tu token sea de ese agente. Staff de Ghosty en el espacio de un cliente: ghosty spaces join <espacio>"));
8419
8440
  if (body.error === "restart_no_aplica" || body.error === "agente_sin_maquina") {
8420
8441
  return new CliError(t("This agent doesn't need a restart: new files, skills and MCP load on its next turn.", "Este agente no necesita reinicio: los archivos, skills y MCP nuevos se cargan en su siguiente turno."), EXIT.API);
8421
8442
  }
@@ -8430,6 +8451,9 @@ import { dirname as dirname2, join as join3, relative, resolve } from "node:path
8430
8451
  // src/prompt.ts
8431
8452
  import { createInterface } from "node:readline/promises";
8432
8453
  var canPrompt = () => Boolean(process.stdin.isTTY && process.stderr.isTTY);
8454
+ function applyHint(cmd) {
8455
+ return t(`Apply: ${cmd}${canPrompt() ? "" : " --yes"}`, `Para aplicarlo: ${cmd}${canPrompt() ? "" : " --yes"}`);
8456
+ }
8433
8457
  function requireYesIfHeadless(yes, retry) {
8434
8458
  if (!yes && !canPrompt()) throw usage(t("--yes required when not running interactively.", "Hace falta --yes cuando no corre en una terminal."), t(`Run: ${retry} --yes`, `Corre: ${retry} --yes`));
8435
8459
  }
@@ -9237,6 +9261,36 @@ async function readAll() {
9237
9261
  for await (const c of process.stdin) s += c;
9238
9262
  return s;
9239
9263
  }
9264
+ async function meMemories(sub, arg, text, v) {
9265
+ const ex = 'ghosty me memories ls [--agent A] | add "texto" [--agent A] | edit <id> "texto" | rm <id>';
9266
+ switch (sub ?? "ls") {
9267
+ case "ls": {
9268
+ const r = await api(`/api/v2/me/memories${v.agent ? `?agente=${enc3(v.agent)}` : ""}`);
9269
+ out(r, () => {
9270
+ const rows = r.memories.map((m) => ({ id: m.id, memory: m.content, agent: m.agentId ?? t("all", "todos"), from: m.source }));
9271
+ return rows.length ? table(rows, ["id", "memory", "agent", "from"]) : dim(t('Nothing yet. Add one: ghosty me memories add "I prefer short answers"', 'A\xFAn nada. Agrega una: ghosty me memories add "prefiero respuestas cortas"'));
9272
+ });
9273
+ return;
9274
+ }
9275
+ case "add": {
9276
+ if (!arg) throw usage(t("Missing the memory text.", "Falta el texto de la memoria."), ex);
9277
+ const r = await api(`/api/v2/me/memories`, { method: "POST", body: { content: arg, agentId: v.agent ?? null } });
9278
+ return out(r, () => `${lavender("\u2713")} ${t("Saved", "Guardada")} (${r.memory.id})`);
9279
+ }
9280
+ case "edit": {
9281
+ if (!arg || !text) throw usage(t("Missing the id or the new text.", "Falta el id o el texto nuevo."), ex);
9282
+ const r = await api(`/api/v2/me/memories/${enc3(arg)}`, { method: "PATCH", body: { content: text } });
9283
+ return out(r, () => `${lavender("\u2713")} ${t("Updated", "Actualizada")} (${r.memory.id})`);
9284
+ }
9285
+ case "rm": {
9286
+ if (!arg) throw usage(t("Missing the memory id.", "Falta el id de la memoria."), ex);
9287
+ const r = await api(`/api/v2/me/memories/${enc3(arg)}`, { method: "DELETE" });
9288
+ return out(r, () => `${lavender("\u2713")} ${t("Forgotten", "Olvidada")}`);
9289
+ }
9290
+ default:
9291
+ throw usage(t(`Unknown memories subcommand: ${sub}.`, `Subcomando de memories desconocido: ${sub}.`), ex);
9292
+ }
9293
+ }
9240
9294
  async function meFiles(sub, arg, v) {
9241
9295
  const ex = "ghosty me files ls [--q TEXT] [--kind image|video|audio|document|other] [--cursor C] | get <file-id> [--out FILE] | upload <file> | rm <file-id>";
9242
9296
  switch (sub) {
@@ -9515,7 +9569,7 @@ async function call(path, v, init = {}) {
9515
9569
  "x-ghosty-ts": ts,
9516
9570
  "x-ghosty-sig": sig,
9517
9571
  "content-type": "application/json",
9518
- "user-agent": `ghosty-cli/${"0.30.0"}`
9572
+ "user-agent": `ghosty-cli/${"0.32.0"}`
9519
9573
  },
9520
9574
  body: method === "GET" ? void 0 : raw
9521
9575
  });
@@ -10333,9 +10387,10 @@ async function channelsCmd(kind, sub, id, ref, v) {
10333
10387
  number: n2.phoneNumber ? `+${n2.phoneNumber}${n2.label !== n2.phoneNumber && n2.label !== n2.id ? ` ${n2.label}` : ""}` : n2.label,
10334
10388
  answers: n2.mine ? lavender(t("this agent", "este agente")) : n2.recordsOnly ? t("this agent (records only)", "este agente (s\xF3lo registra)") : n2.takenByOther ? t("another agent", "otro agente") : dim(t("nobody (silent)", "nadie (en silencio)")),
10335
10389
  "formmy forwards": fwd(n2),
10390
+ "ads only": n2.adsOnly ? lavender(t("yes", "s\xED")) : "",
10336
10391
  id: n2.id
10337
10392
  }));
10338
- return out(cur, () => rows.length ? table(rows, ["number", "answers", "formmy forwards", "id"]) : dim(t(`No WhatsApp Business numbers yet. Pair one at ${cur.salesUrl}`, `Todav\xEDa no hay n\xFAmeros de WhatsApp Business. Parea uno en ${cur.salesUrl}`)));
10393
+ return out(cur, () => rows.length ? table(rows, ["number", "answers", "formmy forwards", "ads only", "id"]) : dim(t(`No WhatsApp Business numbers yet. Pair one at ${cur.salesUrl}`, `Todav\xEDa no hay n\xFAmeros de WhatsApp Business. Parea uno en ${cur.salesUrl}`)));
10339
10394
  }
10340
10395
  if (sub !== "enable" && sub !== "disable") throw usage(t(`Unknown subcommand: ${sub}.`, `Subcomando desconocido: ${sub}.`), t("Try", "Prueba") + ": ghosty channels whatsapp ls | enable | disable | integrations | link | unlink | history \xB7 ghosty channels wa import | status | pause | groups");
10341
10396
  if (!ref) throw usage(t("Missing number.", "Falta el n\xFAmero."), t("Example", "Ejemplo") + `: ghosty channels whatsapp ${sub} ${id} +52 55 1234 5678`);
@@ -10479,17 +10534,27 @@ ${dim(eb ? t(`Next: ghosty channels wa groups ls ${id}`, `Sigue: ghosty channels
10479
10534
  }
10480
10535
  async function linkCmd(sub, id, v) {
10481
10536
  if (!id || !v.integration) throw usage(t("Missing agent id or --integration.", "Falta el id del agente o --integration."), t("Example", "Ejemplo") + `: ghosty channels whatsapp ${sub} <agent-id> --integration <formmy-integration-id> --answer off --dry-run`);
10537
+ if (v["ads-only"] !== void 0 && v["ads-only"] !== "on" && v["ads-only"] !== "off") throw usage(t("--ads-only takes on or off.", "--ads-only acepta on u off."));
10538
+ const adsOnly = v["ads-only"] === void 0 ? void 0 : v["ads-only"] === "on";
10539
+ if (sub === "link" && adsOnly !== void 0 && v.answer === void 0) {
10540
+ const path2 = `/api/v2/agents/${enc7(id)}/whatsapp/link`;
10541
+ const r2 = await api(path2, { method: "POST", body: { integrationId: v.integration, adsOnly, ...v["dry-run"] ? { dryRun: true } : {} } });
10542
+ return out(r2, () => `${r2.plan.map((x) => serverText(x)).join("\n")}${v["dry-run"] ? `
10543
+ ${dim(applyHint(`ghosty channels whatsapp link ${id} --integration ${v.integration} --ads-only ${v["ads-only"]}`))}` : ""}`);
10544
+ }
10482
10545
  if (sub === "link" && v.answer !== "on" && v.answer !== "off") throw usage(t("Missing --answer on|off.", "Falta --answer on|off."), t("on = this agent answers real customers \xB7 off = it only records (add --forward so gs still sees the conversations)", "on = este agente contesta a clientes reales \xB7 off = s\xF3lo registra (agrega --forward para que gs siga viendo las conversaciones)"));
10483
10546
  const path = `/api/v2/agents/${enc7(id)}/whatsapp/link`;
10484
- const body = { integrationId: v.integration, ...sub === "unlink" ? { unlink: true } : { answer: v.answer === "on", ...v.forward ? { forward: true } : {} } };
10547
+ const body = { integrationId: v.integration, ...sub === "unlink" ? { unlink: true } : { answer: v.answer === "on", ...v.forward ? { forward: true } : {}, ...adsOnly !== void 0 ? { adsOnly } : {} } };
10548
+ const cmd = `ghosty channels whatsapp ${sub} ${id} --integration ${v.integration}${sub === "link" ? ` --answer ${v.answer}${v.forward ? " --forward" : ""}${v["ads-only"] ? ` --ads-only ${v["ads-only"]}` : ""}` : ""}`;
10485
10549
  const plan = await api(path, { method: "POST", body: { ...body, dryRun: true } });
10486
10550
  const text = `${plan.plan.map((x) => serverText(x)).join("\n")}
10487
10551
  ${dim(`Formmy: ${plan.formmy.before.url ?? "null"} \u2192 ${plan.formmy.after.url ?? "null"} (${t("as", "como")} ${plan.formmy.by})`)}`;
10488
10552
  if (v["dry-run"]) return out(plan, () => `${text}
10489
- ${dim(t("Dry run: nothing changed.", "Simulacro: no se cambi\xF3 nada."))}`);
10553
+ ${dim(t("Dry run: nothing changed.", "Simulacro: no se cambi\xF3 nada."))}
10554
+ ${dim(applyHint(cmd))}`);
10490
10555
  const digits4 = (plan.number.phoneNumberId ?? v.integration).slice(-4);
10491
10556
  await confirm(`${bold(text)}
10492
- ${sub === "link" ? t("From now on this number's messages reach this agent in gs.", "Desde ahora los mensajes de este n\xFAmero le llegan a este agente en gs.") : t("This number goes back to where it was before.", "Este n\xFAmero regresa a donde estaba antes.")}`, { yes: v.yes, expect: digits4, retry: `ghosty channels whatsapp ${sub} ${id} --integration ${v.integration}${sub === "link" ? ` --answer ${v.answer}${v.forward ? " --forward" : ""}` : ""}` });
10557
+ ${sub === "link" ? t("From now on this number's messages reach this agent in gs.", "Desde ahora los mensajes de este n\xFAmero le llegan a este agente en gs.") : t("This number goes back to where it was before.", "Este n\xFAmero regresa a donde estaba antes.")}`, { yes: v.yes, expect: digits4, retry: cmd });
10493
10558
  const r = await api(path, { method: "POST", body });
10494
10559
  return out(r, () => `${lavender("\u2713")} ${sub === "link" ? t(`Linked (link ${r.linkId}). Check: ghosty channels whatsapp ls ${id} \xB7 ghosty turns ls ${id} --since 10m`, `Enlazado (enlace ${r.linkId}). Revisa: ghosty channels whatsapp ls ${id} \xB7 ghosty turns ls ${id} --since 10m`) : t("Unlinked: Formmy points where it did before.", "Desenlazado: Formmy apunta a donde apuntaba antes.")}`);
10495
10560
  }
@@ -10543,7 +10608,9 @@ async function historyCmd(id, v) {
10543
10608
  if (!Number.isInteger(days) || days < 1 || days > 30) throw usage(t("--days takes 1 to 30.", "--days acepta de 1 a 30."));
10544
10609
  const path = `/api/v2/agents/${enc7(id)}/whatsapp/history`;
10545
10610
  const media = v.media !== void 0;
10546
- const c = await api(path, { method: "POST", body: { integrationId: v.integration, days, dryRun: true, media } });
10611
+ const filters = { ...v["only-ads"] ? { onlyAds: true } : {}, ...v.phones ? { phones: v.phones.split(",").map((x) => x.trim()).filter(Boolean) } : {}, ...v.column ? { column: v.column } : {} };
10612
+ const cmd = `ghosty channels whatsapp history ${id} --integration ${v.integration} --days ${days}${v["only-ads"] ? " --only-ads" : ""}${v.phones ? ` --phones ${v.phones}` : ""}${v.column ? ` --column "${v.column}"` : ""}`;
10613
+ const c = await api(path, { method: "POST", body: { integrationId: v.integration, days, dryRun: true, media, ...v.list ? { list: true } : {}, ...filters } });
10547
10614
  const files = (p) => (p.mediaCopied ?? 0) + (p.mediaFailed ?? 0) ? ` \xB7 ${t(`${p.mediaCopied ?? 0} file(s) copied`, `${p.mediaCopied ?? 0} archivo(s) copiado(s)`)}${p.mediaFailed ? lavender(t(`, ${p.mediaFailed} failed (retry: --media)`, `, ${p.mediaFailed} fallaron (reintenta: --media)`)) : ""}` : "";
10548
10615
  if (media) {
10549
10616
  const missing = c.mediaMissing ?? 0;
@@ -10574,17 +10641,23 @@ ${dim(v["dry-run"] ? t("Dry run: nothing copied.", "Simulacro: no se copi\xF3 na
10574
10641
  );
10575
10642
  const missingHint = c.mediaMissing ? `
10576
10643
  ${lavender(t(`${c.mediaMissing} file(s) already imported have no copy in gs: add --media to copy them (Formmy keeps them 7 days).`, `${c.mediaMissing} archivo(s) ya importado(s) no tienen copia en gs: agrega --media para copiarlos (Formmy los guarda 7 d\xEDas).`))}` : "";
10577
- if (v["dry-run"]) return out(c, () => `${plan}${missingHint}
10578
- ${dim(t("Dry run: nothing imported. Re-running later never duplicates.", "Simulacro: no se import\xF3 nada. Correrlo otra vez nunca duplica."))}`);
10579
- if (!c.conversations) return out({ ...c, imported: 0 }, () => dim(t(`Nothing to import: ${plan}.`, `Nada que importar: ${plan}.`)));
10580
- await confirm(`${bold(plan)}
10581
- ${t(`Cards appear on the team's board (no "new lead" alerts).`, "Las tarjetas aparecen en el tablero del equipo (sin avisos de \xABlead nuevo\xBB).")}`, { yes: v.yes, retry: `ghosty channels whatsapp history ${id} --integration ${v.integration} --days ${days}` });
10644
+ const listText = c.list ? `
10645
+ ${table(c.list.map((x) => ({ phone: x.phone, name: (x.name ?? "").slice(0, 20), ad: x.fromAd ? lavender(t("ad", "anuncio")) : "", msgs: x.messages, first: x.firstMessage.slice(0, 60) })), ["phone", "name", "ad", "msgs", "first"])}
10646
+ ${dim(t(`${c.matching} match the filters`, `${c.matching} pasan los filtros`))}` : "";
10647
+ const filtered = Boolean(v["only-ads"] || v.phones);
10648
+ if (v["dry-run"]) return out(c, () => `${plan}${listText}${missingHint}
10649
+ ${dim(t("Dry run: nothing imported. Re-running later never duplicates.", "Simulacro: no se import\xF3 nada. Correrlo otra vez nunca duplica."))}
10650
+ ${dim(applyHint(cmd))}`);
10651
+ if (!c.conversations || filtered && c.matching === 0) return out({ ...c, imported: 0 }, () => dim(t(`Nothing to import: ${plan}.`, `Nada que importar: ${plan}.`)));
10652
+ await confirm(`${bold(plan)}${filtered ? `
10653
+ ${t(`only the ${c.matching} that match the filters`, `s\xF3lo las ${c.matching} que pasan los filtros`)}` : ""}
10654
+ ${t(`Cards appear on the team's board (no "new lead" alerts).`, "Las tarjetas aparecen en el tablero del equipo (sin avisos de \xABlead nuevo\xBB).")}`, { yes: v.yes, retry: cmd });
10582
10655
  const sum = { newCards: 0, updatedCards: 0, messages: 0, orders: 0, suppressed: 0, mediaCopied: 0, mediaFailed: 0, paused: 0, pausedForever: 0, manual: 0 };
10583
10656
  let cursor = 0;
10584
10657
  let done = 0;
10585
10658
  while (cursor !== null) {
10586
10659
  const at = cursor;
10587
- const p = await pageWithRetry(() => api(path, { method: "POST", body: { integrationId: v.integration, days, cursor: at } }), `${done}/${c.conversations}`);
10660
+ const p = await pageWithRetry(() => api(path, { method: "POST", body: { integrationId: v.integration, days, cursor: at, ...filters } }), `${done}/${c.conversations}`);
10588
10661
  for (const k of Object.keys(sum)) sum[k] += p[k] ?? 0;
10589
10662
  done += p.page;
10590
10663
  if (!globals.json) info(dim(` ${done}/${p.total} ${t("conversations", "conversaciones")}`));
@@ -10662,7 +10735,8 @@ async function boardsCmd(sub, a1, a2, v, context) {
10662
10735
  case "create": {
10663
10736
  if (!a1 || !a2) throw usage(t("Missing workspace or name.", "Falta el espacio o el nombre."), t("Example", "Ejemplo") + ': ghosty boards create <workspace> "Siiqtec - Ixmiquilpan" --columns-from main');
10664
10737
  const r = await api(`/api/v2/workspaces/${enc8(a1)}/boards`, { method: "POST", body: { name: a2, ...v["columns-from"] ? { columnsFrom: v["columns-from"] } : {} } });
10665
- return out(r, () => `${lavender("\u2713")} ${t(`Board \xAB${r.name}\xBB created${r.columnsFrom ? ` with the ${r.columns} column(s) of \xAB${r.columnsFrom}\xBB` : ""}.`, `Tablero \xAB${r.name}\xBB creado${r.columnsFrom ? ` con las ${r.columns} columna(s) de \xAB${r.columnsFrom}\xBB` : ""}.`)} ${dim(`${t("Send an agent's cards there", "Manda ah\xED las tarjetas de un agente")}: ghosty boards assign <agent-id> "${r.name}"`)}`);
10738
+ return out(r, () => `${lavender("\u2713")} ${t(`Board \xAB${r.name}\xBB created${r.columnsFrom ? ` with the ${r.columns} column(s) of \xAB${r.columnsFrom}\xBB` : ""}.`, `Tablero \xAB${r.name}\xBB creado${r.columnsFrom ? ` con las ${r.columns} columna(s) de \xAB${r.columnsFrom}\xBB` : ""}.`)} ${dim(`${t("Send an agent's cards there", "Manda ah\xED las tarjetas de un agente")}: ghosty boards assign <agent-id> "${r.name}"`)}${r.warning ? `
10739
+ ${lavender(`\u26A0\uFE0F ${serverText(r.warning)}`)}` : ""}`);
10666
10740
  }
10667
10741
  case "rm": {
10668
10742
  if (!a1 || !a2) throw usage(t("Missing workspace or board.", "Falta el espacio o el tablero."), t("Example", "Ejemplo") + ': ghosty boards rm <workspace> "Prueba"');
@@ -10752,7 +10826,8 @@ var HELPS_ES = {
10752
10826
  ["--from-workspace", "rm: lo desactiva en cada Teams donde est\xE9 y luego lo borra (due\xF1o o staff)"],
10753
10827
  ["--prompt-mode M", "set: append (default: su prompt + la persona de Ghosty) o replace (s\xF3lo su prompt)"],
10754
10828
  ["--sponsor/--owner EMAIL", "ls: agentes de CUALQUIER cuenta que paga / de quien es ese correo (staff de Ghosty); luego se operan por id"],
10755
- ["--ids", "ls: s\xF3lo los ids, uno por rengl\xF3n (para xargs)"]
10829
+ ["--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"]
10756
10831
  ],
10757
10832
  notes: [
10758
10833
  "doctor sale con 1 cuando encuentra un problema, as\xED puede frenar un script o un CI.",
@@ -10806,7 +10881,7 @@ var HELPS_ES = {
10806
10881
  },
10807
10882
  turns: {
10808
10883
  summary: "Los turnos recientes de un agente: qu\xE9 contest\xF3, qu\xE9 fall\xF3 y por qu\xE9, y en qu\xE9 se fue el tiempo de un turno.",
10809
- replace: [["# last turn: each tool, its seconds, output tokens", "# \xFAltimo turno: cada tool, sus segundos, tokens de salida"], ["# stop the turn AND its background jobs", "# detiene el turno Y sus encargos en fondo"], ["# staff: real failure rate by engine and error", "# staff: tasa real de fallos por motor y por error"]],
10884
+ replace: [["# last turn: each tool, its seconds, output tokens", "# \xFAltimo turno: cada tool, sus segundos, tokens de salida"], ["# stop the turn AND its background jobs", "# detiene el turno Y sus encargos en fondo"], ["# staff: real failure rate by engine, channel (Teams/A2A too) and error", "# staff: tasa real de fallos por motor, canal (tambi\xE9n Teams y A2A) y error"]],
10810
10885
  flags: [
10811
10886
  ["--errors", "s\xF3lo turnos fallidos (igual que --status error)"],
10812
10887
  ["--skipped", "ls: mensajes de clientes de WhatsApp Business que NO abrieron turno, y por qu\xE9 (operador activo, s\xF3lo registra, pausa, reacci\xF3n, canal apagado, sin saldo)"],
@@ -10862,7 +10937,10 @@ var HELPS_ES = {
10862
10937
  replace: [['"what to do"', '"qu\xE9 hacer"'], ["# answers in that thread as if mentioned", "# contesta en ese hilo como si lo mencionaran"]],
10863
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."]
10864
10939
  },
10865
- me: { summary: "Tu cuenta: archivos y uso." },
10940
+ me: {
10941
+ summary: "Tu cuenta: archivos, memorias y uso.",
10942
+ notes: ["Las memorias viajan a tus agentes s\xF3lo en tus chats personales (app, /c, notch), nunca a WhatsApp, Messenger ni demos. --agent la limita a ese agente."]
10943
+ },
10866
10944
  credentials: {
10867
10945
  summary: "Llaves de proveedores en la b\xF3veda de tu cuenta (cifradas; s\xF3lo regresan los \xFAltimos 4 caracteres).",
10868
10946
  replace: [["# hidden prompt", "# pregunta oculta"]],
@@ -10880,6 +10958,10 @@ var HELPS_ES = {
10880
10958
  ["--yes", "enable / groups enable: se salta la confirmaci\xF3n (obligatorio sin terminal)"],
10881
10959
  ["--role R", "groups: equipo (el equipo: escribe en bases de datos, tools de admin) o cliente (como cualquier cliente)"],
10882
10960
  ["--cached", "groups ls: no le pide a WhatsApp la lista en vivo (m\xE1s r\xE1pido; puede faltar alg\xFAn grupo nuevo)"],
10961
+ ["--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
+ ["--list", "history --dry-run: CU\xC1LES conversaciones (tel\xE9fono, nombre, primer mensaje, mensajes, si vino de anuncio)"],
10963
+ ["--only-ads / --phones a,b", "history: importa (o lista) s\xF3lo las de anuncio y/o esos tel\xE9fonos"],
10964
+ ["--column NAME", "history: las tarjetas nuevas nacen en esa columna en vez de la primera"],
10883
10965
  ["--name N", "groups set: el nombre con que lo ve gs (p. ej. un grupo guardado con su jid de nombre); el asunto en WhatsApp no cambia"],
10884
10966
  ["--integration ID", "link/unlink/history: el id de la Integration del n\xFAmero de WhatsApp Business en Formmy"],
10885
10967
  ["--email E", "integrations (staff): lista los n\xFAmeros de otra cuenta de Formmy"],
@@ -10994,7 +11076,7 @@ var HELPS_ES = {
10994
11076
  flags: [
10995
11077
  ["--boxes", "las cajas del espacio contra su tope (spares, dormidas) y, por agente: conversaciones por caja, cajas, despiertas, spares, turnos corriendo ahora"],
10996
11078
  ["--workspace SLUG", "qu\xE9 espacio (due\xF1o o staff); con --boxes el default es el de ghosty use"],
10997
- ["--by agent|day|agent,day", "--workspace: filas por agente (default), por d\xEDa (CDMX) o las dos"],
11079
+ ["--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)"],
10998
11080
  ["--days N", "--workspace: cu\xE1ntos d\xEDas hacia atr\xE1s (default 14, m\xE1x. 92)"]
10999
11081
  ],
11000
11082
  notes: ["failed = turnos que terminaron en error o se detuvieron. \xABno balance\xBB = mensajes que no abrieron turno porque se acab\xF3 la bolsa (ghosty turns ls --workspace X --quota-hits los lista)."]
@@ -11034,11 +11116,13 @@ var HELPS_ES = {
11034
11116
  },
11035
11117
  novedades: {
11036
11118
  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.",
11037
- 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."]
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."]
11038
11120
  },
11039
11121
  spaces: {
11040
- summary: "Operaciones del staff de Ghosty sobre un espacio: mover \xABpagado hasta\xBB y renombrar su slug. Cada una queda en la bit\xE1cora del espacio.",
11122
+ 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.",
11041
11123
  flags: [
11124
+ ["--user EMAIL", "ls (staff): los espacios de esa persona"],
11125
+ ["--combo C", "set (staff): teams | sales | both \u2014 sin Ventas sus tableros existen pero nadie los ve"],
11042
11126
  ["--until DATE", "renew: el nuevo \xABpagado hasta\xBB (AAAA-MM-DD)"],
11043
11127
  ["--note TEXT", "renew: por qu\xE9 (va a la bit\xE1cora)"],
11044
11128
  ["-n, --dry-run", "ense\xF1a qu\xE9 cambia, no cambia nada"],
@@ -11047,7 +11131,7 @@ var HELPS_ES = {
11047
11131
  notes: ["Con suscripci\xF3n de Stripe renew se niega: la fecha la mueve la factura pagada.", "rename cambia s\xF3lo el slug (id y namespace se quedan, as\xED que tablero, Stripe y WhatsApp siguen funcionando); las ligas viejas <slug>.sales dejan de servir."]
11048
11132
  }
11049
11133
  };
11050
- var HELP_ALIASES = { creds: "credentials", board: "cards", conversations: "chat" };
11134
+ var HELP_ALIASES = { creds: "credentials", board: "cards", conversations: "chat", workspaces: "spaces" };
11051
11135
 
11052
11136
  // src/help.ts
11053
11137
  var DOCS = "https://www.ghosty.studio/docs/cli";
@@ -11122,7 +11206,8 @@ var HELPS = {
11122
11206
  ["--from-workspace", "rm: turn it off in every Teams where it's active, then delete it (owner or staff)"],
11123
11207
  ["--prompt-mode M", "set: append (default: its prompt + Ghosty's persona) or replace (its prompt only)"],
11124
11208
  ["--sponsor/--owner EMAIL", "ls: agents of ANY account paid by / owned by that email (Ghosty staff); then operate them by id"],
11125
- ["--ids", "ls: only the ids, one per line (for xargs)"]
11209
+ ["--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"]
11126
11211
  ],
11127
11212
  examples: [
11128
11213
  "ghosty agents doctor cmu7pgbyr0001gee3ruk172mm",
@@ -11233,7 +11318,7 @@ var HELPS = {
11233
11318
  },
11234
11319
  turns: {
11235
11320
  summary: "An agent's recent turns: what answered, what failed and why, and where one turn's time went.",
11236
- usage: ["ghosty turns ls <agent-id> [--errors | --status STATE | --skipped] [--since 1h] [--limit N]", "ghosty turns ls --workspace <slug> --quota-hits [--days 14]", "ghosty turns show <agent-id> [turn-id | last] [--full]", "ghosty turns cancel <agent-id> <conversation-id> # stop the turn AND its background jobs", "ghosty turns stats [--since 14d] [--all] # staff: real failure rate by engine and error"],
11321
+ usage: ["ghosty turns ls <agent-id> [--errors | --status STATE | --skipped] [--since 1h] [--limit N]", "ghosty turns ls --workspace <slug> --quota-hits [--days 14]", "ghosty turns show <agent-id> [turn-id | last] [--full]", "ghosty turns cancel <agent-id> <conversation-id> # stop the turn AND its background jobs", "ghosty turns stats [--since 14d] [--all] # staff: real failure rate by engine, channel (Teams/A2A too) and error"],
11237
11322
  flags: [
11238
11323
  ["--errors", "only failed turns (same as --status error)"],
11239
11324
  ["--skipped", "ls: WhatsApp Business customer messages that did NOT open a turn, and why (operator active, records only, paused, reaction, channel off, no balance)"],
@@ -11324,9 +11409,10 @@ var HELPS = {
11324
11409
  learn: `${REF}#programar-turnos`
11325
11410
  },
11326
11411
  me: {
11327
- summary: "Your account: files and usage.",
11328
- usage: ["ghosty me files ls [--q TEXT] [--kind image|video|audio|document|other] [--cursor C]", "ghosty me files get <file-id> [--out FILE]", "ghosty me files upload <file> | rm <file-id>", "ghosty usage"],
11329
- examples: ["ghosty me files ls --kind document", "ghosty me files ls --q contrato", "ghosty me files get <id> --out contrato.pdf", 'ghosty chat <agent> "res\xFAmelo" --file <id>'],
11412
+ summary: "Your account: files, memories and usage.",
11413
+ usage: ["ghosty me files ls [--q TEXT] [--kind image|video|audio|document|other] [--cursor C]", "ghosty me files get <file-id> [--out FILE]", "ghosty me files upload <file> | rm <file-id>", 'ghosty me memories ls [--agent A] | add "text" [--agent A] | edit <id> "text" | rm <id>', "ghosty usage"],
11414
+ examples: ["ghosty me files ls --kind document", "ghosty me files ls --q contrato", "ghosty me files get <id> --out contrato.pdf", 'ghosty chat <agent> "res\xFAmelo" --file <id>', 'ghosty me memories add "I prefer short answers"'],
11415
+ notes: ["Memories travel to your agents only in your personal chats (app, /c, notch), never to WhatsApp, Messenger or demo threads. --agent limits one to that agent."],
11330
11416
  learn: `${REF}#tu-cuenta`
11331
11417
  }
11332
11418
  };
@@ -11369,6 +11455,10 @@ HELPS.channels = {
11369
11455
  ["--yes", "enable / groups enable: skip the confirmation (required without a terminal)"],
11370
11456
  ["--role R", "groups: equipo (the team: writes to databases, admin tools) or cliente (like any customer)"],
11371
11457
  ["--cached", "groups ls: don't ask WhatsApp for the live list (faster; may miss new groups)"],
11458
+ ["--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
+ ["--list", "history --dry-run: WHICH conversations (phone, name, first message, messages, from an ad)"],
11460
+ ["--only-ads / --phones a,b", "history: import (or list) only the ones from an ad and/or those phones"],
11461
+ ["--column NAME", "history: new cards are born in that column instead of the first one"],
11372
11462
  ["--name N", "groups set: the name gs shows (e.g. a group saved with its jid as name); WhatsApp's subject doesn't change"],
11373
11463
  ["--integration ID", "link/unlink/history: the WhatsApp Business number's Integration id in Formmy"],
11374
11464
  ["--email E", "integrations (staff): list the numbers of another Formmy account"],
@@ -11516,7 +11606,7 @@ HELPS.usage = {
11516
11606
  flags: [
11517
11607
  ["--boxes", "the workspace's boxes against its limit (spares, asleep) and, per agent: conversations per box, boxes, awake, spares, turns running now"],
11518
11608
  ["--workspace SLUG", "which workspace (owner or staff); with --boxes the default is the one from ghosty use"],
11519
- ["--by agent|day|agent,day", "--workspace: rows per agent (default), per day (CDMX) or both"],
11609
+ ["--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)"],
11520
11610
  ["--days N", "--workspace: how far back (default 14, max 92)"]
11521
11611
  ],
11522
11612
  examples: ["ghosty usage", "ghosty usage --json", "ghosty usage --workspace descti --by agent,day --days 14", "ghosty usage --boxes --workspace siiqtec"],
@@ -11580,29 +11670,32 @@ HELPS.novedades = {
11580
11670
  summary: "Ghosty staff: the Android app's announcements (what's new). Each one needs its own illustration; same brakes as the admin: test on your phone first, push 9\u201321 h CDMX, 72 h between pushes to everyone.",
11581
11671
  usage: [
11582
11672
  "ghosty novedades ls",
11583
- 'ghosty novedades create --title T --body B --image hero.png [--link ruta] [--button "Abrir"] [--to a@x.com,b@y.com] [--publish]',
11584
- "ghosty novedades set <id> [--title] [--body] [--image FILE] [--link] [--button] [--to] [--publish|--draft]",
11673
+ 'ghosty novedades create --title T --body B --image hero.png [--link ruta] [--button "Abrir"] [--to a@x.com,b@y.com] [--min-version N] [--publish]',
11674
+ "ghosty novedades set <id> [--title] [--body] [--image FILE] [--link] [--button] [--to] [--min-version N] [--publish|--draft]",
11585
11675
  "ghosty novedades test <id>",
11586
11676
  'ghosty novedades send <id> [--at "2026-10-02T12:00"]',
11587
11677
  "ghosty novedades rm <id>"
11588
11678
  ],
11589
11679
  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"'],
11590
- notes: ["--image is published to img.ghosty.studio (WebP 1200 px). --to empty = everyone. send without --at pushes now (asks); --at '' removes the schedule."],
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."],
11591
11681
  learn: `${REF}#soporte`
11592
11682
  };
11593
11683
  HELPS.spaces = {
11594
- summary: "Ghosty staff operations on a workspace: move \xABpaid until\xBB and rename its slug. Each one is logged in the workspace's audit.",
11595
- usage: ["ghosty spaces renew <workspace> --until YYYY-MM-DD [--note TEXT] [--dry-run] [--yes]", "ghosty spaces rename <old> <new> [--dry-run] [--yes]"],
11684
+ summary: "Workspaces: list yours (or, staff, anyone's), and Ghosty staff operations \u2014 join/leave a client's workspace, change its combo, move \xABpaid until\xBB, rename its slug. Each change is logged in the workspace's audit.",
11685
+ usage: ["ghosty spaces ls [--user EMAIL]", "ghosty spaces join|leave <workspace>", "ghosty spaces set <workspace> --combo teams|sales|both [--dry-run]", "ghosty spaces renew <workspace> --until YYYY-MM-DD [--note TEXT] [--dry-run] [--yes]", "ghosty spaces rename <old> <new> [--dry-run] [--yes]"],
11596
11686
  flags: [
11687
+ ["--user EMAIL", "ls (staff): that person's workspaces"],
11688
+ ["--combo C", "set (staff): teams | sales | both \u2014 without Sales its boards exist but nobody sees them"],
11597
11689
  ["--until DATE", "renew: the new \xABpaid until\xBB (YYYY-MM-DD)"],
11598
11690
  ["--note TEXT", "renew: why (goes into the audit)"],
11599
11691
  ["-n, --dry-run", "show what changes, change nothing"],
11600
11692
  ["--yes", "skip the confirmation (required without a terminal)"]
11601
11693
  ],
11602
- examples: ['ghosty spaces renew descti --until 2026-11-01 --note "pag\xF3 octubre"', "ghosty spaces rename totequim siiqtec --dry-run"],
11694
+ examples: ["ghosty spaces ls", "ghosty spaces ls --user brenda@siiqtec.com", "ghosty spaces join denik", "ghosty spaces set denik --combo sales --dry-run", 'ghosty spaces renew descti --until 2026-11-01 --note "pag\xF3 octubre"', "ghosty spaces rename totequim siiqtec --dry-run"],
11603
11695
  notes: ["With a Stripe subscription renew refuses: the paid invoice moves the date.", "rename changes only the slug (id and namespace stay, so board, Stripe and WhatsApp keep working); old <slug>.sales links stop working."],
11604
11696
  learn: `${REF}#espacios`
11605
11697
  };
11698
+ HELPS.workspaces = HELPS.spaces;
11606
11699
  function helpText(cmd) {
11607
11700
  const base = HELPS[cmd];
11608
11701
  if (!base) return void 0;
@@ -11630,7 +11723,7 @@ import { mkdir as mkdir4, readFile as readFile9, writeFile as writeFile6 } from
11630
11723
  import { homedir as homedir3 } from "node:os";
11631
11724
  import { dirname as dirname3, join as join6 } from "node:path";
11632
11725
  var FILE = join6(process.env.XDG_CONFIG_HOME || join6(homedir3(), ".config"), "ghosty", "update.json");
11633
- var DAY = 24 * 60 * 60 * 1e3;
11726
+ var TTL = 60 * 60 * 1e3;
11634
11727
  var UPGRADE = "npm i -g @ghostystudio/cli@latest";
11635
11728
  function newer(a, b) {
11636
11729
  const pa = a.split(".").map(Number);
@@ -11638,14 +11731,15 @@ function newer(a, b) {
11638
11731
  for (let i = 0; i < 3; i++) if ((pa[i] ?? 0) !== (pb[i] ?? 0)) return (pa[i] ?? 0) > (pb[i] ?? 0);
11639
11732
  return false;
11640
11733
  }
11641
- async function latestVersion() {
11734
+ async function latestVersion(current2) {
11642
11735
  if (process.env.GHOSTY_NO_UPDATE_CHECK || process.env.CI) return null;
11643
11736
  let cache2 = {};
11644
11737
  try {
11645
11738
  cache2 = JSON.parse(await readFile9(FILE, "utf8"));
11646
11739
  } catch {
11647
11740
  }
11648
- if (!cache2.checkedAt || Date.now() - cache2.checkedAt > DAY) {
11741
+ if (current2 && cache2.latest && newer(current2, cache2.latest)) cache2.checkedAt = 0;
11742
+ if (!cache2.checkedAt || Date.now() - cache2.checkedAt > TTL) {
11649
11743
  try {
11650
11744
  const r = await fetch("https://registry.npmjs.org/@ghostystudio/cli/latest", { signal: AbortSignal.timeout(1500) });
11651
11745
  const latest = (await r.json()).version;
@@ -11658,7 +11752,7 @@ async function latestVersion() {
11658
11752
  return cache2.latest ?? null;
11659
11753
  }
11660
11754
  async function updateNotice(current2) {
11661
- const latest = await latestVersion();
11755
+ const latest = await latestVersion(current2);
11662
11756
  return latest && newer(latest, current2) ? t(`Update available: ${current2} \u2192 ${latest}. Run: ${UPGRADE}`, `Hay versi\xF3n nueva: ${current2} \u2192 ${latest}. Corre: ${UPGRADE}`) : null;
11663
11757
  }
11664
11758
 
@@ -12083,7 +12177,33 @@ ${bagsText(r)}`);
12083
12177
  }
12084
12178
  }
12085
12179
  async function spacesCmd(sub, slug, arg, v, context) {
12086
- const ex = "ghosty spaces renew <workspace> --until 2026-11-01 [--note TEXT] \xB7 rename <old> <new> [--dry-run]";
12180
+ const ex = "ghosty spaces ls [--user EMAIL] \xB7 join|leave <workspace> \xB7 set <workspace> --combo teams|sales|both \xB7 renew <workspace> --until 2026-11-01 [--note TEXT] \xB7 rename <old> <new> [--dry-run]";
12181
+ if (!sub || sub === "ls" || sub === "list") {
12182
+ const r = await api(`/api/v2/workspaces${v.user ? `?user=${enc10(v.user)}` : ""}`);
12183
+ const rows = r.workspaces.map((w) => ({ workspace: w.slug, role: w.role ?? "", plan: w.plan, combo: w.combo, owner: w.owner ?? "", paid: w.paidUntil ?? "" }));
12184
+ return out(r, () => rows.length ? `${bold(r.user)}
12185
+ ${table(rows, ["workspace", "role", "plan", "combo", "owner", "paid"])}` : dim(t(`${r.user} has no workspaces.`, `${r.user} no tiene espacios.`)));
12186
+ }
12187
+ if (sub === "join" || sub === "leave") {
12188
+ slug = needSpace(slug, `ghosty spaces ${sub} <workspace>`);
12189
+ const r = await api(wsPath(slug, "/staff"), { method: "POST", body: { join: sub === "join" } });
12190
+ return out(r, () => `${lavender("\u2713")} ${r.joined ? t(`You're in ${r.workspace} as staff (no seat; logged). Leave: ghosty spaces leave ${r.workspace}`, `Entraste a ${r.workspace} como staff (sin asiento; queda en bit\xE1cora). Salir: ghosty spaces leave ${r.workspace}`) : t(`You left ${r.workspace}.`, `Saliste de ${r.workspace}.`)}`);
12191
+ }
12192
+ if (sub === "set") {
12193
+ slug = needSpace(slug ?? context, "ghosty spaces set <workspace> --combo sales");
12194
+ if (!v.combo) throw usage(t("Nothing to change.", "Nada que cambiar."), "--combo teams|sales|both");
12195
+ const path = wsPath(slug, "/combo");
12196
+ const plan = await api(path, { method: "POST", body: { combo: v.combo, dryRun: true } });
12197
+ const line2 = `${plan.workspace}: combo ${plan.from} \u2192 ${plan.to}`;
12198
+ if (!plan.changed) return out(plan, () => dim(t(`Nothing to do: ${plan.workspace} is already ${plan.to}.`, `Nada que hacer: ${plan.workspace} ya es ${plan.to}.`)));
12199
+ if (v["dry-run"]) return out(plan, () => `${line2}
12200
+ ${dim(t("Dry run: nothing changed.", "Simulaci\xF3n: no cambi\xF3 nada."))}`);
12201
+ requireYesIfHeadless(v.yes, `ghosty spaces set ${slug} --combo ${v.combo}`);
12202
+ await confirm(`${line2}
12203
+ ${t("Change it?", "\xBFCambiarlo?")}`, { yes: v.yes, retry: `ghosty spaces set ${slug} --combo ${v.combo}` });
12204
+ const r = await api(path, { method: "POST", body: { combo: v.combo } });
12205
+ return out(r, () => `${lavender("\u2713")} ${r.workspace}: combo ${r.to}`);
12206
+ }
12087
12207
  if (sub === "renew") {
12088
12208
  slug = needSpace(slug ?? context, ex);
12089
12209
  if (!v.until) throw usage(t("Missing --until YYYY-MM-DD.", "Falta --until AAAA-MM-DD."), `ghosty spaces renew ${slug} --until 2026-11-01`);
@@ -12331,10 +12451,23 @@ async function turnStats(v) {
12331
12451
  dim(Object.entries(r.byState).map(([k, n]) => `${k} ${n}`).join(" \xB7 ")),
12332
12452
  "",
12333
12453
  table(r.byEngine.map((e) => ({ engine: e.engine, turns: e.turns, failed: e.failed, rate: `${e.failRate}%` })), ["engine", "turns", "failed", "rate"]),
12454
+ ...r.byCanal?.length ? ["", table(r.byCanal.map((c) => ({ canal: c.canal, turns: c.turns, failed: c.failed, rate: `${c.failRate}%` })), ["canal", "turns", "failed", "rate"])] : [],
12334
12455
  ...r.topErrors.length ? ["", table(r.topErrors.map((e) => ({ n: e.n, error: e.error })), ["n", "error"])] : []
12335
12456
  ].join("\n")
12336
12457
  );
12337
12458
  }
12459
+ async function fleetBoxes() {
12460
+ const r = await api("/api/v2/support/boxes?by=template");
12461
+ const rows = r.templates.map((x) => ({ template: x.template, boxes: x.boxes, on: x.on, asleep: x.asleep, ram: x.memMb.map((m) => `${m / 1024} GB`).join(", "), vcpu: x.vcpus.join(", "), "ram on": `${(x.ramOnMb / 1024).toFixed(1)} GB` }));
12462
+ out(r, () => `${bold(t(`${r.total} box(es), ${r.on} awake`, `${r.total} caja(s), ${r.on} despiertas`))}
12463
+ ${table(rows, ["template", "boxes", "on", "asleep", "ram", "vcpu", "ram on"])}`);
12464
+ }
12465
+ async function appAgentsOf(email, client) {
12466
+ const r = await api(`/api/v2/support/app-agents?user=${enc12(email)}${client && client !== "all" ? `&client=${enc12(client)}` : ""}`);
12467
+ 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) : "" }));
12468
+ 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"])}`);
12470
+ }
12338
12471
  async function pushLog(email, v) {
12339
12472
  if (!email) throw usage(t("Missing --user.", "Falta --user."), "ghosty push log --user ana@empresa.com [--since 2d]");
12340
12473
  const r = await api(`/api/v2/support/push-log?user=${enc12(email)}&since=${enc12(v.since ?? "2d")}${v.limit ? `&limit=${enc12(v.limit)}` : ""}`);
@@ -12344,7 +12477,7 @@ ${table(rows, ["at", "kind", "to", "status", "thread", "title", "detail"])}` : d
12344
12477
  }
12345
12478
  async function novedadesCmd(sub, id, v) {
12346
12479
  const base = "/api/v2/support/announcements";
12347
- const ex = 'ghosty novedades ls \xB7 create --title T --body B --image hero.png [--link ruta] [--button "Abrir"] [--to a@x.com,b@y.com] [--publish] \xB7 set <id> \u2026 \xB7 test <id> \xB7 send <id> [--at "2026-10-02T12:00"] \xB7 rm <id>';
12480
+ const ex = 'ghosty novedades ls \xB7 create --title T --body B --image hero.png [--link ruta] [--button "Abrir"] [--to a@x.com,b@y.com] [--min-version 12] [--publish] \xB7 set <id> \u2026 \xB7 test <id> \xB7 send <id> [--at "2026-10-02T12:00"] \xB7 rm <id>';
12348
12481
  const fields = async () => ({
12349
12482
  ...v.title !== void 0 ? { title: v.title } : {},
12350
12483
  ...v.body !== void 0 ? { body: v.body } : {},
@@ -12352,10 +12485,12 @@ async function novedadesCmd(sub, id, v) {
12352
12485
  ...v.link !== void 0 ? { enlace: v.link || null } : {},
12353
12486
  ...v.button !== void 0 ? { boton: v.button || null } : {},
12354
12487
  ...v.to !== void 0 ? { destinatarios: v.to || null } : {},
12488
+ // «Requiere versión ≥ N» (versionCode de Android): el botón dice «Actualizar». Vacío = la quita.
12489
+ ...v["min-version"] !== void 0 ? { minVersionCode: v["min-version"] ? Number(v["min-version"]) : null } : {},
12355
12490
  ...v.publish ? { published: true } : v.draft ? { published: false } : {}
12356
12491
  });
12357
12492
  const line2 = (a) => `${bold(a.title)} ${dim(a.id)}
12358
- ${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")}`;
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}+`) : ""}`;
12359
12494
  switch (sub ?? "ls") {
12360
12495
  case "ls":
12361
12496
  case "list": {
@@ -12415,18 +12550,21 @@ ${bold("Session")}
12415
12550
  whoami Show who you are signed in as
12416
12551
  use [workspace] Default workspace for boards, agents create and agents ls [--clear]
12417
12552
  completion zsh|bash|fish Tab completion for commands, flags, agents and workspaces
12418
- usage Your plan and how much you've used this week and month [--boxes: boxes vs the workspace limit]
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)]
12419
12554
  usage --workspace SLUG A workspace's turns and tokens per agent/day [--by agent,day] [--days 14]
12420
12555
  plan apply [workspace] Apply a scheduled plan now, showing what changes [--dry-run]
12421
12556
  engines ls Engines and models valid for --engine/--model [--models]
12422
12557
 
12423
12558
  ${bold("Workspaces")} ${dim("(changing them: Ghosty staff)")}
12424
12559
  bags ls|create|set|rm <workspace> ["bag"] Token bags its agents spend from [--tokens 20M] [--recurring on|off] [--hard-cap on|off] [--note]
12560
+ spaces ls Your workspaces (role, plan, combo) [--user EMAIL: someone else's]
12561
+ spaces join|leave <workspace> Enter or leave a client's workspace as staff (no seat; logged)
12562
+ spaces set <workspace> --combo sales teams | sales | both (without Sales its boards are invisible)
12425
12563
  spaces renew <workspace> --until DATE Move \xABpaid until\xBB (logged) [--note] [--dry-run]
12426
12564
  spaces rename <old> <new> Change its slug only [--dry-run]
12427
12565
 
12428
12566
  ${bold("Agents")}
12429
- agents ls List your agents [--engine E] [--ids: only ids, for xargs]
12567
+ agents ls List your agents [--engine E] [--ids: only ids, for xargs] \xB7 --user EMAIL --app all|android|ios|desktop: as their app sees it (staff)
12430
12568
  [--sponsor EMAIL | --owner EMAIL] any account's agents (Ghosty staff)
12431
12569
  agents create Create an agent --name [--engine] [--model] [--prompt | --prompt-file FILE] [--env K=V,\u2026]
12432
12570
  [--workspace SLUG] [--channels teams=off,\u2026] born in a workspace, active in its Teams
@@ -12468,8 +12606,8 @@ ${bold("Configure an agent")}
12468
12606
  mcp get|set <id> MCP servers [--file servers.json]
12469
12607
  channels whatsapp ls|enable|disable <id> [number] Which WhatsApp Business numbers it answers [--dry-run]
12470
12608
  channels whatsapp integrations <id> Its owner's WhatsApp Business numbers in Formmy (number, who answers today, Integration id)
12471
- channels whatsapp link|unlink <id> --integration ID --answer on|off Bring a WhatsApp Business number from Formmy/EasyBits to this agent, or send it back [--forward] [--dry-run]
12472
- channels whatsapp history <id> --integration ID [--days 7] Bring its recent cards, messages, orders and files from Formmy (no duplicates; Formmy keeps files 7 days) [--media] [--dry-run]
12609
+ channels whatsapp link|unlink <id> --integration ID --answer on|off Bring a WhatsApp Business number from Formmy/EasyBits to this agent, or send it back [--forward] [--ads-only on|off] [--dry-run]
12610
+ channels whatsapp history <id> --integration ID [--days 7] Bring its recent cards, messages, orders and files from Formmy (no duplicates; Formmy keeps files 7 days) [--media] [--dry-run [--list]] [--only-ads] [--phones a,b] [--column NAME]
12473
12611
  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]
12474
12612
  channels wa pair <id> --phone N Pair a personal WhatsApp by code (no QR)
12475
12613
  channels wa status|pause <id> Personal WhatsApp: how it is / pause it (keeps the session)
@@ -12484,7 +12622,7 @@ ${bold("Support")} ${dim("(Ghosty staff)")}
12484
12622
  conversations show <id> <conv> --why A conversation: who sees it in their list / by link, and why
12485
12623
  turns stats Real failure rate by engine and error [--since 14d] [--all]
12486
12624
  push log --user EMAIL Pushes sent to someone and what Apple/Google answered [--since 2d]
12487
- novedades ls|create|set|test|send|rm Android app announcements [--title] [--body] [--image FILE] [--link] [--to] [--publish] [--at]
12625
+ novedades ls|create|set|test|send|rm Android app announcements [--title] [--body] [--image FILE] [--link] [--to] [--min-version N] [--publish] [--at]
12488
12626
 
12489
12627
  ${bold("Demos")}
12490
12628
  demos try <slug> "msg" ["msg2"\u2026] Try a public demo as a visitor (same thread across runs) [--new] [--recycle-between --agent X]
@@ -12514,6 +12652,7 @@ ${bold("Partners")} ${dim("(give an agent to each of YOUR customers \u2014 see:
12514
12652
 
12515
12653
  ${bold("Your account")}
12516
12654
  me files ls|get|upload|rm Files in your account [--q TEXT] [--kind image|video|audio|document|other] [--cursor C] \xB7 get <id> [--out FILE]
12655
+ me memories ls|add|edit|rm What your agents remember about you (personal chats only) [--agent A] \xB7 add "text" \xB7 edit <id> "text" \xB7 rm <id>
12517
12656
  credentials ls|set|rm [provider] Provider keys in your vault (hidden prompt, stdin or --from-env VAR; never as an argument)
12518
12657
  docs [topic] Open the docs
12519
12658
 
@@ -12539,18 +12678,21 @@ ${bold("Sesi\xF3n")}
12539
12678
  whoami Muestra con qu\xE9 cuenta entraste
12540
12679
  use [espacio] Espacio por default para boards, agents create y agents ls [--clear]
12541
12680
  completion zsh|bash|fish Autocompletado con TAB: comandos, opciones, agentes y espacios
12542
- usage Tu plan y cu\xE1nto llevas usado esta semana y este mes [--boxes: cajas contra el tope del espacio]
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)]
12543
12682
  usage --workspace SLUG Turnos y tokens de un espacio por agente/d\xEDa [--by agent,day] [--days 14]
12544
12683
  plan apply [espacio] Aplica ya un plan programado, ense\xF1ando qu\xE9 cambia [--dry-run]
12545
12684
  engines ls Motores y modelos v\xE1lidos para --engine/--model [--models]
12546
12685
 
12547
12686
  ${bold("Espacios")} ${dim("(cambiarlos: staff de Ghosty)")}
12548
12687
  bags ls|create|set|rm <espacio> ["bolsa"] Bolsas de tokens de las que gastan sus agentes [--tokens 20M] [--recurring on|off] [--hard-cap on|off] [--note]
12688
+ spaces ls Tus espacios (rol, plan, combo) [--user CORREO: los de alguien m\xE1s]
12689
+ spaces join|leave <espacio> Entra o sal del espacio de un cliente como staff (sin asiento; queda en bit\xE1cora)
12690
+ spaces set <espacio> --combo sales teams | sales | both (sin Ventas sus tableros no se ven)
12549
12691
  spaces renew <espacio> --until FECHA Mueve \xABpagado hasta\xBB (queda en bit\xE1cora) [--note] [--dry-run]
12550
12692
  spaces rename <viejo> <nuevo> Cambia s\xF3lo su slug [--dry-run]
12551
12693
 
12552
12694
  ${bold("Agentes")}
12553
- agents ls Lista tus agentes [--engine E] [--ids: s\xF3lo ids, para xargs]
12695
+ agents ls Lista tus agentes [--engine E] [--ids: s\xF3lo ids, para xargs] \xB7 --user CORREO --app all|android|ios|desktop: como lo ve su app (staff)
12554
12696
  [--sponsor CORREO | --owner CORREO] agentes de cualquier cuenta (staff de Ghosty)
12555
12697
  agents create Crea un agente --name [--engine] [--model] [--prompt | --prompt-file ARCHIVO] [--env K=V,\u2026]
12556
12698
  [--workspace SLUG] [--channels teams=off,\u2026] nace en un espacio, activo en su Teams
@@ -12592,8 +12734,8 @@ ${bold("Configurar un agente")}
12592
12734
  mcp get|set <id> Servidores MCP [--file servers.json]
12593
12735
  channels whatsapp ls|enable|disable <id> [n\xFAmero] Qu\xE9 n\xFAmeros de WhatsApp Business contesta [--dry-run]
12594
12736
  channels whatsapp integrations <id> Los n\xFAmeros de WhatsApp Business de su due\xF1o en Formmy (n\xFAmero, qui\xE9n contesta hoy, id de la Integration)
12595
- channels whatsapp link|unlink <id> --integration ID --answer on|off Trae un n\xFAmero de WhatsApp Business de Formmy/EasyBits a este agente, o devu\xE9lvelo [--forward] [--dry-run]
12596
- channels whatsapp history <id> --integration ID [--days 7] Trae de Formmy sus tarjetas, mensajes, pedidos y archivos recientes (sin duplicar; Formmy guarda los archivos 7 d\xEDas) [--media] [--dry-run]
12737
+ channels whatsapp link|unlink <id> --integration ID --answer on|off Trae un n\xFAmero de WhatsApp Business de Formmy/EasyBits a este agente, o devu\xE9lvelo [--forward] [--ads-only on|off] [--dry-run]
12738
+ channels whatsapp history <id> --integration ID [--days 7] Trae de Formmy sus tarjetas, mensajes, pedidos y archivos recientes (sin duplicar; Formmy guarda los archivos 7 d\xEDas) [--media] [--dry-run [--list]] [--only-ads] [--phones a,b] [--column NOMBRE]
12597
12739
  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]
12598
12740
  channels wa pair <id> --phone N Vincula un WhatsApp personal con c\xF3digo (sin QR)
12599
12741
  channels wa status|pause <id> WhatsApp personal: c\xF3mo est\xE1 / pausarlo (conserva la sesi\xF3n)
@@ -12608,7 +12750,7 @@ ${bold("Soporte")} ${dim("(staff de Ghosty)")}
12608
12750
  conversations show <id> <conv> --why Una conversaci\xF3n: qui\xE9n la ve en su lista / por liga, y por qu\xE9
12609
12751
  turns stats Tasa real de fallos por motor y por error [--since 14d] [--all]
12610
12752
  push log --user CORREO Avisos que se le mandaron a alguien y qu\xE9 contest\xF3 Apple/Google [--since 2d]
12611
- novedades ls|create|set|test|send|rm Novedades de la app Android [--title] [--body] [--image ARCHIVO] [--link] [--to] [--publish] [--at]
12753
+ novedades ls|create|set|test|send|rm Novedades de la app Android [--title] [--body] [--image ARCHIVO] [--link] [--to] [--min-version N] [--publish] [--at]
12612
12754
 
12613
12755
  ${bold("Demos")}
12614
12756
  demos try <slug> "msg" ["msg2"\u2026] Prueba un demo p\xFAblico como visitante (mismo hilo entre corridas) [--new] [--recycle-between --agent X]
@@ -12638,6 +12780,7 @@ ${bold("Partners")} ${dim("(dale un agente a cada uno de TUS clientes \u2014 ve
12638
12780
 
12639
12781
  ${bold("Tu cuenta")}
12640
12782
  me files ls|get|upload|rm Archivos de tu cuenta [--q TEXTO] [--kind image|video|audio|document|other] [--cursor C] \xB7 get <id> [--out ARCHIVO]
12783
+ me memories ls|add|edit|rm Lo que tus agentes recuerdan de ti (s\xF3lo chats personales) [--agent A] \xB7 add "texto" \xB7 edit <id> "texto" \xB7 rm <id>
12641
12784
  credentials ls|set|rm [proveedor] Llaves de proveedor en tu b\xF3veda (prompt oculto, stdin o --from-env VAR; nunca como argumento)
12642
12785
  docs [tema] Abre la documentaci\xF3n
12643
12786
 
@@ -12654,7 +12797,7 @@ ${bold("Opciones globales")}
12654
12797
  ${bold("C\xF3digos de salida")} 0 bien \xB7 1 error de la API \xB7 2 uso incorrecto \xB7 3 sin sesi\xF3n
12655
12798
  ${dim("Nota: `ghosty` puede chocar con otro binario en tu PATH (p. ej. la TUI de Ghosty). Rev\xEDsalo con `which -a ghosty`; corre `npx @ghostystudio/cli` para ir a la segura.")}
12656
12799
  ${dim("Documentaci\xF3n: https://www.ghosty.studio/docs/cli")}`;
12657
- var COMMANDS = ["login", "logout", "whoami", "usage", "use", "apply", "plan", "engines", "bags", "spaces", "demos", "push", "novedades", "completion", "cards", "agents", "files", "skills", "mcp", "dbs", "turns", "try", "chat", "conversations", "schedule", "partner", "me", "channels", "board", "boards", "credentials", "docs", "help"];
12800
+ var COMMANDS = ["login", "logout", "whoami", "usage", "use", "apply", "plan", "engines", "bags", "spaces", "workspaces", "demos", "push", "novedades", "completion", "cards", "agents", "files", "skills", "mcp", "dbs", "turns", "try", "chat", "conversations", "schedule", "partner", "me", "channels", "board", "boards", "credentials", "docs", "help"];
12658
12801
  var AGENTS_SUBS = ["ls", "create", "get", "set", "export", "doctor", "docs", "share", "unshare", "shares", "rm", "restart", "demo", "box"];
12659
12802
  function unknown(what, input, known, helpCmd) {
12660
12803
  const s = suggest(input, known);
@@ -12834,8 +12977,15 @@ async function main() {
12834
12977
  image: { type: "string" },
12835
12978
  link: { type: "string" },
12836
12979
  button: { type: "string" },
12980
+ "min-version": { type: "string" },
12837
12981
  publish: { type: "boolean" },
12838
12982
  draft: { type: "boolean" },
12983
+ "ads-only": { type: "string" },
12984
+ list: { type: "boolean" },
12985
+ "only-ads": { type: "boolean" },
12986
+ phones: { type: "string" },
12987
+ combo: { type: "string" },
12988
+ app: { type: "string" },
12839
12989
  ids: { type: "boolean" },
12840
12990
  force: { type: "boolean" },
12841
12991
  reenv: { type: "boolean" },
@@ -12852,7 +13002,7 @@ async function main() {
12852
13002
  const tokens = splitTokens(pos, v);
12853
13003
  globals.token = tokens.session;
12854
13004
  globals.profile = v.profile;
12855
- if (v.version) return out({ version: "0.30.0" }, () => "0.30.0");
13005
+ if (v.version) return out({ version: "0.32.0" }, () => "0.32.0");
12856
13006
  for (const n of normalizeArgs(pos, v)) if (!globals.json) info(dim(n));
12857
13007
  const context = (await readConfig()).workspace;
12858
13008
  if (!explicitToken()?.startsWith("gat_")) {
@@ -12898,6 +13048,7 @@ async function main() {
12898
13048
  case "apply":
12899
13049
  return applyCmd(sub, v);
12900
13050
  case "agents":
13051
+ if ((sub === "ls" || sub === "list" || !sub) && v.user && v.app !== void 0) return appAgentsOf(v.user, v.app);
12901
13052
  if (sub === "ls" || sub === "list" || !sub) return agentsLs(v.all ? void 0 : v.workspace ?? context, { sponsor: v.sponsor, owner: v.owner, engine: v.engine, ids: v.ids, all: v.all });
12902
13053
  if (sub === "create" || sub === "new") {
12903
13054
  if (v.workspace === "none") delete v.workspace;
@@ -12964,6 +13115,7 @@ async function main() {
12964
13115
  case "partner":
12965
13116
  return partnerCmd(sub, a1, a2, { ...v, token: tokens.turn });
12966
13117
  case "usage":
13118
+ if (v.boxes && v.by === "template") return fleetBoxes();
12967
13119
  if (v.boxes) return boxesUsage(v.workspace ?? context);
12968
13120
  if (v.workspace || v.by || v.days) return workspaceUsage(v.workspace ?? context, v);
12969
13121
  return meUsage();
@@ -12977,13 +13129,15 @@ async function main() {
12977
13129
  return bagsCmd(sub, a1, a2, v, context);
12978
13130
  case "spaces":
12979
13131
  case "space":
13132
+ case "workspaces":
12980
13133
  return spacesCmd(sub, a1, a2, v, context);
12981
13134
  case "plan":
12982
13135
  return planCmd(sub, a1, v, context);
12983
13136
  case "me":
12984
13137
  if (sub === "files") return meFiles(a1, a2, v);
13138
+ if (sub === "memories" || sub === "memory") return meMemories(a1, a2, pos[4], v);
12985
13139
  if (sub === "usage") return meUsage();
12986
- throw usage(t(`Unknown me subcommand: ${sub ?? "(none)"}.`, `Subcomando de me desconocido: ${sub ?? "(ninguno)"}.`), t("Try: ghosty me files ls", "Prueba: ghosty me files ls"));
13140
+ throw usage(t(`Unknown me subcommand: ${sub ?? "(none)"}.`, `Subcomando de me desconocido: ${sub ?? "(ninguno)"}.`), t("Try: ghosty me files ls | ghosty me memories ls", "Prueba: ghosty me files ls | ghosty me memories ls"));
12987
13141
  case "docs": {
12988
13142
  const url = `${await baseUrl()}/docs${sub ? `/${encodeURIComponent(sub)}` : "/cli"}`;
12989
13143
  if (globals.json) return out({ url });
@@ -12994,7 +13148,7 @@ async function main() {
12994
13148
  throw unknown(cmd, cmd, COMMANDS, "ghosty --help");
12995
13149
  }
12996
13150
  }
12997
- var notice = updateNotice("0.30.0").catch(() => null);
13151
+ var notice = updateNotice("0.32.0").catch(() => null);
12998
13152
  main().then(
12999
13153
  async () => {
13000
13154
  const n = await notice;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghostystudio/cli",
3
- "version": "0.30.0",
3
+ "version": "0.32.0",
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": {