@ghostystudio/cli 0.74.2 → 0.76.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 +7 -1
  2. package/dist/index.js +88 -22
  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
@@ -121,6 +122,7 @@ ghosty connectors ls|connect|disconnect [id] [--q TEXT] [--from-env VAR] [--no-
121
122
  ghosty support open --title T --body TEXT · support ls | show <id> | reply <id> --body TEXT # ask Ghosty for help; answered by email
122
123
  ghosty browser logout # revoke your terminal agents' browser tokens and disconnect Ghosty for Chrome
123
124
  ghosty audit ls [--workspace SLUG] [--agent ID] [--actor EMAIL] [--action A] [--since 24h] [--limit N] # who changed what
125
+ ghosty factory rooms repos # the repos your GitHub App sees, and where they're connected
124
126
  ghosty factory rooms connect owner/name --room R --create # set up the factory for a repo from the terminal
125
127
  ghosty factory run "…" --repo owner/name [--wait] # hand a request to the Software Factory from the terminal
126
128
  ghosty factory runs watch|approve|changes|merge|card <run> # wait for it and decide it without opening Teams
@@ -140,9 +142,13 @@ Every command has its own help with examples: `ghosty <command> --help` or `ghos
140
142
 
141
143
  Exit codes: `0` ok · `1` API error · `2` usage error · `3` not signed in.
142
144
 
145
+ ### Changes in 0.75.0
146
+
147
+ - `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.
148
+
143
149
  ### Changes in 0.73.0
144
150
 
145
- - `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.
151
+ - `factory rooms ls|repos|connect|disconnect`: `repos` lists what your GitHub App sees (newest first) and the rooms each repo is in. 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.
146
152
 
147
153
  ### Changes in 0.72.0
148
154
 
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.2"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
1178
+ headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.76.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.2"}`,
1220
+ "user-agent": `ghosty-cli/${"0.76.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.2"}`
11198
+ "user-agent": `ghosty-cli/${"0.76.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: [],
@@ -13583,6 +13591,7 @@ var HELPS_ES = {
13583
13591
  ["--yes", "rename, approve, changes, merge: se salta la confirmaci\xF3n (obligatorio sin terminal)"]
13584
13592
  ],
13585
13593
  notes: [
13594
+ "rooms repos lista los repos que ve tu GitHub App (los m\xE1s recientes primero) y en qu\xE9 rooms ya est\xE1 cada uno.",
13586
13595
  "rooms connect revisa que TU instalaci\xF3n de la GitHub App vea el repo (si no, te da la liga para darle acceso) y dice qu\xE9 tan listo est\xE1 para agentes (niveles 1\u20133 y lo que falta). Cualquier miembro puede conectar; desconectar, quien lo conect\xF3 o el due\xF1o. Queda en bit\xE1cora (factory.room_connect / factory.room_disconnect).",
13587
13596
  "run publica \xAB@plan <tu texto>\xBB en el room A TU NOMBRE, como si lo escribieras en Teams: el mismo plan, firmas y tarjeta. Escr\xEDbelo como ticket: el objetivo, por qu\xE9 y c\xF3mo se sabe que qued\xF3. Cualquier miembro puede pedir, firmar, pedir cambios y mergear; todo queda en bit\xE1cora (factory.run, factory.run_approve\u2026).",
13588
13597
  "card baja la tarjeta viva del pedido al final de su hilo; la anterior queda como huella quieta con liga a la viva. Los roles tambi\xE9n pueden (factory_card).",
@@ -14359,10 +14368,11 @@ HELPS.novedades = {
14359
14368
  };
14360
14369
  HELPS.spaces = {
14361
14370
  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]"],
14371
+ 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
14372
  flags: [
14364
14373
  ["--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)"],
14374
+ ["--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)"],
14375
+ ["--room ROOM", "members add --role collaborator: the Teams room they enter (required with collaborator)"],
14366
14376
  ["--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
14377
  ["--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
14378
  ["--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 +14450,14 @@ HELPS.staff = {
14440
14450
  ],
14441
14451
  learn: `${REF}#staff-asignado`
14442
14452
  };
14453
+ HELPS.ads = {
14454
+ 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.",
14455
+ usage: ["ghosty ads watch [ls]", "ghosty ads watch run [--dry-run]"],
14456
+ flags: [["--dry-run", "run: what it would alert, without alerting or saving"]],
14457
+ examples: ["ghosty ads watch", "ghosty ads watch run --dry-run"],
14458
+ 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."],
14459
+ learn: `${REF}#vigia-de-campanas`
14460
+ };
14443
14461
  HELPS.settings = {
14444
14462
  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
14463
  usage: ["ghosty settings ls", "ghosty settings set <KEY> <value|+id|-id>", "ghosty settings unset <KEY>"],
@@ -14476,6 +14494,7 @@ HELPS.factory = {
14476
14494
  summary: "Hand requests to a workspace's Software Factory from the terminal and decide them (approve the plan, merge the PR) without opening Teams \u2014 the factory as a sidekick for your coding agent. Also its runs (they live in its Teams): list them with time and cost, see one (current plan, PR, preview, latest events, which role is working), see where its time and money went and why it got stuck or failed, totals for the workspace, and fix a run's title.",
14477
14495
  usage: [
14478
14496
  "ghosty factory rooms ls [--full] [--workspace SLUG]",
14497
+ "ghosty factory rooms repos [--workspace SLUG]",
14479
14498
  "ghosty factory rooms connect OWNER/REPO --room R [--create] [--private] [--workspace SLUG]",
14480
14499
  "ghosty factory rooms disconnect OWNER/REPO --room R [--yes]",
14481
14500
  `ghosty factory run "What, why and how we know it's done" --repo OWNER/REPO [--room R] [--wait] [--timeout 1800] [--workspace SLUG]`,
@@ -14515,7 +14534,7 @@ HELPS.factory = {
14515
14534
  ["--yes", "rename, approve, changes, merge: skip the confirmation (required without a terminal)"]
14516
14535
  ],
14517
14536
  examples: ["ghosty factory rooms connect blissito/ghosty-studio --room studio --create --workspace business", 'ghosty factory run "Add a COLLABORATOR role: invited by room, lands in the room, no personal agent. Done when an invite to #factory logs in straight there" --repo blissito/ghosty-studio --wait --workspace business', "ghosty factory runs approve 31 --plan-version 2 --workspace business", "ghosty factory runs watch 31 --workspace business", "ghosty factory runs merge 31 --workspace business", "ghosty factory runs ls --workspace business --room mailmask", "ghosty factory runs show 15 --workspace business", "ghosty factory runs show 13 --timing --workspace mercadito-verde", "ghosty factory runs why 13 --workspace mercadito-verde", "ghosty factory runs advise 115 --workspace business", "ghosty factory runs stats --since 7d --workspace mercadito-verde", 'ghosty factory runs rename 15 "Docs del CLI al d\xEDa" --workspace business --dry-run'],
14518
- notes: ["rooms connect checks that YOUR GitHub App installation sees the repo (if not, it gives you the link to grant access) and tells you how ready the repo is for agents (levels 1\u20133 and what's missing). Any member can connect; only whoever connected it, or the owner, can disconnect. Logged as factory.room_connect / factory.room_disconnect.", "run posts \xAB@plan <your text>\xBB in the room under YOUR name, like typing it in Teams: the same plan, approvals and card. Write it like a ticket: the goal, why, and how you'll know it's done. Any member can run, approve, ask for changes and merge; each one is logged (factory.run, factory.run_approve\u2026).", "card brings the run's live card down to the bottom of its thread; the old one stays as a still footprint linking to the live one. The roles can do it too (factory_card).", "Reading needs membership in the workspace; rename needs owner, admin or Ghosty staff, and is logged (factory.run_rename). The room's card picks the new title up on its next refresh.", "Who is @plan or @build: ghosty agents ls --handle plan --workspace SLUG.", "stats: runs, % merged, median minutes to PR and to merge, @check\u2192@build loops and red CI per run, and cost per run and per merged PR. The cost counts @plan's first turn in the thread, before the run existed.", "--timing reads each role's transcript: engines without one (ACP) show minutes and cost but no commands. It also lists stuck patterns: a command open 8+ min, the same command 4 times, the same error 3 times, going back and forth, or apt install inside a box.", "why: if the run wasn't healthy (stuck patterns, @build over 30 min, loops, red CI, escalated), a category (missing_service, unresolved_build_or_tests, fixed_strategy\u2026), the critical step with its evidence and what to change next time. First it checks the approved plan's criteria against the evidence: approved without the preview a criterion asked for, merged without preview, smoke failed on the merged commit, or a CI criterion with CI none \u2192 approved_unmet_criterion, above any stuck pattern. A healthy run doesn't call the model. When a run closes unhealthy, the room gets this summary in its thread.", "advise: for an ESCALATED run, what to do \u2014 let @build fix it or redo the plan \u2014 with the note for @build point by point (each one cites the finding or plan step behind it) and what @check should review. It compares @check's findings against the signed plan. Nothing is applied: it prints the exact approve/changes command. show tells you why it was escalated.", "While a role works, if it gets stuck the room gets a \u23F3 message in the thread and the role is told what to check; from the second time in the same run, whoever asked gets a push."],
14537
+ notes: ["rooms repos lists the repos your GitHub App sees (most recent first) and which rooms each one is already in.", "rooms connect checks that YOUR GitHub App installation sees the repo (if not, it gives you the link to grant access) and tells you how ready the repo is for agents (levels 1\u20133 and what's missing). Any member can connect; only whoever connected it, or the owner, can disconnect. Logged as factory.room_connect / factory.room_disconnect.", "run posts \xAB@plan <your text>\xBB in the room under YOUR name, like typing it in Teams: the same plan, approvals and card. Write it like a ticket: the goal, why, and how you'll know it's done. Any member can run, approve, ask for changes and merge; each one is logged (factory.run, factory.run_approve\u2026).", "card brings the run's live card down to the bottom of its thread; the old one stays as a still footprint linking to the live one. The roles can do it too (factory_card).", "Reading needs membership in the workspace; rename needs owner, admin or Ghosty staff, and is logged (factory.run_rename). The room's card picks the new title up on its next refresh.", "Who is @plan or @build: ghosty agents ls --handle plan --workspace SLUG.", "stats: runs, % merged, median minutes to PR and to merge, @check\u2192@build loops and red CI per run, and cost per run and per merged PR. The cost counts @plan's first turn in the thread, before the run existed.", "--timing reads each role's transcript: engines without one (ACP) show minutes and cost but no commands. It also lists stuck patterns: a command open 8+ min, the same command 4 times, the same error 3 times, going back and forth, or apt install inside a box.", "why: if the run wasn't healthy (stuck patterns, @build over 30 min, loops, red CI, escalated), a category (missing_service, unresolved_build_or_tests, fixed_strategy\u2026), the critical step with its evidence and what to change next time. First it checks the approved plan's criteria against the evidence: approved without the preview a criterion asked for, merged without preview, smoke failed on the merged commit, or a CI criterion with CI none \u2192 approved_unmet_criterion, above any stuck pattern. A healthy run doesn't call the model. When a run closes unhealthy, the room gets this summary in its thread.", "advise: for an ESCALATED run, what to do \u2014 let @build fix it or redo the plan \u2014 with the note for @build point by point (each one cites the finding or plan step behind it) and what @check should review. It compares @check's findings against the signed plan. Nothing is applied: it prints the exact approve/changes command. show tells you why it was escalated.", "While a role works, if it gets stuck the room gets a \u23F3 message in the thread and the role is told what to check; from the second time in the same run, whoever asked gets a push."],
14519
14538
  learn: `${REF}#fabrica`
14520
14539
  };
14521
14540
  function helpText(cmd) {
@@ -15534,7 +15553,19 @@ async function roomsCmd(ws, verb, repo, v) {
15534
15553
  ].join("\n")
15535
15554
  );
15536
15555
  }
15537
- if (verb !== "connect" && verb !== "disconnect") throw usage(t(`Unknown rooms subcommand: ${verb}.`, `Subcomando de rooms desconocido: ${verb}.`), "ghosty factory rooms ls | connect <owner/repo> --room R [--create] [--private] | disconnect <owner/repo> --room R --yes");
15556
+ if (verb === "repos") {
15557
+ const r2 = await api(`${path}?repos=1`);
15558
+ return out(r2, () => {
15559
+ if (!r2.connected) return red(t(`GitHub isn't connected: ${r2.detail ?? "connect it with ghosty connectors connect github"}`, `GitHub no est\xE1 conectado: ${r2.detail ?? "con\xE9ctalo con ghosty connectors connect github"}`));
15560
+ if (!r2.installed) return red(t(`The Ghosty GitHub App isn't installed: ${r2.installUrl ?? ""}`, `La GitHub App de Ghosty no est\xE1 instalada: ${r2.installUrl ?? ""}`));
15561
+ const rows = r2.repos.map((x) => ({ repo: x.repo, rooms: x.rooms.map((s2) => `#${s2}`).join(", ") || dim("\u2014"), private: x.private ? t("yes", "s\xED") : "", pushed: x.pushedAt?.slice(0, 10) ?? "" }));
15562
+ return [
15563
+ rows.length ? table(rows, ["repo", "rooms", "private", "pushed"]) : dim(t("The App doesn't see any repo yet.", "La App todav\xEDa no ve ning\xFAn repo.")),
15564
+ dim(t(`Missing one? Give the App access: ${r2.installUrl ?? ""}`, `\xBFFalta uno? Dale acceso a la App: ${r2.installUrl ?? ""}`))
15565
+ ].join("\n");
15566
+ });
15567
+ }
15568
+ if (verb !== "connect" && verb !== "disconnect") throw usage(t(`Unknown rooms subcommand: ${verb}.`, `Subcomando de rooms desconocido: ${verb}.`), "ghosty factory rooms ls | repos | connect <owner/repo> --room R [--create] [--private] | disconnect <owner/repo> --room R --yes");
15538
15569
  if (!repo) throw usage(t("Missing the repo.", "Falta el repo."), `ghosty factory rooms ${verb} owner/repo --room R --workspace ${ws}`);
15539
15570
  if (!v.room) throw usage(t("Which room?", "\xBFQu\xE9 room?"), t("Add --room NAME (with --create it's made if missing).", "Agrega --room NOMBRE (con --create se crea si no existe)."));
15540
15571
  if (verb === "disconnect") {
@@ -15874,6 +15905,37 @@ async function settingsCmd(sub, key, value) {
15874
15905
  throw usage(t(`Unknown settings subcommand: ${sub}.`, `Subcomando de settings desconocido: ${sub}.`), "ghosty settings ls|set|unset <KEY> [value]");
15875
15906
  }
15876
15907
 
15908
+ // src/commands/ads.ts
15909
+ init_api();
15910
+ init_i18n();
15911
+ init_util();
15912
+ var day3 = (iso) => iso ? iso.slice(0, 16).replace("T", " ") : "";
15913
+ async function adsCmd(sub, action, v) {
15914
+ if (sub !== "watch") throw usage(t(`Unknown ads subcommand: ${sub ?? ""}.`, `Subcomando de ads desconocido: ${sub ?? ""}.`), "ghosty ads watch [ls|run] [--dry-run]");
15915
+ if (!action || action === "ls" || action === "list") {
15916
+ const r = await api("/api/v2/staff/ads-watch");
15917
+ return out(
15918
+ r,
15919
+ () => [
15920
+ lavender(t("Meta ad accounts", "Cuentas de Meta")),
15921
+ 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)")),
15922
+ "",
15923
+ lavender(t("Ads-only WhatsApp numbers", "N\xFAmeros de WhatsApp \xABs\xF3lo anuncios\xBB")),
15924
+ 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)"))
15925
+ ].join("\n")
15926
+ );
15927
+ }
15928
+ if (action === "run") {
15929
+ const r = await api("/api/v2/staff/ads-watch", { method: "POST", body: { dryRun: Boolean(v["dry-run"]) } });
15930
+ return out(
15931
+ r,
15932
+ () => r.findings.length ? r.findings.map((f) => `${lavender(r.dryRun ? t("would alert", "avisar\xEDa") : t("alerted", "avis\xE9"))} ${f.title}
15933
+ ${f.detail.split("\n").join("\n ")}`).join("\n") : dim(t("Nothing to alert.", "Nada que avisar."))
15934
+ );
15935
+ }
15936
+ throw usage(t(`Unknown ads watch action: ${action}.`, `Acci\xF3n de ads watch desconocida: ${action}.`), "ghosty ads watch [ls|run] [--dry-run]");
15937
+ }
15938
+
15877
15939
  // src/commands/support.ts
15878
15940
  init_api();
15879
15941
  import { readFile as readFile14 } from "node:fs/promises";
@@ -16177,13 +16239,14 @@ ${bold("Workspaces")} ${dim("(changing them: Ghosty staff)")}
16177
16239
  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]
16178
16240
  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]
16179
16241
  spaces log <workspace> Its log: transfers, staff in/out, renewals (owner or staff) [--since 30d] [--action workspace]
16180
- 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]
16242
+ 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]
16181
16243
  access why <email> <agent-id> Why someone sees an agent or a board (every reason, and when temporary access expires) [--workspace W --board B]
16182
16244
  staff ls [staff-email] [client-email] Ghosty staff assigned to client accounts (staff) [--all: also revoked]
16183
16245
  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]
16184
16246
  settings ls|set|unset <KEY> [value] Platform switches (staff): who answers /c without an account, native subagents\u2026 [+id/-id edits a list]
16247
+ 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]
16185
16248
  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]
16186
- 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]
16249
+ factory rooms ls|repos|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]
16187
16250
  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>]
16188
16251
  factory runs ls|show|why|advise|stats|rename A workspace's Software Factory runs: list (time and cost), detail (plan, PR, preview, who is working), where the time and money went, why it got stuck or failed, what to do when it's escalated, totals, fix a title [--workspace SLUG] [--room R] [--status S] [show --timing] [why --refresh] [stats --since 30d --repo R]
16189
16252
 
@@ -16375,13 +16438,14 @@ ${bold("Espacios")} ${dim("(cambiarlos: staff de Ghosty)")}
16375
16438
  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]
16376
16439
  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]
16377
16440
  spaces log <espacio> Su bit\xE1cora: traspasos, staff que entra o sale, renovaciones (due\xF1o o staff) [--since 30d] [--action workspace]
16378
- 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]
16441
+ 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]
16379
16442
  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]
16380
16443
  staff ls [correo-staff] [correo-cliente] Staff de Ghosty asignado a cuentas cliente (staff) [--all: tambi\xE9n revocadas]
16381
16444
  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]
16382
16445
  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]
16446
+ 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]
16383
16447
  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]
16384
- 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]
16448
+ factory rooms ls|repos|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]
16385
16449
  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>]
16386
16450
  factory runs ls|show|why|advise|stats|rename Los pedidos de la Software Factory de un espacio: lista (tiempo y costo), detalle (plan, PR, preview, qui\xE9n trabaja), en qu\xE9 se fue el tiempo y el dinero, por qu\xE9 se ator\xF3 o fall\xF3, qu\xE9 hacer si se escal\xF3, totales, corregir un t\xEDtulo [--workspace SLUG] [--room R] [--status S] [show --timing] [why --refresh] [stats --since 30d --repo R]
16387
16451
 
@@ -16546,7 +16610,7 @@ ${bold("Opciones globales")}
16546
16610
  ${bold("C\xF3digos de salida")} 0 bien \xB7 1 error de la API \xB7 2 uso incorrecto \xB7 3 sin sesi\xF3n
16547
16611
  ${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.")}
16548
16612
  ${dim("Documentaci\xF3n: https://www.ghosty.studio/docs/cli")}`;
16549
- 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"];
16613
+ 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"];
16550
16614
  var AGENTS_SUBS = ["ls", "create", "get", "show", "set", "export", "doctor", "docs", "share", "unshare", "shares", "teams", "rm", "restart", "demo", "embed", "box", "token", "memory", "transfer"];
16551
16615
  function unknown(what, input, known, helpCmd) {
16552
16616
  const s2 = suggest(input, known);
@@ -16847,7 +16911,7 @@ async function main() {
16847
16911
  const tokens = splitTokens(pos, v);
16848
16912
  globals.token = tokens.session;
16849
16913
  globals.profile = v.profile;
16850
- if (v.version) return out({ version: "0.74.2" }, () => "0.74.2");
16914
+ if (v.version) return out({ version: "0.76.0" }, () => "0.76.0");
16851
16915
  for (const n of normalizeArgs(pos, v)) if (!globals.json) info(dim(n));
16852
16916
  const context = (await readConfig()).workspace;
16853
16917
  if (!explicitToken()?.startsWith("gat_")) {
@@ -17014,6 +17078,8 @@ async function main() {
17014
17078
  return staffCmd(sub, a1, a2, v);
17015
17079
  case "settings":
17016
17080
  return settingsCmd(sub, a1, a2);
17081
+ case "ads":
17082
+ return adsCmd(sub, a1, v);
17017
17083
  case "audit":
17018
17084
  return auditCmd(sub, v, context);
17019
17085
  case "factory":
@@ -17040,7 +17106,7 @@ async function main() {
17040
17106
  throw unknown(cmd, cmd, COMMANDS, "ghosty --help");
17041
17107
  }
17042
17108
  }
17043
- var notice = updateNotice("0.74.2").catch(() => null);
17109
+ var notice = updateNotice("0.76.0").catch(() => null);
17044
17110
  main().then(
17045
17111
  async () => {
17046
17112
  const n = await notice;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghostystudio/cli",
3
- "version": "0.74.2",
3
+ "version": "0.76.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": {