@ghostystudio/cli 0.77.0 → 0.78.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 (2) hide show
  1. package/dist/index.js +152 -32
  2. package/package.json +1 -1
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.78.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.78.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":
@@ -11205,7 +11205,7 @@ async function call(path, v, init = {}) {
11205
11205
  "x-ghosty-ts": ts,
11206
11206
  "x-ghosty-sig": sig,
11207
11207
  "content-type": "application/json",
11208
- "user-agent": `ghosty-cli/${"0.77.0"}`
11208
+ "user-agent": `ghosty-cli/${"0.78.0"}`
11209
11209
  },
11210
11210
  body: method === "GET" ? void 0 : raw
11211
11211
  });
@@ -11482,9 +11482,41 @@ async function partnerCmd(sub, a1, a2, v, a3) {
11482
11482
  }
11483
11483
  }
11484
11484
 
11485
+ // src/commands/image.ts
11486
+ import { writeFile as writeFile6 } from "node:fs/promises";
11487
+ init_util();
11488
+ 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
+ }
11493
+ 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
+ const body = {
11495
+ prompt,
11496
+ ...v.ref?.length ? { referenceUrls: v.ref } : {},
11497
+ ...v.aspect ? { aspectRatio: v.aspect } : {},
11498
+ ...v.tenant ? { tenant: { externalId: v.tenant } } : {},
11499
+ ...v.agent ? { agent: v.agent } : {}
11500
+ };
11501
+ const r = await json("/api/v2/partner/images", v, { method: "POST", body });
11502
+ 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()));
11506
+ }
11507
+ return out(
11508
+ r,
11509
+ () => [
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}`)}`] : []
11513
+ ].join("\n")
11514
+ );
11515
+ }
11516
+
11485
11517
  // src/commands/data.ts
11486
11518
  init_api();
11487
- import { readdir as readdir2, readFile as readFile6, writeFile as writeFile6 } from "node:fs/promises";
11519
+ import { readdir as readdir2, readFile as readFile6, writeFile as writeFile7 } from "node:fs/promises";
11488
11520
  import { basename as basename3, join as join5 } from "node:path";
11489
11521
 
11490
11522
  // src/rows.ts
@@ -11653,7 +11685,7 @@ async function dbsExport(id, db, v) {
11653
11685
  const suggested = /filename="([^"]+)"/.exec(res.headers.get("content-disposition") ?? "")?.[1] ?? (format === "csv" ? `${db}-${v.table}.csv` : `${db}.sqlite`);
11654
11686
  const file = v.out ?? suggested;
11655
11687
  const bytes = Buffer.from(await res.arrayBuffer());
11656
- await writeFile6(file, bytes);
11688
+ await writeFile7(file, bytes);
11657
11689
  const countsHeader = res.headers.get("x-gs-row-counts");
11658
11690
  const counts = countsHeader ? JSON.parse(Buffer.from(countsHeader, "base64").toString("utf8")) : {};
11659
11691
  const rows = Object.entries(counts).map(([tb, n]) => ({ table: tb, rows: n }));
@@ -11719,6 +11751,19 @@ ${dim(`${t(`${rows.length} row(s)`, `${rows.length} fila(s)`)} \xB7 ${Math.round
11719
11751
  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
11752
  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
11753
  }
11754
+ case "checkpoints": {
11755
+ db = need2(db, t("database (id or name)", "la base (id o nombre)"), "ghosty dbs checkpoints <agent-id> <db>");
11756
+ const r = await api(agentPath2(id, `/dbs/${enc8(db)}/checkpoints`));
11757
+ 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 }));
11758
+ 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.")));
11759
+ }
11760
+ case "restore": {
11761
+ db = need2(db, t("database (id or name)", "la base (id o nombre)"), "ghosty dbs restore <agent-id> <db> <checkpoint-id> [--yes]");
11762
+ 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>`);
11763
+ 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}` });
11764
+ const r = await api(agentPath2(id, `/dbs/${enc8(db)}/checkpoints`), { method: "POST", body: { checkpointId: ckpt } });
11765
+ 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 ?? "")}`);
11766
+ }
11722
11767
  case "grant": {
11723
11768
  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
11769
  let rowScope;
@@ -11881,6 +11926,7 @@ function skipReason(r) {
11881
11926
  reaction: ["only a reaction", "s\xF3lo una reacci\xF3n"],
11882
11927
  channel_off: ["WhatsApp channel off for this agent", "canal de WhatsApp apagado en este agente"],
11883
11928
  no_quota: ["out of balance", "sin saldo"],
11929
+ 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
11930
  partner_token: ["partner did not issue a turn token", "el partner no dio token de turno"],
11885
11931
  partner_busy: ["partner at its concurrent-turn limit", "el partner lleg\xF3 a su tope de turnos a la vez"]
11886
11932
  };
@@ -13238,6 +13284,24 @@ var HELPS_ES = {
13238
13284
  summary: "Ghosty para Chrome: cerrar la sesi\xF3n del navegador.",
13239
13285
  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
13286
  },
13287
+ 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.",
13289
+ replace: [
13290
+ ["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"]
13292
+ ],
13293
+ 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"],
13296
+ ["--tenant ID", "el negocio (externalId) para el que es: va al desglose de consumo y a su tope diario opcional"],
13297
+ ["--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"]
13299
+ ],
13300
+ 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}'.`,
13302
+ "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
+ ]
13304
+ },
13241
13305
  me: {
13242
13306
  summary: "Tu cuenta: archivos, memorias, documentos editables y uso.",
13243
13307
  replace: [
@@ -13494,6 +13558,7 @@ var HELPS_ES = {
13494
13558
  ["--currency C", "pin: usd (default) o mxn \u2014 la moneda del cobro si hay que pagar"],
13495
13559
  ["--add-pack", "pin: ya usas tus lugares \u2014 suma 3 m\xE1s (mismo periodo y moneda de tu suscripci\xF3n) y fija \xE9ste"],
13496
13560
  ["--cancel / --resume", "pins: cancela al final del periodo (los sitios vuelven a Gratis con aviso), o lo deshace"],
13561
+ ["--days N", "search: la ventana de Search Console en d\xEDas (default 28; Google reporta con 2\u20133 d\xEDas de retraso)"],
13497
13562
  ["--title T", "publish: el nombre del sitio (default: el <title> del HTML o el nombre de la carpeta)"],
13498
13563
  ["--slug S", "publish: el slug de un sitio NUEVO (a-z, 0-9 y guiones, 3\u201360, no reservado)"],
13499
13564
  ["--site X", "publish: actualiza ese sitio de la casa (slug, id o URL) en vez de crear uno"]
@@ -13589,7 +13654,7 @@ var HELPS_ES = {
13589
13654
  ["--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
13655
  ["--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
13656
  ["--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"],
13657
+ ["--gen KEY,KEY", "env push: genera ah\xED mismo llaves al azar (32 bytes hex) con esos nombres; nunca se imprimen"],
13593
13658
  ["--replace", "env push: reemplaza el .env entero de la preview (por defecto agrega y deja las dem\xE1s)"],
13594
13659
  ["--create", "rooms connect: crea el room si no existe"],
13595
13660
  ["--private", "rooms connect --create: el room nuevo es privado (s\xF3lo t\xFA, y a quien agregues)"],
@@ -13836,6 +13901,8 @@ var HELPS = {
13836
13901
  "ghosty dbs tables <agent-id> <db>",
13837
13902
  'ghosty dbs query <agent-id> <db> "SQL" [--arg V]\u2026 [--write]',
13838
13903
  "ghosty dbs origin <agent-id> <db> [--table T] [--since 2026-10-10] [--limit N]",
13904
+ "ghosty dbs checkpoints <agent-id> <db>",
13905
+ "ghosty dbs restore <agent-id> <db> <checkpoint-id> [--yes]",
13839
13906
  "ghosty dbs export <agent-id> <db> [--table T] [--out FILE]",
13840
13907
  "ghosty dbs import <agent-id> <db> --from easybits:<db-id> [--tables a,b] [--append] [--rehost-images t.col,\u2026] [--dry-run] [--yes]",
13841
13908
  "ghosty dbs rehost <agent-id> <db> --table T --column C [--dry-run]",
@@ -14020,6 +14087,26 @@ var HELPS = {
14020
14087
  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
14088
  learn: `${REF}#navegador`
14022
14089
  },
14090
+ 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]'],
14093
+ 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"],
14096
+ ["--tenant ID", "the business (externalId) it is for: it goes to the usage breakdown and to its optional daily cap"],
14097
+ ["--agent SLUG", "which partner agent it is charged to in the breakdown (default: the main one)"],
14098
+ ["--out FILE", "also download the PNG here"]
14099
+ ],
14100
+ examples: [
14101
+ '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'
14103
+ ],
14104
+ 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}'.`,
14106
+ "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
+ ],
14108
+ learn: `${DOCS}/partners#imagenes-con-ia`
14109
+ },
14023
14110
  me: {
14024
14111
  summary: "Your account: files, memories, editable docs and usage.",
14025
14112
  usage: [
@@ -14425,6 +14512,7 @@ HELPS.sites = {
14425
14512
  "ghosty sites pin <slug|id> [--interval year|month] [--currency usd|mxn] [--add-pack]",
14426
14513
  "ghosty sites unpin <slug|id>",
14427
14514
  "ghosty sites pins [--cancel | --resume]",
14515
+ "ghosty sites search <slug|id> [--days 28]",
14428
14516
  "ghosty sites publish <file.html|folder> [--title T] [--slug S] [--site <slug|id>]",
14429
14517
  "ghosty sites slug <slug|id> <new-slug>"
14430
14518
  ],
@@ -14434,6 +14522,7 @@ HELPS.sites = {
14434
14522
  ["--currency C", "pin: usd (default) or mxn \u2014 the currency charged if you need to pay"],
14435
14523
  ["--add-pack", "pin: you already use your slots \u2014 add 3 more (same period and currency as your subscription) and pin this one"],
14436
14524
  ["--cancel / --resume", "pins: cancel at period end (sites go back to Free with notice), or undo it"],
14525
+ ["--days N", "search: Search Console window in days (default 28; Google reports 2\u20133 days late)"],
14437
14526
  ["--title T", "publish: the site's name (default: the HTML <title> or the folder name)"],
14438
14527
  ["--slug S", "publish: the slug for a NEW site (a-z, 0-9 and hyphens, 3\u201360, not reserved)"],
14439
14528
  ["--site X", "publish: update that house site (slug, id or URL) instead of creating one"]
@@ -14585,7 +14674,7 @@ function helpText(cmd) {
14585
14674
 
14586
14675
  // src/update.ts
14587
14676
  init_i18n();
14588
- import { mkdir as mkdir5, readFile as readFile9, writeFile as writeFile8 } from "node:fs/promises";
14677
+ import { mkdir as mkdir5, readFile as readFile9, writeFile as writeFile9 } from "node:fs/promises";
14589
14678
  import { homedir as homedir3 } from "node:os";
14590
14679
  import { dirname as dirname3, join as join7 } from "node:path";
14591
14680
  var FILE = join7(process.env.XDG_CONFIG_HOME || join7(homedir3(), ".config"), "ghosty", "update.json");
@@ -14611,7 +14700,7 @@ async function latestVersion(current2) {
14611
14700
  const latest = (await r.json()).version;
14612
14701
  if (latest) cache2 = { latest, checkedAt: Date.now() };
14613
14702
  await mkdir5(dirname3(FILE), { recursive: true });
14614
- await writeFile8(FILE, JSON.stringify(cache2));
14703
+ await writeFile9(FILE, JSON.stringify(cache2));
14615
14704
  } catch {
14616
14705
  }
14617
14706
  }
@@ -14691,7 +14780,7 @@ function agentArgIndex(pos) {
14691
14780
  }
14692
14781
 
14693
14782
  // src/completion.ts
14694
- import { mkdir as mkdir6, readFile as readFile10, writeFile as writeFile9 } from "node:fs/promises";
14783
+ import { mkdir as mkdir6, readFile as readFile10, writeFile as writeFile10 } from "node:fs/promises";
14695
14784
  import { homedir as homedir4 } from "node:os";
14696
14785
  import { join as join8 } from "node:path";
14697
14786
  var GLOBAL_FLAGS = ["--json", "--token", "--profile", "--yes", "--help", "--version", "--lang", "--dry-run"];
@@ -14743,7 +14832,7 @@ async function names() {
14743
14832
  const workspaces = [...new Set(list2.map((a) => a.espacio?.tipo === "workspace" ? a.espacio.nombre : void 0).filter((x) => !!x))];
14744
14833
  await mkdir6(join8(cacheFile(), ".."), { recursive: true }).catch(() => {
14745
14834
  });
14746
- await writeFile9(cacheFile(), JSON.stringify({ at: Date.now(), agents, workspaces })).catch(() => {
14835
+ await writeFile10(cacheFile(), JSON.stringify({ at: Date.now(), agents, workspaces })).catch(() => {
14747
14836
  });
14748
14837
  return { agents, workspaces };
14749
14838
  }
@@ -14830,7 +14919,7 @@ init_i18n();
14830
14919
  // src/commands/spec.ts
14831
14920
  var import_yaml = __toESM(require_dist(), 1);
14832
14921
  init_api();
14833
- import { mkdir as mkdir7, readdir as readdir4, readFile as readFile11, stat as stat2, writeFile as writeFile10 } from "node:fs/promises";
14922
+ import { mkdir as mkdir7, readdir as readdir4, readFile as readFile11, stat as stat2, writeFile as writeFile11 } from "node:fs/promises";
14834
14923
  import { join as join9, resolve as resolve2 } from "node:path";
14835
14924
  init_util();
14836
14925
  init_i18n();
@@ -14844,7 +14933,7 @@ async function agentsExport(id, v) {
14844
14933
  }
14845
14934
  const dir = resolve2(v.out);
14846
14935
  await mkdir7(dir, { recursive: true });
14847
- await writeFile10(join9(dir, "agent.yaml"), yaml);
14936
+ await writeFile11(join9(dir, "agent.yaml"), yaml);
14848
14937
  const own = (0, import_yaml.parse)(yaml)["x-ghosty"]?.skills?.own ?? [];
14849
14938
  for (const slug of own) {
14850
14939
  const sk = await api(`/api/v2/agents/${enc12(id)}/skills/${enc12(slug)}`);
@@ -14967,7 +15056,7 @@ ${dim(t(`${r.totals.activeTurns} turn(s) running`, `${r.totals.activeTurns} turn
14967
15056
  init_api();
14968
15057
  init_auth();
14969
15058
  import { randomUUID as randomUUID2 } from "node:crypto";
14970
- import { mkdir as mkdir8, readFile as readFile12, writeFile as writeFile11 } from "node:fs/promises";
15059
+ import { mkdir as mkdir8, readFile as readFile12, writeFile as writeFile12 } from "node:fs/promises";
14971
15060
  import { homedir as homedir5 } from "node:os";
14972
15061
  import { dirname as dirname4, join as join10 } from "node:path";
14973
15062
  init_i18n();
@@ -15012,7 +15101,7 @@ async function visitorCookie(base, slug, fresh) {
15012
15101
  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
15102
  jar[slug] = cookie;
15014
15103
  await mkdir8(dirname4(jarPath()), { recursive: true });
15015
- await writeFile11(jarPath(), JSON.stringify(jar, null, 2), { mode: 384 });
15104
+ await writeFile12(jarPath(), JSON.stringify(jar, null, 2), { mode: 384 });
15016
15105
  return cookie;
15017
15106
  }
15018
15107
  async function visitorTurn(base, slug, cookie, text) {
@@ -15128,7 +15217,7 @@ var packPrice = (c, i, lang2) => `$${(PACK[i]?.[c] ?? 0).toLocaleString("en-US")
15128
15217
  var kb = (n) => n ? `${Math.max(1, Math.round(n / 1024))} KB` : "";
15129
15218
  var day = (iso) => iso ? iso.slice(0, 10) : "";
15130
15219
  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>";
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>";
15132
15221
  switch (sub ?? "ls") {
15133
15222
  case "ls":
15134
15223
  case "list": {
@@ -15169,6 +15258,22 @@ ${dim(r.expiresAt ? t(`Free rule: pauses ${day(r.expiresAt)} unless it changes (
15169
15258
  ${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
15259
  );
15171
15260
  }
15261
+ case "search": {
15262
+ 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]");
15263
+ const r = await api(`/api/v2/sites/${encodeURIComponent(a1)}/search${v.days ? `?days=${encodeURIComponent(v.days)}` : ""}`);
15264
+ const p = r.portada;
15265
+ 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 ?? "")}`;
15266
+ const tot = r.total;
15267
+ return out(
15268
+ r,
15269
+ () => [
15270
+ `${bold(r.sitio)} ${dim(`${r.desde} \u2192 ${r.hasta}`)}`,
15271
+ `${t("Home", "Portada")}: ${index}`,
15272
+ `${t("clicks", "clics")} ${tot.clics} \xB7 ${t("impressions", "impresiones")} ${tot.impresiones} \xB7 CTR ${tot.ctr} \xB7 ${t("position", "posici\xF3n")} ${tot.posicion ?? "\u2014"}`,
15273
+ 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)."))
15274
+ ].join("\n")
15275
+ );
15276
+ }
15172
15277
  case "publish":
15173
15278
  case "deploy": {
15174
15279
  if (!a1) throw usage(t("Missing the file or folder.", "Falta el archivo o la carpeta."), ex);
@@ -15198,7 +15303,7 @@ ${dim(t("Links with the old slug may stop working.", "Las ligas con el slug viej
15198
15303
 
15199
15304
  // src/commands/docs.ts
15200
15305
  init_api();
15201
- import { writeFile as writeFile12 } from "node:fs/promises";
15306
+ import { writeFile as writeFile13 } from "node:fs/promises";
15202
15307
 
15203
15308
  // ../docs/src/core/doc-blocks.ts
15204
15309
  var DOC_ENVELOPE_VERSION = 1;
@@ -15361,7 +15466,7 @@ ${t("Ids: ghosty me docs ls", "Ids: ghosty me docs ls")}`);
15361
15466
  }
15362
15467
  const named = /filename\*=UTF-8''([^;]+)/i.exec(res.headers.get("content-disposition") ?? "")?.[1];
15363
15468
  const file = v.out ?? (named ? decodeURIComponent(named) : fileName(id, format));
15364
- await writeFile12(file, bytes);
15469
+ await writeFile13(file, bytes);
15365
15470
  return out({ id, format, saved: file, bytes: bytes.length }, () => `${lavender("\u2713")} ${file} (${bytes.length} bytes)`);
15366
15471
  }
15367
15472
  case "check": {
@@ -15387,7 +15492,7 @@ ${t("Ids: ghosty me docs ls", "Ids: ghosty me docs ls")}`);
15387
15492
  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
15493
  const bytes = Buffer.from(await res.arrayBuffer());
15389
15494
  const file = v.out ?? `${id}-${block}${segment !== null ? `-${segment}` : ""}.${audioExt(res.headers.get("content-type"))}`;
15390
- await writeFile12(file, bytes);
15495
+ await writeFile13(file, bytes);
15391
15496
  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
15497
  }
15393
15498
  case "restore": {
@@ -15901,20 +16006,25 @@ ${dim(`${t("Detail", "Detalle")}: ghosty factory runs show <run> --workspace ${r
15901
16006
  const { advice: a, workspace } = await api(`${path}/advise`, { body: {} });
15902
16007
  const note = a.buildNote.map((p, i) => `${i + 1}. ${p.point}`).join(" ");
15903
16008
  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}`;
16009
+ 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;
16010
+ const label = {
16011
+ fix: t("Let @build fix it", "Que @build lo arregle"),
16012
+ replan: t("Redo the plan", "Rehacer el plan"),
16013
+ close: t("Close the PR", "Cerrar el PR"),
16014
+ close_ignore: t("Close the PR and ignore this version", "Cerrar el PR e ignorar esta versi\xF3n"),
16015
+ recheck: t("Let @check review again", "Que @check revise otra vez"),
16016
+ wait: t("Wait: it's a platform fault", "Esperar: es una falla de la plataforma")
16017
+ }[a.decision] ?? a.decision;
15905
16018
  return out({ workspace, advice: a, apply }, () => {
15906
16019
  const conf = { high: t("high", "alta"), medium: t("medium", "media"), low: t("low", "baja") }[a.confidence] ?? a.confidence;
15907
16020
  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}`)}`,
16021
+ `${bold(`#${id}`)} ${bold(label)} ${dim(`\xB7 ${t("confidence", "confianza")} ${conf} \xB7 ${a.model}`)}`,
15909
16022
  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)}`),
16023
+ ...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}
16024
+ ${dim(p.cites)}`)] : [],
15914
16025
  ...a.checkScope && a.decision === "fix" ? ["", bold(t("@check reviews only", "@check revisa s\xF3lo")), ` ${a.checkScope}`] : [],
15915
16026
  "",
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}`
16027
+ ...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
16028
  ].join("\n");
15919
16029
  });
15920
16030
  }
@@ -16058,6 +16168,9 @@ async function adsCmd(sub, action, v) {
16058
16168
  lavender(t("Meta ad accounts", "Cuentas de Meta")),
16059
16169
  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
16170
  "",
16171
+ lavender(t("Google Ads accounts", "Cuentas de Google Ads")),
16172
+ 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)")),
16173
+ "",
16061
16174
  lavender(t("Ads-only WhatsApp numbers", "N\xFAmeros de WhatsApp \xABs\xF3lo anuncios\xBB")),
16062
16175
  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
16176
  ].join("\n")
@@ -16484,6 +16597,7 @@ ${bold("Sites")} ${dim("(sites your agents publish at <slug>.ghosty.page)")}
16484
16597
  sites ls Sites your agents published (live and paused) [--house: Ghosty's own demos and landings (staff)]
16485
16598
  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
16599
  sites unpin <slug|id> Release its slot (back to the Free rule: 30 days + notice)
16600
+ sites search <slug|id> How Google finds it: clicks, impressions, searches, indexed or not [--days 28]
16487
16601
  sites pins Your pinned-sites subscription and slots [--cancel | --resume]
16488
16602
  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
16603
  sites slug <slug|id> <new-slug> Change a site's slug (staff)
@@ -16526,6 +16640,7 @@ ${bold("Browser")} ${dim("(Ghosty for Chrome)")}
16526
16640
 
16527
16641
  ${bold("Partners")} ${dim("(give an agent to each of YOUR customers \u2014 see: ghosty partner --help)")}
16528
16642
  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]
16529
16644
 
16530
16645
  ${bold("Your account")}
16531
16646
  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]
@@ -16727,6 +16842,7 @@ ${bold("Navegador")} ${dim("(Ghosty para Chrome)")}
16727
16842
 
16728
16843
  ${bold("Partners")} ${dim("(dale un agente a cada uno de TUS clientes \u2014 ver: ghosty partner --help)")}
16729
16844
  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]
16730
16846
 
16731
16847
  ${bold("Tu cuenta")}
16732
16848
  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 +16868,7 @@ ${bold("Opciones globales")}
16752
16868
  ${bold("C\xF3digos de salida")} 0 bien \xB7 1 error de la API \xB7 2 uso incorrecto \xB7 3 sin sesi\xF3n
16753
16869
  ${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
16870
  ${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"];
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"];
16756
16872
  var AGENTS_SUBS = ["ls", "create", "get", "show", "set", "export", "doctor", "docs", "share", "unshare", "shares", "teams", "rm", "restart", "demo", "embed", "box", "token", "memory", "transfer"];
16757
16873
  function unknown(what, input, known, helpCmd) {
16758
16874
  const s2 = suggest(input, known);
@@ -17021,6 +17137,8 @@ async function main() {
17021
17137
  draft: { type: "boolean" },
17022
17138
  "ads-only": { type: "string" },
17023
17139
  "partner-tenant": { type: "string" },
17140
+ ref: { type: "string", multiple: true },
17141
+ aspect: { type: "string" },
17024
17142
  tenant: { type: "string" },
17025
17143
  "app-id": { type: "string" },
17026
17144
  secret: { type: "string" },
@@ -17055,14 +17173,14 @@ async function main() {
17055
17173
  const tokens = splitTokens(pos, v);
17056
17174
  globals.token = tokens.session;
17057
17175
  globals.profile = v.profile;
17058
- if (v.version) return out({ version: "0.77.0" }, () => "0.77.0");
17176
+ if (v.version) return out({ version: "0.78.0" }, () => "0.78.0");
17059
17177
  for (const n of normalizeArgs(pos, v)) if (!globals.json) info(dim(n));
17060
17178
  const context = (await readConfig()).workspace;
17061
17179
  if (!explicitToken()?.startsWith("gat_")) {
17062
17180
  const at = agentArgIndex(pos);
17063
17181
  if (at !== null && pos[at]) pos[at] = await resolveAgent(pos[at], context);
17064
17182
  for (const f of ["like", "prompt-base", "agent"]) {
17065
- if (f === "agent" && pos[0] === "partner") continue;
17183
+ if (f === "agent" && (pos[0] === "partner" || pos[0] === "image")) continue;
17066
17184
  const val = v[f];
17067
17185
  if (typeof val === "string" && val !== "none") v[f] = await resolveAgent(val, context);
17068
17186
  }
@@ -17196,6 +17314,8 @@ async function main() {
17196
17314
  return browserCmd(sub, v);
17197
17315
  case "partner":
17198
17316
  return partnerCmd(sub, a1, a2, { ...v, token: tokens.turn }, pos[4]);
17317
+ case "image":
17318
+ return imageCmd(sub, a1, v);
17199
17319
  case "usage":
17200
17320
  if (v.images) return imageUsage(v);
17201
17321
  if (v.boxes && v.by === "template") return fleetBoxes();
@@ -17250,7 +17370,7 @@ async function main() {
17250
17370
  throw unknown(cmd, cmd, COMMANDS, "ghosty --help");
17251
17371
  }
17252
17372
  }
17253
- var notice = updateNotice("0.77.0").catch(() => null);
17373
+ var notice = updateNotice("0.78.0").catch(() => null);
17254
17374
  main().then(
17255
17375
  async () => {
17256
17376
  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.78.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": {