@ghostystudio/cli 0.74.1 → 0.75.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 +5 -0
  2. package/dist/index.js +72 -19
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -113,6 +113,7 @@ ghosty turns stats [--since 14d] [--all] · push log --user EMAIL [--since 2d]
113
113
  ghosty support ls|show|reply --all [--status open|waiting_customer|resolved] · support resolve|reopen <id> · support link <id> <issue|PR>
114
114
  ghosty staff ls [staff] [client] [--all] · staff assign|unassign <staff> <client> [--reason] # staff on client accounts (logged)
115
115
  ghosty settings ls|set|unset <KEY> [value] # platform switches (+id/-id edits a list)
116
+ ghosty ads watch [ls] · ads watch run [--dry-run] # campaign watch: paused campaigns, billing, token, numbers without ad leads
116
117
  ghosty agents doctor --user EMAIL [agent] [--turns 8] · usage --images [--plan free] [--by user]
117
118
  ghosty novedades ls|create|set|test|send|rm [--title] [--body] [--image FILE] [--link] [--to] [--publish] [--at]
118
119
  ghosty sites ls [--house] · sites publish <file.html|folder> [--title] [--slug S] [--site <slug|id>] · sites slug <slug|id> <new-slug> # house sites on EasyBits
@@ -140,6 +141,10 @@ Every command has its own help with examples: `ghosty <command> --help` or `ghos
140
141
 
141
142
  Exit codes: `0` ok · `1` API error · `2` usage error · `3` not signed in.
142
143
 
144
+ ### Changes in 0.75.0
145
+
146
+ - `ads watch [ls]` and `ads watch run [--dry-run]` (staff): the campaign watch. Every hour gs checks the connected Meta ad accounts and the ads-only WhatsApp numbers and alerts staff once when an active campaign is paused or stops delivering, the account has a payment pending, the Meta token expires within 7 days, or a number goes 24 h without ad leads.
147
+
143
148
  ### Changes in 0.73.0
144
149
 
145
150
  - `factory rooms ls|connect|disconnect`: set up the factory for a repo without opening Teams. `connect owner/repo --room R --create` creates the room, connects the repo (only if your GitHub App sees it) and tells you how ready it is for agents.
package/dist/index.js CHANGED
@@ -1175,7 +1175,7 @@ async function postLong(path, body, timeoutMs = 9e5) {
1175
1175
  const req = httpsRequest(`${base}${path}`, {
1176
1176
  method: "POST",
1177
1177
  timeout: timeoutMs,
1178
- headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.74.1"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
1178
+ headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.75.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
1179
1179
  }, (res) => {
1180
1180
  let buf = "";
1181
1181
  res.on("data", (c) => buf += c);
@@ -1217,7 +1217,7 @@ async function request(path, opts = {}, retried = false) {
1217
1217
  method: opts.method ?? (opts.body !== void 0 || opts.raw !== void 0 ? "POST" : "GET"),
1218
1218
  headers: {
1219
1219
  authorization: `Bearer ${token}`,
1220
- "user-agent": `ghosty-cli/${"0.74.1"}`,
1220
+ "user-agent": `ghosty-cli/${"0.75.0"}`,
1221
1221
  "accept-language": lang(),
1222
1222
  ...opts.body !== void 0 ? { "content-type": "application/json" } : {},
1223
1223
  ...opts.headers
@@ -4427,9 +4427,9 @@ var require_timestamp = __commonJS({
4427
4427
  const match = str.match(timestamp.test);
4428
4428
  if (!match)
4429
4429
  throw new Error("!!timestamp expects a date, starting with yyyy-mm-dd");
4430
- const [, year, month, day3, hour, minute, second] = match.map(Number);
4430
+ const [, year, month, day4, hour, minute, second] = match.map(Number);
4431
4431
  const millisec = match[7] ? Number((match[7] + "00").substr(1, 3)) : 0;
4432
- let date = Date.UTC(year, month - 1, day3, hour || 0, minute || 0, second || 0, millisec);
4432
+ let date = Date.UTC(year, month - 1, day4, hour || 0, minute || 0, second || 0, millisec);
4433
4433
  const tz = match[8];
4434
4434
  if (tz && tz !== "Z") {
4435
4435
  let d = parseSexagesimal(tz, false);
@@ -9166,7 +9166,7 @@ ${dim(t(`${total2} message(s) unanswered for lack of balance in ${r.days} day(s)
9166
9166
  d.noQuota += x.noQuota;
9167
9167
  days.set(x.day, d);
9168
9168
  }
9169
- rows = [...days].map(([day3, x]) => ({ day: day3, ...fmt2(x) }));
9169
+ rows = [...days].map(([day4, x]) => ({ day: day4, ...fmt2(x) }));
9170
9170
  } else rows = r.agents.map((x) => ({ agent: x.agent, ...fmt2(x) }));
9171
9171
  const cols = [...by.has("day") ? ["day"] : [], ...by.has("agent") ? ["agent"] : [], "turns", "failed", "tokens", "no balance"];
9172
9172
  const total = r.agents.reduce((n, a) => n + a.billable, 0);
@@ -9237,7 +9237,7 @@ function fmtUntil(iso) {
9237
9237
  async function spacesMembers(action, slug, email, v, context) {
9238
9238
  if (context && slug?.includes("@") && !email) [slug, email] = [context, slug];
9239
9239
  if (action && !["ls", "list", "add", "rm", "remove"].includes(action) && !slug) [action, slug] = ["ls", action];
9240
- const ex = "ghosty spaces members ls <workspace> \xB7 add <workspace> <email>[,email\u2026] [--role member|admin] [--until 2026-10-28|14d] \xB7 rm <workspace> <email>";
9240
+ const ex = "ghosty spaces members ls <workspace> \xB7 add <workspace> <email>[,email\u2026] [--role member|admin|collaborator --room <room>] [--until 2026-10-28|14d] \xB7 rm <workspace> <email>";
9241
9241
  slug = needSpace(slug ?? context, ex);
9242
9242
  switch (action ?? "ls") {
9243
9243
  case "ls":
@@ -9249,8 +9249,10 @@ ${lavender(`\u26A0\uFE0F ${serverText(r.error)}`)}` : ""}`);
9249
9249
  }
9250
9250
  case "add": {
9251
9251
  if (!email) throw usage(t("Missing email.", "Falta el correo."), ex);
9252
+ if (v.role === "collaborator" && !v.room) throw usage(t("A collaborator needs --room (the Teams room they enter).", "Un colaborador necesita --room (el room de Teams al que entra)."), ex);
9253
+ if (v.room && !/^[A-Za-z0-9_-]{1,64}$/.test(v.room)) throw usage(t("The room takes letters, numbers, _ or - (max 64).", "El room lleva s\xF3lo letras, n\xFAmeros, _ o - (m\xE1x. 64)."), ex);
9252
9254
  const emails = email.split(",").map((e) => e.trim()).filter(Boolean);
9253
- const r = await api(wsPath(slug, "/members"), { method: "POST", body: { emails, role: v.role ?? "member", ...v.until ? { until: v.until } : {} } });
9255
+ const r = await api(wsPath(slug, "/members"), { method: "POST", body: { emails, role: v.role ?? "member", ...v.until ? { until: v.until } : {}, ...v.room ? { room: v.room } : {} } });
9254
9256
  const lines = [
9255
9257
  ...r.invited.map((e) => `${lavender("\u2713")} ${e} ${t(`added to ${r.workspace}`, `agregada a ${r.workspace}`)}${r.until ? dim(t(` until ${fmtUntil(r.until)} (CDMX), then removed`, ` hasta ${fmtUntil(r.until)} (CDMX), luego se retira sola`)) : ""}`),
9256
9258
  ...r.extended.map((e) => `${lavender("\u2713")} ${e} ${t("was already in; expires", "ya estaba; vence")} ${fmtUntil(r.until)} (CDMX)`),
@@ -9912,10 +9914,10 @@ async function agentsRm(id, v = {}) {
9912
9914
  async function agentsArchive(id, archive) {
9913
9915
  id = need(id, "agent id", `ghosty agents ${archive ? "archive" : "restore"} <agent-id>`);
9914
9916
  const r = await api(agentPath(id, "/archive"), { method: "POST", body: { archive } });
9915
- const day3 = r.purgeAt ? r.purgeAt.slice(0, 10) : "";
9917
+ const day4 = r.purgeAt ? r.purgeAt.slice(0, 10) : "";
9916
9918
  out(
9917
9919
  r,
9918
- () => !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(", ")}.`)) : ""}`
9920
+ () => !r.changed ? dim(archive ? t(`Already archived (deleted on ${day4}).`, `Ya estaba archivado (se borra el ${day4}).`) : t("It wasn't archived.", "No estaba archivado.")) : archive ? `${lavender("\u2713")} ${t(`Archived ${id}. It no longer answers anywhere; deleted on ${day4} unless you restore it (ghosty agents restore ${id}).`, `Archivado ${id}. Ya no contesta en ning\xFAn canal; se borra el ${day4} 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(", ")}.`)) : ""}`
9919
9921
  );
9920
9922
  }
9921
9923
  async function setAvatar(id, src) {
@@ -11193,7 +11195,7 @@ async function call(path, v, init = {}) {
11193
11195
  "x-ghosty-ts": ts,
11194
11196
  "x-ghosty-sig": sig,
11195
11197
  "content-type": "application/json",
11196
- "user-agent": `ghosty-cli/${"0.74.1"}`
11198
+ "user-agent": `ghosty-cli/${"0.75.0"}`
11197
11199
  },
11198
11200
  body: method === "GET" ? void 0 : raw
11199
11201
  });
@@ -13505,7 +13507,8 @@ var HELPS_ES = {
13505
13507
  summary: "Espacios: crea uno a tu nombre (prueba), lista los tuyos (o, staff, los de cualquiera) y operaciones del staff de Ghosty \u2014 entrar/salir del espacio de un cliente, cambiar su combo, mover \xABpagado hasta\xBB, renombrar su slug, cambiar de due\xF1o \u2014 y, due\xF1o o staff, archivarlo o restaurarlo. Cada cambio queda en la bit\xE1cora del espacio.",
13506
13508
  flags: [
13507
13509
  ["--user EMAIL", "ls (staff): los espacios de esa persona"],
13508
- ["--role R", "members add: member (default: tableros + editor de los agentes del espacio, sin llaves, cobro ni traspasos) | admin (adem\xE1s invita y quita gente)"],
13510
+ ["--role R", "members add: member (default: tableros + editor de los agentes del espacio, sin llaves, cobro ni traspasos) | admin (adem\xE1s invita y quita gente) | collaborator (invitado a UN room: sin asiento ni agente personal)"],
13511
+ ["--room ROOM", "members add --role collaborator: el room de Teams al que entra (obligatorio con collaborator)"],
13509
13512
  ["--until D", "members add: asiento temporal que se retira solo en esa fecha (fin del d\xEDa, CDMX) o tras 14d / 12h \u2014 cuentas de revisor (Meta, Google, Apple). A quien ya est\xE1, le pone o mueve la fecha"],
13510
13513
  ["--combo C", "set (staff): teams | sales | both | none (ni Teams ni Ventas) \u2014 sin Ventas sus tableros existen pero nadie los ve; create: distinto de teams, s\xF3lo staff"],
13511
13514
  ["--model-cap X", "set (staff): el modelo m\xE1s caro que puede elegir el espacio \u2014 0.25 econ\xF3micos, 1 est\xE1ndar (Sonnet), 4 todos; none = el de su plan"],
@@ -13533,6 +13536,11 @@ var HELPS_ES = {
13533
13536
  "Revocar corta en el siguiente request. Asignar dos veces no hace nada (una sola activa por par)."
13534
13537
  ]
13535
13538
  },
13539
+ ads: {
13540
+ summary: "Vig\xEDa de campa\xF1as (staff de Ghosty): cada hora revisa las cuentas de Meta conectadas y los n\xFAmeros de WhatsApp \xABs\xF3lo anuncios\xBB, y avisa una vez al staff si una campa\xF1a activa se pausa o deja de entregar, si la cuenta queda con pago pendiente, si la conexi\xF3n con Meta caduca en 7 d\xEDas o si un n\xFAmero pasa 24 h sin leads de anuncio.",
13541
+ flags: [["--dry-run", "run: lo que avisar\xEDa, sin avisar ni guardar"]],
13542
+ notes: ["Meta no manda webhook de una pausa a mano ni de un problema de cobro: por eso el vig\xEDa consulta.", "Los avisos van s\xF3lo al staff (correo, WhatsApp y room de Teams de alert()); al cliente se le dice a mano."]
13543
+ },
13536
13544
  settings: {
13537
13545
  summary: "Perillas de la plataforma que se mueven desde el CLI (staff de Ghosty): qu\xE9 agente atiende /c sin cuenta en caja, qu\xE9 agentes y superficies tienen subagentes nativos, el constructor de p\xE1ginas para todos. Cada cambio queda en la bit\xE1cora; las que llevan JSON siguen en /admin/settings.",
13538
13546
  flags: [],
@@ -14359,10 +14367,11 @@ HELPS.novedades = {
14359
14367
  };
14360
14368
  HELPS.spaces = {
14361
14369
  summary: "Workspaces: create one of your own (trial), list yours (or, staff, anyone's), and Ghosty staff operations \u2014 join/leave a client's workspace, change its combo, move \xABpaid until\xBB, rename its slug, change its owner \u2014 and, owner or staff, archive or restore it. Each change is logged in the workspace's audit.",
14362
- usage: ["ghosty spaces ls [--user EMAIL]", "ghosty spaces create <slug> [--combo teams|sales|both|none] [--dry-run]", "ghosty spaces join|leave <workspace>", "ghosty spaces set <workspace> --combo teams|sales|both|none | --model-cap 0.25|1|4|none | --house-claude on|off [--dry-run]", "ghosty spaces renew <workspace> --until YYYY-MM-DD [--note TEXT] [--dry-run] [--yes]", "ghosty spaces rename <old> <new> [--dry-run] [--yes]", "ghosty spaces archive|restore <workspace> [--dry-run] [--yes]", "ghosty spaces transfer <workspace> --to EMAIL [--keep-as admin|member|none] [--copy-keys] [--dry-run] [--yes]", "ghosty spaces log <workspace> [--since 30d] [--action workspace] [--limit N]", "ghosty spaces members ls|add|rm <workspace> [email,\u2026] [--role member|admin] [--until 2026-10-28|14d] [--yes]"],
14370
+ usage: ["ghosty spaces ls [--user EMAIL]", "ghosty spaces create <slug> [--combo teams|sales|both|none] [--dry-run]", "ghosty spaces join|leave <workspace>", "ghosty spaces set <workspace> --combo teams|sales|both|none | --model-cap 0.25|1|4|none | --house-claude on|off [--dry-run]", "ghosty spaces renew <workspace> --until YYYY-MM-DD [--note TEXT] [--dry-run] [--yes]", "ghosty spaces rename <old> <new> [--dry-run] [--yes]", "ghosty spaces archive|restore <workspace> [--dry-run] [--yes]", "ghosty spaces transfer <workspace> --to EMAIL [--keep-as admin|member|none] [--copy-keys] [--dry-run] [--yes]", "ghosty spaces log <workspace> [--since 30d] [--action workspace] [--limit N]", "ghosty spaces members ls|add|rm <workspace> [email,\u2026] [--role member|admin|collaborator] [--room ROOM] [--until 2026-10-28|14d] [--yes]"],
14363
14371
  flags: [
14364
14372
  ["--user EMAIL", "ls (staff): that person's workspaces"],
14365
- ["--role R", "members add: member (default: boards + editor of the workspace's agents, no keys, billing or transfers) | admin (also invites and removes people)"],
14373
+ ["--role R", "members add: member (default: boards + editor of the workspace's agents, no keys, billing or transfers) | admin (also invites and removes people) | collaborator (invited to ONE Teams room: no seat, no personal agent)"],
14374
+ ["--room ROOM", "members add --role collaborator: the Teams room they enter (required with collaborator)"],
14366
14375
  ["--until D", "members add: temporary seat, removed automatically on that date (end of day, CDMX) or after 14d / 12h \u2014 reviewer accounts (Meta, Google, Apple). On someone already in, it sets or moves the date"],
14367
14376
  ["--combo C", "set (staff): teams | sales | both | none (neither Teams nor Sales) \u2014 without Sales its boards exist but nobody sees them; create: other than teams, staff only"],
14368
14377
  ["--model-cap X", "set (staff): the most expensive model the workspace can pick \u2014 0.25 economy, 1 standard (Sonnet), 4 everything; none = its plan's"],
@@ -14440,6 +14449,14 @@ HELPS.staff = {
14440
14449
  ],
14441
14450
  learn: `${REF}#staff-asignado`
14442
14451
  };
14452
+ HELPS.ads = {
14453
+ summary: "Campaign watch (Ghosty staff): every hour it checks the connected Meta ad accounts and the ads-only WhatsApp numbers, and alerts staff once when an active campaign is paused or stops delivering, the account has a payment pending, the Meta connection expires within 7 days, or a number goes 24 h without ad leads.",
14454
+ usage: ["ghosty ads watch [ls]", "ghosty ads watch run [--dry-run]"],
14455
+ flags: [["--dry-run", "run: what it would alert, without alerting or saving"]],
14456
+ examples: ["ghosty ads watch", "ghosty ads watch run --dry-run"],
14457
+ notes: ["Meta sends no webhook for a manual pause or a billing problem, so the watch polls.", "Alerts go to staff only (email, WhatsApp and the Teams room of alert()); the client is told by hand."],
14458
+ learn: `${REF}#vigia-de-campanas`
14459
+ };
14443
14460
  HELPS.settings = {
14444
14461
  summary: "Platform switches you can flip from the CLI (Ghosty staff): which agent answers /c without an account in a box, which agents and surfaces have native subagents, the page builder for everyone. Every change is logged; switches that carry JSON stay in /admin/settings.",
14445
14462
  usage: ["ghosty settings ls", "ghosty settings set <KEY> <value|+id|-id>", "ghosty settings unset <KEY>"],
@@ -15732,7 +15749,8 @@ ${dim(`${t("Detail", "Detalle")}: ghosty factory runs show <run> --workspace ${r
15732
15749
  const id = Number(ref.replace(/^#/, ""));
15733
15750
  const { advice: a, workspace } = await api(`${path}/advise`, { body: {} });
15734
15751
  const note = a.buildNote.map((p, i) => `${i + 1}. ${p.point}`).join(" ");
15735
- const apply = a.decision === "fix" ? `ghosty factory runs approve ${id} --note ${JSON.stringify(note)}${a.checkScope ? ` --check-scope ${JSON.stringify(a.checkScope)}` : ""} --workspace ${workspace}` : `ghosty factory runs changes ${id} --note ${JSON.stringify(note)} --workspace ${workspace}`;
15752
+ const sh = (x) => `'${x.replace(/'/g, `'\\''`)}'`;
15753
+ const apply = a.decision === "fix" ? `ghosty factory runs approve ${id} --note ${sh(note)}${a.checkScope ? ` --check-scope ${sh(a.checkScope)}` : ""} --workspace ${workspace}` : `ghosty factory runs changes ${id} --note ${sh(note)} --workspace ${workspace}`;
15736
15754
  return out({ workspace, advice: a, apply }, () => {
15737
15755
  const conf = { high: t("high", "alta"), medium: t("medium", "media"), low: t("low", "baja") }[a.confidence] ?? a.confidence;
15738
15756
  return [
@@ -15873,6 +15891,37 @@ async function settingsCmd(sub, key, value) {
15873
15891
  throw usage(t(`Unknown settings subcommand: ${sub}.`, `Subcomando de settings desconocido: ${sub}.`), "ghosty settings ls|set|unset <KEY> [value]");
15874
15892
  }
15875
15893
 
15894
+ // src/commands/ads.ts
15895
+ init_api();
15896
+ init_i18n();
15897
+ init_util();
15898
+ var day3 = (iso) => iso ? iso.slice(0, 16).replace("T", " ") : "";
15899
+ async function adsCmd(sub, action, v) {
15900
+ if (sub !== "watch") throw usage(t(`Unknown ads subcommand: ${sub ?? ""}.`, `Subcomando de ads desconocido: ${sub ?? ""}.`), "ghosty ads watch [ls|run] [--dry-run]");
15901
+ if (!action || action === "ls" || action === "list") {
15902
+ const r = await api("/api/v2/staff/ads-watch");
15903
+ return out(
15904
+ r,
15905
+ () => [
15906
+ lavender(t("Meta ad accounts", "Cuentas de Meta")),
15907
+ r.accounts.length ? table(r.accounts.map((a) => ({ workspace: a.workspace, account: a.account ?? "", active: `${a.active}/${a.campaigns}`, status: a.accountStatus ?? "", token: day3(a.tokenExpiresAt), checked: day3(a.checkedAt), alerted: day3(a.alertedAt), error: a.error ?? "" })), ["workspace", "account", "active", "status", "token", "checked", "alerted", "error"]) : dim(t("(none connected)", "(ninguna conectada)")),
15908
+ "",
15909
+ lavender(t("Ads-only WhatsApp numbers", "N\xFAmeros de WhatsApp \xABs\xF3lo anuncios\xBB")),
15910
+ r.numbers.length ? table(r.numbers.map((n) => ({ number: n.number, "last ad lead": day3(n.lastAdLeadAt), alerted: day3(n.alertedAt) })), ["number", "last ad lead", "alerted"]) : dim(t("(none)", "(ninguno)"))
15911
+ ].join("\n")
15912
+ );
15913
+ }
15914
+ if (action === "run") {
15915
+ const r = await api("/api/v2/staff/ads-watch", { method: "POST", body: { dryRun: Boolean(v["dry-run"]) } });
15916
+ return out(
15917
+ r,
15918
+ () => r.findings.length ? r.findings.map((f) => `${lavender(r.dryRun ? t("would alert", "avisar\xEDa") : t("alerted", "avis\xE9"))} ${f.title}
15919
+ ${f.detail.split("\n").join("\n ")}`).join("\n") : dim(t("Nothing to alert.", "Nada que avisar."))
15920
+ );
15921
+ }
15922
+ throw usage(t(`Unknown ads watch action: ${action}.`, `Acci\xF3n de ads watch desconocida: ${action}.`), "ghosty ads watch [ls|run] [--dry-run]");
15923
+ }
15924
+
15876
15925
  // src/commands/support.ts
15877
15926
  init_api();
15878
15927
  import { readFile as readFile14 } from "node:fs/promises";
@@ -16176,11 +16225,12 @@ ${bold("Workspaces")} ${dim("(changing them: Ghosty staff)")}
16176
16225
  spaces archive|restore <workspace> Archive it (out of /app and spaces ls; agents and data stay) or bring it back (owner or staff) [--dry-run]
16177
16226
  spaces transfer <workspace> --to EMAIL New owner; agents go with it (ACP boxes move with their /data, pool boxes are recycled) [--keep-as admin|member|none] [--copy-keys] [--dry-run]
16178
16227
  spaces log <workspace> Its log: transfers, staff in/out, renewals (owner or staff) [--since 30d] [--action workspace]
16179
- spaces members ls|add|rm <workspace> [email] Its people, without the Members screen (owner, admin or staff) [--role member|admin] [--until 2026-10-28|14d: removed automatically]
16228
+ spaces members ls|add|rm <workspace> [email] Its people, without the Members screen (owner, admin or staff) [--role member|admin|collaborator] [--room ROOM] [--until 2026-10-28|14d: removed automatically]
16180
16229
  access why <email> <agent-id> Why someone sees an agent or a board (every reason, and when temporary access expires) [--workspace W --board B]
16181
16230
  staff ls [staff-email] [client-email] Ghosty staff assigned to client accounts (staff) [--all: also revoked]
16182
16231
  staff assign|unassign <staff> <client> Staff sees and runs that account's agents, documents, databases and boards as themselves (no keys, billing or transfers; logged) [--reason]
16183
16232
  settings ls|set|unset <KEY> [value] Platform switches (staff): who answers /c without an account, native subagents\u2026 [+id/-id edits a list]
16233
+ ads watch [ls|run] Campaign watch (staff): Meta campaigns that stop delivering and ads-only WhatsApp numbers with no leads for 24 h [run --dry-run: what it would alert]
16184
16234
  audit ls The audit log: who changed what (agent page, channels, pauses, replies, transfers, "log in as") [--workspace SLUG] [--agent ID] [--actor EMAIL] [--action A] [--since 24h] [--limit N]
16185
16235
  factory rooms ls|connect|disconnect Set up the factory for a repo without opening Teams: create a room and connect the repo to it [connect owner/repo --room R --create]
16186
16236
  factory run "\u2026" --repo o/n [--wait] Hand a request to the Software Factory from the terminal (@plan plans, @build builds, @check reviews); --wait returns when you have to decide [runs watch|approve|changes|merge|card <run>]
@@ -16374,11 +16424,12 @@ ${bold("Espacios")} ${dim("(cambiarlos: staff de Ghosty)")}
16374
16424
  spaces archive|restore <espacio> Arch\xEDvalo (sale de /app y de spaces ls; agentes y datos se quedan) o tr\xE1elo de vuelta (due\xF1o o staff) [--dry-run]
16375
16425
  spaces transfer <espacio> --to CORREO Due\xF1o nuevo; los agentes se van con \xE9l (las cajas ACP se mudan con su /data, las del pool se reciclan) [--keep-as admin|member|none] [--copy-keys] [--dry-run]
16376
16426
  spaces log <espacio> Su bit\xE1cora: traspasos, staff que entra o sale, renovaciones (due\xF1o o staff) [--since 30d] [--action workspace]
16377
- spaces members ls|add|rm <espacio> [correo] Su gente, sin la pantalla Miembros (due\xF1o, admin o staff) [--role member|admin] [--until 2026-10-28|14d: se retira solo]
16427
+ spaces members ls|add|rm <espacio> [correo] Su gente, sin la pantalla Miembros (due\xF1o, admin o staff) [--role member|admin|collaborator] [--room ROOM] [--until 2026-10-28|14d: se retira solo]
16378
16428
  access why <correo> <agente> Por qu\xE9 alguien ve un agente o un tablero (todas las razones y cu\xE1ndo vence lo temporal) [--workspace W --board B]
16379
16429
  staff ls [correo-staff] [correo-cliente] Staff de Ghosty asignado a cuentas cliente (staff) [--all: tambi\xE9n revocadas]
16380
16430
  staff assign|unassign <staff> <cliente> El staff ve y opera agentes, documentos, bases y tableros de esa cuenta como s\xED mismo (sin llaves, cobro ni traspasos; queda en bit\xE1cora) [--reason]
16381
16431
  settings ls|set|unset <CLAVE> [valor] Perillas de la plataforma (staff): qui\xE9n atiende /c sin cuenta, subagentes nativos\u2026 [+id/-id edita una lista]
16432
+ ads watch [ls|run] Vig\xEDa de campa\xF1as (staff): campa\xF1as de Meta que dejan de entregar y n\xFAmeros \xABs\xF3lo anuncios\xBB sin leads en 24 h [run --dry-run: lo que avisar\xEDa]
16382
16433
  audit ls La bit\xE1cora: qui\xE9n cambi\xF3 qu\xE9 (ficha, canales, pausas, respuestas, traspasos, \xABentrar como\xBB) [--workspace SLUG] [--agent ID] [--actor CORREO] [--action A] [--since 24h] [--limit N]
16383
16434
  factory rooms ls|connect|disconnect Deja lista la f\xE1brica de un repo sin abrir Teams: crea un room y con\xE9ctale el repo [connect due\xF1o/repo --room R --create]
16384
16435
  factory run "\u2026" --repo o/n [--wait] P\xEDdele algo a la Software Factory desde la terminal (@plan planea, @build construye, @check revisa); --wait regresa cuando te toca decidir [runs watch|approve|changes|merge|card <pedido>]
@@ -16545,7 +16596,7 @@ ${bold("Opciones globales")}
16545
16596
  ${bold("C\xF3digos de salida")} 0 bien \xB7 1 error de la API \xB7 2 uso incorrecto \xB7 3 sin sesi\xF3n
16546
16597
  ${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.")}
16547
16598
  ${dim("Documentaci\xF3n: https://www.ghosty.studio/docs/cli")}`;
16548
- var COMMANDS = ["login", "logout", "whoami", "usage", "use", "apply", "plan", "engines", "bags", "spaces", "workspaces", "staff", "settings", "audit", "factory", "demos", "sites", "push", "novedades", "completion", "cards", "agents", "files", "skills", "mcp", "dbs", "turns", "try", "chat", "conversations", "schedule", "teams", "partner", "me", "channels", "board", "boards", "access", "credentials", "connectors", "browser", "support", "docs", "help"];
16599
+ var COMMANDS = ["login", "logout", "whoami", "usage", "use", "apply", "plan", "engines", "bags", "spaces", "workspaces", "staff", "settings", "ads", "audit", "factory", "demos", "sites", "push", "novedades", "completion", "cards", "agents", "files", "skills", "mcp", "dbs", "turns", "try", "chat", "conversations", "schedule", "teams", "partner", "me", "channels", "board", "boards", "access", "credentials", "connectors", "browser", "support", "docs", "help"];
16549
16600
  var AGENTS_SUBS = ["ls", "create", "get", "show", "set", "export", "doctor", "docs", "share", "unshare", "shares", "teams", "rm", "restart", "demo", "embed", "box", "token", "memory", "transfer"];
16550
16601
  function unknown(what, input, known, helpCmd) {
16551
16602
  const s2 = suggest(input, known);
@@ -16846,7 +16897,7 @@ async function main() {
16846
16897
  const tokens = splitTokens(pos, v);
16847
16898
  globals.token = tokens.session;
16848
16899
  globals.profile = v.profile;
16849
- if (v.version) return out({ version: "0.74.1" }, () => "0.74.1");
16900
+ if (v.version) return out({ version: "0.75.0" }, () => "0.75.0");
16850
16901
  for (const n of normalizeArgs(pos, v)) if (!globals.json) info(dim(n));
16851
16902
  const context = (await readConfig()).workspace;
16852
16903
  if (!explicitToken()?.startsWith("gat_")) {
@@ -17013,6 +17064,8 @@ async function main() {
17013
17064
  return staffCmd(sub, a1, a2, v);
17014
17065
  case "settings":
17015
17066
  return settingsCmd(sub, a1, a2);
17067
+ case "ads":
17068
+ return adsCmd(sub, a1, v);
17016
17069
  case "audit":
17017
17070
  return auditCmd(sub, v, context);
17018
17071
  case "factory":
@@ -17039,7 +17092,7 @@ async function main() {
17039
17092
  throw unknown(cmd, cmd, COMMANDS, "ghosty --help");
17040
17093
  }
17041
17094
  }
17042
- var notice = updateNotice("0.74.1").catch(() => null);
17095
+ var notice = updateNotice("0.75.0").catch(() => null);
17043
17096
  main().then(
17044
17097
  async () => {
17045
17098
  const n = await notice;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghostystudio/cli",
3
- "version": "0.74.1",
3
+ "version": "0.75.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": {