@ghostystudio/cli 0.59.0 → 0.62.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 +13 -0
  2. package/dist/index.js +132 -7
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -31,6 +31,7 @@ ghosty agents set <id> [--name] [--engine] [--model] [--prompt | --prompt-file F
31
31
  ghosty agents set <id> --prompt-base <base-id> --prompt-overlay overlay.json [--dry-run] # prompt = another agent's + edits, kept in sync
32
32
  ghosty agents share|unshare <id> <email> [--role editor|admin] # share without handing it over · agents shares <id> lists them
33
33
  ghosty agents teams ls|on|off|rm <id> [workspace] [--handle x] # which Teams workspaces it answers in, and its @handle there
34
+ ghosty agents archive|restore <id> # archive: stops answering, deleted after 7 days unless restored
34
35
  ghosty agents rm <id> [--from-workspace] [--yes] # --from-workspace: turn it off in Teams first
35
36
  ghosty agents set <id> [--tool wa:tool=off,…] [--bag NAME|none] [--avatar URL|FILE]
36
37
  ghosty agents restart <id>
@@ -109,6 +110,18 @@ Every command has its own help with examples: `ghosty <command> --help` or `ghos
109
110
 
110
111
  Exit codes: `0` ok · `1` API error · `2` usage error · `3` not signed in.
111
112
 
113
+ ### Changes in 0.62.0
114
+
115
+ - `support link <id> <owner/repo#12 | URL>` (staff): ties a case to the GitHub issue or PR that fixes it; when it closes, every tied customer gets "fixed" by email.
116
+
117
+ ### Changes in 0.61.0
118
+
119
+ - `support open --title T --body TEXT`: ask Ghosty for help; we answer by email and replying to that email adds to the case. `support ls | show <id> | reply <id>` follow your cases. Staff: `support ls|show|reply --all`, `support resolve|reopen <id>`.
120
+
121
+ ### Changes in 0.60.0
122
+
123
+ - `agents archive <id>` / `agents restore <id>`: archive an agent (it stops answering on every channel and leaves the lists; deleted automatically after 7 days) or bring it back as it was. `agents ls --archived` lists archived agents and when they get deleted.
124
+
112
125
  ### Changes in 0.59.0
113
126
 
114
127
  - `spaces archive <workspace>` / `spaces restore <workspace>`: archive a workspace (same as Settings → Archive: it leaves `/app` and `spaces ls`; agents and data stay) or bring it back. Owner or staff; dry run first, archiving asks you to type the slug; logged as `workspace.archive` / `workspace.restore`.
package/dist/index.js CHANGED
@@ -8478,7 +8478,7 @@ async function postLong(path, body, timeoutMs = 9e5) {
8478
8478
  const req = httpsRequest(`${base}${path}`, {
8479
8479
  method: "POST",
8480
8480
  timeout: timeoutMs,
8481
- headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.59.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
8481
+ headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.62.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
8482
8482
  }, (res) => {
8483
8483
  let buf = "";
8484
8484
  res.on("data", (c) => buf += c);
@@ -8520,7 +8520,7 @@ async function request(path, opts = {}, retried = false) {
8520
8520
  method: opts.method ?? (opts.body !== void 0 || opts.raw !== void 0 ? "POST" : "GET"),
8521
8521
  headers: {
8522
8522
  authorization: `Bearer ${token}`,
8523
- "user-agent": `ghosty-cli/${"0.59.0"}`,
8523
+ "user-agent": `ghosty-cli/${"0.62.0"}`,
8524
8524
  "accept-language": lang(),
8525
8525
  ...opts.body !== void 0 ? { "content-type": "application/json" } : {},
8526
8526
  ...opts.headers
@@ -9080,6 +9080,11 @@ var need = (v, what, example) => {
9080
9080
  return v;
9081
9081
  };
9082
9082
  async function agentsLs(workspace, f = {}) {
9083
+ if (f.archived) {
9084
+ const r2 = await api("/api/v2/me/agents/archived");
9085
+ if (f.ids) return void process.stdout.write(r2.agents.map((a) => a.id).join("\n") + (r2.agents.length ? "\n" : ""));
9086
+ return out(r2.agents, () => table(r2.agents.map((a) => ({ id: a.id, name: a.name, engine: a.engine, archivedAt: a.archivedAt, purgeAt: a.purgeAt })), ["id", "name", "engine", "archivedAt", "purgeAt"]));
9087
+ }
9083
9088
  if (f.sponsor || f.owner) {
9084
9089
  const q = new URLSearchParams({ ...f.sponsor ? { sponsor: f.sponsor } : {}, ...f.owner ? { owner: f.owner } : {}, ...f.engine ? { engine: f.engine } : {}, ...workspace && !f.all ? { workspace } : {} });
9085
9090
  const s2 = await api(`/api/v2/agents?${q}`);
@@ -9354,6 +9359,15 @@ async function agentsRm(id, v = {}) {
9354
9359
  const r = await api(agentPath(id, from ? "?fromWorkspace=1" : ""), { method: "DELETE" });
9355
9360
  out(r, () => `${lavender("\u2713")} ${t("Deleted", "Borrado")} ${id}.${r?.unbound?.length ? dim(t(` Turned off in Teams: ${r.unbound.join(", ")}.`, ` Desactivado en Teams: ${r.unbound.join(", ")}.`)) : ""}`);
9356
9361
  }
9362
+ async function agentsArchive(id, archive) {
9363
+ id = need(id, "agent id", `ghosty agents ${archive ? "archive" : "restore"} <agent-id>`);
9364
+ const r = await api(agentPath(id, "/archive"), { method: "POST", body: { archive } });
9365
+ const day3 = r.purgeAt ? r.purgeAt.slice(0, 10) : "";
9366
+ out(
9367
+ r,
9368
+ () => !r.changed ? dim(archive ? t(`Already archived (deleted on ${day3}).`, `Ya estaba archivado (se borra el ${day3}).`) : t("It wasn't archived.", "No estaba archivado.")) : archive ? `${lavender("\u2713")} ${t(`Archived ${id}. It no longer answers anywhere; deleted on ${day3} unless you restore it (ghosty agents restore ${id}).`, `Archivado ${id}. Ya no contesta en ning\xFAn canal; se borra el ${day3} si no lo restauras (ghosty agents restore ${id}).`)}` : `${lavender("\u2713")} ${t(`Restored ${id}.`, `Restaurado ${id}.`)}${r.teams.length || r.whatsapp ? dim(t(` Back on: ${[...r.teams.length ? [`Teams (${r.teams.length})`] : [], ...r.whatsapp ? ["WhatsApp"] : []].join(", ")}.`, ` De vuelta: ${[...r.teams.length ? [`Teams (${r.teams.length})`] : [], ...r.whatsapp ? ["WhatsApp"] : []].join(", ")}.`)) : ""}`
9369
+ );
9370
+ }
9357
9371
  async function setAvatar(id, src) {
9358
9372
  if (/^https:\/\//.test(src)) return api(agentPath(id, "/avatar"), { method: "PUT", body: { url: src } });
9359
9373
  const bytes = await readFile3(src).catch(() => {
@@ -10418,7 +10432,7 @@ async function call(path, v, init = {}) {
10418
10432
  "x-ghosty-ts": ts,
10419
10433
  "x-ghosty-sig": sig,
10420
10434
  "content-type": "application/json",
10421
- "user-agent": `ghosty-cli/${"0.59.0"}`
10435
+ "user-agent": `ghosty-cli/${"0.62.0"}`
10422
10436
  },
10423
10437
  body: method === "GET" ? void 0 : raw
10424
10438
  });
@@ -10919,6 +10933,16 @@ ${table(rows, ["table", "rows", "customer writes", "customer sees", "columns"])}
10919
10933
  ${dim(`${t(`${rows.length} row(s)`, `${rows.length} fila(s)`)} \xB7 ${Math.round(r.ms)} ms`)}`;
10920
10934
  });
10921
10935
  }
10936
+ case "origin": {
10937
+ db = need2(db, t("database (id or name)", "la base (id o nombre)"), "ghosty dbs origin <agent-id> <db> [--table T] [--since 2026-10-10] [--limit N]");
10938
+ const q = new URLSearchParams();
10939
+ if (v.table) q.set("table", v.table);
10940
+ if (v.since) q.set("since", v.since);
10941
+ if (v.limit) q.set("limit", v.limit);
10942
+ const r = await api(agentPath2(id, `/dbs/${enc6(db)}/origin${q.size ? `?${q}` : ""}`));
10943
+ const rows = r.writes.map((w) => ({ when: w.created_at, agent: w.agent_id.slice(0, 10), channel: w.canal ?? "", what: `${w.op} ${w.table_name}${w.row_id != null ? ` #${w.row_id}` : ""}`, customer: w.phone10 ?? "", why: w.reason ?? "" }));
10944
+ return out(r, () => rows.length ? table(rows, ["when", "agent", "channel", "what", "customer", "why"]) : dim(t("No agent writes recorded yet in this database.", "Todav\xEDa no hay escrituras de agentes registradas en esta base.")));
10945
+ }
10922
10946
  case "grant": {
10923
10947
  db = need2(db, t("database (id or name)", "la base (id o nombre)"), "ghosty dbs grant <agent-id> <db> [--external-write t1,t2] [--external-rows t:col+col]");
10924
10948
  let rowScope;
@@ -12090,6 +12114,8 @@ var HELPS_ES = {
12090
12114
  ["# Teams workspaces you administer: where it answers, is paused, and with which @handle", "# los equipos de Teams que administras: d\xF3nde contesta, d\xF3nde est\xE1 en pausa y con qu\xE9 @handle"],
12091
12115
  ["# turn it on there (or change its @handle)", "# lo prende ah\xED (o le cambia el @handle)"],
12092
12116
  ["# pause it there; it keeps its @handle", "# lo pausa ah\xED; conserva su @handle"],
12117
+ ["# archive: stops answering everywhere, hidden, deleted after 7 days unless restored", "# archivar: deja de contestar en todos lados, se oculta y se borra a los 7 d\xEDas si no lo restauras"],
12118
+ ["# archived agents and when they get deleted", "# los archivados y cu\xE1ndo se borran"],
12093
12119
  ["# remove it from there; the agent stays", "# lo quita de ah\xED; el agente sigue"],
12094
12120
  ["# web widget: chat bubble on your site, only on those domains", "# widget web: burbuja de chat en tu sitio, s\xF3lo en esos dominios"],
12095
12121
  ["# state + the <script> to paste", "# su estado + el <script> para pegar"]
@@ -12214,6 +12240,7 @@ var HELPS_ES = {
12214
12240
  ["--rehost-images t.col,\u2026", "import: copia las ligas de imagen de esas columnas al almacenamiento p\xFAblico de Ghosty (ligas permanentes en img.ghosty.studio)"],
12215
12241
  ["--table T --column C", "rehost: la columna de ligas de imagen (p. ej. catalogo imagen_url) que se pasa a ligas permanentes en img.ghosty.studio; idempotente, una liga que falla se queda como estaba"],
12216
12242
  ["--key-column K --dir DIR", "photos put: cada archivo de DIR se llama como la clave de su fila (SKU-123.jpg \u2192 la fila donde K = SKU-123); va al almacenamiento permanente y su liga a --column (default imagen_url)"],
12243
+ ["--since DATE --limit N", "origin: escrituras de agentes desde esa fecha (p. ej. 2026-10-10), hasta N (default 50)"],
12217
12244
  ["-n, --dry-run", "import: imprime el plan (+ crear, ~ reemplazar N \u2192 M filas) y las filas que s\xF3lo existen en gs, no cambia nada \xB7 photos put: lista las claves sin fila y los archivos que no son imagen \xB7 rename: lista los prompts, env y skills que la nombran \xB7 rm: tablas, filas y agentes que la perder\xEDan"],
12218
12245
  ["--yes", "import, rm: se salta la confirmaci\xF3n (obligatorio sin terminal)"]
12219
12246
  ],
@@ -12500,6 +12527,16 @@ var HELPS_ES = {
12500
12527
  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.",
12501
12528
  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.", "Tambi\xE9n salen los push de las novedades (kind novedad)."]
12502
12529
  },
12530
+ support: {
12531
+ summary: "Pide ayuda a Ghosty. Soporte es por correo: cada caso tiene su propia direcci\xF3n de respuesta, as\xED que contestar el correo (o escribir aqu\xED) cae en el mismo caso. Tambi\xE9n te lo abre tu agente: \xABrep\xF3rtalo a soporte\xBB.",
12532
+ notes: [
12533
+ "El caso lleva tu cuenta, tus agentes y sus errores recientes: no hace falta repetirlos.",
12534
+ "Contestar un caso resuelto lo reabre.",
12535
+ "El staff contesta desde su correo: /resuelto en la primera l\xEDnea cierra el caso.",
12536
+ "link liga un caso al issue o PR de GitHub que lo arregla; varios casos al mismo funcionan como tracker. Al cerrarse el issue como completado (o mergearse el PR) a cada cliente le llega \xABya qued\xF3\xBB y su caso se resuelve.",
12537
+ "Solo: al staff le llega un recordatorio tras 4 h sin respuesta; un caso esperando al cliente se cierra solo a los 7 d\xEDas (si contesta, se reabre)."
12538
+ ]
12539
+ },
12503
12540
  novedades: {
12504
12541
  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.",
12505
12542
  notes: ["ls dice, de una ya enviada, cu\xE1ntos entreg\xF3 y rechaz\xF3 FCM (del push log, desde el 1-oct).", "--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.", "--min-version N (versionCode de Android): si la app del tester es m\xE1s vieja, el bot\xF3n dice \xABActualizar\xBB y lanza la actualizaci\xF3n de Play desde la app; '' la quita."]
@@ -12639,6 +12676,8 @@ var HELPS = {
12639
12676
  "ghosty agents embed <agent-id> [--copy '{json}'|none] [--copy-file texts.json] # what it says while it works",
12640
12677
  "ghosty agents embed <agent-id> theme # export the theme as JSON (edit it, apply it back with --style-file)",
12641
12678
  "ghosty agents embed <agent-id> --out src/components/GhostyChat.tsx [--force] # your own React component, token and theme baked in",
12679
+ "ghosty agents archive|restore <agent-id> # archive: stops answering everywhere, hidden, deleted after 7 days unless restored",
12680
+ "ghosty agents ls --archived # archived agents and when they get deleted",
12642
12681
  "ghosty agents rm <agent-id> [--from-workspace] [--yes]"
12643
12682
  ],
12644
12683
  flags: [
@@ -12788,6 +12827,7 @@ var HELPS = {
12788
12827
  "ghosty dbs ls <agent-id> [--orphans]",
12789
12828
  "ghosty dbs tables <agent-id> <db>",
12790
12829
  'ghosty dbs query <agent-id> <db> "SQL" [--arg V]\u2026 [--write]',
12830
+ "ghosty dbs origin <agent-id> <db> [--table T] [--since 2026-10-10] [--limit N]",
12791
12831
  "ghosty dbs export <agent-id> <db> [--table T] [--out FILE]",
12792
12832
  "ghosty dbs import <agent-id> <db> --from easybits:<db-id> [--tables a,b] [--append] [--rehost-images t.col,\u2026] [--dry-run] [--yes]",
12793
12833
  "ghosty dbs rehost <agent-id> <db> --table T --column C [--dry-run]",
@@ -12811,6 +12851,7 @@ var HELPS = {
12811
12851
  ["--rehost-images t.col,\u2026", "import: copy the image links in those columns to Ghosty's public storage (permanent links at img.ghosty.studio)"],
12812
12852
  ["--table T --column C", "rehost: the column of image links (e.g. catalogo imagen_url) to move to permanent links at img.ghosty.studio; idempotent, a link that fails stays as it was"],
12813
12853
  ["--key-column K --dir DIR", "photos put: each file in DIR is named after its row's key (SKU-123.jpg \u2192 the row where K = SKU-123); it goes to permanent storage and its link to --column (default imagen_url)"],
12854
+ ["--since DATE --limit N", "origin: agent writes since that date (e.g. 2026-10-10), up to N (default 50)"],
12814
12855
  ["-n, --dry-run", "import: print the plan (+ create, ~ replace N \u2192 M rows) and the rows that exist only in gs, change nothing \xB7 photos put: list keys with no row and files that aren't images \xB7 rename: list the prompts, env and skills that name it \xB7 rm: tables, rows and agents that would lose it"],
12815
12856
  ["--yes", "import, rm: skip the confirmation (required when not interactive)"]
12816
12857
  ],
@@ -13242,6 +13283,22 @@ HELPS.push = {
13242
13283
  notes: ["thread shows the conversation's kind: a \xABchannel\xBB there means a customer's push reached them.", "Announcement pushes appear too (kind novedad)."],
13243
13284
  learn: `${REF}#soporte`
13244
13285
  };
13286
+ HELPS.support = {
13287
+ summary: "Ask Ghosty for help. Support is by email: each case has its own reply address, so answering the email (or writing here) lands in the same case. Your agent can open one too: \xABreport it to support\xBB.",
13288
+ usage: [
13289
+ "ghosty support open --title T --body TEXT",
13290
+ "ghosty support ls",
13291
+ "ghosty support show <id>",
13292
+ "ghosty support reply <id> --body TEXT",
13293
+ "ghosty support ls|show --all [--status open|waiting_customer|resolved] (staff)",
13294
+ "ghosty support reply <id> --all --body TEXT [--resolve] (staff: emails the customer)",
13295
+ "ghosty support resolve|reopen <id> (staff)",
13296
+ "ghosty support link <id> <owner/repo#12 | issue/PR URL> (staff)"
13297
+ ],
13298
+ examples: ['ghosty support open --title "My agent stopped answering on WhatsApp" --body "Since yesterday 6 pm\u2026"', "ghosty support ls --all", 'ghosty support reply <id> --all --body "Fixed, try again" --resolve'],
13299
+ notes: ["The case carries your account, agents and recent errors, so you don't have to repeat them.", "Replying to a resolved case reopens it.", "Staff answer from their own inbox too: /resuelto on the first line closes the case.", "link ties a case to the GitHub issue or PR that fixes it; several cases on the same one act as a tracker. When the issue closes as completed (or the PR merges) each customer gets \xABya qued\xF3\xBB and the case resolves.", "Automatic: staff get a reminder after 4 h without an answer; a case waiting on the customer closes itself after 7 days (replying reopens it)."],
13300
+ learn: `${REF}#soporte`
13301
+ };
13245
13302
  HELPS.novedades = {
13246
13303
  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.",
13247
13304
  usage: [
@@ -14643,6 +14700,59 @@ async function userDoctor(email, agent, v) {
14643
14700
  return lines.join("\n");
14644
14701
  });
14645
14702
  }
14703
+ var STATUS = (s2) => s2 === "open" ? t("open", "abierto") : s2 === "waiting_customer" ? t("waiting on customer", "esperando al cliente") : s2 === "resolved" ? t("resolved", "resuelto") : s2;
14704
+ async function supportCmd(sub, id, v) {
14705
+ if (!sub || sub === "ls" || sub === "list") {
14706
+ const q = new URLSearchParams({ ...v.status ? { status: v.status } : {}, ...v.limit ? { limit: v.limit } : {} });
14707
+ const r = v.all ? await api(`/api/v2/support/cases?${q}`) : await api("/api/v2/me/support");
14708
+ const rows = r.cases.map((c) => ({ id: c.id, code: c.code, status: STATUS(c.status), title: c.title.slice(0, 50), ...v.all ? { email: c.email } : {}, last: cdmx(c.lastMessageAt) }));
14709
+ return out(r, () => rows.length ? table(rows, v.all ? ["id", "code", "status", "title", "email", "last"] : ["id", "code", "status", "title", "last"]) : dim(t("No cases.", "Sin casos.")));
14710
+ }
14711
+ if (sub === "open") {
14712
+ if (!v.body) throw usage(t("Missing --body.", "Falta --body."), 'ghosty support open --title "\u2026" --body "\u2026"');
14713
+ const r = await api("/api/v2/me/support", { method: "POST", body: { title: v.title ?? "", body: v.body } });
14714
+ return out(r, () => `${lavender("\u2713")} ${t("Case", "Caso")} ${bold(r.case.code)} ${t("opened. We'll answer by email at", "abierto. Te contestamos por correo a")} ${r.case.email}.
14715
+ ${dim(r.case.id)}`);
14716
+ }
14717
+ if (!id) throw usage(t("Missing the case id.", "Falta el id del caso."), "ghosty support ls");
14718
+ if (sub === "show") {
14719
+ const r = v.all ? await api(`/api/v2/support/cases/${enc13(id)}`) : await api(`/api/v2/me/support/${enc13(id)}`);
14720
+ const c = r.case;
14721
+ return out(r, () => {
14722
+ const who2 = (a) => a === "staff" ? "Ghosty" : a === "agent" ? t("agent", "agente") : a === "customer" ? t("customer", "cliente") : a;
14723
+ const ctx = c.context;
14724
+ const lines = [
14725
+ `${bold(c.code)} ${c.title} ${dim(`\xB7 ${STATUS(c.status)} \xB7 ${c.email} \xB7 ${c.channel}`)}`,
14726
+ ...ctx?.plan ? [dim(` plan ${ctx.plan}`)] : [],
14727
+ ...(ctx?.agents ?? []).map((a) => dim(` ${a.name ?? a.id} ${a.engine}/${a.model ?? ""}`) + (a.failedTurns ? red(` \xB7 ${a.failedTurns} ${t("failed turns", "turnos con error")}`) : "")),
14728
+ ...ctx?.thread ? [dim(` ghosty conversations show ${ctx.thread.agentId} ${ctx.thread.sessionId} --why`)] : [],
14729
+ "",
14730
+ ...c.thread.map((m) => `${dim(cdmx(m.createdAt))} ${bold(who2(m.author))}${m.authorEmail ? dim(` ${m.authorEmail}`) : ""} ${dim(`(${m.via})`)}
14731
+ ${m.body}${m.attachments.length ? dim(`
14732
+ \u{1F4CE} ${m.attachments.map((a) => a.name).join(", ")}`) : ""}
14733
+ `)
14734
+ ];
14735
+ return lines.join("\n");
14736
+ });
14737
+ }
14738
+ if (sub === "reply") {
14739
+ if (!v.body && !v.resolve) throw usage(t("Missing --body.", "Falta --body."), 'ghosty support reply <id> --body "\u2026" [--resolve]');
14740
+ const r = v.all || v.resolve ? await api(`/api/v2/support/cases/${enc13(id)}`, { method: "POST", body: { intent: "reply", body: v.body ?? "", resolve: Boolean(v.resolve) } }) : await api(`/api/v2/me/support/${enc13(id)}`, { method: "POST", body: { body: v.body } });
14741
+ return out(r, () => `${lavender("\u2713")} ${r.case.code} \xB7 ${STATUS(r.case.status)}`);
14742
+ }
14743
+ if (sub === "link") {
14744
+ const ref = v.ref;
14745
+ if (!ref) throw usage(t("Missing the issue or PR.", "Falta el issue o PR."), "ghosty support link <id> owner/repo#12 (or the issue/PR URL)");
14746
+ const r = await api(`/api/v2/support/cases/${enc13(id)}`, { method: "POST", body: { intent: "link", ref } });
14747
+ return out(r, () => `${lavender("\u2713")} ${r.case.code} \u2192 ${bold(r.ref)} ${dim(`(${r.github.title})`)}${r.alsoWaiting ? `
14748
+ ${dim(t(`${r.alsoWaiting} more cases wait for the same fix: all get "fixed" when it closes.`, `Otros ${r.alsoWaiting} casos esperan el mismo arreglo: a todos les llega \xABya qued\xF3\xBB al cerrarse.`))}` : ""}`);
14749
+ }
14750
+ if (sub === "resolve" || sub === "reopen") {
14751
+ const r = await api(`/api/v2/support/cases/${enc13(id)}`, { method: "POST", body: { intent: sub } });
14752
+ return out(r, () => `${lavender("\u2713")} ${r.case.code} \xB7 ${STATUS(r.case.status)}`);
14753
+ }
14754
+ throw usage(t(`Unknown support subcommand: ${sub}.`, `Subcomando de support desconocido: ${sub}.`), "ghosty support ls | open | show | reply | resolve | reopen | link");
14755
+ }
14646
14756
 
14647
14757
  // src/index.ts
14648
14758
  var HELP_EN = `${bold("Usage:")} ghosty <command> [options]
@@ -14752,6 +14862,9 @@ ${bold("Support")} ${dim("(Ghosty staff)")}
14752
14862
  conversations show <id> <conv> --why A conversation: who sees it in their list / by link, and why
14753
14863
  turns stats Real failure rate by engine and error [--since 14d] [--all]
14754
14864
  push log --user EMAIL Pushes sent to someone and what Apple/Google answered [--since 2d]
14865
+ support ls|show|reply --all The support inbox; reply goes to the customer by email [--status open|waiting_customer|resolved] [--resolve]
14866
+ support resolve|reopen <id> Close a case (emails the customer) or reopen it
14867
+ support link <id> <issue|PR> Tie it to the GitHub fix: when it closes, every tied case gets "fixed" by email
14755
14868
  novedades ls|create|set|test|send|rm Android app announcements [--title] [--body] [--image FILE] [--link] [--to] [--min-version N] [--publish] [--at]
14756
14869
 
14757
14870
  ${bold("Sites")} ${dim("(sites your agents publish at <slug>.ghosty.page)")}
@@ -14798,6 +14911,8 @@ ${bold("Your account")}
14798
14911
  me memories ls|add|edit|rm What your agents remember about you (personal chats only) add "text" \xB7 edit <id> "text" \xB7 rm <id>
14799
14912
  credentials ls|set|rm [provider] Provider keys in your vault (hidden prompt, stdin or --from-env VAR; never as an argument)
14800
14913
  connectors ls|connect|disconnect [id] Your connectors (Shopify, Stripe, monday\u2026): connect opens the browser or asks for the key
14914
+ support open --title T --body TEXT Ask Ghosty for help; we answer by email (or ask your agent: "report it to support")
14915
+ support ls | show <id> | reply <id> --body TEXT Your support cases (replying to the email works too)
14801
14916
  docs [topic] Open the docs
14802
14917
 
14803
14918
  ${bold("Global options")}
@@ -14920,6 +15035,9 @@ ${bold("Soporte")} ${dim("(staff de Ghosty)")}
14920
15035
  conversations show <id> <conv> --why Una conversaci\xF3n: qui\xE9n la ve en su lista / por liga, y por qu\xE9
14921
15036
  turns stats Tasa real de fallos por motor y por error [--since 14d] [--all]
14922
15037
  push log --user CORREO Avisos que se le mandaron a alguien y qu\xE9 contest\xF3 Apple/Google [--since 2d]
15038
+ support ls|show|reply --all La bandeja de soporte; la respuesta le llega al cliente por correo [--status open|waiting_customer|resolved] [--resolve]
15039
+ support resolve|reopen <id> Cerrar un caso (le avisa al cliente) o reabrirlo
15040
+ support link <id> <issue|PR> Ligarlo al arreglo en GitHub: al cerrarse, a todos los casos ligados les llega \xABya qued\xF3\xBB
14923
15041
  novedades ls|create|set|test|send|rm Novedades de la app Android [--title] [--body] [--image ARCHIVO] [--link] [--to] [--min-version N] [--publish] [--at]
14924
15042
 
14925
15043
  ${bold("Sitios")} ${dim("(los sitios que publican tus agentes en <slug>.ghosty.page)")}
@@ -14966,6 +15084,8 @@ ${bold("Tu cuenta")}
14966
15084
  me memories ls|add|edit|rm Lo que tus agentes recuerdan de ti (s\xF3lo chats personales) add "texto" \xB7 edit <id> "texto" \xB7 rm <id>
14967
15085
  credentials ls|set|rm [proveedor] Llaves de proveedor en tu b\xF3veda (prompt oculto, stdin o --from-env VAR; nunca como argumento)
14968
15086
  connectors ls|connect|disconnect [id] Tus conectores (Shopify, Stripe, monday\u2026): connect abre el navegador o pide la llave
15087
+ support open --title T --body TEXTO Pide ayuda a Ghosty; te contestamos por correo (o dile a tu agente \xABrep\xF3rtalo a soporte\xBB)
15088
+ support ls | show <id> | reply <id> --body TEXTO Tus casos de soporte (contestar el correo tambi\xE9n sirve)
14969
15089
  docs [tema] Abre la documentaci\xF3n
14970
15090
 
14971
15091
  ${bold("Opciones globales")}
@@ -14981,7 +15101,7 @@ ${bold("Opciones globales")}
14981
15101
  ${bold("C\xF3digos de salida")} 0 bien \xB7 1 error de la API \xB7 2 uso incorrecto \xB7 3 sin sesi\xF3n
14982
15102
  ${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.")}
14983
15103
  ${dim("Documentaci\xF3n: https://www.ghosty.studio/docs/cli")}`;
14984
- var COMMANDS = ["login", "logout", "whoami", "usage", "use", "apply", "plan", "engines", "bags", "spaces", "workspaces", "staff", "audit", "factory", "demos", "sites", "push", "novedades", "completion", "cards", "agents", "files", "skills", "mcp", "dbs", "turns", "try", "chat", "conversations", "schedule", "partner", "me", "channels", "board", "boards", "credentials", "connectors", "browser", "docs", "help"];
15104
+ var COMMANDS = ["login", "logout", "whoami", "usage", "use", "apply", "plan", "engines", "bags", "spaces", "workspaces", "staff", "audit", "factory", "demos", "sites", "push", "novedades", "completion", "cards", "agents", "files", "skills", "mcp", "dbs", "turns", "try", "chat", "conversations", "schedule", "partner", "me", "channels", "board", "boards", "credentials", "connectors", "browser", "support", "docs", "help"];
14985
15105
  var AGENTS_SUBS = ["ls", "create", "get", "set", "export", "doctor", "docs", "share", "unshare", "shares", "teams", "rm", "restart", "demo", "embed", "box"];
14986
15106
  function unknown(what, input, known, helpCmd) {
14987
15107
  const s2 = suggest(input, known);
@@ -15134,6 +15254,7 @@ async function main() {
15134
15254
  "columns-from": { type: "string" },
15135
15255
  to: { type: "string" },
15136
15256
  all: { type: "boolean" },
15257
+ resolve: { type: "boolean" },
15137
15258
  clear: { type: "boolean" },
15138
15259
  expires: { type: "string" },
15139
15260
  lang: { type: "string" },
@@ -15200,6 +15321,7 @@ async function main() {
15200
15321
  avatar: { type: "string" },
15201
15322
  tool: { type: "string" },
15202
15323
  "from-workspace": { type: "boolean" },
15324
+ archived: { type: "boolean" },
15203
15325
  "quota-hits": { type: "boolean" },
15204
15326
  by: { type: "string" },
15205
15327
  models: { type: "boolean" },
@@ -15249,7 +15371,7 @@ async function main() {
15249
15371
  const tokens = splitTokens(pos, v);
15250
15372
  globals.token = tokens.session;
15251
15373
  globals.profile = v.profile;
15252
- if (v.version) return out({ version: "0.59.0" }, () => "0.59.0");
15374
+ if (v.version) return out({ version: "0.62.0" }, () => "0.62.0");
15253
15375
  for (const n of normalizeArgs(pos, v)) if (!globals.json) info(dim(n));
15254
15376
  const context = (await readConfig()).workspace;
15255
15377
  if (!explicitToken()?.startsWith("gat_")) {
@@ -15298,7 +15420,7 @@ async function main() {
15298
15420
  case "agents":
15299
15421
  if ((sub === "ls" || sub === "list" || !sub) && v.user && v.app !== void 0) return appAgentsOf(v.user, v.app);
15300
15422
  if ((sub === "ls" || sub === "list" || !sub) && v.handle) return agentsByHandle(v.handle, v.workspace ?? context);
15301
- if (sub === "ls" || sub === "list" || !sub) return agentsLs(v.all ? void 0 : v.workspace ?? context, { sponsor: v.sponsor, owner: v.owner, engine: v.engine, ids: v.ids, all: v.all });
15423
+ if (sub === "ls" || sub === "list" || !sub) return agentsLs(v.all ? void 0 : v.workspace ?? context, { sponsor: v.sponsor, owner: v.owner, engine: v.engine, ids: v.ids, all: v.all, archived: v.archived });
15302
15424
  if (sub === "create" || sub === "new") {
15303
15425
  if (v.workspace === "none") delete v.workspace;
15304
15426
  else if (!v.workspace && context) {
@@ -15312,6 +15434,7 @@ async function main() {
15312
15434
  if (sub === "teams") return agentsTeams(a1, a2, pos[4], v);
15313
15435
  if (sub === "set") return agentsSet(a1, v);
15314
15436
  if (sub === "rm" || sub === "delete") return agentsRm(a1, v);
15437
+ if (sub === "archive" || sub === "restore") return agentsArchive(a1, sub === "archive");
15315
15438
  if (sub === "doctor" && v.user) return userDoctor(v.user, a1, v);
15316
15439
  if (sub === "doctor") return agentsDoctor(a1, v);
15317
15440
  if (sub === "docs" || sub === "documents") return agentDocsCmd(a1, a2, pos.slice(4).filter((x) => x !== void 0), v);
@@ -15335,6 +15458,8 @@ async function main() {
15335
15458
  if (v["quota-hits"]) return workspaceUsage(v.workspace ?? context, v);
15336
15459
  if (sub === "stats") return turnStats(v);
15337
15460
  return turnsCmd(sub, a1, a2, v);
15461
+ case "support":
15462
+ return supportCmd(sub, a1, { ...v, ref: a2 });
15338
15463
  case "push":
15339
15464
  if (sub !== "log") throw usage(t("Unknown push subcommand.", "Subcomando de push desconocido."), "ghosty push log --user <email> [--since 2d]");
15340
15465
  return pushLog(v.user, v);
@@ -15415,7 +15540,7 @@ async function main() {
15415
15540
  throw unknown(cmd, cmd, COMMANDS, "ghosty --help");
15416
15541
  }
15417
15542
  }
15418
- var notice = updateNotice("0.59.0").catch(() => null);
15543
+ var notice = updateNotice("0.62.0").catch(() => null);
15419
15544
  main().then(
15420
15545
  async () => {
15421
15546
  const n = await notice;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghostystudio/cli",
3
- "version": "0.59.0",
3
+ "version": "0.62.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": {