@ghostystudio/cli 0.50.0 → 0.52.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 +14 -0
  2. package/dist/index.js +190 -8
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -38,6 +38,10 @@ ghosty agents box <id> [--check PATH]… [--contains PATH=TEXT]… [--recycle]
38
38
  ghosty agents demo enable|disable <id>
39
39
  ghosty agents demo <id> [--slug NAME] [--expires "YYYY-MM-DDTHH:MM"|no] [--turns N] [--per-ip N]
40
40
  [--welcome TEXT] [--starter TEXT] [--chips "a|b|c"] [--sponsor on|off] [--rotate] # public /p/ link, no account needed
41
+ ghosty agents embed enable|disable <id> [--domains a.com,*.b.com] # web widget: chat bubble on your site, only on those domains
42
+ ghosty agents embed <id> [--per-visitor N] [--per-ip N] [--welcome TEXT] [--color #hex] [--rotate] # state + the <script> to paste
43
+ ghosty agents embed <id> [--title T] [--avatar URL] [--icon chat|avatar|ghost] [--position left] [--theme auto] [--bubble-text T] [--auto-open S] [--starters "a|b"] # its look
44
+ ghosty agents embed <id> [--style '{json}'] [--theme-source teams|site --site-url URL] [--style-file theme.json] # its theme · `theme` exports it
41
45
  ghosty agents doctor <id> [--verbose] # exits 1 when it finds a problem
42
46
  ghosty agents docs ls|grant|revoke <id> [doc] [--write] [--no-deliver]
43
47
  ghosty agents docs add <id> --url URL | --file PATH [--description TEXT] [--grant read|write|no]
@@ -103,6 +107,16 @@ Every command has its own help with examples: `ghosty <command> --help` or `ghos
103
107
 
104
108
  Exit codes: `0` ok · `1` API error · `2` usage error · `3` not signed in.
105
109
 
110
+ ### Changes in 0.52.0
111
+
112
+ - `agents embed <id>` customizes the web widget: `--title`, `--subtitle`, `--avatar URL|none`, `--icon chat|avatar|ghost`, `--placeholder`, `--position right|left`, `--offset X[,Y]`, `--theme light|dark|auto`, `--bubble-text`, `--auto-open S|off`, `--starters "a|b"` (up to 4 of yours; Ghosty's never show). The pasted `<script>` doesn't change.
113
+ - Fine style without free CSS: `--style '{json}'|none` (fontFamily from a Google Fonts allow-list, radius 0–24, header/bubble/input/send/launcher colors, titleUppercase, statusDot, sendArrow), shortcuts `--font` and `--radius`.
114
+ - Theme source: `--theme-source teams` (your Ghosty Teams workspace brand, `--workspace SLUG`) or `--theme-source site --site-url URL` (your site's CSS colors), imported as editable values. `agents embed <id> theme` exports it as JSON; `--style-file theme.json` applies it back.
115
+
116
+ ### Changes in 0.51.0
117
+
118
+ - `agents embed enable|disable <id> [--domains a.com,*.b.com] [--per-visitor N] [--per-ip N] [--welcome T] [--color #hex] [--rotate]`: the **web widget**, a chat bubble with the agent on your own site (one `<script>`), loading only on the domains you list; each visitor gets an isolated conversation and talks as a customer. Claude engine only. No flags = state + the snippet.
119
+
106
120
  ### Changes in 0.50.0
107
121
 
108
122
  - `cards pause|resume <id> <card> [--mode 30min|2h|24h|until_tomorrow|permanent]`: the thread's pause menu; resume also lifts "operator only" and doesn't answer by itself. Logged.
package/dist/index.js CHANGED
@@ -8164,7 +8164,12 @@ var SERVER_MESSAGES = [
8164
8164
  ["s\xF3lo el due\xF1o de \xAB{}\xBB o el staff de Ghosty leen su bit\xE1cora", "only \xAB{}\xBB's owner or Ghosty staff read its audit log"],
8165
8165
  ["s\xF3lo aplica a conversaciones de WhatsApp Business", "only applies to WhatsApp Business conversations"],
8166
8166
  ["ese n\xFAmero ya no est\xE1 conectado", "that number is no longer connected"],
8167
- ["ese n\xFAmero no es de este tablero", "that number isn't on this board"]
8167
+ ["ese n\xFAmero no es de este tablero", "that number isn't on this board"],
8168
+ // — Widget web (`agents embed`)
8169
+ ["s\xF3lo el due\xF1o del agente maneja su widget web", "only the agent's owner manages its web widget"],
8170
+ ["dominio no v\xE1lido: {} (usa ejemplo.com o *.ejemplo.com, sin https:// ni rutas)", "invalid domain: {} (use example.com or *.example.com, no https:// or paths)"],
8171
+ ["el widget web s\xF3lo corre con motor claude (es el que a\xEDsla a cada visitante)", "the web widget only runs on the claude engine (it's the one that isolates each visitor)"],
8172
+ ["pon al menos un dominio donde va a vivir el widget (--domains ejemplo.com)", "add at least one domain where the widget will live (--domains example.com)"]
8168
8173
  ];
8169
8174
 
8170
8175
  // src/i18n.ts
@@ -8463,7 +8468,7 @@ async function postLong(path, body, timeoutMs = 9e5) {
8463
8468
  const req = httpsRequest(`${base}${path}`, {
8464
8469
  method: "POST",
8465
8470
  timeout: timeoutMs,
8466
- headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.50.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
8471
+ headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.52.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
8467
8472
  }, (res) => {
8468
8473
  let buf = "";
8469
8474
  res.on("data", (c) => buf += c);
@@ -8505,7 +8510,7 @@ async function request(path, opts = {}, retried = false) {
8505
8510
  method: opts.method ?? (opts.body !== void 0 || opts.raw !== void 0 ? "POST" : "GET"),
8506
8511
  headers: {
8507
8512
  authorization: `Bearer ${token}`,
8508
- "user-agent": `ghosty-cli/${"0.50.0"}`,
8513
+ "user-agent": `ghosty-cli/${"0.52.0"}`,
8509
8514
  "accept-language": lang(),
8510
8515
  ...opts.body !== void 0 ? { "content-type": "application/json" } : {},
8511
8516
  ...opts.headers
@@ -9339,6 +9344,101 @@ async function agentsDemo(id, v) {
9339
9344
  ].join("\n")
9340
9345
  );
9341
9346
  }
9347
+ function styleSummary(st) {
9348
+ if (!st) return "";
9349
+ return Object.entries(st).filter(([k, x]) => x !== null && !(k === "headerAvatar" && x === true) && !(k === "titleUppercase" && x === false) && !(k === "sendArrow" && x === "up")).map(([k, x]) => `${k}=${x}`).join(" ");
9350
+ }
9351
+ async function agentsEmbed(id, v, action) {
9352
+ id = need(id, "agent id", "ghosty agents embed enable <agent-id> --domains tusitio.com");
9353
+ if (action === "theme") {
9354
+ const e2 = await api(agentPath(id, "/embed"));
9355
+ const theme = { themeSource: e2.themeSource ?? "custom", themeFrom: e2.themeFrom ?? null, siteUrl: e2.siteUrl ?? null, color: e2.color, theme: e2.theme ?? "light", style: e2.style ?? {} };
9356
+ return out(theme, () => JSON.stringify(theme, null, 2));
9357
+ }
9358
+ if (action) throw usage(t(`Unknown: agents embed <id> ${action}`, `No existe: agents embed <id> ${action}`), "ghosty agents embed <id> theme");
9359
+ if (v.on && v.off) throw usage(t("Use enable or disable, not both.", "Usa enable o disable, no los dos."));
9360
+ const body = {};
9361
+ if (v.on) body.enabled = true;
9362
+ if (v.off) body.enabled = false;
9363
+ if (v.domains !== void 0) body.domains = v.domains.split(/[\s,]+/).map((d) => d.trim()).filter(Boolean);
9364
+ if (v["per-visitor"]) body.perVisitorDay = Number(v["per-visitor"]);
9365
+ if (v["per-ip"]) body.perIpDay = Number(v["per-ip"]);
9366
+ if (v.welcome !== void 0) body.welcome = v.welcome || null;
9367
+ if (v.color !== void 0) body.color = v.color;
9368
+ const clear = (x) => x === "" || x.toLowerCase() === "none" ? null : x;
9369
+ if (v.avatar !== void 0) body.avatarUrl = clear(v.avatar);
9370
+ if (v.title !== void 0) body.title = clear(v.title);
9371
+ if (v.subtitle !== void 0) body.subtitle = clear(v.subtitle);
9372
+ if (v.placeholder !== void 0) body.placeholder = clear(v.placeholder);
9373
+ if (v["bubble-text"] !== void 0) body.bubbleText = clear(v["bubble-text"]);
9374
+ if (v.icon !== void 0) body.icon = v.icon;
9375
+ if (v.position !== void 0) body.position = v.position;
9376
+ if (v.theme !== void 0) body.theme = v.theme;
9377
+ if (v.offset !== void 0) {
9378
+ const [x, y] = v.offset.split(/[,x ]+/).map((n) => Number(n));
9379
+ if (!Number.isFinite(x)) throw usage(t("--offset takes px: 20 or 20,40 (side,bottom)", "--offset va en px: 20 o 20,40 (lado,abajo)"));
9380
+ body.offsetX = x;
9381
+ body.offsetY = Number.isFinite(y) ? y : x;
9382
+ }
9383
+ if (v["auto-open"] !== void 0) body.autoOpen = /^(off|no|0|)$/i.test(v["auto-open"]) ? 0 : Number(v["auto-open"]);
9384
+ if (v.starters !== void 0) body.starters = v.starters.split("|").map((x) => x.trim()).filter(Boolean);
9385
+ if (v.style !== void 0) {
9386
+ if (v.style.toLowerCase() === "none") body.style = null;
9387
+ else {
9388
+ try {
9389
+ body.style = JSON.parse(v.style);
9390
+ } catch {
9391
+ throw usage(t(`--style takes JSON: '{"headerBg":"#2d4a2e","radius":0}'`, `--style va en JSON: '{"headerBg":"#2d4a2e","radius":0}'`));
9392
+ }
9393
+ }
9394
+ }
9395
+ if (v["style-file"]) {
9396
+ let j;
9397
+ try {
9398
+ j = JSON.parse(await readFile3(v["style-file"], "utf8"));
9399
+ } catch (err) {
9400
+ throw usage(t(`Could not read ${v["style-file"]} as JSON: ${err.message}`, `No pude leer ${v["style-file"]} como JSON: ${err.message}`));
9401
+ }
9402
+ const isTheme = j && typeof j === "object" && "style" in j;
9403
+ body.style = isTheme ? j.style : j;
9404
+ if (isTheme && typeof j.color === "string" && body.color === void 0) body.color = j.color;
9405
+ if (isTheme && typeof j.theme === "string" && body.theme === void 0) body.theme = j.theme;
9406
+ }
9407
+ if (v["theme-source"] !== void 0) body.themeSource = v["theme-source"];
9408
+ if (v["site-url"] !== void 0) {
9409
+ body.siteUrl = v["site-url"];
9410
+ if (body.themeSource === void 0) body.themeSource = "site";
9411
+ }
9412
+ if (body.themeSource === "teams" && v.workspace) body.teamsWorkspace = v.workspace;
9413
+ if (v.font !== void 0 || v.radius !== void 0) {
9414
+ const st = body.style && typeof body.style === "object" ? body.style : {};
9415
+ if (v.font !== void 0) st.fontFamily = clear(v.font);
9416
+ if (v.radius !== void 0) st.radius = v.radius === "" || v.radius === "none" ? null : Number(v.radius);
9417
+ body.style = st;
9418
+ }
9419
+ if (v.rotate) body.rotate = true;
9420
+ const e = Object.keys(body).length ? await api(agentPath(id, "/embed"), { method: "PUT", body }) : await api(agentPath(id, "/embed"));
9421
+ out(
9422
+ e,
9423
+ () => [
9424
+ `${e.enabled ? lavender(t("\u25CF on", "\u25CF encendido")) : dim(t("\u25CB off", "\u25CB apagado"))} ${t("web widget", "widget web")}`,
9425
+ e.domains.length ? `${t("domains", "dominios")}: ${e.domains.join(", ")}` : red(t("domains: (none \u2014 it loads nowhere; add --domains yoursite.com)", "dominios: (ninguno \u2014 no carga en ning\xFAn lado; agrega --domains tusitio.com)")),
9426
+ ...e.engineOk ? [] : [red(t("engine: only claude isolates each visitor; switch with `ghosty agents set <id> --engine claude`", "motor: s\xF3lo claude a\xEDsla a cada visitante; c\xE1mbialo con `ghosty agents set <id> --engine claude`"))],
9427
+ dim(t(`cap ${e.perVisitorDay} messages per visitor per day \xB7 ${e.perIpDay} per IP per day \xB7 color ${e.color}`, `tope ${e.perVisitorDay} mensajes por visitante al d\xEDa \xB7 ${e.perIpDay} por IP al d\xEDa \xB7 color ${e.color}`)),
9428
+ `${t("first message", "primer mensaje")}: ${e.welcome ?? dim(e.welcomeShown ?? t("(default greeting)", "(saludo por defecto)"))}`,
9429
+ `${t("header", "encabezado")}: ${e.title ?? e.agentName ?? ""}${e.subtitle ? ` \xB7 ${e.subtitle}` : ""} ${dim(`${t("avatar", "avatar")} ${e.avatarUrl ?? t("ghost", "fantasma")} \xB7 ${t("icon", "\xEDcono")} ${e.icon ?? "chat"}`)}`,
9430
+ dim(
9431
+ `${t("position", "lado")} ${e.position ?? "right"} (${e.offsetX ?? 20},${e.offsetY ?? 20}px) \xB7 ${t("theme", "tema")} ${e.theme ?? "light"} \xB7 ${t("text on color", "texto sobre el color")} ${e.textColor ?? ""} \xB7 ${t("auto-open", "abrir solo")} ${e.autoOpen ? `${e.autoOpen}s` : "off"}${e.bubbleText ? ` \xB7 ${t("label", "etiqueta")} \xAB${e.bubbleText}\xBB` : ""}${e.placeholder ? ` \xB7 placeholder \xAB${e.placeholder}\xBB` : ""}`
9432
+ ),
9433
+ e.starters?.length ? `${t("suggestions", "sugerencias")}: ${e.starters.join(" | ")}` : dim(t("suggestions: (none)", "sugerencias: (ninguna)")),
9434
+ `${t("theme", "tema")}: ${e.themeSource ?? "custom"}${e.themeFrom ? dim(` \xB7 ${e.themeFrom}`) : ""}`,
9435
+ ...styleSummary(e.style) ? [dim(`${t("style", "estilo")}: ${styleSummary(e.style)}`)] : [],
9436
+ ...e.snippet ? ["", t("Paste before </body> on your site:", "Pega antes de </body> en tu sitio:"), e.snippet, ""] : [],
9437
+ t(`visitors ${e.stats.visitantes} \xB7 messages ${e.stats.mensajes}`, `visitantes ${e.stats.visitantes} \xB7 mensajes ${e.stats.mensajes}`),
9438
+ ...e.lastError ? [red(`${t("last failure", "\xFAltima falla")} ${e.lastError.at}: ${serverText(e.lastError.message ?? e.lastError.error ?? "")}`)] : []
9439
+ ].join("\n")
9440
+ );
9441
+ }
9342
9442
  async function agentsBox(id, v) {
9343
9443
  id = need(id, "agent id", "ghosty agents box <agent-id> [--check PATH] [--contains PATH=TEXT] [--recycle]");
9344
9444
  if (v.recycle) {
@@ -9950,7 +10050,7 @@ async function call(path, v, init = {}) {
9950
10050
  "x-ghosty-ts": ts,
9951
10051
  "x-ghosty-sig": sig,
9952
10052
  "content-type": "application/json",
9953
- "user-agent": `ghosty-cli/${"0.50.0"}`
10053
+ "user-agent": `ghosty-cli/${"0.52.0"}`
9954
10054
  },
9955
10055
  body: method === "GET" ? void 0 : raw
9956
10056
  });
@@ -11555,6 +11655,10 @@ var HELPS_ES = {
11555
11655
  summary: "Crea, revisa, cambia, diagnostica y borra agentes.",
11556
11656
  replace: [
11557
11657
  ["# the agent as a file; see ghosty apply --help", "# el agente como archivo; ver ghosty apply --help"],
11658
+ ["# its look", "# su apariencia"],
11659
+ ["# its theme", "# su tema"],
11660
+ ["# export the theme as JSON (edit it, apply it back with --style-file)", "# exporta el tema en JSON (ed\xEDtalo y vuelve a aplicarlo con --style-file)"],
11661
+ ["--style-file theme.json", "--style-file tema.json"],
11558
11662
  ["# who is @build in Teams (that workspace, or all of yours)", "# qui\xE9n es @build en Teams (en ese espacio, o en todos los tuyos)"],
11559
11663
  ["# change one piece of the prompt, on the server's current one", "# cambia un pedazo del prompt, sobre el actual del servidor"],
11560
11664
  ["# your account: merge copies with the same content", "# tu cuenta: une las copias con el mismo contenido"],
@@ -11563,7 +11667,9 @@ var HELPS_ES = {
11563
11667
  ["# Teams workspaces you administer: where it answers, is paused, and with which @handle", "# los equipos de Teams que administras: d\xF3nde contesta, d\xF3nde est\xE1 en pausa y con qu\xE9 @handle"],
11564
11668
  ["# turn it on there (or change its @handle)", "# lo prende ah\xED (o le cambia el @handle)"],
11565
11669
  ["# pause it there; it keeps its @handle", "# lo pausa ah\xED; conserva su @handle"],
11566
- ["# remove it from there; the agent stays", "# lo quita de ah\xED; el agente sigue"]
11670
+ ["# remove it from there; the agent stays", "# lo quita de ah\xED; el agente sigue"],
11671
+ ["# web widget: chat bubble on your site, only on those domains", "# widget web: burbuja de chat en tu sitio, s\xF3lo en esos dominios"],
11672
+ ["# state + the <script> to paste", "# su estado + el <script> para pegar"]
11567
11673
  ],
11568
11674
  flags: [
11569
11675
  ["--fields a,b", "get: regresa s\xF3lo estos campos"],
@@ -11606,6 +11712,22 @@ var HELPS_ES = {
11606
11712
  ['--chips "a|b|c"', "demo: sugerencias, separadas por |"],
11607
11713
  ["--sponsor on|off", "demo: las copias que la gente se queda corren con TU llave del motor"],
11608
11714
  ["--rotate", "demo: c\xF3digo nuevo; la liga vieja deja de servir"],
11715
+ ["--domains a.com,*.b.com", "embed: d\xF3nde puede cargarse el widget (vac\xEDo = en ning\xFAn lado)"],
11716
+ ["--welcome TEXT", "embed: el primer mensaje que ve el visitante (vac\xEDo = \xAB\xA1Hola! \xBFEn qu\xE9 te ayudo?\xBB)"],
11717
+ ["--title T / --subtitle T", "embed: nombre en el encabezado del widget (default: el del agente) y un rengl\xF3n chico debajo (\xABResponde en minutos\xBB)"],
11718
+ ["--avatar URL|none", "embed: tu imagen (https JPEG/PNG/WebP, se rehospeda en img.ghosty.studio); none = el fantasma del agente"],
11719
+ ["--icon chat|avatar|ghost", "embed: qu\xE9 lleva el bot\xF3n flotante"],
11720
+ ["--placeholder T", "embed: el texto gris de la caja para escribir"],
11721
+ ["--position right|left / --offset X[,Y]", "embed: esquina de la p\xE1gina y separaci\xF3n en px del lado y de abajo (0-200, default 20)"],
11722
+ ["--theme light|dark|auto", "embed: tema del panel del chat (auto = el del visitante)"],
11723
+ ["--bubble-text T", "embed: etiqueta junto al bot\xF3n (\xAB\xBFDudas?\xBB)"],
11724
+ ["--auto-open S|off", "embed: se abre solo a los S segundos (1-120), una vez por visita, nunca en el tel\xE9fono"],
11725
+ ['--starters "a|b|c"', 'embed: hasta 4 sugerencias TUYAS ("" las quita; nunca salen las de Ghosty)'],
11726
+ ["--style '{json}'|none", "embed: estilo fino, s\xF3lo llaves conocidas: fontFamily, radius, headerBg, headerText, titleUppercase, statusDot, headerAvatar, panelBg, userBubbleBg/Text, agentBubbleBg/Text, inputBg, sendBg, sendIcon, sendArrow (up|right), launcherBg, launcherIcon. PATCH por llave (null resetea una); none resetea todo"],
11727
+ ["--font NOMBRE / --radius 0-24", "embed: atajos de style.fontFamily (lista de Google Fonts: Barlow, Inter, Poppins\u2026) y style.radius"],
11728
+ ["--theme-source custom|teams|site", "embed: importa el tema de la marca de tu espacio de Ghosty Teams (--workspace SLUG para elegir) o de tu sitio (--site-url); queda editable"],
11729
+ ["--site-url URL", "embed: el sitio del que se sacan los colores (implica --theme-source site)"],
11730
+ ["--style-file ARCHIVO", "embed: aplica un tema exportado con `agents embed <id> theme` (o s\xF3lo el objeto style)"],
11609
11731
  ["--check PATH", "box: este archivo debe existir en la m\xE1quina (repetible), p. ej. un archivo que trae la imagen nueva"],
11610
11732
  ["--contains PATH=TEXT", "box: ese archivo debe contener TEXT (un cambio que no agrega archivos)"],
11611
11733
  ["--recycle", "box: reemplaza sus m\xE1quinas (nunca a media respuesta); pool: la nueva nace en el siguiente turno; ACP (goose, lite): su caja se recrea ya con la imagen actual"],
@@ -12065,6 +12187,11 @@ var HELPS = {
12065
12187
  "ghosty agents box <agent-id> [--check PATH]\u2026 [--contains PATH=TEXT]\u2026 [--recycle]",
12066
12188
  "ghosty agents demo enable|disable <agent-id>",
12067
12189
  'ghosty agents demo <agent-id> [--slug NAME] [--expires "YYYY-MM-DDTHH:MM"|no] [--turns N] [--per-ip N] [--welcome TEXT] [--starter TEXT] [--chips "a|b|c"] [--sponsor on|off] [--rotate]',
12190
+ "ghosty agents embed enable|disable <agent-id> [--domains a.com,*.b.com] # web widget: chat bubble on your site, only on those domains",
12191
+ "ghosty agents embed <agent-id> [--domains \u2026] [--per-visitor N] [--per-ip N] [--welcome TEXT] [--color #hex] [--rotate] # state + the <script> to paste",
12192
+ 'ghosty agents embed <agent-id> [--title T] [--subtitle T] [--avatar URL] [--icon chat|avatar|ghost] [--placeholder T] [--position right|left] [--offset X,Y] [--theme light|dark|auto] [--bubble-text T] [--auto-open S] [--starters "a|b"] # its look',
12193
+ "ghosty agents embed <agent-id> [--theme-source custom|teams|site] [--site-url URL] [--style '{json}'] [--style-file theme.json] [--font NAME] [--radius 0-24] # its theme",
12194
+ "ghosty agents embed <agent-id> theme # export the theme as JSON (edit it, apply it back with --style-file)",
12068
12195
  "ghosty agents rm <agent-id> [--from-workspace] [--yes]"
12069
12196
  ],
12070
12197
  flags: [
@@ -12108,6 +12235,22 @@ var HELPS = {
12108
12235
  ['--chips "a|b|c"', "demo: suggestions, separated by |"],
12109
12236
  ["--sponsor on|off", "demo: copies people keep run on YOUR engine key"],
12110
12237
  ["--rotate", "demo: new code; the old link stops working"],
12238
+ ["--domains a.com,*.b.com", "embed: where the widget may load (empty = nowhere)"],
12239
+ ["--welcome TEXT", "embed: the first message the visitor sees (empty = \xAB\xA1Hola! \xBFEn qu\xE9 te ayudo?\xBB)"],
12240
+ ["--title T / --subtitle T", `embed: name in the widget header (default: the agent's name) and a small line under it ("Replies in minutes")`],
12241
+ ["--avatar URL|none", "embed: your own image (https JPEG/PNG/WebP, re-hosted on img.ghosty.studio); none = the agent's ghost"],
12242
+ ["--icon chat|avatar|ghost", "embed: what the floating button shows"],
12243
+ ["--placeholder T", "embed: grey text in the message box"],
12244
+ ["--position right|left / --offset X[,Y]", "embed: corner of the page and distance in px from the side and the bottom (0-200, default 20)"],
12245
+ ["--theme light|dark|auto", "embed: the chat panel's theme (auto = the visitor's)"],
12246
+ ["--bubble-text T", 'embed: label next to the button ("Questions?")'],
12247
+ ["--auto-open S|off", "embed: open by itself after S seconds (1-120), once per visit, never on phones"],
12248
+ ['--starters "a|b|c"', `embed: up to 4 of YOUR suggestions ("" removes them; Ghosty's are never shown)`],
12249
+ ["--style '{json}'|none", "embed: fine style, only known keys: fontFamily, radius, headerBg, headerText, titleUppercase, statusDot, headerAvatar, panelBg, userBubbleBg/Text, agentBubbleBg/Text, inputBg, sendBg, sendIcon, sendArrow (up|right), launcherBg, launcherIcon. Patch per key (null resets one); none resets all"],
12250
+ ["--font NAME / --radius 0-24", "embed: shortcuts for style.fontFamily (Google Fonts allow-list: Barlow, Inter, Poppins\u2026) and style.radius"],
12251
+ ["--theme-source custom|teams|site", "embed: import the theme from the brand of your Ghosty Teams workspace (--workspace SLUG to pick one) or from your site (--site-url); it stays editable"],
12252
+ ["--site-url URL", "embed: the site to take colors from (implies --theme-source site)"],
12253
+ ["--style-file FILE", "embed: apply a theme exported with `agents embed <id> theme` (or a plain style object)"],
12111
12254
  ["--check PATH", "box: this file must exist in the machine (repeatable), e.g. a file the new image brings"],
12112
12255
  ["--contains PATH=TEXT", "box: that file must contain TEXT (a change that adds no files)"],
12113
12256
  ["--recycle", "box: replace its machines (not mid-turn); pool: the new one starts on the next turn; ACP (goose, lite): its box is recreated now with the current image"],
@@ -12143,6 +12286,10 @@ var HELPS = {
12143
12286
  "ghosty agents set <pia-id> --prompt-base <tania-id> --prompt-overlay capa.json --dry-run --out /tmp/pia.md",
12144
12287
  "ghosty agents demo enable <id>",
12145
12288
  'ghosty agents demo <id> --slug guardias --expires "2026-10-15T20:00" --starter "Ens\xE9\xF1ame c\xF3mo empezar"',
12289
+ 'ghosty agents embed enable <id> --domains "mitienda.com,*.mitienda.mx" --welcome "\xA1Hola! \xBFEn qu\xE9 te ayudo?"',
12290
+ 'ghosty agents embed <id> --title "Palmera Legal" --subtitle "Responde en minutos" --position left --bubble-text "\xBFDudas?" --starters "\xBFCu\xE1nto cuesta una consulta?|\xBFD\xF3nde est\xE1n?"',
12291
+ "ghosty agents embed <id> --theme-source site --site-url https://palmeralegal.mx",
12292
+ "ghosty agents embed <id> theme > tema.json \xB7 ghosty agents embed <id> --style-file tema.json",
12146
12293
  "ghosty agents box <id> --contains /opt/claude-worker/skills/pdf-doc/render-doc.mjs=pdf-templates",
12147
12294
  "ghosty agents set <id> --tool wa:actualizar_identidad=off,teams:actualizar_identidad=on",
12148
12295
  'ghosty agents set minighosty --bag "MiniGhosty" --avatar https://www.ghosty.studio/avatars/minighosty.png',
@@ -12989,6 +13136,10 @@ function normalizeArgs(pos, v) {
12989
13136
  if (v.expires !== void 0) v.vence = v.expires;
12990
13137
  else if (v.vence !== void 0) notes.push(t("`--vence` is now `--expires`.", "`--vence` ahora es `--expires`."));
12991
13138
  }
13139
+ if (cmd === "agents" && (sub === "embed" || sub === "widget") && (pos[2] === "enable" || pos[2] === "disable")) {
13140
+ v[pos[2] === "enable" ? "on" : "off"] = true;
13141
+ pos.splice(2, 1);
13142
+ }
12992
13143
  if (cmd === "channels" && pos[2] === "groups") {
12993
13144
  if (pos[3] === "enable" || pos[3] === "disable") {
12994
13145
  v[pos[3] === "enable" ? "on" : "off"] = true;
@@ -13965,6 +14116,12 @@ ${bold("Agents")}
13965
14116
  agents box <id> The machine serving it: is it on the new image? [--check PATH]\u2026 [--contains PATH=TEXT]\u2026 [--recycle [--reenv] [--force]]
13966
14117
  agents demo enable|disable <id> Turn its public demo link on or off
13967
14118
  agents demo <id> Public demo link /p/<slug>: anyone tries it without an account, "Keep it" clones it to theirs
14119
+ agents embed enable|disable <id> Web widget: a chat bubble on YOUR site (one <script>), only on the domains you list [--domains a.com,*.b.com] [--per-visitor N] [--per-ip N] [--welcome T] [--color #hex] [--rotate]
14120
+ look: [--title T] [--subtitle T] [--avatar URL|none] [--icon chat|avatar|ghost] [--placeholder T] [--position right|left] [--offset X[,Y]]
14121
+ [--theme light|dark|auto] [--bubble-text T] [--auto-open SECONDS|off] [--starters "a|b|c"] [--font NAME] [--radius 0-24] [--style '{json}'|none]
14122
+ theme: [--theme-source custom|teams|site] [--workspace SLUG] [--site-url URL] [--style-file theme.json]
14123
+ agents embed <id> theme Export the widget theme as JSON (edit it and apply it back with --style-file)
14124
+ agents embed <id> Its state and the snippet to paste (claude engine only: it isolates each visitor)
13968
14125
  [--slug NAME] [--expires "YYYY-MM-DDTHH:MM"|no] [--turns N] [--per-ip N] [--welcome TEXT] [--chips "a|b|c"] [--starter TEXT] [--sponsor on|off] [--rotate]
13969
14126
 
13970
14127
  ${bold("Configure an agent")}
@@ -14121,6 +14278,12 @@ ${bold("Agentes")}
14121
14278
  agents box <id> La m\xE1quina que lo atiende: \xBFya trae la imagen nueva? [--check RUTA]\u2026 [--contains RUTA=TEXTO]\u2026 [--recycle [--reenv] [--force]]
14122
14279
  agents demo enable|disable <id> Prende o apaga su liga p\xFAblica de demo
14123
14280
  agents demo <id> Liga p\xFAblica de demo /p/<slug>: cualquiera lo prueba sin cuenta y \xABQued\xE1rmelo\xBB lo clona a la suya
14281
+ agents embed enable|disable <id> Widget web: burbuja de chat en TU sitio (un <script>), s\xF3lo en los dominios que pongas [--domains a.com,*.b.com] [--per-visitor N] [--per-ip N] [--welcome T] [--color #hex] [--rotate]
14282
+ look: [--title T] [--subtitle T] [--avatar URL|none] [--icon chat|avatar|ghost] [--placeholder T] [--position right|left] [--offset X[,Y]]
14283
+ [--theme light|dark|auto] [--bubble-text T] [--auto-open SEGUNDOS|off] [--starters "a|b|c"] [--font NOMBRE] [--radius 0-24] [--style '{json}'|none]
14284
+ tema: [--theme-source custom|teams|site] [--workspace SLUG] [--site-url URL] [--style-file tema.json]
14285
+ agents embed <id> theme Exporta el tema del widget en JSON (ed\xEDtalo y vuelve a aplicarlo con --style-file)
14286
+ agents embed <id> Su estado y el snippet para pegar (s\xF3lo motor claude: a\xEDsla a cada visitante)
14124
14287
  [--slug NOMBRE] [--expires "AAAA-MM-DDTHH:MM"|no] [--turns N] [--per-ip N] [--welcome TEXTO] [--chips "a|b|c"] [--starter TEXTO] [--sponsor on|off] [--rotate]
14125
14288
 
14126
14289
  ${bold("Configurar un agente")}
@@ -14214,7 +14377,7 @@ ${bold("C\xF3digos de salida")} 0 bien \xB7 1 error de la API \xB7 2 uso incorr
14214
14377
  ${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.")}
14215
14378
  ${dim("Documentaci\xF3n: https://www.ghosty.studio/docs/cli")}`;
14216
14379
  var COMMANDS = ["login", "logout", "whoami", "usage", "use", "apply", "plan", "engines", "bags", "spaces", "workspaces", "staff", "audit", "factory", "demos", "sites", "push", "novedades", "completion", "cards", "agents", "files", "skills", "mcp", "dbs", "turns", "try", "chat", "conversations", "schedule", "partner", "me", "channels", "board", "boards", "credentials", "connectors", "browser", "docs", "help"];
14217
- var AGENTS_SUBS = ["ls", "create", "get", "set", "export", "doctor", "docs", "share", "unshare", "shares", "teams", "rm", "restart", "demo", "box"];
14380
+ var AGENTS_SUBS = ["ls", "create", "get", "set", "export", "doctor", "docs", "share", "unshare", "shares", "teams", "rm", "restart", "demo", "embed", "box"];
14218
14381
  function unknown(what, input, known, helpCmd) {
14219
14382
  const s = suggest(input, known);
14220
14383
  return usage(t(`Unknown command: ${what}.`, `Comando desconocido: ${what}.`), s ? t(`Did you mean "${s}"? Run: ${helpCmd}`, `\xBFQuisiste decir "${s}"? Corre: ${helpCmd}`) : t(`Run: ${helpCmd}`, `Corre: ${helpCmd}`));
@@ -14296,10 +14459,28 @@ async function main() {
14296
14459
  turns: { type: "string" },
14297
14460
  vence: { type: "string" },
14298
14461
  "per-ip": { type: "string" },
14462
+ "per-visitor": { type: "string" },
14463
+ domains: { type: "string" },
14464
+ color: { type: "string" },
14299
14465
  welcome: { type: "string" },
14300
14466
  chips: { type: "string" },
14301
14467
  starter: { type: "string" },
14302
14468
  rotate: { type: "boolean" },
14469
+ subtitle: { type: "string" },
14470
+ placeholder: { type: "string" },
14471
+ position: { type: "string" },
14472
+ offset: { type: "string" },
14473
+ theme: { type: "string" },
14474
+ icon: { type: "string" },
14475
+ "bubble-text": { type: "string" },
14476
+ "auto-open": { type: "string" },
14477
+ starters: { type: "string" },
14478
+ style: { type: "string" },
14479
+ font: { type: "string" },
14480
+ radius: { type: "string" },
14481
+ "style-file": { type: "string" },
14482
+ "theme-source": { type: "string" },
14483
+ "site-url": { type: "string" },
14303
14484
  sponsor: { type: "string" },
14304
14485
  yes: { type: "boolean", short: "y" },
14305
14486
  dir: { type: "string" },
@@ -14452,7 +14633,7 @@ async function main() {
14452
14633
  const tokens = splitTokens(pos, v);
14453
14634
  globals.token = tokens.session;
14454
14635
  globals.profile = v.profile;
14455
- if (v.version) return out({ version: "0.50.0" }, () => "0.50.0");
14636
+ if (v.version) return out({ version: "0.52.0" }, () => "0.52.0");
14456
14637
  for (const n of normalizeArgs(pos, v)) if (!globals.json) info(dim(n));
14457
14638
  const context = (await readConfig()).workspace;
14458
14639
  if (!explicitToken()?.startsWith("gat_")) {
@@ -14520,6 +14701,7 @@ async function main() {
14520
14701
  if (sub === "docs" || sub === "documents") return agentDocsCmd(a1, a2, pos.slice(4).filter((x) => x !== void 0), v);
14521
14702
  if (sub === "restart") return agentsRestart(a1);
14522
14703
  if (sub === "demo") return agentsDemo(a1, v);
14704
+ if (sub === "embed" || sub === "widget") return agentsEmbed(a1, v, a2);
14523
14705
  if (sub === "box") return agentsBox(a1, v);
14524
14706
  if (sub === "export") return agentsExport(a1, v);
14525
14707
  throw unknown(`agents ${sub}`, sub, AGENTS_SUBS, "ghosty agents --help");
@@ -14617,7 +14799,7 @@ async function main() {
14617
14799
  throw unknown(cmd, cmd, COMMANDS, "ghosty --help");
14618
14800
  }
14619
14801
  }
14620
- var notice = updateNotice("0.50.0").catch(() => null);
14802
+ var notice = updateNotice("0.52.0").catch(() => null);
14621
14803
  main().then(
14622
14804
  async () => {
14623
14805
  const n = await notice;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghostystudio/cli",
3
- "version": "0.50.0",
3
+ "version": "0.52.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": {