@ghostystudio/cli 0.85.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 +7 -0
  2. package/dist/index.js +164 -35
  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]
@@ -105,6 +107,7 @@ ghosty watch add <id> <url> [--every 24h] [--focus "heading"] [--instruction "
105
107
  ghosty watch ls <id>
106
108
  ghosty watch check <id> <watch-id> [--dry-run]
107
109
  ghosty watch rm|resume <id> <watch-id>
110
+ ghosty tts "Hola, te aviso por WhatsApp." [--voice em_santa|em_alex|ef_dora] [--out voz.wav] # the house voice (Mexican Spanish + pronunciation lexicon)
108
111
  ghosty teams post <workspace>#<room>[/<thread-id>] "msg" [--wait] # write there as you; any @agent it mentions answers
109
112
 
110
113
  ghosty me files ls|get|export|upload|rm [--q TEXT] [--kind image|video|audio|document|other] [--cursor C]
@@ -150,6 +153,10 @@ Every command has its own help with examples: `ghosty <command> --help` or `ghos
150
153
 
151
154
  Exit codes: `0` ok · `1` API error · `2` usage error · `3` not signed in.
152
155
 
156
+ ### Changes in 0.86.0
157
+
158
+ - `ghosty tts "text" [--voice em_santa|em_alex|ef_dora] [--out FILE]`: hear the house voice (Kokoro, Mexican Spanish, with Ghosty's pronunciation lexicon) say a text, saved as a .wav. Up to 400 characters.
159
+
153
160
  ### Changes in 0.84.0
154
161
 
155
162
  - `coupons create|set` take `--from` (the lent plan starts then), `--redeem-by` (last moment to redeem) and `--convert-promo` (Stripe promotion code offered in /c/uso when the lent plan is about to end). New `coupons show <CODE>` (staff): who redeemed it, their plan window and who converted to a paid plan. Codes are accent- and case-insensitive.
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.85.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.85.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
@@ -1264,7 +1264,7 @@ var mteams_exports = {};
1264
1264
  __export(mteams_exports, {
1265
1265
  mteamsCmd: () => mteamsCmd
1266
1266
  });
1267
- import { writeFile as writeFile8 } from "node:fs/promises";
1267
+ import { writeFile as writeFile9 } from "node:fs/promises";
1268
1268
  function list(bots) {
1269
1269
  return bots.length ? table(
1270
1270
  bots.map((b) => ({
@@ -1306,7 +1306,7 @@ ${dim(`${t("Next", "Sigue")}: ghosty channels mteams zip ${id} ${b?.id ?? "<bot-
1306
1306
  const res = await request(`${path2}/${enc10(botId)}/zip`);
1307
1307
  const bytes = Buffer.from(await res.arrayBuffer());
1308
1308
  const file = v.out ?? "ghosty-teams.zip";
1309
- await writeFile8(file, bytes);
1309
+ await writeFile9(file, bytes);
1310
1310
  return out({ saved: file, bytes: bytes.length }, () => `${lavender("\u2713")} ${t(`Saved ${file}: upload it in Teams admin \u2192 Manage apps.`, `Se guard\xF3 ${file}: s\xFAbelo en Teams admin \u2192 Administrar apps.`)}`);
1311
1311
  }
1312
1312
  case "on":
@@ -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.85.0"}`
11240
+ "user-agent": `ghosty-cli/${"0.87.0"}`
11241
11241
  },
11242
11242
  body: method === "GET" ? void 0 : raw
11243
11243
  });
@@ -11571,6 +11571,28 @@ async function imageCmd(sub, a1, a2, v) {
11571
11571
  );
11572
11572
  }
11573
11573
 
11574
+ // src/commands/tts.ts
11575
+ init_api();
11576
+ init_util();
11577
+ init_i18n();
11578
+ import { writeFile as writeFile7 } from "node:fs/promises";
11579
+ var MAX_CHARS = 400;
11580
+ var VOICES = ["em_santa", "em_alex", "ef_dora"];
11581
+ async function ttsCmd(text, v) {
11582
+ const ex = 'ghosty tts "Hola, gracias por escribir." [--voice em_santa|em_alex|ef_dora] [--out voz.wav]';
11583
+ const said = text?.trim();
11584
+ if (!said) throw usage(t("Missing the text to say.", "Falta el texto a decir."), ex);
11585
+ if (said.length > MAX_CHARS) throw usage(t(`Too long: ${said.length} characters (max ${MAX_CHARS}). Split it into sentences.`, `Demasiado largo: ${said.length} caracteres (m\xE1x. ${MAX_CHARS}). P\xE1rtelo en frases.`), ex);
11586
+ const voice = v.voice ?? "em_santa";
11587
+ if (!VOICES.includes(voice)) throw usage(t(`Unknown voice: ${voice}.`, `Voz desconocida: ${voice}.`), `${t("Voices", "Voces")}: ${VOICES.join(", ")}`);
11588
+ const res = await request("/api/v2/me/tts", { body: { text: said, voice } });
11589
+ const bytes = Buffer.from(await res.arrayBuffer());
11590
+ const file = v.out ?? "voz.wav";
11591
+ await writeFile7(file, bytes);
11592
+ const durationMs = Number(res.headers.get("x-duration-ms") ?? 0);
11593
+ return out({ saved: file, voice, bytes: bytes.length, durationMs }, () => `${lavender("\u2713")} ${file} (${(durationMs / 1e3).toFixed(1)} s) ${dim(t(`\xB7 play it: afplay ${file}`, `\xB7 esc\xFAchalo: afplay ${file}`))}`);
11594
+ }
11595
+
11574
11596
  // src/commands/watch.ts
11575
11597
  init_api();
11576
11598
  init_util();
@@ -11652,7 +11674,7 @@ ${dim(`id ${w.id} \xB7 ${t("conversation", "conversaci\xF3n")} ${w.sessionId} \x
11652
11674
 
11653
11675
  // src/commands/data.ts
11654
11676
  init_api();
11655
- import { readdir as readdir2, readFile as readFile6, writeFile as writeFile7 } from "node:fs/promises";
11677
+ import { readdir as readdir2, readFile as readFile6, writeFile as writeFile8 } from "node:fs/promises";
11656
11678
  import { basename as basename3, join as join5 } from "node:path";
11657
11679
 
11658
11680
  // src/rows.ts
@@ -11821,7 +11843,7 @@ async function dbsExport(id, db, v) {
11821
11843
  const suggested = /filename="([^"]+)"/.exec(res.headers.get("content-disposition") ?? "")?.[1] ?? (format === "csv" ? `${db}-${v.table}.csv` : `${db}.sqlite`);
11822
11844
  const file = v.out ?? suggested;
11823
11845
  const bytes = Buffer.from(await res.arrayBuffer());
11824
- await writeFile7(file, bytes);
11846
+ await writeFile8(file, bytes);
11825
11847
  const countsHeader = res.headers.get("x-gs-row-counts");
11826
11848
  const counts = countsHeader ? JSON.parse(Buffer.from(countsHeader, "base64").toString("utf8")) : {};
11827
11849
  const rows = Object.entries(counts).map(([tb, n]) => ({ table: tb, rows: n }));
@@ -12557,7 +12579,9 @@ async function channelsCmd(kind, sub, id, ref, v) {
12557
12579
  }
12558
12580
  if (kind === "messenger" || kind === "fb") {
12559
12581
  if (!id) throw usage(t("Missing agent id.", "Falta el id del agente."), t("Example", "Ejemplo") + ": ghosty channels messenger ls <agent-id> [--orphans]");
12560
- 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]");
12561
12585
  return linksCmd(id, "messenger", v.orphans);
12562
12586
  }
12563
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>");
@@ -12799,6 +12823,41 @@ async function pageWithRetry(fetchPage, where, waitMs = (n) => 5e3 * n, tries =
12799
12823
  }
12800
12824
  }
12801
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
+ }
12802
12861
  async function linksCmd(id, kind, orphans) {
12803
12862
  const r = await api(`/api/v2/agents/${enc11(id)}/channels/links?kind=${kind}${orphans ? "&orphans=1" : ""}`);
12804
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"));
@@ -13052,11 +13111,13 @@ init_api();
13052
13111
  init_util();
13053
13112
  init_i18n();
13054
13113
  var enc12 = encodeURIComponent;
13055
- 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) {
13056
13116
  if (context && sub !== "assign") {
13057
13117
  if ((sub === "ls" || sub === "list" || !sub || sub === "move-cards") && !a1) a1 = context;
13058
13118
  else if ((sub === "create" || sub === "rm") && a1 && !a2) [a1, a2] = [context, a1];
13059
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];
13060
13121
  }
13061
13122
  switch (sub ?? "ls") {
13062
13123
  case "ls":
@@ -13123,8 +13184,37 @@ ${table(r.people.map((p) => ({ email: p.email, until: fmtUntil(p.until) })), ["e
13123
13184
  return [head + people, ...changes].join("\n");
13124
13185
  });
13125
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
+ }
13126
13216
  default:
13127
- 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");
13128
13218
  }
13129
13219
  }
13130
13220
 
@@ -13432,6 +13522,16 @@ var HELPS_ES = {
13432
13522
  summary: "Ghosty para Chrome: cerrar la sesi\xF3n del navegador.",
13433
13523
  notes: ["Revoca todos los tokens de Ghosty para Chrome que tengan tus agentes de terminal (Claude Code, CLI, @ghostystudio/browser-mcp) y desconecta la extensi\xF3n. Para volver, copia un comando nuevo del panel de la extensi\xF3n."]
13434
13524
  },
13525
+ tts: {
13526
+ summary: "Oye la voz de la casa decir un texto (Kokoro, espa\xF1ol de M\xE9xico, con el l\xE9xico de pronunciaci\xF3n de Ghosty): la misma de las notas de voz. Guarda un .wav.",
13527
+ usage: ['ghosty tts "texto" [--voice em_santa|em_alex|ef_dora] [--out ARCHIVO]'],
13528
+ flags: [
13529
+ ["--voice V", "em_santa (hombre, la de la casa, default), em_alex (hombre) o ef_dora (mujer)"],
13530
+ ["--out FILE", "d\xF3nde guardar el .wav (default voz.wav)"]
13531
+ ],
13532
+ examples: ['ghosty tts "Hola, gracias por escribir. Te aviso por WhatsApp."', 'ghosty tts "Tu cita qued\xF3 el jueves a las cinco." --voice ef_dora --out cita.wav && afplay cita.wav'],
13533
+ notes: ["Hasta 400 caracteres y 60 frases por minuto. Las marcas (WhatsApp, Claude, GitHub, ghosty.studio\u2026) se escriben normal: el l\xE9xico corrige c\xF3mo suenan."]
13534
+ },
13435
13535
  image: {
13436
13536
  summary: "Im\xE1genes con IA (Gemini \xABNano Banana Pro\xBB) para los negocios de tu espacio de partner: generar, editar una que ya existe o componer con referencias; cobradas del saldo del espacio.",
13437
13537
  replace: [
@@ -13613,13 +13713,16 @@ var HELPS_ES = {
13613
13713
  ["--from / --to B", "move-cards: tableros por nombre, id o main"],
13614
13714
  ["--integration ID | --phone N | --all", "move-cards: qu\xE9 tarjetas (el filtro es obligatorio)"],
13615
13715
  ["-n, --dry-run", "move-cards: cuenta y muestra una muestra, no cambia nada"],
13616
- ["--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"]
13617
13719
  ],
13618
13720
  notes: [
13619
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.",
13620
13722
  "Leer requiere ser miembro; cambiar requiere ser due\xF1o o staff del espacio.",
13621
13723
  "rm s\xF3lo borra un tablero vac\xEDo que no sea el principal.",
13622
- "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."
13623
13726
  ]
13624
13727
  },
13625
13728
  cards: {
@@ -14261,6 +14364,16 @@ var HELPS = {
14261
14364
  notes: ["Revokes every Ghosty for Chrome token your terminal agents hold (Claude Code, CLI, @ghostystudio/browser-mcp) and disconnects the extension. Copy a new command from the extension panel to reconnect."],
14262
14365
  learn: `${REF}#navegador`
14263
14366
  },
14367
+ tts: {
14368
+ summary: "Hear the house voice say a text (Kokoro, Mexican Spanish, with Ghosty's pronunciation lexicon): the same voice as the voice notes. Saves a .wav.",
14369
+ usage: ['ghosty tts "text" [--voice em_santa|em_alex|ef_dora] [--out FILE]'],
14370
+ flags: [
14371
+ ["--voice V", "em_santa (man, the house voice, default), em_alex (man) or ef_dora (woman)"],
14372
+ ["--out FILE", "where to save the .wav (default voz.wav)"]
14373
+ ],
14374
+ examples: ['ghosty tts "Hola, gracias por escribir. Te aviso por WhatsApp."', 'ghosty tts "Tu cita qued\xF3 el jueves a las cinco." --voice ef_dora --out cita.wav && afplay cita.wav'],
14375
+ notes: ["Up to 400 characters and 60 sentences per minute. Brand names (WhatsApp, Claude, GitHub, ghosty.studio\u2026) are written normally: the lexicon fixes how they sound."]
14376
+ },
14264
14377
  image: {
14265
14378
  summary: "AI images (Gemini \xABNano Banana Pro\xBB) for your partner businesses: generate, edit an existing one, or compose from references; charged to the space's balance.",
14266
14379
  usage: [
@@ -14369,6 +14482,8 @@ HELPS.channels = {
14369
14482
  "ghosty channels whatsapp disable <agent-id> <number> [--dry-run]",
14370
14483
  "ghosty channels whatsapp integrations <agent-id> [--email OTHER] [--orphans]",
14371
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]",
14372
14487
  "ghosty channels whatsapp|messenger connect <agent-id> [--no-open] # opens Channels: Meta sign-in in the browser",
14373
14488
  "ghosty channels whatsapp link <agent-id> --integration <formmy-id> --answer on|off [--forward] [--dry-run] [--yes]",
14374
14489
  "ghosty channels whatsapp unlink <agent-id> --integration <formmy-id> [--dry-run] [--yes]",
@@ -14503,7 +14618,8 @@ HELPS.boards = {
14503
14618
  "ghosty boards assign <agent-id> <board|main>",
14504
14619
  "ghosty boards move-cards <workspace> --from <board> --to <board> [--integration ID | --phone N | --all] [--dry-run] [--yes]",
14505
14620
  'ghosty boards rm <workspace> "Name" [--yes]',
14506
- "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]"
14507
14623
  ],
14508
14624
  flags: [
14509
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)"],
@@ -14514,10 +14630,12 @@ HELPS.boards = {
14514
14630
  ["--from / --to B", "move-cards: boards by name, id or main"],
14515
14631
  ["--integration ID | --phone N | --all", "move-cards: which cards (a filter is required)"],
14516
14632
  ["-n, --dry-run", "move-cards: count and show a sample, change nothing"],
14517
- ["--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"]
14518
14636
  ],
14519
- 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'],
14520
- 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."],
14521
14639
  learn: `${REF}#tablero`
14522
14640
  };
14523
14641
  HELPS.cards = {
@@ -14860,7 +14978,7 @@ function helpText(cmd) {
14860
14978
 
14861
14979
  // src/update.ts
14862
14980
  init_i18n();
14863
- import { mkdir as mkdir5, readFile as readFile9, writeFile as writeFile9 } from "node:fs/promises";
14981
+ import { mkdir as mkdir5, readFile as readFile9, writeFile as writeFile10 } from "node:fs/promises";
14864
14982
  import { homedir as homedir3 } from "node:os";
14865
14983
  import { dirname as dirname3, join as join7 } from "node:path";
14866
14984
  var FILE = join7(process.env.XDG_CONFIG_HOME || join7(homedir3(), ".config"), "ghosty", "update.json");
@@ -14886,7 +15004,7 @@ async function latestVersion(current2) {
14886
15004
  const latest = (await r.json()).version;
14887
15005
  if (latest) cache2 = { latest, checkedAt: Date.now() };
14888
15006
  await mkdir5(dirname3(FILE), { recursive: true });
14889
- await writeFile9(FILE, JSON.stringify(cache2));
15007
+ await writeFile10(FILE, JSON.stringify(cache2));
14890
15008
  } catch {
14891
15009
  }
14892
15010
  }
@@ -14967,7 +15085,7 @@ function agentArgIndex(pos) {
14967
15085
  }
14968
15086
 
14969
15087
  // src/completion.ts
14970
- import { mkdir as mkdir6, readFile as readFile10, writeFile as writeFile10 } from "node:fs/promises";
15088
+ import { mkdir as mkdir6, readFile as readFile10, writeFile as writeFile11 } from "node:fs/promises";
14971
15089
  import { homedir as homedir4 } from "node:os";
14972
15090
  import { join as join8 } from "node:path";
14973
15091
  var GLOBAL_FLAGS = ["--json", "--token", "--profile", "--yes", "--help", "--version", "--lang", "--dry-run"];
@@ -15019,7 +15137,7 @@ async function names() {
15019
15137
  const workspaces = [...new Set(list2.map((a) => a.espacio?.tipo === "workspace" ? a.espacio.nombre : void 0).filter((x) => !!x))];
15020
15138
  await mkdir6(join8(cacheFile(), ".."), { recursive: true }).catch(() => {
15021
15139
  });
15022
- await writeFile10(cacheFile(), JSON.stringify({ at: Date.now(), agents, workspaces })).catch(() => {
15140
+ await writeFile11(cacheFile(), JSON.stringify({ at: Date.now(), agents, workspaces })).catch(() => {
15023
15141
  });
15024
15142
  return { agents, workspaces };
15025
15143
  }
@@ -15106,7 +15224,7 @@ init_i18n();
15106
15224
  // src/commands/spec.ts
15107
15225
  var import_yaml = __toESM(require_dist(), 1);
15108
15226
  init_api();
15109
- import { mkdir as mkdir7, readdir as readdir4, readFile as readFile11, stat as stat2, writeFile as writeFile11 } from "node:fs/promises";
15227
+ import { mkdir as mkdir7, readdir as readdir4, readFile as readFile11, stat as stat2, writeFile as writeFile12 } from "node:fs/promises";
15110
15228
  import { join as join9, resolve as resolve2 } from "node:path";
15111
15229
  init_util();
15112
15230
  init_i18n();
@@ -15120,7 +15238,7 @@ async function agentsExport(id, v) {
15120
15238
  }
15121
15239
  const dir = resolve2(v.out);
15122
15240
  await mkdir7(dir, { recursive: true });
15123
- await writeFile11(join9(dir, "agent.yaml"), yaml);
15241
+ await writeFile12(join9(dir, "agent.yaml"), yaml);
15124
15242
  const own = (0, import_yaml.parse)(yaml)["x-ghosty"]?.skills?.own ?? [];
15125
15243
  for (const slug of own) {
15126
15244
  const sk = await api(`/api/v2/agents/${enc13(id)}/skills/${enc13(slug)}`);
@@ -15243,7 +15361,7 @@ ${dim(t(`${r.totals.activeTurns} turn(s) running`, `${r.totals.activeTurns} turn
15243
15361
  init_api();
15244
15362
  init_auth();
15245
15363
  import { randomUUID as randomUUID2 } from "node:crypto";
15246
- import { mkdir as mkdir8, readFile as readFile12, writeFile as writeFile12 } from "node:fs/promises";
15364
+ import { mkdir as mkdir8, readFile as readFile12, writeFile as writeFile13 } from "node:fs/promises";
15247
15365
  import { homedir as homedir5 } from "node:os";
15248
15366
  import { dirname as dirname4, join as join10 } from "node:path";
15249
15367
  init_i18n();
@@ -15288,7 +15406,7 @@ async function visitorCookie(base, slug, fresh) {
15288
15406
  if (!cookie) throw new CliError(t("The demo didn't hand out a visitor cookie.", "El demo no entreg\xF3 cookie de visitante."), EXIT.API);
15289
15407
  jar[slug] = cookie;
15290
15408
  await mkdir8(dirname4(jarPath()), { recursive: true });
15291
- await writeFile12(jarPath(), JSON.stringify(jar, null, 2), { mode: 384 });
15409
+ await writeFile13(jarPath(), JSON.stringify(jar, null, 2), { mode: 384 });
15292
15410
  return cookie;
15293
15411
  }
15294
15412
  async function visitorTurn(base, slug, cookie, text) {
@@ -15528,7 +15646,7 @@ ${domainLine(r, t("en", "es") === "es" ? "es" : "en").replace("<slug>", slug)}`)
15528
15646
 
15529
15647
  // src/commands/docs.ts
15530
15648
  init_api();
15531
- import { writeFile as writeFile13 } from "node:fs/promises";
15649
+ import { writeFile as writeFile14 } from "node:fs/promises";
15532
15650
 
15533
15651
  // ../docs/src/core/doc-blocks.ts
15534
15652
  var DOC_ENVELOPE_VERSION = 1;
@@ -15691,7 +15809,7 @@ ${t("Ids: ghosty me docs ls", "Ids: ghosty me docs ls")}`);
15691
15809
  }
15692
15810
  const named = /filename\*=UTF-8''([^;]+)/i.exec(res.headers.get("content-disposition") ?? "")?.[1];
15693
15811
  const file = v.out ?? (named ? decodeURIComponent(named) : fileName(id, format));
15694
- await writeFile13(file, bytes);
15812
+ await writeFile14(file, bytes);
15695
15813
  return out({ id, format, saved: file, bytes: bytes.length }, () => `${lavender("\u2713")} ${file} (${bytes.length} bytes)`);
15696
15814
  }
15697
15815
  case "check": {
@@ -15717,7 +15835,7 @@ ${t("Ids: ghosty me docs ls", "Ids: ghosty me docs ls")}`);
15717
15835
  if (res.status === 204) return out({ id, block, segment, segments, saved: null }, () => dim(t("That block has no text to read.", "Ese bloque no tiene texto que leer.")));
15718
15836
  const bytes = Buffer.from(await res.arrayBuffer());
15719
15837
  const file = v.out ?? `${id}-${block}${segment !== null ? `-${segment}` : ""}.${audioExt(res.headers.get("content-type"))}`;
15720
- await writeFile13(file, bytes);
15838
+ await writeFile14(file, bytes);
15721
15839
  return out({ id, block, segment, segments, saved: file, bytes: bytes.length }, () => `${lavender("\u2713")} ${file} (${bytes.length} bytes) ${dim(t(`\xB7 the block has ${segments} sentence(s): --segment 0\u2026${Math.max(segments - 1, 0)}`, `\xB7 el bloque tiene ${segments} frase(s): --segment 0\u2026${Math.max(segments - 1, 0)}`))}`);
15722
15840
  }
15723
15841
  case "restore": {
@@ -16864,6 +16982,8 @@ ${bold("Configure an agent")}
16864
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]
16865
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]
16866
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]
16867
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]
16868
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]
16869
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]
@@ -16871,7 +16991,7 @@ ${bold("Configure an agent")}
16871
16991
  channels wa pair <id> --phone N Pair a personal WhatsApp by code (no QR)
16872
16992
  channels wa status|pause <id> Personal WhatsApp: how it is / pause it (keeps the session)
16873
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]
16874
- 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
16875
16995
  cards ls <id> Recent cards on its sales board [--q TEXT] [--paused] [--limit 20]
16876
16996
  cards show <id> <card> A card's thread: who wrote, text, attachments and whether gs kept a copy (folio, phone or id)
16877
16997
  cards archive <id> Archive cards in bulk (reversible) --integration X | --column X | --before 30d [--dry-run]
@@ -16894,7 +17014,7 @@ ${bold("Sites")} ${dim("(sites your agents publish at <slug>.ghosty.page)")}
16894
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]
16895
17015
  sites unpin <slug|id> Release its slot (back to the Free rule: 30 days + notice)
16896
17016
  sites search <slug|id> How Google finds it: clicks, impressions, searches, indexed or not [--days 28]
16897
- 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]
16898
17018
  sites pins Your pinned-sites subscription and slots [--cancel | --resume]
16899
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]
16900
17020
  sites slug <slug|id> <new-slug> Change a site's slug (staff)
@@ -16945,6 +17065,7 @@ ${bold("Partners")} ${dim("(give an agent to each of YOUR customers \u2014 see:
16945
17065
  partner ls | enable | credentials | agent | tenants | audit | try
16946
17066
  image generate "prompt" An AI image for your businesses, charged to the space's balance [--ref URL (repeat, keeps the character)] [--style-ref URL] [--scene URL] [--text "-50 %"] [--direction D]\u2026 [--variants N] [--size 1K|2K|4K] [--aspect 16:9] [--tenant ID] [--out FILE]
16947
17067
  image edit URL "change" Edit an existing image: only that change, the rest stays [--ref URL] [--text T] [--variants N] [--size] [--aspect] [--tenant ID] [--out FILE]
17068
+ tts "text" Hear the house voice (Mexican Spanish, em_santa) say it, to a .wav [--voice em_santa|em_alex|ef_dora] [--out FILE]
16948
17069
 
16949
17070
  ${bold("Your account")}
16950
17071
  me files ls|get|export|upload|rm Files in your account [--q TEXT] [--kind image|video|audio|document|other] [--cursor C] \xB7 get <id> [--out FILE]
@@ -17079,6 +17200,8 @@ ${bold("Configurar un agente")}
17079
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]
17080
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]
17081
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]
17082
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]
17083
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]
17084
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]
@@ -17086,7 +17209,7 @@ ${bold("Configurar un agente")}
17086
17209
  channels wa pair <id> --phone N Vincula un WhatsApp personal con c\xF3digo (sin QR)
17087
17210
  channels wa status|pause <id> WhatsApp personal: c\xF3mo est\xE1 / pausarlo (conserva la sesi\xF3n)
17088
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]
17089
- 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
17090
17213
  cards ls <id> Tarjetas recientes de su tablero de ventas [--q TEXTO] [--paused] [--limit 20]
17091
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)
17092
17215
  cards archive <id> Archiva tarjetas en bloque (reversible) --integration X | --column X | --before 30d [--dry-run]
@@ -17109,7 +17232,7 @@ ${bold("Sitios")} ${dim("(los sitios que publican tus agentes en <slug>.ghosty.
17109
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]
17110
17233
  sites unpin <slug|id> Suelta su lugar (vuelve a la regla Gratis: 30 d\xEDas + aviso)
17111
17234
  sites pins Tu suscripci\xF3n de Sitios fijos y sus lugares [--cancel | --resume]
17112
- 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]
17113
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]
17114
17237
  sites slug <slug|id> <slug-nuevo> Cambia el slug de un sitio (staff)
17115
17238
 
@@ -17159,6 +17282,7 @@ ${bold("Partners")} ${dim("(dale un agente a cada uno de TUS clientes \u2014 ve
17159
17282
  partner ls | enable | credentials | agent | tenants | audit | try
17160
17283
  image generate "prompt" Una imagen con IA para tus negocios, cobrada del saldo del espacio [--ref URL (se repite, conserva al personaje)] [--style-ref URL] [--scene URL] [--text "-50 %"] [--direction D]\u2026 [--variants N] [--size 1K|2K|4K] [--aspect 16:9] [--tenant ID] [--out ARCHIVO]
17161
17284
  image edit URL "cambio" Edita una imagen que ya existe: s\xF3lo ese cambio, lo dem\xE1s se queda [--ref URL] [--text T] [--variants N] [--size] [--aspect] [--tenant ID] [--out ARCHIVO]
17285
+ tts "texto" Oye la voz de la casa (espa\xF1ol de M\xE9xico, em_santa) decirlo, en un .wav [--voice em_santa|em_alex|ef_dora] [--out ARCHIVO]
17162
17286
 
17163
17287
  ${bold("Tu cuenta")}
17164
17288
  me files ls|get|export|upload|rm Archivos de tu cuenta [--q TEXTO] [--kind image|video|audio|document|other] [--cursor C] \xB7 get <id> [--out ARCHIVO]
@@ -17184,7 +17308,7 @@ ${bold("Opciones globales")}
17184
17308
  ${bold("C\xF3digos de salida")} 0 bien \xB7 1 error de la API \xB7 2 uso incorrecto \xB7 3 sin sesi\xF3n
17185
17309
  ${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.")}
17186
17310
  ${dim("Documentaci\xF3n: https://www.ghosty.studio/docs/cli")}`;
17187
- var COMMANDS = ["login", "logout", "whoami", "usage", "use", "apply", "plan", "engines", "bags", "spaces", "workspaces", "staff", "settings", "coupons", "ads", "audit", "factory", "demos", "sites", "push", "novedades", "completion", "cards", "agents", "files", "skills", "mcp", "dbs", "turns", "try", "chat", "conversations", "schedule", "watch", "teams", "partner", "image", "me", "channels", "board", "boards", "access", "credentials", "connectors", "browser", "support", "docs", "help"];
17311
+ var COMMANDS = ["login", "logout", "whoami", "usage", "use", "apply", "plan", "engines", "bags", "spaces", "workspaces", "staff", "settings", "coupons", "ads", "audit", "factory", "demos", "sites", "push", "novedades", "completion", "cards", "agents", "files", "skills", "mcp", "dbs", "turns", "try", "chat", "conversations", "schedule", "watch", "teams", "partner", "image", "tts", "me", "channels", "board", "boards", "access", "credentials", "connectors", "browser", "support", "docs", "help"];
17188
17312
  var AGENTS_SUBS = ["ls", "create", "get", "show", "set", "export", "doctor", "docs", "share", "unshare", "shares", "teams", "rm", "restart", "demo", "embed", "box", "token", "memory", "transfer"];
17189
17313
  function unknown(what, input, known, helpCmd) {
17190
17314
  const s2 = suggest(input, known);
@@ -17326,6 +17450,7 @@ async function main() {
17326
17450
  "key-column": { type: "string" },
17327
17451
  "unset-env": { type: "string" },
17328
17452
  orphans: { type: "boolean" },
17453
+ take: { type: "boolean" },
17329
17454
  skipped: { type: "boolean" },
17330
17455
  paused: { type: "boolean" },
17331
17456
  email: { type: "string" },
@@ -17342,6 +17467,8 @@ async function main() {
17342
17467
  enable: { type: "string" },
17343
17468
  disable: { type: "string" },
17344
17469
  "columns-from": { type: "string" },
17470
+ steps: { type: "string" },
17471
+ minutes: { type: "string" },
17345
17472
  add: { type: "string", multiple: true },
17346
17473
  remove: { type: "string", multiple: true },
17347
17474
  everyone: { type: "boolean" },
@@ -17501,7 +17628,7 @@ async function main() {
17501
17628
  const tokens = splitTokens(pos, v);
17502
17629
  globals.token = tokens.session;
17503
17630
  globals.profile = v.profile;
17504
- if (v.version) return out({ version: "0.85.0" }, () => "0.85.0");
17631
+ if (v.version) return out({ version: "0.87.0" }, () => "0.87.0");
17505
17632
  for (const n of normalizeArgs(pos, v)) if (!globals.json) info(dim(n));
17506
17633
  const context = (await readConfig()).workspace;
17507
17634
  if (!explicitToken()?.startsWith("gat_")) {
@@ -17608,7 +17735,7 @@ async function main() {
17608
17735
  case "board":
17609
17736
  return boardCmd(sub, a1, v, a2);
17610
17737
  case "boards":
17611
- return boardsCmd(sub, a1, a2, v, context);
17738
+ return boardsCmd(sub, a1, a2, v, context, pos[4]);
17612
17739
  case "channels":
17613
17740
  if (a1 === "connect" && ["whatsapp", "wa", "messenger", "fb"].includes(sub ?? "")) {
17614
17741
  if (!a2) throw usage(t("Missing agent id.", "Falta el id del agente."), `ghosty channels ${sub} connect <agent-id>`);
@@ -17646,6 +17773,8 @@ async function main() {
17646
17773
  return partnerCmd(sub, a1, a2, { ...v, token: tokens.turn }, pos[4]);
17647
17774
  case "image":
17648
17775
  return imageCmd(sub, a1, a2, v);
17776
+ case "tts":
17777
+ return ttsCmd(sub, v);
17649
17778
  case "usage":
17650
17779
  if (v.images) return imageUsage(v);
17651
17780
  if (v.boxes && v.by === "template") return fleetBoxes();
@@ -17702,7 +17831,7 @@ async function main() {
17702
17831
  throw unknown(cmd, cmd, COMMANDS, "ghosty --help");
17703
17832
  }
17704
17833
  }
17705
- var notice = updateNotice("0.85.0").catch(() => null);
17834
+ var notice = updateNotice("0.87.0").catch(() => null);
17706
17835
  main().then(
17707
17836
  async () => {
17708
17837
  const n = await notice;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghostystudio/cli",
3
- "version": "0.85.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": {