@ghostystudio/cli 0.29.0 → 0.30.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 -1
  2. package/dist/index.js +353 -45
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -72,7 +72,13 @@ ghosty schedule ls <id>
72
72
  ghosty schedule rm <id> <schedule-id>
73
73
  ghosty schedule add|ls|rm <id> … --teams <workspace>#<room>[/<thread-id>] # in a Teams room/thread: it answers there as if mentioned
74
74
 
75
- ghosty me files ls|upload|rm [--kind image|video|audio|document|other]
75
+ ghosty me files ls|get|upload|rm [--q TEXT] [--kind image|video|audio|document|other] [--cursor C]
76
+ ghosty conversations archive|rm <id> --kind test|channel [--before 7d] [--dry-run]
77
+ ghosty agents docs dedupe [--dry-run] # your account: merge documents with the same content
78
+ # Ghosty staff
79
+ ghosty conversations ls --user EMAIL [--since 30d] · conversations show <id> <conv> --why
80
+ ghosty turns stats [--since 14d] [--all] · push log --user EMAIL [--since 2d]
81
+ ghosty novedades ls|create|set|test|send|rm [--title] [--body] [--image FILE] [--link] [--to] [--publish] [--at]
76
82
  ghosty credentials ls|set|rm [provider] [--from-env VAR] # never as an argument
77
83
  ghosty docs
78
84
 
@@ -89,6 +95,14 @@ Every command has its own help with examples: `ghosty <command> --help` or `ghos
89
95
 
90
96
  Exit codes: `0` ok · `1` API error · `2` usage error · `3` not signed in.
91
97
 
98
+ ### Changes in 0.30.0
99
+
100
+ - Support (Ghosty staff): `conversations ls --user EMAIL`, `conversations show <agent> <conv> --why` (who sees it and why), `turns stats` (real failure rate), `push log --user EMAIL`, `novedades ls|create|set|test|send|rm` (Android app announcements, `--image FILE`).
101
+ - `conversations archive|rm <agent> --kind test|channel [--before 7d]`: clean an agent's history (rm: tests only). Deleting an agent now also deletes its chat copy.
102
+ - `agents docs dedupe`: merge your documents with the same content (keeps every agent's access); `dedupe <keep> <drop>` for two by hand.
103
+ - `me files ls --q TEXT [--cursor]`, `me files get <id> [--out]`, `chat --file <id>` (attach from your files).
104
+ - `channels wa groups set <agent> <group> --name "…"`.
105
+
92
106
  ### Changes in 0.29.0
93
107
 
94
108
  - `agents ls --engine E --ids` (ids only, for `xargs`); staff: `agents ls --sponsor EMAIL | --owner EMAIL` lists any account's agents, and `agents get|set|box` work on them by id (logged).
package/dist/index.js CHANGED
@@ -8020,6 +8020,30 @@ var SERVER_MESSAGES = [
8020
8020
  ["pon al menos un filtro: sponsor, owner, workspace o engine", "add at least one filter: sponsor, owner, workspace or engine"],
8021
8021
  ["no hay una cuenta con ese correo", "there's no account with that email"],
8022
8022
  ["files: [{ name, mimeType, data (base64) }], hasta 8 de 10 MB", "files: [{ name, mimeType, data (base64) }], up to 8 of 10 MB"],
8023
+ ["leer los hilos de otra persona: s\xF3lo el staff de Ghosty", "reading another person's conversations: Ghosty staff only"],
8024
+ ["leer un hilo ajeno: s\xF3lo el staff de Ghosty", "reading someone else's conversation: Ghosty staff only"],
8025
+ ["la salud de los turnos: s\xF3lo el staff de Ghosty", "turn health: Ghosty staff only"],
8026
+ ["la bit\xE1cora de push: s\xF3lo el staff de Ghosty", "the push log: Ghosty staff only"],
8027
+ ["las novedades de la app: s\xF3lo el staff de Ghosty", "app announcements: Ghosty staff only"],
8028
+ ["falta user (correo)", "missing user (email)"],
8029
+ ["no hay una cuenta con el correo {}", "there's no account with the email {}"],
8030
+ ["limpiar el historial: due\xF1o, admin del agente o staff", "cleaning the history: the agent's owner, admin or staff"],
8031
+ ["los hilos de cliente (channel) se archivan, no se borran: su memoria es la que los sigue atendiendo", "customer threads (channel) are archived, not deleted: their memory is what keeps serving them"],
8032
+ ["kind: test | channel", "kind: test | channel"],
8033
+ ["action: archive | rm", "action: archive | rm"],
8034
+ ["before: 7d, 12h o una fecha ISO", "before: 7d, 12h or an ISO date"],
8035
+ ["keep y drop van juntos", "keep and drop go together"],
8036
+ ["los dos documentos tienen que ser tuyos y existir", "both documents must exist and be yours"],
8037
+ ["es el mismo documento", "it's the same document"],
8038
+ ["no se pudo borrar el duplicado", "couldn't delete the duplicate"],
8039
+ ["t\xEDtulo y cuerpo son obligatorios", "title and body are required"],
8040
+ ["Esa imagen ya la usa \xAB{}\xBB. Cada novedad lleva la suya.", "That image is already used by \xAB{}\xBB. Each announcement needs its own."],
8041
+ ["Elige una hora futura.", "Pick a future time."],
8042
+ ["S\xF3lo de 9 a 21 h (CDMX).", "Only from 9 to 21 h (CDMX)."],
8043
+ ["Publ\xEDcala primero.", "Publish it first."],
8044
+ ["Esta novedad ya se mand\xF3.", "This announcement was already sent."],
8045
+ ["no hay novedad {}", "no announcement {}"],
8046
+ ["lista, b\xFAsqueda y push: s\xF3lo kind=personal de quien lo abri\xF3; por liga: el suyo, y el due\xF1o/admin lee los de canal y prueba", "list, search and push: only kind=personal of whoever opened it; by link: their own, and the owner/admin reads channel and test threads"],
8023
8047
  // — Infra interna (casi nunca llega al CLI, pero por si acaso)
8024
8048
  ["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)."],
8025
8049
  ["sqld: fetch sin namespace", "sqld: fetch without namespace"],
@@ -8322,7 +8346,7 @@ async function postLong(path, body, timeoutMs = 9e5) {
8322
8346
  const req = httpsRequest(`${base}${path}`, {
8323
8347
  method: "POST",
8324
8348
  timeout: timeoutMs,
8325
- headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.29.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
8349
+ headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.30.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
8326
8350
  }, (res) => {
8327
8351
  let buf = "";
8328
8352
  res.on("data", (c) => buf += c);
@@ -8364,7 +8388,7 @@ async function request(path, opts = {}, retried = false) {
8364
8388
  method: opts.method ?? (opts.body !== void 0 || opts.raw !== void 0 ? "POST" : "GET"),
8365
8389
  headers: {
8366
8390
  authorization: `Bearer ${token}`,
8367
- "user-agent": `ghosty-cli/${"0.29.0"}`,
8391
+ "user-agent": `ghosty-cli/${"0.30.0"}`,
8368
8392
  "accept-language": lang(),
8369
8393
  ...opts.body !== void 0 ? { "content-type": "application/json" } : {},
8370
8394
  ...opts.headers
@@ -8443,12 +8467,12 @@ var enc = encodeURIComponent;
8443
8467
  var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
8444
8468
  var MIME = { jpg: "image/jpeg", jpeg: "image/jpeg", png: "image/png", webp: "image/webp", gif: "image/gif", pdf: "application/pdf", ogg: "audio/ogg", opus: "audio/ogg", mp3: "audio/mpeg", m4a: "audio/mp4", wav: "audio/wav", mp4: "video/mp4" };
8445
8469
  async function localFiles(paths) {
8446
- const { readFile: readFile13 } = await import("node:fs/promises");
8470
+ const { readFile: readFile14 } = await import("node:fs/promises");
8447
8471
  const { basename: basename4 } = await import("node:path");
8448
8472
  return Promise.all(
8449
8473
  paths.map(async (p) => {
8450
8474
  const ext = p.toLowerCase().split(".").pop() ?? "";
8451
- return { name: basename4(p), mimeType: MIME[ext] ?? "application/octet-stream", data: (await readFile13(p)).toString("base64") };
8475
+ return { name: basename4(p), mimeType: MIME[ext] ?? "application/octet-stream", data: (await readFile14(p)).toString("base64") };
8452
8476
  })
8453
8477
  );
8454
8478
  }
@@ -9067,7 +9091,7 @@ function capacityLine(c) {
9067
9091
  // src/commands/chat.ts
9068
9092
  import { randomUUID } from "node:crypto";
9069
9093
  import { createInterface as createInterface2 } from "node:readline/promises";
9070
- import { readFile as readFile4 } from "node:fs/promises";
9094
+ import { readFile as readFile4, writeFile as writeFile4 } from "node:fs/promises";
9071
9095
  import { basename as basename2 } from "node:path";
9072
9096
 
9073
9097
  // src/sse.ts
@@ -9177,7 +9201,10 @@ async function chat(agent, message, v) {
9177
9201
  first = true;
9178
9202
  if (!globals.json) info(dim(t(`conversation ${sid} (continue it with --conversation ${sid})`, `conversaci\xF3n ${sid} (s\xEDguela con --conversation ${sid})`)));
9179
9203
  }
9180
- const images = v.media?.length ? await localFiles(v.media) : [];
9204
+ const images = [
9205
+ ...v.media?.length ? await localFiles(v.media) : [],
9206
+ ...(v.file ?? "").split(",").map((x) => x.trim()).filter(Boolean).map((fileId) => ({ fileId }))
9207
+ ];
9181
9208
  if (message || images.length) {
9182
9209
  await turn(agent, sid, message ?? "", first, images);
9183
9210
  return;
@@ -9211,9 +9238,20 @@ async function readAll() {
9211
9238
  return s;
9212
9239
  }
9213
9240
  async function meFiles(sub, arg, v) {
9214
- const ex = "ghosty me files ls [--kind image|video|audio|document|other] | upload <file> | rm <file-id>";
9241
+ const ex = "ghosty me files ls [--q TEXT] [--kind image|video|audio|document|other] [--cursor C] | get <file-id> [--out FILE] | upload <file> | rm <file-id>";
9215
9242
  switch (sub) {
9216
9243
  case "ls": {
9244
+ if (v.q !== void 0 || v.cursor) {
9245
+ const q = new URLSearchParams({ q: v.q ?? "", ...v.kind ? { kind: v.kind } : {}, ...v.cursor ? { cursor: v.cursor } : {}, ...v.limit ? { limit: v.limit } : {} });
9246
+ const r2 = await api(`/api/v2/me/files?${q}`);
9247
+ out(r2, () => {
9248
+ const rows = r2.files.map((f) => ({ id: f.id, name: f.name, kind: f.kind, size: f.size, created: f.createdAt.slice(0, 10) }));
9249
+ return `${table(rows, ["id", "name", "kind", "size", "created"])}${r2.nextCursor ? `
9250
+ ${dim(t(`More: ghosty me files ls --q "${v.q ?? ""}" --cursor ${r2.nextCursor}`, `M\xE1s: ghosty me files ls --q "${v.q ?? ""}" --cursor ${r2.nextCursor}`))}` : ""}
9251
+ ${dim(t('Attach one in a chat: ghosty chat <agent> "\u2026" --file <id>', 'Adj\xFAntalo en un chat: ghosty chat <agente> "\u2026" --file <id>'))}`;
9252
+ });
9253
+ return;
9254
+ }
9217
9255
  const r = await api(
9218
9256
  `/api/v2/me/files${v.kind ? `?kind=${enc3(v.kind)}` : ""}`
9219
9257
  );
@@ -9224,6 +9262,18 @@ ${dim(t(`${(r.storage.usedBytes / 1e9).toFixed(2)} GB of ${r.storage.limitGb} GB
9224
9262
  });
9225
9263
  return;
9226
9264
  }
9265
+ case "get": {
9266
+ if (!arg) throw usage(t("Missing file id.", "Falta el id del archivo."), ex);
9267
+ const r = await api(`/api/v2/me/files/${enc3(arg)}`);
9268
+ if (v.out) {
9269
+ const res = await fetch(r.url);
9270
+ if (!res.ok) throw new CliError(`${res.status} ${t("downloading the file", "al bajar el archivo")}`, EXIT.API);
9271
+ const bytes = Buffer.from(await res.arrayBuffer());
9272
+ await writeFile4(v.out, bytes);
9273
+ return out({ id: arg, saved: v.out, bytes: bytes.length }, () => `${lavender("\u2713")} ${v.out} (${bytes.length} bytes)`);
9274
+ }
9275
+ return out(r, () => r.url);
9276
+ }
9227
9277
  case "upload": {
9228
9278
  if (!arg) throw usage(t("Missing file.", "Falta el archivo."), ex);
9229
9279
  const bytes = await readFile4(arg);
@@ -9378,7 +9428,7 @@ ${dim(`${t("conversation", "conversaci\xF3n")} ${p.sessionId}`)}`
9378
9428
 
9379
9429
  // src/commands/partner.ts
9380
9430
  import { createHmac } from "node:crypto";
9381
- import { readFile as readFile5, writeFile as writeFile4 } from "node:fs/promises";
9431
+ import { readFile as readFile5, writeFile as writeFile5 } from "node:fs/promises";
9382
9432
  var partnerHelp = () => t(
9383
9433
  `${bold("Usage:")} ghosty partner <command> [options]
9384
9434
 
@@ -9465,7 +9515,7 @@ async function call(path, v, init = {}) {
9465
9515
  "x-ghosty-ts": ts,
9466
9516
  "x-ghosty-sig": sig,
9467
9517
  "content-type": "application/json",
9468
- "user-agent": `ghosty-cli/${"0.29.0"}`
9518
+ "user-agent": `ghosty-cli/${"0.30.0"}`
9469
9519
  },
9470
9520
  body: method === "GET" ? void 0 : raw
9471
9521
  });
@@ -9489,7 +9539,7 @@ GHOSTY_PARTNER_KEY_SECRET=${r.secret}
9489
9539
  `;
9490
9540
  async function showSecret(r, v) {
9491
9541
  if (v["env-file"]) {
9492
- await writeFile4(v["env-file"], envLines(r), { mode: 384 });
9542
+ await writeFile5(v["env-file"], envLines(r), { mode: 384 });
9493
9543
  return out({ keyId: r.keyId, envFile: v["env-file"] }, () => `${lavender("\u2713")} ${r.keyId} ${t(`\u2192 ${v["env-file"]} (mode 600; the secret was not printed)`, `\u2192 ${v["env-file"]} (modo 600; el secreto no se imprimi\xF3)`)}`);
9494
9544
  }
9495
9545
  if (v.format === "env") return void process.stdout.write(envLines(r));
@@ -9892,8 +9942,29 @@ ${dim(`${t(`${rows.length} row(s)`, `${rows.length} fila(s)`)} \xB7 ${Math.round
9892
9942
  throw usage(t(`Unknown dbs subcommand: ${sub}.`, `Subcomando de dbs desconocido: ${sub}.`), t("Try", "Prueba") + ": ghosty dbs ls | tables | query | import | rehost | grant | revoke | rename | rm (ghosty dbs --help)");
9893
9943
  }
9894
9944
  }
9945
+ async function docsDedupe(v) {
9946
+ const path = "/api/v2/me/documents/dedupe";
9947
+ const pair = v.keep || v.drop ? { keep: v.keep, drop: v.drop } : {};
9948
+ const plan = await api(path, { method: "POST", body: { ...pair, dryRun: true } });
9949
+ const name = (d) => typeof d === "string" ? d : `\xAB${d.name}\xBB ${dim(`${d.id.slice(0, 10)} \xB7 ${d.agents} ${t("agent(s)", "agente(s)")}`)}`;
9950
+ const text = [
9951
+ ...plan.merges.map((m) => `${t("keep", "queda")} ${name(m.keep)}
9952
+ ${m.drop.map((d) => ` - ${name(d)}`).join("\n")}`),
9953
+ ...plan.sameName?.length ? ["", dim(t("Same name, different content (not merged; if they're the same: ghosty agents docs dedupe <keep-id> <drop-id>):", "Mismo nombre, otro contenido (no se unen; si s\xED son el mismo: ghosty agents docs dedupe <id-que-queda> <id-que-se-va>):")), ...plan.sameName.map((g) => ` ${g.map(name).join(" \xB7 ")}`)] : []
9954
+ ].join("\n");
9955
+ if (!plan.merges.length) return out(plan, () => text.trim() ? text : dim(t("No duplicate documents.", "Sin documentos duplicados.")));
9956
+ if (v["dry-run"]) return out(plan, () => `${text}
9957
+ ${dim(t("Dry run: nothing changed.", "Simulaci\xF3n: no cambi\xF3 nada."))}`);
9958
+ const retry = `ghosty agents docs dedupe${v.keep ? ` ${v.keep} ${v.drop ?? ""}` : ""}`;
9959
+ requireYesIfHeadless(v.yes, retry);
9960
+ await confirm(`${text}
9961
+ ${t("Merge? The extra copies are deleted and their agents keep access through the one that stays.", "\xBFUnir? Las copias sobrantes se borran y sus agentes siguen leyendo por la que queda.")}`, { yes: v.yes, retry });
9962
+ const r = await api(path, { method: "POST", body: pair });
9963
+ out(r, () => `${lavender("\u2713")} ${t(`${r.merged} duplicate(s) merged \xB7 ${r.grantsMoved} permission(s) moved.`, `${r.merged} duplicado(s) unido(s) \xB7 ${r.grantsMoved} permiso(s) movido(s).`)}`);
9964
+ }
9895
9965
  async function agentDocsCmd(sub, id, docs, v) {
9896
9966
  let doc = docs[0];
9967
+ if (sub === "dedupe") return docsDedupe(v);
9897
9968
  id = need2(id, t("agent id", "el id del agente"), "ghosty agents docs ls <agent-id>");
9898
9969
  switch (sub ?? "ls") {
9899
9970
  case "ls":
@@ -10290,7 +10361,7 @@ function findGroup(groups, ref) {
10290
10361
  }
10291
10362
  async function groupsCmd(args, v) {
10292
10363
  const [sub, id, ...rest] = args;
10293
- const ex = 'ghosty channels wa groups ls <agent-id> | set <agent-id> <group> [--role equipo|cliente] [--on|--off] | create <agent-id> "Name"';
10364
+ const ex = 'ghosty channels wa groups ls <agent-id> | set <agent-id> <group> [--role equipo|cliente] [--on|--off] [--name "New name"] | create <agent-id> "Name"';
10294
10365
  if (!id) throw usage(t("Missing agent id.", "Falta el id del agente."), `${t("Example", "Ejemplo")}: ${ex}`);
10295
10366
  const path = `/api/v2/agents/${enc7(id)}/wa/groups`;
10296
10367
  if (v.role !== void 0 && v.role !== "equipo" && v.role !== "cliente") throw usage(t("--role takes equipo or cliente.", "--role acepta equipo o cliente."));
@@ -10300,10 +10371,10 @@ async function groupsCmd(args, v) {
10300
10371
  return out(r2, () => rows.length ? table(rows, ["name", "on", "role", "jid"]) : dim(t('No groups. Pair its personal WhatsApp in the web, or: ghosty channels wa groups create <agent-id> "Name"', 'Sin grupos. Parea su WhatsApp personal en la web, o: ghosty channels wa groups create <agent-id> "Nombre"')));
10301
10372
  }
10302
10373
  if (sub === "create") {
10303
- const name = rest.join(" ").trim();
10304
- if (!name) throw usage(t("Missing group name.", "Falta el nombre del grupo."), t("Example", "Ejemplo") + `: ghosty channels wa groups create ${id} "Equipo ventas" --role equipo`);
10305
- const r2 = await api(path, { method: "POST", body: { create: name, role: v.role ?? "equipo" } });
10306
- return out(r2, () => `${lavender("\u2713")} ${t("Created", "Creado")} ${bold(name)} (${v.role ?? "equipo"}, on)
10374
+ const name2 = rest.join(" ").trim();
10375
+ if (!name2) throw usage(t("Missing group name.", "Falta el nombre del grupo."), t("Example", "Ejemplo") + `: ghosty channels wa groups create ${id} "Equipo ventas" --role equipo`);
10376
+ const r2 = await api(path, { method: "POST", body: { create: name2, role: v.role ?? "equipo" } });
10377
+ return out(r2, () => `${lavender("\u2713")} ${t("Created", "Creado")} ${bold(name2)} (${v.role ?? "equipo"}, on)
10307
10378
  ${t("invite", "invitaci\xF3n")}: ${r2.inviteUrl}`);
10308
10379
  }
10309
10380
  if (sub !== "set") throw usage(t(`Unknown groups subcommand: ${sub}.`, `Subcomando de groups desconocido: ${sub}.`), ex);
@@ -10314,16 +10385,17 @@ async function groupsCmd(args, v) {
10314
10385
  const g = findGroup(cur.groups, ref);
10315
10386
  const enabled = v.on ? true : v.off ? false : g.enabled;
10316
10387
  const role = v.role ?? g.role;
10317
- const changes = [...enabled !== g.enabled ? [`${g.enabled ? "on" : "off"}\u2192${enabled ? "on" : "off"}`] : [], ...role !== g.role ? [`${g.role}\u2192${role}`] : []];
10388
+ const name = v.name?.trim() || void 0;
10389
+ const changes = [...enabled !== g.enabled ? [`${g.enabled ? "on" : "off"}\u2192${enabled ? "on" : "off"}`] : [], ...role !== g.role ? [`${g.role}\u2192${role}`] : [], ...name && name !== g.name ? [`\xAB${g.name}\xBB\u2192\xAB${name}\xBB`] : []];
10318
10390
  if (!changes.length) return out({ changed: 0, group: g }, () => dim(t(`Nothing to do: ${g.name} is already ${g.enabled ? "on" : "off"} as ${g.role}.`, `Nada que hacer: ${g.name} ya est\xE1 ${g.enabled ? "encendido" : "apagado"} como ${g.role}.`)));
10319
10391
  const plan = `~ ${g.name}: ${changes.join(", ")}`;
10320
10392
  if (v["dry-run"]) return out({ dryRun: true, plan: [plan] }, () => `${plan}
10321
10393
  ${dim(t("Dry run: nothing changed.", "Simulacro: no se cambi\xF3 nada."))}`);
10322
10394
  if (enabled && !g.enabled) await confirm(`${bold(plan)}
10323
10395
  ${t(`The agent will start answering in this group as ${role}.`, `El agente empezar\xE1 a contestar en este grupo como ${role}.`)}`, { yes: v.yes, retry: `ghosty channels wa groups set ${id} "${ref}"${v.role ? ` --role ${v.role}` : ""} --on` });
10324
- const r = await api(path, { method: "POST", body: { jid: g.jid, enabled, role } });
10396
+ const r = await api(path, { method: "POST", body: { jid: g.jid, enabled, role, ...name ? { name } : {} } });
10325
10397
  const now = r.groups.find((x) => x.jid === g.jid);
10326
- return out({ group: now ?? null, plan: [plan] }, () => `${lavender("\u2713")} ${g.name}: ${now?.enabled ? "on" : "off"} ${t("as", "como")} ${now?.role ?? role}`);
10398
+ return out({ group: now ?? null, plan: [plan] }, () => `${lavender("\u2713")} ${now?.name ?? g.name}: ${now?.enabled ? "on" : "off"} ${t("as", "como")} ${now?.role ?? role}`);
10327
10399
  }
10328
10400
  var EB_EXPORT = (agentId) => `${(process.env.EASYBITS_URL ?? "https://www.easybits.cloud").replace(/\/+$/, "")}/api/v2/fleet-agents/${enc7(agentId)}/baileys/export`;
10329
10401
  async function easybitsSession(agentId, keys) {
@@ -10633,6 +10705,7 @@ var HELPS_ES = {
10633
10705
  summary: "Crea, revisa, cambia, diagnostica y borra agentes.",
10634
10706
  replace: [
10635
10707
  ["# the agent as a file; see ghosty apply --help", "# el agente como archivo; ver ghosty apply --help"],
10708
+ ["# your account: merge copies with the same content", "# tu cuenta: une las copias con el mismo contenido"],
10636
10709
  ["# what it really has: effective toolsets, connectors connected + granted", "# lo que de verdad tiene: toolsets efectivos, conectores conectados + concedidos"]
10637
10710
  ],
10638
10711
  flags: [
@@ -10733,7 +10806,7 @@ var HELPS_ES = {
10733
10806
  },
10734
10807
  turns: {
10735
10808
  summary: "Los turnos recientes de un agente: qu\xE9 contest\xF3, qu\xE9 fall\xF3 y por qu\xE9, y en qu\xE9 se fue el tiempo de un turno.",
10736
- replace: [["# last turn: each tool, its seconds, output tokens", "# \xFAltimo turno: cada tool, sus segundos, tokens de salida"], ["# stop the turn AND its background jobs", "# detiene el turno Y sus encargos en fondo"]],
10809
+ replace: [["# last turn: each tool, its seconds, output tokens", "# \xFAltimo turno: cada tool, sus segundos, tokens de salida"], ["# stop the turn AND its background jobs", "# detiene el turno Y sus encargos en fondo"], ["# staff: real failure rate by engine and error", "# staff: tasa real de fallos por motor y por error"]],
10737
10810
  flags: [
10738
10811
  ["--errors", "s\xF3lo turnos fallidos (igual que --status error)"],
10739
10812
  ["--skipped", "ls: mensajes de clientes de WhatsApp Business que NO abrieron turno, y por qu\xE9 (operador activo, s\xF3lo registra, pausa, reacci\xF3n, canal apagado, sin saldo)"],
@@ -10742,6 +10815,7 @@ var HELPS_ES = {
10742
10815
  ["--limit N", "m\xE1ximo de filas (default 20, m\xE1x. 500)"],
10743
10816
  ["--full", "show: el mensaje que entr\xF3 y la respuesta completos (default: primeros 600 caracteres)"],
10744
10817
  ["--quota-hits", "ls --workspace: mensajes (WhatsApp Business y Teams) que no abrieron turno porque se acab\xF3 el saldo"],
10818
+ ["--all", "stats (staff): cuenta tambi\xE9n staff, canal prueba y agentes borrados"],
10745
10819
  ["--session S", "ls: s\xF3lo los turnos de esa conversaci\xF3n. Los de Claude dicen \xABresumed\xBB: \u2713 = retom\xF3 la sesi\xF3n del SDK, \u26A0 desde cero (orphan|stale|replaced) = perdi\xF3 la memoria"]
10746
10820
  ],
10747
10821
  notes: ["--skipped guarda 30 d\xEDas y s\xF3lo ve lo que Formmy reenvi\xF3: si Formmy no reenv\xEDa (ghosty channels whatsapp ls, \xABformmy forwards\xBB), a gs nunca le llega el mensaje.", "show imprime lo que entr\xF3 y lo que sali\xF3 en cualquier motor o canal (grupos de WhatsApp tambi\xE9n); la l\xEDnea de tiempo lee el transcript del worker, as\xED que s\xF3lo motores del pool (no ACP/goose)."]
@@ -10773,7 +10847,15 @@ var HELPS_ES = {
10773
10847
  },
10774
10848
  chat: {
10775
10849
  summary: "Conversa en streaming; sin mensaje es interactivo.",
10776
- replace: [['["message"]', '["mensaje"]']]
10850
+ replace: [['["message"]', '["mensaje"]'], ["# staff: who sees it and why", "# staff: qui\xE9n lo ve y por qu\xE9"]],
10851
+ flags: [
10852
+ ["--media FILE", "chat: adjunta un archivo local (se repite)"],
10853
+ ["--file ID", "chat: adjunta de tus archivos (ghosty me files ls --q \u2026), separados por comas"],
10854
+ ["--kind K", "archive|rm: test (pruebas) o channel (clientes: s\xF3lo archivar, su memoria los sigue atendiendo)"],
10855
+ ["--before T", "archive|rm: s\xF3lo conversaciones sin mensajes desde T (7d, 12h o ISO)"],
10856
+ ["--user EMAIL", "ls/show (staff de Ghosty): las conversaciones de esa persona en cualquier agente; queda en bit\xE1cora"],
10857
+ ["--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"]
10858
+ ]
10777
10859
  },
10778
10860
  schedule: {
10779
10861
  summary: "Turnos que corren despu\xE9s, con el reloj propio del agente.",
@@ -10798,6 +10880,7 @@ var HELPS_ES = {
10798
10880
  ["--yes", "enable / groups enable: se salta la confirmaci\xF3n (obligatorio sin terminal)"],
10799
10881
  ["--role R", "groups: equipo (el equipo: escribe en bases de datos, tools de admin) o cliente (como cualquier cliente)"],
10800
10882
  ["--cached", "groups ls: no le pide a WhatsApp la lista en vivo (m\xE1s r\xE1pido; puede faltar alg\xFAn grupo nuevo)"],
10883
+ ["--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"],
10801
10884
  ["--integration ID", "link/unlink/history: el id de la Integration del n\xFAmero de WhatsApp Business en Formmy"],
10802
10885
  ["--email E", "integrations (staff): lista los n\xFAmeros de otra cuenta de Formmy"],
10803
10886
  ["--days N", "history: conversaciones activas en los \xFAltimos N d\xEDas (1-30, default 7)"],
@@ -10945,6 +11028,14 @@ var HELPS_ES = {
10945
11028
  ],
10946
11029
  notes: ["try cuenta contra los topes del demo (mensajes por visitante, por IP al d\xEDa) y sale en sus n\xFAmeros y en el CRM.", "history es s\xF3lo para staff de Ghosty: los chats de los visitantes no se le ense\xF1an al due\xF1o."]
10947
11030
  },
11031
+ push: {
11032
+ summary: "Staff de Ghosty: qu\xE9 avisos (push) se le mandaron a alguien (fin de turno, permiso, escalar, documento\u2026), a qu\xE9 dispositivo y qu\xE9 contest\xF3 Apple/Google. Se registran desde el 1-oct y se guardan 30 d\xEDas.",
11033
+ notes: ["thread ense\xF1a la clase de la conversaci\xF3n: un \xABchannel\xBB ah\xED es que le lleg\xF3 el aviso de un cliente."]
11034
+ },
11035
+ novedades: {
11036
+ summary: "Staff de Ghosty: las novedades de la app Android. Cada una con su ilustraci\xF3n; mismos frenos que el admin: pru\xE9bala en tu tel\xE9fono primero, push de 9 a 21 h CDMX, 72 h entre env\xEDos a todos.",
11037
+ notes: ["--image se publica en img.ghosty.studio (WebP 1200 px). --to vac\xEDo = todos. send sin --at la manda ya (pregunta); --at '' quita la programaci\xF3n."]
11038
+ },
10948
11039
  spaces: {
10949
11040
  summary: "Operaciones del staff de Ghosty sobre un espacio: mover \xABpagado hasta\xBB y renombrar su slug. Cada una queda en la bit\xE1cora del espacio.",
10950
11041
  flags: [
@@ -10975,6 +11066,7 @@ var HELPS = {
10975
11066
  "ghosty agents doctor <agent-id> [--verbose]",
10976
11067
  "ghosty agents docs ls|grant|revoke <agent-id> [doc\u2026] [--write] [--no-deliver] [--like AGENT]",
10977
11068
  "ghosty agents docs add <agent-id> --url URL | --file PATH [--name N] [--description TEXT] [--grant read|write|no] [--no-deliver]",
11069
+ "ghosty agents docs dedupe [<keep-id> <drop-id>] [--dry-run] # your account: merge copies with the same content",
10978
11070
  "ghosty agents docs replace <agent-id> <doc> --url URL | --file PATH [--note TEXT] # new version: keeps name, access and the agents that use it",
10979
11071
  "ghosty agents docs history|restore <agent-id> <doc> [n]",
10980
11072
  "ghosty agents share <agent-id> <email> [--role editor|admin]",
@@ -11141,7 +11233,7 @@ var HELPS = {
11141
11233
  },
11142
11234
  turns: {
11143
11235
  summary: "An agent's recent turns: what answered, what failed and why, and where one turn's time went.",
11144
- usage: ["ghosty turns ls <agent-id> [--errors | --status STATE | --skipped] [--since 1h] [--limit N]", "ghosty turns ls --workspace <slug> --quota-hits [--days 14]", "ghosty turns show <agent-id> [turn-id | last] [--full]", "ghosty turns cancel <agent-id> <conversation-id> # stop the turn AND its background jobs"],
11236
+ usage: ["ghosty turns ls <agent-id> [--errors | --status STATE | --skipped] [--since 1h] [--limit N]", "ghosty turns ls --workspace <slug> --quota-hits [--days 14]", "ghosty turns show <agent-id> [turn-id | last] [--full]", "ghosty turns cancel <agent-id> <conversation-id> # stop the turn AND its background jobs", "ghosty turns stats [--since 14d] [--all] # staff: real failure rate by engine and error"],
11145
11237
  flags: [
11146
11238
  ["--errors", "only failed turns (same as --status error)"],
11147
11239
  ["--skipped", "ls: WhatsApp Business customer messages that did NOT open a turn, and why (operator active, records only, paused, reaction, channel off, no balance)"],
@@ -11150,6 +11242,7 @@ var HELPS = {
11150
11242
  ["--limit N", "max rows (default 20, max 500)"],
11151
11243
  ["--full", "show: the whole message that came in and the whole answer (default: first 600 chars)"],
11152
11244
  ["--quota-hits", "ls --workspace: messages (WhatsApp Business and Teams) that got no turn because the balance ran out"],
11245
+ ["--all", "stats (staff): also count staff users, the test channel and deleted agents"],
11153
11246
  ["--session S", "ls: only this conversation's turns. Claude turns show \xABresumed\xBB: \u2713 = it kept the SDK session, \u26A0 from scratch (orphan|stale|replaced) = it lost its memory"]
11154
11247
  ],
11155
11248
  examples: ["ghosty turns ls <id> --errors --since 1h", "ghosty turns ls --workspace descti --quota-hits --days 30", "ghosty turns ls <id> --skipped --since 1d", "ghosty turns ls <id> --json --limit 100", "ghosty turns show <id> # last turn: each tool, its seconds, output tokens"],
@@ -11204,8 +11297,23 @@ var HELPS = {
11204
11297
  },
11205
11298
  chat: {
11206
11299
  summary: "Stream a conversation; with no message it is interactive.",
11207
- usage: ['ghosty chat <agent-id> ["message"] [--conversation ID] [--media FILE]\u2026', "ghosty conversations ls <agent-id>", "ghosty conversations show <agent-id> <conversation-id> [--limit 20]"],
11208
- examples: ['ghosty chat <id> "hola"', "ghosty chat <id>", 'ghosty chat <id> "p\xE1salo a PDF" --media foto.jpg'],
11300
+ usage: [
11301
+ 'ghosty chat <agent-id> ["message"] [--conversation ID] [--media FILE]\u2026 [--file ID[,ID\u2026]]',
11302
+ "ghosty conversations ls <agent-id>",
11303
+ "ghosty conversations show <agent-id> <conversation-id> [--limit 20]",
11304
+ "ghosty conversations archive|rm <agent-id> --kind test|channel [--before 7d] [--dry-run] [--yes]",
11305
+ "ghosty conversations ls --user <email> [--since 30d] [--surface android] # staff",
11306
+ "ghosty conversations show <agent-id> <conversation-id> --why # staff: who sees it and why"
11307
+ ],
11308
+ flags: [
11309
+ ["--media FILE", "chat: attach a local file (repeatable)"],
11310
+ ["--file ID", "chat: attach from your files (ghosty me files ls --q \u2026), comma-separated"],
11311
+ ["--kind K", "archive|rm: test (test chats) or channel (customers: archive only, their memory keeps serving them)"],
11312
+ ["--before T", "archive|rm: only conversations with no message since T (7d, 12h or ISO)"],
11313
+ ["--user EMAIL", "ls/show (Ghosty staff): that person's conversations in any agent; logged"],
11314
+ ["--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"]
11315
+ ],
11316
+ 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"],
11209
11317
  learn: `${REF}#conversaciones`
11210
11318
  },
11211
11319
  schedule: {
@@ -11217,8 +11325,8 @@ var HELPS = {
11217
11325
  },
11218
11326
  me: {
11219
11327
  summary: "Your account: files and usage.",
11220
- usage: ["ghosty me files ls|upload|rm [--kind image|video|audio|document|other]", "ghosty usage"],
11221
- examples: ["ghosty me files ls --kind document"],
11328
+ usage: ["ghosty me files ls [--q TEXT] [--kind image|video|audio|document|other] [--cursor C]", "ghosty me files get <file-id> [--out FILE]", "ghosty me files upload <file> | rm <file-id>", "ghosty usage"],
11329
+ examples: ["ghosty me files ls --kind document", "ghosty me files ls --q contrato", "ghosty me files get <id> --out contrato.pdf", 'ghosty chat <agent> "res\xFAmelo" --file <id>'],
11222
11330
  learn: `${REF}#tu-cuenta`
11223
11331
  }
11224
11332
  };
@@ -11253,7 +11361,7 @@ HELPS.channels = {
11253
11361
  "ghosty channels wa status|pause <agent-id>",
11254
11362
  "ghosty channels wa groups ls <agent-id> [--cached]",
11255
11363
  "ghosty channels wa groups enable|disable <agent-id> <group> [--role equipo|cliente] [--dry-run] [--yes]",
11256
- "ghosty channels wa groups set <agent-id> <group> --role equipo|cliente [--dry-run]",
11364
+ 'ghosty channels wa groups set <agent-id> <group> [--role equipo|cliente] [--name "New name"] [--dry-run]',
11257
11365
  'ghosty channels wa groups create <agent-id> "Name" [--role equipo|cliente]'
11258
11366
  ],
11259
11367
  flags: [
@@ -11261,6 +11369,7 @@ HELPS.channels = {
11261
11369
  ["--yes", "enable / groups enable: skip the confirmation (required without a terminal)"],
11262
11370
  ["--role R", "groups: equipo (the team: writes to databases, admin tools) or cliente (like any customer)"],
11263
11371
  ["--cached", "groups ls: don't ask WhatsApp for the live list (faster; may miss new groups)"],
11372
+ ["--name N", "groups set: the name gs shows (e.g. a group saved with its jid as name); WhatsApp's subject doesn't change"],
11264
11373
  ["--integration ID", "link/unlink/history: the WhatsApp Business number's Integration id in Formmy"],
11265
11374
  ["--email E", "integrations (staff): list the numbers of another Formmy account"],
11266
11375
  ["--days N", "history: conversations active in the last N days (1-30, default 7)"],
@@ -11460,6 +11569,27 @@ HELPS.demos = {
11460
11569
  notes: ["try counts against the demo's caps (messages per visitor, per IP per day) and shows up in its numbers and CRM.", "history is Ghosty staff only: visitors' chats are not shown to the owner."],
11461
11570
  learn: `${REF}#demo-publico-agents-demo`
11462
11571
  };
11572
+ HELPS.push = {
11573
+ summary: "Ghosty staff: which pushes were sent to someone (turn end, permission, escalate, document\u2026), to which device and what Apple/Google answered. Logged since 2026-10-01, kept 30 days.",
11574
+ usage: ["ghosty push log --user <email> [--since 2d] [--limit 100]"],
11575
+ examples: ["ghosty push log --user brenda@siiqtec.com --since 1d"],
11576
+ notes: ["thread shows the conversation's kind: a \xABchannel\xBB there means a customer's push reached them."],
11577
+ learn: `${REF}#soporte`
11578
+ };
11579
+ HELPS.novedades = {
11580
+ summary: "Ghosty staff: the Android app's announcements (what's new). Each one needs its own illustration; same brakes as the admin: test on your phone first, push 9\u201321 h CDMX, 72 h between pushes to everyone.",
11581
+ usage: [
11582
+ "ghosty novedades ls",
11583
+ 'ghosty novedades create --title T --body B --image hero.png [--link ruta] [--button "Abrir"] [--to a@x.com,b@y.com] [--publish]',
11584
+ "ghosty novedades set <id> [--title] [--body] [--image FILE] [--link] [--button] [--to] [--publish|--draft]",
11585
+ "ghosty novedades test <id>",
11586
+ 'ghosty novedades send <id> [--at "2026-10-02T12:00"]',
11587
+ "ghosty novedades rm <id>"
11588
+ ],
11589
+ examples: ['ghosty novedades create --title "PDF y audio" --body "Ahora\u2026" --image hero.png --link chats --publish', "ghosty novedades test <id>", 'ghosty novedades send <id> --at "2026-10-02T12:00"'],
11590
+ notes: ["--image is published to img.ghosty.studio (WebP 1200 px). --to empty = everyone. send without --at pushes now (asks); --at '' removes the schedule."],
11591
+ learn: `${REF}#soporte`
11592
+ };
11463
11593
  HELPS.spaces = {
11464
11594
  summary: "Ghosty staff operations on a workspace: move \xABpaid until\xBB and rename its slug. Each one is logged in the workspace's audit.",
11465
11595
  usage: ["ghosty spaces renew <workspace> --until YYYY-MM-DD [--note TEXT] [--dry-run] [--yes]", "ghosty spaces rename <old> <new> [--dry-run] [--yes]"],
@@ -11496,7 +11626,7 @@ function helpText(cmd) {
11496
11626
  }
11497
11627
 
11498
11628
  // src/update.ts
11499
- import { mkdir as mkdir4, readFile as readFile9, writeFile as writeFile5 } from "node:fs/promises";
11629
+ import { mkdir as mkdir4, readFile as readFile9, writeFile as writeFile6 } from "node:fs/promises";
11500
11630
  import { homedir as homedir3 } from "node:os";
11501
11631
  import { dirname as dirname3, join as join6 } from "node:path";
11502
11632
  var FILE = join6(process.env.XDG_CONFIG_HOME || join6(homedir3(), ".config"), "ghosty", "update.json");
@@ -11521,7 +11651,7 @@ async function latestVersion() {
11521
11651
  const latest = (await r.json()).version;
11522
11652
  if (latest) cache2 = { latest, checkedAt: Date.now() };
11523
11653
  await mkdir4(dirname3(FILE), { recursive: true });
11524
- await writeFile5(FILE, JSON.stringify(cache2));
11654
+ await writeFile6(FILE, JSON.stringify(cache2));
11525
11655
  } catch {
11526
11656
  }
11527
11657
  }
@@ -11575,6 +11705,7 @@ function agentArgIndex(pos) {
11575
11705
  case "db":
11576
11706
  return sub === "photos" ? 3 : 2;
11577
11707
  case "turns":
11708
+ return pos[1] === "stats" ? null : 2;
11578
11709
  case "board":
11579
11710
  case "conversations":
11580
11711
  case "schedule":
@@ -11595,7 +11726,7 @@ function agentArgIndex(pos) {
11595
11726
  }
11596
11727
 
11597
11728
  // src/completion.ts
11598
- import { mkdir as mkdir5, readFile as readFile10, writeFile as writeFile6 } from "node:fs/promises";
11729
+ import { mkdir as mkdir5, readFile as readFile10, writeFile as writeFile7 } from "node:fs/promises";
11599
11730
  import { homedir as homedir4 } from "node:os";
11600
11731
  import { join as join7 } from "node:path";
11601
11732
  var GLOBAL_FLAGS = ["--json", "--token", "--profile", "--yes", "--help", "--version", "--lang", "--dry-run"];
@@ -11647,7 +11778,7 @@ async function names() {
11647
11778
  const workspaces = [...new Set(list.map((a) => a.espacio?.tipo === "workspace" ? a.espacio.nombre : void 0).filter((x) => !!x))];
11648
11779
  await mkdir5(join7(cacheFile(), ".."), { recursive: true }).catch(() => {
11649
11780
  });
11650
- await writeFile6(cacheFile(), JSON.stringify({ at: Date.now(), agents, workspaces })).catch(() => {
11781
+ await writeFile7(cacheFile(), JSON.stringify({ at: Date.now(), agents, workspaces })).catch(() => {
11651
11782
  });
11652
11783
  return { agents, workspaces };
11653
11784
  }
@@ -11725,7 +11856,7 @@ function normalizeArgs(pos, v) {
11725
11856
 
11726
11857
  // src/commands/spec.ts
11727
11858
  var import_yaml = __toESM(require_dist(), 1);
11728
- import { mkdir as mkdir6, readdir as readdir4, readFile as readFile11, stat, writeFile as writeFile7 } from "node:fs/promises";
11859
+ import { mkdir as mkdir6, readdir as readdir4, readFile as readFile11, stat, writeFile as writeFile8 } from "node:fs/promises";
11729
11860
  import { join as join8, resolve as resolve2 } from "node:path";
11730
11861
  var enc9 = encodeURIComponent;
11731
11862
  async function agentsExport(id, v) {
@@ -11737,7 +11868,7 @@ async function agentsExport(id, v) {
11737
11868
  }
11738
11869
  const dir = resolve2(v.out);
11739
11870
  await mkdir6(dir, { recursive: true });
11740
- await writeFile7(join8(dir, "agent.yaml"), yaml);
11871
+ await writeFile8(join8(dir, "agent.yaml"), yaml);
11741
11872
  const own = (0, import_yaml.parse)(yaml)["x-ghosty"]?.skills?.own ?? [];
11742
11873
  for (const slug of own) {
11743
11874
  const sk = await api(`/api/v2/agents/${enc9(id)}/skills/${enc9(slug)}`);
@@ -11987,9 +12118,9 @@ ${t("Rename?", "\xBFRenombrar?")}`, { yes: v.yes, expect: plan.from, retry: `gho
11987
12118
  async function workspaceUsage(slug, v) {
11988
12119
  slug = needSpace(slug, "ghosty usage --workspace <slug> --by agent,day --days 14");
11989
12120
  const r = await api(wsPath(slug, `/usage?days=${enc10(v.days ?? "14")}`));
11990
- const cdmx = (iso) => new Date(Date.parse(iso) - 6 * 36e5).toISOString().slice(5, 16).replace("T", " ");
12121
+ const cdmx2 = (iso) => new Date(Date.parse(iso) - 6 * 36e5).toISOString().slice(5, 16).replace("T", " ");
11991
12122
  if (v["quota-hits"]) {
11992
- const rows2 = r.quotaHits.map((q) => ({ at: cdmx(q.at), agent: q.agent, channel: q.channel, from: q.phone ?? q.conversationId.slice(0, 24), message: (q.preview ?? "").slice(0, 60) }));
12123
+ const rows2 = r.quotaHits.map((q) => ({ at: cdmx2(q.at), agent: q.agent, channel: q.channel, from: q.phone ?? q.conversationId.slice(0, 24), message: (q.preview ?? "").slice(0, 60) }));
11993
12124
  const total2 = r.agents.reduce((n, a) => n + a.noQuota, 0);
11994
12125
  return out(r.quotaHits, () => rows2.length ? `${table(rows2, ["at", "agent", "channel", "from", "message"])}
11995
12126
  ${dim(t(`${total2} message(s) unanswered for lack of balance in ${r.days} day(s) (CDMX time)`, `${total2} mensaje(s) sin atender por saldo en ${r.days} d\xEDa(s) (hora CDMX)`))}` : dim(t(`No message ran out of balance in ${r.days} day(s).`, `Ning\xFAn mensaje se qued\xF3 sin saldo en ${r.days} d\xEDa(s).`)));
@@ -12020,7 +12151,7 @@ ${dim(t(`${fmtTokens(total)} billable tokens \xB7 failed = error or stopped \xB7
12020
12151
 
12021
12152
  // src/commands/demos.ts
12022
12153
  import { randomUUID as randomUUID2 } from "node:crypto";
12023
- import { mkdir as mkdir7, readFile as readFile12, writeFile as writeFile8 } from "node:fs/promises";
12154
+ import { mkdir as mkdir7, readFile as readFile12, writeFile as writeFile9 } from "node:fs/promises";
12024
12155
  import { homedir as homedir5 } from "node:os";
12025
12156
  import { dirname as dirname4, join as join9 } from "node:path";
12026
12157
  var enc11 = encodeURIComponent;
@@ -12063,7 +12194,7 @@ async function visitorCookie(base, slug, fresh) {
12063
12194
  if (!cookie) throw new CliError(t("The demo didn't hand out a visitor cookie.", "El demo no entreg\xF3 cookie de visitante."), EXIT.API);
12064
12195
  jar[slug] = cookie;
12065
12196
  await mkdir7(dirname4(jarPath()), { recursive: true });
12066
- await writeFile8(jarPath(), JSON.stringify(jar, null, 2), { mode: 384 });
12197
+ await writeFile9(jarPath(), JSON.stringify(jar, null, 2), { mode: 384 });
12067
12198
  return cookie;
12068
12199
  }
12069
12200
  async function visitorTurn(base, slug, cookie, text) {
@@ -12134,6 +12265,146 @@ async function demosCmd(sub, a1, rest, v) {
12134
12265
  throw usage(t(`Unknown demos subcommand: ${sub ?? "(none)"}.`, `Subcomando de demos desconocido: ${sub ?? "(ninguno)"}.`), 'ghosty demos history <agent> \xB7 ghosty demos try <slug> "message"');
12135
12266
  }
12136
12267
 
12268
+ // src/commands/support.ts
12269
+ import { readFile as readFile13 } from "node:fs/promises";
12270
+ var enc12 = encodeURIComponent;
12271
+ var cdmx = (iso) => new Date(Date.parse(iso) - 6 * 36e5).toISOString().slice(5, 16).replace("T", " ");
12272
+ async function supportThreads(email, v) {
12273
+ const q = new URLSearchParams({ user: email, since: v.since ?? "30d", ...v.surface ? { surface: v.surface } : {} });
12274
+ const r = await api(`/api/v2/support/threads?${q}`);
12275
+ const rows = r.threads.map((x) => ({
12276
+ last: cdmx(x.lastTurn),
12277
+ agent: x.deletedAgent ? dim(t("(deleted)", "(borrado)")) : x.agent ?? x.agentId.slice(0, 8),
12278
+ conversation: x.sessionId,
12279
+ title: (x.title ?? "").slice(0, 40),
12280
+ kind: x.kind ?? "?",
12281
+ listed: x.listed ? t("yes", "s\xED") : "",
12282
+ turns: x.failed ? `${x.turns} (${red(String(x.failed))})` : x.turns,
12283
+ from: x.surfaces.join(",")
12284
+ }));
12285
+ out(r, () => `${bold(r.user)}
12286
+ ${table(rows, ["last", "agent", "conversation", "title", "kind", "listed", "turns", "from"])}
12287
+ ${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"))}`);
12288
+ }
12289
+ async function supportThread(agent, sid, v) {
12290
+ const r = await api(`/api/v2/support/threads/${enc12(agent)}/${enc12(sid)}?tail=${enc12(v.limit ?? "40")}`);
12291
+ out(r, () => {
12292
+ const w = r.why;
12293
+ const who2 = (x, label) => x ? ` ${label} ${x.email}: ${x.listed ? lavender(t("in their list/search/push", "en su lista/b\xFAsqueda/push")) : dim(t("not listed", "no sale en su lista"))} \xB7 ${t("by link", "por liga")}: ${x.open || t("no", "no")}` : "";
12294
+ const why = [
12295
+ `${bold(r.agent.name ?? r.agent.id)}${r.agent.deleted ? red(t(" (deleted)", " (borrado)")) : ""} \xB7 ${r.title ?? ""}`,
12296
+ ` kind ${bold(w.kind ?? "?")} \xB7 ${t("thread of", "hilo de")} ${w.user ?? "\u2014"}${w.archived ? dim(` \xB7 ${t("archived", "archivado")} ${cdmx(w.archived)}`) : ""}`,
12297
+ w.lastTurn ? dim(` ${t("last turn", "\xFAltimo turno")}: canal ${w.lastTurn.canal ?? "?"} \xB7 origen ${w.lastTurn.origen ?? "?"} \xB7 ${w.lastTurn.surface ?? "?"} \xB7 ${w.lastTurn.userId ?? "?"}`) : "",
12298
+ who2(w.owner, t("agent owner", "due\xF1o del agente")),
12299
+ who2(w.threadUser, t("thread user", "usuario del hilo")),
12300
+ dim(` ${serverText(w.rule)}`)
12301
+ ].filter(Boolean);
12302
+ if (v.why) return why.join("\n");
12303
+ const msgs = r.messages.map((m) => `${dim(cdmx(m.at))} ${bold(m.role.padEnd(9))} ${m.text.replace(/\s+/g, " ").slice(0, 400)}`);
12304
+ return [...why, "", ...msgs].join("\n");
12305
+ });
12306
+ }
12307
+ async function cleanupConversations(action, agent, v) {
12308
+ const ex = `ghosty conversations ${action} <agent> --kind ${action === "rm" ? "test" : "test|channel"} [--before 7d] [--dry-run]`;
12309
+ if (!agent) throw usage(t("Missing agent.", "Falta el agente."), ex);
12310
+ if (v.kind !== "test" && v.kind !== "channel") throw usage(t("--kind takes test or channel.", "--kind acepta test o channel."), ex);
12311
+ const path = `/api/v2/agents/${enc12(agent)}/conversations/cleanup`;
12312
+ const body = { kind: v.kind, action, ...v.before ? { before: v.before } : {} };
12313
+ const plan = await api(path, { method: "POST", body: { ...body, dryRun: true } });
12314
+ 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)");
12315
+ const line2 = t(`${plan.threads} ${v.kind} conversation(s), ${plan.messages} message(s) to ${verb}`, `${plan.threads} conversaci\xF3n(es) ${v.kind}, ${plan.messages} mensaje(s) para ${verb}`);
12316
+ if (v["dry-run"] || !plan.threads) return out(plan, () => `${line2}${plan.sample.length ? `
12317
+ ${dim(plan.sample.join("\n"))}` : ""}
12318
+ ${dim(t("Dry run: nothing changed.", "Simulaci\xF3n: no cambi\xF3 nada."))}`);
12319
+ requireYesIfHeadless(v.yes, `ghosty conversations ${action} ${agent} --kind ${v.kind}${v.before ? ` --before ${v.before}` : ""}`);
12320
+ await confirm(`${line2}
12321
+ ${t("Go ahead?", "\xBFSigo?")}`, { yes: v.yes, retry: `ghosty conversations ${action} ${agent} --kind ${v.kind}` });
12322
+ const r = await api(path, { method: "POST", body });
12323
+ out(r, () => `${lavender("\u2713")} ${t(`${r.threads} conversation(s) ${action === "rm" ? "deleted" : "archived"}.`, `${r.threads} conversaci\xF3n(es) ${action === "rm" ? "borradas" : "archivadas"}.`)}`);
12324
+ }
12325
+ async function turnStats(v) {
12326
+ const r = await api(`/api/v2/support/turn-stats?since=${enc12(v.since ?? "14d")}${v.all ? "&all=1" : ""}`);
12327
+ out(
12328
+ r,
12329
+ () => [
12330
+ `${bold(`${r.failRate}%`)} ${t("of turns failed", "de los turnos fallaron")} ${dim(`(${r.failed}/${r.total}, ${v.since ?? "14d"}${r.filtered ? t(" \xB7 without staff, test channel or deleted agents (--all to include)", " \xB7 sin staff, canal prueba ni agentes borrados (--all los incluye)") : ""})`)}`,
12331
+ dim(Object.entries(r.byState).map(([k, n]) => `${k} ${n}`).join(" \xB7 ")),
12332
+ "",
12333
+ table(r.byEngine.map((e) => ({ engine: e.engine, turns: e.turns, failed: e.failed, rate: `${e.failRate}%` })), ["engine", "turns", "failed", "rate"]),
12334
+ ...r.topErrors.length ? ["", table(r.topErrors.map((e) => ({ n: e.n, error: e.error })), ["n", "error"])] : []
12335
+ ].join("\n")
12336
+ );
12337
+ }
12338
+ async function pushLog(email, v) {
12339
+ if (!email) throw usage(t("Missing --user.", "Falta --user."), "ghosty push log --user ana@empresa.com [--since 2d]");
12340
+ const r = await api(`/api/v2/support/push-log?user=${enc12(email)}&since=${enc12(v.since ?? "2d")}${v.limit ? `&limit=${enc12(v.limit)}` : ""}`);
12341
+ 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 ?? "" }));
12342
+ out(r, () => rows.length ? `${bold(r.user)}
12343
+ ${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).`)));
12344
+ }
12345
+ async function novedadesCmd(sub, id, v) {
12346
+ const base = "/api/v2/support/announcements";
12347
+ const ex = 'ghosty novedades ls \xB7 create --title T --body B --image hero.png [--link ruta] [--button "Abrir"] [--to a@x.com,b@y.com] [--publish] \xB7 set <id> \u2026 \xB7 test <id> \xB7 send <id> [--at "2026-10-02T12:00"] \xB7 rm <id>';
12348
+ const fields = async () => ({
12349
+ ...v.title !== void 0 ? { title: v.title } : {},
12350
+ ...v.body !== void 0 ? { body: v.body } : {},
12351
+ ...v.image ? { image: (await readFile13(v.image)).toString("base64") } : {},
12352
+ ...v.link !== void 0 ? { enlace: v.link || null } : {},
12353
+ ...v.button !== void 0 ? { boton: v.button || null } : {},
12354
+ ...v.to !== void 0 ? { destinatarios: v.to || null } : {},
12355
+ ...v.publish ? { published: true } : v.draft ? { published: false } : {}
12356
+ });
12357
+ const line2 = (a) => `${bold(a.title)} ${dim(a.id)}
12358
+ ${a.published ? lavender(t("published", "publicada")) : dim(t("draft", "borrador"))} \xB7 ${a.pushedAt ? t(`pushed ${cdmx(a.pushedAt)} (${a.pushCount})`, `enviada ${cdmx(a.pushedAt)} (${a.pushCount})`) : a.schedule ? t(`push scheduled ${cdmx(a.schedule)} CDMX`, `push programado ${cdmx(a.schedule)} CDMX`) : t("not pushed", "sin enviar")} \xB7 ${a.destinatarios ? t(`to ${a.destinatarios.split(",").length}`, `para ${a.destinatarios.split(",").length}`) : t("everyone", "todos")}`;
12359
+ switch (sub ?? "ls") {
12360
+ case "ls":
12361
+ case "list": {
12362
+ const r = await api(base);
12363
+ return out(r, () => r.announcements.map(line2).join("\n") || dim(t("No announcements.", "Sin novedades.")));
12364
+ }
12365
+ case "create":
12366
+ case "new": {
12367
+ if (!v.title || !v.body) throw usage(t("Missing --title and --body.", "Faltan --title y --body."), ex);
12368
+ const r = await api(base, { method: "POST", body: await fields() });
12369
+ return out(r, () => `${lavender("\u2713")} ${line2(r)}
12370
+ ${dim(t(`Try it on your phone: ghosty novedades test ${r.id}`, `Pru\xE9bala en tu tel\xE9fono: ghosty novedades test ${r.id}`))}`);
12371
+ }
12372
+ case "set": {
12373
+ if (!id) throw usage(t("Missing id.", "Falta el id."), ex);
12374
+ const b = await fields();
12375
+ if (!Object.keys(b).length) throw usage(t("Nothing to change.", "Nada que cambiar."), ex);
12376
+ const r = await api(`${base}/${enc12(id)}`, { method: "PATCH", body: b });
12377
+ return out(r, () => `${lavender("\u2713")} ${line2(r)}`);
12378
+ }
12379
+ case "test": {
12380
+ if (!id) throw usage(t("Missing id.", "Falta el id."), ex);
12381
+ const r = await api(`${base}/${enc12(id)}`, { method: "POST", body: { push: "test" } });
12382
+ return out(r, () => `${lavender("\u2713")} ${t(`Sent to your phone (${r.enviados} device(s)).`, `Enviada a tu tel\xE9fono (${r.enviados} dispositivo(s)).`)}`);
12383
+ }
12384
+ case "send":
12385
+ case "schedule": {
12386
+ if (!id) throw usage(t("Missing id.", "Falta el id."), ex);
12387
+ if (v.at !== void 0) {
12388
+ const r2 = await api(`${base}/${enc12(id)}`, { method: "PATCH", body: { schedule: v.at || null } });
12389
+ return out(r2, () => `${lavender("\u2713")} ${line2(r2)}`);
12390
+ }
12391
+ requireYesIfHeadless(v.yes, `ghosty novedades send ${id}`);
12392
+ 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}` });
12393
+ const r = await api(`${base}/${enc12(id)}`, { method: "POST", body: { push: "all" } });
12394
+ return out(r, () => `${lavender("\u2713")} ${t(`Pushed to ${r.enviados} device(s).`, `Enviada a ${r.enviados} dispositivo(s).`)}`);
12395
+ }
12396
+ case "rm":
12397
+ case "delete": {
12398
+ if (!id) throw usage(t("Missing id.", "Falta el id."), ex);
12399
+ requireYesIfHeadless(v.yes, `ghosty novedades rm ${id}`);
12400
+ await confirm(t(`Delete announcement ${id}?`, `\xBFBorrar la novedad ${id}?`), { yes: v.yes, retry: `ghosty novedades rm ${id}` });
12401
+ return out(await api(`${base}/${enc12(id)}`, { method: "DELETE" }), () => `${lavender("\u2713")} ${t("Deleted.", "Borrada.")}`);
12402
+ }
12403
+ default:
12404
+ throw usage(t(`Unknown novedades subcommand: ${sub}.`, `Subcomando de novedades desconocido: ${sub}.`), ex);
12405
+ }
12406
+ }
12407
+
12137
12408
  // src/index.ts
12138
12409
  var HELP_EN = `${bold("Usage:")} ghosty <command> [options]
12139
12410
 
@@ -12175,6 +12446,7 @@ ${bold("Agents")}
12175
12446
  agents docs ls|grant|revoke <id> [doc] Documents it reads and may send [--write] [--no-deliver]
12176
12447
  agents docs grant <id> A B\u2026 Grant several documents at once [--like AGENT] copies another agent's
12177
12448
  agents docs add <id> Upload a document and grant it --url URL | --file PATH [--description] [--grant read|write|no]
12449
+ agents docs dedupe Your account: merge documents with the same content (keeps every agent's access) [--dry-run] \xB7 dedupe <keep> <drop>
12178
12450
  agents docs replace <id> <doc> New version (keeps name and access) --url URL | --file PATH [--note]
12179
12451
  agents docs history|restore <id> <doc> [n] Its versions; restore n as a new version
12180
12452
  agents share|unshare <id> <email> Share it without handing it over [--role editor|admin] \xB7 agents shares <id> lists them
@@ -12201,12 +12473,19 @@ ${bold("Configure an agent")}
12201
12473
  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]
12202
12474
  channels wa pair <id> --phone N Pair a personal WhatsApp by code (no QR)
12203
12475
  channels wa status|pause <id> Personal WhatsApp: how it is / pause it (keeps the session)
12204
- channels wa groups ls|enable|disable|set|create <id> [group] Personal WhatsApp groups it answers, and as whom [--role equipo|cliente] [--dry-run]
12476
+ channels wa groups ls|enable|disable|set|create <id> [group] Personal WhatsApp groups it answers, and as whom [--role equipo|cliente] [--name "New name"] [--dry-run]
12205
12477
  boards ls|create|assign|move-cards|rm A workspace's boards: create (--columns-from), where an agent sends cards, move cards
12206
12478
  cards ls <id> Recent cards on its sales board [--q TEXT] [--paused] [--limit 20]
12207
12479
  cards show <id> <card> A card's thread: who wrote, text, attachments and whether gs kept a copy (folio, phone or id)
12208
12480
  cards archive <id> Archive cards in bulk (reversible) --integration X | --column X | --before 30d [--dry-run]
12209
12481
 
12482
+ ${bold("Support")} ${dim("(Ghosty staff)")}
12483
+ conversations ls --user EMAIL Someone's conversations in any agent [--since 30d] [--surface android]
12484
+ conversations show <id> <conv> --why A conversation: who sees it in their list / by link, and why
12485
+ turns stats Real failure rate by engine and error [--since 14d] [--all]
12486
+ push log --user EMAIL Pushes sent to someone and what Apple/Google answered [--since 2d]
12487
+ novedades ls|create|set|test|send|rm Android app announcements [--title] [--body] [--image FILE] [--link] [--to] [--publish] [--at]
12488
+
12210
12489
  ${bold("Demos")}
12211
12490
  demos try <slug> "msg" ["msg2"\u2026] Try a public demo as a visitor (same thread across runs) [--new] [--recycle-between --agent X]
12212
12491
  demos history <id> What visitors did: turns, messages, files (Ghosty staff) [--since 2d] [--session web:\u2026] [--timeline] [--full]
@@ -12215,9 +12494,10 @@ ${bold("Talk to an agent")}
12215
12494
  try <id> "message" One test turn, prints the full answer [--session] [--reset] [--media FILE: attach, as from the app]
12216
12495
  --waba --session PHONE \u2026through the real WhatsApp path, nothing sent; lists what it would send (tools too) [--media FILE [--as-formmy]] [--history FILE]
12217
12496
  --replay FILE --out DIR Replay real conversations; report written locally [--max 15] [--concurrency 8] [--resume]
12218
- chat <id> ["message"] Stream a conversation (no message = interactive) [--conversation ID] [--media FILE]
12497
+ chat <id> ["message"] Stream a conversation (no message = interactive) [--conversation ID] [--media FILE] [--file ID: from your files]
12219
12498
  conversations ls <id> List conversations
12220
12499
  conversations show <id> <conversation> What was said [--limit 20] (0 = all)
12500
+ conversations archive|rm <id> --kind test|channel Clean its history (rm: tests only) [--before 7d] [--dry-run]
12221
12501
  turns ls <id> Recent turns and why they failed [--errors] [--skipped] [--since 1h] [--limit N]
12222
12502
  turns ls --workspace SLUG --quota-hits Messages that got no turn because the balance ran out [--days 14]
12223
12503
  turns show <id> [turn] What came in, what it answered, and where the time went (default: last) [--full]
@@ -12233,7 +12513,7 @@ ${bold("Partners")} ${dim("(give an agent to each of YOUR customers \u2014 see:
12233
12513
  partner ls | enable | credentials | agent | tenants | audit | try
12234
12514
 
12235
12515
  ${bold("Your account")}
12236
- me files ls|upload|rm Files in your account [--kind image|video|audio|document|other]
12516
+ me files ls|get|upload|rm Files in your account [--q TEXT] [--kind image|video|audio|document|other] [--cursor C] \xB7 get <id> [--out FILE]
12237
12517
  credentials ls|set|rm [provider] Provider keys in your vault (hidden prompt, stdin or --from-env VAR; never as an argument)
12238
12518
  docs [topic] Open the docs
12239
12519
 
@@ -12290,6 +12570,7 @@ ${bold("Agentes")}
12290
12570
  agents docs ls|grant|revoke <id> [doc] Documentos que lee y puede enviar [--write] [--no-deliver]
12291
12571
  agents docs grant <id> A B\u2026 Da varios documentos a la vez [--like AGENTE] copia los de otro agente
12292
12572
  agents docs add <id> Sube un documento y d\xE1selo --url URL | --file RUTA [--description] [--grant read|write|no]
12573
+ agents docs dedupe Tu cuenta: une los documentos con el mismo contenido (conserva el acceso de cada agente) [--dry-run] \xB7 dedupe <queda> <se-va>
12293
12574
  agents docs replace <id> <doc> Versi\xF3n nueva (conserva nombre y accesos) --url URL | --file RUTA [--note]
12294
12575
  agents docs history|restore <id> <doc> [n] Sus versiones; restaura la n como versi\xF3n nueva
12295
12576
  agents share|unshare <id> <correo> Comp\xE1rtelo sin cederlo [--role editor|admin] \xB7 agents shares <id> los lista
@@ -12316,12 +12597,19 @@ ${bold("Configurar un agente")}
12316
12597
  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]
12317
12598
  channels wa pair <id> --phone N Vincula un WhatsApp personal con c\xF3digo (sin QR)
12318
12599
  channels wa status|pause <id> WhatsApp personal: c\xF3mo est\xE1 / pausarlo (conserva la sesi\xF3n)
12319
- channels wa groups ls|enable|disable|set|create <id> [grupo] Grupos de WhatsApp personal que contesta, y como qui\xE9n [--role equipo|cliente] [--dry-run]
12600
+ channels wa groups ls|enable|disable|set|create <id> [grupo] Grupos de WhatsApp personal que contesta, y como qui\xE9n [--role equipo|cliente] [--name "Nombre nuevo"] [--dry-run]
12320
12601
  boards ls|create|assign|move-cards|rm Los tableros de un espacio: crear (--columns-from), a d\xF3nde manda tarjetas un agente, mover tarjetas
12321
12602
  cards ls <id> Tarjetas recientes de su tablero de ventas [--q TEXTO] [--paused] [--limit 20]
12322
12603
  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)
12323
12604
  cards archive <id> Archiva tarjetas en bloque (reversible) --integration X | --column X | --before 30d [--dry-run]
12324
12605
 
12606
+ ${bold("Soporte")} ${dim("(staff de Ghosty)")}
12607
+ conversations ls --user CORREO Las conversaciones de alguien en cualquier agente [--since 30d] [--surface android]
12608
+ conversations show <id> <conv> --why Una conversaci\xF3n: qui\xE9n la ve en su lista / por liga, y por qu\xE9
12609
+ turns stats Tasa real de fallos por motor y por error [--since 14d] [--all]
12610
+ push log --user CORREO Avisos que se le mandaron a alguien y qu\xE9 contest\xF3 Apple/Google [--since 2d]
12611
+ novedades ls|create|set|test|send|rm Novedades de la app Android [--title] [--body] [--image ARCHIVO] [--link] [--to] [--publish] [--at]
12612
+
12325
12613
  ${bold("Demos")}
12326
12614
  demos try <slug> "msg" ["msg2"\u2026] Prueba un demo p\xFAblico como visitante (mismo hilo entre corridas) [--new] [--recycle-between --agent X]
12327
12615
  demos history <id> Qu\xE9 hicieron los visitantes: turnos, mensajes, archivos (staff de Ghosty) [--since 2d] [--session web:\u2026] [--timeline] [--full]
@@ -12330,9 +12618,10 @@ ${bold("Hablar con un agente")}
12330
12618
  try <id> "mensaje" Un turno de prueba; imprime la respuesta completa [--session] [--reset] [--media ARCHIVO: adjunto, como desde la app]
12331
12619
  --waba --session TEL \u2026por el camino real de WhatsApp, sin enviar nada; lista lo que mandar\xEDa (tools tambi\xE9n) [--media ARCHIVO [--as-formmy]] [--history ARCHIVO]
12332
12620
  --replay ARCHIVO --out DIR Repite conversaciones reales; el reporte se escribe local [--max 15] [--concurrency 8] [--resume]
12333
- chat <id> ["mensaje"] Conversaci\xF3n en streaming (sin mensaje = interactivo) [--conversation ID] [--media ARCHIVO]
12621
+ chat <id> ["mensaje"] Conversaci\xF3n en streaming (sin mensaje = interactivo) [--conversation ID] [--media ARCHIVO] [--file ID: de tus archivos]
12334
12622
  conversations ls <id> Lista conversaciones
12335
12623
  conversations show <id> <conversaci\xF3n> Qu\xE9 se dijo [--limit 20] (0 = todo)
12624
+ conversations archive|rm <id> --kind test|channel Limpia su historial (rm: s\xF3lo pruebas) [--before 7d] [--dry-run]
12336
12625
  turns ls <id> Turnos recientes y por qu\xE9 fallaron [--errors] [--skipped] [--since 1h] [--limit N]
12337
12626
  turns ls --workspace SLUG --quota-hits Mensajes que no abrieron turno porque se acab\xF3 el saldo [--days 14]
12338
12627
  turns show <id> [turno] Qu\xE9 lleg\xF3, qu\xE9 contest\xF3 y en qu\xE9 se fue el tiempo (default: el \xFAltimo) [--full]
@@ -12348,7 +12637,7 @@ ${bold("Partners")} ${dim("(dale un agente a cada uno de TUS clientes \u2014 ve
12348
12637
  partner ls | enable | credentials | agent | tenants | audit | try
12349
12638
 
12350
12639
  ${bold("Tu cuenta")}
12351
- me files ls|upload|rm Archivos de tu cuenta [--kind image|video|audio|document|other]
12640
+ me files ls|get|upload|rm Archivos de tu cuenta [--q TEXTO] [--kind image|video|audio|document|other] [--cursor C] \xB7 get <id> [--out ARCHIVO]
12352
12641
  credentials ls|set|rm [proveedor] Llaves de proveedor en tu b\xF3veda (prompt oculto, stdin o --from-env VAR; nunca como argumento)
12353
12642
  docs [tema] Abre la documentaci\xF3n
12354
12643
 
@@ -12365,7 +12654,7 @@ ${bold("Opciones globales")}
12365
12654
  ${bold("C\xF3digos de salida")} 0 bien \xB7 1 error de la API \xB7 2 uso incorrecto \xB7 3 sin sesi\xF3n
12366
12655
  ${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.")}
12367
12656
  ${dim("Documentaci\xF3n: https://www.ghosty.studio/docs/cli")}`;
12368
- var COMMANDS = ["login", "logout", "whoami", "usage", "use", "apply", "plan", "engines", "bags", "spaces", "demos", "completion", "cards", "agents", "files", "skills", "mcp", "dbs", "turns", "try", "chat", "conversations", "schedule", "partner", "me", "channels", "board", "boards", "credentials", "docs", "help"];
12657
+ var COMMANDS = ["login", "logout", "whoami", "usage", "use", "apply", "plan", "engines", "bags", "spaces", "demos", "push", "novedades", "completion", "cards", "agents", "files", "skills", "mcp", "dbs", "turns", "try", "chat", "conversations", "schedule", "partner", "me", "channels", "board", "boards", "credentials", "docs", "help"];
12369
12658
  var AGENTS_SUBS = ["ls", "create", "get", "set", "export", "doctor", "docs", "share", "unshare", "shares", "rm", "restart", "demo", "box"];
12370
12659
  function unknown(what, input, known, helpCmd) {
12371
12660
  const s = suggest(input, known);
@@ -12538,6 +12827,15 @@ async function main() {
12538
12827
  models: { type: "boolean" },
12539
12828
  teams: { type: "string" },
12540
12829
  owner: { type: "string" },
12830
+ cursor: { type: "string" },
12831
+ user: { type: "string" },
12832
+ why: { type: "boolean" },
12833
+ surface: { type: "string" },
12834
+ image: { type: "string" },
12835
+ link: { type: "string" },
12836
+ button: { type: "string" },
12837
+ publish: { type: "boolean" },
12838
+ draft: { type: "boolean" },
12541
12839
  ids: { type: "boolean" },
12542
12840
  force: { type: "boolean" },
12543
12841
  reenv: { type: "boolean" },
@@ -12554,7 +12852,7 @@ async function main() {
12554
12852
  const tokens = splitTokens(pos, v);
12555
12853
  globals.token = tokens.session;
12556
12854
  globals.profile = v.profile;
12557
- if (v.version) return out({ version: "0.29.0" }, () => "0.29.0");
12855
+ if (v.version) return out({ version: "0.30.0" }, () => "0.30.0");
12558
12856
  for (const n of normalizeArgs(pos, v)) if (!globals.json) info(dim(n));
12559
12857
  const context = (await readConfig()).workspace;
12560
12858
  if (!explicitToken()?.startsWith("gat_")) {
@@ -12632,7 +12930,14 @@ async function main() {
12632
12930
  return dbsCmd(sub, a1, a2, pos.slice(4).filter((x) => x !== void 0), v);
12633
12931
  case "turns":
12634
12932
  if (v["quota-hits"]) return workspaceUsage(v.workspace ?? context, v);
12933
+ if (sub === "stats") return turnStats(v);
12635
12934
  return turnsCmd(sub, a1, a2, v);
12935
+ case "push":
12936
+ if (sub !== "log") throw usage(t("Unknown push subcommand.", "Subcomando de push desconocido."), "ghosty push log --user <email> [--since 2d]");
12937
+ return pushLog(v.user, v);
12938
+ case "novedades":
12939
+ case "announcements":
12940
+ return novedadesCmd(sub, a1, v);
12636
12941
  case "board":
12637
12942
  return boardCmd(sub, a1, v, a2);
12638
12943
  case "boards":
@@ -12648,8 +12953,11 @@ async function main() {
12648
12953
  case "chat":
12649
12954
  return chat(sub, a1, v);
12650
12955
  case "conversations":
12956
+ if ((sub === "ls" || sub === "list") && v.user) return supportThreads(v.user, v);
12651
12957
  if (sub === "ls" || sub === "list") return conversationsLs(a1);
12958
+ if (sub === "show" && (v.why || v.user)) return supportThread(a1, a2, v);
12652
12959
  if (sub === "show") return conversationsShow(a1, a2, v);
12960
+ if (sub === "archive" || sub === "rm") return cleanupConversations(sub, a1, v);
12653
12961
  throw usage(t(`Unknown conversations subcommand: ${sub ?? "(none)"}.`, `Subcomando de conversations desconocido: ${sub ?? "(ninguno)"}.`), t("Try: ghosty conversations ls <agent-id> \xB7 ghosty conversations show <agent-id> <conversation-id>", "Prueba: ghosty conversations ls <agent-id> \xB7 ghosty conversations show <agent-id> <conversation-id>"));
12654
12962
  case "schedule":
12655
12963
  return scheduleCmd(sub, a1, a2, v);
@@ -12686,7 +12994,7 @@ async function main() {
12686
12994
  throw unknown(cmd, cmd, COMMANDS, "ghosty --help");
12687
12995
  }
12688
12996
  }
12689
- var notice = updateNotice("0.29.0").catch(() => null);
12997
+ var notice = updateNotice("0.30.0").catch(() => null);
12690
12998
  main().then(
12691
12999
  async () => {
12692
13000
  const n = await notice;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghostystudio/cli",
3
- "version": "0.29.0",
3
+ "version": "0.30.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": {