@ghostystudio/cli 0.53.0 → 0.54.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 +101 -12
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -42,6 +42,7 @@ ghosty agents embed enable|disable <id> [--domains a.com,*.b.com] # web widget
42
42
  ghosty agents embed <id> [--per-visitor N] [--per-ip N] [--welcome TEXT] [--color #hex] [--rotate] # state + the <script> to paste
43
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
44
  ghosty agents embed <id> [--style '{json}'] [--theme-source teams|site --site-url URL] [--style-file theme.json] # its theme · `theme` exports it
45
+ ghosty agents embed <id> [--copy '{"waking":"One moment, please","thinking":["Reviewing your question…"]}'|none] [--copy-file texts.json] # what it says while it works
45
46
  ghosty agents embed <id> --out src/components/GhostyChat.tsx [--force] # your own React chat component (no deps), token + theme baked in · the output also prints the React snippet (`react` in --json)
46
47
  ghosty agents doctor <id> [--verbose] # exits 1 when it finds a problem
47
48
  ghosty agents docs ls|grant|revoke <id> [doc] [--write] [--no-deliver]
@@ -108,6 +109,14 @@ Every command has its own help with examples: `ghosty <command> --help` or `ghos
108
109
 
109
110
  Exit codes: `0` ok · `1` API error · `2` usage error · `3` not signed in.
110
111
 
112
+ ### Changes in 0.54.0
113
+
114
+ - `agents embed <id> --copy '{json}'|none` and `--copy-file texts.json`: the phrases the web widget shows while the agent works (`waking`, `connecting`, `thinking` — 1–8 that rotate —, `stillWorking`, `cantStart`, `interrupted`, `retryLabel`, plain text ≤ 80 chars) and whether it shows the duration and the Copy button (`showDuration`, `showCopyButton`). Patch per key; `null` = Ghosty's phrase. `agents embed <id> theme` exports `copy` too, and `--out` bakes it into the component.
115
+
116
+ ### Changes in 0.53.0
117
+
118
+ - `agents embed` prints the React snippet (`react` in `--json`) and `--out <file.jsx|.tsx> [--force]` writes your own dependency-free chat component with your token and theme.
119
+
111
120
  ### Changes in 0.52.0
112
121
 
113
122
  - `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.
package/dist/index.js CHANGED
@@ -8468,7 +8468,7 @@ async function postLong(path, body, timeoutMs = 9e5) {
8468
8468
  const req = httpsRequest(`${base}${path}`, {
8469
8469
  method: "POST",
8470
8470
  timeout: timeoutMs,
8471
- headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.53.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
8471
+ headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.54.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
8472
8472
  }, (res) => {
8473
8473
  let buf = "";
8474
8474
  res.on("data", (c) => buf += c);
@@ -8510,7 +8510,7 @@ async function request(path, opts = {}, retried = false) {
8510
8510
  method: opts.method ?? (opts.body !== void 0 || opts.raw !== void 0 ? "POST" : "GET"),
8511
8511
  headers: {
8512
8512
  authorization: `Bearer ${token}`,
8513
- "user-agent": `ghosty-cli/${"0.53.0"}`,
8513
+ "user-agent": `ghosty-cli/${"0.54.0"}`,
8514
8514
  "accept-language": lang(),
8515
8515
  ...opts.body !== void 0 ? { "content-type": "application/json" } : {},
8516
8516
  ...opts.headers
@@ -8765,6 +8765,24 @@ function lineDiff(before, after, context = 2) {
8765
8765
  }
8766
8766
 
8767
8767
  // src/embed-component.ts
8768
+ var COPY_DEFAULTS = {
8769
+ waking: "Me estoy poniendo los lentes \u{1F453}",
8770
+ connecting: "Estoy abriendo mi libreta",
8771
+ thinking: [
8772
+ "Estoy pensando",
8773
+ "Estoy leyendo tu mensaje",
8774
+ "Estoy revisando lo que me mandaste",
8775
+ "Estoy ordenando las ideas",
8776
+ "Estoy armando tu respuesta",
8777
+ "Estoy afinando los detalles"
8778
+ ],
8779
+ stillWorking: "Sigo trabajando",
8780
+ cantStart: "No pude arrancar.",
8781
+ interrupted: "Me qued\xE9 a medias",
8782
+ retryLabel: "Reintentar",
8783
+ showDuration: true,
8784
+ showCopyButton: true
8785
+ };
8768
8786
  var DEFAULT_BASE = "https://www.ghosty.studio";
8769
8787
  function baseFromSnippet(snippet) {
8770
8788
  const m = snippet?.match(/src="(https?:\/\/[^"]+?)\/widget\.js"/);
@@ -8786,6 +8804,7 @@ function reactComponentSource(th, ts) {
8786
8804
  const st = th.style ?? {};
8787
8805
  const radius = typeof st.radius === "number" ? st.radius : 16;
8788
8806
  const font = typeof st.fontFamily === "string" ? st.fontFamily : null;
8807
+ const cp = { ...COPY_DEFAULTS, ...th.copy ?? {} };
8789
8808
  return `// Chat de Ghosty Studio \u2014 generado por \`ghosty agents embed --out\`. Este archivo es TUYO:
8790
8809
  // c\xE1mbialo como quieras; no depende de ning\xFAn paquete, s\xF3lo de React.
8791
8810
  // Habla con tu agente por el contrato del widget web (/embed/w/<token>/v/<visitante>/\u2026).
@@ -8805,6 +8824,16 @@ const WELCOME = ${s(th.welcome)}; // el primer mensaje que ve el visitante
8805
8824
  const PLACEHOLDER = ${s(th.placeholder ?? `Escr\xEDbele a ${th.title}`)};
8806
8825
  const STARTERS${T(": string[]")} = ${s(th.starters)}; // sugerencias (se ocultan al primer mensaje)
8807
8826
 
8827
+ // Lo que dice mientras trabaja (ficha \u2192 Canales \u2192 Widget web \u2192 Textos, o \`--copy\`).
8828
+ const COPY = {
8829
+ connecting: ${s(cp.connecting)}, // mientras se conecta
8830
+ thinking: ${s(cp.thinking)}, // rotan cada 4 s mientras piensa
8831
+ stillWorking: ${s(cp.stillWorking)}, // pasados 90 s
8832
+ interrupted: ${s(cp.interrupted)}, // si la respuesta se cort\xF3
8833
+ showDuration: ${cp.showDuration === true}, // el \xAB\xB7 12 s\xBB de la espera
8834
+ showCopyButton: ${cp.showCopyButton === true}, // \xABCopiar\xBB bajo cada respuesta
8835
+ };
8836
+
8808
8837
  // \u2500\u2500 Tema (tu tema actual del widget) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
8809
8838
  const THEME = {
8810
8839
  font: ${s(font ? `"${font}", ui-sans-serif, system-ui, sans-serif` : "ui-sans-serif, system-ui, sans-serif")},
@@ -8890,7 +8919,7 @@ export function useGhostyChat() {
8890
8919
  // \`reposo\`: llega al conectar (no hab\xEDa turno). S\xF3lo cuenta si esper\xE1bamos respuesta.
8891
8920
  if (d.reposo && !waiting.current) return;
8892
8921
  if (d.reposo && d.ultimo?.text) put(d.ultimo.text, true);
8893
- if (d.interrumpido) setError(d.interrumpido.motivo ?? "la respuesta se cort\xF3");
8922
+ if (d.interrumpido) setError(COPY.interrupted);
8894
8923
  waiting.current = false;
8895
8924
  settle();
8896
8925
  setStatus("idle");
@@ -8929,9 +8958,23 @@ export function useGhostyChat() {
8929
8958
  return { messages, send, stop, status, error };
8930
8959
  }
8931
8960
 
8961
+ // La frase de espera: rota mientras piensa y, pasado minuto y medio, dice que sigue.
8962
+ function useWaitLabel(active${T(": boolean")}) {
8963
+ const [secs, setSecs] = useState(0);
8964
+ useEffect(() => {
8965
+ if (!active) return;
8966
+ setSecs(0);
8967
+ const t = setInterval(() => setSecs((x) => x + 1), 1000);
8968
+ return () => clearInterval(t);
8969
+ }, [active]);
8970
+ const phrase = secs >= 90 ? COPY.stillWorking : COPY.thinking[Math.floor(secs / 4) % COPY.thinking.length];
8971
+ return COPY.showDuration ? \`\${phrase} \xB7 \${secs} s\` : phrase;
8972
+ }
8973
+
8932
8974
  // \u2500\u2500 La UI: c\xE1mbiala a tu gusto \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
8933
8975
  export default function GhostyChat() {
8934
8976
  const { messages, send, stop, status, error } = useGhostyChat();
8977
+ const waitLabel = useWaitLabel(status === "thinking");
8935
8978
  const [text, setText] = useState("");
8936
8979
  const end = useRef${T("<HTMLDivElement>")}(null);
8937
8980
  const busy = status === "thinking" || status === "streaming";
@@ -8975,9 +9018,17 @@ export default function GhostyChat() {
8975
9018
  <div style={{ flex: 1, overflowY: "auto", padding: 16, display: "flex", flexDirection: "column", gap: 8, fontSize: 14, lineHeight: 1.45 }}>
8976
9019
  <div style={bubble("assistant")}>{WELCOME}</div>
8977
9020
  {messages.map((m) => (
8978
- <div key={m.id} style={bubble(m.role)}>{m.text}</div>
9021
+ <div key={m.id} style={{ display: "flex", flexDirection: "column", alignItems: m.role === "user" ? "flex-end" : "flex-start" }}>
9022
+ <div style={bubble(m.role)}>{m.text}</div>
9023
+ {COPY.showCopyButton && m.role === "assistant" && !m.pending && m.text && (
9024
+ <button type="button" onClick={() => void navigator.clipboard?.writeText(m.text)} style={{ border: 0, background: "transparent", color: "#6b7280", fontSize: 12, cursor: "pointer", padding: "2px 0" }}>
9025
+ Copiar
9026
+ </button>
9027
+ )}
9028
+ </div>
8979
9029
  ))}
8980
- {status === "thinking" && <div style={{ ...bubble("assistant"), opacity: 0.6 }}>\u2026</div>}
9030
+ {status === "connecting" && <div style={{ ...bubble("assistant"), opacity: 0.6 }}>{COPY.connecting}</div>}
9031
+ {status === "thinking" && <div style={{ ...bubble("assistant"), opacity: 0.6 }}>{waitLabel}</div>}
8981
9032
  {!messages.length && STARTERS.length > 0 && (
8982
9033
  <div style={{ display: "flex", flexWrap: "wrap", gap: 6 }}>
8983
9034
  {STARTERS.map((s) => (
@@ -9590,6 +9641,10 @@ async function agentsDemo(id, v) {
9590
9641
  ].join("\n")
9591
9642
  );
9592
9643
  }
9644
+ function copySummary(c) {
9645
+ if (!c) return "";
9646
+ return Object.entries(c).filter(([k, x]) => JSON.stringify(x) !== JSON.stringify(COPY_DEFAULTS[k])).map(([k, x]) => `${k}=${Array.isArray(x) ? x.join(" | ") : x}`).join(" \xB7 ");
9647
+ }
9593
9648
  function styleSummary(st) {
9594
9649
  if (!st) return "";
9595
9650
  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(" ");
@@ -9598,7 +9653,7 @@ async function agentsEmbed(id, v, action) {
9598
9653
  id = need(id, "agent id", "ghosty agents embed enable <agent-id> --domains tusitio.com");
9599
9654
  if (action === "theme") {
9600
9655
  const e2 = await api(agentPath(id, "/embed"));
9601
- const theme = { themeSource: e2.themeSource ?? "custom", themeFrom: e2.themeFrom ?? null, siteUrl: e2.siteUrl ?? null, color: e2.color, theme: e2.theme ?? "light", style: e2.style ?? {} };
9656
+ const theme = { themeSource: e2.themeSource ?? "custom", themeFrom: e2.themeFrom ?? null, siteUrl: e2.siteUrl ?? null, color: e2.color, theme: e2.theme ?? "light", style: e2.style ?? {}, copy: e2.copy ?? {} };
9602
9657
  return out(theme, () => JSON.stringify(theme, null, 2));
9603
9658
  }
9604
9659
  if (action) throw usage(t(`Unknown: agents embed <id> ${action}`, `No existe: agents embed <id> ${action}`), "ghosty agents embed <id> theme");
@@ -9649,6 +9704,26 @@ async function agentsEmbed(id, v, action) {
9649
9704
  body.style = isTheme ? j.style : j;
9650
9705
  if (isTheme && typeof j.color === "string" && body.color === void 0) body.color = j.color;
9651
9706
  if (isTheme && typeof j.theme === "string" && body.theme === void 0) body.theme = j.theme;
9707
+ if (isTheme && j.copy && typeof j.copy === "object" && body.copy === void 0) body.copy = j.copy;
9708
+ }
9709
+ if (v.copy !== void 0) {
9710
+ if (v.copy.toLowerCase() === "none") body.copy = null;
9711
+ else {
9712
+ try {
9713
+ body.copy = JSON.parse(v.copy);
9714
+ } catch {
9715
+ throw usage(t(`--copy takes JSON: '{"waking":"One moment, please","thinking":["Reviewing your question\u2026"]}'`, `--copy va en JSON: '{"waking":"Un momento, por favor","thinking":["Revisando su consulta\u2026"]}'`));
9716
+ }
9717
+ }
9718
+ }
9719
+ if (v["copy-file"]) {
9720
+ let j;
9721
+ try {
9722
+ j = JSON.parse(await readFile3(v["copy-file"], "utf8"));
9723
+ } catch (err) {
9724
+ throw usage(t(`Could not read ${v["copy-file"]} as JSON: ${err.message}`, `No pude leer ${v["copy-file"]} como JSON: ${err.message}`));
9725
+ }
9726
+ body.copy = j && typeof j === "object" && "copy" in j && !("waking" in j) ? j.copy : j;
9652
9727
  }
9653
9728
  if (v["theme-source"] !== void 0) body.themeSource = v["theme-source"];
9654
9729
  if (v["site-url"] !== void 0) {
@@ -9682,6 +9757,7 @@ async function agentsEmbed(id, v, action) {
9682
9757
  e.starters?.length ? `${t("suggestions", "sugerencias")}: ${e.starters.join(" | ")}` : dim(t("suggestions: (none)", "sugerencias: (ninguna)")),
9683
9758
  `${t("theme", "tema")}: ${e.themeSource ?? "custom"}${e.themeFrom ? dim(` \xB7 ${e.themeFrom}`) : ""}`,
9684
9759
  ...styleSummary(e.style) ? [dim(`${t("style", "estilo")}: ${styleSummary(e.style)}`)] : [],
9760
+ ...copySummary(e.copy) ? [dim(`${t("texts", "textos")}: ${copySummary(e.copy)}`)] : [],
9685
9761
  ...e.snippet ? ["", t("Paste before </body> on your site:", "Pega antes de </body> en tu sitio:"), e.snippet, ""] : [],
9686
9762
  ...react ? [t("React:", "React:"), dim(` ${react.install}`), ...react.code.split("\n").map((l) => ` ${l}`), ""] : [],
9687
9763
  ...wrote ? [`${lavender("\u2713")} ${t(`Component written to ${wrote} (yours to edit: token and theme are at the top)`, `Componente escrito en ${wrote} (es tuyo: el token y el tema van arriba)`)}`, ""] : [],
@@ -9708,7 +9784,8 @@ async function writeEmbedComponent(e, baseUrl2, file, force) {
9708
9784
  color: e.color,
9709
9785
  textColor: e.textColor ?? "#ffffff",
9710
9786
  starters: e.starters ?? [],
9711
- style: e.style ?? {}
9787
+ style: e.style ?? {},
9788
+ copy: { ...COPY_DEFAULTS, ...e.copy ?? {} }
9712
9789
  },
9713
9790
  ext === "tsx"
9714
9791
  );
@@ -10327,7 +10404,7 @@ async function call(path, v, init = {}) {
10327
10404
  "x-ghosty-ts": ts,
10328
10405
  "x-ghosty-sig": sig,
10329
10406
  "content-type": "application/json",
10330
- "user-agent": `ghosty-cli/${"0.53.0"}`
10407
+ "user-agent": `ghosty-cli/${"0.54.0"}`
10331
10408
  },
10332
10409
  body: method === "GET" ? void 0 : raw
10333
10410
  });
@@ -11936,6 +12013,8 @@ var HELPS_ES = {
11936
12013
  ["# its theme", "# su tema"],
11937
12014
  ["# 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)"],
11938
12015
  ["--style-file theme.json", "--style-file tema.json"],
12016
+ ["# what it says while it works", "# lo que dice mientras trabaja"],
12017
+ ["--copy-file texts.json", "--copy-file textos.json"],
11939
12018
  ["# 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)"],
11940
12019
  ["# change one piece of the prompt, on the server's current one", "# cambia un pedazo del prompt, sobre el actual del servidor"],
11941
12020
  ["# your account: merge copies with the same content", "# tu cuenta: une las copias con el mismo contenido"],
@@ -12005,6 +12084,8 @@ var HELPS_ES = {
12005
12084
  ["--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"],
12006
12085
  ["--site-url URL", "embed: el sitio del que se sacan los colores (implica --theme-source site)"],
12007
12086
  ["--style-file FILE", "embed: aplica un tema exportado con `agents embed <id> theme` (o s\xF3lo el objeto style)"],
12087
+ ["--copy '{json}'|none", "embed: las frases mientras el agente trabaja, texto plano \u2264 80 car.: waking, connecting, thinking (lista de 1 a 8 que rotan), stillWorking, cantStart, interrupted, retryLabel, showDuration, showCopyButton (true|false). PATCH por llave (null = la frase de Ghosty); none las regresa todas"],
12088
+ ["--copy-file FILE", "embed: aplica el objeto copy desde un archivo (o el `copy` de un tema exportado)"],
12008
12089
  ["--out FILE.tsx|.jsx [--force]", "embed: escribe en tu proyecto un componente de React COMPLETO y editable (sin depender de ning\xFAn paquete) con tu token y tu tema horneados arriba; no pisa un archivo que ya existe sin --force"],
12009
12090
  ["--check PATH", "box: este archivo debe existir en la m\xE1quina (repetible), p. ej. un archivo que trae la imagen nueva"],
12010
12091
  ["--contains PATH=TEXT", "box: ese archivo debe contener TEXT (un cambio que no agrega archivos)"],
@@ -12469,6 +12550,7 @@ var HELPS = {
12469
12550
  "ghosty agents embed <agent-id> [--domains \u2026] [--per-visitor N] [--per-ip N] [--welcome TEXT] [--color #hex] [--rotate] # state + the <script> to paste",
12470
12551
  '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',
12471
12552
  "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",
12553
+ "ghosty agents embed <agent-id> [--copy '{json}'|none] [--copy-file texts.json] # what it says while it works",
12472
12554
  "ghosty agents embed <agent-id> theme # export the theme as JSON (edit it, apply it back with --style-file)",
12473
12555
  "ghosty agents embed <agent-id> --out src/components/GhostyChat.tsx [--force] # your own React component, token and theme baked in",
12474
12556
  "ghosty agents rm <agent-id> [--from-workspace] [--yes]"
@@ -12530,6 +12612,8 @@ var HELPS = {
12530
12612
  ["--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"],
12531
12613
  ["--site-url URL", "embed: the site to take colors from (implies --theme-source site)"],
12532
12614
  ["--style-file FILE", "embed: apply a theme exported with `agents embed <id> theme` (or a plain style object)"],
12615
+ ["--copy '{json}'|none", "embed: the phrases while the agent works, plain text \u2264 80 chars: waking, connecting, thinking (list of 1\u20138 that rotate), stillWorking, cantStart, interrupted, retryLabel, showDuration, showCopyButton (true|false). Patch per key (null = Ghosty's phrase); none resets all"],
12616
+ ["--copy-file FILE", "embed: apply the copy object from a file (or the `copy` of an exported theme)"],
12533
12617
  ["--out FILE.tsx|.jsx [--force]", "embed: write a COMPLETE, editable React component into your project (no package needed) with your token and theme baked in at the top; won't overwrite an existing file without --force"],
12534
12618
  ["--check PATH", "box: this file must exist in the machine (repeatable), e.g. a file the new image brings"],
12535
12619
  ["--contains PATH=TEXT", "box: that file must contain TEXT (a change that adds no files)"],
@@ -12570,6 +12654,7 @@ var HELPS = {
12570
12654
  '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?"',
12571
12655
  "ghosty agents embed <id> --theme-source site --site-url https://palmeralegal.mx",
12572
12656
  "ghosty agents embed <id> theme > tema.json \xB7 ghosty agents embed <id> --style-file tema.json",
12657
+ `ghosty agents embed <id> --copy '{"waking":"Un momento, por favor","thinking":["Revisando su consulta\u2026","Consultando el expediente\u2026"],"showDuration":false}'`,
12573
12658
  "ghosty agents box <id> --contains /opt/claude-worker/skills/pdf-doc/render-doc.mjs=pdf-templates",
12574
12659
  "ghosty agents set <id> --tool wa:actualizar_identidad=off,teams:actualizar_identidad=on",
12575
12660
  'ghosty agents set minighosty --bag "MiniGhosty" --avatar https://www.ghosty.studio/avatars/minighosty.png',
@@ -14400,7 +14485,8 @@ ${bold("Agents")}
14400
14485
  look: [--title T] [--subtitle T] [--avatar URL|none] [--icon chat|avatar|ghost] [--placeholder T] [--position right|left] [--offset X[,Y]]
14401
14486
  [--theme light|dark|auto] [--bubble-text T] [--auto-open SECONDS|off] [--starters "a|b|c"] [--font NAME] [--radius 0-24] [--style '{json}'|none]
14402
14487
  theme: [--theme-source custom|teams|site] [--workspace SLUG] [--site-url URL] [--style-file theme.json]
14403
- agents embed <id> theme Export the widget theme as JSON (edit it and apply it back with --style-file)
14488
+ texts while it works: [--copy '{"waking":"One moment, please","thinking":["Reviewing your question\u2026"]}'|none] [--copy-file copy.json]
14489
+ agents embed <id> theme Export the widget theme + texts as JSON (edit it and apply it back with --style-file)
14404
14490
  agents embed <id> --out F.tsx Write your own React chat component (token + theme baked in; --force to overwrite) \xB7 the output also prints the React snippet
14405
14491
  agents embed <id> Its state and the snippet to paste (claude engine only: it isolates each visitor)
14406
14492
  [--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]
@@ -14563,7 +14649,8 @@ ${bold("Agentes")}
14563
14649
  look: [--title T] [--subtitle T] [--avatar URL|none] [--icon chat|avatar|ghost] [--placeholder T] [--position right|left] [--offset X[,Y]]
14564
14650
  [--theme light|dark|auto] [--bubble-text T] [--auto-open SEGUNDOS|off] [--starters "a|b|c"] [--font NOMBRE] [--radius 0-24] [--style '{json}'|none]
14565
14651
  tema: [--theme-source custom|teams|site] [--workspace SLUG] [--site-url URL] [--style-file tema.json]
14566
- agents embed <id> theme Exporta el tema del widget en JSON (ed\xEDtalo y vuelve a aplicarlo con --style-file)
14652
+ textos mientras trabaja: [--copy '{"waking":"Un momento, por favor","thinking":["Revisando su consulta\u2026"]}'|none] [--copy-file textos.json]
14653
+ agents embed <id> theme Exporta el tema y los textos del widget en JSON (ed\xEDtalo y vuelve a aplicarlo con --style-file)
14567
14654
  agents embed <id> --out F.tsx Escribe tu propio componente de chat en React (token y tema horneados; --force para pisar) \xB7 la salida tambi\xE9n trae el snippet de React
14568
14655
  agents embed <id> Su estado y el snippet para pegar (Claude o MiniGhosty: a\xEDslan a cada visitante)
14569
14656
  [--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]
@@ -14761,6 +14848,8 @@ async function main() {
14761
14848
  font: { type: "string" },
14762
14849
  radius: { type: "string" },
14763
14850
  "style-file": { type: "string" },
14851
+ copy: { type: "string" },
14852
+ "copy-file": { type: "string" },
14764
14853
  "theme-source": { type: "string" },
14765
14854
  "site-url": { type: "string" },
14766
14855
  sponsor: { type: "string" },
@@ -14915,7 +15004,7 @@ async function main() {
14915
15004
  const tokens = splitTokens(pos, v);
14916
15005
  globals.token = tokens.session;
14917
15006
  globals.profile = v.profile;
14918
- if (v.version) return out({ version: "0.53.0" }, () => "0.53.0");
15007
+ if (v.version) return out({ version: "0.54.0" }, () => "0.54.0");
14919
15008
  for (const n of normalizeArgs(pos, v)) if (!globals.json) info(dim(n));
14920
15009
  const context = (await readConfig()).workspace;
14921
15010
  if (!explicitToken()?.startsWith("gat_")) {
@@ -15081,7 +15170,7 @@ async function main() {
15081
15170
  throw unknown(cmd, cmd, COMMANDS, "ghosty --help");
15082
15171
  }
15083
15172
  }
15084
- var notice = updateNotice("0.53.0").catch(() => null);
15173
+ var notice = updateNotice("0.54.0").catch(() => null);
15085
15174
  main().then(
15086
15175
  async () => {
15087
15176
  const n = await notice;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghostystudio/cli",
3
- "version": "0.53.0",
3
+ "version": "0.54.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": {