@ghostystudio/cli 0.51.0 → 0.53.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.
- package/README.md +10 -1
- package/dist/index.js +511 -96
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -40,6 +40,9 @@ ghosty agents demo <id> [--slug NAME] [--expires "YYYY-MM-DDTHH:MM"|no] [--turns
|
|
|
40
40
|
[--welcome TEXT] [--starter TEXT] [--chips "a|b|c"] [--sponsor on|off] [--rotate] # public /p/ link, no account needed
|
|
41
41
|
ghosty agents embed enable|disable <id> [--domains a.com,*.b.com] # web widget: chat bubble on your site, only on those domains
|
|
42
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
|
|
45
|
+
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)
|
|
43
46
|
ghosty agents doctor <id> [--verbose] # exits 1 when it finds a problem
|
|
44
47
|
ghosty agents docs ls|grant|revoke <id> [doc] [--write] [--no-deliver]
|
|
45
48
|
ghosty agents docs add <id> --url URL | --file PATH [--description TEXT] [--grant read|write|no]
|
|
@@ -105,9 +108,15 @@ Every command has its own help with examples: `ghosty <command> --help` or `ghos
|
|
|
105
108
|
|
|
106
109
|
Exit codes: `0` ok · `1` API error · `2` usage error · `3` not signed in.
|
|
107
110
|
|
|
111
|
+
### Changes in 0.52.0
|
|
112
|
+
|
|
113
|
+
- `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.
|
|
114
|
+
- 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`.
|
|
115
|
+
- 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.
|
|
116
|
+
|
|
108
117
|
### Changes in 0.51.0
|
|
109
118
|
|
|
110
|
-
- `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
|
+
- `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 or MiniGhosty (ghosty-lite) engine only. No flags = state + the snippet.
|
|
111
120
|
|
|
112
121
|
### Changes in 0.50.0
|
|
113
122
|
|
package/dist/index.js
CHANGED
|
@@ -2625,10 +2625,10 @@ var require_binary = __commonJS({
|
|
|
2625
2625
|
if (typeof node_buffer.Buffer === "function") {
|
|
2626
2626
|
str = buf instanceof node_buffer.Buffer ? buf.toString("base64") : node_buffer.Buffer.from(buf.buffer).toString("base64");
|
|
2627
2627
|
} else if (typeof btoa === "function") {
|
|
2628
|
-
let
|
|
2628
|
+
let s2 = "";
|
|
2629
2629
|
for (let i = 0; i < buf.length; ++i)
|
|
2630
|
-
|
|
2631
|
-
str = btoa(
|
|
2630
|
+
s2 += String.fromCharCode(buf[i]);
|
|
2631
|
+
str = btoa(s2);
|
|
2632
2632
|
} else {
|
|
2633
2633
|
throw new Error("This environment does not support writing binary tags; either Buffer or btoa is required");
|
|
2634
2634
|
}
|
|
@@ -3686,8 +3686,8 @@ var require_Document = __commonJS({
|
|
|
3686
3686
|
if (this.errors.length > 0)
|
|
3687
3687
|
throw new Error("Document with errors cannot be stringified");
|
|
3688
3688
|
if ("indent" in options && (!Number.isInteger(options.indent) || Number(options.indent) <= 0)) {
|
|
3689
|
-
const
|
|
3690
|
-
throw new Error(`"indent" option must be a positive integer, not ${
|
|
3689
|
+
const s2 = JSON.stringify(options.indent);
|
|
3690
|
+
throw new Error(`"indent" option must be a positive integer, not ${s2}`);
|
|
3691
3691
|
}
|
|
3692
3692
|
return stringifyDocument.stringifyDocument(this, options);
|
|
3693
3693
|
}
|
|
@@ -5900,12 +5900,12 @@ var require_lexer = __commonJS({
|
|
|
5900
5900
|
if (ch === "-" || ch === ".") {
|
|
5901
5901
|
if (!this.atEnd && !this.hasChars(4))
|
|
5902
5902
|
return this.setNext("line-start");
|
|
5903
|
-
const
|
|
5904
|
-
if ((
|
|
5903
|
+
const s2 = this.peek(3);
|
|
5904
|
+
if ((s2 === "---" || s2 === "...") && isEmpty(this.charAt(3))) {
|
|
5905
5905
|
yield* this.pushCount(3);
|
|
5906
5906
|
this.indentValue = 0;
|
|
5907
5907
|
this.indentNext = 0;
|
|
5908
|
-
return
|
|
5908
|
+
return s2 === "---" ? "doc" : "stream";
|
|
5909
5909
|
}
|
|
5910
5910
|
}
|
|
5911
5911
|
this.indentValue = yield* this.pushSpaces(false);
|
|
@@ -6210,11 +6210,11 @@ var require_lexer = __commonJS({
|
|
|
6210
6210
|
return 0;
|
|
6211
6211
|
}
|
|
6212
6212
|
*pushToIndex(i, allowEmpty) {
|
|
6213
|
-
const
|
|
6214
|
-
if (
|
|
6215
|
-
yield
|
|
6216
|
-
this.pos +=
|
|
6217
|
-
return
|
|
6213
|
+
const s2 = this.buffer.slice(this.pos, i);
|
|
6214
|
+
if (s2) {
|
|
6215
|
+
yield s2;
|
|
6216
|
+
this.pos += s2.length;
|
|
6217
|
+
return s2.length;
|
|
6218
6218
|
} else if (allowEmpty)
|
|
6219
6219
|
yield "";
|
|
6220
6220
|
return 0;
|
|
@@ -8168,7 +8168,7 @@ var SERVER_MESSAGES = [
|
|
|
8168
8168
|
// — Widget web (`agents embed`)
|
|
8169
8169
|
["s\xF3lo el due\xF1o del agente maneja su widget web", "only the agent's owner manages its web widget"],
|
|
8170
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
|
|
8171
|
+
["el widget web s\xF3lo corre con Claude o MiniGhosty (ghosty-lite): son los que a\xEDslan a cada visitante", "the web widget only runs on Claude or MiniGhosty (ghosty-lite): they isolate each visitor"],
|
|
8172
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)"]
|
|
8173
8173
|
];
|
|
8174
8174
|
|
|
@@ -8184,7 +8184,7 @@ function setLang(l) {
|
|
|
8184
8184
|
var lang = () => current ?? (current = detectLang());
|
|
8185
8185
|
var t = (en, es) => lang() === "es" ? es : en;
|
|
8186
8186
|
var compiled = null;
|
|
8187
|
-
var escape = (
|
|
8187
|
+
var escape = (s2) => s2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
8188
8188
|
function compile(es) {
|
|
8189
8189
|
return new RegExp(`^${es.split("{}").map(escape).join("(.+?)")}$`, "s");
|
|
8190
8190
|
}
|
|
@@ -8214,12 +8214,12 @@ var CliError = class extends Error {
|
|
|
8214
8214
|
var usage = (message, hint) => new CliError(message, EXIT.USAGE, hint);
|
|
8215
8215
|
var globals = { json: false };
|
|
8216
8216
|
var color = process.stderr.isTTY && !process.env.NO_COLOR;
|
|
8217
|
-
var paint = (code) => (
|
|
8217
|
+
var paint = (code) => (s2) => color ? `\x1B[${code}m${s2}\x1B[0m` : s2;
|
|
8218
8218
|
var dim = paint("2");
|
|
8219
8219
|
var bold = paint("1");
|
|
8220
8220
|
var lavender = paint("38;2;154;153;234");
|
|
8221
8221
|
var red = paint("31");
|
|
8222
|
-
var info = (
|
|
8222
|
+
var info = (s2) => process.stderr.write(s2 + "\n");
|
|
8223
8223
|
function out(data, human) {
|
|
8224
8224
|
if (globals.json || !human) process.stdout.write(JSON.stringify(data, null, globals.json ? 0 : 2) + "\n");
|
|
8225
8225
|
else process.stdout.write(human() + "\n");
|
|
@@ -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.
|
|
8471
|
+
headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.53.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.
|
|
8513
|
+
"user-agent": `ghosty-cli/${"0.53.0"}`,
|
|
8514
8514
|
"accept-language": lang(),
|
|
8515
8515
|
...opts.body !== void 0 ? { "content-type": "application/json" } : {},
|
|
8516
8516
|
...opts.headers
|
|
@@ -8624,10 +8624,10 @@ async function uploadMedia(file) {
|
|
|
8624
8624
|
const mime = MIME[ext] ?? "application/octet-stream";
|
|
8625
8625
|
return { url: r.url, mime, filename: r.name, type: mime.startsWith("image/") ? "image" : mime.startsWith("audio/") ? "audio" : mime.startsWith("video/") ? "video" : "document" };
|
|
8626
8626
|
}
|
|
8627
|
-
function richLine(
|
|
8628
|
-
const d =
|
|
8627
|
+
function richLine(s2) {
|
|
8628
|
+
const d = s2.data ?? {};
|
|
8629
8629
|
const str = (k) => typeof d[k] === "string" ? d[k] : "";
|
|
8630
|
-
switch (
|
|
8630
|
+
switch (s2.rich) {
|
|
8631
8631
|
case "ubicacion":
|
|
8632
8632
|
return `\u{1F4CD} ${str("nombre") || t("location", "ubicaci\xF3n")} \xB7 ${Number(d.latitud).toFixed(4)},${Number(d.longitud).toFixed(4)}${str("direccion") ? dim(` ${str("direccion")}`) : ""}`;
|
|
8633
8633
|
case "boton":
|
|
@@ -8644,7 +8644,7 @@ function richLine(s) {
|
|
|
8644
8644
|
}
|
|
8645
8645
|
function sentText(sent) {
|
|
8646
8646
|
return sent.map(
|
|
8647
|
-
(
|
|
8647
|
+
(s2) => s2.kind === "text" ? `${lavender("\u2192")} ${s2.text}` : s2.kind === "voice" ? `${lavender("\u{1F3A4}")} ${s2.text}` : s2.kind === "file" ? `${lavender(/^audio\//.test(s2.mime ?? "") ? "\u{1F3A4}" : "\u{1F4C4}")} ${s2.name} ${dim(s2.url)}` : richLine(s2)
|
|
8648
8648
|
).join("\n\n");
|
|
8649
8649
|
}
|
|
8650
8650
|
async function tryWaba(id, text, v) {
|
|
@@ -8702,7 +8702,7 @@ async function tryReplay(id, v) {
|
|
|
8702
8702
|
entry.steps.push({ user: p.user, original: p.original, reply: r.body.text ?? "", sent: r.body.sent ?? [], error: r.body.error ?? null, ms: Date.now() - t0, status: r.status });
|
|
8703
8703
|
await save();
|
|
8704
8704
|
}
|
|
8705
|
-
const bad = entry.steps.filter((
|
|
8705
|
+
const bad = entry.steps.filter((s2) => s2.status !== 200 || !s2.reply).length;
|
|
8706
8706
|
info(` ${bad ? red("!") : lavender("\u2713")} ${c.id}: ${t(`${entry.steps.length} turn(s)`, `${entry.steps.length} turno(s)`)}${bad ? t(`, ${bad} without an answer`, `, ${bad} sin respuesta`) : ""}`);
|
|
8707
8707
|
}
|
|
8708
8708
|
};
|
|
@@ -8711,8 +8711,8 @@ async function tryReplay(id, v) {
|
|
|
8711
8711
|
report.finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
8712
8712
|
await save();
|
|
8713
8713
|
const steps = Object.values(report.results).flatMap((r) => r.steps);
|
|
8714
|
-
const failed = steps.filter((
|
|
8715
|
-
const summary = { file, conversations: Object.keys(report.results).length, turns: steps.length, failed, medianMs: steps.map((
|
|
8714
|
+
const failed = steps.filter((s2) => s2.status !== 200 || !s2.reply).length;
|
|
8715
|
+
const summary = { file, conversations: Object.keys(report.results).length, turns: steps.length, failed, medianMs: steps.map((s2) => s2.ms).sort((a, b) => a - b)[Math.floor(steps.length / 2)] ?? 0 };
|
|
8716
8716
|
out(summary, () => `${failed ? red("!") : lavender("\u2713")} ${t(`${summary.turns} turn(s) in ${summary.conversations} conversation(s), ${failed} without an answer, median ${Math.round(summary.medianMs / 1e3)}s`, `${summary.turns} turno(s) en ${summary.conversations} conversaci\xF3n(es), ${failed} sin respuesta, mediana ${Math.round(summary.medianMs / 1e3)}s`)}
|
|
8717
8717
|
${dim(t(`Report: ${file} Failed turns: ghosty turns ls ${id} --errors --since 2h`, `Reporte: ${file} Turnos fallidos: ghosty turns ls ${id} --errors --since 2h`))}`);
|
|
8718
8718
|
if (failed) process.exitCode = 1;
|
|
@@ -8764,6 +8764,252 @@ function lineDiff(before, after, context = 2) {
|
|
|
8764
8764
|
return lines.join("\n");
|
|
8765
8765
|
}
|
|
8766
8766
|
|
|
8767
|
+
// src/embed-component.ts
|
|
8768
|
+
var DEFAULT_BASE = "https://www.ghosty.studio";
|
|
8769
|
+
function baseFromSnippet(snippet) {
|
|
8770
|
+
const m = snippet?.match(/src="(https?:\/\/[^"]+?)\/widget\.js"/);
|
|
8771
|
+
return m ? m[1] : DEFAULT_BASE;
|
|
8772
|
+
}
|
|
8773
|
+
function reactSnippet(token, baseUrl2) {
|
|
8774
|
+
const base = baseUrl2 === DEFAULT_BASE ? "" : ` baseUrl="${baseUrl2}"`;
|
|
8775
|
+
return {
|
|
8776
|
+
install: "npm i @ghostystudio/widget",
|
|
8777
|
+
code: `import { GhostyWidget } from "@ghostystudio/widget";
|
|
8778
|
+
|
|
8779
|
+
<GhostyWidget token="${token}"${base} />`
|
|
8780
|
+
};
|
|
8781
|
+
}
|
|
8782
|
+
var s = (v) => JSON.stringify(v ?? null);
|
|
8783
|
+
var hexOr = (v, def) => typeof v === "string" && /^#[0-9a-f]{6}$/i.test(v) ? v : def;
|
|
8784
|
+
function reactComponentSource(th, ts) {
|
|
8785
|
+
const T = (x) => ts ? x : "";
|
|
8786
|
+
const st = th.style ?? {};
|
|
8787
|
+
const radius = typeof st.radius === "number" ? st.radius : 16;
|
|
8788
|
+
const font = typeof st.fontFamily === "string" ? st.fontFamily : null;
|
|
8789
|
+
return `// Chat de Ghosty Studio \u2014 generado por \`ghosty agents embed --out\`. Este archivo es TUYO:
|
|
8790
|
+
// c\xE1mbialo como quieras; no depende de ning\xFAn paquete, s\xF3lo de React.
|
|
8791
|
+
// Habla con tu agente por el contrato del widget web (/embed/w/<token>/v/<visitante>/\u2026).
|
|
8792
|
+
// \u26A0\uFE0F S\xF3lo funciona en los dominios que autorizaste en el widget (ficha \u2192 Canales \u2192 Widget web,
|
|
8793
|
+
// o \`ghosty agents embed <id> --domains tusitio.com\`). En localhost el servidor lo rechaza.
|
|
8794
|
+
import { useEffect, useRef, useState } from "react";
|
|
8795
|
+
|
|
8796
|
+
// \u2500\u2500 Conexi\xF3n \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
8797
|
+
// El token del widget. Si lo rotas (\`--rotate\`), c\xE1mbialo aqu\xED.
|
|
8798
|
+
const TOKEN = ${s(th.token)};
|
|
8799
|
+
const BASE_URL = ${s(th.baseUrl)};
|
|
8800
|
+
|
|
8801
|
+
// \u2500\u2500 Textos \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
8802
|
+
const TITLE = ${s(th.title)};
|
|
8803
|
+
const SUBTITLE = ${s(th.subtitle)}; // null = sin rengl\xF3n bajo el t\xEDtulo
|
|
8804
|
+
const WELCOME = ${s(th.welcome)}; // el primer mensaje que ve el visitante
|
|
8805
|
+
const PLACEHOLDER = ${s(th.placeholder ?? `Escr\xEDbele a ${th.title}`)};
|
|
8806
|
+
const STARTERS${T(": string[]")} = ${s(th.starters)}; // sugerencias (se ocultan al primer mensaje)
|
|
8807
|
+
|
|
8808
|
+
// \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
|
+
const THEME = {
|
|
8810
|
+
font: ${s(font ? `"${font}", ui-sans-serif, system-ui, sans-serif` : "ui-sans-serif, system-ui, sans-serif")},
|
|
8811
|
+
fontHref: ${s(font ? `https://fonts.googleapis.com/css2?family=${encodeURIComponent(font).replace(/%20/g, "+")}:wght@400;500;600;700&display=swap` : null)}, // null = no carga fuente
|
|
8812
|
+
radius: ${radius}, // esquinas en px
|
|
8813
|
+
headerBg: ${s(hexOr(st.headerBg, th.color))},
|
|
8814
|
+
headerText: ${s(hexOr(st.headerText, th.textColor))},
|
|
8815
|
+
panelBg: ${s(hexOr(st.panelBg, "#ffffff"))},
|
|
8816
|
+
userBubbleBg: ${s(hexOr(st.userBubbleBg, th.color))},
|
|
8817
|
+
userBubbleText: ${s(hexOr(st.userBubbleText, th.textColor))},
|
|
8818
|
+
agentBubbleBg: ${s(hexOr(st.agentBubbleBg, "#f1f1f4"))},
|
|
8819
|
+
agentBubbleText: ${s(hexOr(st.agentBubbleText, "#191a20"))},
|
|
8820
|
+
inputBg: ${s(hexOr(st.inputBg, "#ffffff"))},
|
|
8821
|
+
sendBg: ${s(hexOr(st.sendBg, th.color))},
|
|
8822
|
+
sendIcon: ${s(hexOr(st.sendIcon, th.textColor))},
|
|
8823
|
+
titleUppercase: ${st.titleUppercase === true},
|
|
8824
|
+
};
|
|
8825
|
+
|
|
8826
|
+
${T(`type Msg = { id: string; role: "user" | "assistant"; text: string; pending?: boolean };
|
|
8827
|
+
type Status = "connecting" | "idle" | "thinking" | "streaming";
|
|
8828
|
+
|
|
8829
|
+
`)}// El visitante: un uuid por navegador (misma llave que el widget). B\xF3rralo para empezar de cero.
|
|
8830
|
+
const VISITOR_KEY = "ghosty:widget:vid";
|
|
8831
|
+
function visitorId()${T(": string")} {
|
|
8832
|
+
try {
|
|
8833
|
+
const v = localStorage.getItem(VISITOR_KEY);
|
|
8834
|
+
if (v && /^[0-9a-f-]{36}$/.test(v)) return v;
|
|
8835
|
+
const fresh = crypto.randomUUID();
|
|
8836
|
+
localStorage.setItem(VISITOR_KEY, fresh);
|
|
8837
|
+
return fresh;
|
|
8838
|
+
} catch {
|
|
8839
|
+
return crypto.randomUUID();
|
|
8840
|
+
}
|
|
8841
|
+
}
|
|
8842
|
+
|
|
8843
|
+
// \u2500\u2500 La l\xF3gica del chat (puedes usarla sola con tu propia UI) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
8844
|
+
export function useGhostyChat() {
|
|
8845
|
+
const [messages, setMessages] = useState${T("<Msg[]>")}([]);
|
|
8846
|
+
const [status, setStatus] = useState${T("<Status>")}("connecting");
|
|
8847
|
+
const [error, setError] = useState${T("<string | null>")}(null);
|
|
8848
|
+
const base = useRef${T("<string | null>")}(null);
|
|
8849
|
+
const waiting = useRef(false);
|
|
8850
|
+
|
|
8851
|
+
useEffect(() => {
|
|
8852
|
+
const url = \`\${BASE_URL}/embed/w/\${TOKEN}/v/\${visitorId()}\`;
|
|
8853
|
+
base.current = url;
|
|
8854
|
+
let es${T(": EventSource | null")} = null;
|
|
8855
|
+
let closed = false;
|
|
8856
|
+
// Texto del agente: se pega a la burbuja en curso o abre una nueva.
|
|
8857
|
+
const put = (text${T(": string")}, replace${T(": boolean")}) =>
|
|
8858
|
+
setMessages((ms) => {
|
|
8859
|
+
const last = ms[ms.length - 1];
|
|
8860
|
+
if (last && last.role === "assistant" && last.pending) return [...ms.slice(0, -1), { ...last, text: replace ? text : last.text + text }];
|
|
8861
|
+
return [...ms, { id: crypto.randomUUID(), role: "assistant", text, pending: true }];
|
|
8862
|
+
});
|
|
8863
|
+
const settle = () => setMessages((ms) => ms.map((m) => (m.pending ? { ...m, pending: false } : m)).filter((m) => m.role === "user" || m.text));
|
|
8864
|
+
const data = (e${T(": Event")}) => {
|
|
8865
|
+
try {
|
|
8866
|
+
return JSON.parse((e${T(" as MessageEvent")}).data || "{}");
|
|
8867
|
+
} catch {
|
|
8868
|
+
return {};
|
|
8869
|
+
}
|
|
8870
|
+
};
|
|
8871
|
+
|
|
8872
|
+
fetch(\`\${url}/history\`)
|
|
8873
|
+
.then((r) => (r.ok ? r.json() : { messages: [] }))
|
|
8874
|
+
.catch(() => ({ messages: [] }))
|
|
8875
|
+
.then((j) => {
|
|
8876
|
+
if (closed) return;
|
|
8877
|
+
setMessages((j.messages ?? []).map((m${T(": { role: string; text: string }")}) => ({ id: crypto.randomUUID(), role: m.role === "user" ? "user" : "assistant", text: m.text })));
|
|
8878
|
+
setStatus("idle");
|
|
8879
|
+
es = new EventSource(\`\${url}/events\`);
|
|
8880
|
+
// Reconexi\xF3n: el texto del turno vuelve a llegar entero, se vac\xEDa la burbuja en curso.
|
|
8881
|
+
es.addEventListener("started", () => setMessages((ms) => ms.map((m) => (m.pending ? { ...m, text: "" } : m))));
|
|
8882
|
+
es.addEventListener("chunk", (e) => {
|
|
8883
|
+
const d = data(e);
|
|
8884
|
+
if (d.text) { put(d.text, false); setStatus("streaming"); }
|
|
8885
|
+
});
|
|
8886
|
+
es.addEventListener("reemplazo", (e) => { const d = data(e); if (typeof d.text === "string") put(d.text, true); });
|
|
8887
|
+
es.addEventListener("error", (e) => { const d = data(e); if (d.message) setError(d.message); });
|
|
8888
|
+
es.addEventListener("done", (e) => {
|
|
8889
|
+
const d = data(e);
|
|
8890
|
+
// \`reposo\`: llega al conectar (no hab\xEDa turno). S\xF3lo cuenta si esper\xE1bamos respuesta.
|
|
8891
|
+
if (d.reposo && !waiting.current) return;
|
|
8892
|
+
if (d.reposo && d.ultimo?.text) put(d.ultimo.text, true);
|
|
8893
|
+
if (d.interrumpido) setError(d.interrumpido.motivo ?? "la respuesta se cort\xF3");
|
|
8894
|
+
waiting.current = false;
|
|
8895
|
+
settle();
|
|
8896
|
+
setStatus("idle");
|
|
8897
|
+
});
|
|
8898
|
+
});
|
|
8899
|
+
return () => {
|
|
8900
|
+
closed = true;
|
|
8901
|
+
es?.close();
|
|
8902
|
+
};
|
|
8903
|
+
}, []);
|
|
8904
|
+
|
|
8905
|
+
async function send(text${T(": string")}) {
|
|
8906
|
+
const content = text.trim();
|
|
8907
|
+
if (!content || !base.current) return;
|
|
8908
|
+
setError(null);
|
|
8909
|
+
waiting.current = true;
|
|
8910
|
+
setStatus("thinking");
|
|
8911
|
+
setMessages((ms) => [...ms, { id: crypto.randomUUID(), role: "user", text: content }]);
|
|
8912
|
+
const r = await fetch(\`\${base.current}/messages\`, {
|
|
8913
|
+
method: "POST",
|
|
8914
|
+
headers: { "Content-Type": "application/json" },
|
|
8915
|
+
body: JSON.stringify({ content, turnId: crypto.randomUUID() }),
|
|
8916
|
+
}).catch(() => null);
|
|
8917
|
+
if (!r || !r.ok) {
|
|
8918
|
+
// Los topes (mensajes por d\xEDa) y avisos llegan como texto listo para ense\xF1ar.
|
|
8919
|
+
waiting.current = false;
|
|
8920
|
+
setStatus("idle");
|
|
8921
|
+
setError(r ? (await r.text().catch(() => "")) || \`error \${r.status}\` : "no se pudo enviar el mensaje");
|
|
8922
|
+
}
|
|
8923
|
+
}
|
|
8924
|
+
|
|
8925
|
+
async function stop() {
|
|
8926
|
+
if (base.current) await fetch(\`\${base.current}/cancel\`, { method: "POST", headers: { "Content-Type": "application/json" }, body: "{}" }).catch(() => {});
|
|
8927
|
+
}
|
|
8928
|
+
|
|
8929
|
+
return { messages, send, stop, status, error };
|
|
8930
|
+
}
|
|
8931
|
+
|
|
8932
|
+
// \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
|
+
export default function GhostyChat() {
|
|
8934
|
+
const { messages, send, stop, status, error } = useGhostyChat();
|
|
8935
|
+
const [text, setText] = useState("");
|
|
8936
|
+
const end = useRef${T("<HTMLDivElement>")}(null);
|
|
8937
|
+
const busy = status === "thinking" || status === "streaming";
|
|
8938
|
+
|
|
8939
|
+
useEffect(() => {
|
|
8940
|
+
end.current?.scrollIntoView({ behavior: "smooth", block: "end" });
|
|
8941
|
+
}, [messages]);
|
|
8942
|
+
useEffect(() => {
|
|
8943
|
+
if (!THEME.fontHref) return;
|
|
8944
|
+
const l = document.createElement("link");
|
|
8945
|
+
l.rel = "stylesheet";
|
|
8946
|
+
l.href = THEME.fontHref;
|
|
8947
|
+
document.head.appendChild(l);
|
|
8948
|
+
return () => l.remove();
|
|
8949
|
+
}, []);
|
|
8950
|
+
|
|
8951
|
+
const submit = (e${T("?: { preventDefault: () => void }")}) => {
|
|
8952
|
+
e?.preventDefault();
|
|
8953
|
+
if (busy || !text.trim()) return;
|
|
8954
|
+
void send(text);
|
|
8955
|
+
setText("");
|
|
8956
|
+
};
|
|
8957
|
+
|
|
8958
|
+
const bubble = (role${T(': "user" | "assistant"')}) => ({
|
|
8959
|
+
maxWidth: "85%",
|
|
8960
|
+
padding: "8px 12px",
|
|
8961
|
+
borderRadius: THEME.radius,
|
|
8962
|
+
whiteSpace: "pre-wrap"${T(" as const")},
|
|
8963
|
+
alignSelf: role === "user" ? "flex-end" : "flex-start",
|
|
8964
|
+
background: role === "user" ? THEME.userBubbleBg : THEME.agentBubbleBg,
|
|
8965
|
+
color: role === "user" ? THEME.userBubbleText : THEME.agentBubbleText,
|
|
8966
|
+
});
|
|
8967
|
+
|
|
8968
|
+
return (
|
|
8969
|
+
<div style={{ display: "flex", flexDirection: "column", height: 560, width: "100%", maxWidth: 400, overflow: "hidden", borderRadius: THEME.radius, background: THEME.panelBg, fontFamily: THEME.font, boxShadow: "0 12px 40px rgba(0,0,0,.18)" }}>
|
|
8970
|
+
<header style={{ padding: "12px 16px", background: THEME.headerBg, color: THEME.headerText }}>
|
|
8971
|
+
<div style={{ fontWeight: 600, fontSize: 14, textTransform: THEME.titleUppercase ? "uppercase" : "none", letterSpacing: THEME.titleUppercase ? "0.12em" : undefined }}>{TITLE}</div>
|
|
8972
|
+
{SUBTITLE && <div style={{ fontSize: 12, opacity: 0.8 }}>{SUBTITLE}</div>}
|
|
8973
|
+
</header>
|
|
8974
|
+
|
|
8975
|
+
<div style={{ flex: 1, overflowY: "auto", padding: 16, display: "flex", flexDirection: "column", gap: 8, fontSize: 14, lineHeight: 1.45 }}>
|
|
8976
|
+
<div style={bubble("assistant")}>{WELCOME}</div>
|
|
8977
|
+
{messages.map((m) => (
|
|
8978
|
+
<div key={m.id} style={bubble(m.role)}>{m.text}</div>
|
|
8979
|
+
))}
|
|
8980
|
+
{status === "thinking" && <div style={{ ...bubble("assistant"), opacity: 0.6 }}>\u2026</div>}
|
|
8981
|
+
{!messages.length && STARTERS.length > 0 && (
|
|
8982
|
+
<div style={{ display: "flex", flexWrap: "wrap", gap: 6 }}>
|
|
8983
|
+
{STARTERS.map((s) => (
|
|
8984
|
+
<button key={s} type="button" onClick={() => void send(s)} style={{ border: \`1px solid \${THEME.sendBg}\`, color: THEME.sendBg, background: "transparent", borderRadius: 999, padding: "4px 10px", fontSize: 13, cursor: "pointer" }}>
|
|
8985
|
+
{s}
|
|
8986
|
+
</button>
|
|
8987
|
+
))}
|
|
8988
|
+
</div>
|
|
8989
|
+
)}
|
|
8990
|
+
{error && <div style={{ color: "#b42318", fontSize: 13 }}>{error}</div>}
|
|
8991
|
+
<div ref={end} />
|
|
8992
|
+
</div>
|
|
8993
|
+
|
|
8994
|
+
<form onSubmit={submit} style={{ display: "flex", gap: 8, padding: 12, borderTop: "1px solid rgba(0,0,0,.08)" }}>
|
|
8995
|
+
<input
|
|
8996
|
+
value={text}
|
|
8997
|
+
onChange={(e) => setText(e.target.value)}
|
|
8998
|
+
placeholder={PLACEHOLDER}
|
|
8999
|
+
style={{ flex: 1, padding: "10px 12px", borderRadius: THEME.radius, border: "1px solid rgba(0,0,0,.15)", background: THEME.inputBg, fontSize: 16, fontFamily: "inherit" }}
|
|
9000
|
+
/>
|
|
9001
|
+
{busy ? (
|
|
9002
|
+
<button type="button" onClick={() => void stop()} aria-label="Detener" style={{ width: 42, borderRadius: THEME.radius, border: 0, background: THEME.sendBg, color: THEME.sendIcon, cursor: "pointer" }}>\u25A0</button>
|
|
9003
|
+
) : (
|
|
9004
|
+
<button type="submit" aria-label="Enviar" style={{ width: 42, borderRadius: THEME.radius, border: 0, background: THEME.sendBg, color: THEME.sendIcon, cursor: "pointer" }}>\u2191</button>
|
|
9005
|
+
)}
|
|
9006
|
+
</form>
|
|
9007
|
+
</div>
|
|
9008
|
+
);
|
|
9009
|
+
}
|
|
9010
|
+
`;
|
|
9011
|
+
}
|
|
9012
|
+
|
|
8767
9013
|
// src/commands/agents.ts
|
|
8768
9014
|
var enc2 = encodeURIComponent;
|
|
8769
9015
|
var agentPath = (id, rest = "") => `/api/v2/agents/${enc2(id)}${rest}`;
|
|
@@ -8775,9 +9021,9 @@ var need = (v, what, example) => {
|
|
|
8775
9021
|
async function agentsLs(workspace, f = {}) {
|
|
8776
9022
|
if (f.sponsor || f.owner) {
|
|
8777
9023
|
const q = new URLSearchParams({ ...f.sponsor ? { sponsor: f.sponsor } : {}, ...f.owner ? { owner: f.owner } : {}, ...f.engine ? { engine: f.engine } : {}, ...workspace && !f.all ? { workspace } : {} });
|
|
8778
|
-
const
|
|
8779
|
-
if (f.ids) return void process.stdout.write(
|
|
8780
|
-
return out(
|
|
9024
|
+
const s2 = await api(`/api/v2/agents?${q}`);
|
|
9025
|
+
if (f.ids) return void process.stdout.write(s2.agents.map((a) => a.id).join("\n") + (s2.agents.length ? "\n" : ""));
|
|
9026
|
+
return out(s2.agents, () => table(s2.agents.map((a) => ({ id: a.id, name: a.name, engine: a.engine, model: a.model, owner: a.owner, sponsored: a.sponsored ? t("yes", "s\xED") : "" })), ["id", "name", "engine", "model", "owner", "sponsored"]));
|
|
8781
9027
|
}
|
|
8782
9028
|
const r = await api("/api/v2/me/agents?client=cli");
|
|
8783
9029
|
const list = (workspace ? r.agentes.filter((a) => a.espacio?.nombre === workspace) : r.agentes).filter((a) => !f.engine || (a.motor ?? a.origen) === f.engine);
|
|
@@ -8985,8 +9231,8 @@ ${dim(t(`${r3.beforeChars} \u2192 ${r3.chars} chars. Dry run: nothing changed.`,
|
|
|
8985
9231
|
const unset = (v["unset-env"] ?? "").split(",").map((k) => k.trim()).filter(Boolean);
|
|
8986
9232
|
if (env || unset.length) body.env = { ...env ?? {}, ...Object.fromEntries(unset.map((k) => [k, ""])) };
|
|
8987
9233
|
const connectors = {};
|
|
8988
|
-
for (const c of (v.connect ?? "").split(",").map((
|
|
8989
|
-
for (const c of (v.disconnect ?? "").split(",").map((
|
|
9234
|
+
for (const c of (v.connect ?? "").split(",").map((s2) => s2.trim()).filter(Boolean)) connectors[c] = {};
|
|
9235
|
+
for (const c of (v.disconnect ?? "").split(",").map((s2) => s2.trim()).filter(Boolean)) connectors[c] = null;
|
|
8990
9236
|
if (Object.keys(connectors).length) body.connectors = connectors;
|
|
8991
9237
|
const voice = {};
|
|
8992
9238
|
if (v.voice) {
|
|
@@ -9212,8 +9458,8 @@ async function skillsCmd(sub, id, slug, v) {
|
|
|
9212
9458
|
const r = await api(agentPath(id, "/skills"));
|
|
9213
9459
|
const short3 = (d) => (d ?? "").replace(/\s+/g, " ").slice(0, 70);
|
|
9214
9460
|
const rows = [
|
|
9215
|
-
...r.skills.map((
|
|
9216
|
-
...(r.house ?? []).map((
|
|
9461
|
+
...r.skills.map((s2) => ({ slug: s2.slug, source: "own", on: "on", description: short3(s2.description) })),
|
|
9462
|
+
...(r.house ?? []).map((s2) => ({ slug: s2.slug, source: "ghosty", on: s2.enabled ? "on" : "off", description: short3(s2.description) }))
|
|
9217
9463
|
];
|
|
9218
9464
|
const more = r.community.filter((c) => !c.installed).length;
|
|
9219
9465
|
out(r, () => `${table(rows, ["slug", "source", "on", "description"])}
|
|
@@ -9221,7 +9467,7 @@ ${dim(t(`${more} more in the community catalog: ghosty skills add <agent-id> <sl
|
|
|
9221
9467
|
return;
|
|
9222
9468
|
}
|
|
9223
9469
|
case "house": {
|
|
9224
|
-
const list = (x) => x ? x.split(",").map((
|
|
9470
|
+
const list = (x) => x ? x.split(",").map((s2) => s2.trim()).filter(Boolean) : void 0;
|
|
9225
9471
|
const only = list(v.only), on = list(v.enable), off = list(v.disable);
|
|
9226
9472
|
if (!only && !on && !off) {
|
|
9227
9473
|
const r2 = await api(agentPath(id, "/skills/house"));
|
|
@@ -9344,8 +9590,18 @@ async function agentsDemo(id, v) {
|
|
|
9344
9590
|
].join("\n")
|
|
9345
9591
|
);
|
|
9346
9592
|
}
|
|
9347
|
-
|
|
9593
|
+
function styleSummary(st) {
|
|
9594
|
+
if (!st) return "";
|
|
9595
|
+
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(" ");
|
|
9596
|
+
}
|
|
9597
|
+
async function agentsEmbed(id, v, action) {
|
|
9348
9598
|
id = need(id, "agent id", "ghosty agents embed enable <agent-id> --domains tusitio.com");
|
|
9599
|
+
if (action === "theme") {
|
|
9600
|
+
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 ?? {} };
|
|
9602
|
+
return out(theme, () => JSON.stringify(theme, null, 2));
|
|
9603
|
+
}
|
|
9604
|
+
if (action) throw usage(t(`Unknown: agents embed <id> ${action}`, `No existe: agents embed <id> ${action}`), "ghosty agents embed <id> theme");
|
|
9349
9605
|
if (v.on && v.off) throw usage(t("Use enable or disable, not both.", "Usa enable o disable, no los dos."));
|
|
9350
9606
|
const body = {};
|
|
9351
9607
|
if (v.on) body.enabled = true;
|
|
@@ -9355,22 +9611,111 @@ async function agentsEmbed(id, v) {
|
|
|
9355
9611
|
if (v["per-ip"]) body.perIpDay = Number(v["per-ip"]);
|
|
9356
9612
|
if (v.welcome !== void 0) body.welcome = v.welcome || null;
|
|
9357
9613
|
if (v.color !== void 0) body.color = v.color;
|
|
9614
|
+
const clear = (x) => x === "" || x.toLowerCase() === "none" ? null : x;
|
|
9615
|
+
if (v.avatar !== void 0) body.avatarUrl = clear(v.avatar);
|
|
9616
|
+
if (v.title !== void 0) body.title = clear(v.title);
|
|
9617
|
+
if (v.subtitle !== void 0) body.subtitle = clear(v.subtitle);
|
|
9618
|
+
if (v.placeholder !== void 0) body.placeholder = clear(v.placeholder);
|
|
9619
|
+
if (v["bubble-text"] !== void 0) body.bubbleText = clear(v["bubble-text"]);
|
|
9620
|
+
if (v.icon !== void 0) body.icon = v.icon;
|
|
9621
|
+
if (v.position !== void 0) body.position = v.position;
|
|
9622
|
+
if (v.theme !== void 0) body.theme = v.theme;
|
|
9623
|
+
if (v.offset !== void 0) {
|
|
9624
|
+
const [x, y] = v.offset.split(/[,x ]+/).map((n) => Number(n));
|
|
9625
|
+
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)"));
|
|
9626
|
+
body.offsetX = x;
|
|
9627
|
+
body.offsetY = Number.isFinite(y) ? y : x;
|
|
9628
|
+
}
|
|
9629
|
+
if (v["auto-open"] !== void 0) body.autoOpen = /^(off|no|0|)$/i.test(v["auto-open"]) ? 0 : Number(v["auto-open"]);
|
|
9630
|
+
if (v.starters !== void 0) body.starters = v.starters.split("|").map((x) => x.trim()).filter(Boolean);
|
|
9631
|
+
if (v.style !== void 0) {
|
|
9632
|
+
if (v.style.toLowerCase() === "none") body.style = null;
|
|
9633
|
+
else {
|
|
9634
|
+
try {
|
|
9635
|
+
body.style = JSON.parse(v.style);
|
|
9636
|
+
} catch {
|
|
9637
|
+
throw usage(t(`--style takes JSON: '{"headerBg":"#2d4a2e","radius":0}'`, `--style va en JSON: '{"headerBg":"#2d4a2e","radius":0}'`));
|
|
9638
|
+
}
|
|
9639
|
+
}
|
|
9640
|
+
}
|
|
9641
|
+
if (v["style-file"]) {
|
|
9642
|
+
let j;
|
|
9643
|
+
try {
|
|
9644
|
+
j = JSON.parse(await readFile3(v["style-file"], "utf8"));
|
|
9645
|
+
} catch (err) {
|
|
9646
|
+
throw usage(t(`Could not read ${v["style-file"]} as JSON: ${err.message}`, `No pude leer ${v["style-file"]} como JSON: ${err.message}`));
|
|
9647
|
+
}
|
|
9648
|
+
const isTheme = j && typeof j === "object" && "style" in j;
|
|
9649
|
+
body.style = isTheme ? j.style : j;
|
|
9650
|
+
if (isTheme && typeof j.color === "string" && body.color === void 0) body.color = j.color;
|
|
9651
|
+
if (isTheme && typeof j.theme === "string" && body.theme === void 0) body.theme = j.theme;
|
|
9652
|
+
}
|
|
9653
|
+
if (v["theme-source"] !== void 0) body.themeSource = v["theme-source"];
|
|
9654
|
+
if (v["site-url"] !== void 0) {
|
|
9655
|
+
body.siteUrl = v["site-url"];
|
|
9656
|
+
if (body.themeSource === void 0) body.themeSource = "site";
|
|
9657
|
+
}
|
|
9658
|
+
if (body.themeSource === "teams" && v.workspace) body.teamsWorkspace = v.workspace;
|
|
9659
|
+
if (v.font !== void 0 || v.radius !== void 0) {
|
|
9660
|
+
const st = body.style && typeof body.style === "object" ? body.style : {};
|
|
9661
|
+
if (v.font !== void 0) st.fontFamily = clear(v.font);
|
|
9662
|
+
if (v.radius !== void 0) st.radius = v.radius === "" || v.radius === "none" ? null : Number(v.radius);
|
|
9663
|
+
body.style = st;
|
|
9664
|
+
}
|
|
9358
9665
|
if (v.rotate) body.rotate = true;
|
|
9359
9666
|
const e = Object.keys(body).length ? await api(agentPath(id, "/embed"), { method: "PUT", body }) : await api(agentPath(id, "/embed"));
|
|
9667
|
+
const baseUrl2 = baseFromSnippet(e.snippet);
|
|
9668
|
+
const react = e.token ? reactSnippet(e.token, baseUrl2) : null;
|
|
9669
|
+
const wrote = v.out ? await writeEmbedComponent(e, baseUrl2, v.out, v.force === true) : null;
|
|
9360
9670
|
out(
|
|
9361
|
-
e,
|
|
9671
|
+
{ ...e, react, ...wrote ? { wrote } : {} },
|
|
9362
9672
|
() => [
|
|
9363
9673
|
`${e.enabled ? lavender(t("\u25CF on", "\u25CF encendido")) : dim(t("\u25CB off", "\u25CB apagado"))} ${t("web widget", "widget web")}`,
|
|
9364
9674
|
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)")),
|
|
9365
|
-
...e.engineOk ? [] : [red(t("engine: only
|
|
9675
|
+
...e.engineOk ? [] : [red(t("engine: only Claude or MiniGhosty (ghosty-lite) isolate each visitor; switch with `ghosty agents set <id> --engine claude`", "motor: s\xF3lo Claude o MiniGhosty (ghosty-lite) a\xEDslan a cada visitante; c\xE1mbialo con `ghosty agents set <id> --engine claude`"))],
|
|
9366
9676
|
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}`)),
|
|
9367
|
-
|
|
9677
|
+
`${t("first message", "primer mensaje")}: ${e.welcome ?? dim(e.welcomeShown ?? t("(default greeting)", "(saludo por defecto)"))}`,
|
|
9678
|
+
`${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"}`)}`,
|
|
9679
|
+
dim(
|
|
9680
|
+
`${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` : ""}`
|
|
9681
|
+
),
|
|
9682
|
+
e.starters?.length ? `${t("suggestions", "sugerencias")}: ${e.starters.join(" | ")}` : dim(t("suggestions: (none)", "sugerencias: (ninguna)")),
|
|
9683
|
+
`${t("theme", "tema")}: ${e.themeSource ?? "custom"}${e.themeFrom ? dim(` \xB7 ${e.themeFrom}`) : ""}`,
|
|
9684
|
+
...styleSummary(e.style) ? [dim(`${t("style", "estilo")}: ${styleSummary(e.style)}`)] : [],
|
|
9368
9685
|
...e.snippet ? ["", t("Paste before </body> on your site:", "Pega antes de </body> en tu sitio:"), e.snippet, ""] : [],
|
|
9686
|
+
...react ? [t("React:", "React:"), dim(` ${react.install}`), ...react.code.split("\n").map((l) => ` ${l}`), ""] : [],
|
|
9687
|
+
...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)`)}`, ""] : [],
|
|
9369
9688
|
t(`visitors ${e.stats.visitantes} \xB7 messages ${e.stats.mensajes}`, `visitantes ${e.stats.visitantes} \xB7 mensajes ${e.stats.mensajes}`),
|
|
9370
9689
|
...e.lastError ? [red(`${t("last failure", "\xFAltima falla")} ${e.lastError.at}: ${serverText(e.lastError.message ?? e.lastError.error ?? "")}`)] : []
|
|
9371
9690
|
].join("\n")
|
|
9372
9691
|
);
|
|
9373
9692
|
}
|
|
9693
|
+
async function writeEmbedComponent(e, baseUrl2, file, force) {
|
|
9694
|
+
if (!e.token) throw usage(t("Turn the widget on first: ghosty agents embed enable <id> --domains yoursite.com", "Prende primero el widget: ghosty agents embed enable <id> --domains tusitio.com"));
|
|
9695
|
+
const ext = file.toLowerCase().match(/\.(tsx|jsx)$/)?.[1];
|
|
9696
|
+
if (!ext) throw usage(t("--out must end in .tsx or .jsx", "--out debe terminar en .tsx o .jsx"));
|
|
9697
|
+
const path = resolve(file);
|
|
9698
|
+
const exists = await readFile3(path).then(() => true, () => false);
|
|
9699
|
+
if (exists && !force) throw usage(t(`${file} already exists; add --force to overwrite it`, `${file} ya existe; agrega --force para pisarlo`));
|
|
9700
|
+
const src = reactComponentSource(
|
|
9701
|
+
{
|
|
9702
|
+
token: e.token,
|
|
9703
|
+
baseUrl: baseUrl2,
|
|
9704
|
+
title: e.title ?? e.agentName ?? "Asistente",
|
|
9705
|
+
subtitle: e.subtitle ?? null,
|
|
9706
|
+
placeholder: e.placeholder ?? null,
|
|
9707
|
+
welcome: e.welcome ?? e.welcomeShown ?? "\xA1Hola! \xBFEn qu\xE9 te ayudo?",
|
|
9708
|
+
color: e.color,
|
|
9709
|
+
textColor: e.textColor ?? "#ffffff",
|
|
9710
|
+
starters: e.starters ?? [],
|
|
9711
|
+
style: e.style ?? {}
|
|
9712
|
+
},
|
|
9713
|
+
ext === "tsx"
|
|
9714
|
+
);
|
|
9715
|
+
await mkdir3(dirname2(path), { recursive: true });
|
|
9716
|
+
await writeFile3(path, src);
|
|
9717
|
+
return relative(process.cwd(), path) || path;
|
|
9718
|
+
}
|
|
9374
9719
|
async function agentsBox(id, v) {
|
|
9375
9720
|
id = need(id, "agent id", "ghosty agents box <agent-id> [--check PATH] [--contains PATH=TEXT] [--recycle]");
|
|
9376
9721
|
if (v.recycle) {
|
|
@@ -9579,9 +9924,9 @@ async function chat(agent, message, v) {
|
|
|
9579
9924
|
rl.close();
|
|
9580
9925
|
}
|
|
9581
9926
|
async function readAll() {
|
|
9582
|
-
let
|
|
9583
|
-
for await (const c of process.stdin)
|
|
9584
|
-
return
|
|
9927
|
+
let s2 = "";
|
|
9928
|
+
for await (const c of process.stdin) s2 += c;
|
|
9929
|
+
return s2;
|
|
9585
9930
|
}
|
|
9586
9931
|
async function meMemories(sub, arg, text) {
|
|
9587
9932
|
const ex = 'ghosty me memories ls | add "texto" | edit <id> "texto" | rm <id>';
|
|
@@ -9613,7 +9958,7 @@ async function meMemories(sub, arg, text) {
|
|
|
9613
9958
|
throw usage(t(`Unknown memories subcommand: ${sub}.`, `Subcomando de memories desconocido: ${sub}.`), ex);
|
|
9614
9959
|
}
|
|
9615
9960
|
}
|
|
9616
|
-
var safeName = (
|
|
9961
|
+
var safeName = (s2) => s2.replace(/[\\/:*?"<>|\u0000-\u001f]+/g, "_").trim() || "archivo";
|
|
9617
9962
|
async function meFilesExport(v) {
|
|
9618
9963
|
const dir = v.out ?? "ghosty-archivos";
|
|
9619
9964
|
const all = [];
|
|
@@ -9743,20 +10088,20 @@ async function meUsage() {
|
|
|
9743
10088
|
|
|
9744
10089
|
// src/commands/schedule.ts
|
|
9745
10090
|
var enc4 = encodeURIComponent;
|
|
9746
|
-
function minutesOf(
|
|
9747
|
-
const m = /^(\d+(?:\.\d+)?)\s*(m|min|h|d)?$/i.exec(
|
|
9748
|
-
if (!m) throw usage(t(`Invalid ${flag}: ${
|
|
10091
|
+
function minutesOf(s2, flag) {
|
|
10092
|
+
const m = /^(\d+(?:\.\d+)?)\s*(m|min|h|d)?$/i.exec(s2.trim());
|
|
10093
|
+
if (!m) throw usage(t(`Invalid ${flag}: ${s2}.`, `${flag} inv\xE1lido: ${s2}.`), t("Use minutes, hours or days: 30m, 12h, 1d", "Usa minutos, horas o d\xEDas: 30m, 12h, 1d"));
|
|
9749
10094
|
const n = Number(m[1]);
|
|
9750
10095
|
const unit = (m[2] ?? "m").toLowerCase();
|
|
9751
10096
|
return Math.round(unit === "d" ? n * 1440 : unit === "h" ? n * 60 : n);
|
|
9752
10097
|
}
|
|
9753
|
-
function isoOf(
|
|
9754
|
-
const d = new Date(
|
|
9755
|
-
if (Number.isNaN(d.getTime())) throw usage(t(`Invalid ${flag}: ${
|
|
10098
|
+
function isoOf(s2, flag) {
|
|
10099
|
+
const d = new Date(s2);
|
|
10100
|
+
if (Number.isNaN(d.getTime())) throw usage(t(`Invalid ${flag}: ${s2}.`, `${flag} inv\xE1lido: ${s2}.`), t("Use an ISO date, e.g. 2026-10-01T09:00-06:00", "Usa una fecha ISO, p. ej. 2026-10-01T09:00-06:00"));
|
|
9756
10101
|
return d.toISOString();
|
|
9757
10102
|
}
|
|
9758
10103
|
var every = (min) => min == null ? "once" : min % 1440 === 0 ? `${min / 1440}d` : min % 60 === 0 ? `${min / 60}h` : `${min}m`;
|
|
9759
|
-
var short = (
|
|
10104
|
+
var short = (s2, n = 60) => s2.length > n ? s2.slice(0, n - 1) + "\u2026" : s2;
|
|
9760
10105
|
async function agentSchedule(agent) {
|
|
9761
10106
|
return api(`/api/v2/me/agents/${enc4(agent)}/schedule`);
|
|
9762
10107
|
}
|
|
@@ -9982,7 +10327,7 @@ async function call(path, v, init = {}) {
|
|
|
9982
10327
|
"x-ghosty-ts": ts,
|
|
9983
10328
|
"x-ghosty-sig": sig,
|
|
9984
10329
|
"content-type": "application/json",
|
|
9985
|
-
"user-agent": `ghosty-cli/${"0.
|
|
10330
|
+
"user-agent": `ghosty-cli/${"0.53.0"}`
|
|
9986
10331
|
},
|
|
9987
10332
|
body: method === "GET" ? void 0 : raw
|
|
9988
10333
|
});
|
|
@@ -10243,10 +10588,10 @@ import { basename as basename3, join as join5 } from "node:path";
|
|
|
10243
10588
|
// src/rows.ts
|
|
10244
10589
|
var IDENT = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
10245
10590
|
function parseRowScope(raw) {
|
|
10246
|
-
const
|
|
10247
|
-
if (!
|
|
10591
|
+
const s2 = raw.trim();
|
|
10592
|
+
if (!s2) return null;
|
|
10248
10593
|
const out2 = {};
|
|
10249
|
-
for (const part of
|
|
10594
|
+
for (const part of s2.split(",").map((x) => x.trim()).filter(Boolean)) {
|
|
10250
10595
|
const [table2, cols = ""] = part.split(":").map((x) => x.trim());
|
|
10251
10596
|
const list = cols.split("+").map((x) => x.trim()).filter(Boolean);
|
|
10252
10597
|
if (!IDENT.test(table2 ?? "") || !list.length || !list.every((c) => IDENT.test(c))) {
|
|
@@ -10265,7 +10610,7 @@ var need2 = (v, what, example) => {
|
|
|
10265
10610
|
if (!v) throw usage(t(`Missing ${what}.`, `Falta ${what}.`), `${t("Example", "Ejemplo")}: ${example}`);
|
|
10266
10611
|
return v;
|
|
10267
10612
|
};
|
|
10268
|
-
var csv = (
|
|
10613
|
+
var csv = (s2) => (s2 ?? "").split(",").map((x) => x.trim()).filter(Boolean);
|
|
10269
10614
|
function importWarnings(ws) {
|
|
10270
10615
|
if (!ws?.length) return "";
|
|
10271
10616
|
const flag = ws.map((w) => `${w.table}.${w.column}`).join(",");
|
|
@@ -10296,9 +10641,9 @@ function shrinking(plan) {
|
|
|
10296
10641
|
return plan.filter((p) => p.action === "replace" && p.targetRows > 0 && (p.resultRows === 0 || p.resultRows < p.targetRows / 2));
|
|
10297
10642
|
}
|
|
10298
10643
|
function shrinkWarnings(plan) {
|
|
10299
|
-
const
|
|
10300
|
-
if (!
|
|
10301
|
-
const lines =
|
|
10644
|
+
const s2 = shrinking(plan);
|
|
10645
|
+
if (!s2.length) return "";
|
|
10646
|
+
const lines = s2.map(
|
|
10302
10647
|
(p) => red(p.resultRows === 0 ? t(` ! ${p.table}: would be left EMPTY (${p.targetRows} \u2192 0)`, ` ! ${p.table}: quedar\xEDa VAC\xCDA (${p.targetRows} \u2192 0)`) : t(` ! ${p.table}: loses ${p.targetRows - p.resultRows} of ${p.targetRows} rows`, ` ! ${p.table}: pierde ${p.targetRows - p.resultRows} de ${p.targetRows} filas`))
|
|
10303
10648
|
);
|
|
10304
10649
|
return `
|
|
@@ -10663,7 +11008,7 @@ async function turnsCmd(sub, id, turnId, v) {
|
|
|
10663
11008
|
if (v.session) q.set("session", v.session);
|
|
10664
11009
|
q.set("limit", v.limit ?? "20");
|
|
10665
11010
|
const r = await api(agentPath2(id, `/turns?${q}`));
|
|
10666
|
-
const when = (
|
|
11011
|
+
const when = (s2) => s2.slice(0, 16).replace("T", " ");
|
|
10667
11012
|
const rows = r.turns.map((tu) => ({
|
|
10668
11013
|
id: tu.id,
|
|
10669
11014
|
started: when(tu.startedAt),
|
|
@@ -10702,7 +11047,7 @@ async function turnShow(id, turnId, full = false) {
|
|
|
10702
11047
|
const r = await api(agentPath2(id, `/turns/${enc6(turnId ?? "last")}`));
|
|
10703
11048
|
out(r, () => {
|
|
10704
11049
|
const tu = r.turn;
|
|
10705
|
-
const clip = (
|
|
11050
|
+
const clip = (s2) => full || s2.length <= 600 ? s2 : `${s2.slice(0, 600)}\u2026 ${dim(t(`(+${s2.length - 600} chars, --full)`, `(+${s2.length - 600} caracteres, --full)`))}`;
|
|
10706
11051
|
const io = [
|
|
10707
11052
|
`${lavender("in ")} ${r.input ? clip(r.input) : dim(t("(no copy of the message)", "(sin copia del mensaje)"))}`,
|
|
10708
11053
|
`${lavender("out")} ${r.output ? clip(r.output) : dim(t("(no answer saved)", "(sin respuesta guardada)"))}`
|
|
@@ -10711,11 +11056,11 @@ async function turnShow(id, turnId, full = false) {
|
|
|
10711
11056
|
const state = `${bold(tu.id)} ${tu.state === "error" ? red(tu.state) : tu.state} ${tu.secs ?? "?"} s${tu.session ? dim(` ${tu.session}`) : ""}${sdk}`;
|
|
10712
11057
|
if (!r.totals) return [state, ...io, dim(`${t("no timeline", "sin l\xEDnea de tiempo")}: ${r.why ? serverText(r.why) : t("no transcript", "sin transcript")}`)].join("\n");
|
|
10713
11058
|
const head = `${state} ${dim(t(`${r.totals.model ?? ""} \xB7 ${r.totals.modelCalls} model calls \xB7 ${r.totals.outputTokens} output tokens \xB7 ${r.totals.toolSecs} s in tools`, `${r.totals.model ?? ""} \xB7 ${r.totals.modelCalls} llamadas al modelo \xB7 ${r.totals.outputTokens} tokens de salida \xB7 ${r.totals.toolSecs} s en tools`))}`;
|
|
10714
|
-
const line2 = (
|
|
10715
|
-
const at = `+${
|
|
10716
|
-
if (
|
|
10717
|
-
const tok =
|
|
10718
|
-
return
|
|
11059
|
+
const line2 = (s2) => {
|
|
11060
|
+
const at = `+${s2.at.toFixed(1)}s`.padStart(8);
|
|
11061
|
+
if (s2.kind === "tool") return `${at} ${bold(s2.tool ?? "?")} ${dim(s2.input ?? "")} ${s2.secs === void 0 ? red(t("(no result)", "(sin resultado)")) : `${s2.secs} s`}${s2.error ? red(" error") : ""}`;
|
|
11062
|
+
const tok = s2.outTokens ? dim(` [${s2.outTokens} out]`) : "";
|
|
11063
|
+
return s2.kind === "thinking" ? `${at} ${dim(t("thinking", "pensando"))}${tok}` : `${at} ${s2.text}${tok}`;
|
|
10719
11064
|
};
|
|
10720
11065
|
const top = r.byTool.slice(0, 5).map((b) => `${b.tool} ${b.secs} s (${b.calls})`).join(" \xB7 ");
|
|
10721
11066
|
return [head, ...io, ...r.steps.map(line2), dim(`${t("slowest", "lo m\xE1s lento")}: ${top || "\u2014"}`)].join("\n");
|
|
@@ -10774,7 +11119,7 @@ async function dbsPhotosCmd(verb, id, db, v) {
|
|
|
10774
11119
|
results.push(...r.items);
|
|
10775
11120
|
if (!globals.json && files.length > 8) info(dim(` ${Math.min(i + 8, files.length)}/${files.length}`));
|
|
10776
11121
|
}
|
|
10777
|
-
const by = (
|
|
11122
|
+
const by = (s2) => results.filter((r) => r.status === s2);
|
|
10778
11123
|
const stored = by(v["dry-run"] ? "would_store" : "stored");
|
|
10779
11124
|
const missing = by("missing_row");
|
|
10780
11125
|
const bad = [...by("bad_type"), ...by("too_big"), ...by("failed")];
|
|
@@ -11034,7 +11379,7 @@ async function readMultiFileAuth(dir) {
|
|
|
11034
11379
|
|
|
11035
11380
|
// src/commands/channels.ts
|
|
11036
11381
|
var enc7 = encodeURIComponent;
|
|
11037
|
-
var digits = (
|
|
11382
|
+
var digits = (s2) => s2.replace(/\D/g, "");
|
|
11038
11383
|
function find2(nums, ref) {
|
|
11039
11384
|
const d = digits(ref);
|
|
11040
11385
|
const hit = nums.find((n) => n.id === ref) ?? nums.find((n) => n.label.toLowerCase() === ref.toLowerCase()) ?? (d.length >= 6 ? nums.find((n) => digits(n.label).endsWith(d)) : void 0);
|
|
@@ -11171,9 +11516,9 @@ async function waCmd2(sub, id, v) {
|
|
|
11171
11516
|
if (!id) throw usage(t("Missing agent id.", "Falta el id del agente."), t("Example", "Ejemplo") + `: ghosty channels wa ${sub} <agent-id>`);
|
|
11172
11517
|
const base = `/api/v2/agents/${enc7(id)}/wa`;
|
|
11173
11518
|
if (sub === "status") {
|
|
11174
|
-
const
|
|
11175
|
-
return out(
|
|
11176
|
-
${dim(serverText(
|
|
11519
|
+
const s2 = await api(base);
|
|
11520
|
+
return out(s2, () => `${s2.status === "connected" ? lavender(t("\u25CF connected", "\u25CF conectado")) : dim(`\u25CB ${s2.status}`)} ${s2.phone ? `+${s2.phone}` : dim(t("(no number)", "(sin n\xFAmero)"))}${s2.live ? "" : dim(t(" \xB7 no socket in gs", " \xB7 sin socket en gs"))}${s2.hasCreds ? "" : dim(t(" \xB7 no saved session", " \xB7 sin sesi\xF3n guardada"))}${s2.reason ? `
|
|
11521
|
+
${dim(serverText(s2.reason))}` : ""}`);
|
|
11177
11522
|
}
|
|
11178
11523
|
if (sub === "pair") {
|
|
11179
11524
|
const phone = (v.phone ?? "").replace(/\D/g, "");
|
|
@@ -11183,13 +11528,13 @@ ${dim(serverText(s.reason))}` : ""}`);
|
|
|
11183
11528
|
if (!globals.json) info(dim(t(`Mexico: +${phone} and +${other} are different WhatsApp accounts. Use the one shown in WhatsApp \u2192 Settings \u2192 your profile (newer numbers have no 1). If the code doesn't match, run: ghosty channels wa pause ${id} and pair again with --phone ${other}.`, `M\xE9xico: +${phone} y +${other} son cuentas distintas de WhatsApp. Usa el que sale en WhatsApp \u2192 Ajustes \u2192 tu perfil (los n\xFAmeros nuevos no llevan el 1). Si el c\xF3digo no empata: ghosty channels wa pause ${id} y vuelve a parear con --phone ${other}.`)));
|
|
11184
11529
|
}
|
|
11185
11530
|
await api(base, { method: "POST", body: { phone } });
|
|
11186
|
-
let
|
|
11187
|
-
for (let i = 0; i < 15 && !
|
|
11531
|
+
let s2 = { status: "connecting", phone: null, live: false, hasCreds: false };
|
|
11532
|
+
for (let i = 0; i < 15 && !s2.pairingCode && s2.status !== "connected"; i++) {
|
|
11188
11533
|
await new Promise((r2) => setTimeout(r2, 1e3));
|
|
11189
|
-
|
|
11534
|
+
s2 = await api(base);
|
|
11190
11535
|
}
|
|
11191
|
-
return out(
|
|
11192
|
-
${dim(t("On the phone: WhatsApp \u2192 Linked devices \u2192 Link with phone number \u2192 type it. Then: ghosty channels wa status ", "En el tel\xE9fono: WhatsApp \u2192 Dispositivos vinculados \u2192 Vincular con n\xFAmero de tel\xE9fono \u2192 escr\xEDbelo. Luego: ghosty channels wa status ") + id)}` : dim(t(`No code yet (status: ${
|
|
11536
|
+
return out(s2, () => s2.status === "connected" ? `${lavender("\u2713")} ${t("Already connected", "Ya est\xE1 conectado")} (+${s2.phone}).` : s2.pairingCode ? `${t("Code", "C\xF3digo")}: ${bold(s2.pairingCode)}
|
|
11537
|
+
${dim(t("On the phone: WhatsApp \u2192 Linked devices \u2192 Link with phone number \u2192 type it. Then: ghosty channels wa status ", "En el tel\xE9fono: WhatsApp \u2192 Dispositivos vinculados \u2192 Vincular con n\xFAmero de tel\xE9fono \u2192 escr\xEDbelo. Luego: ghosty channels wa status ") + id)}` : dim(t(`No code yet (status: ${s2.status}${s2.reason ? `, ${serverText(s2.reason)}` : ""}). Try again in a minute.`, `Todav\xEDa no hay c\xF3digo (estado: ${s2.status}${s2.reason ? `, ${s2.reason}` : ""}). Intenta de nuevo en un minuto.`)));
|
|
11193
11538
|
}
|
|
11194
11539
|
if (sub === "pause") {
|
|
11195
11540
|
await api(base, { method: "DELETE" });
|
|
@@ -11259,11 +11604,11 @@ function bareMediaFlag(argv) {
|
|
|
11259
11604
|
if (argv[0] !== "channels" || !argv.includes("history")) return argv;
|
|
11260
11605
|
return argv.map((a, i) => a === "--media" && (argv[i + 1] === void 0 || argv[i + 1].startsWith("-")) ? "--media=" : a);
|
|
11261
11606
|
}
|
|
11262
|
-
function pausedLine(
|
|
11263
|
-
if (!
|
|
11607
|
+
function pausedLine(s2, id) {
|
|
11608
|
+
if (!s2.paused && !s2.manual) return "";
|
|
11264
11609
|
const txt = t(
|
|
11265
|
-
`${
|
|
11266
|
-
`${
|
|
11610
|
+
`${s2.paused} paused by Formmy${s2.pausedForever ? ` (${s2.pausedForever} indefinitely)` : ""}, ${s2.manual} in manual mode: the agent doesn't answer them`,
|
|
11611
|
+
`${s2.paused} pausada(s) por Formmy${s2.pausedForever ? ` (${s2.pausedForever} sin fecha)` : ""}, ${s2.manual} en modo manual: el agente no las contesta`
|
|
11267
11612
|
);
|
|
11268
11613
|
return `
|
|
11269
11614
|
${lavender(txt)}
|
|
@@ -11587,6 +11932,10 @@ var HELPS_ES = {
|
|
|
11587
11932
|
summary: "Crea, revisa, cambia, diagnostica y borra agentes.",
|
|
11588
11933
|
replace: [
|
|
11589
11934
|
["# the agent as a file; see ghosty apply --help", "# el agente como archivo; ver ghosty apply --help"],
|
|
11935
|
+
["# its look", "# su apariencia"],
|
|
11936
|
+
["# its theme", "# su tema"],
|
|
11937
|
+
["# 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
|
+
["--style-file theme.json", "--style-file tema.json"],
|
|
11590
11939
|
["# 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)"],
|
|
11591
11940
|
["# change one piece of the prompt, on the server's current one", "# cambia un pedazo del prompt, sobre el actual del servidor"],
|
|
11592
11941
|
["# your account: merge copies with the same content", "# tu cuenta: une las copias con el mismo contenido"],
|
|
@@ -11640,6 +11989,23 @@ var HELPS_ES = {
|
|
|
11640
11989
|
['--chips "a|b|c"', "demo: sugerencias, separadas por |"],
|
|
11641
11990
|
["--sponsor on|off", "demo: las copias que la gente se queda corren con TU llave del motor"],
|
|
11642
11991
|
["--rotate", "demo: c\xF3digo nuevo; la liga vieja deja de servir"],
|
|
11992
|
+
["--domains a.com,*.b.com", "embed: d\xF3nde puede cargarse el widget (vac\xEDo = en ning\xFAn lado)"],
|
|
11993
|
+
["--welcome TEXT", "embed: el primer mensaje que ve el visitante (vac\xEDo = \xAB\xA1Hola! \xBFEn qu\xE9 te ayudo?\xBB)"],
|
|
11994
|
+
["--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)"],
|
|
11995
|
+
["--avatar URL|none", "embed: tu imagen (https JPEG/PNG/WebP, se rehospeda en img.ghosty.studio); none = el fantasma del agente"],
|
|
11996
|
+
["--icon chat|avatar|ghost", "embed: qu\xE9 lleva el bot\xF3n flotante"],
|
|
11997
|
+
["--placeholder T", "embed: el texto gris de la caja para escribir"],
|
|
11998
|
+
["--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)"],
|
|
11999
|
+
["--theme light|dark|auto", "embed: tema del panel del chat (auto = el del visitante)"],
|
|
12000
|
+
["--bubble-text T", "embed: etiqueta junto al bot\xF3n (\xAB\xBFDudas?\xBB)"],
|
|
12001
|
+
["--auto-open S|off", "embed: se abre solo a los S segundos (1-120), una vez por visita, nunca en el tel\xE9fono"],
|
|
12002
|
+
['--starters "a|b|c"', 'embed: hasta 4 sugerencias TUYAS ("" las quita; nunca salen las de Ghosty)'],
|
|
12003
|
+
["--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"],
|
|
12004
|
+
["--font NAME / --radius 0-24", "embed: atajos de style.fontFamily (lista de Google Fonts: Barlow, Inter, Poppins\u2026) y style.radius"],
|
|
12005
|
+
["--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
|
+
["--site-url URL", "embed: el sitio del que se sacan los colores (implica --theme-source site)"],
|
|
12007
|
+
["--style-file FILE", "embed: aplica un tema exportado con `agents embed <id> theme` (o s\xF3lo el objeto style)"],
|
|
12008
|
+
["--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"],
|
|
11643
12009
|
["--check PATH", "box: este archivo debe existir en la m\xE1quina (repetible), p. ej. un archivo que trae la imagen nueva"],
|
|
11644
12010
|
["--contains PATH=TEXT", "box: ese archivo debe contener TEXT (un cambio que no agrega archivos)"],
|
|
11645
12011
|
["--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"],
|
|
@@ -12101,6 +12467,10 @@ var HELPS = {
|
|
|
12101
12467
|
'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]',
|
|
12102
12468
|
"ghosty agents embed enable|disable <agent-id> [--domains a.com,*.b.com] # web widget: chat bubble on your site, only on those domains",
|
|
12103
12469
|
"ghosty agents embed <agent-id> [--domains \u2026] [--per-visitor N] [--per-ip N] [--welcome TEXT] [--color #hex] [--rotate] # state + the <script> to paste",
|
|
12470
|
+
'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
|
+
"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",
|
|
12472
|
+
"ghosty agents embed <agent-id> theme # export the theme as JSON (edit it, apply it back with --style-file)",
|
|
12473
|
+
"ghosty agents embed <agent-id> --out src/components/GhostyChat.tsx [--force] # your own React component, token and theme baked in",
|
|
12104
12474
|
"ghosty agents rm <agent-id> [--from-workspace] [--yes]"
|
|
12105
12475
|
],
|
|
12106
12476
|
flags: [
|
|
@@ -12144,6 +12514,23 @@ var HELPS = {
|
|
|
12144
12514
|
['--chips "a|b|c"', "demo: suggestions, separated by |"],
|
|
12145
12515
|
["--sponsor on|off", "demo: copies people keep run on YOUR engine key"],
|
|
12146
12516
|
["--rotate", "demo: new code; the old link stops working"],
|
|
12517
|
+
["--domains a.com,*.b.com", "embed: where the widget may load (empty = nowhere)"],
|
|
12518
|
+
["--welcome TEXT", "embed: the first message the visitor sees (empty = \xAB\xA1Hola! \xBFEn qu\xE9 te ayudo?\xBB)"],
|
|
12519
|
+
["--title T / --subtitle T", `embed: name in the widget header (default: the agent's name) and a small line under it ("Replies in minutes")`],
|
|
12520
|
+
["--avatar URL|none", "embed: your own image (https JPEG/PNG/WebP, re-hosted on img.ghosty.studio); none = the agent's ghost"],
|
|
12521
|
+
["--icon chat|avatar|ghost", "embed: what the floating button shows"],
|
|
12522
|
+
["--placeholder T", "embed: grey text in the message box"],
|
|
12523
|
+
["--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)"],
|
|
12524
|
+
["--theme light|dark|auto", "embed: the chat panel's theme (auto = the visitor's)"],
|
|
12525
|
+
["--bubble-text T", 'embed: label next to the button ("Questions?")'],
|
|
12526
|
+
["--auto-open S|off", "embed: open by itself after S seconds (1-120), once per visit, never on phones"],
|
|
12527
|
+
['--starters "a|b|c"', `embed: up to 4 of YOUR suggestions ("" removes them; Ghosty's are never shown)`],
|
|
12528
|
+
["--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"],
|
|
12529
|
+
["--font NAME / --radius 0-24", "embed: shortcuts for style.fontFamily (Google Fonts allow-list: Barlow, Inter, Poppins\u2026) and style.radius"],
|
|
12530
|
+
["--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
|
+
["--site-url URL", "embed: the site to take colors from (implies --theme-source site)"],
|
|
12532
|
+
["--style-file FILE", "embed: apply a theme exported with `agents embed <id> theme` (or a plain style object)"],
|
|
12533
|
+
["--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"],
|
|
12147
12534
|
["--check PATH", "box: this file must exist in the machine (repeatable), e.g. a file the new image brings"],
|
|
12148
12535
|
["--contains PATH=TEXT", "box: that file must contain TEXT (a change that adds no files)"],
|
|
12149
12536
|
["--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"],
|
|
@@ -12180,6 +12567,9 @@ var HELPS = {
|
|
|
12180
12567
|
"ghosty agents demo enable <id>",
|
|
12181
12568
|
'ghosty agents demo <id> --slug guardias --expires "2026-10-15T20:00" --starter "Ens\xE9\xF1ame c\xF3mo empezar"',
|
|
12182
12569
|
'ghosty agents embed enable <id> --domains "mitienda.com,*.mitienda.mx" --welcome "\xA1Hola! \xBFEn qu\xE9 te ayudo?"',
|
|
12570
|
+
'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
|
+
"ghosty agents embed <id> --theme-source site --site-url https://palmeralegal.mx",
|
|
12572
|
+
"ghosty agents embed <id> theme > tema.json \xB7 ghosty agents embed <id> --style-file tema.json",
|
|
12183
12573
|
"ghosty agents box <id> --contains /opt/claude-worker/skills/pdf-doc/render-doc.mjs=pdf-templates",
|
|
12184
12574
|
"ghosty agents set <id> --tool wa:actualizar_identidad=off,teams:actualizar_identidad=on",
|
|
12185
12575
|
'ghosty agents set minighosty --bag "MiniGhosty" --avatar https://www.ghosty.studio/avatars/minighosty.png',
|
|
@@ -12844,7 +13234,7 @@ async function updateNotice(current2) {
|
|
|
12844
13234
|
}
|
|
12845
13235
|
|
|
12846
13236
|
// src/resolve.ts
|
|
12847
|
-
var looksLikeId = (
|
|
13237
|
+
var looksLikeId = (s2) => /^c[a-z0-9]{20,}$/.test(s2) || /^[0-9a-f]{24}$/.test(s2);
|
|
12848
13238
|
function pickAgent(list, ref, workspace) {
|
|
12849
13239
|
const want = ref.trim().toLowerCase();
|
|
12850
13240
|
let hits = list.filter((a) => a.nombre.trim().toLowerCase() === want);
|
|
@@ -12918,7 +13308,7 @@ function subcommandsOf(cmd) {
|
|
|
12918
13308
|
for (const u of HELPS[cmd]?.usage ?? []) {
|
|
12919
13309
|
const m = new RegExp(`^ghosty ${cmd} ([a-z][a-z|-]*)`).exec(u);
|
|
12920
13310
|
if (m) {
|
|
12921
|
-
for (const
|
|
13311
|
+
for (const s2 of m[1].split("|")) if (!s2.includes("-") || /^[a-z]+-[a-z]+$/.test(s2)) subs.add(s2);
|
|
12922
13312
|
}
|
|
12923
13313
|
}
|
|
12924
13314
|
return [...subs];
|
|
@@ -13074,7 +13464,7 @@ function fillSecrets(file) {
|
|
|
13074
13464
|
};
|
|
13075
13465
|
const x = file["x-ghosty"];
|
|
13076
13466
|
fill(x?.env);
|
|
13077
|
-
for (const
|
|
13467
|
+
for (const s2 of x?.mcp ?? []) fill(s2.env), fill(s2.headers);
|
|
13078
13468
|
return missing;
|
|
13079
13469
|
}
|
|
13080
13470
|
async function readSkills(dir) {
|
|
@@ -13157,7 +13547,7 @@ async function boxesUsage(slug) {
|
|
|
13157
13547
|
const r = await api(`/api/v2/workspaces/${encodeURIComponent(slug)}/boxes`);
|
|
13158
13548
|
const rows = r.agents.map((a) => ({ agent: a.name ?? a.id, engine: a.engine, "per box": a.conversationsPerBox ?? "", boxes: a.boxes ?? "", on: a.boxesOn ?? "", spares: a.spares ?? "", "running now": a.activeTurns ?? "", ...a.error ? { note: a.error } : {} }));
|
|
13159
13549
|
const head = `${bold(r.workspace)} ${dim(t(`plan: ${r.plan.parallel} in parallel \xB7 up to ${r.plan.maxBoxes} box(es) \xB7 ${r.plan.hotBoxes} always-on spare(s)`, `plan: ${r.plan.parallel} en paralelo \xB7 hasta ${r.plan.maxBoxes} caja(s) \xB7 ${r.plan.hotBoxes} spare(s) siempre encendido(s)`))}`;
|
|
13160
|
-
const space = r.space ? (r.space.used > r.space.limit ? (
|
|
13550
|
+
const space = r.space ? (r.space.used > r.space.limit ? (s2) => s2 : dim)(
|
|
13161
13551
|
t(
|
|
13162
13552
|
`${r.space.used} of ${r.space.limit} box(es) in use \xB7 ${r.space.spares} spare \xB7 ${r.space.asleep} asleep${r.space.used > r.space.limit ? " \xB7 OVER the limit" : ""}`,
|
|
13163
13553
|
`${r.space.used} de ${r.space.limit} caja(s) \xB7 ${r.space.spares} spare \xB7 ${r.space.asleep} dormida(s)${r.space.used > r.space.limit ? " \xB7 SOBRE el tope" : ""}`
|
|
@@ -13428,7 +13818,7 @@ async function history(agent, v) {
|
|
|
13428
13818
|
lines.push("", bold(`${c.sessionId}`) + dim(` \xB7 ${hora(c.createdAt)} \u2192 ${hora(c.updatedAt)}`));
|
|
13429
13819
|
for (const tu of c.turns) lines.push(` ${t("turn", "turno")} ${hora(tu.startedAt)} \xB7 ${tu.state === "error" ? red(tu.state) : tu.state} \xB7 ${tu.secs != null ? `${tu.secs} s` : t("not closed", "sin cerrar")}${tu.error ? red(` \xB7 ${serverText(tu.error)}`) : ""}`);
|
|
13430
13820
|
for (const m of c.messages) lines.push(` ${m.role === "user" ? "\u{1F464}" : "\u{1F916}"} ${m.text}${m.attachments.length ? dim(` [${t("attached", "adjuntos")}: ${m.attachments.join(", ")}]`) : ""}${m.delivered.length ? dim(` [${t("delivered", "entreg\xF3")}: ${m.delivered.join(", ")}]`) : ""}`);
|
|
13431
|
-
for (const
|
|
13821
|
+
for (const s2 of c.timeline ?? []) lines.push(dim(s2.kind === "turn" ? ` \u2500\u2500 ${t("turn", "turno")} ${hora(s2.at)}` : ` ${String(s2.secs).padStart(4)}s ${s2.kind === "tool" ? `${s2.tool}: ${s2.input} (out ${s2.outTokens ?? "?"})` : `\xAB${s2.text}\xBB`}`));
|
|
13432
13822
|
}
|
|
13433
13823
|
if (!r.conversations.length) lines.push("", dim(t("No visitors in that range.", "Sin visitantes en ese rango.")));
|
|
13434
13824
|
return lines.join("\n");
|
|
@@ -13560,8 +13950,8 @@ async function collectSiteFiles(target) {
|
|
|
13560
13950
|
}
|
|
13561
13951
|
function titleOf(html) {
|
|
13562
13952
|
const m = html.match(/<title[^>]*>([^<]*)<\/title>/i);
|
|
13563
|
-
const
|
|
13564
|
-
return
|
|
13953
|
+
const s2 = m?.[1]?.replace(/\s+/g, " ").trim();
|
|
13954
|
+
return s2 || null;
|
|
13565
13955
|
}
|
|
13566
13956
|
var kb = (n) => n ? `${Math.max(1, Math.round(n / 1024))} KB` : "";
|
|
13567
13957
|
var day = (iso) => iso ? iso.slice(0, 10) : "";
|
|
@@ -13571,7 +13961,7 @@ async function sitesCmd(sub, a1, a2, v) {
|
|
|
13571
13961
|
case "ls":
|
|
13572
13962
|
case "list": {
|
|
13573
13963
|
const r = await api(`/api/v2/sites?scope=${v.house ? "house" : "mine"}`);
|
|
13574
|
-
const rows = r.sites.map((
|
|
13964
|
+
const rows = r.sites.map((s2) => ({ slug: s2.slug, title: (s2.title ?? "").slice(0, 40), url: s2.url, files: s2.files ?? "", size: kb(s2.bytes), published: day(s2.publishedAt), expires: s2.expiresAt ? day(s2.expiresAt) : t("never", "nunca") }));
|
|
13575
13965
|
return out(r, () => `${bold(v.house ? t("House sites", "Sitios de la casa") : t("Your sites", "Tus sitios"))}
|
|
13576
13966
|
${table(rows, ["slug", "title", "url", "files", "size", "published", "expires"])}`);
|
|
13577
13967
|
}
|
|
@@ -13607,8 +13997,8 @@ function detailLine(d, max = 90) {
|
|
|
13607
13997
|
if (d === null || d === void 0) return "";
|
|
13608
13998
|
if (typeof d !== "object") return String(d).slice(0, max);
|
|
13609
13999
|
const parts = Object.entries(d).filter(([k]) => k !== "via").map(([k, v]) => `${k}=${v !== null && typeof v === "object" ? JSON.stringify(v) : String(v)}`);
|
|
13610
|
-
const
|
|
13611
|
-
return
|
|
14000
|
+
const s2 = parts.join(" ");
|
|
14001
|
+
return s2.length > max ? `${s2.slice(0, max - 1)}\u2026` : s2;
|
|
13612
14002
|
}
|
|
13613
14003
|
async function auditCmd(sub, v, context) {
|
|
13614
14004
|
if (sub && sub !== "ls" && sub !== "list") throw usage(t(`Unknown audit subcommand: ${sub}.`, `Subcomando de audit desconocido: ${sub}.`), "ghosty audit ls [--workspace SLUG] [--agent ID] [--actor EMAIL] [--action A] [--since 24h] [--limit N]");
|
|
@@ -13635,7 +14025,7 @@ ${dim(t("Times in UTC. --full for the whole detail; --json for everything.", "Ho
|
|
|
13635
14025
|
|
|
13636
14026
|
// src/commands/factory.ts
|
|
13637
14027
|
var enc12 = encodeURIComponent;
|
|
13638
|
-
var short2 = (
|
|
14028
|
+
var short2 = (s2) => (s2 ?? "").slice(0, 16).replace("T", " ");
|
|
13639
14029
|
async function factoryCmd(sub, verb, ref, title, v, context) {
|
|
13640
14030
|
if (sub !== "runs") throw usage(t(`Unknown factory subcommand: ${sub ?? "(none)"}.`, `Subcomando de factory desconocido: ${sub ?? "(ninguno)"}.`), "ghosty factory runs ls|show|rename \u2026");
|
|
13641
14031
|
const ws = v.workspace ?? context;
|
|
@@ -13753,7 +14143,7 @@ async function supportThread(agent, sid, v) {
|
|
|
13753
14143
|
const lines = [
|
|
13754
14144
|
`${bold(r.agent.name ?? r.agent.id)} \xB7 ${r.title ?? sid} ${dim(`(${t("from", "de")} ${x.source})`)}`,
|
|
13755
14145
|
...x.note ? [dim(` ${serverText(x.note)}`)] : [],
|
|
13756
|
-
...x.tools.map((
|
|
14146
|
+
...x.tools.map((s2) => `${dim(s2.at ? cdmx(s2.at) : " \xB7 ")} ${bold(s2.tool)} ${s2.input.replace(/\s+/g, " ").slice(0, 300)}${s2.status && s2.status !== "completed" ? dim(` [${s2.status}]`) : ""}`),
|
|
13757
14147
|
"",
|
|
13758
14148
|
x.domains.length ? `${t("External domains", "Dominios externos")}: ${x.domains.map((d) => `${d.host} (${d.n})`).join(", ")}` : dim(t("No external domains named in tool inputs.", "Ninguna tool nombr\xF3 dominios externos."))
|
|
13759
14149
|
];
|
|
@@ -14007,6 +14397,11 @@ ${bold("Agents")}
|
|
|
14007
14397
|
agents demo enable|disable <id> Turn its public demo link on or off
|
|
14008
14398
|
agents demo <id> Public demo link /p/<slug>: anyone tries it without an account, "Keep it" clones it to theirs
|
|
14009
14399
|
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]
|
|
14400
|
+
look: [--title T] [--subtitle T] [--avatar URL|none] [--icon chat|avatar|ghost] [--placeholder T] [--position right|left] [--offset X[,Y]]
|
|
14401
|
+
[--theme light|dark|auto] [--bubble-text T] [--auto-open SECONDS|off] [--starters "a|b|c"] [--font NAME] [--radius 0-24] [--style '{json}'|none]
|
|
14402
|
+
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)
|
|
14404
|
+
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
|
|
14010
14405
|
agents embed <id> Its state and the snippet to paste (claude engine only: it isolates each visitor)
|
|
14011
14406
|
[--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]
|
|
14012
14407
|
|
|
@@ -14165,7 +14560,12 @@ ${bold("Agentes")}
|
|
|
14165
14560
|
agents demo enable|disable <id> Prende o apaga su liga p\xFAblica de demo
|
|
14166
14561
|
agents demo <id> Liga p\xFAblica de demo /p/<slug>: cualquiera lo prueba sin cuenta y \xABQued\xE1rmelo\xBB lo clona a la suya
|
|
14167
14562
|
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]
|
|
14168
|
-
|
|
14563
|
+
look: [--title T] [--subtitle T] [--avatar URL|none] [--icon chat|avatar|ghost] [--placeholder T] [--position right|left] [--offset X[,Y]]
|
|
14564
|
+
[--theme light|dark|auto] [--bubble-text T] [--auto-open SEGUNDOS|off] [--starters "a|b|c"] [--font NOMBRE] [--radius 0-24] [--style '{json}'|none]
|
|
14565
|
+
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)
|
|
14567
|
+
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
|
+
agents embed <id> Su estado y el snippet para pegar (Claude o MiniGhosty: a\xEDslan a cada visitante)
|
|
14169
14569
|
[--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]
|
|
14170
14570
|
|
|
14171
14571
|
${bold("Configurar un agente")}
|
|
@@ -14261,8 +14661,8 @@ ${dim("Documentaci\xF3n: https://www.ghosty.studio/docs/cli")}`;
|
|
|
14261
14661
|
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"];
|
|
14262
14662
|
var AGENTS_SUBS = ["ls", "create", "get", "set", "export", "doctor", "docs", "share", "unshare", "shares", "teams", "rm", "restart", "demo", "embed", "box"];
|
|
14263
14663
|
function unknown(what, input, known, helpCmd) {
|
|
14264
|
-
const
|
|
14265
|
-
return usage(t(`Unknown command: ${what}.`, `Comando desconocido: ${what}.`),
|
|
14664
|
+
const s2 = suggest(input, known);
|
|
14665
|
+
return usage(t(`Unknown command: ${what}.`, `Comando desconocido: ${what}.`), s2 ? t(`Did you mean "${s2}"? Run: ${helpCmd}`, `\xBFQuisiste decir "${s2}"? Corre: ${helpCmd}`) : t(`Run: ${helpCmd}`, `Corre: ${helpCmd}`));
|
|
14266
14666
|
}
|
|
14267
14667
|
function showHelp() {
|
|
14268
14668
|
if (process.stdout.isTTY && !process.env.NO_COLOR && !globals.json) process.stdout.write(BANNER + "\n\n");
|
|
@@ -14348,6 +14748,21 @@ async function main() {
|
|
|
14348
14748
|
chips: { type: "string" },
|
|
14349
14749
|
starter: { type: "string" },
|
|
14350
14750
|
rotate: { type: "boolean" },
|
|
14751
|
+
subtitle: { type: "string" },
|
|
14752
|
+
placeholder: { type: "string" },
|
|
14753
|
+
position: { type: "string" },
|
|
14754
|
+
offset: { type: "string" },
|
|
14755
|
+
theme: { type: "string" },
|
|
14756
|
+
icon: { type: "string" },
|
|
14757
|
+
"bubble-text": { type: "string" },
|
|
14758
|
+
"auto-open": { type: "string" },
|
|
14759
|
+
starters: { type: "string" },
|
|
14760
|
+
style: { type: "string" },
|
|
14761
|
+
font: { type: "string" },
|
|
14762
|
+
radius: { type: "string" },
|
|
14763
|
+
"style-file": { type: "string" },
|
|
14764
|
+
"theme-source": { type: "string" },
|
|
14765
|
+
"site-url": { type: "string" },
|
|
14351
14766
|
sponsor: { type: "string" },
|
|
14352
14767
|
yes: { type: "boolean", short: "y" },
|
|
14353
14768
|
dir: { type: "string" },
|
|
@@ -14500,7 +14915,7 @@ async function main() {
|
|
|
14500
14915
|
const tokens = splitTokens(pos, v);
|
|
14501
14916
|
globals.token = tokens.session;
|
|
14502
14917
|
globals.profile = v.profile;
|
|
14503
|
-
if (v.version) return out({ version: "0.
|
|
14918
|
+
if (v.version) return out({ version: "0.53.0" }, () => "0.53.0");
|
|
14504
14919
|
for (const n of normalizeArgs(pos, v)) if (!globals.json) info(dim(n));
|
|
14505
14920
|
const context = (await readConfig()).workspace;
|
|
14506
14921
|
if (!explicitToken()?.startsWith("gat_")) {
|
|
@@ -14568,7 +14983,7 @@ async function main() {
|
|
|
14568
14983
|
if (sub === "docs" || sub === "documents") return agentDocsCmd(a1, a2, pos.slice(4).filter((x) => x !== void 0), v);
|
|
14569
14984
|
if (sub === "restart") return agentsRestart(a1);
|
|
14570
14985
|
if (sub === "demo") return agentsDemo(a1, v);
|
|
14571
|
-
if (sub === "embed" || sub === "widget") return agentsEmbed(a1, v);
|
|
14986
|
+
if (sub === "embed" || sub === "widget") return agentsEmbed(a1, v, a2);
|
|
14572
14987
|
if (sub === "box") return agentsBox(a1, v);
|
|
14573
14988
|
if (sub === "export") return agentsExport(a1, v);
|
|
14574
14989
|
throw unknown(`agents ${sub}`, sub, AGENTS_SUBS, "ghosty agents --help");
|
|
@@ -14666,7 +15081,7 @@ async function main() {
|
|
|
14666
15081
|
throw unknown(cmd, cmd, COMMANDS, "ghosty --help");
|
|
14667
15082
|
}
|
|
14668
15083
|
}
|
|
14669
|
-
var notice = updateNotice("0.
|
|
15084
|
+
var notice = updateNotice("0.53.0").catch(() => null);
|
|
14670
15085
|
main().then(
|
|
14671
15086
|
async () => {
|
|
14672
15087
|
const n = await notice;
|
package/package.json
CHANGED