@ghostystudio/cli 0.86.0 → 0.87.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 +2 -0
  2. package/dist/index.js +102 -19
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -74,6 +74,8 @@ ghosty channels whatsapp ls|enable|disable <id> [number] [--dry-run]
74
74
  ghosty channels whatsapp link|unlink <id> --integration ID --answer on|off [--forward] [--dry-run] # bring a WABA number from Formmy
75
75
  ghosty channels whatsapp history <id> --integration ID [--days 7] [--dry-run] # its recent cards, messages and orders (no duplicates)
76
76
  ghosty channels whatsapp|messenger connect <id> # opens Channels to connect with Meta (browser sign-in)
77
+ ghosty channels messenger status <id> # each page: who answers, last webhook, token health
78
+ ghosty channels messenger on|off <id> <page> [--take] # this agent answers the page, or stops (off keeps logging)
77
79
  ghosty channels mteams connect <id> --app-id <guid> --tenant <guid> # Microsoft Teams bot (secret in GHOSTY_MTEAMS_SECRET); ls shows endpoint and last problem
78
80
  ghosty channels wa import <id> --from-file F | --from-dir DIR --own-number yes|no [--dry-run] # personal WhatsApp session, no QR
79
81
  ghosty channels wa pair|status|pause <id> [--phone N]
package/dist/index.js CHANGED
@@ -1175,7 +1175,7 @@ async function postLong(path2, body, timeoutMs = 9e5) {
1175
1175
  const req = httpsRequest(`${base}${path2}`, {
1176
1176
  method: "POST",
1177
1177
  timeout: timeoutMs,
1178
- headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.86.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
1178
+ headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.87.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
1179
1179
  }, (res) => {
1180
1180
  let buf = "";
1181
1181
  res.on("data", (c) => buf += c);
@@ -1217,7 +1217,7 @@ async function request(path2, opts = {}, retried = false) {
1217
1217
  method: opts.method ?? (opts.body !== void 0 || opts.raw !== void 0 ? "POST" : "GET"),
1218
1218
  headers: {
1219
1219
  authorization: `Bearer ${token}`,
1220
- "user-agent": `ghosty-cli/${"0.86.0"}`,
1220
+ "user-agent": `ghosty-cli/${"0.87.0"}`,
1221
1221
  "accept-language": lang(),
1222
1222
  ...opts.body !== void 0 ? { "content-type": "application/json" } : {},
1223
1223
  ...opts.headers
@@ -11237,7 +11237,7 @@ async function call(path2, v, init = {}) {
11237
11237
  "x-ghosty-ts": ts,
11238
11238
  "x-ghosty-sig": sig,
11239
11239
  "content-type": "application/json",
11240
- "user-agent": `ghosty-cli/${"0.86.0"}`
11240
+ "user-agent": `ghosty-cli/${"0.87.0"}`
11241
11241
  },
11242
11242
  body: method === "GET" ? void 0 : raw
11243
11243
  });
@@ -12579,7 +12579,9 @@ async function channelsCmd(kind, sub, id, ref, v) {
12579
12579
  }
12580
12580
  if (kind === "messenger" || kind === "fb") {
12581
12581
  if (!id) throw usage(t("Missing agent id.", "Falta el id del agente."), t("Example", "Ejemplo") + ": ghosty channels messenger ls <agent-id> [--orphans]");
12582
- if (sub && sub !== "ls" && sub !== "list") throw usage(t(`Unknown subcommand: ${sub}.`, `Subcomando desconocido: ${sub}.`), t("Try", "Prueba") + ": ghosty channels messenger ls <agent-id> [--orphans]");
12582
+ if (sub === "status") return messengerStatusCmd(id);
12583
+ if (sub === "on" || sub === "off") return messengerToggleCmd(id, ref, sub === "on", v);
12584
+ if (sub && sub !== "ls" && sub !== "list") throw usage(t(`Unknown subcommand: ${sub}.`, `Subcomando desconocido: ${sub}.`), t("Try", "Prueba") + ": ghosty channels messenger ls|status|on|off <agent-id> [page]");
12583
12585
  return linksCmd(id, "messenger", v.orphans);
12584
12586
  }
12585
12587
  if (kind !== "whatsapp" && kind !== "wa") throw usage(t(`Unknown channel: ${kind ?? "(none)"}.`, `Canal desconocido: ${kind ?? "(ninguno)"}.`), t("Try", "Prueba") + ": ghosty channels whatsapp ls <agent-id>");
@@ -12821,6 +12823,41 @@ async function pageWithRetry(fetchPage, where, waitMs = (n) => 5e3 * n, tries =
12821
12823
  }
12822
12824
  }
12823
12825
  var lastCell = (l) => l ? `${l.agentName ?? l.agentId} ${dim(l.at.slice(0, 16).replace("T", " "))}` : dim("\u2014");
12826
+ function findPage(pages, ref) {
12827
+ const hit = pages.find((p) => p.pageId === ref) ?? pages.find((p) => (p.name ?? "").toLowerCase() === ref.toLowerCase());
12828
+ if (!hit) throw usage(t(`No Messenger page "${ref}" for this agent.`, `Este agente no tiene la p\xE1gina de Messenger "${ref}".`), pages.length ? `${t("Available", "Disponibles")}: ${pages.map((p) => p.name ?? p.pageId).join(", ")}` : t("Connect one first in the web (the agent's Channels page).", "Primero conecta una en la web (p\xE1gina Canales del agente)."));
12829
+ return hit;
12830
+ }
12831
+ var ago = (iso) => {
12832
+ if (!iso) return dim(t("never", "nunca"));
12833
+ const h = (Date.now() - new Date(iso).getTime()) / 36e5;
12834
+ return h < 1 ? t("< 1 h ago", "hace < 1 h") : h < 48 ? t(`${Math.round(h)} h ago`, `hace ${Math.round(h)} h`) : t(`${Math.round(h / 24)} d ago`, `hace ${Math.round(h / 24)} d`);
12835
+ };
12836
+ async function messengerStatusCmd(id) {
12837
+ const r = await api(`/api/v2/agents/${enc11(id)}/messenger?health=1`);
12838
+ const health = (p) => !p.health ? dim("?") : p.health.ok ? p.health.subscribed ? t("ok", "ok") : lavender(t("not subscribed: reconnect it", "sin suscripci\xF3n: recon\xE9ctala")) : lavender(p.health.code === 190 ? t("token dead: reconnect it", "token muerto: recon\xE9ctala") : p.health.message);
12839
+ const rows = r.pages.map((p) => ({
12840
+ page: p.name ?? p.pageId,
12841
+ answers: p.answers === "this" ? lavender(t("this agent", "este agente")) : p.answers === "other" ? t("another agent", "otro agente") : dim(t("nobody (records only)", "nadie (s\xF3lo registra)")),
12842
+ "last webhook": ago(p.lastWebhookAt),
12843
+ health: health(p),
12844
+ id: p.pageId
12845
+ }));
12846
+ return out(r, () => rows.length ? table(rows, ["page", "answers", "last webhook", "health", "id"]) : dim(t("No Messenger pages yet. Connect one in the agent's Channels page.", "Todav\xEDa no hay p\xE1ginas de Messenger. Conecta una en la p\xE1gina Canales del agente.")));
12847
+ }
12848
+ async function messengerToggleCmd(id, ref, on, v) {
12849
+ if (!ref) throw usage(t("Missing the page (id or name).", "Falta la p\xE1gina (id o nombre)."), t("Example", "Ejemplo") + `: ghosty channels messenger ${on ? "on" : "off"} ${id} <page-id>`);
12850
+ const cur = await api(`/api/v2/agents/${enc11(id)}/messenger`);
12851
+ const page = findPage(cur.pages, ref);
12852
+ const label = page.name ?? page.pageId;
12853
+ const plan = on ? page.answers === "other" && !v.take ? null : t(`This agent will answer the Messenger and comments of "${label}" (real customers).`, `Este agente va a contestar el Messenger y los comentarios de \xAB${label}\xBB (clientes reales).`) : t(`Nobody will answer "${label}"; messages keep landing on the board.`, `Nadie va a contestar \xAB${label}\xBB; los mensajes se siguen registrando en el tablero.`);
12854
+ if (!plan) throw usage(t(`Another agent answers "${label}".`, `Otro agente contesta \xAB${label}\xBB.`), t("Add --take to move it to this agent.", "Agrega --take para pasarla a este agente."));
12855
+ if (v["dry-run"]) return out({ dryRun: true, pageId: page.pageId, on }, () => `${plan}
12856
+ ${dim(t("(dry run: nothing changed)", "(dry run: no se cambi\xF3 nada)"))}`);
12857
+ if (on) await confirm(`${plan} ${t("Continue?", "\xBFSeguir?")}`, { yes: v.yes, retry: `ghosty channels messenger on ${id} ${page.pageId}${v.take ? " --take" : ""}` });
12858
+ const r = await api(`/api/v2/agents/${enc11(id)}/messenger`, { method: "POST", body: { pageId: page.pageId, on, ...v.take ? { take: true } : {} } });
12859
+ return out(r, () => on ? t(`Now answering "${label}".`, `Ya contesta \xAB${label}\xBB.`) : t(`No longer answering "${label}"; messages are still logged.`, `Ya no contesta \xAB${label}\xBB; los mensajes se siguen registrando.`));
12860
+ }
12824
12861
  async function linksCmd(id, kind, orphans) {
12825
12862
  const r = await api(`/api/v2/agents/${enc11(id)}/channels/links?kind=${kind}${orphans ? "&orphans=1" : ""}`);
12826
12863
  const now = (l) => l.answersNow ? l.answersNow.agentName ?? l.answersNow.agentId : l.recordsOnly ? t("nobody (records only)", "nadie (s\xF3lo registra)") : l.via === "easybits" ? "EasyBits" : l.via === "partner" ? "partner" : lavender(t("nobody", "nadie"));
@@ -13074,11 +13111,13 @@ init_api();
13074
13111
  init_util();
13075
13112
  init_i18n();
13076
13113
  var enc12 = encodeURIComponent;
13077
- async function boardsCmd(sub, a1, a2, v, context) {
13114
+ var FOLLOWUP_ACTIONS = /* @__PURE__ */ new Set(["on", "off", "template", "dry-run"]);
13115
+ async function boardsCmd(sub, a1, a2, v, context, a3) {
13078
13116
  if (context && sub !== "assign") {
13079
13117
  if ((sub === "ls" || sub === "list" || !sub || sub === "move-cards") && !a1) a1 = context;
13080
13118
  else if ((sub === "create" || sub === "rm") && a1 && !a2) [a1, a2] = [context, a1];
13081
13119
  else if (sub === "access" && !a2) [a1, a2] = [context, a1 ?? "main"];
13120
+ else if (sub === "followups" && (!a1 || FOLLOWUP_ACTIONS.has(a1))) [a1, a2, a3] = [context, void 0, a1];
13082
13121
  }
13083
13122
  switch (sub ?? "ls") {
13084
13123
  case "ls":
@@ -13145,8 +13184,37 @@ ${table(r.people.map((p) => ({ email: p.email, until: fmtUntil(p.until) })), ["e
13145
13184
  return [head + people, ...changes].join("\n");
13146
13185
  });
13147
13186
  }
13187
+ // Seguimiento de cotizaciones del tablero: estado, prender/apagar, plantilla y prueba sin enviar.
13188
+ case "followups": {
13189
+ if (!a1) throw usage(t("Missing workspace.", "Falta el espacio."), t("Example", "Ejemplo") + ": ghosty boards followups <workspace> [board|main] [on|off|template|dry-run]");
13190
+ if (a2 && FOLLOWUP_ACTIONS.has(a2) && !a3) [a2, a3] = [void 0, a2];
13191
+ const board = a2 ?? "main";
13192
+ const path2 = `/api/v2/workspaces/${enc12(a1)}/boards/followups`;
13193
+ const steps = v.steps ? v.steps.split(",").map((x) => Number(x.trim())).filter((n) => n > 0) : void 0;
13194
+ const minutes = v.minutes === void 0 ? void 0 : v.minutes === "on";
13195
+ if (a3 === "dry-run") {
13196
+ const r2 = await api(path2, { method: "POST", body: { board, intent: "dry-run" } });
13197
+ return out(r2, () => r2.items.length ? table(r2.items.map((i) => ({ folio: i.folio, toque: i.step + 1, action: i.action, detail: i.detail })), ["folio", "toque", "action", "detail"]) : dim(t("Nothing due right now.", "Nada que toque ahora.")));
13198
+ }
13199
+ if (a3 === "template") {
13200
+ const r2 = await api(path2, { method: "POST", body: { board, intent: "create-template" } });
13201
+ return out(r2, () => t("Template sent to Meta for review (utility).", "Plantilla enviada a revisi\xF3n de Meta (utilidad)."));
13202
+ }
13203
+ if (a3 === "on" || a3 === "off" || steps || minutes !== void 0) {
13204
+ await api(path2, { method: "POST", body: { board, intent: "set", ...a3 ? { enabled: a3 === "on" } : {}, ...steps ? { steps } : {}, ...minutes !== void 0 ? { testMinutes: minutes } : {} } });
13205
+ } else if (a3) throw usage(t(`Unknown action: ${a3}.`, `Acci\xF3n desconocida: ${a3}.`), "on | off | template | dry-run");
13206
+ const r = await api(`${path2}?board=${enc12(board)}`);
13207
+ return out(r, () => {
13208
+ const c = r.config;
13209
+ const head = `\xAB${r.board}\xBB: ${c.enabled ? lavender(t("on", "prendido")) : t("off", "apagado")} \xB7 ${t("touches at", "toques a los")} ${c.steps.join(", ")} ${c.testMinutes ? "min" : t("days", "d\xEDas")} \xB7 ${c.template} ${dim(`(${r.template.status ?? r.template.error ?? t("not in Meta", "sin dar de alta en Meta")})`)}`;
13210
+ const s2 = r.stats;
13211
+ const nums = `${s2.active} ${t("active", "en curso")} \xB7 ${s2.sent} ${t("sent", "enviados")} \xB7 ${s2.replied} ${t("replied", "contestaron")} \xB7 ${s2.paid} ${t("paid/closed", "pagaron o cerraron")} \xB7 ${s2.done} ${t("no reply", "sin respuesta")}`;
13212
+ const rows = r.recent.map((x) => ({ folio: x.folio, name: x.nombre ?? "", step: x.step, status: x.stop_reason ? `${x.status}: ${x.stop_reason}` : x.status, next: x.next_at ? new Date(x.next_at * 1e3).toISOString().slice(0, 16) : "" }));
13213
+ return [head, nums, rows.length ? table(rows, ["folio", "name", "step", "status", "next"]) : ""].filter(Boolean).join("\n");
13214
+ });
13215
+ }
13148
13216
  default:
13149
- throw usage(t(`Unknown boards subcommand: ${sub}.`, `Subcomando de boards desconocido: ${sub}.`), t("Try", "Prueba") + ": ghosty boards ls | create | assign | move-cards | access | rm");
13217
+ throw usage(t(`Unknown boards subcommand: ${sub}.`, `Subcomando de boards desconocido: ${sub}.`), t("Try", "Prueba") + ": ghosty boards ls | create | assign | move-cards | access | followups | rm");
13150
13218
  }
13151
13219
  }
13152
13220
 
@@ -13645,13 +13713,16 @@ var HELPS_ES = {
13645
13713
  ["--from / --to B", "move-cards: tableros por nombre, id o main"],
13646
13714
  ["--integration ID | --phone N | --all", "move-cards: qu\xE9 tarjetas (el filtro es obligatorio)"],
13647
13715
  ["-n, --dry-run", "move-cards: cuenta y muestra una muestra, no cambia nada"],
13648
- ["--yes", "move-cards: se salta la confirmaci\xF3n (obligatorio sin terminal)"]
13716
+ ["--yes", "move-cards: se salta la confirmaci\xF3n (obligatorio sin terminal)"],
13717
+ ["--steps 2,7,14", "followups: d\xEDas despu\xE9s de la cotizaci\xF3n de cada toque (hasta 5)"],
13718
+ ["--minutes on|off", "followups (staff): los pasos cuentan en minutos, para probar sin esperar d\xEDas"]
13649
13719
  ],
13650
13720
  notes: [
13651
13721
  "assign cambia a d\xF3nde van las tarjetas NUEVAS; move-cards trae las que ya existen (hilo, pedidos, actividad) con folio nuevo. Una tarjeta cuya conversaci\xF3n ya est\xE1 en el tablero destino se salta.",
13652
13722
  "Leer requiere ser miembro; cambiar requiere ser due\xF1o o staff del espacio.",
13653
13723
  "rm s\xF3lo borra un tablero vac\xEDo que no sea el principal.",
13654
- "access sin flags dice qui\xE9n ve el tablero; due\xF1o y staff ven todos. Por qu\xE9 alguien lo ve: ghosty access why <correo> --workspace <espacio> --board <tablero>."
13724
+ "access sin flags dice qui\xE9n ve el tablero; due\xF1o y staff ven todos. Por qu\xE9 alguien lo ve: ghosty access why <correo> --workspace <espacio> --board <tablero>.",
13725
+ "followups: cada cotizaci\xF3n que manda el agente recibe hasta 3 toques por WhatsApp (2, 7 y 14 d\xEDas) escritos con la pl\xE1tica; paran si el cliente contesta, paga o alguien toma la pl\xE1tica. Fuera de la ventana de 24 h usa la plantilla de utilidad (template = mandarla a Meta). Cambiarlo pide el mismo permiso que mandar campa\xF1as."
13655
13726
  ]
13656
13727
  },
13657
13728
  cards: {
@@ -14411,6 +14482,8 @@ HELPS.channels = {
14411
14482
  "ghosty channels whatsapp disable <agent-id> <number> [--dry-run]",
14412
14483
  "ghosty channels whatsapp integrations <agent-id> [--email OTHER] [--orphans]",
14413
14484
  "ghosty channels messenger ls <agent-id> [--orphans]",
14485
+ "ghosty channels messenger status <agent-id> # who answers each page, last webhook, token health",
14486
+ "ghosty channels messenger on|off <agent-id> <page> [--take] [--dry-run] [--yes]",
14414
14487
  "ghosty channels whatsapp|messenger connect <agent-id> [--no-open] # opens Channels: Meta sign-in in the browser",
14415
14488
  "ghosty channels whatsapp link <agent-id> --integration <formmy-id> --answer on|off [--forward] [--dry-run] [--yes]",
14416
14489
  "ghosty channels whatsapp unlink <agent-id> --integration <formmy-id> [--dry-run] [--yes]",
@@ -14545,7 +14618,8 @@ HELPS.boards = {
14545
14618
  "ghosty boards assign <agent-id> <board|main>",
14546
14619
  "ghosty boards move-cards <workspace> --from <board> --to <board> [--integration ID | --phone N | --all] [--dry-run] [--yes]",
14547
14620
  'ghosty boards rm <workspace> "Name" [--yes]',
14548
- "ghosty boards access <workspace> [board|main] [--add EMAIL] [--remove EMAIL] [--restrict] [--everyone] [--until 2026-10-28|14d]"
14621
+ "ghosty boards access <workspace> [board|main] [--add EMAIL] [--remove EMAIL] [--restrict] [--everyone] [--until 2026-10-28|14d]",
14622
+ "ghosty boards followups <workspace> [board|main] [on|off|template|dry-run] [--steps 2,7,14] [--minutes on|off]"
14549
14623
  ],
14550
14624
  flags: [
14551
14625
  ["--add / --remove EMAIL", "access: add to or take off the board's \xABonly these people\xBB list (repeatable or comma-separated; only workspace members)"],
@@ -14556,10 +14630,12 @@ HELPS.boards = {
14556
14630
  ["--from / --to B", "move-cards: boards by name, id or main"],
14557
14631
  ["--integration ID | --phone N | --all", "move-cards: which cards (a filter is required)"],
14558
14632
  ["-n, --dry-run", "move-cards: count and show a sample, change nothing"],
14559
- ["--yes", "move-cards: skip the confirmation (required without a terminal)"]
14633
+ ["--yes", "move-cards: skip the confirmation (required without a terminal)"],
14634
+ ["--steps 2,7,14", "followups: days after the quote for each touch (up to 5)"],
14635
+ ["--minutes on|off", "followups (staff): count the steps in minutes, to test without waiting days"]
14560
14636
  ],
14561
- examples: ["ghosty boards ls siiqtec", 'ghosty boards create siiqtec "Siiqtec - Ixmiquilpan" --columns-from main', 'ghosty boards assign <agent-id> "Siiqtec - Ixmiquilpan"', 'ghosty boards move-cards siiqtec --from main --to "Siiqtec - Ixmiquilpan" --integration 6a3b02ad\u2026 --dry-run'],
14562
- notes: ["assign changes where NEW cards go; move-cards brings the existing ones (thread, orders, activity) with a new folio. A card whose conversation is already on the target board is skipped.", "Reading needs membership; changing needs owner or staff of the workspace.", "rm only deletes an empty board that is not the main one.", "access without flags shows who sees the board; owner and staff always see every board. Why someone sees it: ghosty access why <email> --workspace <ws> --board <board>."],
14637
+ examples: ["ghosty boards ls siiqtec", "ghosty boards followups siiqtec main template", "ghosty boards followups siiqtec main on", 'ghosty boards create siiqtec "Siiqtec - Ixmiquilpan" --columns-from main', 'ghosty boards assign <agent-id> "Siiqtec - Ixmiquilpan"', 'ghosty boards move-cards siiqtec --from main --to "Siiqtec - Ixmiquilpan" --integration 6a3b02ad\u2026 --dry-run'],
14638
+ notes: ["assign changes where NEW cards go; move-cards brings the existing ones (thread, orders, activity) with a new folio. A card whose conversation is already on the target board is skipped.", "Reading needs membership; changing needs owner or staff of the workspace.", "rm only deletes an empty board that is not the main one.", "access without flags shows who sees the board; owner and staff always see every board. Why someone sees it: ghosty access why <email> --workspace <ws> --board <board>.", "followups: every quote the agent sends gets up to 3 WhatsApp touches (2, 7 and 14 days) written from the conversation; they stop when the customer replies, pays or someone takes the chat. Outside the 24 h window it uses the utility template (template = submit it to Meta). Changing it takes the same permission as sending campaigns."],
14563
14639
  learn: `${REF}#tablero`
14564
14640
  };
14565
14641
  HELPS.cards = {
@@ -16906,6 +16982,8 @@ ${bold("Configure an agent")}
16906
16982
  channels whatsapp|messenger connect <id> Opens the agent's Channels page to connect a number or page with Meta (browser sign-in) [--no-open]
16907
16983
  channels whatsapp integrations <id> Its owner's WhatsApp Business numbers in Formmy (number, who answers today, who answered last, Integration id) [--orphans: answered by nobody but with turns]
16908
16984
  channels messenger ls <id> The workspace's Messenger pages: who answers now and who answered last [--orphans]
16985
+ channels messenger status <id> Each page: who answers, last webhook and token health (asks Meta now)
16986
+ channels messenger on|off <id> <page> Answer a page with this agent, or stop (off keeps logging to the board) [--take --dry-run --yes]
16909
16987
  channels mteams ls|connect|zip|on|off|rm <id> [bot] Microsoft Teams bot (Azure Bot in the client's tenant) [--app-id --tenant --secret|GHOSTY_MTEAMS_SECRET --name --out]
16910
16988
  channels whatsapp link|unlink <id> --integration ID --answer on|off Bring a WhatsApp Business number from Formmy/EasyBits to this agent, or send it back [--forward] [--ads-only on|off] [--board NAME|agent] [--dry-run]
16911
16989
  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]
@@ -16913,7 +16991,7 @@ ${bold("Configure an agent")}
16913
16991
  channels wa pair <id> --phone N Pair a personal WhatsApp by code (no QR)
16914
16992
  channels wa status|pause <id> Personal WhatsApp: how it is / pause it (keeps the session)
16915
16993
  channels wa groups ls|enable|disable|set|create <id> [group] Personal WhatsApp groups it answers, and as whom [--role equipo|cliente] [--wake mention|all] [--name "New name"] [--dry-run]
16916
- boards ls|create|assign|move-cards|access|rm A workspace's boards: create (--columns-from), where an agent sends cards, move cards, who sees each one
16994
+ boards ls|create|assign|move-cards|access|followups|rm A workspace's boards: create (--columns-from), where an agent sends cards, move cards, who sees each one, quote follow-ups
16917
16995
  cards ls <id> Recent cards on its sales board [--q TEXT] [--paused] [--limit 20]
16918
16996
  cards show <id> <card> A card's thread: who wrote, text, attachments and whether gs kept a copy (folio, phone or id)
16919
16997
  cards archive <id> Archive cards in bulk (reversible) --integration X | --column X | --before 30d [--dry-run]
@@ -16936,7 +17014,7 @@ ${bold("Sites")} ${dim("(sites your agents publish at <slug>.ghosty.page)")}
16936
17014
  sites pin <slug|id> Pin it: never pauses, no banner ($49 USD/yr or $5/mo, includes up to 3 sites) [--interval year|month] [--currency usd|mxn] [--add-pack: 3 more]
16937
17015
  sites unpin <slug|id> Release its slot (back to the Free rule: 30 days + notice)
16938
17016
  sites search <slug|id> How Google finds it: clicks, impressions, searches, indexed or not [--days 28]
16939
- sites domain add|verify|rm <slug> <dom> Custom domain for an app (DNS record \u2192 verify \u2192 HTTPS) [ls <slug>: list]
17017
+ sites domain add|verify|rm <slug> <dom> Custom domain for a site or app (DNS record \u2192 verify \u2192 HTTPS) [ls <slug>: list]
16940
17018
  sites pins Your pinned-sites subscription and slots [--cancel | --resume]
16941
17019
  sites publish <file.html|folder> Publish a house site (staff): no owner, no expiry, no banner [--title] [--slug S] [--site <slug|id>: update that one]
16942
17020
  sites slug <slug|id> <new-slug> Change a site's slug (staff)
@@ -17122,6 +17200,8 @@ ${bold("Configurar un agente")}
17122
17200
  channels whatsapp|messenger connect <id> Abre la p\xE1gina Canales del agente para conectar un n\xFAmero o una p\xE1gina con Meta (inicio de sesi\xF3n en el navegador) [--no-open]
17123
17201
  channels whatsapp integrations <id> Los n\xFAmeros de WhatsApp Business de su due\xF1o en Formmy (n\xFAmero, qui\xE9n contesta hoy, qui\xE9n contest\xF3 al \xFAltimo, id de la Integration) [--orphans: sin qui\xE9n conteste pero con turnos]
17124
17202
  channels messenger ls <id> Las p\xE1ginas de Messenger del espacio: qui\xE9n contesta hoy y qui\xE9n contest\xF3 al \xFAltimo [--orphans]
17203
+ channels messenger status <id> Cada p\xE1gina: qui\xE9n contesta, \xFAltimo webhook y salud del token (le pregunta a Meta en el momento)
17204
+ channels messenger on|off <id> <p\xE1gina> Que este agente conteste una p\xE1gina, o que deje de hacerlo (off sigue registrando en el tablero) [--take --dry-run --yes]
17125
17205
  channels mteams ls|connect|zip|on|off|rm <id> [bot] Bot de Microsoft Teams (Azure Bot en el tenant del cliente) [--app-id --tenant --secret|GHOSTY_MTEAMS_SECRET --name --out]
17126
17206
  channels whatsapp link|unlink <id> --integration ID --answer on|off Trae un n\xFAmero de WhatsApp Business de Formmy/EasyBits a este agente, o devu\xE9lvelo [--forward] [--ads-only on|off] [--board NOMBRE|agent] [--dry-run]
17127
17207
  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]
@@ -17129,7 +17209,7 @@ ${bold("Configurar un agente")}
17129
17209
  channels wa pair <id> --phone N Vincula un WhatsApp personal con c\xF3digo (sin QR)
17130
17210
  channels wa status|pause <id> WhatsApp personal: c\xF3mo est\xE1 / pausarlo (conserva la sesi\xF3n)
17131
17211
  channels wa groups ls|enable|disable|set|create <id> [grupo] Grupos de WhatsApp personal que contesta, y como qui\xE9n [--role equipo|cliente] [--wake mention|all] [--name "Nombre nuevo"] [--dry-run]
17132
- boards ls|create|assign|move-cards|access|rm Los tableros de un espacio: crear (--columns-from), a d\xF3nde manda tarjetas un agente, mover tarjetas, qui\xE9n ve cada uno
17212
+ boards ls|create|assign|move-cards|access|followups|rm Los tableros de un espacio: crear (--columns-from), a d\xF3nde manda tarjetas un agente, mover tarjetas, qui\xE9n ve cada uno, seguimiento de cotizaciones
17133
17213
  cards ls <id> Tarjetas recientes de su tablero de ventas [--q TEXTO] [--paused] [--limit 20]
17134
17214
  cards show <id> <tarjeta> El hilo de una tarjeta: qui\xE9n escribi\xF3, texto, adjuntos y si gs guard\xF3 copia (folio, tel\xE9fono o id)
17135
17215
  cards archive <id> Archiva tarjetas en bloque (reversible) --integration X | --column X | --before 30d [--dry-run]
@@ -17152,7 +17232,7 @@ ${bold("Sitios")} ${dim("(los sitios que publican tus agentes en <slug>.ghosty.
17152
17232
  sites pin <slug|id> F\xEDjalo: no se pausa y va sin franja ($49 USD al a\xF1o o $5 al mes, incluye hasta 3 sitios) [--interval year|month] [--currency usd|mxn] [--add-pack: 3 m\xE1s]
17153
17233
  sites unpin <slug|id> Suelta su lugar (vuelve a la regla Gratis: 30 d\xEDas + aviso)
17154
17234
  sites pins Tu suscripci\xF3n de Sitios fijos y sus lugares [--cancel | --resume]
17155
- sites domain add|verify|rm <slug> <dom> Dominio propio para una app (registro DNS \u2192 verificar \u2192 HTTPS) [ls <slug>: lista]
17235
+ sites domain add|verify|rm <slug> <dom> Dominio propio para un sitio o app (registro DNS \u2192 verificar \u2192 HTTPS) [ls <slug>: lista]
17156
17236
  sites publish <archivo.html|carpeta> Publica un sitio de la casa (staff): sin due\xF1o, sin caducidad, sin franja [--title] [--slug S] [--site <slug|id>: actualiza \xE9se]
17157
17237
  sites slug <slug|id> <slug-nuevo> Cambia el slug de un sitio (staff)
17158
17238
 
@@ -17370,6 +17450,7 @@ async function main() {
17370
17450
  "key-column": { type: "string" },
17371
17451
  "unset-env": { type: "string" },
17372
17452
  orphans: { type: "boolean" },
17453
+ take: { type: "boolean" },
17373
17454
  skipped: { type: "boolean" },
17374
17455
  paused: { type: "boolean" },
17375
17456
  email: { type: "string" },
@@ -17386,6 +17467,8 @@ async function main() {
17386
17467
  enable: { type: "string" },
17387
17468
  disable: { type: "string" },
17388
17469
  "columns-from": { type: "string" },
17470
+ steps: { type: "string" },
17471
+ minutes: { type: "string" },
17389
17472
  add: { type: "string", multiple: true },
17390
17473
  remove: { type: "string", multiple: true },
17391
17474
  everyone: { type: "boolean" },
@@ -17545,7 +17628,7 @@ async function main() {
17545
17628
  const tokens = splitTokens(pos, v);
17546
17629
  globals.token = tokens.session;
17547
17630
  globals.profile = v.profile;
17548
- if (v.version) return out({ version: "0.86.0" }, () => "0.86.0");
17631
+ if (v.version) return out({ version: "0.87.0" }, () => "0.87.0");
17549
17632
  for (const n of normalizeArgs(pos, v)) if (!globals.json) info(dim(n));
17550
17633
  const context = (await readConfig()).workspace;
17551
17634
  if (!explicitToken()?.startsWith("gat_")) {
@@ -17652,7 +17735,7 @@ async function main() {
17652
17735
  case "board":
17653
17736
  return boardCmd(sub, a1, v, a2);
17654
17737
  case "boards":
17655
- return boardsCmd(sub, a1, a2, v, context);
17738
+ return boardsCmd(sub, a1, a2, v, context, pos[4]);
17656
17739
  case "channels":
17657
17740
  if (a1 === "connect" && ["whatsapp", "wa", "messenger", "fb"].includes(sub ?? "")) {
17658
17741
  if (!a2) throw usage(t("Missing agent id.", "Falta el id del agente."), `ghosty channels ${sub} connect <agent-id>`);
@@ -17748,7 +17831,7 @@ async function main() {
17748
17831
  throw unknown(cmd, cmd, COMMANDS, "ghosty --help");
17749
17832
  }
17750
17833
  }
17751
- var notice = updateNotice("0.86.0").catch(() => null);
17834
+ var notice = updateNotice("0.87.0").catch(() => null);
17752
17835
  main().then(
17753
17836
  async () => {
17754
17837
  const n = await notice;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghostystudio/cli",
3
- "version": "0.86.0",
3
+ "version": "0.87.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": {