@ghostystudio/cli 0.77.0 → 0.83.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 +356 -45
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -23,6 +23,7 @@ ghosty engines ls [--models] # valid --engine / --model
23
23
  ghosty bags ls|create|set|rm <workspace> ["bag"] [--tokens 20M] [--recurring on|off] [--hard-cap on|off] [--note] # token bags (changing: staff)
24
24
  ghosty spaces create <slug> [--combo …: staff] [--dry-run] # yours, trial
25
25
  ghosty spaces renew <workspace> --until YYYY-MM-DD [--note] [--dry-run] # staff
26
+ ghosty spaces plan <workspace> trial|pro|empresarial [--courtesy] [--until YYYY-MM-DD] [--note] [--dry-run] # staff, same rules as /admin/tenants, no Stripe charge
26
27
  ghosty spaces rename <old> <new> [--dry-run] # staff, slug only
27
28
  ghosty spaces transfer <workspace> --to EMAIL [--keep-as admin] [--copy-keys] [--dry-run] # staff, new owner
28
29
  ghosty agents ls [--engine E] [--ids] [--sponsor EMAIL | --owner EMAIL] # --sponsor/--owner: any account (Ghosty staff)
@@ -145,6 +146,10 @@ Every command has its own help with examples: `ghosty <command> --help` or `ghos
145
146
 
146
147
  Exit codes: `0` ok · `1` API error · `2` usage error · `3` not signed in.
147
148
 
149
+ ### Changes in 0.83.0
150
+
151
+ - `spaces plan <workspace> trial|pro|empresarial [--courtesy] [--until YYYY-MM-DD] [--note] [--dry-run]` (staff): change a workspace's plan without opening /admin/tenants, with the same rules. The dry run shows plan, model cap, tokens (used/included, hard cap or not) and «paid until» before → after. Never charges Stripe nor emails the customer; `--courtesy` records it as a no-charge courtesy in the plan note and the audit (`workspace.plan`).
152
+
148
153
  ### Changes in 0.75.0
149
154
 
150
155
  - `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.
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.77.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
1178
+ headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.83.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.77.0"}`,
1220
+ "user-agent": `ghosty-cli/${"0.83.0"}`,
1221
1221
  "accept-language": lang(),
1222
1222
  ...opts.body !== void 0 ? { "content-type": "application/json" } : {},
1223
1223
  ...opts.headers
@@ -1264,7 +1264,7 @@ var mteams_exports = {};
1264
1264
  __export(mteams_exports, {
1265
1265
  mteamsCmd: () => mteamsCmd
1266
1266
  });
1267
- import { writeFile as writeFile7 } from "node:fs/promises";
1267
+ import { writeFile as writeFile8 } from "node:fs/promises";
1268
1268
  function list(bots) {
1269
1269
  return bots.length ? table(
1270
1270
  bots.map((b) => ({
@@ -1306,7 +1306,7 @@ ${dim(`${t("Next", "Sigue")}: ghosty channels mteams zip ${id} ${b?.id ?? "<bot-
1306
1306
  const res = await request(`${path}/${enc9(botId)}/zip`);
1307
1307
  const bytes = Buffer.from(await res.arrayBuffer());
1308
1308
  const file = v.out ?? "ghosty-teams.zip";
1309
- await writeFile7(file, bytes);
1309
+ await writeFile8(file, bytes);
1310
1310
  return out({ saved: file, bytes: bytes.length }, () => `${lavender("\u2713")} ${t(`Saved ${file}: upload it in Teams admin \u2192 Manage apps.`, `Se guard\xF3 ${file}: s\xFAbelo en Teams admin \u2192 Administrar apps.`)}`);
1311
1311
  }
1312
1312
  case "on":
@@ -9017,7 +9017,7 @@ ${bagsText(r)}`);
9017
9017
  }
9018
9018
  }
9019
9019
  async function spacesCmd(sub, slug, arg, v, context) {
9020
- const ex = "ghosty spaces ls [--user EMAIL] \xB7 create <slug> [--combo teams|sales|both|none] [--dry-run] \xB7 join|leave <workspace> \xB7 set <workspace> --combo teams|sales|both|none | --model-cap 0.25|1|4|none | --house-claude on|off \xB7 renew <workspace> --until 2026-11-01 [--note TEXT] \xB7 rename <old> <new> [--dry-run] \xB7 archive|restore <workspace> [--dry-run] \xB7 transfer <workspace> --to EMAIL [--keep-as admin|member|none] [--copy-keys] [--dry-run] \xB7 log <workspace> [--since 30d] [--action workspace]";
9020
+ const ex = "ghosty spaces ls [--user EMAIL] \xB7 create <slug> [--combo teams|sales|both|none] [--dry-run] \xB7 join|leave <workspace> \xB7 set <workspace> --combo teams|sales|both|none | --model-cap 0.25|1|4|none | --house-claude on|off \xB7 renew <workspace> --until 2026-11-01 [--note TEXT] \xB7 plan <workspace> trial|pro|empresarial [--courtesy] [--until DATE] [--note TEXT] \xB7 rename <old> <new> [--dry-run] \xB7 archive|restore <workspace> [--dry-run] \xB7 transfer <workspace> --to EMAIL [--keep-as admin|member|none] [--copy-keys] [--dry-run] \xB7 log <workspace> [--since 30d] [--action workspace]";
9021
9021
  if (!sub || sub === "ls" || sub === "list") {
9022
9022
  const r = await api(`/api/v2/workspaces${v.user ? `?user=${enc2(v.user)}` : ""}`);
9023
9023
  const rows = r.workspaces.map((w) => ({ workspace: w.slug, role: w.role ?? "", plan: w.plan, combo: w.combo, owner: w.owner ?? "", paid: w.paidUntil ?? "" }));
@@ -9085,6 +9085,24 @@ ${t("Renew?", "\xBFRenovar?")}`, { yes: v.yes, retry: `ghosty spaces renew ${slu
9085
9085
  const r = await api(path, { method: "POST", body: { until: v.until, ...v.note ? { note: v.note } : {} } });
9086
9086
  return out(r, () => `${lavender("\u2713")} ${line2} ${dim(t("(logged in the workspace's audit)", "(queda en la bit\xE1cora del espacio)"))}`);
9087
9087
  }
9088
+ if (sub === "plan") {
9089
+ slug = needSpace(slug ?? context, "ghosty spaces plan <workspace> pro --courtesy --dry-run");
9090
+ if (!arg) throw usage(t("Which plan?", "\xBFQu\xE9 plan?"), `ghosty spaces plan ${slug} trial|pro|empresarial [--courtesy] [--until YYYY-MM-DD] --dry-run`);
9091
+ const path = wsPath(slug, "/plan");
9092
+ const body = { plan: arg, ...v.courtesy ? { courtesy: true } : {}, ...v.until ? { until: v.until } : {}, ...v.note ? { note: v.note } : {} };
9093
+ const plan = await api(path, { method: "POST", body: { ...body, dryRun: true } });
9094
+ const text = planChangeText(plan);
9095
+ if (!plan.changed && !v.courtesy && !v.note) return out(plan, () => dim(t(`Nothing to do: ${plan.workspace} is already ${plan.before.plan}.`, `Nada que hacer: ${plan.workspace} ya es ${plan.before.plan}.`)));
9096
+ if (v["dry-run"]) return out(plan, () => `${text}
9097
+ ${dim(t("Dry run: nothing changed.", "Simulaci\xF3n: no cambi\xF3 nada."))}`);
9098
+ const retry = `ghosty spaces plan ${slug} ${arg}${v.courtesy ? " --courtesy" : ""}${v.until ? ` --until ${v.until}` : ""}${v.note ? ` --note ${JSON.stringify(v.note)}` : ""}`;
9099
+ requireYesIfHeadless(v.yes, retry);
9100
+ await confirm(`${text}
9101
+ ${t("Change the plan?", "\xBFCambiar el plan?")}`, { yes: v.yes, retry });
9102
+ const r = await api(path, { method: "POST", body });
9103
+ return out(r, () => `${lavender("\u2713")} ${planChangeText(r)}
9104
+ ${dim(t("(logged in the workspace's audit)", "(queda en la bit\xE1cora del espacio)"))}`);
9105
+ }
9088
9106
  if (sub === "rename") {
9089
9107
  slug = needSpace(slug, ex);
9090
9108
  if (!arg) throw usage(t("Missing the new name.", "Falta el nombre nuevo."), `ghosty spaces rename ${slug} <new> --dry-run`);
@@ -9141,6 +9159,20 @@ ${r.warnings.map((w) => red(` \xB7 ${serverText(w)}`)).join("\n")}` : ""}`);
9141
9159
  }
9142
9160
  throw usage(t(`Unknown spaces subcommand: ${sub ?? "(none)"}.`, `Subcomando de spaces desconocido: ${sub ?? "(ninguno)"}.`), ex);
9143
9161
  }
9162
+ function planChangeText(p) {
9163
+ const cap = (x) => x === null ? t("no cap", "sin tope") : `${x}\xD7`;
9164
+ const tok = (s2) => `${fmtTokens(s2.tokensUsed)}/${fmtTokens(s2.tokensIncluded)}${s2.hardCap ? t(" (hard cap)", " (corta)") : ""}`;
9165
+ const paid = (s2) => `${s2.paidUntil ?? "\u2014"}${s2.expires ? t(" (trial ends)", " (fin de prueba)") : ""}`;
9166
+ const row = (label, a, b) => ` ${label.padEnd(10)} ${a} \u2192 ${a === b ? b : bold(b)}`;
9167
+ return [
9168
+ `${bold(p.workspace)}${p.courtesy ? dim(t(" \xB7 courtesy, no charge", " \xB7 cortes\xEDa, sin cobro")) : ""}`,
9169
+ row("plan", p.before.plan, p.after.plan),
9170
+ row(t("model cap", "modelos"), cap(p.before.modelCap), cap(p.after.modelCap)),
9171
+ row("tokens", tok(p.before), tok(p.after)),
9172
+ row(t("paid until", "pagado"), paid(p.before), paid(p.after)),
9173
+ ...p.warnings.map((w) => red(` \xB7 ${serverText(w)}`))
9174
+ ].join("\n");
9175
+ }
9144
9176
  async function workspaceUsage(slug, v) {
9145
9177
  slug = needSpace(slug, "ghosty usage --workspace <slug> --by agent,day --days 14");
9146
9178
  const r = await api(wsPath(slug, `/usage?days=${enc2(v.days ?? "14")}`));
@@ -11205,7 +11237,7 @@ async function call(path, v, init = {}) {
11205
11237
  "x-ghosty-ts": ts,
11206
11238
  "x-ghosty-sig": sig,
11207
11239
  "content-type": "application/json",
11208
- "user-agent": `ghosty-cli/${"0.77.0"}`
11240
+ "user-agent": `ghosty-cli/${"0.83.0"}`
11209
11241
  },
11210
11242
  body: method === "GET" ? void 0 : raw
11211
11243
  });
@@ -11482,9 +11514,66 @@ async function partnerCmd(sub, a1, a2, v, a3) {
11482
11514
  }
11483
11515
  }
11484
11516
 
11517
+ // src/commands/image.ts
11518
+ import { writeFile as writeFile6 } from "node:fs/promises";
11519
+ import { extname as extname2 } from "node:path";
11520
+ init_util();
11521
+ init_i18n();
11522
+ function outPath(base, i, n) {
11523
+ if (n === 1) return base;
11524
+ const ext = extname2(base);
11525
+ return `${ext ? base.slice(0, -ext.length) : base}-${i + 1}${ext}`;
11526
+ }
11527
+ async function imageCmd(sub, a1, a2, v) {
11528
+ const edit = sub === "edit";
11529
+ if (!edit && sub !== "generate" && sub !== "gen") {
11530
+ throw usage(t(`Unknown image subcommand: ${sub ?? "(none)"}.`, `Subcomando de image desconocido: ${sub ?? "(ninguno)"}.`), t("Try", "Prueba") + ': ghosty image generate "a cat astronaut" [--ref URL] [--aspect 16:9] \xB7 ghosty image edit URL "add a big -50 % in the corner"');
11531
+ }
11532
+ const base = edit ? a1 : void 0;
11533
+ const prompt = edit ? a2 : a1;
11534
+ if (edit && !base?.trim()) throw usage(t("Missing the URL of the image to edit.", "Falta la URL de la imagen a editar."), t("Example", "Ejemplo") + ': ghosty image edit https://img.ghosty.studio/u/x/promo.png "add a big red -50 % in the top right corner" --text "-50 %"');
11535
+ if (!prompt?.trim()) throw usage(t("Missing the prompt.", "Falta el prompt."), t("Example", "Ejemplo") + ': ghosty image generate "my mascot in a Halloween scene" --ref https://example.com/mascot.png --aspect 16:9');
11536
+ const body = {
11537
+ prompt,
11538
+ ...base ? { baseImageUrl: base } : {},
11539
+ ...v.ref?.length ? { referenceUrls: v.ref } : {},
11540
+ ...v["style-ref"]?.length ? { styleUrls: v["style-ref"] } : {},
11541
+ ...v.scene?.length ? { sceneUrls: v.scene } : {},
11542
+ ...v.direction?.length ? { directions: v.direction } : {},
11543
+ ...v.variants ? { variants: Number(v.variants) } : {},
11544
+ ...v.size ? { size: v.size } : {},
11545
+ ...v.text ? { text: v.text } : {},
11546
+ ...v.aspect ? { aspectRatio: v.aspect } : {},
11547
+ ...v.tenant ? { tenant: { externalId: v.tenant } } : {},
11548
+ ...v.agent ? { agent: v.agent } : {}
11549
+ };
11550
+ const r = await json("/api/v2/partner/images", v, { method: "POST", body });
11551
+ const images = r.images?.length ? r.images : [r];
11552
+ const saved = [];
11553
+ if (v.out) {
11554
+ for (const [i, img] of images.entries()) {
11555
+ const res = await fetch(img.url);
11556
+ if (!res.ok) throw new Error(t(`Could not download the image (${res.status}).`, `No se pudo bajar la imagen (${res.status}).`));
11557
+ const file = outPath(v.out, i, images.length);
11558
+ await writeFile6(file, Buffer.from(await res.arrayBuffer()));
11559
+ saved.push(file);
11560
+ }
11561
+ }
11562
+ const how = { edit: t("edited", "editada"), compose: t("composed from references", "compuesta con referencias"), generate: t("generated", "generada") }[r.mode] ?? r.mode;
11563
+ return out(
11564
+ r,
11565
+ () => [
11566
+ ...images.map((img) => `${img.url}${img.direction ? dim(` \xB7 ${img.direction}`) : ""}${img.width && img.height ? dim(` \xB7 ${img.width}\xD7${img.height}`) : ""}`),
11567
+ dim(`${how} \xB7 ${r.size ?? "2K"} \xB7 ${images.length}/${r.requested ?? 1} \xB7 ${t("charged", "cobrado")} USD ${r.priceUsd.toFixed(2)}`),
11568
+ ...r.failed?.length ? [dim(t(`${r.failed.length} failed (not charged)`, `${r.failed.length} fallaron (sin cobro)`))] : [],
11569
+ ...saved.map((f) => `${lavender("\u2713")} ${t(`saved to ${f}`, `guardada en ${f}`)}`)
11570
+ ].join("\n")
11571
+ );
11572
+ }
11573
+
11485
11574
  // src/commands/data.ts
11486
11575
  init_api();
11487
- import { readdir as readdir2, readFile as readFile6, writeFile as writeFile6 } from "node:fs/promises";
11576
+ import { readdir as readdir2, readFile as readFile6, writeFile as writeFile7 } from "node:fs/promises";
11488
11577
  import { basename as basename3, join as join5 } from "node:path";
11489
11578
 
11490
11579
  // src/rows.ts
@@ -11653,7 +11742,7 @@ async function dbsExport(id, db, v) {
11653
11742
  const suggested = /filename="([^"]+)"/.exec(res.headers.get("content-disposition") ?? "")?.[1] ?? (format === "csv" ? `${db}-${v.table}.csv` : `${db}.sqlite`);
11654
11743
  const file = v.out ?? suggested;
11655
11744
  const bytes = Buffer.from(await res.arrayBuffer());
11656
- await writeFile6(file, bytes);
11745
+ await writeFile7(file, bytes);
11657
11746
  const countsHeader = res.headers.get("x-gs-row-counts");
11658
11747
  const counts = countsHeader ? JSON.parse(Buffer.from(countsHeader, "base64").toString("utf8")) : {};
11659
11748
  const rows = Object.entries(counts).map(([tb, n]) => ({ table: tb, rows: n }));
@@ -11719,6 +11808,19 @@ ${dim(`${t(`${rows.length} row(s)`, `${rows.length} fila(s)`)} \xB7 ${Math.round
11719
11808
  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 ?? "" }));
11720
11809
  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.")));
11721
11810
  }
11811
+ case "checkpoints": {
11812
+ db = need2(db, t("database (id or name)", "la base (id o nombre)"), "ghosty dbs checkpoints <agent-id> <db>");
11813
+ const r = await api(agentPath2(id, `/dbs/${enc8(db)}/checkpoints`));
11814
+ const rows = r.checkpoints.map((c) => ({ id: c.id, when: c.at, rows: Object.values(c.counts).reduce((a, b) => a + b, 0), why: c.reason }));
11815
+ return out(r, () => rows.length ? table(rows, ["id", "when", "rows", "why"]) : dim(t("No checkpoints yet: one is taken before the first write of each agent turn.", "Todav\xEDa no hay checkpoints: se toma uno antes de la primera escritura de cada turno de un agente.")));
11816
+ }
11817
+ case "restore": {
11818
+ db = need2(db, t("database (id or name)", "la base (id o nombre)"), "ghosty dbs restore <agent-id> <db> <checkpoint-id> [--yes]");
11819
+ const ckpt = need2(rest[0], t("checkpoint id (from dbs checkpoints)", "el id del checkpoint (de dbs checkpoints)"), `ghosty dbs restore ${id} ${db} <checkpoint-id>`);
11820
+ await confirm(t(`Return every table of ${db} to checkpoint ${ckpt}? A new checkpoint is taken first, so you can undo it.`, `\xBFRegresar todas las tablas de ${db} al checkpoint ${ckpt}? Antes se toma otro, as\xED que se puede deshacer.`), { yes: v.yes, retry: `ghosty dbs restore ${id} ${db} ${ckpt}` });
11821
+ const r = await api(agentPath2(id, `/dbs/${enc8(db)}/checkpoints`), { method: "POST", body: { checkpointId: ckpt } });
11822
+ return out(r, () => `${lavender("\u2713")} ${t(`${r.db} returned to ${r.restoredTo.at}.`, `${r.db} regresada a ${r.restoredTo.at}.`)} ${dim(r.undo ? t(`Undo: ghosty dbs restore ${id} ${db} ${r.undo.id}`, `Para deshacer: ghosty dbs restore ${id} ${db} ${r.undo.id}`) : r.warning ?? "")}`);
11823
+ }
11722
11824
  case "grant": {
11723
11825
  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]");
11724
11826
  let rowScope;
@@ -11881,6 +11983,7 @@ function skipReason(r) {
11881
11983
  reaction: ["only a reaction", "s\xF3lo una reacci\xF3n"],
11882
11984
  channel_off: ["WhatsApp channel off for this agent", "canal de WhatsApp apagado en este agente"],
11883
11985
  no_quota: ["out of balance", "sin saldo"],
11986
+ own_number: ["sent by another of our agent numbers (bot loop guard)", "lo mand\xF3 otro n\xFAmero nuestro con agente (guarda de bucle)"],
11884
11987
  partner_token: ["partner did not issue a turn token", "el partner no dio token de turno"],
11885
11988
  partner_busy: ["partner at its concurrent-turn limit", "el partner lleg\xF3 a su tope de turnos a la vez"]
11886
11989
  };
@@ -13238,6 +13341,35 @@ var HELPS_ES = {
13238
13341
  summary: "Ghosty para Chrome: cerrar la sesi\xF3n del navegador.",
13239
13342
  notes: ["Revoca todos los tokens de Ghosty para Chrome que tengan tus agentes de terminal (Claude Code, CLI, @ghostystudio/browser-mcp) y desconecta la extensi\xF3n. Para volver, copia un comando nuevo del panel de la extensi\xF3n."]
13240
13343
  },
13344
+ image: {
13345
+ summary: "Im\xE1genes con IA (Gemini \xABNano Banana Pro\xBB) para los negocios de tu espacio de partner: generar, editar una que ya existe o componer con referencias; cobradas del saldo del espacio.",
13346
+ replace: [
13347
+ ["our mascot dressed as a pumpkin in a Halloween scene, flat illustration, no text", "nuestra mascota disfrazada de calabaza en una escena de Halloween, ilustraci\xF3n plana, sin texto"],
13348
+ ["add a big red badge in the top right corner", "a\xF1ade una etiqueta roja grande en la esquina superior derecha"],
13349
+ ["a cozy spa with candles, warm light", "un spa acogedor con velas, luz c\xE1lida"],
13350
+ ["flat illustration with thick outlines", "ilustraci\xF3n plana con contorno grueso"],
13351
+ ["warm studio photo", "foto c\xE1lida de estudio"],
13352
+ ['"change"', '"cambio"']
13353
+ ],
13354
+ flags: [
13355
+ ["--ref URL", "una referencia de personaje u objeto (https; JPEG, PNG, WebP, GIF o SVG); se repite, hasta 6. La imagen CONSERVA ese personaje u objeto (mascota, logo, producto)"],
13356
+ ["--style-ref URL", "una referencia de estilo (hasta 3): s\xF3lo se toma su t\xE9cnica, paleta e iluminaci\xF3n, no su contenido"],
13357
+ ["--scene URL", "un fondo o escena a usar (hasta 2)"],
13358
+ ["--text T", 'texto exacto que va dentro de la imagen ("-50 %", "$299"), hasta 200 caracteres'],
13359
+ ["--direction D", "una direcci\xF3n de arte; se repite (hasta 4) y sale una imagen por direcci\xF3n para escoger"],
13360
+ ["--variants N", "cu\xE1ntas opciones (1-4); cada una se cobra como una imagen"],
13361
+ ["--size SIZE", "1K, 2K (default) o 4K; 4K cuesta m\xE1s"],
13362
+ ["--aspect RATIO", "1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9 o 21:9 (edit: om\xEDtelo para conservar la del original)"],
13363
+ ["--tenant ID", "el negocio (externalId) para el que es: va al desglose de consumo y a su tope diario opcional"],
13364
+ ["--agent SLUG", "a qu\xE9 agente del partner se carga en el desglose (default: el de siempre)"],
13365
+ ["--out FILE", "adem\xE1s baja el PNG aqu\xED (varias: FILE-1.png, FILE-2.png\u2026)"]
13366
+ ],
13367
+ notes: [
13368
+ "Para cambiar una imagen que ya existe usa edit (no generate): sale la misma imagen con s\xF3lo ese cambio.",
13369
+ `Cada imagen se cobra aparte del saldo del espacio de partner: USD 0.25 a 1K/2K y USD 0.45 a 4K; las variantes se cobran una por una y las fallidas no se cobran. Sin saldo: 402. gs no pone topes propios; un negocio puede tener el suyo: tenants set <id> --config '{"imageDailyCap":20}'.`,
13370
+ "Auth: tu login (due\xF1a del espacio; --workspace si tienes varios) o la credencial del servidor del partner en el env (GHOSTY_PARTNER_KEY + GHOSTY_PARTNER_KEY_SECRET), que entonces exige --tenant."
13371
+ ]
13372
+ },
13241
13373
  me: {
13242
13374
  summary: "Tu cuenta: archivos, memorias, documentos editables y uso.",
13243
13375
  replace: [
@@ -13494,6 +13626,7 @@ var HELPS_ES = {
13494
13626
  ["--currency C", "pin: usd (default) o mxn \u2014 la moneda del cobro si hay que pagar"],
13495
13627
  ["--add-pack", "pin: ya usas tus lugares \u2014 suma 3 m\xE1s (mismo periodo y moneda de tu suscripci\xF3n) y fija \xE9ste"],
13496
13628
  ["--cancel / --resume", "pins: cancela al final del periodo (los sitios vuelven a Gratis con aviso), o lo deshace"],
13629
+ ["--days N", "search: la ventana de Search Console en d\xEDas (default 28; Google reporta con 2\u20133 d\xEDas de retraso)"],
13497
13630
  ["--title T", "publish: el nombre del sitio (default: el <title> del HTML o el nombre de la carpeta)"],
13498
13631
  ["--slug S", "publish: el slug de un sitio NUEVO (a-z, 0-9 y guiones, 3\u201360, no reservado)"],
13499
13632
  ["--site X", "publish: actualiza ese sitio de la casa (slug, id o URL) en vez de crear uno"]
@@ -13514,7 +13647,7 @@ var HELPS_ES = {
13514
13647
  notes: ["Lo pregunta quien administra lo preguntado: el due\xF1o del agente o del espacio, un admin del espacio, o staff.", "Dar o quitar acceso: ghosty spaces members add|rm, ghosty boards access."]
13515
13648
  },
13516
13649
  spaces: {
13517
- 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.",
13650
+ 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 o su plan, 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.",
13518
13651
  flags: [
13519
13652
  ["--user EMAIL", "ls (staff): los espacios de esa persona"],
13520
13653
  ["--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)"],
@@ -13524,8 +13657,9 @@ var HELPS_ES = {
13524
13657
  ["--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"],
13525
13658
  ["--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"],
13526
13659
  ["--house-claude on|off", "set (staff): sus agentes corren con la Claude de Ghosty si el due\xF1o no trae llave; recicla sus cajas de Claude"],
13527
- ["--until DATE", "renew: el nuevo \xABpagado hasta\xBB (AAAA-MM-DD)"],
13528
- ["--note TEXT", "renew: por qu\xE9 (va a la bit\xE1cora)"],
13660
+ ["--until DATE", "renew, plan: el nuevo \xABpagado hasta\xBB (AAAA-MM-DD); plan sin \xE9l conserva el actual"],
13661
+ ["--note TEXT", "renew, plan: por qu\xE9 (va a la bit\xE1cora)"],
13662
+ ["--courtesy", "plan (staff): lo marca como cortes\xEDa sin cobro (queda en la nota del plan del espacio y en la bit\xE1cora)"],
13529
13663
  ["--to EMAIL", "transfer: el due\xF1o nuevo (si no tiene cuenta se precrea y la reclama al entrar)"],
13530
13664
  ["--keep-as R", "transfer: el rol del due\xF1o anterior \u2014 admin | member (default) | none (sale del espacio)"],
13531
13665
  ["--copy-keys", "transfer: copia las llaves de proveedor del anterior y los conectores que usan sus agentes (MercadoPago, Skydropx\u2026) que le falten al nuevo. S\xF3lo cambia el due\xF1o: documentos, bases, tableros y n\xFAmeros que usan sus agentes se van con \xE9l; lo que tambi\xE9n se usa fuera del espacio se queda y se avisa"],
@@ -13533,7 +13667,7 @@ var HELPS_ES = {
13533
13667
  ["-n, --dry-run", "ense\xF1a qu\xE9 cambia, no cambia nada"],
13534
13668
  ["--yes", "s\xE1ltate la confirmaci\xF3n (obligatorio sin terminal)"]
13535
13669
  ],
13536
- notes: ["create sigue las reglas de la web: queda a tu nombre, nace como prueba de 30 d\xEDas, tu correo debe estar confirmado y tu plan topa cu\xE1ntos tienes. Sigue: ghosty partner enable <slug>.", "Con suscripci\xF3n de Stripe renew se niega: la fecha la mueve la factura pagada.", "rename cambia s\xF3lo el slug (id y namespace se quedan, as\xED que tablero, Stripe y WhatsApp siguen funcionando); las ligas viejas <slug>.sales dejan de servir.", "archive hace lo mismo que Ajustes \u2192 Archivar: el espacio sale de /app y de spaces ls, sus agentes y datos se quedan; pide escribir el slug. Deshacer: ghosty spaces restore <slug>. Queda en bit\xE1cora como workspace.archive / workspace.restore.", "transfer se lleva los agentes con el espacio: las cajas ACP se mudan al due\xF1o nuevo con su /data, las del pool se reciclan (el due\xF1o va horneado en la caja); sin --copy-keys la simulaci\xF3n dice qu\xE9 llaves le faltan al nuevo \u2014 esos agentes dejar\xEDan de contestar."]
13670
+ notes: ["create sigue las reglas de la web: queda a tu nombre, nace como prueba de 30 d\xEDas, tu correo debe estar confirmado y tu plan topa cu\xE1ntos tienes. Sigue: ghosty partner enable <slug>.", "Con suscripci\xF3n de Stripe renew se niega: la fecha la mueve la factura pagada.", "plan aplica las mismas reglas que \xABPlan y pago\xBB de /admin/tenants: de trial a pro se quitan el corte por saldo y el fin de prueba, el tope de modelos sube de 0.25\xD7 a 1\xD7 y los tokens se miden por mes natural. Nunca cobra en Stripe ni le escribe al cliente; la simulaci\xF3n ense\xF1a plan, modelos, tokens y \xABpagado hasta\xBB antes \u2192 despu\xE9s. Queda en bit\xE1cora como workspace.plan.", "rename cambia s\xF3lo el slug (id y namespace se quedan, as\xED que tablero, Stripe y WhatsApp siguen funcionando); las ligas viejas <slug>.sales dejan de servir.", "archive hace lo mismo que Ajustes \u2192 Archivar: el espacio sale de /app y de spaces ls, sus agentes y datos se quedan; pide escribir el slug. Deshacer: ghosty spaces restore <slug>. Queda en bit\xE1cora como workspace.archive / workspace.restore.", "transfer se lleva los agentes con el espacio: las cajas ACP se mudan al due\xF1o nuevo con su /data, las del pool se reciclan (el due\xF1o va horneado en la caja); sin --copy-keys la simulaci\xF3n dice qu\xE9 llaves le faltan al nuevo \u2014 esos agentes dejar\xEDan de contestar."]
13537
13671
  },
13538
13672
  staff: {
13539
13673
  summary: "Staff de Ghosty asignado a CUENTAS cliente (admin delegado que act\xFAa como s\xED mismo): ve y opera con su propia sesi\xF3n los agentes (con y sin espacio), documentos, bases y tableros de esa cuenta. Sin llaves, cobro, traspasos ni borrados. S\xF3lo el staff de Ghosty asigna; el cliente s\xF3lo ve \xABTu ejecutivo de Ghosty\xBB. Cada cambio y cada escritura quedan en la bit\xE1cora.",
@@ -13589,7 +13723,7 @@ var HELPS_ES = {
13589
13723
  ["--note T", "changes: qu\xE9 cambiar del plan (o por qu\xE9 replanear tras un escalado). approve tras un escalado: qu\xE9 debe arreglar @build"],
13590
13724
  ["--check-scope T", "approve/changes: lo \xFAnico que @check revisa en la siguiente vuelta (lo propone runs advise); lo nuevo va como nota, no como hallazgo"],
13591
13725
  ["--skip-preview", "merge: mergea sin la preview que ped\xEDa el plan (queda en bit\xE1cora)"],
13592
- ["--gen LLAVE,LLAVE", "env push: genera ah\xED mismo llaves al azar (32 bytes hex) con esos nombres; nunca se imprimen"],
13726
+ ["--gen KEY,KEY", "env push: genera ah\xED mismo llaves al azar (32 bytes hex) con esos nombres; nunca se imprimen"],
13593
13727
  ["--replace", "env push: reemplaza el .env entero de la preview (por defecto agrega y deja las dem\xE1s)"],
13594
13728
  ["--create", "rooms connect: crea el room si no existe"],
13595
13729
  ["--private", "rooms connect --create: el room nuevo es privado (s\xF3lo t\xFA, y a quien agregues)"],
@@ -13836,6 +13970,8 @@ var HELPS = {
13836
13970
  "ghosty dbs tables <agent-id> <db>",
13837
13971
  'ghosty dbs query <agent-id> <db> "SQL" [--arg V]\u2026 [--write]',
13838
13972
  "ghosty dbs origin <agent-id> <db> [--table T] [--since 2026-10-10] [--limit N]",
13973
+ "ghosty dbs checkpoints <agent-id> <db>",
13974
+ "ghosty dbs restore <agent-id> <db> <checkpoint-id> [--yes]",
13839
13975
  "ghosty dbs export <agent-id> <db> [--table T] [--out FILE]",
13840
13976
  "ghosty dbs import <agent-id> <db> --from easybits:<db-id> [--tables a,b] [--append] [--rehost-images t.col,\u2026] [--dry-run] [--yes]",
13841
13977
  "ghosty dbs rehost <agent-id> <db> --table T --column C [--dry-run]",
@@ -14020,6 +14156,37 @@ var HELPS = {
14020
14156
  notes: ["Revokes every Ghosty for Chrome token your terminal agents hold (Claude Code, CLI, @ghostystudio/browser-mcp) and disconnects the extension. Copy a new command from the extension panel to reconnect."],
14021
14157
  learn: `${REF}#navegador`
14022
14158
  },
14159
+ image: {
14160
+ summary: "AI images (Gemini \xABNano Banana Pro\xBB) for your partner businesses: generate, edit an existing one, or compose from references; charged to the space's balance.",
14161
+ usage: [
14162
+ 'ghosty image generate "prompt" [--ref URL]... [--style-ref URL]... [--scene URL] [--text T] [--direction D]... [--variants N] [--size 2K] [--aspect 16:9] [--tenant ID] [--agent SLUG] [--out FILE]',
14163
+ 'ghosty image edit URL "change" [--ref URL]... [--text T] [--variants N] [--size 2K] [--aspect 16:9] [--tenant ID] [--out FILE]'
14164
+ ],
14165
+ flags: [
14166
+ ["--ref URL", "a character/object reference (https; JPEG, PNG, WebP, GIF or SVG); repeat it, up to 6. The image KEEPS that character or object (mascot, logo, product)"],
14167
+ ["--style-ref URL", "a style reference (up to 3): only its technique, palette and lighting are borrowed, not its content"],
14168
+ ["--scene URL", "a background/scene to use (up to 2)"],
14169
+ ["--text T", 'exact text to render inside the image ("-50 %", "$299"), up to 200 characters'],
14170
+ ["--direction D", "an art direction; repeat it (up to 4) to get one image per direction and pick one"],
14171
+ ["--variants N", "how many options (1-4); each one is charged as an image"],
14172
+ ["--size SIZE", "1K, 2K (default) or 4K; 4K costs more"],
14173
+ ["--aspect RATIO", "1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9 or 21:9 (edit: omit it to keep the original's)"],
14174
+ ["--tenant ID", "the business (externalId) it is for: it goes to the usage breakdown and to its optional daily cap"],
14175
+ ["--agent SLUG", "which partner agent it is charged to in the breakdown (default: the main one)"],
14176
+ ["--out FILE", "also download the PNG here (several: FILE-1.png, FILE-2.png\u2026)"]
14177
+ ],
14178
+ examples: [
14179
+ 'ghosty image generate "our mascot dressed as a pumpkin in a Halloween scene, flat illustration, no text" --ref https://example.com/mascot.svg --aspect 16:9 --tenant org_123',
14180
+ 'ghosty image edit https://img.ghosty.studio/u/x/promo.png "add a big red badge in the top right corner" --text "-50 %"',
14181
+ 'ghosty image generate "a cozy spa with candles, warm light" --direction "flat illustration with thick outlines" --direction "warm studio photo" --out spa.png'
14182
+ ],
14183
+ notes: [
14184
+ "Use edit (not generate) to change an image that already exists: the result is the same image with only that change.",
14185
+ `Each image is charged apart: USD 0.25 at 1K/2K and USD 0.45 at 4K from the partner space's balance; variants are charged one by one and failed ones are not charged. Without balance: 402. gs sets no cap of its own; a business can have one: tenants set <id> --config '{"imageDailyCap":20}'.`,
14186
+ "Auth: your login (space owner; --workspace if you have several) or the partner server credential in the env (GHOSTY_PARTNER_KEY + GHOSTY_PARTNER_KEY_SECRET), which then requires --tenant."
14187
+ ],
14188
+ learn: `${DOCS}/partners#imagenes-con-ia`
14189
+ },
14023
14190
  me: {
14024
14191
  summary: "Your account: files, memories, editable docs and usage.",
14025
14192
  usage: [
@@ -14382,8 +14549,8 @@ HELPS.novedades = {
14382
14549
  learn: `${REF}#soporte`
14383
14550
  };
14384
14551
  HELPS.spaces = {
14385
- 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.",
14386
- 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] [--cap N|none] [--until 2026-10-28|14d] [--yes]"],
14552
+ 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 or plan, 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.",
14553
+ 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 plan <workspace> trial|pro|empresarial [--courtesy] [--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] [--cap N|none] [--until 2026-10-28|14d] [--yes]"],
14387
14554
  flags: [
14388
14555
  ["--user EMAIL", "ls (staff): that person's workspaces"],
14389
14556
  ["--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)"],
@@ -14393,8 +14560,9 @@ HELPS.spaces = {
14393
14560
  ["--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"],
14394
14561
  ["--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"],
14395
14562
  ["--house-claude on|off", "set (staff): its agents run on Ghosty's Claude when the owner brings no key; recycles their Claude boxes"],
14396
- ["--until DATE", "renew: the new \xABpaid until\xBB (YYYY-MM-DD)"],
14397
- ["--note TEXT", "renew: why (goes into the audit)"],
14563
+ ["--until DATE", "renew, plan: the new \xABpaid until\xBB (YYYY-MM-DD); plan without it keeps the current one"],
14564
+ ["--note TEXT", "renew, plan: why (goes into the audit)"],
14565
+ ["--courtesy", "plan (staff): mark it as a no-charge courtesy (written to the workspace's plan note and the audit)"],
14398
14566
  ["--to EMAIL", "transfer: the new owner (if they have no account it is pre-created and claimed on login)"],
14399
14567
  ["--keep-as R", "transfer: the previous owner's role \u2014 admin | member (default) | none (removed)"],
14400
14568
  ["--copy-keys", "transfer: copy the previous owner's provider keys and the connectors its agents use (MercadoPago, Skydropx\u2026) that the new one lacks. Only the owner changes: documents, databases, boards and numbers its agents use move with it; anything also used outside the space stays and is listed"],
@@ -14402,8 +14570,8 @@ HELPS.spaces = {
14402
14570
  ["-n, --dry-run", "show what changes, change nothing"],
14403
14571
  ["--yes", "skip the confirmation (required without a terminal)"]
14404
14572
  ],
14405
- examples: ["ghosty spaces ls", "ghosty spaces create mailmask --dry-run", "ghosty spaces ls --user brenda@siiqtec.com", "ghosty spaces join denik", "ghosty spaces set denik --combo sales --dry-run", "ghosty spaces set mailmask --model-cap 4 --house-claude on", 'ghosty spaces renew descti --until 2026-11-01 --note "pag\xF3 octubre"', "ghosty spaces rename totequim siiqtec --dry-run", "ghosty spaces archive box-xfer-test --dry-run", "ghosty spaces transfer siiqtec --to siiqtec@gmail.com --keep-as admin --copy-keys --dry-run", "ghosty spaces log siiqtec --since 30d", "ghosty spaces members add business chrome-review@ghosty.studio --until 14d", "ghosty spaces members rm business chrome-review@ghosty.studio --yes"],
14406
- notes: ["create follows the same rules as the web: you own it, it starts as a 30-day trial, your email must be confirmed, and your plan caps how many you own. Next: ghosty partner enable <slug>.", "With a Stripe subscription renew refuses: the paid invoice moves the date.", "rename changes only the slug (id and namespace stay, so board, Stripe and WhatsApp keep working); old <slug>.sales links stop working.", "archive does what Settings \u2192 Archive does: the workspace leaves /app and spaces ls, its agents and data stay; it asks you to type the slug. Undo: ghosty spaces restore <slug>. Logged as workspace.archive / workspace.restore.", "transfer moves the agents with the workspace: ACP boxes move to the new owner with their /data, pool boxes are recycled (the owner is baked into the box); without --copy-keys the dry run lists which keys the new owner lacks \u2014 those agents would stop answering."],
14573
+ examples: ["ghosty spaces ls", "ghosty spaces create mailmask --dry-run", "ghosty spaces ls --user brenda@siiqtec.com", "ghosty spaces join denik", "ghosty spaces set denik --combo sales --dry-run", "ghosty spaces set mailmask --model-cap 4 --house-claude on", 'ghosty spaces renew descti --until 2026-11-01 --note "pag\xF3 octubre"', "ghosty spaces plan business pro --courtesy --dry-run", "ghosty spaces rename totequim siiqtec --dry-run", "ghosty spaces archive box-xfer-test --dry-run", "ghosty spaces transfer siiqtec --to siiqtec@gmail.com --keep-as admin --copy-keys --dry-run", "ghosty spaces log siiqtec --since 30d", "ghosty spaces members add business chrome-review@ghosty.studio --until 14d", "ghosty spaces members rm business chrome-review@ghosty.studio --yes"],
14574
+ notes: ["create follows the same rules as the web: you own it, it starts as a 30-day trial, your email must be confirmed, and your plan caps how many you own. Next: ghosty partner enable <slug>.", "With a Stripe subscription renew refuses: the paid invoice moves the date.", "plan applies the same rules as Plan and billing in /admin/tenants: trial \u2192 pro lifts the out-of-balance cut and the trial expiry, raises the model cap 0.25\xD7 \u2192 1\xD7 and measures tokens by calendar month. It never charges Stripe nor emails the customer; the dry run shows plan, model cap, tokens and \xABpaid until\xBB before \u2192 after. Logged as workspace.plan.", "rename changes only the slug (id and namespace stay, so board, Stripe and WhatsApp keep working); old <slug>.sales links stop working.", "archive does what Settings \u2192 Archive does: the workspace leaves /app and spaces ls, its agents and data stay; it asks you to type the slug. Undo: ghosty spaces restore <slug>. Logged as workspace.archive / workspace.restore.", "transfer moves the agents with the workspace: ACP boxes move to the new owner with their /data, pool boxes are recycled (the owner is baked into the box); without --copy-keys the dry run lists which keys the new owner lacks \u2014 those agents would stop answering."],
14407
14575
  learn: `${REF}#espacios`
14408
14576
  };
14409
14577
  HELPS.workspaces = HELPS.spaces;
@@ -14425,6 +14593,7 @@ HELPS.sites = {
14425
14593
  "ghosty sites pin <slug|id> [--interval year|month] [--currency usd|mxn] [--add-pack]",
14426
14594
  "ghosty sites unpin <slug|id>",
14427
14595
  "ghosty sites pins [--cancel | --resume]",
14596
+ "ghosty sites search <slug|id> [--days 28]",
14428
14597
  "ghosty sites publish <file.html|folder> [--title T] [--slug S] [--site <slug|id>]",
14429
14598
  "ghosty sites slug <slug|id> <new-slug>"
14430
14599
  ],
@@ -14434,6 +14603,7 @@ HELPS.sites = {
14434
14603
  ["--currency C", "pin: usd (default) or mxn \u2014 the currency charged if you need to pay"],
14435
14604
  ["--add-pack", "pin: you already use your slots \u2014 add 3 more (same period and currency as your subscription) and pin this one"],
14436
14605
  ["--cancel / --resume", "pins: cancel at period end (sites go back to Free with notice), or undo it"],
14606
+ ["--days N", "search: Search Console window in days (default 28; Google reports 2\u20133 days late)"],
14437
14607
  ["--title T", "publish: the site's name (default: the HTML <title> or the folder name)"],
14438
14608
  ["--slug S", "publish: the slug for a NEW site (a-z, 0-9 and hyphens, 3\u201360, not reserved)"],
14439
14609
  ["--site X", "publish: update that house site (slug, id or URL) instead of creating one"]
@@ -14585,7 +14755,7 @@ function helpText(cmd) {
14585
14755
 
14586
14756
  // src/update.ts
14587
14757
  init_i18n();
14588
- import { mkdir as mkdir5, readFile as readFile9, writeFile as writeFile8 } from "node:fs/promises";
14758
+ import { mkdir as mkdir5, readFile as readFile9, writeFile as writeFile9 } from "node:fs/promises";
14589
14759
  import { homedir as homedir3 } from "node:os";
14590
14760
  import { dirname as dirname3, join as join7 } from "node:path";
14591
14761
  var FILE = join7(process.env.XDG_CONFIG_HOME || join7(homedir3(), ".config"), "ghosty", "update.json");
@@ -14611,7 +14781,7 @@ async function latestVersion(current2) {
14611
14781
  const latest = (await r.json()).version;
14612
14782
  if (latest) cache2 = { latest, checkedAt: Date.now() };
14613
14783
  await mkdir5(dirname3(FILE), { recursive: true });
14614
- await writeFile8(FILE, JSON.stringify(cache2));
14784
+ await writeFile9(FILE, JSON.stringify(cache2));
14615
14785
  } catch {
14616
14786
  }
14617
14787
  }
@@ -14691,7 +14861,7 @@ function agentArgIndex(pos) {
14691
14861
  }
14692
14862
 
14693
14863
  // src/completion.ts
14694
- import { mkdir as mkdir6, readFile as readFile10, writeFile as writeFile9 } from "node:fs/promises";
14864
+ import { mkdir as mkdir6, readFile as readFile10, writeFile as writeFile10 } from "node:fs/promises";
14695
14865
  import { homedir as homedir4 } from "node:os";
14696
14866
  import { join as join8 } from "node:path";
14697
14867
  var GLOBAL_FLAGS = ["--json", "--token", "--profile", "--yes", "--help", "--version", "--lang", "--dry-run"];
@@ -14743,7 +14913,7 @@ async function names() {
14743
14913
  const workspaces = [...new Set(list2.map((a) => a.espacio?.tipo === "workspace" ? a.espacio.nombre : void 0).filter((x) => !!x))];
14744
14914
  await mkdir6(join8(cacheFile(), ".."), { recursive: true }).catch(() => {
14745
14915
  });
14746
- await writeFile9(cacheFile(), JSON.stringify({ at: Date.now(), agents, workspaces })).catch(() => {
14916
+ await writeFile10(cacheFile(), JSON.stringify({ at: Date.now(), agents, workspaces })).catch(() => {
14747
14917
  });
14748
14918
  return { agents, workspaces };
14749
14919
  }
@@ -14830,7 +15000,7 @@ init_i18n();
14830
15000
  // src/commands/spec.ts
14831
15001
  var import_yaml = __toESM(require_dist(), 1);
14832
15002
  init_api();
14833
- import { mkdir as mkdir7, readdir as readdir4, readFile as readFile11, stat as stat2, writeFile as writeFile10 } from "node:fs/promises";
15003
+ import { mkdir as mkdir7, readdir as readdir4, readFile as readFile11, stat as stat2, writeFile as writeFile11 } from "node:fs/promises";
14834
15004
  import { join as join9, resolve as resolve2 } from "node:path";
14835
15005
  init_util();
14836
15006
  init_i18n();
@@ -14844,7 +15014,7 @@ async function agentsExport(id, v) {
14844
15014
  }
14845
15015
  const dir = resolve2(v.out);
14846
15016
  await mkdir7(dir, { recursive: true });
14847
- await writeFile10(join9(dir, "agent.yaml"), yaml);
15017
+ await writeFile11(join9(dir, "agent.yaml"), yaml);
14848
15018
  const own = (0, import_yaml.parse)(yaml)["x-ghosty"]?.skills?.own ?? [];
14849
15019
  for (const slug of own) {
14850
15020
  const sk = await api(`/api/v2/agents/${enc12(id)}/skills/${enc12(slug)}`);
@@ -14967,7 +15137,7 @@ ${dim(t(`${r.totals.activeTurns} turn(s) running`, `${r.totals.activeTurns} turn
14967
15137
  init_api();
14968
15138
  init_auth();
14969
15139
  import { randomUUID as randomUUID2 } from "node:crypto";
14970
- import { mkdir as mkdir8, readFile as readFile12, writeFile as writeFile11 } from "node:fs/promises";
15140
+ import { mkdir as mkdir8, readFile as readFile12, writeFile as writeFile12 } from "node:fs/promises";
14971
15141
  import { homedir as homedir5 } from "node:os";
14972
15142
  import { dirname as dirname4, join as join10 } from "node:path";
14973
15143
  init_i18n();
@@ -15012,7 +15182,7 @@ async function visitorCookie(base, slug, fresh) {
15012
15182
  if (!cookie) throw new CliError(t("The demo didn't hand out a visitor cookie.", "El demo no entreg\xF3 cookie de visitante."), EXIT.API);
15013
15183
  jar[slug] = cookie;
15014
15184
  await mkdir8(dirname4(jarPath()), { recursive: true });
15015
- await writeFile11(jarPath(), JSON.stringify(jar, null, 2), { mode: 384 });
15185
+ await writeFile12(jarPath(), JSON.stringify(jar, null, 2), { mode: 384 });
15016
15186
  return cookie;
15017
15187
  }
15018
15188
  async function visitorTurn(base, slug, cookie, text) {
@@ -15127,8 +15297,8 @@ var PACK = { year: { usd: 49, mxn: 990 }, month: { usd: 5, mxn: 99 } };
15127
15297
  var packPrice = (c, i, lang2) => `$${(PACK[i]?.[c] ?? 0).toLocaleString("en-US")} ${c.toUpperCase()}${lang2 === "es" ? i === "year" ? " al a\xF1o" : " al mes" : i === "year" ? "/yr" : "/mo"}`;
15128
15298
  var kb = (n) => n ? `${Math.max(1, Math.round(n / 1024))} KB` : "";
15129
15299
  var day = (iso) => iso ? iso.slice(0, 10) : "";
15130
- async function sitesCmd(sub, a1, a2, v) {
15131
- const ex = "ghosty sites ls [--house] \xB7 pin|unpin <slug|id> \xB7 pins [--cancel|--resume] \xB7 publish <file.html|folder> [--title T] [--slug S] [--site <slug|id>] \xB7 slug <slug|id> <new-slug>";
15300
+ async function sitesCmd(sub, a1, a2, v, a3) {
15301
+ const ex = "ghosty sites ls [--house] \xB7 pin|unpin <slug|id> \xB7 pins [--cancel|--resume] \xB7 search <slug|id> [--days 28] \xB7 domain [ls|add|verify|rm] <slug> [domain] \xB7 publish <file.html|folder> [--title T] [--slug S] [--site <slug|id>] \xB7 slug <slug|id> <new-slug>";
15132
15302
  switch (sub ?? "ls") {
15133
15303
  case "ls":
15134
15304
  case "list": {
@@ -15169,6 +15339,25 @@ ${dim(r.expiresAt ? t(`Free rule: pauses ${day(r.expiresAt)} unless it changes (
15169
15339
  ${table(r.pinned.map((p) => ({ slug: p.slug, title: (p.title ?? "").slice(0, 40), url: p.url, since: day(p.pinnedAt) })), ["slug", "title", "url", "since"])}` : t("No pinned sites. Pin one ($49 USD/yr or $5 USD/mo, includes up to 3 sites): ghosty sites pin <slug>", "Sin sitios fijos. Fija uno ($49 USD al a\xF1o o $5 USD al mes, incluye hasta 3 sitios): ghosty sites pin <slug>")
15170
15340
  );
15171
15341
  }
15342
+ case "search": {
15343
+ if (!a1) throw usage(t("Missing the site (slug, id or URL).", "Falta el sitio (slug, id o URL)."), "ghosty sites search <slug|id> [--days 28]");
15344
+ const r = await api(`/api/v2/sites/${encodeURIComponent(a1)}/search${v.days ? `?days=${encodeURIComponent(v.days)}` : ""}`);
15345
+ const p = r.portada;
15346
+ const index = p.error ? dim(p.error) : p.indexada ? `${lavender("\u2713")} ${t("indexed", "indexada")} ${dim(day(p.ultimoRastreo ?? null))}` : `${t("not indexed", "sin indexar")} ${dim(p.estado ?? "")}`;
15347
+ const tot = r.total;
15348
+ return out(
15349
+ r,
15350
+ () => [
15351
+ `${bold(r.sitio)} ${dim(`${r.desde} \u2192 ${r.hasta}`)}`,
15352
+ `${t("Home", "Portada")}: ${index}`,
15353
+ `${t("clicks", "clics")} ${tot.clics} \xB7 ${t("impressions", "impresiones")} ${tot.impresiones} \xB7 CTR ${tot.ctr} \xB7 ${t("position", "posici\xF3n")} ${tot.posicion ?? "\u2014"}`,
15354
+ r.busquedas.length ? table(r.busquedas.map((q) => ({ query: q.busqueda, clicks: q.clics, impressions: q.impresiones, position: q.posicion })), ["query", "clicks", "impressions", "position"]) : dim(t("No searches yet (Google reports 2\u20133 days late).", "A\xFAn sin b\xFAsquedas (Google reporta con 2\u20133 d\xEDas de atraso)."))
15355
+ ].join("\n")
15356
+ );
15357
+ }
15358
+ case "domain":
15359
+ case "domains":
15360
+ return domainCmd(a1, a2, a3);
15172
15361
  case "publish":
15173
15362
  case "deploy": {
15174
15363
  if (!a1) throw usage(t("Missing the file or folder.", "Falta el archivo o la carpeta."), ex);
@@ -15195,10 +15384,45 @@ ${dim(t("Links with the old slug may stop working.", "Las ligas con el slug viej
15195
15384
  throw usage(t(`Unknown sites subcommand: ${sub}.`, `Subcomando de sites desconocido: ${sub}.`), ex);
15196
15385
  }
15197
15386
  }
15387
+ function domainLine(r, lang2 = "es") {
15388
+ if (r.verified) {
15389
+ return r.applied === false ? lang2 === "es" ? `Verificado; la app se est\xE1 levantando y queda en ${r.url} en 1\u20133 min.` : `Verified; the app is starting and will be at ${r.url} in 1\u20133 min.` : lang2 === "es" ? `Listo: ${r.url} ya sirve la app (HTTPS sale solo con la primera visita).` : `Done: ${r.url} serves the app (HTTPS is issued on the first visit).`;
15390
+ }
15391
+ const rec = `${r.dns.type} ${r.dns.name} \u2192 ${r.dns.value}`;
15392
+ return lang2 === "es" ? `Pon este registro en tu proveedor de DNS: ${rec}${r.reason ? `
15393
+ (${r.reason})` : ""}
15394
+ Luego: ghosty sites domain verify <slug> ${r.host}` : `Add this record at your DNS provider: ${rec}${r.reason ? `
15395
+ (${r.reason})` : ""}
15396
+ Then: ghosty sites domain verify <slug> ${r.host}`;
15397
+ }
15398
+ async function domainCmd(action, a2, a3) {
15399
+ const ex = "ghosty sites domain [ls] <slug> \xB7 add|verify|rm <slug> <domain>";
15400
+ const acts = /* @__PURE__ */ new Set(["ls", "list", "add", "verify", "rm", "remove"]);
15401
+ const [act, slug, host] = action && !acts.has(action) ? ["ls", action, a2] : [action ?? "ls", a2, a3];
15402
+ if (!slug) throw usage(t("Missing the app (slug, id or URL).", "Falta la app (slug, id o URL)."), ex);
15403
+ const path = `/api/v2/sites/${encodeURIComponent(slug)}/domain`;
15404
+ if (act === "ls" || act === "list") {
15405
+ const r2 = await api(path);
15406
+ return out(
15407
+ r2,
15408
+ () => r2.domains.length ? `${bold(r2.url)}
15409
+ ${table(r2.domains.map((d) => ({ domain: d.host, state: d.verified ? t("verified", "verificado") : t("waiting for DNS", "esperando DNS"), dns: `${d.dns.type} \u2192 ${d.dns.value}` })), ["domain", "state", "dns"])}` : t(`${r2.url} has no custom domain. Add one: ghosty sites domain add ${r2.slug} app.yourdomain.com`, `${r2.url} no tiene dominio propio. Agrega uno: ghosty sites domain add ${r2.slug} app.tudominio.com`)
15410
+ );
15411
+ }
15412
+ if (!host) throw usage(t("Missing the domain.", "Falta el dominio."), ex);
15413
+ if (act === "rm" || act === "remove") {
15414
+ const r2 = await api(path, { method: "DELETE", body: { domain: host } });
15415
+ return out(r2, () => `${lavender("\u2713")} ${t("Removed", "Quitado")} ${bold(r2.host)}`);
15416
+ }
15417
+ if (act !== "add" && act !== "verify") throw usage(t(`Unknown domain action: ${act}.`, `Acci\xF3n de dominio desconocida: ${act}.`), ex);
15418
+ const r = await api(path, { method: "POST", body: { domain: host, ...act === "verify" ? { verify: true } : {} } });
15419
+ return out(r, () => `${r.verified ? lavender("\u2713") : dim("\u2026")} ${bold(r.host)}
15420
+ ${domainLine(r, t("en", "es") === "es" ? "es" : "en").replace("<slug>", slug)}`);
15421
+ }
15198
15422
 
15199
15423
  // src/commands/docs.ts
15200
15424
  init_api();
15201
- import { writeFile as writeFile12 } from "node:fs/promises";
15425
+ import { writeFile as writeFile13 } from "node:fs/promises";
15202
15426
 
15203
15427
  // ../docs/src/core/doc-blocks.ts
15204
15428
  var DOC_ENVELOPE_VERSION = 1;
@@ -15361,7 +15585,7 @@ ${t("Ids: ghosty me docs ls", "Ids: ghosty me docs ls")}`);
15361
15585
  }
15362
15586
  const named = /filename\*=UTF-8''([^;]+)/i.exec(res.headers.get("content-disposition") ?? "")?.[1];
15363
15587
  const file = v.out ?? (named ? decodeURIComponent(named) : fileName(id, format));
15364
- await writeFile12(file, bytes);
15588
+ await writeFile13(file, bytes);
15365
15589
  return out({ id, format, saved: file, bytes: bytes.length }, () => `${lavender("\u2713")} ${file} (${bytes.length} bytes)`);
15366
15590
  }
15367
15591
  case "check": {
@@ -15387,7 +15611,7 @@ ${t("Ids: ghosty me docs ls", "Ids: ghosty me docs ls")}`);
15387
15611
  if (res.status === 204) return out({ id, block, segment, segments, saved: null }, () => dim(t("That block has no text to read.", "Ese bloque no tiene texto que leer.")));
15388
15612
  const bytes = Buffer.from(await res.arrayBuffer());
15389
15613
  const file = v.out ?? `${id}-${block}${segment !== null ? `-${segment}` : ""}.${audioExt(res.headers.get("content-type"))}`;
15390
- await writeFile12(file, bytes);
15614
+ await writeFile13(file, bytes);
15391
15615
  return out({ id, block, segment, segments, saved: file, bytes: bytes.length }, () => `${lavender("\u2713")} ${file} (${bytes.length} bytes) ${dim(t(`\xB7 the block has ${segments} sentence(s): --segment 0\u2026${Math.max(segments - 1, 0)}`, `\xB7 el bloque tiene ${segments} frase(s): --segment 0\u2026${Math.max(segments - 1, 0)}`))}`);
15392
15616
  }
15393
15617
  case "restore": {
@@ -15901,20 +16125,25 @@ ${dim(`${t("Detail", "Detalle")}: ghosty factory runs show <run> --workspace ${r
15901
16125
  const { advice: a, workspace } = await api(`${path}/advise`, { body: {} });
15902
16126
  const note = a.buildNote.map((p, i) => `${i + 1}. ${p.point}`).join(" ");
15903
16127
  const sh = (x) => `'${x.replace(/'/g, `'\\''`)}'`;
15904
- 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}`;
16128
+ const apply = a.decision === "fix" ? `ghosty factory runs approve ${id} --note ${sh(note)}${a.checkScope ? ` --check-scope ${sh(a.checkScope)}` : ""} --workspace ${workspace}` : a.decision === "replan" ? `ghosty factory runs changes ${id} --note ${sh(note)} --workspace ${workspace}` : a.decision === "recheck" ? `ghosty factory runs recheck ${id} --workspace ${workspace}` : null;
16129
+ const label = {
16130
+ fix: t("Let @build fix it", "Que @build lo arregle"),
16131
+ replan: t("Redo the plan", "Rehacer el plan"),
16132
+ close: t("Close the PR", "Cerrar el PR"),
16133
+ close_ignore: t("Close the PR and ignore this version", "Cerrar el PR e ignorar esta versi\xF3n"),
16134
+ recheck: t("Let @check review again", "Que @check revise otra vez"),
16135
+ wait: t("Wait: it's a platform fault", "Esperar: es una falla de la plataforma")
16136
+ }[a.decision] ?? a.decision;
15905
16137
  return out({ workspace, advice: a, apply }, () => {
15906
16138
  const conf = { high: t("high", "alta"), medium: t("medium", "media"), low: t("low", "baja") }[a.confidence] ?? a.confidence;
15907
16139
  return [
15908
- `${bold(`#${id}`)} ${bold(a.decision === "fix" ? t("Let @build fix it", "Que @build lo arregle") : t("Redo the plan", "Rehacer el plan"))} ${dim(`\xB7 ${t("confidence", "confianza")} ${conf} \xB7 ${a.model}`)}`,
16140
+ `${bold(`#${id}`)} ${bold(label)} ${dim(`\xB7 ${t("confidence", "confianza")} ${conf} \xB7 ${a.model}`)}`,
15909
16141
  a.why,
15910
- "",
15911
- bold(a.decision === "fix" ? t("Note for @build", "Nota para @build") : t("What to change in the plan", "Qu\xE9 cambiar en el plan")),
15912
- ...a.buildNote.map((p, i) => ` ${i + 1}. ${p.point}
15913
- ${dim(p.cites)}`),
16142
+ ...a.buildNote.length ? ["", bold(a.decision === "fix" ? t("Note for @build", "Nota para @build") : t("What to change in the plan", "Qu\xE9 cambiar en el plan")), ...a.buildNote.map((p, i) => ` ${i + 1}. ${p.point}
16143
+ ${dim(p.cites)}`)] : [],
15914
16144
  ...a.checkScope && a.decision === "fix" ? ["", bold(t("@check reviews only", "@check revisa s\xF3lo")), ` ${a.checkScope}`] : [],
15915
16145
  "",
15916
- dim(t("Nothing was applied. To apply it (edit the note first if you want):", "No se aplic\xF3 nada. Para aplicarla (edita la nota antes si quieres):")),
15917
- ` ${apply}`
16146
+ ...apply ? [dim(t("Nothing was applied. To apply it (edit the note first if you want):", "No se aplic\xF3 nada. Para aplicarla (edita la nota antes si quieres):")), ` ${apply}`] : [dim(t("Nothing was applied. Decide it on the run's card or in the app.", "No se aplic\xF3 nada. Dec\xEDdelo en la tarjeta del pedido o en la app."))]
15918
16147
  ].join("\n");
15919
16148
  });
15920
16149
  }
@@ -16043,6 +16272,58 @@ async function settingsCmd(sub, key, value) {
16043
16272
  throw usage(t(`Unknown settings subcommand: ${sub}.`, `Subcomando de settings desconocido: ${sub}.`), "ghosty settings ls|set|unset <KEY> [value]");
16044
16273
  }
16045
16274
 
16275
+ // src/commands/coupons.ts
16276
+ init_api();
16277
+ init_i18n();
16278
+ init_util();
16279
+ function couponRow(c) {
16280
+ return {
16281
+ code: c.code,
16282
+ credit: `$${c.creditMxn}`,
16283
+ plan: c.plan ?? "",
16284
+ until: c.until ? c.until.slice(0, 10) : "",
16285
+ used: `${c.redemptions}/${c.maxRedemptions ?? "\u221E"}`,
16286
+ state: c.active ? t("active", "activo") : t("draft", "borrador"),
16287
+ only: c.eligibility ?? "",
16288
+ agent: c.agentId ?? "",
16289
+ note: c.note ?? ""
16290
+ };
16291
+ }
16292
+ function bodyOf(v) {
16293
+ const b = {};
16294
+ if (v.credit !== void 0) b.creditMxn = Number(v.credit);
16295
+ if (v.plan !== void 0) b.plan = v.plan === "none" ? null : v.plan;
16296
+ if (v.until !== void 0) b.until = v.until;
16297
+ if (v.max !== void 0) b.maxRedemptions = v.max === "none" ? null : Number(v.max);
16298
+ if (v.on) b.active = true;
16299
+ if (v.off) b.active = false;
16300
+ if (v.note !== void 0) b.note = v.note;
16301
+ if (v.eligibility !== void 0) b.eligibility = v.eligibility;
16302
+ if (v.agent !== void 0) b.agentId = v.agent;
16303
+ return b;
16304
+ }
16305
+ async function couponsCmd(sub, code, v) {
16306
+ const one = (c) => `${lavender("\u2713")} ${c.code} $${c.creditMxn}${c.plan ? ` + ${c.plan} \u2192 ${c.until?.slice(0, 10)}` : ""} ${c.active ? t("active", "activo") : dim(t("draft", "borrador"))}`;
16307
+ if (!sub || sub === "ls" || sub === "list") {
16308
+ const r = await api("/api/v2/support/coupons");
16309
+ return out(r, () => r.coupons.length ? table(r.coupons.map(couponRow), ["code", "credit", "plan", "until", "used", "state", "only", "agent", "note"]) : dim(t("No coupons.", "Sin cupones.")));
16310
+ }
16311
+ if (!code) throw usage(t("Missing the coupon code.", "Falta el c\xF3digo del cup\xF3n."), `ghosty coupons ${sub} <CODE>`);
16312
+ if (sub === "create") {
16313
+ const r = await api("/api/v2/support/coupons", { method: "POST", body: { code, ...bodyOf(v) } });
16314
+ return out(r, () => one(r.coupon));
16315
+ }
16316
+ if (sub === "set") {
16317
+ const r = await api(`/api/v2/support/coupons/${encodeURIComponent(code)}`, { method: "PATCH", body: bodyOf(v) });
16318
+ return out(r, () => one(r.coupon));
16319
+ }
16320
+ if (sub === "redeem") {
16321
+ const r = await api("/api/v2/me/coupons", { method: "POST", body: { code } });
16322
+ return out(r, () => `${lavender("\u2713")} ${r.message}`);
16323
+ }
16324
+ throw usage(t(`Unknown coupons subcommand: ${sub}.`, `Subcomando de coupons desconocido: ${sub}.`), "ghosty coupons ls|create|set|redeem <CODE>");
16325
+ }
16326
+
16046
16327
  // src/commands/ads.ts
16047
16328
  init_api();
16048
16329
  init_i18n();
@@ -16058,6 +16339,9 @@ async function adsCmd(sub, action, v) {
16058
16339
  lavender(t("Meta ad accounts", "Cuentas de Meta")),
16059
16340
  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)")),
16060
16341
  "",
16342
+ lavender(t("Google Ads accounts", "Cuentas de Google Ads")),
16343
+ r.google?.length ? table(r.google.map((g) => ({ workspace: g.workspace, account: g.account, link: g.link, active: `${g.active}/${g.campaigns}`, status: g.accountStatus ?? "", checked: day3(g.checkedAt), alerted: day3(g.alertedAt), error: g.error ?? "" })), ["workspace", "account", "link", "active", "status", "checked", "alerted", "error"]) : dim(t("(none linked)", "(ninguna vinculada)")),
16344
+ "",
16061
16345
  lavender(t("Ads-only WhatsApp numbers", "N\xFAmeros de WhatsApp \xABs\xF3lo anuncios\xBB")),
16062
16346
  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)"))
16063
16347
  ].join("\n")
@@ -16373,6 +16657,7 @@ ${bold("Workspaces")} ${dim("(changing them: Ghosty staff)")}
16373
16657
  spaces join|leave <workspace> Enter or leave a client's workspace as staff (no seat; logged)
16374
16658
  spaces set <workspace> --combo sales teams | sales | both | none (without Sales its boards are invisible) [--model-cap 0.25|1|4|none: most expensive model] [--house-claude on|off]
16375
16659
  spaces renew <workspace> --until DATE Move \xABpaid until\xBB (logged) [--note] [--dry-run]
16660
+ spaces plan <workspace> trial|pro|empresarial Change its plan, same rules as /admin/tenants; no Stripe charge, no email (logged) [--courtesy] [--until DATE] [--note] [--dry-run]
16376
16661
  spaces rename <old> <new> Change its slug only [--dry-run]
16377
16662
  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]
16378
16663
  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]
@@ -16382,6 +16667,8 @@ ${bold("Workspaces")} ${dim("(changing them: Ghosty staff)")}
16382
16667
  staff ls [staff-email] [client-email] Ghosty staff assigned to client accounts (staff) [--all: also revoked]
16383
16668
  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]
16384
16669
  settings ls|set|unset <KEY> [value] Platform switches (staff): who answers /c without an account, native subagents\u2026 [+id/-id edits a list]
16670
+ coupons ls|create|set <CODE> Redeemable coupons (staff): balance in cost pesos and an optional lent plan until a date; created as a draft [--credit 50] [--plan pro|power|max --until 2026-11-13] [--max 100] [--eligibility hackathon:<event>: accepted people only] [--agent ID: each account gets its own copy] [--on|--off] [--note]
16671
+ coupons redeem <CODE> Redeem a coupon for your account (also /canjear?codigo=CODE)
16385
16672
  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]
16386
16673
  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]
16387
16674
  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]
@@ -16484,6 +16771,8 @@ ${bold("Sites")} ${dim("(sites your agents publish at <slug>.ghosty.page)")}
16484
16771
  sites ls Sites your agents published (live and paused) [--house: Ghosty's own demos and landings (staff)]
16485
16772
  sites pin <slug|id> Pin it: never pauses, no banner ($49 USD/yr or $5/mo, includes up to 3 sites) [--interval year|month] [--currency usd|mxn] [--add-pack: 3 more]
16486
16773
  sites unpin <slug|id> Release its slot (back to the Free rule: 30 days + notice)
16774
+ sites search <slug|id> How Google finds it: clicks, impressions, searches, indexed or not [--days 28]
16775
+ sites domain add|verify|rm <slug> <dom> Custom domain for an app (DNS record \u2192 verify \u2192 HTTPS) [ls <slug>: list]
16487
16776
  sites pins Your pinned-sites subscription and slots [--cancel | --resume]
16488
16777
  sites publish <file.html|folder> Publish a house site (staff): no owner, no expiry, no banner [--title] [--slug S] [--site <slug|id>: update that one]
16489
16778
  sites slug <slug|id> <new-slug> Change a site's slug (staff)
@@ -16526,6 +16815,8 @@ ${bold("Browser")} ${dim("(Ghosty for Chrome)")}
16526
16815
 
16527
16816
  ${bold("Partners")} ${dim("(give an agent to each of YOUR customers \u2014 see: ghosty partner --help)")}
16528
16817
  partner ls | enable | credentials | agent | tenants | audit | try
16818
+ image generate "prompt" An AI image for your businesses, charged to the space's balance [--ref URL (repeat, keeps the character)] [--style-ref URL] [--scene URL] [--text "-50 %"] [--direction D]\u2026 [--variants N] [--size 1K|2K|4K] [--aspect 16:9] [--tenant ID] [--out FILE]
16819
+ image edit URL "change" Edit an existing image: only that change, the rest stays [--ref URL] [--text T] [--variants N] [--size] [--aspect] [--tenant ID] [--out FILE]
16529
16820
 
16530
16821
  ${bold("Your account")}
16531
16822
  me files ls|get|export|upload|rm Files in your account [--q TEXT] [--kind image|video|audio|document|other] [--cursor C] \xB7 get <id> [--out FILE]
@@ -16574,6 +16865,7 @@ ${bold("Espacios")} ${dim("(cambiarlos: staff de Ghosty)")}
16574
16865
  spaces join|leave <espacio> Entra o sal del espacio de un cliente como staff (sin asiento; queda en bit\xE1cora)
16575
16866
  spaces set <espacio> --combo sales teams | sales | both | none (sin Ventas sus tableros no se ven) [--model-cap 0.25|1|4|none: el modelo m\xE1s caro] [--house-claude on|off]
16576
16867
  spaces renew <espacio> --until FECHA Mueve \xABpagado hasta\xBB (queda en bit\xE1cora) [--note] [--dry-run]
16868
+ spaces plan <espacio> trial|pro|empresarial Cambia su plan, mismas reglas que /admin/tenants; sin cobro en Stripe ni correo (queda en bit\xE1cora) [--courtesy] [--until FECHA] [--note] [--dry-run]
16577
16869
  spaces rename <viejo> <nuevo> Cambia s\xF3lo su slug [--dry-run]
16578
16870
  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]
16579
16871
  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]
@@ -16583,6 +16875,8 @@ ${bold("Espacios")} ${dim("(cambiarlos: staff de Ghosty)")}
16583
16875
  staff ls [correo-staff] [correo-cliente] Staff de Ghosty asignado a cuentas cliente (staff) [--all: tambi\xE9n revocadas]
16584
16876
  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]
16585
16877
  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]
16878
+ coupons ls|create|set <C\xD3DIGO> Cupones canjeables (staff): saldo en pesos de costo y un plan prestado opcional hasta una fecha; nacen como borrador [--credit 50] [--plan pro|power|max --until 2026-11-13] [--max 100] [--eligibility hackathon:<evento>: s\xF3lo aceptados] [--agent ID: cada cuenta recibe su copia] [--on|--off] [--note]
16879
+ coupons redeem <C\xD3DIGO> Canjea un cup\xF3n para tu cuenta (tambi\xE9n /canjear?codigo=C\xD3DIGO)
16586
16880
  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]
16587
16881
  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]
16588
16882
  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]
@@ -16686,6 +16980,7 @@ ${bold("Sitios")} ${dim("(los sitios que publican tus agentes en <slug>.ghosty.
16686
16980
  sites pin <slug|id> F\xEDjalo: no se pausa y va sin franja ($49 USD al a\xF1o o $5 al mes, incluye hasta 3 sitios) [--interval year|month] [--currency usd|mxn] [--add-pack: 3 m\xE1s]
16687
16981
  sites unpin <slug|id> Suelta su lugar (vuelve a la regla Gratis: 30 d\xEDas + aviso)
16688
16982
  sites pins Tu suscripci\xF3n de Sitios fijos y sus lugares [--cancel | --resume]
16983
+ sites domain add|verify|rm <slug> <dom> Dominio propio para una app (registro DNS \u2192 verificar \u2192 HTTPS) [ls <slug>: lista]
16689
16984
  sites publish <archivo.html|carpeta> Publica un sitio de la casa (staff): sin due\xF1o, sin caducidad, sin franja [--title] [--slug S] [--site <slug|id>: actualiza \xE9se]
16690
16985
  sites slug <slug|id> <slug-nuevo> Cambia el slug de un sitio (staff)
16691
16986
 
@@ -16727,6 +17022,8 @@ ${bold("Navegador")} ${dim("(Ghosty para Chrome)")}
16727
17022
 
16728
17023
  ${bold("Partners")} ${dim("(dale un agente a cada uno de TUS clientes \u2014 ver: ghosty partner --help)")}
16729
17024
  partner ls | enable | credentials | agent | tenants | audit | try
17025
+ image generate "prompt" Una imagen con IA para tus negocios, cobrada del saldo del espacio [--ref URL (se repite, conserva al personaje)] [--style-ref URL] [--scene URL] [--text "-50 %"] [--direction D]\u2026 [--variants N] [--size 1K|2K|4K] [--aspect 16:9] [--tenant ID] [--out ARCHIVO]
17026
+ image edit URL "cambio" Edita una imagen que ya existe: s\xF3lo ese cambio, lo dem\xE1s se queda [--ref URL] [--text T] [--variants N] [--size] [--aspect] [--tenant ID] [--out ARCHIVO]
16730
17027
 
16731
17028
  ${bold("Tu cuenta")}
16732
17029
  me files ls|get|export|upload|rm Archivos de tu cuenta [--q TEXTO] [--kind image|video|audio|document|other] [--cursor C] \xB7 get <id> [--out ARCHIVO]
@@ -16752,7 +17049,7 @@ ${bold("Opciones globales")}
16752
17049
  ${bold("C\xF3digos de salida")} 0 bien \xB7 1 error de la API \xB7 2 uso incorrecto \xB7 3 sin sesi\xF3n
16753
17050
  ${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.")}
16754
17051
  ${dim("Documentaci\xF3n: https://www.ghosty.studio/docs/cli")}`;
16755
- 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"];
17052
+ var COMMANDS = ["login", "logout", "whoami", "usage", "use", "apply", "plan", "engines", "bags", "spaces", "workspaces", "staff", "settings", "coupons", "ads", "audit", "factory", "demos", "sites", "push", "novedades", "completion", "cards", "agents", "files", "skills", "mcp", "dbs", "turns", "try", "chat", "conversations", "schedule", "teams", "partner", "image", "me", "channels", "board", "boards", "access", "credentials", "connectors", "browser", "support", "docs", "help"];
16756
17053
  var AGENTS_SUBS = ["ls", "create", "get", "show", "set", "export", "doctor", "docs", "share", "unshare", "shares", "teams", "rm", "restart", "demo", "embed", "box", "token", "memory", "transfer"];
16757
17054
  function unknown(what, input, known, helpCmd) {
16758
17055
  const s2 = suggest(input, known);
@@ -16818,6 +17115,8 @@ async function main() {
16818
17115
  in: { type: "string" },
16819
17116
  every: { type: "string" },
16820
17117
  until: { type: "string" },
17118
+ credit: { type: "string" },
17119
+ eligibility: { type: "string" },
16821
17120
  title: { type: "string" },
16822
17121
  body: { type: "string" },
16823
17122
  profile: { type: "string" },
@@ -16896,6 +17195,7 @@ async function main() {
16896
17195
  like: { type: "string" },
16897
17196
  "copy-dbs": { type: "boolean" },
16898
17197
  "copy-keys": { type: "boolean" },
17198
+ courtesy: { type: "boolean" },
16899
17199
  "keep-as": { type: "string" },
16900
17200
  "no-notify": { type: "boolean" },
16901
17201
  board: { type: "string" },
@@ -17021,6 +17321,13 @@ async function main() {
17021
17321
  draft: { type: "boolean" },
17022
17322
  "ads-only": { type: "string" },
17023
17323
  "partner-tenant": { type: "string" },
17324
+ ref: { type: "string", multiple: true },
17325
+ "style-ref": { type: "string", multiple: true },
17326
+ scene: { type: "string", multiple: true },
17327
+ direction: { type: "string", multiple: true },
17328
+ variants: { type: "string" },
17329
+ size: { type: "string" },
17330
+ aspect: { type: "string" },
17024
17331
  tenant: { type: "string" },
17025
17332
  "app-id": { type: "string" },
17026
17333
  secret: { type: "string" },
@@ -17055,14 +17362,14 @@ async function main() {
17055
17362
  const tokens = splitTokens(pos, v);
17056
17363
  globals.token = tokens.session;
17057
17364
  globals.profile = v.profile;
17058
- if (v.version) return out({ version: "0.77.0" }, () => "0.77.0");
17365
+ if (v.version) return out({ version: "0.83.0" }, () => "0.83.0");
17059
17366
  for (const n of normalizeArgs(pos, v)) if (!globals.json) info(dim(n));
17060
17367
  const context = (await readConfig()).workspace;
17061
17368
  if (!explicitToken()?.startsWith("gat_")) {
17062
17369
  const at = agentArgIndex(pos);
17063
17370
  if (at !== null && pos[at]) pos[at] = await resolveAgent(pos[at], context);
17064
17371
  for (const f of ["like", "prompt-base", "agent"]) {
17065
- if (f === "agent" && pos[0] === "partner") continue;
17372
+ if (f === "agent" && (pos[0] === "partner" || pos[0] === "image")) continue;
17066
17373
  const val = v[f];
17067
17374
  if (typeof val === "string" && val !== "none") v[f] = await resolveAgent(val, context);
17068
17375
  }
@@ -17158,7 +17465,7 @@ async function main() {
17158
17465
  return novedadesCmd(sub, a1, v);
17159
17466
  case "sites":
17160
17467
  case "site":
17161
- return sitesCmd(sub, a1, a2, v);
17468
+ return sitesCmd(sub, a1, a2, v, pos[4]);
17162
17469
  case "board":
17163
17470
  return boardCmd(sub, a1, v, a2);
17164
17471
  case "boards":
@@ -17196,6 +17503,8 @@ async function main() {
17196
17503
  return browserCmd(sub, v);
17197
17504
  case "partner":
17198
17505
  return partnerCmd(sub, a1, a2, { ...v, token: tokens.turn }, pos[4]);
17506
+ case "image":
17507
+ return imageCmd(sub, a1, a2, v);
17199
17508
  case "usage":
17200
17509
  if (v.images) return imageUsage(v);
17201
17510
  if (v.boxes && v.by === "template") return fleetBoxes();
@@ -17222,6 +17531,8 @@ async function main() {
17222
17531
  return staffCmd(sub, a1, a2, v);
17223
17532
  case "settings":
17224
17533
  return settingsCmd(sub, a1, a2);
17534
+ case "coupons":
17535
+ return couponsCmd(sub, a1, v);
17225
17536
  case "ads":
17226
17537
  return adsCmd(sub, a1, v);
17227
17538
  case "audit":
@@ -17250,7 +17561,7 @@ async function main() {
17250
17561
  throw unknown(cmd, cmd, COMMANDS, "ghosty --help");
17251
17562
  }
17252
17563
  }
17253
- var notice = updateNotice("0.77.0").catch(() => null);
17564
+ var notice = updateNotice("0.83.0").catch(() => null);
17254
17565
  main().then(
17255
17566
  async () => {
17256
17567
  const n = await notice;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghostystudio/cli",
3
- "version": "0.77.0",
3
+ "version": "0.83.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": {