@ghostystudio/cli 0.49.0 → 0.51.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 +15 -0
  2. package/dist/index.js +510 -40
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -38,6 +38,8 @@ ghosty agents box <id> [--check PATH]… [--contains PATH=TEXT]… [--recycle]
38
38
  ghosty agents demo enable|disable <id>
39
39
  ghosty agents demo <id> [--slug NAME] [--expires "YYYY-MM-DDTHH:MM"|no] [--turns N] [--per-ip N]
40
40
  [--welcome TEXT] [--starter TEXT] [--chips "a|b|c"] [--sponsor on|off] [--rotate] # public /p/ link, no account needed
41
+ ghosty agents embed enable|disable <id> [--domains a.com,*.b.com] # web widget: chat bubble on your site, only on those domains
42
+ ghosty agents embed <id> [--per-visitor N] [--per-ip N] [--welcome TEXT] [--color #hex] [--rotate] # state + the <script> to paste
41
43
  ghosty agents doctor <id> [--verbose] # exits 1 when it finds a problem
42
44
  ghosty agents docs ls|grant|revoke <id> [doc] [--write] [--no-deliver]
43
45
  ghosty agents docs add <id> --url URL | --file PATH [--description TEXT] [--grant read|write|no]
@@ -103,6 +105,19 @@ Every command has its own help with examples: `ghosty <command> --help` or `ghos
103
105
 
104
106
  Exit codes: `0` ok · `1` API error · `2` usage error · `3` not signed in.
105
107
 
108
+ ### Changes in 0.51.0
109
+
110
+ - `agents embed enable|disable <id> [--domains a.com,*.b.com] [--per-visitor N] [--per-ip N] [--welcome T] [--color #hex] [--rotate]`: the **web widget**, a chat bubble with the agent on your own site (one `<script>`), loading only on the domains you list; each visitor gets an isolated conversation and talks as a customer. Claude engine only. No flags = state + the snippet.
111
+
112
+ ### Changes in 0.50.0
113
+
114
+ - `cards pause|resume <id> <card> [--mode 30min|2h|24h|until_tomorrow|permanent]`: the thread's pause menu; resume also lifts "operator only" and doesn't answer by itself. Logged.
115
+ - `cards reply <id> <card> [--directive TEXT]` ("Ask for a reply") and `cards pending <id> [--since 2d|ISO] [--reply] [--dry-run]`: conversations whose last message is the customer's, flagged when they must not be answered (paused, escalated, outside the 24 h window, only a reaction, not WhatsApp Business, number without agent).
116
+ - `channels whatsapp integrations` shows who answered each number last; `--orphans` and `channels messenger ls [--orphans]` list numbers/pages nobody answers today that had turns.
117
+ - `agents set --prompt-patch overlay.json` / `--prompt-append FILE [--dry-run]`: change one piece of the prompt on the server's current copy (dry run prints the diff).
118
+ - `audit ls [--workspace] [--agent] [--actor] [--action] [--since]`: the audit log. The agent page's Save and channel changes are logged now.
119
+ - `factory runs ls|show|rename` (a workspace's Software Factory runs) and `agents ls --handle build`.
120
+
106
121
  ### Changes in 0.49.0
107
122
 
108
123
  - `dbs export <id> <db> [--table T] [--out FILE]`: download a database — the whole thing as `.sqlite` (sqlite3, DB Browser, DBeaver) or one table as CSV (Excel). Same buttons on each database's page in /app/datos.
package/dist/index.js CHANGED
@@ -8135,7 +8135,41 @@ var SERVER_MESSAGES = [
8135
8135
  // — Infra interna (casi nunca llega al CLI, pero por si acaso)
8136
8136
  ["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)."],
8137
8137
  ["sqld: fetch sin namespace", "sqld: fetch without namespace"],
8138
- ["sqld: token sin namespace ser\xEDa un token maestro", "sqld: a token without namespace would be a master token"]
8138
+ ["sqld: token sin namespace ser\xEDa un token maestro", "sqld: a token without namespace would be a master token"],
8139
+ // — CLI 0.50: cards pause/resume/reply/pending, canales huérfanos, audit, factory, prompt-patch
8140
+ ["el agente est\xE1 trabajando ahora; repite cuando termine", "the agent is working right now; try again when it's done"],
8141
+ ["no pude saber si el agente est\xE1 trabajando", "couldn't tell whether the agent is working"],
8142
+ ["s\xF3lo el due\xF1o cambia a qui\xE9n puede escribir el agente desde canales de clientes", "only the owner changes who the agent may email from customer channels"],
8143
+ ["el pedido es un n\xFAmero (#15 o 15)", "the run is a number (#15 or 15)"],
8144
+ ["el prompt cambi\xF3 mientras tanto y {}", "the prompt changed in the meantime and {}"],
8145
+ ["el prompt no tiene \xAB{}\xBB", "the prompt has no \xAB{}\xBB"],
8146
+ ["el prompt quedar\xEDa de {} caracteres (m\xE1x. 120000)", "the prompt would be {} characters (max 120000)"],
8147
+ ["el token del agente no busca handles: usa tu sesi\xF3n (ghosty login)", "the agent token can't look up handles: use your session (ghosty login)"],
8148
+ ["el token del agente no lee la bit\xE1cora: usa tu sesi\xF3n (ghosty login)", "the agent token can't read the audit log: use your session (ghosty login)"],
8149
+ ["el token del agente no lista los canales del espacio: usa tu sesi\xF3n (ghosty login)", "the agent token can't list the workspace's channels: use your session (ghosty login)"],
8150
+ ["el token del agente no opera la f\xE1brica: usa tu sesi\xF3n (ghosty login)", "the agent token can't operate the factory: use your session (ghosty login)"],
8151
+ ["el t\xEDtulo pasa de 120 caracteres", "the title is longer than 120 characters"],
8152
+ ["ese n\xFAmero lo contesta otro agente ({})", "that number is answered by another agent ({})"],
8153
+ ["este n\xFAmero no tiene un agente que conteste", "this number has no agent answering it"],
8154
+ ["este prompt se genera de otro agente + una capa: cambia la base o la capa, o su\xE9ltalo (ghosty agents set {} --prompt-base none)", "this prompt is generated from another agent + an overlay: change the base or the overlay, or unlink it (ghosty agents set {} --prompt-base none)"],
8155
+ ["falta handle", "missing handle"],
8156
+ ["kind: waba o messenger", "kind: waba or messenger"],
8157
+ ["no encuentro el agente {} (o su bit\xE1cora no es tuya)", "agent {} not found (or its audit log isn't yours)"],
8158
+ ["no eres miembro del espacio \xAB{}\xBB", "you're not a member of workspace \xAB{}\xBB"],
8159
+ ["no hay un pedido #{} en la f\xE1brica de este espacio", "there's no run #{} in this workspace's factory"],
8160
+ ["para cambiar los pedidos de \xAB{}\xBB hace falta ser due\xF1o o admin del espacio", "changing \xAB{}\xBB's runs needs the workspace's owner or an admin"],
8161
+ ["no se pudo", "it couldn't be done"],
8162
+ ["parche inv\xE1lido: {}", "invalid patch: {}"],
8163
+ ["promptAppend vac\xEDo", "empty promptAppend"],
8164
+ ["s\xF3lo el due\xF1o de \xAB{}\xBB o el staff de Ghosty leen su bit\xE1cora", "only \xAB{}\xBB's owner or Ghosty staff read its audit log"],
8165
+ ["s\xF3lo aplica a conversaciones de WhatsApp Business", "only applies to WhatsApp Business conversations"],
8166
+ ["ese n\xFAmero ya no est\xE1 conectado", "that number is no longer connected"],
8167
+ ["ese n\xFAmero no es de este tablero", "that number isn't on this board"],
8168
+ // — Widget web (`agents embed`)
8169
+ ["s\xF3lo el due\xF1o del agente maneja su widget web", "only the agent's owner manages its web widget"],
8170
+ ["dominio no v\xE1lido: {} (usa ejemplo.com o *.ejemplo.com, sin https:// ni rutas)", "invalid domain: {} (use example.com or *.example.com, no https:// or paths)"],
8171
+ ["el widget web s\xF3lo corre con motor claude (es el que a\xEDsla a cada visitante)", "the web widget only runs on the claude engine (it's the one that isolates each visitor)"],
8172
+ ["pon al menos un dominio donde va a vivir el widget (--domains ejemplo.com)", "add at least one domain where the widget will live (--domains example.com)"]
8139
8173
  ];
8140
8174
 
8141
8175
  // src/i18n.ts
@@ -8434,7 +8468,7 @@ async function postLong(path, body, timeoutMs = 9e5) {
8434
8468
  const req = httpsRequest(`${base}${path}`, {
8435
8469
  method: "POST",
8436
8470
  timeout: timeoutMs,
8437
- headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.49.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
8471
+ headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.51.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
8438
8472
  }, (res) => {
8439
8473
  let buf = "";
8440
8474
  res.on("data", (c) => buf += c);
@@ -8476,7 +8510,7 @@ async function request(path, opts = {}, retried = false) {
8476
8510
  method: opts.method ?? (opts.body !== void 0 || opts.raw !== void 0 ? "POST" : "GET"),
8477
8511
  headers: {
8478
8512
  authorization: `Bearer ${token}`,
8479
- "user-agent": `ghosty-cli/${"0.49.0"}`,
8513
+ "user-agent": `ghosty-cli/${"0.51.0"}`,
8480
8514
  "accept-language": lang(),
8481
8515
  ...opts.body !== void 0 ? { "content-type": "application/json" } : {},
8482
8516
  ...opts.headers
@@ -8684,6 +8718,52 @@ ${dim(t(`Report: ${file} Failed turns: ghosty turns ls ${id} --errors --since
8684
8718
  if (failed) process.exitCode = 1;
8685
8719
  }
8686
8720
 
8721
+ // src/diff.ts
8722
+ function lineDiff(before, after, context = 2) {
8723
+ const a = before.split("\n");
8724
+ const b = after.split("\n");
8725
+ let start = 0;
8726
+ while (start < a.length && start < b.length && a[start] === b[start]) start++;
8727
+ let endA = a.length;
8728
+ let endB = b.length;
8729
+ while (endA > start && endB > start && a[endA - 1] === b[endB - 1]) {
8730
+ endA--;
8731
+ endB--;
8732
+ }
8733
+ const midA = a.slice(start, endA);
8734
+ const midB = b.slice(start, endB);
8735
+ const n = midA.length;
8736
+ const m = midB.length;
8737
+ const dp = Array.from({ length: n + 1 }, () => new Uint32Array(m + 1));
8738
+ for (let i2 = n - 1; i2 >= 0; i2--) for (let j2 = m - 1; j2 >= 0; j2--) dp[i2][j2] = midA[i2] === midB[j2] ? dp[i2 + 1][j2 + 1] + 1 : Math.max(dp[i2 + 1][j2], dp[i2][j2 + 1]);
8739
+ const ops = a.slice(0, start).map((text, i2) => ({ kind: " ", text, at: i2 + 1 }));
8740
+ let i = 0;
8741
+ let j = 0;
8742
+ while (i < n || j < m) {
8743
+ if (i < n && j < m && midA[i] === midB[j]) {
8744
+ ops.push({ kind: " ", text: midA[i], at: start + i + 1 });
8745
+ i++;
8746
+ j++;
8747
+ } else if (i < n && (j >= m || dp[i + 1][j] >= dp[i][j + 1])) {
8748
+ ops.push({ kind: "-", text: midA[i], at: start + i + 1 });
8749
+ i++;
8750
+ } else ops.push({ kind: "+", text: midB[j++], at: start + i + 1 });
8751
+ }
8752
+ for (let k = endA; k < a.length; k++) ops.push({ kind: " ", text: a[k], at: k + 1 });
8753
+ const keep = /* @__PURE__ */ new Set();
8754
+ ops.forEach((o, k) => {
8755
+ if (o.kind !== " ") for (let x = Math.max(0, k - context); x <= Math.min(ops.length - 1, k + context); x++) keep.add(x);
8756
+ });
8757
+ const lines = [];
8758
+ let last = -2;
8759
+ for (const k of [...keep].sort((x, y) => x - y)) {
8760
+ if (k !== last + 1) lines.push(`@@ ${ops[k].at}`);
8761
+ lines.push(`${ops[k].kind} ${ops[k].text}`);
8762
+ last = k;
8763
+ }
8764
+ return lines.join("\n");
8765
+ }
8766
+
8687
8767
  // src/commands/agents.ts
8688
8768
  var enc2 = encodeURIComponent;
8689
8769
  var agentPath = (id, rest = "") => `/api/v2/agents/${enc2(id)}${rest}`;
@@ -8830,6 +8910,25 @@ ${dim(t("Dry run: nothing changed.", "Simulacro: no se cambi\xF3 nada."))}`);
8830
8910
  }
8831
8911
  return out(r2, () => r2.unlinked ? `${lavender("\u2713")} ${t("Unlinked: the prompt stays as it is and is edited by hand again.", "Desenlazado: el prompt se queda como est\xE1 y se vuelve a editar a mano.")}` : `${lavender("\u2713")} Prompt = ${v["prompt-base"]} + ${v["prompt-overlay"]} ${t(`(${r2.chars} chars). It regenerates when the base changes; ghosty agents doctor ${id} tells if the overlay stops applying.`, `(${r2.chars} caracteres). Se regenera cuando cambia la base; ghosty agents doctor ${id} avisa si la capa deja de aplicar.`)}`);
8832
8912
  }
8913
+ if (v["prompt-patch"] || v["prompt-append"]) {
8914
+ const body2 = {};
8915
+ if (v["prompt-patch"]) {
8916
+ try {
8917
+ body2.promptPatch = JSON.parse(await readFile3(v["prompt-patch"], "utf8"));
8918
+ } catch (e) {
8919
+ throw usage(t(`Can't read ${v["prompt-patch"]}: ${e.message}`, `No pude leer ${v["prompt-patch"]}: ${e.message}`), t("Its format", "Su formato") + ': {"edits":[{"what":"horario","find":"9 a 18","replace":"9 a 19","all":true}]} (regex: true ' + t("for patterns", "para patrones") + ")");
8920
+ }
8921
+ }
8922
+ if (v["prompt-append"]) body2.promptAppend = await readFile3(v["prompt-append"], "utf8");
8923
+ if (v["dry-run"]) {
8924
+ const r3 = await api(agentPath(id), { method: "PATCH", body: { ...body2, dryRun: true } });
8925
+ const diff = lineDiff(r3.before, r3.after);
8926
+ return out({ dryRun: true, beforeChars: r3.beforeChars, chars: r3.chars, diff }, () => `${diff.split("\n").map((l) => l.startsWith("+ ") ? lavender(l) : l.startsWith("- ") ? red(l) : l.startsWith("@@") ? dim(l) : l).join("\n")}
8927
+ ${dim(t(`${r3.beforeChars} \u2192 ${r3.chars} chars. Dry run: nothing changed.`, `${r3.beforeChars} \u2192 ${r3.chars} caracteres. Simulacro: no se cambi\xF3 nada.`))}`);
8928
+ }
8929
+ const r2 = await api(agentPath(id), { method: "PATCH", body: body2 });
8930
+ return out(r2, () => `${lavender("\u2713")} ${t(`Prompt patched (${r2.edits} edit(s)${r2.appended ? " + appended text" : ""}): ${r2.beforeChars} \u2192 ${r2.chars} chars. It applies from the next conversation.`, `Prompt parchado (${r2.edits} edici\xF3n(es)${r2.appended ? " + texto anexado" : ""}): ${r2.beforeChars} \u2192 ${r2.chars} caracteres. Entra desde la siguiente conversaci\xF3n.`)}`);
8931
+ }
8833
8932
  if (v.engine && v["dry-run"]) {
8834
8933
  const p = await api(agentPath(id), { method: "PATCH", body: { engine: v.engine, ...v.model ? { model: v.model } : {}, dryRun: true } });
8835
8934
  return out(p, () => [
@@ -9111,10 +9210,10 @@ async function skillsCmd(sub, id, slug, v) {
9111
9210
  case "ls":
9112
9211
  case "list": {
9113
9212
  const r = await api(agentPath(id, "/skills"));
9114
- const short2 = (d) => (d ?? "").replace(/\s+/g, " ").slice(0, 70);
9213
+ const short3 = (d) => (d ?? "").replace(/\s+/g, " ").slice(0, 70);
9115
9214
  const rows = [
9116
- ...r.skills.map((s) => ({ slug: s.slug, source: "own", on: "on", description: short2(s.description) })),
9117
- ...(r.house ?? []).map((s) => ({ slug: s.slug, source: "ghosty", on: s.enabled ? "on" : "off", description: short2(s.description) }))
9215
+ ...r.skills.map((s) => ({ slug: s.slug, source: "own", on: "on", description: short3(s.description) })),
9216
+ ...(r.house ?? []).map((s) => ({ slug: s.slug, source: "ghosty", on: s.enabled ? "on" : "off", description: short3(s.description) }))
9118
9217
  ];
9119
9218
  const more = r.community.filter((c) => !c.installed).length;
9120
9219
  out(r, () => `${table(rows, ["slug", "source", "on", "description"])}
@@ -9245,6 +9344,33 @@ async function agentsDemo(id, v) {
9245
9344
  ].join("\n")
9246
9345
  );
9247
9346
  }
9347
+ async function agentsEmbed(id, v) {
9348
+ id = need(id, "agent id", "ghosty agents embed enable <agent-id> --domains tusitio.com");
9349
+ if (v.on && v.off) throw usage(t("Use enable or disable, not both.", "Usa enable o disable, no los dos."));
9350
+ const body = {};
9351
+ if (v.on) body.enabled = true;
9352
+ if (v.off) body.enabled = false;
9353
+ if (v.domains !== void 0) body.domains = v.domains.split(/[\s,]+/).map((d) => d.trim()).filter(Boolean);
9354
+ if (v["per-visitor"]) body.perVisitorDay = Number(v["per-visitor"]);
9355
+ if (v["per-ip"]) body.perIpDay = Number(v["per-ip"]);
9356
+ if (v.welcome !== void 0) body.welcome = v.welcome || null;
9357
+ if (v.color !== void 0) body.color = v.color;
9358
+ if (v.rotate) body.rotate = true;
9359
+ const e = Object.keys(body).length ? await api(agentPath(id, "/embed"), { method: "PUT", body }) : await api(agentPath(id, "/embed"));
9360
+ out(
9361
+ e,
9362
+ () => [
9363
+ `${e.enabled ? lavender(t("\u25CF on", "\u25CF encendido")) : dim(t("\u25CB off", "\u25CB apagado"))} ${t("web widget", "widget web")}`,
9364
+ e.domains.length ? `${t("domains", "dominios")}: ${e.domains.join(", ")}` : red(t("domains: (none \u2014 it loads nowhere; add --domains yoursite.com)", "dominios: (ninguno \u2014 no carga en ning\xFAn lado; agrega --domains tusitio.com)")),
9365
+ ...e.engineOk ? [] : [red(t("engine: only claude isolates each visitor; switch with `ghosty agents set <id> --engine claude`", "motor: s\xF3lo claude a\xEDsla a cada visitante; c\xE1mbialo con `ghosty agents set <id> --engine claude`"))],
9366
+ dim(t(`cap ${e.perVisitorDay} messages per visitor per day \xB7 ${e.perIpDay} per IP per day \xB7 color ${e.color}`, `tope ${e.perVisitorDay} mensajes por visitante al d\xEDa \xB7 ${e.perIpDay} por IP al d\xEDa \xB7 color ${e.color}`)),
9367
+ e.welcome ? `${t("welcome", "bienvenida")}: ${e.welcome}` : dim(t("welcome: (default greeting)", "bienvenida: (saludo por defecto)")),
9368
+ ...e.snippet ? ["", t("Paste before </body> on your site:", "Pega antes de </body> en tu sitio:"), e.snippet, ""] : [],
9369
+ t(`visitors ${e.stats.visitantes} \xB7 messages ${e.stats.mensajes}`, `visitantes ${e.stats.visitantes} \xB7 mensajes ${e.stats.mensajes}`),
9370
+ ...e.lastError ? [red(`${t("last failure", "\xFAltima falla")} ${e.lastError.at}: ${serverText(e.lastError.message ?? e.lastError.error ?? "")}`)] : []
9371
+ ].join("\n")
9372
+ );
9373
+ }
9248
9374
  async function agentsBox(id, v) {
9249
9375
  id = need(id, "agent id", "ghosty agents box <agent-id> [--check PATH] [--contains PATH=TEXT] [--recycle]");
9250
9376
  if (v.recycle) {
@@ -9856,7 +9982,7 @@ async function call(path, v, init = {}) {
9856
9982
  "x-ghosty-ts": ts,
9857
9983
  "x-ghosty-sig": sig,
9858
9984
  "content-type": "application/json",
9859
- "user-agent": `ghosty-cli/${"0.49.0"}`
9985
+ "user-agent": `ghosty-cli/${"0.51.0"}`
9860
9986
  },
9861
9987
  body: method === "GET" ? void 0 : raw
9862
9988
  });
@@ -10916,10 +11042,16 @@ function find2(nums, ref) {
10916
11042
  return hit;
10917
11043
  }
10918
11044
  async function channelsCmd(kind, sub, id, ref, v) {
11045
+ if (kind === "messenger" || kind === "fb") {
11046
+ if (!id) throw usage(t("Missing agent id.", "Falta el id del agente."), t("Example", "Ejemplo") + ": ghosty channels messenger ls <agent-id> [--orphans]");
11047
+ 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]");
11048
+ return linksCmd(id, "messenger", v.orphans);
11049
+ }
10919
11050
  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>");
10920
11051
  if (!id) throw usage(t("Missing agent id.", "Falta el id del agente."), t("Example", "Ejemplo") + ": ghosty channels whatsapp ls <agent-id>");
10921
11052
  if (sub === "link" || sub === "unlink") return linkCmd(sub, id, v);
10922
11053
  if (sub === "history") return historyCmd(id, v);
11054
+ if (sub === "integrations" && v.orphans) return linksCmd(id, "waba", true);
10923
11055
  if (sub === "integrations") return integrationsCmd(id, v.email);
10924
11056
  if (sub === "import" || sub === "status" || sub === "pause" || sub === "pair") return waCmd2(sub, id, v);
10925
11057
  if (sub === "enable" && !v["dry-run"]) requireYesIfHeadless(v.yes, `ghosty channels whatsapp enable ${id} ${ref ?? "<number>"}`);
@@ -11150,6 +11282,19 @@ async function pageWithRetry(fetchPage, where, waitMs = (n) => 5e3 * n, tries =
11150
11282
  }
11151
11283
  }
11152
11284
  }
11285
+ var lastCell = (l) => l ? `${l.agentName ?? l.agentId} ${dim(l.at.slice(0, 16).replace("T", " "))}` : dim("\u2014");
11286
+ async function linksCmd(id, kind, orphans) {
11287
+ const r = await api(`/api/v2/agents/${enc7(id)}/channels/links?kind=${kind}${orphans ? "&orphans=1" : ""}`);
11288
+ 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"));
11289
+ const rows = r.links.map((l) => ({ [kind === "waba" ? "number" : "page"]: l.label, "answers now": now(l), "last answered by": lastCell(l.lastAnswer), turns: l.lastAnswer?.turns ?? 0, id: l.id }));
11290
+ const first = r.links.find((l) => l.nobody && l.lastAnswer);
11291
+ const hint = first ? `
11292
+ ${dim(`${t("Give it back", "Regresarlo")}: ${kind === "waba" ? `ghosty channels whatsapp enable ${first.lastAnswer.agentId} ${first.label.split(" ")[0]} --dry-run` : t(`the agent's Channels page (Messenger \u2192 \xABPass to this agent\xBB)`, `la p\xE1gina Canales del agente (Messenger \u2192 \xABPasar a este agente\xBB)`)}`)}` : "";
11293
+ return out(
11294
+ r,
11295
+ () => rows.length ? `${table(rows, [kind === "waba" ? "number" : "page", "answers now", "last answered by", "turns", "id"])}${hint}` : dim(orphans ? t("No orphans: every number/page with turns has an agent answering.", "Sin hu\xE9rfanos: todo n\xFAmero o p\xE1gina con turnos tiene un agente que contesta.") : t("No numbers/pages in this agent's workspace.", "No hay n\xFAmeros ni p\xE1ginas en el espacio de este agente."))
11296
+ );
11297
+ }
11153
11298
  async function integrationsCmd(id, email) {
11154
11299
  const r = await api(`/api/v2/agents/${enc7(id)}/whatsapp/integrations${email ? `?email=${enc7(email)}` : ""}`);
11155
11300
  const who2 = { gs: "gs", easybits: "EasyBits", droplet: "droplet", formmy: "Formmy", other: t("other", "otro") };
@@ -11159,11 +11304,12 @@ async function integrationsCmd(id, email) {
11159
11304
  "answered by": `${who2[i.answeredBy] ?? i.answeredBy}${i.gsAgent ? ` (${i.thisAgent ? lavender(t("this agent", "este agente")) : i.gsAgent.name})` : ""}${i.isActive ? "" : dim(t(" \xB7 inactive", " \xB7 inactiva"))}`,
11160
11305
  "formmy forwards": i.forwards ? t("yes", "s\xED") : dim("no"),
11161
11306
  "formmy agent": i.agent ?? "",
11307
+ "last answered by": lastCell(i.lastAnswer),
11162
11308
  integration: i.integrationId
11163
11309
  }));
11164
11310
  return out(
11165
11311
  r,
11166
- () => rows.length ? `${table(rows, ["number", "name", "answered by", "formmy forwards", "formmy agent", "integration"])}
11312
+ () => rows.length ? `${table(rows, ["number", "name", "answered by", "last answered by", "formmy forwards", "formmy agent", "integration"])}
11167
11313
  ${dim(`${t("Next", "Siguiente")}: ghosty channels whatsapp link ${id} --integration <integration> --answer on|off --dry-run`)}` : dim(t(`${r.ownerEmail} has no WhatsApp Business numbers in Formmy (staff: --email <other Formmy account>).`, `${r.ownerEmail} no tiene n\xFAmeros de WhatsApp Business en Formmy (staff: --email <otra cuenta de Formmy>).`))
11168
11314
  );
11169
11315
  }
@@ -11231,6 +11377,31 @@ ${t(`Cards appear on the team's board (no "new lead" alerts).`, "Las tarjetas ap
11231
11377
  out({ ...sum, conversations: done }, () => `${lavender("\u2713")} ${t(`${sum.newCards} new card(s), ${sum.updatedCards} updated, ${sum.messages} message(s), ${sum.orders} order(s)`, `${sum.newCards} tarjeta(s) nueva(s), ${sum.updatedCards} actualizada(s), ${sum.messages} mensaje(s), ${sum.orders} pedido(s)`)}${files(sum)}${sum.suppressed ? dim(t(` \xB7 ${sum.suppressed} bot draft(s) the customer never saw, skipped`, ` \xB7 se omitieron ${sum.suppressed} borrador(es) del bot que el cliente nunca vio`)) : ""}${pausedLine(sum, id)}`);
11232
11378
  }
11233
11379
 
11380
+ // src/cards.ts
11381
+ var PAUSE_MODES = ["30min", "2h", "24h", "until_tomorrow", "permanent"];
11382
+ function pauseMode(raw) {
11383
+ const m = (raw ?? "24h").trim();
11384
+ if (!PAUSE_MODES.includes(m)) throw new Error(t(`--mode takes ${PAUSE_MODES.join(" | ")}.`, `--mode acepta ${PAUSE_MODES.join(" | ")}.`));
11385
+ return m;
11386
+ }
11387
+ function flagLabel(flag) {
11388
+ const labels = {
11389
+ paused: ["paused", "en pausa"],
11390
+ escalated: ["escalated (operator only)", "escalada (s\xF3lo operador)"],
11391
+ window_closed: ["outside WhatsApp's 24 h window", "fuera de la ventana de 24 h"],
11392
+ reaction: ["only a reaction", "s\xF3lo una reacci\xF3n"],
11393
+ not_whatsapp: ["not WhatsApp Business", "no es WhatsApp Business"],
11394
+ no_agent: ["number answered by nobody", "n\xFAmero sin agente"]
11395
+ };
11396
+ const l = labels[flag];
11397
+ return l ? t(l[0], l[1]) : flag;
11398
+ }
11399
+ function pauseLabel(p) {
11400
+ if (p.state === "escalated") return t("escalated (operator only)", "escalada (s\xF3lo operador)");
11401
+ if (p.state === "paused") return t(`paused until ${p.until?.slice(0, 16).replace("T", " ")}`, `en pausa hasta ${p.until?.slice(0, 16).replace("T", " ")}`);
11402
+ return t("active (the agent answers)", "activa (el agente contesta)");
11403
+ }
11404
+
11234
11405
  // src/commands/board.ts
11235
11406
  var who = (role) => ({ user: t("customer", "cliente"), assistant: t("agent", "agente"), operator: t("operator", "operador") })[role] ?? role;
11236
11407
  async function boardLs(id, v) {
@@ -11254,7 +11425,79 @@ async function boardShow(id, ref, v) {
11254
11425
  };
11255
11426
  out(r, () => [head, ...r.messages.map(line2)].join("\n"));
11256
11427
  }
11428
+ var cardName = (c) => `${c.folio ? `#${c.folio} ` : ""}${c.name ?? `+${c.phone}`}`;
11429
+ var cardPath = (id, ref, tail = "") => `/api/v2/agents/${encodeURIComponent(id)}/board/conversations/${encodeURIComponent(ref)}${tail}`;
11430
+ async function cardPause(sub, id, ref, v) {
11431
+ if (!ref) throw usage(t("Missing card.", "Falta la tarjeta."), t("Example", "Ejemplo") + `: ghosty cards ${sub} ${id} "#12"${sub === "pause" ? " --mode 2h" : ""}`);
11432
+ let mode = "resume";
11433
+ if (sub === "pause") {
11434
+ try {
11435
+ mode = pauseMode(v.mode);
11436
+ } catch (e) {
11437
+ throw usage(e.message);
11438
+ }
11439
+ }
11440
+ const r = await api(cardPath(id, ref, "/pause"), { body: { mode } });
11441
+ out(r, () => `${lavender("\u2713")} ${bold(cardName(r.card))}: ${pauseLabel(r.before)} \u2192 ${pauseLabel(r.after)}${sub === "resume" ? `
11442
+ ${dim(t(`Resuming doesn't answer by itself; to answer what's pending: ghosty cards reply ${id} ${JSON.stringify(ref)}`, `Reanudar no contesta solo; para contestar lo pendiente: ghosty cards reply ${id} ${JSON.stringify(ref)}`))}` : ""}`);
11443
+ }
11444
+ async function cardReply(id, ref, v) {
11445
+ if (!ref) throw usage(t("Missing card.", "Falta la tarjeta."), t("Example", "Ejemplo") + `: ghosty cards reply ${id} "#12" [--directive "ofr\xE9cele el env\xEDo"]`);
11446
+ const retry = `ghosty cards reply ${id} ${JSON.stringify(ref)}${v.directive ? ` --directive ${JSON.stringify(v.directive)}` : ""}`;
11447
+ requireYesIfHeadless(v.yes, retry);
11448
+ await confirm(t(`The agent will answer card ${ref} \u2014 a real customer gets the message. Go?`, `El agente contestar\xE1 la tarjeta ${ref}: un cliente real recibe el mensaje. \xBFSeguimos?`), { yes: v.yes, retry });
11449
+ const r = await api(cardPath(id, ref, "/reply"), { body: v.directive ? { directive: v.directive } : {} });
11450
+ out(r, () => `${lavender("\u2713")} ${t("Answered", "Contest\xF3")} ${bold(cardName(r.card))}${r.resumed ? dim(t(" (it was paused: resumed)", " (estaba en pausa: se reanud\xF3)")) : ""}`);
11451
+ }
11452
+ async function cardPending(id, v) {
11453
+ const retry = `ghosty cards pending ${id}${v.since ? ` --since ${v.since}` : ""} --reply`;
11454
+ if (v.reply && !v["dry-run"]) requireYesIfHeadless(v.yes, retry);
11455
+ const qs = new URLSearchParams({ ...v.since ? { since: v.since } : {}, ...v.limit ? { limit: v.limit } : {} });
11456
+ const r = await api(`/api/v2/agents/${encodeURIComponent(id)}/board/pending?${qs}`);
11457
+ const go = r.cards.filter((c) => c.flags.length === 0);
11458
+ const rows = r.cards.map((c) => ({
11459
+ card: c.folio ? `#${c.folio}` : c.id.slice(0, 8),
11460
+ name: c.name ?? "",
11461
+ phone: c.phone,
11462
+ msgs: c.pendingMessages,
11463
+ since: c.lastUserAt.slice(5, 16).replace("T", " "),
11464
+ last: c.lastText.replace(/\s+/g, " ").slice(0, 40),
11465
+ status: c.flags.length ? dim(c.flags.map(flagLabel).join(", ")) : lavender(t("answer", "contestar"))
11466
+ }));
11467
+ const summary = dim(t(
11468
+ `\xAB${r.board}\xBB since ${r.since.slice(0, 16).replace("T", " ")}: ${r.cards.length} waiting, ${go.length} can be answered${r.otherAgents ? ` (${r.otherAgents} more belong to another agent's numbers)` : ""}.`,
11469
+ `\xAB${r.board}\xBB desde ${r.since.slice(0, 16).replace("T", " ")}: ${r.cards.length} esperando, ${go.length} se pueden contestar${r.otherAgents ? ` (otras ${r.otherAgents} son de n\xFAmeros de otro agente)` : ""}.`
11470
+ ));
11471
+ if (!v.reply || v["dry-run"] || !go.length) {
11472
+ return out({ ...r, ...v.reply ? { dryRun: true, wouldReply: go.map((c) => c.id) } : {} }, () => `${rows.length ? `${table(rows, ["card", "name", "phone", "msgs", "since", "last", "status"])}
11473
+ ` : ""}${summary}${v.reply && go.length ? `
11474
+ ${dim(t("Dry run: nothing was sent.", "Simulacro: no se mand\xF3 nada."))}` : go.length ? `
11475
+ ${dim(t(`Answer them: ${retry}`, `Contestarlas: ${retry}`))}` : ""}`);
11476
+ }
11477
+ if (!globals.json) info(table(rows.filter((_, k) => r.cards[k].flags.length === 0), ["card", "name", "phone", "msgs", "since", "last"]));
11478
+ await confirm(t(`The agent will answer ${go.length} real customer(s), one by one. Go?`, `El agente les contestar\xE1 a ${go.length} cliente(s) reales, uno por uno. \xBFSeguimos?`), { yes: v.yes, retry });
11479
+ const done = [];
11480
+ for (const c of go) {
11481
+ try {
11482
+ await api(cardPath(id, c.id, "/reply"), { body: v.directive ? { directive: v.directive } : {} });
11483
+ done.push({ id: c.id, ok: true });
11484
+ if (!globals.json) info(`${lavender("\u2713")} ${cardName(c)}`);
11485
+ } catch (e) {
11486
+ const msg = e instanceof Error ? e.message : String(e);
11487
+ done.push({ id: c.id, ok: false, error: msg });
11488
+ if (!globals.json) info(`\u2717 ${cardName(c)}: ${serverText(msg)}`);
11489
+ }
11490
+ }
11491
+ const ok = done.filter((d) => d.ok).length;
11492
+ out({ board: r.board, replied: done }, () => `${lavender("\u2713")} ${t(`Answered ${ok} of ${go.length}.`, `Se contestaron ${ok} de ${go.length}.`)}`);
11493
+ }
11257
11494
  async function boardCmd(sub, id, v, ref) {
11495
+ if (sub === "pause" || sub === "resume" || sub === "reply" || sub === "pending") {
11496
+ if (!id) throw usage(t("Missing agent id.", "Falta el id del agente."), t("Example", "Ejemplo") + `: ghosty cards ${sub} <agent-id>${sub === "pending" ? "" : ' "#12"'}`);
11497
+ if (sub === "reply") return cardReply(id, ref, v);
11498
+ if (sub === "pending") return cardPending(id, v);
11499
+ return cardPause(sub, id, ref, v);
11500
+ }
11258
11501
  if (sub === "ls" || sub === "list") {
11259
11502
  if (!id) throw usage(t("Missing agent id.", "Falta el id del agente."), t("Example", "Ejemplo") + ": ghosty board ls <agent-id> [--q text] [--limit 20]");
11260
11503
  return boardLs(id, v);
@@ -11263,7 +11506,7 @@ async function boardCmd(sub, id, v, ref) {
11263
11506
  if (!id) throw usage(t("Missing agent id.", "Falta el id del agente."), t("Example", "Ejemplo") + ': ghosty board show <agent-id> "#12"');
11264
11507
  return boardShow(id, ref, v);
11265
11508
  }
11266
- if (sub !== "archive") throw usage(t(`Unknown board subcommand: ${sub ?? "(none)"}.`, `Subcomando de board desconocido: ${sub ?? "(ninguno)"}.`), t("Try", "Prueba") + ": ghosty board ls <agent-id> \xB7 ghosty board show <agent-id> <folio|phone> \xB7 ghosty board archive <agent-id> --integration prueba --dry-run");
11509
+ if (sub !== "archive") throw usage(t(`Unknown board subcommand: ${sub ?? "(none)"}.`, `Subcomando de board desconocido: ${sub ?? "(ninguno)"}.`), t("Try", "Prueba") + ": ghosty cards ls <agent-id> \xB7 show <agent-id> <folio|phone> \xB7 archive <agent-id> --integration prueba --dry-run \xB7 pause|resume|reply <agent-id> <card> \xB7 pending <agent-id>");
11267
11510
  if (!id) throw usage(t("Missing agent id.", "Falta el id del agente."), t("Example", "Ejemplo") + ": ghosty board archive <agent-id> --integration prueba --dry-run");
11268
11511
  const filter = { ...v.integration ? { integration: v.integration } : {}, ...v.column ? { column: v.column } : {}, ...v.before ? { before: v.before } : {} };
11269
11512
  if (!Object.keys(filter).length) throw usage(t("Add at least one filter: --integration, --column or --before.", "Agrega al menos un filtro: --integration, --column o --before."), t("A filter-less archive would empty the board.", "Archivar sin filtro vaciar\xEDa el tablero."));
@@ -11344,13 +11587,17 @@ var HELPS_ES = {
11344
11587
  summary: "Crea, revisa, cambia, diagnostica y borra agentes.",
11345
11588
  replace: [
11346
11589
  ["# the agent as a file; see ghosty apply --help", "# el agente como archivo; ver ghosty apply --help"],
11590
+ ["# who is @build in Teams (that workspace, or all of yours)", "# qui\xE9n es @build en Teams (en ese espacio, o en todos los tuyos)"],
11591
+ ["# change one piece of the prompt, on the server's current one", "# cambia un pedazo del prompt, sobre el actual del servidor"],
11347
11592
  ["# your account: merge copies with the same content", "# tu cuenta: une las copias con el mismo contenido"],
11348
11593
  ["# staff: their agents, latest turns, plan and boxes", "# staff: sus agentes, \xFAltimos turnos, plan y cajas"],
11349
11594
  ["# what it really has: effective toolsets, connectors connected + granted", "# lo que de verdad tiene: toolsets efectivos, conectores conectados + concedidos"],
11350
11595
  ["# Teams workspaces you administer: where it answers, is paused, and with which @handle", "# los equipos de Teams que administras: d\xF3nde contesta, d\xF3nde est\xE1 en pausa y con qu\xE9 @handle"],
11351
11596
  ["# turn it on there (or change its @handle)", "# lo prende ah\xED (o le cambia el @handle)"],
11352
11597
  ["# pause it there; it keeps its @handle", "# lo pausa ah\xED; conserva su @handle"],
11353
- ["# remove it from there; the agent stays", "# lo quita de ah\xED; el agente sigue"]
11598
+ ["# remove it from there; the agent stays", "# lo quita de ah\xED; el agente sigue"],
11599
+ ["# web widget: chat bubble on your site, only on those domains", "# widget web: burbuja de chat en tu sitio, s\xF3lo en esos dominios"],
11600
+ ["# state + the <script> to paste", "# su estado + el <script> para pegar"]
11354
11601
  ],
11355
11602
  flags: [
11356
11603
  ["--fields a,b", "get: regresa s\xF3lo estos campos"],
@@ -11365,6 +11612,8 @@ var HELPS_ES = {
11365
11612
  ["--toolsets LIST", "set: grupos de tools de gs que recibe (crm, sales\u2026): una lista reemplaza; +x / -x agrega o quita"],
11366
11613
  ["--prompt-base ID", "set: el prompt de este agente = el prompt de ese agente + --prompt-overlay; se regenera cuando cambia la base. none = desliga (conserva el prompt de hoy)"],
11367
11614
  ["--prompt-overlay FILE", 'set: JSON {"edits":[{"what","find","replace","regex"?,"all"?}]}, aplicados en orden; cada uno debe encontrar su lugar o no cambia nada'],
11615
+ ["--prompt-patch FILE", 'set: el mismo formato {"edits":[\u2026]}, aplicado UNA vez sobre el prompt actual del agente en el servidor (no sobre una copia local: nunca pisa el cambio de alguien m\xE1s). Si una edici\xF3n no encuentra su lugar, no cambia nada. --dry-run imprime el diff'],
11616
+ ["--prompt-append FILE", "set: agrega ese texto al final del prompt (con --prompt-patch tambi\xE9n: primero las ediciones, luego el texto)"],
11368
11617
  ["--voice E:ID", "set: voz de las notas de voz: elevenlabs:<voiceId> o kokoro:em_santa|em_alex|ef_dora"],
11369
11618
  ["--voice-replies M", "set: auto = contesta con nota de voz cuando el cliente manda o pide audio; off = siempre texto"],
11370
11619
  ["--auto-stage on|off", "set: mueve solo las tarjetas del tablero tras cada turno del cliente (una llamada extra al modelo por turno)"],
@@ -11379,7 +11628,7 @@ var HELPS_ES = {
11379
11628
  ["--grant read|write|no", "docs add: acceso de este agente (default read; no = s\xF3lo subirlo a la cuenta)"],
11380
11629
  ["--note TEXT", "docs replace/restore: por qu\xE9 cambi\xF3 (sale en docs history)"],
11381
11630
  ["--role R", "share: editor (lo configura y lo usa) o admin (adem\xE1s lo comparte); repetirlo sobre un correo ya compartido cambia el rol"],
11382
- ["--handle NAME", "teams on: su @handle en ese equipo (default: sacado de su nombre, con sufijo si est\xE1 ocupado; @ghosty est\xE1 reservado). Sobre un equipo donde ya est\xE1, le cambia el handle"],
11631
+ ["--handle NAME", "teams on: su @handle en ese equipo (default: sacado de su nombre, con sufijo si est\xE1 ocupado; @ghosty est\xE1 reservado). Sobre un equipo donde ya est\xE1, le cambia el handle. ls: qui\xE9n contesta a ese @handle"],
11383
11632
  ["--factory ROLES", "teams on: le pasa esos roles de la Software Factory del equipo (plan, build, check, eval; separados por comas) \u2014 lo mismo que \xABEquipo\xBB en /factory. Cada rol conserva su nombre y avatar; su siguiente turno ya corre en \xE9l. La f\xE1brica tiene que estar instalada ah\xED"],
11384
11633
  ["--yes", "rm: se salta la confirmaci\xF3n (obligatorio sin terminal)"],
11385
11634
  ["--slug NAME", "demo: nombre de la liga; se le agrega un c\xF3digo de 6 caracteres (/p/NAME-CODE)"],
@@ -11575,6 +11824,7 @@ var HELPS_ES = {
11575
11824
  ["--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"],
11576
11825
  ["--integration ID", "link/unlink/history: el id de la Integration del n\xFAmero de WhatsApp Business en Formmy"],
11577
11826
  ["--email E", "integrations (staff): lista los n\xFAmeros de otra cuenta de Formmy"],
11827
+ ["--orphans", "integrations / messenger ls: s\xF3lo los n\xFAmeros o p\xE1ginas que hoy no contesta nadie y S\xCD tuvieron turnos, con qui\xE9n los contest\xF3 al \xFAltimo (a qui\xE9n regresarlos)"],
11578
11828
  ["--days N", "history: conversaciones activas en los \xFAltimos N d\xEDas (1-30, default 7)"],
11579
11829
  ["--media", "history: s\xF3lo re-copia los archivos que faltan (fotos, PDF, audios) de las conversaciones ya importadas"],
11580
11830
  ["--answer on|off", "link (obligatorio, sin default): on = este agente contesta a clientes reales; off = s\xF3lo registra"],
@@ -11592,7 +11842,7 @@ var HELPS_ES = {
11592
11842
  "Apagarlo sigue registrando los mensajes; nadie los contesta.",
11593
11843
  "Los grupos son del WhatsApp PERSONAL emparejado al agente (no de WhatsApp Business). <group> es su nombre o su jid.",
11594
11844
  "link mueve un n\xFAmero que ya vive en Formmy (EasyBits, un droplet) sin rehacer el alta de Meta; unlink lo regresa. S\xF3lo el due\xF1o, un admin del agente o el staff de Ghosty.",
11595
- "integrations lista los n\xFAmeros de WhatsApp Business del due\xF1o en Formmy (n\xFAmero, nombre, qui\xE9n contesta hoy: gs, EasyBits, un droplet o Formmy) con el id de la Integration que piden link e history. Tambi\xE9n salen los n\xFAmeros que el espacio ya enlaz\xF3 en gs, aunque en Formmy sean de otra cuenta.",
11845
+ "integrations lista los n\xFAmeros de WhatsApp Business del due\xF1o en Formmy (n\xFAmero, nombre, qui\xE9n contesta hoy: gs, EasyBits, un droplet o Formmy) con el id de la Integration que piden link e history. Tambi\xE9n salen los n\xFAmeros que el espacio ya enlaz\xF3 en gs, aunque en Formmy sean de otra cuenta. \xABlast answered by\xBB es el agente del \xFAltimo turno en ese n\xFAmero (de los turnos de gs): as\xED un n\xFAmero que qued\xF3 en nadie se puede regresar.",
11596
11846
  "history trae al tablero las tarjetas, mensajes y pedidos recientes del n\xFAmero desde Formmy (enl\xE1zalo antes; --dry-run funciona sin enlazar, para decidir --days). Repetirlo nunca duplica: c\xF3rrelo antes del corte y otra vez justo en el corte. Los archivos (fotos, PDF, audios) tambi\xE9n se copian a gs: Formmy los borra a los 7 d\xEDas, as\xED que trae el n\xFAmero dentro de esa semana; una copia que fall\xF3 se reintenta con --media.",
11597
11847
  "import: apaga primero el socket de EasyBits SIN logout (dos sockets se tumban entre s\xED; un logout invalida las llaves). Borra el archivo despu\xE9s."
11598
11848
  ]
@@ -11674,12 +11924,19 @@ var HELPS_ES = {
11674
11924
  ["--integration ID", "tarjetas de este canal/integraci\xF3n (p. ej. prueba)"],
11675
11925
  ["--column NAME", "tarjetas en esta columna"],
11676
11926
  ["--before T", "tarjetas sin actividad desde T (30d, 12h o una fecha ISO)"],
11677
- ["-n, --dry-run", "cuenta y muestra una muestra, no cambia nada"],
11678
- ["--yes", "se salta la confirmaci\xF3n (obligatorio sin terminal)"]
11927
+ ["-n, --dry-run", "cuenta y muestra una muestra, no cambia nada \xB7 pending --reply: lista lo que contestar\xEDa, no manda nada"],
11928
+ ["--yes", "se salta la confirmaci\xF3n (obligatorio sin terminal)"],
11929
+ ["--mode M", "pause: 30min | 2h | 24h (default) | until_tomorrow (9:00) | permanent (escalada: s\xF3lo operador)"],
11930
+ ["--directive TEXT", "reply / pending --reply: una instrucci\xF3n para el agente (redacta su respuesta sigui\xE9ndola, no la repite)"],
11931
+ ["--since T", "pending: clientes que escribieron desde T (30m, 2d o una fecha ISO; default 3d)"],
11932
+ ["--reply", "pending: contesta, una por una, las que no traen bandera (pregunta antes)"]
11679
11933
  ],
11680
11934
  notes: [
11681
11935
  "show imprime cada mensaje con qui\xE9n lo escribi\xF3 (cliente, agente, operador), su adjunto y si gs guarda una copia.",
11682
- "Archivar es reversible: cuando el cliente vuelve a escribir, la tarjeta regresa. Se necesita al menos un filtro."
11936
+ "Archivar es reversible: cuando el cliente vuelve a escribir, la tarjeta regresa. Se necesita al menos un filtro.",
11937
+ "resume quita la pausa Y el \xABs\xF3lo operador\xBB (escalar = pausa permanente), en gs y en Formmy; NO contesta solo \u2014 para eso est\xE1 reply. reply s\xF3lo sirve en conversaciones de WhatsApp Business de un n\xFAmero que contesta este agente.",
11938
+ "pending marca y nunca contesta: en pausa, escalada, fuera de la ventana de 24 h de WhatsApp (Meta s\xF3lo acepta plantillas), s\xF3lo una reacci\xF3n, no es WhatsApp Business, n\xFAmero sin agente. Las de n\xFAmeros de otro agente no salen (s\xF3lo se cuentan).",
11939
+ "pause, resume y reply quedan en bit\xE1cora (ghosty audit ls --action card)."
11683
11940
  ]
11684
11941
  },
11685
11942
  usage: {
@@ -11770,6 +12027,39 @@ var HELPS_ES = {
11770
12027
  "En el panel web el staff elige \xABAtendiendo: <cliente>\xBB (Documentos y bases, Agentes); es una cookie que se revalida en cada request, nunca otra sesi\xF3n.",
11771
12028
  "Revocar corta en el siguiente request. Asignar dos veces no hace nada (una sola activa por par)."
11772
12029
  ]
12030
+ },
12031
+ audit: {
12032
+ summary: "La bit\xE1cora (AuditEvent): qui\xE9n entr\xF3 \xABcomo\xBB qui\xE9n, qu\xE9 cambi\xF3 en la ficha y en los canales de un agente, pausas y respuestas desde el CLI, traspasos, staff que entra o sale. El staff de Ghosty ve todo; cualquier otra cuenta, s\xF3lo lo de sus espacios y agentes.",
12033
+ flags: [
12034
+ ["--workspace SLUG", "los eventos de ese espacio y de sus agentes (default: el de ghosty use; none = todos los tuyos)"],
12035
+ ["--agent ID", "s\xF3lo los de ese agente (nombre o id)"],
12036
+ ["--actor EMAIL", "s\xF3lo lo que hizo esa persona"],
12037
+ ["--action A", "agent, channel, card, factory, workspace\u2026 (un prefijo) o una acci\xF3n exacta (agent.save)"],
12038
+ ["--since T", "desde T (30m, 24h, 7d o una fecha ISO; default 7d)"],
12039
+ ["--limit N", "cu\xE1ntos (default 50, m\xE1x. 1000)"],
12040
+ ["--full", "el detalle completo de cada evento (JSON)"]
12041
+ ],
12042
+ notes: [
12043
+ "Hoy se anotan: agent.save (el Guardar de la ficha), channel.* (p\xE1gina Canales), agent.prompt_patch, card.pause/resume/reply, factory.run_rename, workspace.*, staff y \xABentrar como\xBB. Cambios m\xE1s viejos pueden no estar.",
12044
+ "El token gat_ de un agente no lee la bit\xE1cora."
12045
+ ]
12046
+ },
12047
+ factory: {
12048
+ summary: "Los pedidos de la Software Factory de un espacio (viven en su Teams): listarlos, ver uno (plan vigente, PR, preview, \xFAltimos eventos, qu\xE9 rol trabaja) y corregir el t\xEDtulo de un pedido.",
12049
+ replace: [['"Title"', '"T\xEDtulo"']],
12050
+ flags: [
12051
+ ["--workspace SLUG", "el espacio (default: el de ghosty use)"],
12052
+ ["--room R", "ls: s\xF3lo los pedidos de ese room (#slug o nombre)"],
12053
+ ["--status S", "ls: s\xF3lo esa etapa (planning, building, checking, pr_review, merged\u2026)"],
12054
+ ["--limit N", "ls: cu\xE1ntos (default 30)"],
12055
+ ["--full", "show: imprime el plan vigente"],
12056
+ ["-n, --dry-run", "rename: ense\xF1a el cambio, no cambia nada"],
12057
+ ["--yes", "rename: se salta la confirmaci\xF3n (obligatorio sin terminal)"]
12058
+ ],
12059
+ notes: [
12060
+ "Leer pide ser miembro del espacio; renombrar, ser due\xF1o, admin o staff de Ghosty, y queda en bit\xE1cora (factory.run_rename). La tarjeta del room toma el t\xEDtulo nuevo en su siguiente refresco.",
12061
+ "Qui\xE9n es @plan o @build: ghosty agents ls --handle plan --workspace SLUG."
12062
+ ]
11773
12063
  }
11774
12064
  };
11775
12065
  var HELP_ALIASES = { creds: "credentials", conectores: "connectors", board: "cards", conversations: "chat", workspaces: "spaces" };
@@ -11782,12 +12072,14 @@ var HELPS = {
11782
12072
  summary: "Create, inspect, change, check and delete agents.",
11783
12073
  usage: [
11784
12074
  "ghosty agents ls [--engine E] [--ids] [--sponsor EMAIL | --owner EMAIL]",
12075
+ "ghosty agents ls --handle build [--workspace WS] # who is @build in Teams (that workspace, or all of yours)",
11785
12076
  "ghosty agents create --name NAME [--engine E] [--model M] [--prompt TEXT | --prompt-file FILE] [--env K=V,\u2026] [--workspace SLUG] [--channels teams=off,\u2026] [--like AGENT [--copy-dbs] [--board same|main|new|NAME] [--no-notify] [--dry-run]]",
11786
12077
  "ghosty agents get <agent-id> [--fields name,model,prompt] [--prompt-out FILE]",
11787
12078
  "ghosty agents export <agent-id> [--out DIR] # the agent as a file; see ghosty apply --help",
11788
12079
  "ghosty agents set <agent-id> [--name] [--engine] [--model] [--prompt | --prompt-file] [--env K=V,\u2026] [--unset-env K1,K2] [--connect ID,\u2026] [--disconnect ID,\u2026] [--voice E:ID] [--voice-replies auto|off] [--auto-stage on|off] [--toolsets crm,sales | +crm,-sales] [--tool [channel:]tool=on|off,\u2026] [--bag NAME|none] [--avatar URL|FILE]",
11789
12080
  "ghosty agents set <agent-id> --engine E [--model M] --dry-run # what changing the engine does (sessions, machines, memory), without changing it",
11790
12081
  "ghosty agents set <agent-id> --prompt-base <base-agent-id> --prompt-overlay overlay.json [--dry-run [--out FILE]] | --prompt-base none",
12082
+ "ghosty agents set <agent-id> --prompt-patch overlay.json [--prompt-append FILE] [--dry-run] # change one piece of the prompt, on the server's current one",
11791
12083
  "ghosty agents doctor <agent-id> [--verbose]",
11792
12084
  "ghosty agents doctor --user <email> [agent-id] [--turns 8] # staff: their agents, latest turns, plan and boxes",
11793
12085
  "ghosty agents docs ls|grant|revoke <agent-id> [doc\u2026] [--write] [--no-deliver] [--like AGENT]",
@@ -11807,6 +12099,8 @@ var HELPS = {
11807
12099
  "ghosty agents box <agent-id> [--check PATH]\u2026 [--contains PATH=TEXT]\u2026 [--recycle]",
11808
12100
  "ghosty agents demo enable|disable <agent-id>",
11809
12101
  'ghosty agents demo <agent-id> [--slug NAME] [--expires "YYYY-MM-DDTHH:MM"|no] [--turns N] [--per-ip N] [--welcome TEXT] [--starter TEXT] [--chips "a|b|c"] [--sponsor on|off] [--rotate]',
12102
+ "ghosty agents embed enable|disable <agent-id> [--domains a.com,*.b.com] # web widget: chat bubble on your site, only on those domains",
12103
+ "ghosty agents embed <agent-id> [--domains \u2026] [--per-visitor N] [--per-ip N] [--welcome TEXT] [--color #hex] [--rotate] # state + the <script> to paste",
11810
12104
  "ghosty agents rm <agent-id> [--from-workspace] [--yes]"
11811
12105
  ],
11812
12106
  flags: [
@@ -11822,6 +12116,8 @@ var HELPS = {
11822
12116
  ["--toolsets LIST", "set: gs tool groups it gets (crm, sales\u2026): a list replaces; +x / -x add or remove"],
11823
12117
  ["--prompt-base ID", "set: this agent's prompt = that agent's prompt + --prompt-overlay; regenerates when the base changes. none = unlink (keeps today's prompt)"],
11824
12118
  ["--prompt-overlay FILE", 'set: JSON {"edits":[{"what","find","replace","regex"?,"all"?}]}, applied in order; each must find its place or nothing changes'],
12119
+ ["--prompt-patch FILE", `set: the same {"edits":[\u2026]} format, applied ONCE on the agent's current prompt on the server (not a local copy: it never overwrites someone else's change). If one edit doesn't find its place, nothing changes. --dry-run prints the diff`],
12120
+ ["--prompt-append FILE", "set: add that text at the end of the prompt (with --prompt-patch too: edits first, then the text)"],
11825
12121
  ["--voice E:ID", "set: voice for voice notes: elevenlabs:<voiceId> or kokoro:em_santa|em_alex|ef_dora"],
11826
12122
  ["--voice-replies M", "set: auto = answer with a voice note when the customer sends or asks for audio; off = always text"],
11827
12123
  ["--auto-stage on|off", "set: move board cards by itself after each customer turn (one extra model call per turn)"],
@@ -11836,7 +12132,7 @@ var HELPS = {
11836
12132
  ["--grant read|write|no", "docs add: access for this agent (default read; no = only upload to the account)"],
11837
12133
  ["--note TEXT", "docs replace/restore: why it changed (shown in docs history)"],
11838
12134
  ["--role R", "share: editor (configures and uses it) or admin (also shares it); again on a shared email changes the role"],
11839
- ["--handle NAME", "teams on: its @handle in that workspace (default: from its name, with a suffix if taken; @ghosty is reserved). On an active workspace it changes the handle"],
12135
+ ["--handle NAME", "teams on: its @handle in that workspace (default: from its name, with a suffix if taken; @ghosty is reserved). On an active workspace it changes the handle. ls: who answers to that @handle"],
11840
12136
  ["--factory ROLES", "teams on: give it those Software Factory roles in that workspace (plan, build, check, eval; comma-separated) \u2014 the same as \xABTeam\xBB in /factory. Keeps each role's name and avatar; the next turn of each role runs on it. The factory must be installed there"],
11841
12137
  ["--yes", "rm: skip the confirmation (required without a terminal)"],
11842
12138
  ["--slug NAME", "demo: link name; a 6-char code is appended (/p/NAME-CODE)"],
@@ -11883,6 +12179,7 @@ var HELPS = {
11883
12179
  "ghosty agents set <pia-id> --prompt-base <tania-id> --prompt-overlay capa.json --dry-run --out /tmp/pia.md",
11884
12180
  "ghosty agents demo enable <id>",
11885
12181
  'ghosty agents demo <id> --slug guardias --expires "2026-10-15T20:00" --starter "Ens\xE9\xF1ame c\xF3mo empezar"',
12182
+ 'ghosty agents embed enable <id> --domains "mitienda.com,*.mitienda.mx" --welcome "\xA1Hola! \xBFEn qu\xE9 te ayudo?"',
11886
12183
  "ghosty agents box <id> --contains /opt/claude-worker/skills/pdf-doc/render-doc.mjs=pdf-templates",
11887
12184
  "ghosty agents set <id> --tool wa:actualizar_identidad=off,teams:actualizar_identidad=on",
11888
12185
  'ghosty agents set minighosty --bag "MiniGhosty" --avatar https://www.ghosty.studio/avatars/minighosty.png',
@@ -12136,7 +12433,8 @@ HELPS.channels = {
12136
12433
  "ghosty channels whatsapp ls <agent-id>",
12137
12434
  "ghosty channels whatsapp enable <agent-id> <number> [--dry-run] [--yes]",
12138
12435
  "ghosty channels whatsapp disable <agent-id> <number> [--dry-run]",
12139
- "ghosty channels whatsapp integrations <agent-id> [--email OTHER]",
12436
+ "ghosty channels whatsapp integrations <agent-id> [--email OTHER] [--orphans]",
12437
+ "ghosty channels messenger ls <agent-id> [--orphans]",
12140
12438
  "ghosty channels whatsapp link <agent-id> --integration <formmy-id> --answer on|off [--forward] [--dry-run] [--yes]",
12141
12439
  "ghosty channels whatsapp unlink <agent-id> --integration <formmy-id> [--dry-run] [--yes]",
12142
12440
  "ghosty channels whatsapp history <agent-id> --integration <formmy-id> [--days 7] [--media] [--dry-run] [--yes]",
@@ -12163,6 +12461,7 @@ HELPS.channels = {
12163
12461
  ["--name N", "groups set: the name gs shows (e.g. a group saved with its jid as name); WhatsApp's subject doesn't change"],
12164
12462
  ["--integration ID", "link/unlink/history: the WhatsApp Business number's Integration id in Formmy"],
12165
12463
  ["--email E", "integrations (staff): list the numbers of another Formmy account"],
12464
+ ["--orphans", "integrations / messenger ls: only the numbers or pages nobody answers today that DID have turns, with who answered them last (who to give them back to)"],
12166
12465
  ["--days N", "history: conversations active in the last N days (1-30, default 7)"],
12167
12466
  ["--media", "history: only re-copy the missing files (photos, PDFs, audio) of conversations already imported"],
12168
12467
  ["--answer on|off", "link (required, no default): on = this agent answers real customers; off = it only records"],
@@ -12182,6 +12481,8 @@ HELPS.channels = {
12182
12481
  "ghosty channels wa groups ls <id>",
12183
12482
  'ghosty channels wa groups enable <id> "Pia-cotizadora" --role equipo --dry-run',
12184
12483
  "ghosty channels whatsapp integrations <id>",
12484
+ "ghosty channels whatsapp integrations <id> --orphans",
12485
+ "ghosty channels messenger ls <id>",
12185
12486
  "ghosty channels whatsapp link <id> --integration 6a3af395\u2026 --answer off --dry-run",
12186
12487
  "ghosty channels whatsapp history <id> --integration 6a3af395\u2026 --days 7 --dry-run",
12187
12488
  "ghosty channels whatsapp history <id> --integration 6a3af395\u2026 --days 7 --media",
@@ -12193,7 +12494,7 @@ HELPS.channels = {
12193
12494
  "Disabling keeps logging messages; nobody answers them.",
12194
12495
  "Groups belong to the PERSONAL WhatsApp paired to the agent (not WhatsApp Business). <group> is its name or its jid.",
12195
12496
  "link moves a number that already lives in Formmy (EasyBits, a droplet) without redoing Meta's signup; unlink sends it back. Only the owner, an admin of the agent or Ghosty staff.",
12196
- "integrations lists the owner's WhatsApp Business numbers in Formmy (number, name, who answers today: gs, EasyBits, a droplet or Formmy) with the Integration id that link and history take. Numbers the workspace already linked in gs show up too, even if another Formmy account owns them.",
12497
+ "integrations lists the owner's WhatsApp Business numbers in Formmy (number, name, who answers today: gs, EasyBits, a droplet or Formmy) with the Integration id that link and history take. Numbers the workspace already linked in gs show up too, even if another Formmy account owns them. \xABlast answered by\xBB is the agent of the latest turn on that number (from gs's turns), so a number left answered by nobody can be given back.",
12197
12498
  "history brings the number's recent cards, messages and orders from Formmy to the board (link it first; --dry-run works before linking, to choose --days). Re-running never duplicates: run it before the cutover and again right at it. Files (photos, PDFs, audio) are copied to gs too: Formmy deletes them after 7 days, so bring the number within that week; a copy that failed is retried with --media.",
12198
12499
  "import: turn off the EasyBits socket first WITHOUT logout (two sockets knock each other off; a logout invalidates the keys). Delete the file afterwards."
12199
12500
  ],
@@ -12283,7 +12584,11 @@ HELPS.cards = {
12283
12584
  usage: [
12284
12585
  "ghosty cards ls <agent-id> [--q TEXT] [--paused] [--limit 20]",
12285
12586
  "ghosty cards show <agent-id> <folio|phone|session|id> [--limit 20]",
12286
- "ghosty cards archive <agent-id> [--integration ID] [--column NAME] [--before 30d|ISO] [--dry-run] [--yes]"
12587
+ "ghosty cards archive <agent-id> [--integration ID] [--column NAME] [--before 30d|ISO] [--dry-run] [--yes]",
12588
+ "ghosty cards pause <agent-id> <card> [--mode 30min|2h|24h|until_tomorrow|permanent]",
12589
+ "ghosty cards resume <agent-id> <card>",
12590
+ "ghosty cards reply <agent-id> <card> [--directive TEXT] [--yes]",
12591
+ "ghosty cards pending <agent-id> [--since 2d|ISO] [--limit N] [--reply [--directive TEXT] [--yes]] [--dry-run]"
12287
12592
  ],
12288
12593
  flags: [
12289
12594
  ["--q TEXT", "ls: cards whose name, phone or last message contains TEXT"],
@@ -12292,11 +12597,15 @@ HELPS.cards = {
12292
12597
  ["--integration ID", "cards from this channel/integration (e.g. prueba)"],
12293
12598
  ["--column NAME", "cards in this column"],
12294
12599
  ["--before T", "cards with no activity since T (30d, 12h or an ISO date)"],
12295
- ["-n, --dry-run", "count and show a sample, change nothing"],
12296
- ["--yes", "skip the confirmation (required without a terminal)"]
12600
+ ["-n, --dry-run", "count and show a sample, change nothing \xB7 pending --reply: list what it would answer, send nothing"],
12601
+ ["--yes", "skip the confirmation (required without a terminal)"],
12602
+ ["--mode M", "pause: 30min | 2h | 24h (default) | until_tomorrow (9:00) | permanent (escalated: operator only)"],
12603
+ ["--directive TEXT", "reply / pending --reply: an instruction for the agent (it writes its own answer following it, it doesn't repeat it)"],
12604
+ ["--since T", "pending: customers who wrote since T (30m, 2d or an ISO date; default 3d)"],
12605
+ ["--reply", "pending: answer, one by one, the ones with no flag (asks first)"]
12297
12606
  ],
12298
- examples: ["ghosty cards ls <id> --q 7712", "ghosty cards ls <id> --paused --limit 200", 'ghosty cards show <id> "#12"', "ghosty cards show <id> 5210000009922 --limit 50", "ghosty cards archive <id> --integration prueba --dry-run", "ghosty cards archive <id> --column Perdido --before 30d --yes"],
12299
- notes: ["show prints each message with who wrote it (customer, agent, operator), its attachment and whether gs keeps a copy of it.", "Archiving is reversible: when the customer writes again the card comes back. At least one filter is required."],
12607
+ examples: ["ghosty cards ls <id> --q 7712", "ghosty cards ls <id> --paused --limit 200", 'ghosty cards show <id> "#12"', "ghosty cards show <id> 5210000009922 --limit 50", "ghosty cards archive <id> --integration prueba --dry-run", "ghosty cards archive <id> --column Perdido --before 30d --yes", 'ghosty cards pause <id> "#12" --mode 2h', 'ghosty cards resume <id> "#12"', 'ghosty cards reply <id> "#12" --directive "ofr\xE9cele el env\xEDo a domicilio"', "ghosty cards pending <id> --since 2026-10-06T02:13Z", "ghosty cards pending <id> --since 12h --reply --dry-run"],
12608
+ notes: ["show prints each message with who wrote it (customer, agent, operator), its attachment and whether gs keeps a copy of it.", "Archiving is reversible: when the customer writes again the card comes back. At least one filter is required.", "resume lifts the pause AND \xABoperator only\xBB (escalating = a permanent pause), on gs and on Formmy; it does NOT answer by itself \u2014 reply does. reply only works on WhatsApp Business conversations of a number this agent answers.", "pending flags and never answers: paused, escalated, outside WhatsApp's 24 h window (Meta only takes templates), only a reaction, not WhatsApp Business, number answered by nobody. Conversations of another agent's numbers are left out (only counted).", "pause, resume and reply are logged (ghosty audit ls --action card)."],
12300
12609
  learn: `${REF}#tablero`
12301
12610
  };
12302
12611
  HELPS.board = HELPS.cards;
@@ -12438,6 +12747,42 @@ HELPS.staff = {
12438
12747
  ],
12439
12748
  learn: `${REF}#staff-asignado`
12440
12749
  };
12750
+ HELPS.audit = {
12751
+ summary: "The audit log (AuditEvent): who logged in \xABas\xBB whom, what changed on an agent's page and channels, pauses and replies from the CLI, transfers, staff in and out. Ghosty staff see everything; anyone else, only the events of their own workspaces and agents.",
12752
+ usage: ["ghosty audit ls [--workspace SLUG] [--agent ID] [--actor EMAIL] [--action A] [--since 7d] [--limit 50] [--full]"],
12753
+ flags: [
12754
+ ["--workspace SLUG", "that workspace's events and its agents' (default: the one from ghosty use; none = all of yours)"],
12755
+ ["--agent ID", "only that agent's events (name or id)"],
12756
+ ["--actor EMAIL", "only what that person did"],
12757
+ ["--action A", "agent, channel, card, factory, workspace\u2026 (a prefix) or an exact action (agent.save)"],
12758
+ ["--since T", "since T (30m, 24h, 7d or an ISO date; default 7d)"],
12759
+ ["--limit N", "how many (default 50, max 1000)"],
12760
+ ["--full", "the whole detail of each event (JSON)"]
12761
+ ],
12762
+ examples: ["ghosty audit ls --workspace siiqtec --since 24h", "ghosty audit ls --agent tania-0 --action agent", "ghosty audit ls --actor brenda@siiqtec.com --since 2026-10-05T00:00Z", "ghosty audit ls --action card --json"],
12763
+ notes: ["Logged today: agent.save (the agent page's Save), channel.* (Channels page), agent.prompt_patch, card.pause/resume/reply, factory.run_rename, workspace.*, staff and \xABlog in as\xBB. Older changes may not be there.", "A gat_ agent token can't read the log."],
12764
+ learn: `${REF}#bitacora`
12765
+ };
12766
+ HELPS.factory = {
12767
+ summary: "A workspace's Software Factory runs (they live in its Teams): list them, see one (current plan, PR, preview, latest events, which role is working) and fix a run's title.",
12768
+ usage: [
12769
+ "ghosty factory runs ls [--workspace SLUG] [--room R] [--status S] [--limit 30]",
12770
+ "ghosty factory runs show <run> [--workspace SLUG] [--full]",
12771
+ 'ghosty factory runs rename <run> "Title" [--workspace SLUG] [--dry-run] [--yes]'
12772
+ ],
12773
+ flags: [
12774
+ ["--workspace SLUG", "the workspace (default: the one from ghosty use)"],
12775
+ ["--room R", "ls: only that room's runs (#slug or name)"],
12776
+ ["--status S", "ls: only that stage (planning, building, checking, pr_review, merged\u2026)"],
12777
+ ["--limit N", "ls: how many (default 30)"],
12778
+ ["--full", "show: print the current plan"],
12779
+ ["-n, --dry-run", "rename: show the change, change nothing"],
12780
+ ["--yes", "rename: skip the confirmation (required without a terminal)"]
12781
+ ],
12782
+ examples: ["ghosty factory runs ls --workspace business --room mailmask", "ghosty factory runs show 15 --workspace business", 'ghosty factory runs rename 15 "Docs del CLI al d\xEDa" --workspace business --dry-run'],
12783
+ notes: ["Reading needs membership in the workspace; rename needs owner, admin or Ghosty staff, and is logged (factory.run_rename). The room's card picks the new title up on its next refresh.", "Who is @plan or @build: ghosty agents ls --handle plan --workspace SLUG."],
12784
+ learn: `${REF}#fabrica`
12785
+ };
12441
12786
  function helpText(cmd) {
12442
12787
  const base = HELPS[cmd];
12443
12788
  if (!base) return void 0;
@@ -12681,6 +13026,10 @@ function normalizeArgs(pos, v) {
12681
13026
  if (v.expires !== void 0) v.vence = v.expires;
12682
13027
  else if (v.vence !== void 0) notes.push(t("`--vence` is now `--expires`.", "`--vence` ahora es `--expires`."));
12683
13028
  }
13029
+ if (cmd === "agents" && (sub === "embed" || sub === "widget") && (pos[2] === "enable" || pos[2] === "disable")) {
13030
+ v[pos[2] === "enable" ? "on" : "off"] = true;
13031
+ pos.splice(2, 1);
13032
+ }
12684
13033
  if (cmd === "channels" && pos[2] === "groups") {
12685
13034
  if (pos[3] === "enable" || pos[3] === "disable") {
12686
13035
  v[pos[3] === "enable" ? "on" : "off"] = true;
@@ -13253,6 +13602,91 @@ ${dim(t("Links with the old slug may stop working.", "Las ligas con el slug viej
13253
13602
  }
13254
13603
  }
13255
13604
 
13605
+ // src/commands/audit.ts
13606
+ function detailLine(d, max = 90) {
13607
+ if (d === null || d === void 0) return "";
13608
+ if (typeof d !== "object") return String(d).slice(0, max);
13609
+ const parts = Object.entries(d).filter(([k]) => k !== "via").map(([k, v]) => `${k}=${v !== null && typeof v === "object" ? JSON.stringify(v) : String(v)}`);
13610
+ const s = parts.join(" ");
13611
+ return s.length > max ? `${s.slice(0, max - 1)}\u2026` : s;
13612
+ }
13613
+ async function auditCmd(sub, v, context) {
13614
+ if (sub && sub !== "ls" && sub !== "list") throw usage(t(`Unknown audit subcommand: ${sub}.`, `Subcomando de audit desconocido: ${sub}.`), "ghosty audit ls [--workspace SLUG] [--agent ID] [--actor EMAIL] [--action A] [--since 24h] [--limit N]");
13615
+ const ws = v.workspace === "none" ? void 0 : v.workspace ?? (v.agent ? void 0 : context);
13616
+ const qs = new URLSearchParams({
13617
+ ...ws ? { workspace: ws } : {},
13618
+ ...v.agent ? { agent: v.agent } : {},
13619
+ ...v.actor ? { actor: v.actor } : {},
13620
+ ...v.action ? { action: v.action } : {},
13621
+ since: v.since ?? "7d",
13622
+ ...v.limit ? { limit: v.limit } : {}
13623
+ });
13624
+ const r = await api(`/api/v2/audit?${qs}`);
13625
+ const rows = r.events.map((e) => ({
13626
+ at: e.at.slice(0, 16).replace("T", " "),
13627
+ actor: e.actor,
13628
+ action: e.action,
13629
+ where: [e.workspace, e.agentName ?? e.agentId].filter(Boolean).join(" \xB7 "),
13630
+ detail: v.full ? JSON.stringify(e.detail ?? null) : detailLine(e.detail)
13631
+ }));
13632
+ return out(r, () => rows.length ? `${table(rows, ["at", "actor", "action", "where", "detail"])}
13633
+ ${dim(t("Times in UTC. --full for the whole detail; --json for everything.", "Horas en UTC. --full para el detalle completo; --json para todo."))}` : dim(t(`No events since ${v.since ?? "7d"}${ws ? ` in ${ws}` : ""}.`, `Sin eventos desde ${v.since ?? "7d"}${ws ? ` en ${ws}` : ""}.`)));
13634
+ }
13635
+
13636
+ // src/commands/factory.ts
13637
+ var enc12 = encodeURIComponent;
13638
+ var short2 = (s) => (s ?? "").slice(0, 16).replace("T", " ");
13639
+ async function factoryCmd(sub, verb, ref, title, v, context) {
13640
+ if (sub !== "runs") throw usage(t(`Unknown factory subcommand: ${sub ?? "(none)"}.`, `Subcomando de factory desconocido: ${sub ?? "(ninguno)"}.`), "ghosty factory runs ls|show|rename \u2026");
13641
+ const ws = v.workspace ?? context;
13642
+ if (!ws) throw usage(t("Which workspace?", "\xBFQu\xE9 espacio?"), t("Add --workspace SLUG (or set one with ghosty use SLUG).", "Agrega --workspace SLUG (o fija uno con ghosty use SLUG)."));
13643
+ const base = `/api/v2/workspaces/${enc12(ws)}/factory/runs`;
13644
+ if (!verb || verb === "ls" || verb === "list") {
13645
+ const qs = new URLSearchParams({ ...v.room ? { room: v.room } : {}, ...v.status ? { status: v.status } : {}, ...v.limit ? { limit: v.limit } : {} });
13646
+ const r = await api(`${base}?${qs}`);
13647
+ const rows = r.runs.map((x) => ({ run: `#${x.id}`, title: x.title.slice(0, 60), status: x.status, room: x.room ? `#${x.room}` : "", pr: x.pr ? x.pr.replace(/^https:\/\/github\.com\//, "") : "", updated: short2(x.updatedAt) }));
13648
+ return out(r, () => rows.length ? `${table(rows, ["run", "title", "status", "room", "pr", "updated"])}
13649
+ ${dim(`${t("Detail", "Detalle")}: ghosty factory runs show <run> --workspace ${r.workspace}`)}` : dim(t(`No factory runs in ${r.workspace}.`, `No hay pedidos de la f\xE1brica en ${r.workspace}.`)));
13650
+ }
13651
+ if (!ref) throw usage(t("Missing run.", "Falta el pedido."), `ghosty factory runs ${verb} <run> --workspace ${ws}`);
13652
+ const path = `${base}/${enc12(ref.replace(/^#/, ""))}`;
13653
+ if (verb === "show") {
13654
+ const { run: r } = await api(path);
13655
+ const lines = [
13656
+ `${bold(`#${r.id} ${r.title}`)} ${dim(`\xB7 ${r.status}${r.kind ? ` (${r.kind})` : ""} \xB7 ${r.room ? `#${r.room}` : ""}`)}`,
13657
+ `${t("repo", "repo")}: ${r.repo ?? "\u2014"}${r.branch ? ` (${r.branch})` : ""} PR: ${r.pr ?? "\u2014"}`,
13658
+ `preview: ${r.preview ?? "\u2014"}${r.previewState ? dim(` (${r.previewState})`) : ""}`,
13659
+ `${t("plan", "plan")}: v${r.planVersion}${r.plan ? ` ${r.plan.decision ?? t("pending", "pendiente")}${r.plan.decidedBy ? ` ${t("by", "por")} ${r.plan.decidedBy}` : ""}` : ""} \xB7 ${t("loops", "vueltas")}: ${r.loops} \xB7 ${t("requested by", "pidi\xF3")} ${r.requestedBy}`,
13660
+ ...r.plans.map((p) => dim(` v${p.version} ${p.decision ?? "\u2014"}: ${p.title ?? ""}`)),
13661
+ `${t("running", "trabajando")}: ${r.running.length ? r.running.map((x) => `@${x.role} ${dim(`(${x.turn}, ${short2(x.since)})`)}`).join(", ") : dim(t("nobody", "nadie"))}`,
13662
+ bold(t("Latest events", "\xDAltimos eventos")),
13663
+ ...r.events.map((e) => ` ${dim(short2(e.at))} ${e.type}${e.actor ? ` ${dim(e.actor)}` : ""}`),
13664
+ ...v.full && r.plan ? ["", bold(t("Current plan", "Plan vigente")), r.plan.markdown] : [dim(t("--full prints the current plan.", "--full imprime el plan vigente."))]
13665
+ ];
13666
+ return out(r, () => lines.join("\n"));
13667
+ }
13668
+ if (verb === "rename") {
13669
+ if (!title?.trim()) throw usage(t("Missing the new title.", "Falta el t\xEDtulo nuevo."), `ghosty factory runs rename ${ref} "T\xEDtulo" --workspace ${ws}`);
13670
+ const retry = `ghosty factory runs rename ${ref} ${JSON.stringify(title)} --workspace ${ws}`;
13671
+ if (!v["dry-run"]) requireYesIfHeadless(v.yes, retry);
13672
+ const plan = await api(path, { body: { title, dryRun: true } });
13673
+ const line2 = `#${ref.replace(/^#/, "")}: \xAB${plan.from}\xBB \u2192 \xAB${plan.to}\xBB`;
13674
+ if (v["dry-run"]) return out({ ...plan, dryRun: true }, () => `${line2}
13675
+ ${dim(t("Dry run: nothing changed.", "Simulacro: no se cambi\xF3 nada."))}`);
13676
+ await confirm(t(`Rename ${line2}?`, `\xBFRenombrar ${line2}?`), { yes: v.yes, retry });
13677
+ const r = await api(path, { body: { title } });
13678
+ return out(r, () => `${lavender("\u2713")} ${line2} ${dim(t("(logged; the room card picks it up on its next refresh)", "(queda en bit\xE1cora; la tarjeta del room lo toma en su siguiente refresco)"))}`);
13679
+ }
13680
+ throw usage(t(`Unknown runs subcommand: ${verb}.`, `Subcomando de runs desconocido: ${verb}.`), 'ghosty factory runs ls | show <run> | rename <run> "title"');
13681
+ }
13682
+ async function agentsByHandle(handle, workspace) {
13683
+ const qs = new URLSearchParams({ handle, ...workspace ? { workspace } : {} });
13684
+ const r = await api(`/api/v2/teams/handles?${qs}`);
13685
+ const rows = r.agents.map((a) => ({ workspace: a.workspace, handle: `@${a.handle}`, name: a.name ?? "", engine: [a.engine, a.model].filter(Boolean).join("/"), on: a.enabled ? t("yes", "s\xED") : dim(t("paused", "en pausa")), id: a.agentId ?? dim(t("(not a gs agent)", "(no es agente de gs)")) }));
13686
+ return out(r, () => `${rows.length ? table(rows, ["workspace", "handle", "name", "engine", "on", "id"]) : dim(t(`Nobody is @${r.handle}${workspace ? ` in ${workspace}` : " in your workspaces"}.`, `Nadie es @${r.handle}${workspace ? ` en ${workspace}` : " en tus espacios"}.`))}${r.unreadable?.length ? `
13687
+ ${dim(t(`Couldn't read: ${r.unreadable.join(", ")}`, `No se pudieron leer: ${r.unreadable.join(", ")}`))}` : ""}`);
13688
+ }
13689
+
13256
13690
  // src/commands/staff.ts
13257
13691
  var day2 = (iso) => iso ? iso.slice(0, 10) : "";
13258
13692
  async function staffCmd(sub, staff, client, v) {
@@ -13292,7 +13726,7 @@ async function staffCmd(sub, staff, client, v) {
13292
13726
 
13293
13727
  // src/commands/support.ts
13294
13728
  import { readFile as readFile14 } from "node:fs/promises";
13295
- var enc12 = encodeURIComponent;
13729
+ var enc13 = encodeURIComponent;
13296
13730
  var cdmx = (iso) => new Date(Date.parse(iso) - 6 * 36e5).toISOString().slice(5, 16).replace("T", " ");
13297
13731
  async function supportThreads(email, v) {
13298
13732
  const q = new URLSearchParams({ user: email, since: v.since ?? "30d", ...v.surface ? { surface: v.surface } : {} });
@@ -13312,7 +13746,7 @@ ${table(rows, ["last", "agent", "conversation", "title", "kind", "listed", "turn
13312
13746
  ${dim(t("Read one: ghosty conversations show <agent> <conversation> --why \xB7 times in CDMX \xB7 listed = in their app list", "Leer uno: ghosty conversations show <agente> <conversaci\xF3n> --why \xB7 horas CDMX \xB7 listed = sale en su lista de la app"))}`);
13313
13747
  }
13314
13748
  async function supportThread(agent, sid, v) {
13315
- const r = await api(`/api/v2/support/threads/${enc12(agent)}/${enc12(sid)}?tail=${enc12(v.limit ?? "40")}${v.tools ? "&tools=1" : ""}`);
13749
+ const r = await api(`/api/v2/support/threads/${enc13(agent)}/${enc13(sid)}?tail=${enc13(v.limit ?? "40")}${v.tools ? "&tools=1" : ""}`);
13316
13750
  out(r, () => {
13317
13751
  if (v.tools && r.tools) {
13318
13752
  const x = r.tools;
@@ -13344,7 +13778,7 @@ async function cleanupConversations(action, agent, v) {
13344
13778
  const ex = `ghosty conversations ${action} <agent> --kind ${action === "rm" ? "test" : "test|channel"} [--before 7d] [--dry-run]`;
13345
13779
  if (!agent) throw usage(t("Missing agent.", "Falta el agente."), ex);
13346
13780
  if (v.kind !== "test" && v.kind !== "channel") throw usage(t("--kind takes test or channel.", "--kind acepta test o channel."), ex);
13347
- const path = `/api/v2/agents/${enc12(agent)}/conversations/cleanup`;
13781
+ const path = `/api/v2/agents/${enc13(agent)}/conversations/cleanup`;
13348
13782
  const body = { kind: v.kind, action, ...v.before ? { before: v.before } : {} };
13349
13783
  const plan = await api(path, { method: "POST", body: { ...body, dryRun: true } });
13350
13784
  const verb = action === "rm" ? t("delete (rows + agent memory)", "borrar (filas + memoria del agente)") : t("archive (hidden; memory stays)", "archivar (se ocultan; la memoria se queda)");
@@ -13359,7 +13793,7 @@ ${t("Go ahead?", "\xBFSigo?")}`, { yes: v.yes, retry: `ghosty conversations ${ac
13359
13793
  out(r, () => `${lavender("\u2713")} ${t(`${r.threads} conversation(s) ${action === "rm" ? "deleted" : "archived"}.`, `${r.threads} conversaci\xF3n(es) ${action === "rm" ? "borradas" : "archivadas"}.`)}`);
13360
13794
  }
13361
13795
  async function turnStats(v) {
13362
- const r = await api(`/api/v2/support/turn-stats?since=${enc12(v.since ?? "14d")}${v.all ? "&all=1" : ""}`);
13796
+ const r = await api(`/api/v2/support/turn-stats?since=${enc13(v.since ?? "14d")}${v.all ? "&all=1" : ""}`);
13363
13797
  out(
13364
13798
  r,
13365
13799
  () => [
@@ -13379,7 +13813,7 @@ async function fleetBoxes() {
13379
13813
  ${table(rows, ["template", "boxes", "on", "asleep", "ram", "vcpu", "ram on"])}`);
13380
13814
  }
13381
13815
  async function appAgentsOf(email, client) {
13382
- const r = await api(`/api/v2/support/app-agents?user=${enc12(email)}${client && client !== "all" ? `&client=${enc12(client)}` : ""}`);
13816
+ const r = await api(`/api/v2/support/app-agents?user=${enc13(email)}${client && client !== "all" ? `&client=${enc13(client)}` : ""}`);
13383
13817
  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) : "" }));
13384
13818
  const phones = (r.devices ?? []).map((d) => `${d.platform} ${d.appVersion ?? ""}${d.appBuild ? ` (build ${d.appBuild})` : " (build ?)"} \xB7 ${t("seen", "visto")} ${cdmx(d.lastSeenAt)}`.replace(/\s+/g, " "));
13385
13819
  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"))}
@@ -13390,7 +13824,7 @@ ${dim(t("No phones registered for push.", "Sin tel\xE9fonos registrados para avi
13390
13824
  }
13391
13825
  async function pushLog(email, v) {
13392
13826
  if (!email) throw usage(t("Missing --user.", "Falta --user."), "ghosty push log --user ana@empresa.com [--since 2d]");
13393
- const r = await api(`/api/v2/support/push-log?user=${enc12(email)}&since=${enc12(v.since ?? "2d")}${v.limit ? `&limit=${enc12(v.limit)}` : ""}`);
13827
+ const r = await api(`/api/v2/support/push-log?user=${enc13(email)}&since=${enc13(v.since ?? "2d")}${v.limit ? `&limit=${enc13(v.limit)}` : ""}`);
13394
13828
  const rows = r.pushes.map((p) => ({ at: cdmx(p.at), kind: p.kind, to: p.platform, status: p.status === "sent" ? p.status : red(p.status), thread: p.threadKind ? `${p.threadId?.slice(0, 18)} (${p.threadKind})` : (p.threadId ?? "").slice(0, 24), title: (p.title ?? "").slice(0, 40), detail: p.detail ?? "" }));
13395
13829
  out(r, () => rows.length ? `${bold(r.user)}
13396
13830
  ${table(rows, ["at", "kind", "to", "status", "thread", "title", "detail"])}` : dim(t(`No pushes for ${r.user} in that range (logged since 2026-10-01).`, `Sin avisos para ${r.user} en ese rango (se registran desde el 1-oct).`)));
@@ -13428,24 +13862,24 @@ ${dim(t(`Try it on your phone: ghosty novedades test ${r.id}`, `Pru\xE9bala en t
13428
13862
  if (!id) throw usage(t("Missing id.", "Falta el id."), ex);
13429
13863
  const b = await fields();
13430
13864
  if (!Object.keys(b).length) throw usage(t("Nothing to change.", "Nada que cambiar."), ex);
13431
- const r = await api(`${base}/${enc12(id)}`, { method: "PATCH", body: b });
13865
+ const r = await api(`${base}/${enc13(id)}`, { method: "PATCH", body: b });
13432
13866
  return out(r, () => `${lavender("\u2713")} ${line2(r)}`);
13433
13867
  }
13434
13868
  case "test": {
13435
13869
  if (!id) throw usage(t("Missing id.", "Falta el id."), ex);
13436
- const r = await api(`${base}/${enc12(id)}`, { method: "POST", body: { push: "test" } });
13870
+ const r = await api(`${base}/${enc13(id)}`, { method: "POST", body: { push: "test" } });
13437
13871
  return out(r, () => `${lavender("\u2713")} ${t(`Sent to your phone (${r.enviados} device(s)).`, `Enviada a tu tel\xE9fono (${r.enviados} dispositivo(s)).`)}`);
13438
13872
  }
13439
13873
  case "send":
13440
13874
  case "schedule": {
13441
13875
  if (!id) throw usage(t("Missing id.", "Falta el id."), ex);
13442
13876
  if (v.at !== void 0) {
13443
- const r2 = await api(`${base}/${enc12(id)}`, { method: "PATCH", body: { schedule: v.at || null } });
13877
+ const r2 = await api(`${base}/${enc13(id)}`, { method: "PATCH", body: { schedule: v.at || null } });
13444
13878
  return out(r2, () => `${lavender("\u2713")} ${line2(r2)}`);
13445
13879
  }
13446
13880
  requireYesIfHeadless(v.yes, `ghosty novedades send ${id}`);
13447
13881
  await confirm(t("Push this announcement to every recipient now?", "\xBFMandar ya el push de esta novedad a todos sus destinatarios?"), { yes: v.yes, retry: `ghosty novedades send ${id}` });
13448
- const r = await api(`${base}/${enc12(id)}`, { method: "POST", body: { push: "all" } });
13882
+ const r = await api(`${base}/${enc13(id)}`, { method: "POST", body: { push: "all" } });
13449
13883
  return out(r, () => `${lavender("\u2713")} ${t(`Pushed to ${r.enviados} device(s).`, `Enviada a ${r.enviados} dispositivo(s).`)}`);
13450
13884
  }
13451
13885
  case "rm":
@@ -13453,7 +13887,7 @@ ${dim(t(`Try it on your phone: ghosty novedades test ${r.id}`, `Pru\xE9bala en t
13453
13887
  if (!id) throw usage(t("Missing id.", "Falta el id."), ex);
13454
13888
  requireYesIfHeadless(v.yes, `ghosty novedades rm ${id}`);
13455
13889
  await confirm(t(`Delete announcement ${id}?`, `\xBFBorrar la novedad ${id}?`), { yes: v.yes, retry: `ghosty novedades rm ${id}` });
13456
- return out(await api(`${base}/${enc12(id)}`, { method: "DELETE" }), () => `${lavender("\u2713")} ${t("Deleted.", "Borrada.")}`);
13890
+ return out(await api(`${base}/${enc13(id)}`, { method: "DELETE" }), () => `${lavender("\u2713")} ${t("Deleted.", "Borrada.")}`);
13457
13891
  }
13458
13892
  default:
13459
13893
  throw usage(t(`Unknown novedades subcommand: ${sub}.`, `Subcomando de novedades desconocido: ${sub}.`), ex);
@@ -13534,9 +13968,12 @@ ${bold("Workspaces")} ${dim("(changing them: Ghosty staff)")}
13534
13968
  spaces log <workspace> Its log: transfers, staff in/out, renewals (owner or staff) [--since 30d] [--action workspace]
13535
13969
  staff ls [staff-email] [client-email] Ghosty staff assigned to client accounts (staff) [--all: also revoked]
13536
13970
  staff assign|unassign <staff> <client> Staff sees and runs that account's agents, documents, databases and boards as themselves (no keys, billing or transfers; logged) [--reason]
13971
+ audit ls The audit log: who changed what (agent page, channels, pauses, replies, transfers, "log in as") [--workspace SLUG] [--agent ID] [--actor EMAIL] [--action A] [--since 24h] [--limit N]
13972
+ factory runs ls|show|rename A workspace's Software Factory runs: list, detail (plan, PR, preview, who is working), fix a title [--workspace SLUG] [--room R] [--status S]
13537
13973
 
13538
13974
  ${bold("Agents")}
13539
13975
  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)
13976
+ [--handle build [--workspace WS]] who answers to that @handle in Teams
13540
13977
  [--sponsor EMAIL | --owner EMAIL] any account's agents (Ghosty staff)
13541
13978
  agents create Create an agent --name [--engine] [--model] [--prompt | --prompt-file FILE] [--env K=V,\u2026]
13542
13979
  [--workspace SLUG] [--channels teams=off,\u2026] born in a workspace, active in its Teams
@@ -13552,6 +13989,7 @@ ${bold("Agents")}
13552
13989
  [--catalog productos.csv|json] its catalog: sku,nombre,precio[,unidad,categoria]
13553
13990
  [--toolsets crm,sales | +crm,-sales] gs tool groups it gets
13554
13991
  [--prompt-base AGENT|none --prompt-overlay FILE [--dry-run]] prompt = another agent's + edits, kept in sync
13992
+ [--prompt-patch overlay.json | --prompt-append FILE [--dry-run]] change one piece of the prompt, on the server's current one
13555
13993
  [--tool wa:tool=off,\u2026] [--bag NAME|none] [--avatar URL|FILE] a tool per channel \xB7 its token bag (staff) \xB7 its face in Teams
13556
13994
  [--prompt-mode append|replace] replace = its prompt WITHOUT Ghosty's house persona
13557
13995
  agents doctor <id> Check the config: overlapping skills, tools the prompt names, broken grants, failing turns
@@ -13568,6 +14006,8 @@ ${bold("Agents")}
13568
14006
  agents box <id> The machine serving it: is it on the new image? [--check PATH]\u2026 [--contains PATH=TEXT]\u2026 [--recycle [--reenv] [--force]]
13569
14007
  agents demo enable|disable <id> Turn its public demo link on or off
13570
14008
  agents demo <id> Public demo link /p/<slug>: anyone tries it without an account, "Keep it" clones it to theirs
14009
+ agents embed enable|disable <id> Web widget: a chat bubble on YOUR site (one <script>), only on the domains you list [--domains a.com,*.b.com] [--per-visitor N] [--per-ip N] [--welcome T] [--color #hex] [--rotate]
14010
+ agents embed <id> Its state and the snippet to paste (claude engine only: it isolates each visitor)
13571
14011
  [--slug NAME] [--expires "YYYY-MM-DDTHH:MM"|no] [--turns N] [--per-ip N] [--welcome TEXT] [--chips "a|b|c"] [--starter TEXT] [--sponsor on|off] [--rotate]
13572
14012
 
13573
14013
  ${bold("Configure an agent")}
@@ -13581,7 +14021,8 @@ ${bold("Configure an agent")}
13581
14021
  dbs photos put <id> <db> --table T --key-column K --dir DIR Loose photos to a catalog (SKU-123.jpg \u2192 its row), permanent links [--column C] [--dry-run]
13582
14022
  mcp get|set <id> MCP servers [--file servers.json] (http headers accept \${turn.token})
13583
14023
  channels whatsapp ls|enable|disable <id> [number] Which WhatsApp Business numbers it answers [--dry-run]
13584
- channels whatsapp integrations <id> Its owner's WhatsApp Business numbers in Formmy (number, who answers today, Integration id)
14024
+ 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]
14025
+ channels messenger ls <id> The workspace's Messenger pages: who answers now and who answered last [--orphans]
13585
14026
  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]
13586
14027
  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]
13587
14028
  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]
@@ -13592,6 +14033,9 @@ ${bold("Configure an agent")}
13592
14033
  cards ls <id> Recent cards on its sales board [--q TEXT] [--paused] [--limit 20]
13593
14034
  cards show <id> <card> A card's thread: who wrote, text, attachments and whether gs kept a copy (folio, phone or id)
13594
14035
  cards archive <id> Archive cards in bulk (reversible) --integration X | --column X | --before 30d [--dry-run]
14036
+ cards pause|resume <id> <card> Pause the agent in a conversation, or resume it (also lifts "operator only") [--mode 30min|2h|24h|until_tomorrow|permanent]
14037
+ cards reply <id> <card> The agent answers what the customer left pending ("Ask for a reply") [--directive TEXT] [--yes]
14038
+ cards pending <id> Conversations whose last message is the customer's, and why some can't be answered [--since 2d|ISO] [--reply [--directive] [--yes]] [--dry-run]
13595
14039
 
13596
14040
  ${bold("Support")} ${dim("(Ghosty staff)")}
13597
14041
  conversations ls --user EMAIL Someone's conversations in any agent [--since 30d] [--surface android]
@@ -13682,9 +14126,12 @@ ${bold("Espacios")} ${dim("(cambiarlos: staff de Ghosty)")}
13682
14126
  spaces log <espacio> Su bit\xE1cora: traspasos, staff que entra o sale, renovaciones (due\xF1o o staff) [--since 30d] [--action workspace]
13683
14127
  staff ls [correo-staff] [correo-cliente] Staff de Ghosty asignado a cuentas cliente (staff) [--all: tambi\xE9n revocadas]
13684
14128
  staff assign|unassign <staff> <cliente> El staff ve y opera agentes, documentos, bases y tableros de esa cuenta como s\xED mismo (sin llaves, cobro ni traspasos; queda en bit\xE1cora) [--reason]
14129
+ audit ls La bit\xE1cora: qui\xE9n cambi\xF3 qu\xE9 (ficha, canales, pausas, respuestas, traspasos, \xABentrar como\xBB) [--workspace SLUG] [--agent ID] [--actor CORREO] [--action A] [--since 24h] [--limit N]
14130
+ factory runs ls|show|rename Los pedidos de la Software Factory de un espacio: lista, detalle (plan, PR, preview, qui\xE9n trabaja), corregir un t\xEDtulo [--workspace SLUG] [--room R] [--status S]
13685
14131
 
13686
14132
  ${bold("Agentes")}
13687
14133
  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)
14134
+ [--handle build [--workspace WS]] qui\xE9n contesta a ese @handle en Teams
13688
14135
  [--sponsor CORREO | --owner CORREO] agentes de cualquier cuenta (staff de Ghosty)
13689
14136
  agents create Crea un agente --name [--engine] [--model] [--prompt | --prompt-file ARCHIVO] [--env K=V,\u2026]
13690
14137
  [--workspace SLUG] [--channels teams=off,\u2026] nace en un espacio, activo en su Teams
@@ -13700,6 +14147,7 @@ ${bold("Agentes")}
13700
14147
  [--catalog productos.csv|json] su cat\xE1logo: sku,nombre,precio[,unidad,categoria]
13701
14148
  [--toolsets crm,sales | +crm,-sales] grupos de tools de gs que recibe
13702
14149
  [--prompt-base AGENTE|none --prompt-overlay ARCHIVO [--dry-run]] prompt = el de otro agente + ediciones, sincronizado
14150
+ [--prompt-patch overlay.json | --prompt-append ARCHIVO [--dry-run]] cambia un pedazo del prompt, sobre el actual del servidor
13703
14151
  [--tool wa:tool=off,\u2026] [--bag NOMBRE|none] [--avatar URL|ARCHIVO] una tool por canal \xB7 su bolsa de tokens (staff) \xB7 su cara en Teams
13704
14152
  [--prompt-mode append|replace] replace = su prompt SIN la persona de casa de Ghosty
13705
14153
  agents doctor <id> Revisa la configuraci\xF3n: skills encimadas, tools que nombra el prompt, permisos rotos, turnos que fallan
@@ -13716,6 +14164,8 @@ ${bold("Agentes")}
13716
14164
  agents box <id> La m\xE1quina que lo atiende: \xBFya trae la imagen nueva? [--check RUTA]\u2026 [--contains RUTA=TEXTO]\u2026 [--recycle [--reenv] [--force]]
13717
14165
  agents demo enable|disable <id> Prende o apaga su liga p\xFAblica de demo
13718
14166
  agents demo <id> Liga p\xFAblica de demo /p/<slug>: cualquiera lo prueba sin cuenta y \xABQued\xE1rmelo\xBB lo clona a la suya
14167
+ agents embed enable|disable <id> Widget web: burbuja de chat en TU sitio (un <script>), s\xF3lo en los dominios que pongas [--domains a.com,*.b.com] [--per-visitor N] [--per-ip N] [--welcome T] [--color #hex] [--rotate]
14168
+ agents embed <id> Su estado y el snippet para pegar (s\xF3lo motor claude: a\xEDsla a cada visitante)
13719
14169
  [--slug NOMBRE] [--expires "AAAA-MM-DDTHH:MM"|no] [--turns N] [--per-ip N] [--welcome TEXTO] [--chips "a|b|c"] [--starter TEXTO] [--sponsor on|off] [--rotate]
13720
14170
 
13721
14171
  ${bold("Configurar un agente")}
@@ -13729,7 +14179,8 @@ ${bold("Configurar un agente")}
13729
14179
  dbs photos put <id> <db> --table T --key-column K --dir DIR Fotos sueltas a un cat\xE1logo (SKU-123.jpg \u2192 su fila), ligas permanentes [--column C] [--dry-run]
13730
14180
  mcp get|set <id> Servidores MCP [--file servers.json] (headers http admiten \${turn.token})
13731
14181
  channels whatsapp ls|enable|disable <id> [n\xFAmero] Qu\xE9 n\xFAmeros de WhatsApp Business contesta [--dry-run]
13732
- 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)
14182
+ 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]
14183
+ channels messenger ls <id> Las p\xE1ginas de Messenger del espacio: qui\xE9n contesta hoy y qui\xE9n contest\xF3 al \xFAltimo [--orphans]
13733
14184
  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]
13734
14185
  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]
13735
14186
  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]
@@ -13740,6 +14191,9 @@ ${bold("Configurar un agente")}
13740
14191
  cards ls <id> Tarjetas recientes de su tablero de ventas [--q TEXTO] [--paused] [--limit 20]
13741
14192
  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)
13742
14193
  cards archive <id> Archiva tarjetas en bloque (reversible) --integration X | --column X | --before 30d [--dry-run]
14194
+ cards pause|resume <id> <tarjeta> Pausa al agente en una conversaci\xF3n, o lo reanuda (tambi\xE9n quita \xABs\xF3lo operador\xBB) [--mode 30min|2h|24h|until_tomorrow|permanent]
14195
+ cards reply <id> <tarjeta> El agente contesta lo que el cliente dej\xF3 pendiente (\xABPedir respuesta\xBB) [--directive TEXTO] [--yes]
14196
+ cards pending <id> Conversaciones cuyo \xFAltimo mensaje es del cliente, y por qu\xE9 algunas no se contestan [--since 2d|ISO] [--reply [--directive] [--yes]] [--dry-run]
13743
14197
 
13744
14198
  ${bold("Soporte")} ${dim("(staff de Ghosty)")}
13745
14199
  conversations ls --user CORREO Las conversaciones de alguien en cualquier agente [--since 30d] [--surface android]
@@ -13804,8 +14258,8 @@ ${bold("Opciones globales")}
13804
14258
  ${bold("C\xF3digos de salida")} 0 bien \xB7 1 error de la API \xB7 2 uso incorrecto \xB7 3 sin sesi\xF3n
13805
14259
  ${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.")}
13806
14260
  ${dim("Documentaci\xF3n: https://www.ghosty.studio/docs/cli")}`;
13807
- var COMMANDS = ["login", "logout", "whoami", "usage", "use", "apply", "plan", "engines", "bags", "spaces", "workspaces", "staff", "demos", "sites", "push", "novedades", "completion", "cards", "agents", "files", "skills", "mcp", "dbs", "turns", "try", "chat", "conversations", "schedule", "partner", "me", "channels", "board", "boards", "credentials", "connectors", "browser", "docs", "help"];
13808
- var AGENTS_SUBS = ["ls", "create", "get", "set", "export", "doctor", "docs", "share", "unshare", "shares", "teams", "rm", "restart", "demo", "box"];
14261
+ var COMMANDS = ["login", "logout", "whoami", "usage", "use", "apply", "plan", "engines", "bags", "spaces", "workspaces", "staff", "audit", "factory", "demos", "sites", "push", "novedades", "completion", "cards", "agents", "files", "skills", "mcp", "dbs", "turns", "try", "chat", "conversations", "schedule", "partner", "me", "channels", "board", "boards", "credentials", "connectors", "browser", "docs", "help"];
14262
+ var AGENTS_SUBS = ["ls", "create", "get", "set", "export", "doctor", "docs", "share", "unshare", "shares", "teams", "rm", "restart", "demo", "embed", "box"];
13809
14263
  function unknown(what, input, known, helpCmd) {
13810
14264
  const s = suggest(input, known);
13811
14265
  return usage(t(`Unknown command: ${what}.`, `Comando desconocido: ${what}.`), s ? t(`Did you mean "${s}"? Run: ${helpCmd}`, `\xBFQuisiste decir "${s}"? Corre: ${helpCmd}`) : t(`Run: ${helpCmd}`, `Corre: ${helpCmd}`));
@@ -13887,6 +14341,9 @@ async function main() {
13887
14341
  turns: { type: "string" },
13888
14342
  vence: { type: "string" },
13889
14343
  "per-ip": { type: "string" },
14344
+ "per-visitor": { type: "string" },
14345
+ domains: { type: "string" },
14346
+ color: { type: "string" },
13890
14347
  welcome: { type: "string" },
13891
14348
  chips: { type: "string" },
13892
14349
  starter: { type: "string" },
@@ -13940,6 +14397,13 @@ async function main() {
13940
14397
  toolsets: { type: "string" },
13941
14398
  "prompt-base": { type: "string" },
13942
14399
  "prompt-overlay": { type: "string" },
14400
+ "prompt-patch": { type: "string" },
14401
+ "prompt-append": { type: "string" },
14402
+ mode: { type: "string" },
14403
+ directive: { type: "string" },
14404
+ reply: { type: "boolean" },
14405
+ actor: { type: "string" },
14406
+ room: { type: "string" },
13943
14407
  url: { type: "string" },
13944
14408
  description: { type: "string" },
13945
14409
  grant: { type: "string" },
@@ -14036,7 +14500,7 @@ async function main() {
14036
14500
  const tokens = splitTokens(pos, v);
14037
14501
  globals.token = tokens.session;
14038
14502
  globals.profile = v.profile;
14039
- if (v.version) return out({ version: "0.49.0" }, () => "0.49.0");
14503
+ if (v.version) return out({ version: "0.51.0" }, () => "0.51.0");
14040
14504
  for (const n of normalizeArgs(pos, v)) if (!globals.json) info(dim(n));
14041
14505
  const context = (await readConfig()).workspace;
14042
14506
  if (!explicitToken()?.startsWith("gat_")) {
@@ -14084,6 +14548,7 @@ async function main() {
14084
14548
  return applyCmd(sub, v);
14085
14549
  case "agents":
14086
14550
  if ((sub === "ls" || sub === "list" || !sub) && v.user && v.app !== void 0) return appAgentsOf(v.user, v.app);
14551
+ if ((sub === "ls" || sub === "list" || !sub) && v.handle) return agentsByHandle(v.handle, v.workspace ?? context);
14087
14552
  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 });
14088
14553
  if (sub === "create" || sub === "new") {
14089
14554
  if (v.workspace === "none") delete v.workspace;
@@ -14103,6 +14568,7 @@ async function main() {
14103
14568
  if (sub === "docs" || sub === "documents") return agentDocsCmd(a1, a2, pos.slice(4).filter((x) => x !== void 0), v);
14104
14569
  if (sub === "restart") return agentsRestart(a1);
14105
14570
  if (sub === "demo") return agentsDemo(a1, v);
14571
+ if (sub === "embed" || sub === "widget") return agentsEmbed(a1, v);
14106
14572
  if (sub === "box") return agentsBox(a1, v);
14107
14573
  if (sub === "export") return agentsExport(a1, v);
14108
14574
  throw unknown(`agents ${sub}`, sub, AGENTS_SUBS, "ghosty agents --help");
@@ -14179,6 +14645,10 @@ async function main() {
14179
14645
  return spacesCmd(sub, a1, a2, v, context);
14180
14646
  case "staff":
14181
14647
  return staffCmd(sub, a1, a2, v);
14648
+ case "audit":
14649
+ return auditCmd(sub, v, context);
14650
+ case "factory":
14651
+ return factoryCmd(sub, a1, a2, pos[4], v, context);
14182
14652
  case "plan":
14183
14653
  return planCmd(sub, a1, v, context);
14184
14654
  case "me":
@@ -14196,7 +14666,7 @@ async function main() {
14196
14666
  throw unknown(cmd, cmd, COMMANDS, "ghosty --help");
14197
14667
  }
14198
14668
  }
14199
- var notice = updateNotice("0.49.0").catch(() => null);
14669
+ var notice = updateNotice("0.51.0").catch(() => null);
14200
14670
  main().then(
14201
14671
  async () => {
14202
14672
  const n = await notice;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghostystudio/cli",
3
- "version": "0.49.0",
3
+ "version": "0.51.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": {
@@ -16,7 +16,7 @@
16
16
  "scripts": {
17
17
  "build": "esbuild src/index.ts --bundle --platform=node --format=esm --target=node22 --outfile=dist/index.js --banner:js='#!/usr/bin/env node\nimport { createRequire as __cr } from \"node:module\"; const require = __cr(import.meta.url);' --define:__VERSION__=\\\"$npm_package_version\\\"",
18
18
  "prepack": "npm run build",
19
- "test": "for t in flags prompt api rows wa-auth resolve aliases i18n help completion photos channels-retry spaces sites; do esbuild src/$t.test.ts --bundle --platform=node --format=esm --outfile=/tmp/ghosty-cli-$t.test.mjs --log-level=error || exit 1; done && node --test /tmp/ghosty-cli-*.test.mjs"
19
+ "test": "for t in flags prompt api rows wa-auth resolve aliases i18n help completion photos channels-retry spaces sites diff cards; do esbuild src/$t.test.ts --bundle --platform=node --format=esm --outfile=/tmp/ghosty-cli-$t.test.mjs --log-level=error || exit 1; done && node --test /tmp/ghosty-cli-*.test.mjs"
20
20
  },
21
21
  "keywords": [
22
22
  "ghosty",