@ghostystudio/cli 0.30.0 → 0.31.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 +8 -0
  2. package/dist/index.js +144 -41
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -95,6 +95,14 @@ Every command has its own help with examples: `ghosty <command> --help` or `ghos
95
95
 
96
96
  Exit codes: `0` ok · `1` API error · `2` usage error · `3` not signed in.
97
97
 
98
+ ### Changes in 0.31.0
99
+
100
+ - `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).
101
+ - `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.
102
+ - After a `--dry-run`, the suggested command already carries `--yes` when there's no terminal. A 404 says it may be missing access.
103
+ - Staff: `agents ls --user EMAIL --app all|android|ios|desktop` (their list as their app gets it), `usage --boxes --by template` (whole fleet).
104
+ - The «new version» notice checks npm every hour (it was once a day).
105
+
98
106
  ### Changes in 0.30.0
99
107
 
100
108
  - 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,17 @@ 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"],
8047
8058
  // — Infra interna (casi nunca llega al CLI, pero por si acaso)
8048
8059
  ["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
8060
  ["sqld: fetch sin namespace", "sqld: fetch without namespace"],
@@ -8346,7 +8357,7 @@ async function postLong(path, body, timeoutMs = 9e5) {
8346
8357
  const req = httpsRequest(`${base}${path}`, {
8347
8358
  method: "POST",
8348
8359
  timeout: timeoutMs,
8349
- headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.30.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
8360
+ headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.31.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
8350
8361
  }, (res) => {
8351
8362
  let buf = "";
8352
8363
  res.on("data", (c) => buf += c);
@@ -8388,7 +8399,7 @@ async function request(path, opts = {}, retried = false) {
8388
8399
  method: opts.method ?? (opts.body !== void 0 || opts.raw !== void 0 ? "POST" : "GET"),
8389
8400
  headers: {
8390
8401
  authorization: `Bearer ${token}`,
8391
- "user-agent": `ghosty-cli/${"0.30.0"}`,
8402
+ "user-agent": `ghosty-cli/${"0.31.0"}`,
8392
8403
  "accept-language": lang(),
8393
8404
  ...opts.body !== void 0 ? { "content-type": "application/json" } : {},
8394
8405
  ...opts.headers
@@ -8415,7 +8426,7 @@ async function toError(res) {
8415
8426
  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
8427
  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
8428
  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."));
8429
+ 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
8430
  if (body.error === "restart_no_aplica" || body.error === "agente_sin_maquina") {
8420
8431
  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
8432
  }
@@ -8430,6 +8441,9 @@ import { dirname as dirname2, join as join3, relative, resolve } from "node:path
8430
8441
  // src/prompt.ts
8431
8442
  import { createInterface } from "node:readline/promises";
8432
8443
  var canPrompt = () => Boolean(process.stdin.isTTY && process.stderr.isTTY);
8444
+ function applyHint(cmd) {
8445
+ return t(`Apply: ${cmd}${canPrompt() ? "" : " --yes"}`, `Para aplicarlo: ${cmd}${canPrompt() ? "" : " --yes"}`);
8446
+ }
8433
8447
  function requireYesIfHeadless(yes, retry) {
8434
8448
  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
8449
  }
@@ -9515,7 +9529,7 @@ async function call(path, v, init = {}) {
9515
9529
  "x-ghosty-ts": ts,
9516
9530
  "x-ghosty-sig": sig,
9517
9531
  "content-type": "application/json",
9518
- "user-agent": `ghosty-cli/${"0.30.0"}`
9532
+ "user-agent": `ghosty-cli/${"0.31.0"}`
9519
9533
  },
9520
9534
  body: method === "GET" ? void 0 : raw
9521
9535
  });
@@ -10333,9 +10347,10 @@ async function channelsCmd(kind, sub, id, ref, v) {
10333
10347
  number: n2.phoneNumber ? `+${n2.phoneNumber}${n2.label !== n2.phoneNumber && n2.label !== n2.id ? ` ${n2.label}` : ""}` : n2.label,
10334
10348
  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
10349
  "formmy forwards": fwd(n2),
10350
+ "ads only": n2.adsOnly ? lavender(t("yes", "s\xED")) : "",
10336
10351
  id: n2.id
10337
10352
  }));
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}`)));
10353
+ 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
10354
  }
10340
10355
  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
10356
  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 +10494,27 @@ ${dim(eb ? t(`Next: ghosty channels wa groups ls ${id}`, `Sigue: ghosty channels
10479
10494
  }
10480
10495
  async function linkCmd(sub, id, v) {
10481
10496
  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`);
10497
+ 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."));
10498
+ const adsOnly = v["ads-only"] === void 0 ? void 0 : v["ads-only"] === "on";
10499
+ if (sub === "link" && adsOnly !== void 0 && v.answer === void 0) {
10500
+ const path2 = `/api/v2/agents/${enc7(id)}/whatsapp/link`;
10501
+ const r2 = await api(path2, { method: "POST", body: { integrationId: v.integration, adsOnly, ...v["dry-run"] ? { dryRun: true } : {} } });
10502
+ return out(r2, () => `${r2.plan.map((x) => serverText(x)).join("\n")}${v["dry-run"] ? `
10503
+ ${dim(applyHint(`ghosty channels whatsapp link ${id} --integration ${v.integration} --ads-only ${v["ads-only"]}`))}` : ""}`);
10504
+ }
10482
10505
  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
10506
  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 } : {} } };
10507
+ const body = { integrationId: v.integration, ...sub === "unlink" ? { unlink: true } : { answer: v.answer === "on", ...v.forward ? { forward: true } : {}, ...adsOnly !== void 0 ? { adsOnly } : {} } };
10508
+ 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
10509
  const plan = await api(path, { method: "POST", body: { ...body, dryRun: true } });
10486
10510
  const text = `${plan.plan.map((x) => serverText(x)).join("\n")}
10487
10511
  ${dim(`Formmy: ${plan.formmy.before.url ?? "null"} \u2192 ${plan.formmy.after.url ?? "null"} (${t("as", "como")} ${plan.formmy.by})`)}`;
10488
10512
  if (v["dry-run"]) return out(plan, () => `${text}
10489
- ${dim(t("Dry run: nothing changed.", "Simulacro: no se cambi\xF3 nada."))}`);
10513
+ ${dim(t("Dry run: nothing changed.", "Simulacro: no se cambi\xF3 nada."))}
10514
+ ${dim(applyHint(cmd))}`);
10490
10515
  const digits4 = (plan.number.phoneNumberId ?? v.integration).slice(-4);
10491
10516
  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" : ""}` : ""}` });
10517
+ ${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
10518
  const r = await api(path, { method: "POST", body });
10494
10519
  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
10520
  }
@@ -10543,7 +10568,9 @@ async function historyCmd(id, v) {
10543
10568
  if (!Number.isInteger(days) || days < 1 || days > 30) throw usage(t("--days takes 1 to 30.", "--days acepta de 1 a 30."));
10544
10569
  const path = `/api/v2/agents/${enc7(id)}/whatsapp/history`;
10545
10570
  const media = v.media !== void 0;
10546
- const c = await api(path, { method: "POST", body: { integrationId: v.integration, days, dryRun: true, media } });
10571
+ const filters = { ...v["only-ads"] ? { onlyAds: true } : {}, ...v.phones ? { phones: v.phones.split(",").map((x) => x.trim()).filter(Boolean) } : {}, ...v.column ? { column: v.column } : {} };
10572
+ 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}"` : ""}`;
10573
+ const c = await api(path, { method: "POST", body: { integrationId: v.integration, days, dryRun: true, media, ...v.list ? { list: true } : {}, ...filters } });
10547
10574
  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
10575
  if (media) {
10549
10576
  const missing = c.mediaMissing ?? 0;
@@ -10574,17 +10601,23 @@ ${dim(v["dry-run"] ? t("Dry run: nothing copied.", "Simulacro: no se copi\xF3 na
10574
10601
  );
10575
10602
  const missingHint = c.mediaMissing ? `
10576
10603
  ${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}` });
10604
+ const listText = c.list ? `
10605
+ ${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"])}
10606
+ ${dim(t(`${c.matching} match the filters`, `${c.matching} pasan los filtros`))}` : "";
10607
+ const filtered = Boolean(v["only-ads"] || v.phones);
10608
+ if (v["dry-run"]) return out(c, () => `${plan}${listText}${missingHint}
10609
+ ${dim(t("Dry run: nothing imported. Re-running later never duplicates.", "Simulacro: no se import\xF3 nada. Correrlo otra vez nunca duplica."))}
10610
+ ${dim(applyHint(cmd))}`);
10611
+ if (!c.conversations || filtered && c.matching === 0) return out({ ...c, imported: 0 }, () => dim(t(`Nothing to import: ${plan}.`, `Nada que importar: ${plan}.`)));
10612
+ await confirm(`${bold(plan)}${filtered ? `
10613
+ ${t(`only the ${c.matching} that match the filters`, `s\xF3lo las ${c.matching} que pasan los filtros`)}` : ""}
10614
+ ${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
10615
  const sum = { newCards: 0, updatedCards: 0, messages: 0, orders: 0, suppressed: 0, mediaCopied: 0, mediaFailed: 0, paused: 0, pausedForever: 0, manual: 0 };
10583
10616
  let cursor = 0;
10584
10617
  let done = 0;
10585
10618
  while (cursor !== null) {
10586
10619
  const at = cursor;
10587
- const p = await pageWithRetry(() => api(path, { method: "POST", body: { integrationId: v.integration, days, cursor: at } }), `${done}/${c.conversations}`);
10620
+ const p = await pageWithRetry(() => api(path, { method: "POST", body: { integrationId: v.integration, days, cursor: at, ...filters } }), `${done}/${c.conversations}`);
10588
10621
  for (const k of Object.keys(sum)) sum[k] += p[k] ?? 0;
10589
10622
  done += p.page;
10590
10623
  if (!globals.json) info(dim(` ${done}/${p.total} ${t("conversations", "conversaciones")}`));
@@ -10662,7 +10695,8 @@ async function boardsCmd(sub, a1, a2, v, context) {
10662
10695
  case "create": {
10663
10696
  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
10697
  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}"`)}`);
10698
+ 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 ? `
10699
+ ${lavender(`\u26A0\uFE0F ${serverText(r.warning)}`)}` : ""}`);
10666
10700
  }
10667
10701
  case "rm": {
10668
10702
  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 +10786,8 @@ var HELPS_ES = {
10752
10786
  ["--from-workspace", "rm: lo desactiva en cada Teams donde est\xE9 y luego lo borra (due\xF1o o staff)"],
10753
10787
  ["--prompt-mode M", "set: append (default: su prompt + la persona de Ghosty) o replace (s\xF3lo su prompt)"],
10754
10788
  ["--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)"]
10789
+ ["--ids", "ls: s\xF3lo los ids, uno por rengl\xF3n (para xargs)"],
10790
+ ["--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
10791
  ],
10757
10792
  notes: [
10758
10793
  "doctor sale con 1 cuando encuentra un problema, as\xED puede frenar un script o un CI.",
@@ -10880,6 +10915,10 @@ var HELPS_ES = {
10880
10915
  ["--yes", "enable / groups enable: se salta la confirmaci\xF3n (obligatorio sin terminal)"],
10881
10916
  ["--role R", "groups: equipo (el equipo: escribe en bases de datos, tools de admin) o cliente (como cualquier cliente)"],
10882
10917
  ["--cached", "groups ls: no le pide a WhatsApp la lista en vivo (m\xE1s r\xE1pido; puede faltar alg\xFAn grupo nuevo)"],
10918
+ ["--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)"],
10919
+ ["--list", "history --dry-run: CU\xC1LES conversaciones (tel\xE9fono, nombre, primer mensaje, mensajes, si vino de anuncio)"],
10920
+ ["--only-ads / --phones a,b", "history: importa (o lista) s\xF3lo las de anuncio y/o esos tel\xE9fonos"],
10921
+ ["--column NAME", "history: las tarjetas nuevas nacen en esa columna en vez de la primera"],
10883
10922
  ["--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
10923
  ["--integration ID", "link/unlink/history: el id de la Integration del n\xFAmero de WhatsApp Business en Formmy"],
10885
10924
  ["--email E", "integrations (staff): lista los n\xFAmeros de otra cuenta de Formmy"],
@@ -10994,7 +11033,7 @@ var HELPS_ES = {
10994
11033
  flags: [
10995
11034
  ["--boxes", "las cajas del espacio contra su tope (spares, dormidas) y, por agente: conversaciones por caja, cajas, despiertas, spares, turnos corriendo ahora"],
10996
11035
  ["--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"],
11036
+ ["--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
11037
  ["--days N", "--workspace: cu\xE1ntos d\xEDas hacia atr\xE1s (default 14, m\xE1x. 92)"]
10999
11038
  ],
11000
11039
  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)."]
@@ -11037,8 +11076,10 @@ var HELPS_ES = {
11037
11076
  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."]
11038
11077
  },
11039
11078
  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.",
11079
+ 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
11080
  flags: [
11081
+ ["--user EMAIL", "ls (staff): los espacios de esa persona"],
11082
+ ["--combo C", "set (staff): teams | sales | both \u2014 sin Ventas sus tableros existen pero nadie los ve"],
11042
11083
  ["--until DATE", "renew: el nuevo \xABpagado hasta\xBB (AAAA-MM-DD)"],
11043
11084
  ["--note TEXT", "renew: por qu\xE9 (va a la bit\xE1cora)"],
11044
11085
  ["-n, --dry-run", "ense\xF1a qu\xE9 cambia, no cambia nada"],
@@ -11047,7 +11088,7 @@ var HELPS_ES = {
11047
11088
  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
11089
  }
11049
11090
  };
11050
- var HELP_ALIASES = { creds: "credentials", board: "cards", conversations: "chat" };
11091
+ var HELP_ALIASES = { creds: "credentials", board: "cards", conversations: "chat", workspaces: "spaces" };
11051
11092
 
11052
11093
  // src/help.ts
11053
11094
  var DOCS = "https://www.ghosty.studio/docs/cli";
@@ -11122,7 +11163,8 @@ var HELPS = {
11122
11163
  ["--from-workspace", "rm: turn it off in every Teams where it's active, then delete it (owner or staff)"],
11123
11164
  ["--prompt-mode M", "set: append (default: its prompt + Ghosty's persona) or replace (its prompt only)"],
11124
11165
  ["--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)"]
11166
+ ["--ids", "ls: only the ids, one per line (for xargs)"],
11167
+ ["--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
11168
  ],
11127
11169
  examples: [
11128
11170
  "ghosty agents doctor cmu7pgbyr0001gee3ruk172mm",
@@ -11369,6 +11411,10 @@ HELPS.channels = {
11369
11411
  ["--yes", "enable / groups enable: skip the confirmation (required without a terminal)"],
11370
11412
  ["--role R", "groups: equipo (the team: writes to databases, admin tools) or cliente (like any customer)"],
11371
11413
  ["--cached", "groups ls: don't ask WhatsApp for the live list (faster; may miss new groups)"],
11414
+ ["--ads-only on|off", "link: only conversations that come from an ad open a card (also alone, to change it on a linked number)"],
11415
+ ["--list", "history --dry-run: WHICH conversations (phone, name, first message, messages, from an ad)"],
11416
+ ["--only-ads / --phones a,b", "history: import (or list) only the ones from an ad and/or those phones"],
11417
+ ["--column NAME", "history: new cards are born in that column instead of the first one"],
11372
11418
  ["--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
11419
  ["--integration ID", "link/unlink/history: the WhatsApp Business number's Integration id in Formmy"],
11374
11420
  ["--email E", "integrations (staff): list the numbers of another Formmy account"],
@@ -11516,7 +11562,7 @@ HELPS.usage = {
11516
11562
  flags: [
11517
11563
  ["--boxes", "the workspace's boxes against its limit (spares, asleep) and, per agent: conversations per box, boxes, awake, spares, turns running now"],
11518
11564
  ["--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"],
11565
+ ["--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
11566
  ["--days N", "--workspace: how far back (default 14, max 92)"]
11521
11567
  ],
11522
11568
  examples: ["ghosty usage", "ghosty usage --json", "ghosty usage --workspace descti --by agent,day --days 14", "ghosty usage --boxes --workspace siiqtec"],
@@ -11591,18 +11637,21 @@ HELPS.novedades = {
11591
11637
  learn: `${REF}#soporte`
11592
11638
  };
11593
11639
  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]"],
11640
+ 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.",
11641
+ 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
11642
  flags: [
11643
+ ["--user EMAIL", "ls (staff): that person's workspaces"],
11644
+ ["--combo C", "set (staff): teams | sales | both \u2014 without Sales its boards exist but nobody sees them"],
11597
11645
  ["--until DATE", "renew: the new \xABpaid until\xBB (YYYY-MM-DD)"],
11598
11646
  ["--note TEXT", "renew: why (goes into the audit)"],
11599
11647
  ["-n, --dry-run", "show what changes, change nothing"],
11600
11648
  ["--yes", "skip the confirmation (required without a terminal)"]
11601
11649
  ],
11602
- examples: ['ghosty spaces renew descti --until 2026-11-01 --note "pag\xF3 octubre"', "ghosty spaces rename totequim siiqtec --dry-run"],
11650
+ 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
11651
  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
11652
  learn: `${REF}#espacios`
11605
11653
  };
11654
+ HELPS.workspaces = HELPS.spaces;
11606
11655
  function helpText(cmd) {
11607
11656
  const base = HELPS[cmd];
11608
11657
  if (!base) return void 0;
@@ -11630,7 +11679,7 @@ import { mkdir as mkdir4, readFile as readFile9, writeFile as writeFile6 } from
11630
11679
  import { homedir as homedir3 } from "node:os";
11631
11680
  import { dirname as dirname3, join as join6 } from "node:path";
11632
11681
  var FILE = join6(process.env.XDG_CONFIG_HOME || join6(homedir3(), ".config"), "ghosty", "update.json");
11633
- var DAY = 24 * 60 * 60 * 1e3;
11682
+ var TTL = 60 * 60 * 1e3;
11634
11683
  var UPGRADE = "npm i -g @ghostystudio/cli@latest";
11635
11684
  function newer(a, b) {
11636
11685
  const pa = a.split(".").map(Number);
@@ -11638,14 +11687,15 @@ function newer(a, b) {
11638
11687
  for (let i = 0; i < 3; i++) if ((pa[i] ?? 0) !== (pb[i] ?? 0)) return (pa[i] ?? 0) > (pb[i] ?? 0);
11639
11688
  return false;
11640
11689
  }
11641
- async function latestVersion() {
11690
+ async function latestVersion(current2) {
11642
11691
  if (process.env.GHOSTY_NO_UPDATE_CHECK || process.env.CI) return null;
11643
11692
  let cache2 = {};
11644
11693
  try {
11645
11694
  cache2 = JSON.parse(await readFile9(FILE, "utf8"));
11646
11695
  } catch {
11647
11696
  }
11648
- if (!cache2.checkedAt || Date.now() - cache2.checkedAt > DAY) {
11697
+ if (current2 && cache2.latest && newer(current2, cache2.latest)) cache2.checkedAt = 0;
11698
+ if (!cache2.checkedAt || Date.now() - cache2.checkedAt > TTL) {
11649
11699
  try {
11650
11700
  const r = await fetch("https://registry.npmjs.org/@ghostystudio/cli/latest", { signal: AbortSignal.timeout(1500) });
11651
11701
  const latest = (await r.json()).version;
@@ -11658,7 +11708,7 @@ async function latestVersion() {
11658
11708
  return cache2.latest ?? null;
11659
11709
  }
11660
11710
  async function updateNotice(current2) {
11661
- const latest = await latestVersion();
11711
+ const latest = await latestVersion(current2);
11662
11712
  return latest && newer(latest, current2) ? t(`Update available: ${current2} \u2192 ${latest}. Run: ${UPGRADE}`, `Hay versi\xF3n nueva: ${current2} \u2192 ${latest}. Corre: ${UPGRADE}`) : null;
11663
11713
  }
11664
11714
 
@@ -12083,7 +12133,33 @@ ${bagsText(r)}`);
12083
12133
  }
12084
12134
  }
12085
12135
  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]";
12136
+ 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]";
12137
+ if (!sub || sub === "ls" || sub === "list") {
12138
+ const r = await api(`/api/v2/workspaces${v.user ? `?user=${enc10(v.user)}` : ""}`);
12139
+ const rows = r.workspaces.map((w) => ({ workspace: w.slug, role: w.role ?? "", plan: w.plan, combo: w.combo, owner: w.owner ?? "", paid: w.paidUntil ?? "" }));
12140
+ return out(r, () => rows.length ? `${bold(r.user)}
12141
+ ${table(rows, ["workspace", "role", "plan", "combo", "owner", "paid"])}` : dim(t(`${r.user} has no workspaces.`, `${r.user} no tiene espacios.`)));
12142
+ }
12143
+ if (sub === "join" || sub === "leave") {
12144
+ slug = needSpace(slug, `ghosty spaces ${sub} <workspace>`);
12145
+ const r = await api(wsPath(slug, "/staff"), { method: "POST", body: { join: sub === "join" } });
12146
+ 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}.`)}`);
12147
+ }
12148
+ if (sub === "set") {
12149
+ slug = needSpace(slug ?? context, "ghosty spaces set <workspace> --combo sales");
12150
+ if (!v.combo) throw usage(t("Nothing to change.", "Nada que cambiar."), "--combo teams|sales|both");
12151
+ const path = wsPath(slug, "/combo");
12152
+ const plan = await api(path, { method: "POST", body: { combo: v.combo, dryRun: true } });
12153
+ const line2 = `${plan.workspace}: combo ${plan.from} \u2192 ${plan.to}`;
12154
+ 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}.`)));
12155
+ if (v["dry-run"]) return out(plan, () => `${line2}
12156
+ ${dim(t("Dry run: nothing changed.", "Simulaci\xF3n: no cambi\xF3 nada."))}`);
12157
+ requireYesIfHeadless(v.yes, `ghosty spaces set ${slug} --combo ${v.combo}`);
12158
+ await confirm(`${line2}
12159
+ ${t("Change it?", "\xBFCambiarlo?")}`, { yes: v.yes, retry: `ghosty spaces set ${slug} --combo ${v.combo}` });
12160
+ const r = await api(path, { method: "POST", body: { combo: v.combo } });
12161
+ return out(r, () => `${lavender("\u2713")} ${r.workspace}: combo ${r.to}`);
12162
+ }
12087
12163
  if (sub === "renew") {
12088
12164
  slug = needSpace(slug ?? context, ex);
12089
12165
  if (!v.until) throw usage(t("Missing --until YYYY-MM-DD.", "Falta --until AAAA-MM-DD."), `ghosty spaces renew ${slug} --until 2026-11-01`);
@@ -12335,6 +12411,18 @@ async function turnStats(v) {
12335
12411
  ].join("\n")
12336
12412
  );
12337
12413
  }
12414
+ async function fleetBoxes() {
12415
+ const r = await api("/api/v2/support/boxes?by=template");
12416
+ 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` }));
12417
+ out(r, () => `${bold(t(`${r.total} box(es), ${r.on} awake`, `${r.total} caja(s), ${r.on} despiertas`))}
12418
+ ${table(rows, ["template", "boxes", "on", "asleep", "ram", "vcpu", "ram on"])}`);
12419
+ }
12420
+ async function appAgentsOf(email, client) {
12421
+ const r = await api(`/api/v2/support/app-agents?user=${enc12(email)}${client && client !== "all" ? `&client=${enc12(client)}` : ""}`);
12422
+ 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) : "" }));
12423
+ 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"))}
12424
+ ${table(rows, ["id", "name", "engine", "model", "tone", "space", "last"])}`);
12425
+ }
12338
12426
  async function pushLog(email, v) {
12339
12427
  if (!email) throw usage(t("Missing --user.", "Falta --user."), "ghosty push log --user ana@empresa.com [--since 2d]");
12340
12428
  const r = await api(`/api/v2/support/push-log?user=${enc12(email)}&since=${enc12(v.since ?? "2d")}${v.limit ? `&limit=${enc12(v.limit)}` : ""}`);
@@ -12415,18 +12503,21 @@ ${bold("Session")}
12415
12503
  whoami Show who you are signed in as
12416
12504
  use [workspace] Default workspace for boards, agents create and agents ls [--clear]
12417
12505
  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]
12506
+ 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
12507
  usage --workspace SLUG A workspace's turns and tokens per agent/day [--by agent,day] [--days 14]
12420
12508
  plan apply [workspace] Apply a scheduled plan now, showing what changes [--dry-run]
12421
12509
  engines ls Engines and models valid for --engine/--model [--models]
12422
12510
 
12423
12511
  ${bold("Workspaces")} ${dim("(changing them: Ghosty staff)")}
12424
12512
  bags ls|create|set|rm <workspace> ["bag"] Token bags its agents spend from [--tokens 20M] [--recurring on|off] [--hard-cap on|off] [--note]
12513
+ spaces ls Your workspaces (role, plan, combo) [--user EMAIL: someone else's]
12514
+ spaces join|leave <workspace> Enter or leave a client's workspace as staff (no seat; logged)
12515
+ spaces set <workspace> --combo sales teams | sales | both (without Sales its boards are invisible)
12425
12516
  spaces renew <workspace> --until DATE Move \xABpaid until\xBB (logged) [--note] [--dry-run]
12426
12517
  spaces rename <old> <new> Change its slug only [--dry-run]
12427
12518
 
12428
12519
  ${bold("Agents")}
12429
- agents ls List your agents [--engine E] [--ids: only ids, for xargs]
12520
+ 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
12521
  [--sponsor EMAIL | --owner EMAIL] any account's agents (Ghosty staff)
12431
12522
  agents create Create an agent --name [--engine] [--model] [--prompt | --prompt-file FILE] [--env K=V,\u2026]
12432
12523
  [--workspace SLUG] [--channels teams=off,\u2026] born in a workspace, active in its Teams
@@ -12468,8 +12559,8 @@ ${bold("Configure an agent")}
12468
12559
  mcp get|set <id> MCP servers [--file servers.json]
12469
12560
  channels whatsapp ls|enable|disable <id> [number] Which WhatsApp Business numbers it answers [--dry-run]
12470
12561
  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]
12562
+ 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]
12563
+ 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
12564
  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
12565
  channels wa pair <id> --phone N Pair a personal WhatsApp by code (no QR)
12475
12566
  channels wa status|pause <id> Personal WhatsApp: how it is / pause it (keeps the session)
@@ -12539,18 +12630,21 @@ ${bold("Sesi\xF3n")}
12539
12630
  whoami Muestra con qu\xE9 cuenta entraste
12540
12631
  use [espacio] Espacio por default para boards, agents create y agents ls [--clear]
12541
12632
  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]
12633
+ 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
12634
  usage --workspace SLUG Turnos y tokens de un espacio por agente/d\xEDa [--by agent,day] [--days 14]
12544
12635
  plan apply [espacio] Aplica ya un plan programado, ense\xF1ando qu\xE9 cambia [--dry-run]
12545
12636
  engines ls Motores y modelos v\xE1lidos para --engine/--model [--models]
12546
12637
 
12547
12638
  ${bold("Espacios")} ${dim("(cambiarlos: staff de Ghosty)")}
12548
12639
  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]
12640
+ spaces ls Tus espacios (rol, plan, combo) [--user CORREO: los de alguien m\xE1s]
12641
+ spaces join|leave <espacio> Entra o sal del espacio de un cliente como staff (sin asiento; queda en bit\xE1cora)
12642
+ spaces set <espacio> --combo sales teams | sales | both (sin Ventas sus tableros no se ven)
12549
12643
  spaces renew <espacio> --until FECHA Mueve \xABpagado hasta\xBB (queda en bit\xE1cora) [--note] [--dry-run]
12550
12644
  spaces rename <viejo> <nuevo> Cambia s\xF3lo su slug [--dry-run]
12551
12645
 
12552
12646
  ${bold("Agentes")}
12553
- agents ls Lista tus agentes [--engine E] [--ids: s\xF3lo ids, para xargs]
12647
+ 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
12648
  [--sponsor CORREO | --owner CORREO] agentes de cualquier cuenta (staff de Ghosty)
12555
12649
  agents create Crea un agente --name [--engine] [--model] [--prompt | --prompt-file ARCHIVO] [--env K=V,\u2026]
12556
12650
  [--workspace SLUG] [--channels teams=off,\u2026] nace en un espacio, activo en su Teams
@@ -12592,8 +12686,8 @@ ${bold("Configurar un agente")}
12592
12686
  mcp get|set <id> Servidores MCP [--file servers.json]
12593
12687
  channels whatsapp ls|enable|disable <id> [n\xFAmero] Qu\xE9 n\xFAmeros de WhatsApp Business contesta [--dry-run]
12594
12688
  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]
12689
+ 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]
12690
+ 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
12691
  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
12692
  channels wa pair <id> --phone N Vincula un WhatsApp personal con c\xF3digo (sin QR)
12599
12693
  channels wa status|pause <id> WhatsApp personal: c\xF3mo est\xE1 / pausarlo (conserva la sesi\xF3n)
@@ -12654,7 +12748,7 @@ ${bold("Opciones globales")}
12654
12748
  ${bold("C\xF3digos de salida")} 0 bien \xB7 1 error de la API \xB7 2 uso incorrecto \xB7 3 sin sesi\xF3n
12655
12749
  ${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
12750
  ${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"];
12751
+ 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
12752
  var AGENTS_SUBS = ["ls", "create", "get", "set", "export", "doctor", "docs", "share", "unshare", "shares", "rm", "restart", "demo", "box"];
12659
12753
  function unknown(what, input, known, helpCmd) {
12660
12754
  const s = suggest(input, known);
@@ -12836,6 +12930,12 @@ async function main() {
12836
12930
  button: { type: "string" },
12837
12931
  publish: { type: "boolean" },
12838
12932
  draft: { type: "boolean" },
12933
+ "ads-only": { type: "string" },
12934
+ list: { type: "boolean" },
12935
+ "only-ads": { type: "boolean" },
12936
+ phones: { type: "string" },
12937
+ combo: { type: "string" },
12938
+ app: { type: "string" },
12839
12939
  ids: { type: "boolean" },
12840
12940
  force: { type: "boolean" },
12841
12941
  reenv: { type: "boolean" },
@@ -12852,7 +12952,7 @@ async function main() {
12852
12952
  const tokens = splitTokens(pos, v);
12853
12953
  globals.token = tokens.session;
12854
12954
  globals.profile = v.profile;
12855
- if (v.version) return out({ version: "0.30.0" }, () => "0.30.0");
12955
+ if (v.version) return out({ version: "0.31.0" }, () => "0.31.0");
12856
12956
  for (const n of normalizeArgs(pos, v)) if (!globals.json) info(dim(n));
12857
12957
  const context = (await readConfig()).workspace;
12858
12958
  if (!explicitToken()?.startsWith("gat_")) {
@@ -12898,6 +12998,7 @@ async function main() {
12898
12998
  case "apply":
12899
12999
  return applyCmd(sub, v);
12900
13000
  case "agents":
13001
+ if ((sub === "ls" || sub === "list" || !sub) && v.user && v.app !== void 0) return appAgentsOf(v.user, v.app);
12901
13002
  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
13003
  if (sub === "create" || sub === "new") {
12903
13004
  if (v.workspace === "none") delete v.workspace;
@@ -12964,6 +13065,7 @@ async function main() {
12964
13065
  case "partner":
12965
13066
  return partnerCmd(sub, a1, a2, { ...v, token: tokens.turn });
12966
13067
  case "usage":
13068
+ if (v.boxes && v.by === "template") return fleetBoxes();
12967
13069
  if (v.boxes) return boxesUsage(v.workspace ?? context);
12968
13070
  if (v.workspace || v.by || v.days) return workspaceUsage(v.workspace ?? context, v);
12969
13071
  return meUsage();
@@ -12977,6 +13079,7 @@ async function main() {
12977
13079
  return bagsCmd(sub, a1, a2, v, context);
12978
13080
  case "spaces":
12979
13081
  case "space":
13082
+ case "workspaces":
12980
13083
  return spacesCmd(sub, a1, a2, v, context);
12981
13084
  case "plan":
12982
13085
  return planCmd(sub, a1, v, context);
@@ -12994,7 +13097,7 @@ async function main() {
12994
13097
  throw unknown(cmd, cmd, COMMANDS, "ghosty --help");
12995
13098
  }
12996
13099
  }
12997
- var notice = updateNotice("0.30.0").catch(() => null);
13100
+ var notice = updateNotice("0.31.0").catch(() => null);
12998
13101
  main().then(
12999
13102
  async () => {
13000
13103
  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.31.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": {