@ghostystudio/cli 0.78.0 → 0.84.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 +9 -0
  2. package/dist/index.js +256 -46
  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,14 @@ 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.84.0
150
+
151
+ - `coupons create|set` take `--from` (the lent plan starts then), `--redeem-by` (last moment to redeem) and `--convert-promo` (Stripe promotion code offered in /c/uso when the lent plan is about to end). New `coupons show <CODE>` (staff): who redeemed it, their plan window and who converted to a paid plan. Codes are accent- and case-insensitive.
152
+
153
+ ### Changes in 0.83.0
154
+
155
+ - `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`).
156
+
148
157
  ### Changes in 0.75.0
149
158
 
150
159
  - `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.78.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
1178
+ headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.84.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.78.0"}`,
1220
+ "user-agent": `ghosty-cli/${"0.84.0"}`,
1221
1221
  "accept-language": lang(),
1222
1222
  ...opts.body !== void 0 ? { "content-type": "application/json" } : {},
1223
1223
  ...opts.headers
@@ -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.78.0"}`
11240
+ "user-agent": `ghosty-cli/${"0.84.0"}`
11209
11241
  },
11210
11242
  body: method === "GET" ? void 0 : raw
11211
11243
  });
@@ -11484,32 +11516,57 @@ async function partnerCmd(sub, a1, a2, v, a3) {
11484
11516
 
11485
11517
  // src/commands/image.ts
11486
11518
  import { writeFile as writeFile6 } from "node:fs/promises";
11519
+ import { extname as extname2 } from "node:path";
11487
11520
  init_util();
11488
11521
  init_i18n();
11489
- async function imageCmd(sub, prompt, v) {
11490
- if (sub !== "generate" && sub !== "gen") {
11491
- 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]');
11492
- }
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 %"');
11493
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');
11494
11536
  const body = {
11495
11537
  prompt,
11538
+ ...base ? { baseImageUrl: base } : {},
11496
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 } : {},
11497
11546
  ...v.aspect ? { aspectRatio: v.aspect } : {},
11498
11547
  ...v.tenant ? { tenant: { externalId: v.tenant } } : {},
11499
11548
  ...v.agent ? { agent: v.agent } : {}
11500
11549
  };
11501
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 = [];
11502
11553
  if (v.out) {
11503
- const res = await fetch(r.url);
11504
- if (!res.ok) throw new Error(t(`Could not download the image (${res.status}).`, `No se pudo bajar la imagen (${res.status}).`));
11505
- await writeFile6(v.out, Buffer.from(await res.arrayBuffer()));
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
+ }
11506
11561
  }
11562
+ const how = { edit: t("edited", "editada"), compose: t("composed from references", "compuesta con referencias"), generate: t("generated", "generada") }[r.mode] ?? r.mode;
11507
11563
  return out(
11508
11564
  r,
11509
11565
  () => [
11510
- r.url,
11511
- dim(`${r.mode === "edit" ? t("edited from references", "editada desde referencias") : t("generated", "generada")}${r.width && r.height ? ` \xB7 ${r.width}\xD7${r.height}` : ""} \xB7 ${t("charged", "cobrado")} USD ${r.priceUsd.toFixed(2)}`),
11512
- ...v.out ? [`${lavender("\u2713")} ${t(`saved to ${v.out}`, `guardada en ${v.out}`)}`] : []
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}`)}`)
11513
11570
  ].join("\n")
11514
11571
  );
11515
11572
  }
@@ -13285,20 +13342,31 @@ var HELPS_ES = {
13285
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."]
13286
13343
  },
13287
13344
  image: {
13288
- summary: "Una imagen con IA (Gemini \xABNano Banana Pro\xBB) para los negocios de tu espacio de partner, cobrada del saldo del espacio.",
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.",
13289
13346
  replace: [
13290
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"],
13291
- ["a cozy spa with candles, warm light", "un spa acogedor con velas, luz c\xE1lida"]
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"']
13292
13353
  ],
13293
13354
  flags: [
13294
- ["--ref URL", "una imagen de referencia (https; JPEG, PNG, WebP, GIF o SVG); se repite, hasta 4. La imagen CONSERVA su personaje u objeto (mascota, logo, producto)"],
13295
- ["--aspect RATIO", "1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9 o 21:9"],
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)"],
13296
13363
  ["--tenant ID", "el negocio (externalId) para el que es: va al desglose de consumo y a su tope diario opcional"],
13297
13364
  ["--agent SLUG", "a qu\xE9 agente del partner se carga en el desglose (default: el de siempre)"],
13298
- ["--out FILE", "adem\xE1s baja el PNG aqu\xED"]
13365
+ ["--out FILE", "adem\xE1s baja el PNG aqu\xED (varias: FILE-1.png, FILE-2.png\u2026)"]
13299
13366
  ],
13300
13367
  notes: [
13301
- `Cada imagen se cobra aparte: USD 0.25 del saldo del espacio de partner (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}'.`,
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}'.`,
13302
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."
13303
13371
  ]
13304
13372
  },
@@ -13579,7 +13647,7 @@ var HELPS_ES = {
13579
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."]
13580
13648
  },
13581
13649
  spaces: {
13582
- 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.",
13583
13651
  flags: [
13584
13652
  ["--user EMAIL", "ls (staff): los espacios de esa persona"],
13585
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)"],
@@ -13589,8 +13657,9 @@ var HELPS_ES = {
13589
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"],
13590
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"],
13591
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"],
13592
- ["--until DATE", "renew: el nuevo \xABpagado hasta\xBB (AAAA-MM-DD)"],
13593
- ["--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)"],
13594
13663
  ["--to EMAIL", "transfer: el due\xF1o nuevo (si no tiene cuenta se precrea y la reclama al entrar)"],
13595
13664
  ["--keep-as R", "transfer: el rol del due\xF1o anterior \u2014 admin | member (default) | none (sale del espacio)"],
13596
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"],
@@ -13598,7 +13667,7 @@ var HELPS_ES = {
13598
13667
  ["-n, --dry-run", "ense\xF1a qu\xE9 cambia, no cambia nada"],
13599
13668
  ["--yes", "s\xE1ltate la confirmaci\xF3n (obligatorio sin terminal)"]
13600
13669
  ],
13601
- 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."]
13602
13671
  },
13603
13672
  staff: {
13604
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.",
@@ -14088,21 +14157,32 @@ var HELPS = {
14088
14157
  learn: `${REF}#navegador`
14089
14158
  },
14090
14159
  image: {
14091
- summary: "An AI image (Gemini \xABNano Banana Pro\xBB) for your partner businesses, charged to the space's balance.",
14092
- usage: ['ghosty image generate "prompt" [--ref URL]... [--aspect 16:9] [--tenant ID] [--agent SLUG] [--out FILE]'],
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
+ ],
14093
14165
  flags: [
14094
- ["--ref URL", "a reference image (https; JPEG, PNG, WebP, GIF or SVG); repeat it, up to 4. The image KEEPS its character or object (mascot, logo, product)"],
14095
- ["--aspect RATIO", "1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9 or 21:9"],
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)"],
14096
14174
  ["--tenant ID", "the business (externalId) it is for: it goes to the usage breakdown and to its optional daily cap"],
14097
14175
  ["--agent SLUG", "which partner agent it is charged to in the breakdown (default: the main one)"],
14098
- ["--out FILE", "also download the PNG here"]
14176
+ ["--out FILE", "also download the PNG here (several: FILE-1.png, FILE-2.png\u2026)"]
14099
14177
  ],
14100
14178
  examples: [
14101
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',
14102
- 'ghosty image generate "a cozy spa with candles, warm light" --aspect 1:1 --out spa.png'
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'
14103
14182
  ],
14104
14183
  notes: [
14105
- `Each image is charged apart: USD 0.25 from the partner space's balance (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}'.`,
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}'.`,
14106
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."
14107
14187
  ],
14108
14188
  learn: `${DOCS}/partners#imagenes-con-ia`
@@ -14469,8 +14549,8 @@ HELPS.novedades = {
14469
14549
  learn: `${REF}#soporte`
14470
14550
  };
14471
14551
  HELPS.spaces = {
14472
- 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.",
14473
- 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]"],
14474
14554
  flags: [
14475
14555
  ["--user EMAIL", "ls (staff): that person's workspaces"],
14476
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)"],
@@ -14480,8 +14560,9 @@ HELPS.spaces = {
14480
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"],
14481
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"],
14482
14562
  ["--house-claude on|off", "set (staff): its agents run on Ghosty's Claude when the owner brings no key; recycles their Claude boxes"],
14483
- ["--until DATE", "renew: the new \xABpaid until\xBB (YYYY-MM-DD)"],
14484
- ["--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)"],
14485
14566
  ["--to EMAIL", "transfer: the new owner (if they have no account it is pre-created and claimed on login)"],
14486
14567
  ["--keep-as R", "transfer: the previous owner's role \u2014 admin | member (default) | none (removed)"],
14487
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"],
@@ -14489,8 +14570,8 @@ HELPS.spaces = {
14489
14570
  ["-n, --dry-run", "show what changes, change nothing"],
14490
14571
  ["--yes", "skip the confirmation (required without a terminal)"]
14491
14572
  ],
14492
- 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"],
14493
- 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."],
14494
14575
  learn: `${REF}#espacios`
14495
14576
  };
14496
14577
  HELPS.workspaces = HELPS.spaces;
@@ -15216,8 +15297,8 @@ var PACK = { year: { usd: 49, mxn: 990 }, month: { usd: 5, mxn: 99 } };
15216
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"}`;
15217
15298
  var kb = (n) => n ? `${Math.max(1, Math.round(n / 1024))} KB` : "";
15218
15299
  var day = (iso) => iso ? iso.slice(0, 10) : "";
15219
- async function sitesCmd(sub, a1, a2, v) {
15220
- const ex = "ghosty sites ls [--house] \xB7 pin|unpin <slug|id> \xB7 pins [--cancel|--resume] \xB7 search <slug|id> [--days 28] \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>";
15221
15302
  switch (sub ?? "ls") {
15222
15303
  case "ls":
15223
15304
  case "list": {
@@ -15274,6 +15355,9 @@ ${table(r.pinned.map((p) => ({ slug: p.slug, title: (p.title ?? "").slice(0, 40)
15274
15355
  ].join("\n")
15275
15356
  );
15276
15357
  }
15358
+ case "domain":
15359
+ case "domains":
15360
+ return domainCmd(a1, a2, a3);
15277
15361
  case "publish":
15278
15362
  case "deploy": {
15279
15363
  if (!a1) throw usage(t("Missing the file or folder.", "Falta el archivo o la carpeta."), ex);
@@ -15300,6 +15384,41 @@ ${dim(t("Links with the old slug may stop working.", "Las ligas con el slug viej
15300
15384
  throw usage(t(`Unknown sites subcommand: ${sub}.`, `Subcomando de sites desconocido: ${sub}.`), ex);
15301
15385
  }
15302
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
+ }
15303
15422
 
15304
15423
  // src/commands/docs.ts
15305
15424
  init_api();
@@ -16153,6 +16272,73 @@ async function settingsCmd(sub, key, value) {
16153
16272
  throw usage(t(`Unknown settings subcommand: ${sub}.`, `Subcomando de settings desconocido: ${sub}.`), "ghosty settings ls|set|unset <KEY> [value]");
16154
16273
  }
16155
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
+ from: c.planFrom ? c.planFrom.slice(0, 10) : "",
16285
+ until: c.until ? c.until.slice(0, 10) : "",
16286
+ redeem: c.redeemBy ? c.redeemBy.slice(0, 10) : "",
16287
+ promo: c.convertPromo ?? "",
16288
+ used: `${c.redemptions}/${c.maxRedemptions ?? "\u221E"}`,
16289
+ state: c.active ? t("active", "activo") : t("draft", "borrador"),
16290
+ only: c.eligibility ?? "",
16291
+ agent: c.agentId ?? "",
16292
+ note: c.note ?? ""
16293
+ };
16294
+ }
16295
+ function redemptionRow(x) {
16296
+ return { email: x.email, redeemed: x.redeemedAt.slice(0, 10), plan: x.plan ?? "", from: x.from?.slice(0, 10) ?? "", until: x.until?.slice(0, 10) ?? "", status: x.status ?? "", converted: x.converted ?? "\u2014" };
16297
+ }
16298
+ function bodyOf(v) {
16299
+ const b = {};
16300
+ if (v.credit !== void 0) b.creditMxn = Number(v.credit);
16301
+ if (v.plan !== void 0) b.plan = v.plan === "none" ? null : v.plan;
16302
+ if (v.until !== void 0) b.until = v.until;
16303
+ if (v.from !== void 0) b.planFrom = v.from;
16304
+ if (v["redeem-by"] !== void 0) b.redeemBy = v["redeem-by"];
16305
+ if (v["convert-promo"] !== void 0) b.convertPromo = v["convert-promo"];
16306
+ if (v.max !== void 0) b.maxRedemptions = v.max === "none" ? null : Number(v.max);
16307
+ if (v.on) b.active = true;
16308
+ if (v.off) b.active = false;
16309
+ if (v.note !== void 0) b.note = v.note;
16310
+ if (v.eligibility !== void 0) b.eligibility = v.eligibility;
16311
+ if (v.agent !== void 0) b.agentId = v.agent;
16312
+ return b;
16313
+ }
16314
+ async function couponsCmd(sub, code, v) {
16315
+ 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"))}`;
16316
+ if (!sub || sub === "ls" || sub === "list") {
16317
+ const r = await api("/api/v2/support/coupons");
16318
+ return out(r, () => r.coupons.length ? table(r.coupons.map(couponRow), ["code", "credit", "plan", "from", "until", "redeem", "promo", "used", "state", "only", "agent", "note"]) : dim(t("No coupons.", "Sin cupones.")));
16319
+ }
16320
+ if (!code) throw usage(t("Missing the coupon code.", "Falta el c\xF3digo del cup\xF3n."), `ghosty coupons ${sub} <CODE>`);
16321
+ if (sub === "create") {
16322
+ const r = await api("/api/v2/support/coupons", { method: "POST", body: { code, ...bodyOf(v) } });
16323
+ return out(r, () => one(r.coupon));
16324
+ }
16325
+ if (sub === "set") {
16326
+ const r = await api(`/api/v2/support/coupons/${encodeURIComponent(code)}`, { method: "PATCH", body: bodyOf(v) });
16327
+ return out(r, () => one(r.coupon));
16328
+ }
16329
+ if (sub === "show") {
16330
+ const r = await api(`/api/v2/support/coupons/${encodeURIComponent(code)}`);
16331
+ return out(r, () => `${one(r.coupon)}
16332
+ ${r.redemptions.length ? table(r.redemptions.map(redemptionRow), ["email", "redeemed", "plan", "from", "until", "status", "converted"]) : dim(t("Nobody redeemed it yet.", "Nadie lo ha canjeado."))}
16333
+ ${dim(t(`${r.converted}/${r.redemptions.length} converted to a paid plan`, `${r.converted}/${r.redemptions.length} ya pagan un plan`))}`);
16334
+ }
16335
+ if (sub === "redeem") {
16336
+ const r = await api("/api/v2/me/coupons", { method: "POST", body: { code } });
16337
+ return out(r, () => `${lavender("\u2713")} ${r.message}`);
16338
+ }
16339
+ throw usage(t(`Unknown coupons subcommand: ${sub}.`, `Subcomando de coupons desconocido: ${sub}.`), "ghosty coupons ls|create|set|show|redeem <CODE>");
16340
+ }
16341
+
16156
16342
  // src/commands/ads.ts
16157
16343
  init_api();
16158
16344
  init_i18n();
@@ -16486,6 +16672,7 @@ ${bold("Workspaces")} ${dim("(changing them: Ghosty staff)")}
16486
16672
  spaces join|leave <workspace> Enter or leave a client's workspace as staff (no seat; logged)
16487
16673
  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]
16488
16674
  spaces renew <workspace> --until DATE Move \xABpaid until\xBB (logged) [--note] [--dry-run]
16675
+ 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]
16489
16676
  spaces rename <old> <new> Change its slug only [--dry-run]
16490
16677
  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]
16491
16678
  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]
@@ -16495,6 +16682,9 @@ ${bold("Workspaces")} ${dim("(changing them: Ghosty staff)")}
16495
16682
  staff ls [staff-email] [client-email] Ghosty staff assigned to client accounts (staff) [--all: also revoked]
16496
16683
  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]
16497
16684
  settings ls|set|unset <KEY> [value] Platform switches (staff): who answers /c without an account, native subagents\u2026 [+id/-id edits a list]
16685
+ 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] [--from DATE: the lent plan starts then] [--redeem-by DATE: last day to redeem] [--convert-promo STRIPE_CODE: offered when the plan ends] [--on|--off] [--note]
16686
+ coupons show <CODE> Who redeemed it: plan window (pending/active/ended) and who converted to a paid plan (staff)
16687
+ coupons redeem <CODE> Redeem a coupon for your account (also /canjear?codigo=CODE)
16498
16688
  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]
16499
16689
  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]
16500
16690
  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]
@@ -16598,6 +16788,7 @@ ${bold("Sites")} ${dim("(sites your agents publish at <slug>.ghosty.page)")}
16598
16788
  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]
16599
16789
  sites unpin <slug|id> Release its slot (back to the Free rule: 30 days + notice)
16600
16790
  sites search <slug|id> How Google finds it: clicks, impressions, searches, indexed or not [--days 28]
16791
+ sites domain add|verify|rm <slug> <dom> Custom domain for an app (DNS record \u2192 verify \u2192 HTTPS) [ls <slug>: list]
16601
16792
  sites pins Your pinned-sites subscription and slots [--cancel | --resume]
16602
16793
  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]
16603
16794
  sites slug <slug|id> <new-slug> Change a site's slug (staff)
@@ -16640,7 +16831,8 @@ ${bold("Browser")} ${dim("(Ghosty for Chrome)")}
16640
16831
 
16641
16832
  ${bold("Partners")} ${dim("(give an agent to each of YOUR customers \u2014 see: ghosty partner --help)")}
16642
16833
  partner ls | enable | credentials | agent | tenants | audit | try
16643
- image generate "prompt" An AI image for your businesses, charged to the space's balance [--ref URL (repeat, keeps the character)] [--aspect 16:9] [--tenant ID] [--out FILE]
16834
+ 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]
16835
+ 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]
16644
16836
 
16645
16837
  ${bold("Your account")}
16646
16838
  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]
@@ -16689,6 +16881,7 @@ ${bold("Espacios")} ${dim("(cambiarlos: staff de Ghosty)")}
16689
16881
  spaces join|leave <espacio> Entra o sal del espacio de un cliente como staff (sin asiento; queda en bit\xE1cora)
16690
16882
  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]
16691
16883
  spaces renew <espacio> --until FECHA Mueve \xABpagado hasta\xBB (queda en bit\xE1cora) [--note] [--dry-run]
16884
+ 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]
16692
16885
  spaces rename <viejo> <nuevo> Cambia s\xF3lo su slug [--dry-run]
16693
16886
  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]
16694
16887
  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]
@@ -16698,6 +16891,9 @@ ${bold("Espacios")} ${dim("(cambiarlos: staff de Ghosty)")}
16698
16891
  staff ls [correo-staff] [correo-cliente] Staff de Ghosty asignado a cuentas cliente (staff) [--all: tambi\xE9n revocadas]
16699
16892
  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]
16700
16893
  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]
16894
+ 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] [--from FECHA: el plan prestado arranca ah\xED] [--redeem-by FECHA: \xFAltimo d\xEDa para canjear] [--convert-promo C\xD3DIGO_STRIPE: se ofrece al terminar el plan] [--on|--off] [--note]
16895
+ coupons show <C\xD3DIGO> Qui\xE9n lo canje\xF3: ventana del plan (por empezar/activo/termin\xF3) y qui\xE9n ya paga un plan (staff)
16896
+ coupons redeem <C\xD3DIGO> Canjea un cup\xF3n para tu cuenta (tambi\xE9n /canjear?codigo=C\xD3DIGO)
16701
16897
  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]
16702
16898
  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]
16703
16899
  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]
@@ -16801,6 +16997,7 @@ ${bold("Sitios")} ${dim("(los sitios que publican tus agentes en <slug>.ghosty.
16801
16997
  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]
16802
16998
  sites unpin <slug|id> Suelta su lugar (vuelve a la regla Gratis: 30 d\xEDas + aviso)
16803
16999
  sites pins Tu suscripci\xF3n de Sitios fijos y sus lugares [--cancel | --resume]
17000
+ sites domain add|verify|rm <slug> <dom> Dominio propio para una app (registro DNS \u2192 verificar \u2192 HTTPS) [ls <slug>: lista]
16804
17001
  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]
16805
17002
  sites slug <slug|id> <slug-nuevo> Cambia el slug de un sitio (staff)
16806
17003
 
@@ -16842,7 +17039,8 @@ ${bold("Navegador")} ${dim("(Ghosty para Chrome)")}
16842
17039
 
16843
17040
  ${bold("Partners")} ${dim("(dale un agente a cada uno de TUS clientes \u2014 ver: ghosty partner --help)")}
16844
17041
  partner ls | enable | credentials | agent | tenants | audit | try
16845
- image generate "prompt" Una imagen con IA para tus negocios, cobrada del saldo del espacio [--ref URL (se repite, conserva al personaje)] [--aspect 16:9] [--tenant ID] [--out ARCHIVO]
17042
+ 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]
17043
+ 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]
16846
17044
 
16847
17045
  ${bold("Tu cuenta")}
16848
17046
  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]
@@ -16868,7 +17066,7 @@ ${bold("Opciones globales")}
16868
17066
  ${bold("C\xF3digos de salida")} 0 bien \xB7 1 error de la API \xB7 2 uso incorrecto \xB7 3 sin sesi\xF3n
16869
17067
  ${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.")}
16870
17068
  ${dim("Documentaci\xF3n: https://www.ghosty.studio/docs/cli")}`;
16871
- 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", "image", "me", "channels", "board", "boards", "access", "credentials", "connectors", "browser", "support", "docs", "help"];
17069
+ 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"];
16872
17070
  var AGENTS_SUBS = ["ls", "create", "get", "show", "set", "export", "doctor", "docs", "share", "unshare", "shares", "teams", "rm", "restart", "demo", "embed", "box", "token", "memory", "transfer"];
16873
17071
  function unknown(what, input, known, helpCmd) {
16874
17072
  const s2 = suggest(input, known);
@@ -16934,6 +17132,10 @@ async function main() {
16934
17132
  in: { type: "string" },
16935
17133
  every: { type: "string" },
16936
17134
  until: { type: "string" },
17135
+ credit: { type: "string" },
17136
+ eligibility: { type: "string" },
17137
+ "redeem-by": { type: "string" },
17138
+ "convert-promo": { type: "string" },
16937
17139
  title: { type: "string" },
16938
17140
  body: { type: "string" },
16939
17141
  profile: { type: "string" },
@@ -17012,6 +17214,7 @@ async function main() {
17012
17214
  like: { type: "string" },
17013
17215
  "copy-dbs": { type: "boolean" },
17014
17216
  "copy-keys": { type: "boolean" },
17217
+ courtesy: { type: "boolean" },
17015
17218
  "keep-as": { type: "string" },
17016
17219
  "no-notify": { type: "boolean" },
17017
17220
  board: { type: "string" },
@@ -17138,6 +17341,11 @@ async function main() {
17138
17341
  "ads-only": { type: "string" },
17139
17342
  "partner-tenant": { type: "string" },
17140
17343
  ref: { type: "string", multiple: true },
17344
+ "style-ref": { type: "string", multiple: true },
17345
+ scene: { type: "string", multiple: true },
17346
+ direction: { type: "string", multiple: true },
17347
+ variants: { type: "string" },
17348
+ size: { type: "string" },
17141
17349
  aspect: { type: "string" },
17142
17350
  tenant: { type: "string" },
17143
17351
  "app-id": { type: "string" },
@@ -17173,7 +17381,7 @@ async function main() {
17173
17381
  const tokens = splitTokens(pos, v);
17174
17382
  globals.token = tokens.session;
17175
17383
  globals.profile = v.profile;
17176
- if (v.version) return out({ version: "0.78.0" }, () => "0.78.0");
17384
+ if (v.version) return out({ version: "0.84.0" }, () => "0.84.0");
17177
17385
  for (const n of normalizeArgs(pos, v)) if (!globals.json) info(dim(n));
17178
17386
  const context = (await readConfig()).workspace;
17179
17387
  if (!explicitToken()?.startsWith("gat_")) {
@@ -17276,7 +17484,7 @@ async function main() {
17276
17484
  return novedadesCmd(sub, a1, v);
17277
17485
  case "sites":
17278
17486
  case "site":
17279
- return sitesCmd(sub, a1, a2, v);
17487
+ return sitesCmd(sub, a1, a2, v, pos[4]);
17280
17488
  case "board":
17281
17489
  return boardCmd(sub, a1, v, a2);
17282
17490
  case "boards":
@@ -17315,7 +17523,7 @@ async function main() {
17315
17523
  case "partner":
17316
17524
  return partnerCmd(sub, a1, a2, { ...v, token: tokens.turn }, pos[4]);
17317
17525
  case "image":
17318
- return imageCmd(sub, a1, v);
17526
+ return imageCmd(sub, a1, a2, v);
17319
17527
  case "usage":
17320
17528
  if (v.images) return imageUsage(v);
17321
17529
  if (v.boxes && v.by === "template") return fleetBoxes();
@@ -17342,6 +17550,8 @@ async function main() {
17342
17550
  return staffCmd(sub, a1, a2, v);
17343
17551
  case "settings":
17344
17552
  return settingsCmd(sub, a1, a2);
17553
+ case "coupons":
17554
+ return couponsCmd(sub, a1, v);
17345
17555
  case "ads":
17346
17556
  return adsCmd(sub, a1, v);
17347
17557
  case "audit":
@@ -17370,7 +17580,7 @@ async function main() {
17370
17580
  throw unknown(cmd, cmd, COMMANDS, "ghosty --help");
17371
17581
  }
17372
17582
  }
17373
- var notice = updateNotice("0.78.0").catch(() => null);
17583
+ var notice = updateNotice("0.84.0").catch(() => null);
17374
17584
  main().then(
17375
17585
  async () => {
17376
17586
  const n = await notice;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghostystudio/cli",
3
- "version": "0.78.0",
3
+ "version": "0.84.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": {