@ghostystudio/cli 0.58.0 → 0.58.1

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 +5 -1
  2. package/dist/index.js +87 -29
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -84,7 +84,7 @@ ghosty schedule add|ls|rm <id> … --teams <workspace>#<room>[/<thread-id>] #
84
84
 
85
85
  ghosty me files ls|get|export|upload|rm [--q TEXT] [--kind image|video|audio|document|other] [--cursor C]
86
86
  ghosty me memories ls|add|edit|rm
87
- ghosty conversations archive|rm <id> --kind test|channel [--before 7d] [--dry-run]
87
+ ghosty conversations archive|rm <id> --kind test|channel|personal [--before 7d] [--dry-run]
88
88
  ghosty agents docs dedupe [--dry-run] # your account: merge documents with the same content
89
89
  # Ghosty staff
90
90
  ghosty conversations ls --user EMAIL [--since 30d] · conversations show <id> <conv> --why|--tools
@@ -109,6 +109,10 @@ Every command has its own help with examples: `ghosty <command> --help` or `ghos
109
109
 
110
110
  Exit codes: `0` ok · `1` API error · `2` usage error · `3` not signed in.
111
111
 
112
+ ### Changes in 0.58.1
113
+
114
+ - `agents docs add|replace --file` sends the file type from its extension. Before, the server stored every CLI upload as `application/octet-stream`, and a PDF reached WhatsApp customers as `.bin`.
115
+
112
116
  ### Changes in 0.57.1 (0.57.0 + the 0.56.1 factory fix)
113
117
 
114
118
  - `sites pin <slug>` / `sites unpin <slug>` / `sites pins [--cancel|--resume]`: «Pinned sites» — $49 USD/yr or $5 USD/mo ($990 / $99 MXN), includes up to 3 sites per pack; a pinned site never pauses and has no banner. `--interval year|month`, `--currency usd|mxn`, `--add-pack`.
package/dist/index.js CHANGED
@@ -7863,11 +7863,11 @@ var SERVER_MESSAGES = [
7863
7863
  ["la caja ACP {} arranc\xF3 en error: {}", "ACP box {} started in error: {}"],
7864
7864
  ["la caja ACP {} no lleg\xF3 a running en 90s", "ACP box {} didn't reach running in 90s"],
7865
7865
  ["la caja {} no lleg\xF3 a running (\xFAltimo estado: {})", "box {} didn't reach running (last state: {})"],
7866
+ ["la caja {} no arranc\xF3: {}", "box {} failed to boot: {}"],
7866
7867
  ["la caja del agente naci\xF3 muda (no arranc\xF3 {}-runtime): {}", "the agent's box came up mute ({}-runtime didn't start): {}"],
7867
7868
  ["no se pudo re-rutear tras caja muerta", "couldn't re-route after a dead box"],
7868
7869
  ["fleet route race sin ganador", "fleet route race with no winner"],
7869
7870
  ["create: respuesta sin sandboxId", "create: response without sandboxId"],
7870
- ["warmpool.claim: respuesta sin sandboxId", "warmpool.claim: response without sandboxId"],
7871
7871
  ["expose: respuesta sin url", "expose: response without url"],
7872
7872
  ["expose-raw: respuesta sin hostPort", "expose-raw: response without hostPort"],
7873
7873
  // — Cajas compartidas: voz, render, video, ortografía, eventos
@@ -8468,7 +8468,7 @@ async function postLong(path, body, timeoutMs = 9e5) {
8468
8468
  const req = httpsRequest(`${base}${path}`, {
8469
8469
  method: "POST",
8470
8470
  timeout: timeoutMs,
8471
- headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.58.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
8471
+ headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.58.1"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
8472
8472
  }, (res) => {
8473
8473
  let buf = "";
8474
8474
  res.on("data", (c) => buf += c);
@@ -8510,7 +8510,7 @@ async function request(path, opts = {}, retried = false) {
8510
8510
  method: opts.method ?? (opts.body !== void 0 || opts.raw !== void 0 ? "POST" : "GET"),
8511
8511
  headers: {
8512
8512
  authorization: `Bearer ${token}`,
8513
- "user-agent": `ghosty-cli/${"0.58.0"}`,
8513
+ "user-agent": `ghosty-cli/${"0.58.1"}`,
8514
8514
  "accept-language": lang(),
8515
8515
  ...opts.body !== void 0 ? { "content-type": "application/json" } : {},
8516
8516
  ...opts.headers
@@ -9095,7 +9095,7 @@ ${dim(t(`only ${workspace} (ghosty use) \xB7 all: --all`, `s\xF3lo ${workspace}
9095
9095
  function parseEnv(raw) {
9096
9096
  if (!raw) return void 0;
9097
9097
  const env = {};
9098
- for (const pair of raw.split(",")) {
9098
+ for (const pair of raw.split(/,(?=[A-Za-z_][A-Za-z0-9_]*=)/)) {
9099
9099
  const i = pair.indexOf("=");
9100
9100
  if (i < 1) throw usage(t(`Bad --env entry: ${pair}.`, `Entrada de --env inv\xE1lida: ${pair}.`), t("Example", "Ejemplo") + ": --env GHOSTY_THINKING_EFFORT=max");
9101
9101
  env[pair.slice(0, i).trim()] = pair.slice(i + 1);
@@ -10305,6 +10305,7 @@ ${bold("Partner space")} ${dim("(you, signed in as the space owner)")}
10305
10305
  partner credentials rotate <keyId> [--grace MIN] New secret, same keyId (old one valid MIN, default 60)
10306
10306
  partner credentials revoke <keyId> Revoke it (idempotent)
10307
10307
  partner audit Who did what, when
10308
+ partner usage [--tenant ID] [--from YYYY-MM-DD] [--to YYYY-MM-DD] Turns, tokens, cost and errors per business (30 days by default)
10308
10309
 
10309
10310
  ${bold("Shared agents (solo-MCP)")} ${dim("(--agent SLUG picks another agent of the partner: nik-admin, nik-public\u2026)")}
10310
10311
  partner agent get [--agent SLUG] The agent template and its version
@@ -10350,6 +10351,7 @@ ${bold("Espacio de partner")} ${dim("(t\xFA, con sesi\xF3n como due\xF1a del es
10350
10351
  partner credentials rotate <keyId> [--grace MIN] Secreto nuevo, mismo keyId (el viejo vale MIN, default 60)
10351
10352
  partner credentials revoke <keyId> La revoca (idempotente)
10352
10353
  partner audit Qui\xE9n hizo qu\xE9 y cu\xE1ndo
10354
+ partner usage [--tenant ID] [--from AAAA-MM-DD] [--to AAAA-MM-DD] Turnos, tokens, costo y fallas por negocio (30 d\xEDas por default)
10353
10355
 
10354
10356
  ${bold("Agentes compartidos (solo-MCP)")} ${dim("(--agent SLUG elige otro agente del partner: nik-admin, nik-public\u2026)")}
10355
10357
  partner agent get [--agent SLUG] La plantilla del agente y su versi\xF3n
@@ -10406,7 +10408,7 @@ async function call(path, v, init = {}) {
10406
10408
  "x-ghosty-ts": ts,
10407
10409
  "x-ghosty-sig": sig,
10408
10410
  "content-type": "application/json",
10409
- "user-agent": `ghosty-cli/${"0.58.0"}`
10411
+ "user-agent": `ghosty-cli/${"0.58.1"}`
10410
10412
  },
10411
10413
  body: method === "GET" ? void 0 : raw
10412
10414
  });
@@ -10649,6 +10651,29 @@ async function partnerCmd(sub, a1, a2, v, a3) {
10649
10651
  const r = await json("/api/v2/partner/audit", v, { ownerOnly: true });
10650
10652
  return out(r, () => table(r.events.map((e) => ({ when: fmt(e.ts), action: e.action, externalId: e.externalId, actor: e.actor })), ["when", "action", "externalId", "actor"]));
10651
10653
  }
10654
+ case "usage": {
10655
+ const q = new URLSearchParams();
10656
+ if (v.tenant) q.set("externalId", v.tenant);
10657
+ if (v.from) q.set("from", v.from);
10658
+ if (v.to) q.set("to", v.to);
10659
+ const r = await json(
10660
+ `/api/v2/partner/usage${q.size ? `?${q}` : ""}`,
10661
+ v
10662
+ );
10663
+ return out(
10664
+ r,
10665
+ () => r.tenants.length ? table(
10666
+ r.tenants.map((x) => ({
10667
+ tenant: x.name ? `${x.name} (${x.externalId})` : x.externalId,
10668
+ turns: x.turns,
10669
+ tokens: x.tokens,
10670
+ usd: x.costUsd.toFixed(4),
10671
+ errors: Object.entries(x.errors).map(([k, n]) => `${k}\xD7${n}`).join(" ") || "\u2014"
10672
+ })),
10673
+ ["tenant", "turns", "tokens", "usd", "errors"]
10674
+ ) : dim(t("No usage in that range.", "Sin consumo en ese rango."))
10675
+ );
10676
+ }
10652
10677
  case "try":
10653
10678
  return tryCmd(a1, a2, v);
10654
10679
  case "wa":
@@ -10986,7 +11011,7 @@ async function agentDocsCmd(sub, id, docs, v) {
10986
11011
  if (!v.url === !v.file) throw usage(t("Use --url URL or --file PATH (one of them).", "Usa --url URL o --file RUTA (uno de los dos)."), t("Example", "Ejemplo") + ': ghosty agents docs add <agent-id> --url https://\u2026/lista.pdf --description "Precios 2026"');
10987
11012
  if (v.grant && !["read", "write", "no"].includes(v.grant)) throw usage(t("--grant takes read, write or no.", "--grant acepta read, write o no."));
10988
11013
  const body = {
10989
- ...v.url ? { url: v.url } : { contentBase64: (await readFile6(v.file)).toString("base64"), name: v.name ?? basename3(v.file) },
11014
+ ...v.url ? { url: v.url } : { contentBase64: (await readFile6(v.file)).toString("base64"), name: v.name ?? basename3(v.file), ...mimeField(v.file) },
10990
11015
  ...v.url && v.name ? { name: v.name } : {},
10991
11016
  ...v.description ? { description: v.description } : {},
10992
11017
  grant: v.grant === "no" ? false : v.grant === "write" || v.write ? "write" : "read",
@@ -11005,7 +11030,7 @@ async function agentDocsCmd(sub, id, docs, v) {
11005
11030
  doc = need2(doc, t("document (id or exact name)", "el documento (id o nombre exacto)"), ex);
11006
11031
  if (!v.url === !v.file) throw usage(t("Use --url URL or --file PATH (one of them).", "Usa --url URL o --file RUTA (uno de los dos)."), t("Example", "Ejemplo") + ": " + ex);
11007
11032
  const body = {
11008
- ...v.url ? { url: v.url } : { contentBase64: (await readFile6(v.file)).toString("base64") },
11033
+ ...v.url ? { url: v.url } : { contentBase64: (await readFile6(v.file)).toString("base64"), ...mimeField(v.file) },
11009
11034
  ...v.note ? { note: v.note } : {}
11010
11035
  };
11011
11036
  const r = await api(agentPath2(id, `/documents/${enc6(doc)}/versions`), { method: "POST", body });
@@ -11121,6 +11146,33 @@ async function turnsGrep(id, text, v) {
11121
11146
  out(r, () => rows.length ? `${table(rows, ["at", "who", "turn", "state", "session", "text"])}
11122
11147
  ${dim(`${t("open one", "abre uno")}: ghosty turns show ${id} <turn>`)}` : dim(t(`Nothing says \xAB${text}\xBB${v.since ? ` since ${v.since}` : ""}.`, `Nada dice \xAB${text}\xBB${v.since ? ` desde ${v.since}` : ""}.`)));
11123
11148
  }
11149
+ function mimeField(path) {
11150
+ const types = {
11151
+ pdf: "application/pdf",
11152
+ png: "image/png",
11153
+ jpg: "image/jpeg",
11154
+ jpeg: "image/jpeg",
11155
+ gif: "image/gif",
11156
+ webp: "image/webp",
11157
+ mp3: "audio/mpeg",
11158
+ ogg: "audio/ogg",
11159
+ wav: "audio/wav",
11160
+ m4a: "audio/mp4",
11161
+ mp4: "video/mp4",
11162
+ mov: "video/quicktime",
11163
+ csv: "text/csv",
11164
+ md: "text/markdown",
11165
+ txt: "text/plain",
11166
+ html: "text/html",
11167
+ json: "application/json",
11168
+ zip: "application/zip",
11169
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
11170
+ docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
11171
+ pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation"
11172
+ };
11173
+ const mime = types[path.split(".").pop()?.toLowerCase() ?? ""];
11174
+ return mime ? { mime } : {};
11175
+ }
11124
11176
  async function turnShow(id, turnId, full = false) {
11125
11177
  id = need2(id, t("agent id", "el id del agente"), "ghosty turns show <agent-id> [turn-id|last] [--full]");
11126
11178
  const r = await api(agentPath2(id, `/turns/${enc6(turnId ?? "last")}`));
@@ -11489,9 +11541,10 @@ async function channelsCmd(kind, sub, id, ref, v) {
11489
11541
  answers: n2.mine ? lavender(t("this agent", "este agente")) : n2.recordsOnly ? t("this agent (records only)", "este agente (s\xF3lo registra)") : n2.takenByOther ? t("another agent", "otro agente") : dim(t("nobody (silent)", "nadie (en silencio)")),
11490
11542
  "formmy forwards": fwd(n2),
11491
11543
  "ads only": n2.adsOnly ? lavender(t("yes", "s\xED")) : "",
11544
+ board: n2.board ? n2.board.name : dim(t("the agent's", "el del agente")),
11492
11545
  id: n2.id
11493
11546
  }));
11494
- return out(cur, () => rows.length ? table(rows, ["number", "answers", "formmy forwards", "ads only", "id"]) : dim(t(`No WhatsApp Business numbers yet. Pair one at ${cur.salesUrl}`, `Todav\xEDa no hay n\xFAmeros de WhatsApp Business. Parea uno en ${cur.salesUrl}`)));
11547
+ return out(cur, () => rows.length ? table(rows, ["number", "answers", "formmy forwards", "ads only", "board", "id"]) : dim(t(`No WhatsApp Business numbers yet. Pair one at ${cur.salesUrl}`, `Todav\xEDa no hay n\xFAmeros de WhatsApp Business. Parea uno en ${cur.salesUrl}`)));
11495
11548
  }
11496
11549
  if (sub !== "enable" && sub !== "disable") throw usage(t(`Unknown subcommand: ${sub}.`, `Subcomando desconocido: ${sub}.`), t("Try", "Prueba") + ": ghosty channels whatsapp ls | enable | disable | integrations | link | unlink | history \xB7 ghosty channels wa import | status | pause | groups");
11497
11550
  if (!ref) throw usage(t("Missing number.", "Falta el n\xFAmero."), t("Example", "Ejemplo") + `: ghosty channels whatsapp ${sub} ${id} +52 55 1234 5678`);
@@ -11654,19 +11707,21 @@ async function linkCmd(sub, id, v) {
11654
11707
  if (!id || !v.integration) throw usage(t("Missing agent id or --integration.", "Falta el id del agente o --integration."), t("Example", "Ejemplo") + `: ghosty channels whatsapp ${sub} <agent-id> --integration <formmy-integration-id> --answer off --dry-run`);
11655
11708
  if (v["ads-only"] !== void 0 && v["ads-only"] !== "on" && v["ads-only"] !== "off") throw usage(t("--ads-only takes on or off.", "--ads-only acepta on u off."));
11656
11709
  const adsOnly = v["ads-only"] === void 0 ? void 0 : v["ads-only"] === "on";
11657
- if (sub === "link" && adsOnly !== void 0 && v.answer === void 0) {
11710
+ const board = v.board === void 0 ? void 0 : /^(agent|agente)$/i.test(v.board) ? null : v.board;
11711
+ const boardFlag = v.board !== void 0 ? ` --board ${JSON.stringify(v.board)}` : "";
11712
+ if (sub === "link" && (adsOnly !== void 0 || board !== void 0) && v.answer === void 0) {
11658
11713
  const path2 = `/api/v2/agents/${enc7(id)}/whatsapp/link`;
11659
- const r2 = await api(path2, { method: "POST", body: { integrationId: v.integration, adsOnly, ...v["dry-run"] ? { dryRun: true } : {} } });
11714
+ const r2 = await api(path2, { method: "POST", body: { integrationId: v.integration, ...adsOnly !== void 0 ? { adsOnly } : {}, ...board !== void 0 ? { board } : {}, ...v["dry-run"] ? { dryRun: true } : {} } });
11660
11715
  return out(r2, () => `${r2.plan.map((x) => serverText(x)).join("\n")}${v["dry-run"] ? `
11661
- ${dim(applyHint(`ghosty channels whatsapp link ${id} --integration ${v.integration} --ads-only ${v["ads-only"]}`))}` : ""}`);
11716
+ ${dim(applyHint(`ghosty channels whatsapp link ${id} --integration ${v.integration}${v["ads-only"] ? ` --ads-only ${v["ads-only"]}` : ""}${boardFlag}`))}` : ""}`);
11662
11717
  }
11663
11718
  if (sub === "link" && v.answer !== "on" && v.answer !== "off") throw usage(t("Missing --answer on|off.", "Falta --answer on|off."), t("on = this agent answers real customers \xB7 off = it only records (add --forward so gs still sees the conversations)", "on = este agente contesta a clientes reales \xB7 off = s\xF3lo registra (agrega --forward para que gs siga viendo las conversaciones)"));
11664
11719
  const path = `/api/v2/agents/${enc7(id)}/whatsapp/link`;
11665
11720
  if (v["partner-agent"] && !v["partner-tenant"]) throw usage(t("--partner-agent needs --partner-tenant <externalId>.", "--partner-agent necesita --partner-tenant <externalId>."));
11666
11721
  const partner = v["partner-tenant"] ? { partnerExternalId: v["partner-tenant"], ...v["partner-agent"] ? { partnerAgentSlug: v["partner-agent"] } : {} } : {};
11667
- const body = { integrationId: v.integration, ...sub === "unlink" ? { unlink: true } : { answer: v.answer === "on", ...v.forward ? { forward: true } : {}, ...adsOnly !== void 0 ? { adsOnly } : {}, ...partner } };
11722
+ const body = { integrationId: v.integration, ...sub === "unlink" ? { unlink: true } : { answer: v.answer === "on", ...v.forward ? { forward: true } : {}, ...adsOnly !== void 0 ? { adsOnly } : {}, ...board !== void 0 ? { board } : {}, ...partner } };
11668
11723
  const partnerFlags = v["partner-tenant"] ? ` --partner-tenant ${v["partner-tenant"]}${v["partner-agent"] ? ` --partner-agent ${v["partner-agent"]}` : ""}` : "";
11669
- const cmd = `ghosty channels whatsapp ${sub} ${id} --integration ${v.integration}${sub === "link" ? ` --answer ${v.answer}${v.forward ? " --forward" : ""}${v["ads-only"] ? ` --ads-only ${v["ads-only"]}` : ""}${partnerFlags}` : ""}`;
11724
+ const cmd = `ghosty channels whatsapp ${sub} ${id} --integration ${v.integration}${sub === "link" ? ` --answer ${v.answer}${v.forward ? " --forward" : ""}${v["ads-only"] ? ` --ads-only ${v["ads-only"]}` : ""}${boardFlag}${partnerFlags}` : ""}`;
11670
11725
  const plan = await api(path, { method: "POST", body: { ...body, dryRun: true } });
11671
11726
  const text = `${plan.plan.map((x) => serverText(x)).join("\n")}
11672
11727
  ${dim(`Formmy: ${plan.formmy.before.url ?? "null"} \u2192 ${plan.formmy.after.url ?? "null"} (${t("as", "como")} ${plan.formmy.by})`)}`;
@@ -12041,7 +12096,7 @@ var HELPS_ES = {
12041
12096
  ["--engine E --dry-run", "set: qu\xE9 hace cambiar de motor (sesiones que se olvidan, caja que se levanta y cu\xE1l se destruye, si est\xE1 trabajando) sin cambiarlo; cada hilo conserva su conversaci\xF3n"],
12042
12097
  ["--toolsets LIST", "set: grupos de tools de gs que recibe (crm, sales\u2026): una lista reemplaza; +x / -x agrega o quita"],
12043
12098
  ["--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)"],
12044
- ["--prompt-overlay FILE", 'set: JSON {"edits":[{"what","find","replace","regex"?,"all"?}]}, aplicados en orden; cada uno debe encontrar su lugar o no cambia nada'],
12099
+ ["--prompt-overlay FILE", 'set: JSON {"edits":[{"what","find","replace","regex"?,"all"?}]}, aplicados en orden; cada uno debe encontrar su lugar o no cambia nada. {"edits":[]} = espejo exacto: un solo prompt y una sola memoria para los dos'],
12045
12100
  ["--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'],
12046
12101
  ["--prompt-append FILE", "set: agrega ese texto al final del prompt (con --prompt-patch tambi\xE9n: primero las ediciones, luego el texto)"],
12047
12102
  ["--voice E:ID", "set: voz de las notas de voz: elevenlabs:<voiceId> o kokoro:em_santa|em_alex|ef_dora"],
@@ -12092,7 +12147,7 @@ var HELPS_ES = {
12092
12147
  ["--out FILE.tsx|.jsx [--force]", "embed: escribe en tu proyecto un componente de React COMPLETO y editable (sin depender de ning\xFAn paquete) con tu token y tu tema horneados arriba; no pisa un archivo que ya existe sin --force"],
12093
12148
  ["--check PATH", "box: este archivo debe existir en la m\xE1quina (repetible), p. ej. un archivo que trae la imagen nueva"],
12094
12149
  ["--contains PATH=TEXT", "box: ese archivo debe contener TEXT (un cambio que no agrega archivos)"],
12095
- ["--recycle", "box: reemplaza sus m\xE1quinas (nunca a media respuesta); pool: la nueva nace en el siguiente turno; ACP (goose, lite): su caja se recrea ya con la imagen actual"],
12150
+ ["--recycle", "box: reemplaza sus m\xE1quinas (nunca a media respuesta); pool: la nueva nace en el siguiente turno; ACP (goose, lite) igual, con el historial del \xFAltimo respaldo"],
12096
12151
  ["--reenv / --force", "box --recycle en ACP: --reenv s\xF3lo re-hornea el env en la misma caja; --force la recrea aunque est\xE9 ocupada (due\xF1o o staff)"],
12097
12152
  ["--unset-env K1,K2", "set: borra esas variables del env (--env K= tambi\xE9n borra K)"],
12098
12153
  ["--tool [ch:]tool=on|off", "set: prende/apaga una tool en un canal (wa:actualizar_identidad=off) o en todos (crm_buscar=off); separadas por comas"],
@@ -12208,7 +12263,7 @@ var HELPS_ES = {
12208
12263
  flags: [
12209
12264
  ["--media FILE", "chat: adjunta un archivo local (se repite)"],
12210
12265
  ["--file ID", "chat: adjunta de tus archivos (ghosty me files ls --q \u2026), separados por comas"],
12211
- ["--kind K", "archive|rm: test (pruebas) o channel (clientes: s\xF3lo archivar, su memoria los sigue atendiendo)"],
12266
+ ["--kind K", "archive|rm: test (pruebas), channel (clientes: s\xF3lo archivar, su memoria los sigue atendiendo) o personal (s\xF3lo TUS chats con ese agente; tambi\xE9n se van de tus apps)"],
12212
12267
  ["--before T", "archive|rm: s\xF3lo conversaciones sin mensajes desde T (7d, 12h o ISO)"],
12213
12268
  ["--user EMAIL", "ls/show (staff de Ghosty): las conversaciones de esa persona en cualquier agente; queda en bit\xE1cora"],
12214
12269
  ["--why", "show (staff de Ghosty): clase, de qui\xE9n es, qu\xE9 turno la fij\xF3, y si el due\xF1o / su usuario la ven en su lista o por liga"],
@@ -12268,6 +12323,7 @@ var HELPS_ES = {
12268
12323
  ["--text T / --sender N", "groups inject (staff): un mensaje que se perdi\xF3 (grupo apagado, deploy) entra como si N lo hubiera escrito ah\xED, y el agente contesta en el grupo"],
12269
12324
  ["--cached", "groups ls: no le pide a WhatsApp la lista en vivo (m\xE1s r\xE1pido; puede faltar alg\xFAn grupo nuevo)"],
12270
12325
  ["--ads-only on|off", "link: s\xF3lo las conversaciones que vienen de un anuncio abren tarjeta (tambi\xE9n solo, para cambiarlo en un n\xFAmero ya ligado)"],
12326
+ ["--board NAME|agent", "link: este n\xFAmero llena su propio tablero de ventas (nombre, id o main) en vez del del agente; las tools del CRM del agente trabajan ese tablero en los turnos de este n\xFAmero. agent = vuelve al del agente. Tambi\xE9n solo, en un n\xFAmero ya ligado"],
12271
12327
  ["--list", "history --dry-run: CU\xC1LES conversaciones (tel\xE9fono, nombre, primer mensaje, mensajes, si vino de anuncio)"],
12272
12328
  ["--only-ads / --phones a,b", "history: importa (o lista) s\xF3lo las de anuncio y/o esos tel\xE9fonos"],
12273
12329
  ["--column NAME", "history: las tarjetas nuevas nacen en esa columna en vez de la primera"],
@@ -12587,7 +12643,7 @@ var HELPS = {
12587
12643
  ["--engine E --dry-run", "set: what changing the engine does (sessions forgotten, machine started and which one is destroyed, whether it is busy) without changing it; each conversation keeps its history"],
12588
12644
  ["--toolsets LIST", "set: gs tool groups it gets (crm, sales\u2026): a list replaces; +x / -x add or remove"],
12589
12645
  ["--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)"],
12590
- ["--prompt-overlay FILE", 'set: JSON {"edits":[{"what","find","replace","regex"?,"all"?}]}, applied in order; each must find its place or nothing changes'],
12646
+ ["--prompt-overlay FILE", 'set: JSON {"edits":[{"what","find","replace","regex"?,"all"?}]}, applied in order; each must find its place or nothing changes. {"edits":[]} = an exact mirror: one prompt and one memory for both agents'],
12591
12647
  ["--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`],
12592
12648
  ["--prompt-append FILE", "set: add that text at the end of the prompt (with --prompt-patch too: edits first, then the text)"],
12593
12649
  ["--voice E:ID", "set: voice for voice notes: elevenlabs:<voiceId> or kokoro:em_santa|em_alex|ef_dora"],
@@ -12638,7 +12694,7 @@ var HELPS = {
12638
12694
  ["--out FILE.tsx|.jsx [--force]", "embed: write a COMPLETE, editable React component into your project (no package needed) with your token and theme baked in at the top; won't overwrite an existing file without --force"],
12639
12695
  ["--check PATH", "box: this file must exist in the machine (repeatable), e.g. a file the new image brings"],
12640
12696
  ["--contains PATH=TEXT", "box: that file must contain TEXT (a change that adds no files)"],
12641
- ["--recycle", "box: replace its machines (not mid-turn); pool: the new one starts on the next turn; ACP (goose, lite): its box is recreated now with the current image"],
12697
+ ["--recycle", "box: replace its machines (not mid-turn); pool: the new one starts on the next turn; ACP (goose, lite) too, history from its last backup"],
12642
12698
  ["--reenv / --force", "box --recycle on ACP: --reenv only re-bakes the env in the same box; --force recreates it even if busy (owner or staff)"],
12643
12699
  ["--unset-env K1,K2", "set: delete these env variables (--env K= also deletes K)"],
12644
12700
  ["--tool [ch:]tool=on|off", "set: one tool on/off in one channel (wa:actualizar_identidad=off) or everywhere (crm_buscar=off); comma-separated"],
@@ -12841,7 +12897,7 @@ var HELPS = {
12841
12897
  'ghosty chat <agent-id> ["message"] [--conversation ID] [--media FILE]\u2026 [--file ID[,ID\u2026]]',
12842
12898
  "ghosty conversations ls <agent-id>",
12843
12899
  "ghosty conversations show <agent-id> <conversation-id> [--limit 20]",
12844
- "ghosty conversations archive|rm <agent-id> --kind test|channel [--before 7d] [--dry-run] [--yes]",
12900
+ "ghosty conversations archive|rm <agent-id> --kind test|channel|personal [--before 7d] [--dry-run] [--yes]",
12845
12901
  "ghosty conversations ls --user <email> [--since 30d] [--surface android] # staff",
12846
12902
  "ghosty conversations show <agent-id> <conversation-id> --why # staff: who sees it and why",
12847
12903
  "ghosty conversations show <agent-id> <conversation-id> --tools # staff: what it ran and where it went"
@@ -12849,13 +12905,13 @@ var HELPS = {
12849
12905
  flags: [
12850
12906
  ["--media FILE", "chat: attach a local file (repeatable)"],
12851
12907
  ["--file ID", "chat: attach from your files (ghosty me files ls --q \u2026), comma-separated"],
12852
- ["--kind K", "archive|rm: test (test chats) or channel (customers: archive only, their memory keeps serving them)"],
12908
+ ["--kind K", "archive|rm: test (test chats), channel (customers: archive only, their memory keeps serving them) or personal (only YOUR own chats with that agent; also gone from your apps)"],
12853
12909
  ["--before T", "archive|rm: only conversations with no message since T (7d, 12h or ISO)"],
12854
12910
  ["--user EMAIL", "ls/show (Ghosty staff): that person's conversations in any agent; logged"],
12855
12911
  ["--why", "show (Ghosty staff): kind, whose it is, which turn set it, and whether the owner / its user see it in their list or by link"],
12856
12912
  ["--tools", "show (Ghosty staff): every tool the agent ran with its command/args (secrets masked) and the external domains it named; never while a turn runs in that thread"]
12857
12913
  ],
12858
- examples: ['ghosty chat <id> "hola"', "ghosty chat <id>", 'ghosty chat <id> "p\xE1salo a PDF" --media foto.jpg', "ghosty conversations rm <id> --kind test --before 7d --dry-run", "ghosty conversations archive <id> --kind channel --before 30d", "ghosty conversations ls --user david@empresa.com --since 7d"],
12914
+ examples: ['ghosty chat <id> "hola"', "ghosty chat <id>", 'ghosty chat <id> "p\xE1salo a PDF" --media foto.jpg', "ghosty conversations rm <id> --kind test --before 7d --dry-run", "ghosty conversations archive <id> --kind channel --before 30d", "ghosty conversations rm <id> --kind personal --dry-run", "ghosty conversations ls --user david@empresa.com --since 7d"],
12859
12915
  learn: `${REF}#conversaciones`
12860
12916
  },
12861
12917
  schedule: {
@@ -12951,6 +13007,7 @@ HELPS.channels = {
12951
13007
  ["--text T / --sender N", "groups inject (staff): a message that got lost (group off, deploy) enters as if N wrote it there, and the agent answers in the group"],
12952
13008
  ["--cached", "groups ls: don't ask WhatsApp for the live list (faster; may miss new groups)"],
12953
13009
  ["--ads-only on|off", "link: only conversations that come from an ad open a card (also alone, to change it on a linked number)"],
13010
+ ["--board NAME|agent", "link: this number fills its own sales board (name, id or main) instead of the agent's; the agent's CRM tools work that board on this number's turns. agent = back to the agent's board. Also alone, on a linked number"],
12954
13011
  ["--list", "history --dry-run: WHICH conversations (phone, name, first message, messages, from an ad)"],
12955
13012
  ["--only-ads / --phones a,b", "history: import (or list) only the ones from an ad and/or those phones"],
12956
13013
  ["--column NAME", "history: new cards are born in that column instead of the first one"],
@@ -14394,9 +14451,10 @@ async function supportThread(agent, sid, v) {
14394
14451
  });
14395
14452
  }
14396
14453
  async function cleanupConversations(action, agent, v) {
14397
- const ex = `ghosty conversations ${action} <agent> --kind ${action === "rm" ? "test" : "test|channel"} [--before 7d] [--dry-run]`;
14454
+ const ex = `ghosty conversations ${action} <agent> --kind ${action === "rm" ? "test|personal" : "test|channel|personal"} [--before 7d] [--dry-run]`;
14398
14455
  if (!agent) throw usage(t("Missing agent.", "Falta el agente."), ex);
14399
- if (v.kind !== "test" && v.kind !== "channel") throw usage(t("--kind takes test or channel.", "--kind acepta test o channel."), ex);
14456
+ if (v.kind !== "test" && v.kind !== "channel" && v.kind !== "personal") throw usage(t("--kind takes test, channel or personal.", "--kind acepta test, channel o personal."), ex);
14457
+ if (action === "rm" && v.kind === "channel") throw usage(t("Customer conversations (channel) are archived, never deleted: their memory keeps serving them.", "Los hilos de clientes (channel) se archivan, nunca se borran: su memoria los sigue atendiendo."), ex);
14400
14458
  const path = `/api/v2/agents/${enc13(agent)}/conversations/cleanup`;
14401
14459
  const body = { kind: v.kind, action, ...v.before ? { before: v.before } : {} };
14402
14460
  const plan = await api(path, { method: "POST", body: { ...body, dryRun: true } });
@@ -14648,7 +14706,7 @@ ${bold("Configure an agent")}
14648
14706
  channels whatsapp ls|enable|disable <id> [number] Which WhatsApp Business numbers it answers [--dry-run]
14649
14707
  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]
14650
14708
  channels messenger ls <id> The workspace's Messenger pages: who answers now and who answered last [--orphans]
14651
- 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]
14709
+ channels whatsapp link|unlink <id> --integration ID --answer on|off Bring a WhatsApp Business number from Formmy/EasyBits to this agent, or send it back [--forward] [--ads-only on|off] [--board NAME|agent] [--dry-run]
14652
14710
  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]
14653
14711
  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]
14654
14712
  channels wa pair <id> --phone N Pair a personal WhatsApp by code (no QR)
@@ -14688,7 +14746,7 @@ ${bold("Talk to an agent")}
14688
14746
  chat <id> ["message"] Stream a conversation (no message = interactive) [--conversation ID] [--media FILE] [--file ID: from your files]
14689
14747
  conversations ls <id> List conversations
14690
14748
  conversations show <id> <conversation> What was said [--limit 20] (0 = all)
14691
- conversations archive|rm <id> --kind test|channel Clean its history (rm: tests only) [--before 7d] [--dry-run]
14749
+ conversations archive|rm <id> --kind test|channel|personal Clean its history (rm: never customers) [--before 7d] [--dry-run]
14692
14750
  turns ls <id> Recent turns and why they failed [--errors] [--skipped] [--since 1h] [--limit N]
14693
14751
  turns ls --workspace SLUG --quota-hits Messages that got no turn because the balance ran out [--days 14]
14694
14752
  turns show <id> [turn] What came in, what it answered, and where the time went (default: last) [--full]
@@ -14815,7 +14873,7 @@ ${bold("Configurar un agente")}
14815
14873
  channels whatsapp ls|enable|disable <id> [n\xFAmero] Qu\xE9 n\xFAmeros de WhatsApp Business contesta [--dry-run]
14816
14874
  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]
14817
14875
  channels messenger ls <id> Las p\xE1ginas de Messenger del espacio: qui\xE9n contesta hoy y qui\xE9n contest\xF3 al \xFAltimo [--orphans]
14818
- 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]
14876
+ channels whatsapp link|unlink <id> --integration ID --answer on|off Trae un n\xFAmero de WhatsApp Business de Formmy/EasyBits a este agente, o devu\xE9lvelo [--forward] [--ads-only on|off] [--board NOMBRE|agent] [--dry-run]
14819
14877
  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]
14820
14878
  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]
14821
14879
  channels wa pair <id> --phone N Vincula un WhatsApp personal con c\xF3digo (sin QR)
@@ -14855,7 +14913,7 @@ ${bold("Hablar con un agente")}
14855
14913
  chat <id> ["mensaje"] Conversaci\xF3n en streaming (sin mensaje = interactivo) [--conversation ID] [--media ARCHIVO] [--file ID: de tus archivos]
14856
14914
  conversations ls <id> Lista conversaciones
14857
14915
  conversations show <id> <conversaci\xF3n> Qu\xE9 se dijo [--limit 20] (0 = todo)
14858
- conversations archive|rm <id> --kind test|channel Limpia su historial (rm: s\xF3lo pruebas) [--before 7d] [--dry-run]
14916
+ conversations archive|rm <id> --kind test|channel|personal Limpia su historial (rm: nunca clientes) [--before 7d] [--dry-run]
14859
14917
  turns ls <id> Turnos recientes y por qu\xE9 fallaron [--errors] [--skipped] [--since 1h] [--limit N]
14860
14918
  turns ls --workspace SLUG --quota-hits Mensajes que no abrieron turno porque se acab\xF3 el saldo [--days 14]
14861
14919
  turns show <id> [turno] Qu\xE9 lleg\xF3, qu\xE9 contest\xF3 y en qu\xE9 se fue el tiempo (default: el \xFAltimo) [--full]
@@ -15163,7 +15221,7 @@ async function main() {
15163
15221
  const tokens = splitTokens(pos, v);
15164
15222
  globals.token = tokens.session;
15165
15223
  globals.profile = v.profile;
15166
- if (v.version) return out({ version: "0.58.0" }, () => "0.58.0");
15224
+ if (v.version) return out({ version: "0.58.1" }, () => "0.58.1");
15167
15225
  for (const n of normalizeArgs(pos, v)) if (!globals.json) info(dim(n));
15168
15226
  const context = (await readConfig()).workspace;
15169
15227
  if (!explicitToken()?.startsWith("gat_")) {
@@ -15329,7 +15387,7 @@ async function main() {
15329
15387
  throw unknown(cmd, cmd, COMMANDS, "ghosty --help");
15330
15388
  }
15331
15389
  }
15332
- var notice = updateNotice("0.58.0").catch(() => null);
15390
+ var notice = updateNotice("0.58.1").catch(() => null);
15333
15391
  main().then(
15334
15392
  async () => {
15335
15393
  const n = await notice;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghostystudio/cli",
3
- "version": "0.58.0",
3
+ "version": "0.58.1",
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 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"
19
+ "test": "for t in flags prompt api rows wa-auth resolve aliases i18n help completion photos channels-retry spaces sites diff cards env; 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",