@ghostystudio/cli 0.52.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.
- package/README.md +11 -1
- package/dist/index.js +469 -98
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -42,6 +42,8 @@ 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
|
|
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)
|
|
45
47
|
ghosty agents doctor <id> [--verbose] # exits 1 when it finds a problem
|
|
46
48
|
ghosty agents docs ls|grant|revoke <id> [doc] [--write] [--no-deliver]
|
|
47
49
|
ghosty agents docs add <id> --url URL | --file PATH [--description TEXT] [--grant read|write|no]
|
|
@@ -107,6 +109,14 @@ Every command has its own help with examples: `ghosty <command> --help` or `ghos
|
|
|
107
109
|
|
|
108
110
|
Exit codes: `0` ok · `1` API error · `2` usage error · `3` not signed in.
|
|
109
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
|
+
|
|
110
120
|
### Changes in 0.52.0
|
|
111
121
|
|
|
112
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.
|
|
@@ -115,7 +125,7 @@ Exit codes: `0` ok · `1` API error · `2` usage error · `3` not signed in.
|
|
|
115
125
|
|
|
116
126
|
### Changes in 0.51.0
|
|
117
127
|
|
|
118
|
-
- `agents embed enable|disable <id> [--domains a.com,*.b.com] [--per-visitor N] [--per-ip N] [--welcome T] [--color #hex] [--rotate]`: the **web widget**, a chat bubble with the agent on your own site (one `<script>`), loading only on the domains you list; each visitor gets an isolated conversation and talks as a customer. Claude engine only. No flags = state + the snippet.
|
|
128
|
+
- `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.
|
|
119
129
|
|
|
120
130
|
### Changes in 0.50.0
|
|
121
131
|
|
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.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.
|
|
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
|
|
@@ -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,303 @@ function lineDiff(before, after, context = 2) {
|
|
|
8764
8764
|
return lines.join("\n");
|
|
8765
8765
|
}
|
|
8766
8766
|
|
|
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
|
+
};
|
|
8786
|
+
var DEFAULT_BASE = "https://www.ghosty.studio";
|
|
8787
|
+
function baseFromSnippet(snippet) {
|
|
8788
|
+
const m = snippet?.match(/src="(https?:\/\/[^"]+?)\/widget\.js"/);
|
|
8789
|
+
return m ? m[1] : DEFAULT_BASE;
|
|
8790
|
+
}
|
|
8791
|
+
function reactSnippet(token, baseUrl2) {
|
|
8792
|
+
const base = baseUrl2 === DEFAULT_BASE ? "" : ` baseUrl="${baseUrl2}"`;
|
|
8793
|
+
return {
|
|
8794
|
+
install: "npm i @ghostystudio/widget",
|
|
8795
|
+
code: `import { GhostyWidget } from "@ghostystudio/widget";
|
|
8796
|
+
|
|
8797
|
+
<GhostyWidget token="${token}"${base} />`
|
|
8798
|
+
};
|
|
8799
|
+
}
|
|
8800
|
+
var s = (v) => JSON.stringify(v ?? null);
|
|
8801
|
+
var hexOr = (v, def) => typeof v === "string" && /^#[0-9a-f]{6}$/i.test(v) ? v : def;
|
|
8802
|
+
function reactComponentSource(th, ts) {
|
|
8803
|
+
const T = (x) => ts ? x : "";
|
|
8804
|
+
const st = th.style ?? {};
|
|
8805
|
+
const radius = typeof st.radius === "number" ? st.radius : 16;
|
|
8806
|
+
const font = typeof st.fontFamily === "string" ? st.fontFamily : null;
|
|
8807
|
+
const cp = { ...COPY_DEFAULTS, ...th.copy ?? {} };
|
|
8808
|
+
return `// Chat de Ghosty Studio \u2014 generado por \`ghosty agents embed --out\`. Este archivo es TUYO:
|
|
8809
|
+
// c\xE1mbialo como quieras; no depende de ning\xFAn paquete, s\xF3lo de React.
|
|
8810
|
+
// Habla con tu agente por el contrato del widget web (/embed/w/<token>/v/<visitante>/\u2026).
|
|
8811
|
+
// \u26A0\uFE0F S\xF3lo funciona en los dominios que autorizaste en el widget (ficha \u2192 Canales \u2192 Widget web,
|
|
8812
|
+
// o \`ghosty agents embed <id> --domains tusitio.com\`). En localhost el servidor lo rechaza.
|
|
8813
|
+
import { useEffect, useRef, useState } from "react";
|
|
8814
|
+
|
|
8815
|
+
// \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
|
|
8816
|
+
// El token del widget. Si lo rotas (\`--rotate\`), c\xE1mbialo aqu\xED.
|
|
8817
|
+
const TOKEN = ${s(th.token)};
|
|
8818
|
+
const BASE_URL = ${s(th.baseUrl)};
|
|
8819
|
+
|
|
8820
|
+
// \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
|
|
8821
|
+
const TITLE = ${s(th.title)};
|
|
8822
|
+
const SUBTITLE = ${s(th.subtitle)}; // null = sin rengl\xF3n bajo el t\xEDtulo
|
|
8823
|
+
const WELCOME = ${s(th.welcome)}; // el primer mensaje que ve el visitante
|
|
8824
|
+
const PLACEHOLDER = ${s(th.placeholder ?? `Escr\xEDbele a ${th.title}`)};
|
|
8825
|
+
const STARTERS${T(": string[]")} = ${s(th.starters)}; // sugerencias (se ocultan al primer mensaje)
|
|
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
|
+
|
|
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
|
|
8838
|
+
const THEME = {
|
|
8839
|
+
font: ${s(font ? `"${font}", ui-sans-serif, system-ui, sans-serif` : "ui-sans-serif, system-ui, sans-serif")},
|
|
8840
|
+
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
|
|
8841
|
+
radius: ${radius}, // esquinas en px
|
|
8842
|
+
headerBg: ${s(hexOr(st.headerBg, th.color))},
|
|
8843
|
+
headerText: ${s(hexOr(st.headerText, th.textColor))},
|
|
8844
|
+
panelBg: ${s(hexOr(st.panelBg, "#ffffff"))},
|
|
8845
|
+
userBubbleBg: ${s(hexOr(st.userBubbleBg, th.color))},
|
|
8846
|
+
userBubbleText: ${s(hexOr(st.userBubbleText, th.textColor))},
|
|
8847
|
+
agentBubbleBg: ${s(hexOr(st.agentBubbleBg, "#f1f1f4"))},
|
|
8848
|
+
agentBubbleText: ${s(hexOr(st.agentBubbleText, "#191a20"))},
|
|
8849
|
+
inputBg: ${s(hexOr(st.inputBg, "#ffffff"))},
|
|
8850
|
+
sendBg: ${s(hexOr(st.sendBg, th.color))},
|
|
8851
|
+
sendIcon: ${s(hexOr(st.sendIcon, th.textColor))},
|
|
8852
|
+
titleUppercase: ${st.titleUppercase === true},
|
|
8853
|
+
};
|
|
8854
|
+
|
|
8855
|
+
${T(`type Msg = { id: string; role: "user" | "assistant"; text: string; pending?: boolean };
|
|
8856
|
+
type Status = "connecting" | "idle" | "thinking" | "streaming";
|
|
8857
|
+
|
|
8858
|
+
`)}// El visitante: un uuid por navegador (misma llave que el widget). B\xF3rralo para empezar de cero.
|
|
8859
|
+
const VISITOR_KEY = "ghosty:widget:vid";
|
|
8860
|
+
function visitorId()${T(": string")} {
|
|
8861
|
+
try {
|
|
8862
|
+
const v = localStorage.getItem(VISITOR_KEY);
|
|
8863
|
+
if (v && /^[0-9a-f-]{36}$/.test(v)) return v;
|
|
8864
|
+
const fresh = crypto.randomUUID();
|
|
8865
|
+
localStorage.setItem(VISITOR_KEY, fresh);
|
|
8866
|
+
return fresh;
|
|
8867
|
+
} catch {
|
|
8868
|
+
return crypto.randomUUID();
|
|
8869
|
+
}
|
|
8870
|
+
}
|
|
8871
|
+
|
|
8872
|
+
// \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
|
|
8873
|
+
export function useGhostyChat() {
|
|
8874
|
+
const [messages, setMessages] = useState${T("<Msg[]>")}([]);
|
|
8875
|
+
const [status, setStatus] = useState${T("<Status>")}("connecting");
|
|
8876
|
+
const [error, setError] = useState${T("<string | null>")}(null);
|
|
8877
|
+
const base = useRef${T("<string | null>")}(null);
|
|
8878
|
+
const waiting = useRef(false);
|
|
8879
|
+
|
|
8880
|
+
useEffect(() => {
|
|
8881
|
+
const url = \`\${BASE_URL}/embed/w/\${TOKEN}/v/\${visitorId()}\`;
|
|
8882
|
+
base.current = url;
|
|
8883
|
+
let es${T(": EventSource | null")} = null;
|
|
8884
|
+
let closed = false;
|
|
8885
|
+
// Texto del agente: se pega a la burbuja en curso o abre una nueva.
|
|
8886
|
+
const put = (text${T(": string")}, replace${T(": boolean")}) =>
|
|
8887
|
+
setMessages((ms) => {
|
|
8888
|
+
const last = ms[ms.length - 1];
|
|
8889
|
+
if (last && last.role === "assistant" && last.pending) return [...ms.slice(0, -1), { ...last, text: replace ? text : last.text + text }];
|
|
8890
|
+
return [...ms, { id: crypto.randomUUID(), role: "assistant", text, pending: true }];
|
|
8891
|
+
});
|
|
8892
|
+
const settle = () => setMessages((ms) => ms.map((m) => (m.pending ? { ...m, pending: false } : m)).filter((m) => m.role === "user" || m.text));
|
|
8893
|
+
const data = (e${T(": Event")}) => {
|
|
8894
|
+
try {
|
|
8895
|
+
return JSON.parse((e${T(" as MessageEvent")}).data || "{}");
|
|
8896
|
+
} catch {
|
|
8897
|
+
return {};
|
|
8898
|
+
}
|
|
8899
|
+
};
|
|
8900
|
+
|
|
8901
|
+
fetch(\`\${url}/history\`)
|
|
8902
|
+
.then((r) => (r.ok ? r.json() : { messages: [] }))
|
|
8903
|
+
.catch(() => ({ messages: [] }))
|
|
8904
|
+
.then((j) => {
|
|
8905
|
+
if (closed) return;
|
|
8906
|
+
setMessages((j.messages ?? []).map((m${T(": { role: string; text: string }")}) => ({ id: crypto.randomUUID(), role: m.role === "user" ? "user" : "assistant", text: m.text })));
|
|
8907
|
+
setStatus("idle");
|
|
8908
|
+
es = new EventSource(\`\${url}/events\`);
|
|
8909
|
+
// Reconexi\xF3n: el texto del turno vuelve a llegar entero, se vac\xEDa la burbuja en curso.
|
|
8910
|
+
es.addEventListener("started", () => setMessages((ms) => ms.map((m) => (m.pending ? { ...m, text: "" } : m))));
|
|
8911
|
+
es.addEventListener("chunk", (e) => {
|
|
8912
|
+
const d = data(e);
|
|
8913
|
+
if (d.text) { put(d.text, false); setStatus("streaming"); }
|
|
8914
|
+
});
|
|
8915
|
+
es.addEventListener("reemplazo", (e) => { const d = data(e); if (typeof d.text === "string") put(d.text, true); });
|
|
8916
|
+
es.addEventListener("error", (e) => { const d = data(e); if (d.message) setError(d.message); });
|
|
8917
|
+
es.addEventListener("done", (e) => {
|
|
8918
|
+
const d = data(e);
|
|
8919
|
+
// \`reposo\`: llega al conectar (no hab\xEDa turno). S\xF3lo cuenta si esper\xE1bamos respuesta.
|
|
8920
|
+
if (d.reposo && !waiting.current) return;
|
|
8921
|
+
if (d.reposo && d.ultimo?.text) put(d.ultimo.text, true);
|
|
8922
|
+
if (d.interrumpido) setError(COPY.interrupted);
|
|
8923
|
+
waiting.current = false;
|
|
8924
|
+
settle();
|
|
8925
|
+
setStatus("idle");
|
|
8926
|
+
});
|
|
8927
|
+
});
|
|
8928
|
+
return () => {
|
|
8929
|
+
closed = true;
|
|
8930
|
+
es?.close();
|
|
8931
|
+
};
|
|
8932
|
+
}, []);
|
|
8933
|
+
|
|
8934
|
+
async function send(text${T(": string")}) {
|
|
8935
|
+
const content = text.trim();
|
|
8936
|
+
if (!content || !base.current) return;
|
|
8937
|
+
setError(null);
|
|
8938
|
+
waiting.current = true;
|
|
8939
|
+
setStatus("thinking");
|
|
8940
|
+
setMessages((ms) => [...ms, { id: crypto.randomUUID(), role: "user", text: content }]);
|
|
8941
|
+
const r = await fetch(\`\${base.current}/messages\`, {
|
|
8942
|
+
method: "POST",
|
|
8943
|
+
headers: { "Content-Type": "application/json" },
|
|
8944
|
+
body: JSON.stringify({ content, turnId: crypto.randomUUID() }),
|
|
8945
|
+
}).catch(() => null);
|
|
8946
|
+
if (!r || !r.ok) {
|
|
8947
|
+
// Los topes (mensajes por d\xEDa) y avisos llegan como texto listo para ense\xF1ar.
|
|
8948
|
+
waiting.current = false;
|
|
8949
|
+
setStatus("idle");
|
|
8950
|
+
setError(r ? (await r.text().catch(() => "")) || \`error \${r.status}\` : "no se pudo enviar el mensaje");
|
|
8951
|
+
}
|
|
8952
|
+
}
|
|
8953
|
+
|
|
8954
|
+
async function stop() {
|
|
8955
|
+
if (base.current) await fetch(\`\${base.current}/cancel\`, { method: "POST", headers: { "Content-Type": "application/json" }, body: "{}" }).catch(() => {});
|
|
8956
|
+
}
|
|
8957
|
+
|
|
8958
|
+
return { messages, send, stop, status, error };
|
|
8959
|
+
}
|
|
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
|
+
|
|
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
|
|
8975
|
+
export default function GhostyChat() {
|
|
8976
|
+
const { messages, send, stop, status, error } = useGhostyChat();
|
|
8977
|
+
const waitLabel = useWaitLabel(status === "thinking");
|
|
8978
|
+
const [text, setText] = useState("");
|
|
8979
|
+
const end = useRef${T("<HTMLDivElement>")}(null);
|
|
8980
|
+
const busy = status === "thinking" || status === "streaming";
|
|
8981
|
+
|
|
8982
|
+
useEffect(() => {
|
|
8983
|
+
end.current?.scrollIntoView({ behavior: "smooth", block: "end" });
|
|
8984
|
+
}, [messages]);
|
|
8985
|
+
useEffect(() => {
|
|
8986
|
+
if (!THEME.fontHref) return;
|
|
8987
|
+
const l = document.createElement("link");
|
|
8988
|
+
l.rel = "stylesheet";
|
|
8989
|
+
l.href = THEME.fontHref;
|
|
8990
|
+
document.head.appendChild(l);
|
|
8991
|
+
return () => l.remove();
|
|
8992
|
+
}, []);
|
|
8993
|
+
|
|
8994
|
+
const submit = (e${T("?: { preventDefault: () => void }")}) => {
|
|
8995
|
+
e?.preventDefault();
|
|
8996
|
+
if (busy || !text.trim()) return;
|
|
8997
|
+
void send(text);
|
|
8998
|
+
setText("");
|
|
8999
|
+
};
|
|
9000
|
+
|
|
9001
|
+
const bubble = (role${T(': "user" | "assistant"')}) => ({
|
|
9002
|
+
maxWidth: "85%",
|
|
9003
|
+
padding: "8px 12px",
|
|
9004
|
+
borderRadius: THEME.radius,
|
|
9005
|
+
whiteSpace: "pre-wrap"${T(" as const")},
|
|
9006
|
+
alignSelf: role === "user" ? "flex-end" : "flex-start",
|
|
9007
|
+
background: role === "user" ? THEME.userBubbleBg : THEME.agentBubbleBg,
|
|
9008
|
+
color: role === "user" ? THEME.userBubbleText : THEME.agentBubbleText,
|
|
9009
|
+
});
|
|
9010
|
+
|
|
9011
|
+
return (
|
|
9012
|
+
<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)" }}>
|
|
9013
|
+
<header style={{ padding: "12px 16px", background: THEME.headerBg, color: THEME.headerText }}>
|
|
9014
|
+
<div style={{ fontWeight: 600, fontSize: 14, textTransform: THEME.titleUppercase ? "uppercase" : "none", letterSpacing: THEME.titleUppercase ? "0.12em" : undefined }}>{TITLE}</div>
|
|
9015
|
+
{SUBTITLE && <div style={{ fontSize: 12, opacity: 0.8 }}>{SUBTITLE}</div>}
|
|
9016
|
+
</header>
|
|
9017
|
+
|
|
9018
|
+
<div style={{ flex: 1, overflowY: "auto", padding: 16, display: "flex", flexDirection: "column", gap: 8, fontSize: 14, lineHeight: 1.45 }}>
|
|
9019
|
+
<div style={bubble("assistant")}>{WELCOME}</div>
|
|
9020
|
+
{messages.map((m) => (
|
|
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>
|
|
9029
|
+
))}
|
|
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>}
|
|
9032
|
+
{!messages.length && STARTERS.length > 0 && (
|
|
9033
|
+
<div style={{ display: "flex", flexWrap: "wrap", gap: 6 }}>
|
|
9034
|
+
{STARTERS.map((s) => (
|
|
9035
|
+
<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" }}>
|
|
9036
|
+
{s}
|
|
9037
|
+
</button>
|
|
9038
|
+
))}
|
|
9039
|
+
</div>
|
|
9040
|
+
)}
|
|
9041
|
+
{error && <div style={{ color: "#b42318", fontSize: 13 }}>{error}</div>}
|
|
9042
|
+
<div ref={end} />
|
|
9043
|
+
</div>
|
|
9044
|
+
|
|
9045
|
+
<form onSubmit={submit} style={{ display: "flex", gap: 8, padding: 12, borderTop: "1px solid rgba(0,0,0,.08)" }}>
|
|
9046
|
+
<input
|
|
9047
|
+
value={text}
|
|
9048
|
+
onChange={(e) => setText(e.target.value)}
|
|
9049
|
+
placeholder={PLACEHOLDER}
|
|
9050
|
+
style={{ flex: 1, padding: "10px 12px", borderRadius: THEME.radius, border: "1px solid rgba(0,0,0,.15)", background: THEME.inputBg, fontSize: 16, fontFamily: "inherit" }}
|
|
9051
|
+
/>
|
|
9052
|
+
{busy ? (
|
|
9053
|
+
<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>
|
|
9054
|
+
) : (
|
|
9055
|
+
<button type="submit" aria-label="Enviar" style={{ width: 42, borderRadius: THEME.radius, border: 0, background: THEME.sendBg, color: THEME.sendIcon, cursor: "pointer" }}>\u2191</button>
|
|
9056
|
+
)}
|
|
9057
|
+
</form>
|
|
9058
|
+
</div>
|
|
9059
|
+
);
|
|
9060
|
+
}
|
|
9061
|
+
`;
|
|
9062
|
+
}
|
|
9063
|
+
|
|
8767
9064
|
// src/commands/agents.ts
|
|
8768
9065
|
var enc2 = encodeURIComponent;
|
|
8769
9066
|
var agentPath = (id, rest = "") => `/api/v2/agents/${enc2(id)}${rest}`;
|
|
@@ -8775,9 +9072,9 @@ var need = (v, what, example) => {
|
|
|
8775
9072
|
async function agentsLs(workspace, f = {}) {
|
|
8776
9073
|
if (f.sponsor || f.owner) {
|
|
8777
9074
|
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(
|
|
9075
|
+
const s2 = await api(`/api/v2/agents?${q}`);
|
|
9076
|
+
if (f.ids) return void process.stdout.write(s2.agents.map((a) => a.id).join("\n") + (s2.agents.length ? "\n" : ""));
|
|
9077
|
+
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
9078
|
}
|
|
8782
9079
|
const r = await api("/api/v2/me/agents?client=cli");
|
|
8783
9080
|
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 +9282,8 @@ ${dim(t(`${r3.beforeChars} \u2192 ${r3.chars} chars. Dry run: nothing changed.`,
|
|
|
8985
9282
|
const unset = (v["unset-env"] ?? "").split(",").map((k) => k.trim()).filter(Boolean);
|
|
8986
9283
|
if (env || unset.length) body.env = { ...env ?? {}, ...Object.fromEntries(unset.map((k) => [k, ""])) };
|
|
8987
9284
|
const connectors = {};
|
|
8988
|
-
for (const c of (v.connect ?? "").split(",").map((
|
|
8989
|
-
for (const c of (v.disconnect ?? "").split(",").map((
|
|
9285
|
+
for (const c of (v.connect ?? "").split(",").map((s2) => s2.trim()).filter(Boolean)) connectors[c] = {};
|
|
9286
|
+
for (const c of (v.disconnect ?? "").split(",").map((s2) => s2.trim()).filter(Boolean)) connectors[c] = null;
|
|
8990
9287
|
if (Object.keys(connectors).length) body.connectors = connectors;
|
|
8991
9288
|
const voice = {};
|
|
8992
9289
|
if (v.voice) {
|
|
@@ -9212,8 +9509,8 @@ async function skillsCmd(sub, id, slug, v) {
|
|
|
9212
9509
|
const r = await api(agentPath(id, "/skills"));
|
|
9213
9510
|
const short3 = (d) => (d ?? "").replace(/\s+/g, " ").slice(0, 70);
|
|
9214
9511
|
const rows = [
|
|
9215
|
-
...r.skills.map((
|
|
9216
|
-
...(r.house ?? []).map((
|
|
9512
|
+
...r.skills.map((s2) => ({ slug: s2.slug, source: "own", on: "on", description: short3(s2.description) })),
|
|
9513
|
+
...(r.house ?? []).map((s2) => ({ slug: s2.slug, source: "ghosty", on: s2.enabled ? "on" : "off", description: short3(s2.description) }))
|
|
9217
9514
|
];
|
|
9218
9515
|
const more = r.community.filter((c) => !c.installed).length;
|
|
9219
9516
|
out(r, () => `${table(rows, ["slug", "source", "on", "description"])}
|
|
@@ -9221,7 +9518,7 @@ ${dim(t(`${more} more in the community catalog: ghosty skills add <agent-id> <sl
|
|
|
9221
9518
|
return;
|
|
9222
9519
|
}
|
|
9223
9520
|
case "house": {
|
|
9224
|
-
const list = (x) => x ? x.split(",").map((
|
|
9521
|
+
const list = (x) => x ? x.split(",").map((s2) => s2.trim()).filter(Boolean) : void 0;
|
|
9225
9522
|
const only = list(v.only), on = list(v.enable), off = list(v.disable);
|
|
9226
9523
|
if (!only && !on && !off) {
|
|
9227
9524
|
const r2 = await api(agentPath(id, "/skills/house"));
|
|
@@ -9344,6 +9641,10 @@ async function agentsDemo(id, v) {
|
|
|
9344
9641
|
].join("\n")
|
|
9345
9642
|
);
|
|
9346
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
|
+
}
|
|
9347
9648
|
function styleSummary(st) {
|
|
9348
9649
|
if (!st) return "";
|
|
9349
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(" ");
|
|
@@ -9352,7 +9653,7 @@ async function agentsEmbed(id, v, action) {
|
|
|
9352
9653
|
id = need(id, "agent id", "ghosty agents embed enable <agent-id> --domains tusitio.com");
|
|
9353
9654
|
if (action === "theme") {
|
|
9354
9655
|
const e2 = await api(agentPath(id, "/embed"));
|
|
9355
|
-
const theme = { themeSource: e2.themeSource ?? "custom", themeFrom: e2.themeFrom ?? null, siteUrl: e2.siteUrl ?? null, color: e2.color, theme: e2.theme ?? "light", style: e2.style ?? {} };
|
|
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 ?? {} };
|
|
9356
9657
|
return out(theme, () => JSON.stringify(theme, null, 2));
|
|
9357
9658
|
}
|
|
9358
9659
|
if (action) throw usage(t(`Unknown: agents embed <id> ${action}`, `No existe: agents embed <id> ${action}`), "ghosty agents embed <id> theme");
|
|
@@ -9403,6 +9704,26 @@ async function agentsEmbed(id, v, action) {
|
|
|
9403
9704
|
body.style = isTheme ? j.style : j;
|
|
9404
9705
|
if (isTheme && typeof j.color === "string" && body.color === void 0) body.color = j.color;
|
|
9405
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;
|
|
9406
9727
|
}
|
|
9407
9728
|
if (v["theme-source"] !== void 0) body.themeSource = v["theme-source"];
|
|
9408
9729
|
if (v["site-url"] !== void 0) {
|
|
@@ -9418,12 +9739,15 @@ async function agentsEmbed(id, v, action) {
|
|
|
9418
9739
|
}
|
|
9419
9740
|
if (v.rotate) body.rotate = true;
|
|
9420
9741
|
const e = Object.keys(body).length ? await api(agentPath(id, "/embed"), { method: "PUT", body }) : await api(agentPath(id, "/embed"));
|
|
9742
|
+
const baseUrl2 = baseFromSnippet(e.snippet);
|
|
9743
|
+
const react = e.token ? reactSnippet(e.token, baseUrl2) : null;
|
|
9744
|
+
const wrote = v.out ? await writeEmbedComponent(e, baseUrl2, v.out, v.force === true) : null;
|
|
9421
9745
|
out(
|
|
9422
|
-
e,
|
|
9746
|
+
{ ...e, react, ...wrote ? { wrote } : {} },
|
|
9423
9747
|
() => [
|
|
9424
9748
|
`${e.enabled ? lavender(t("\u25CF on", "\u25CF encendido")) : dim(t("\u25CB off", "\u25CB apagado"))} ${t("web widget", "widget web")}`,
|
|
9425
9749
|
e.domains.length ? `${t("domains", "dominios")}: ${e.domains.join(", ")}` : red(t("domains: (none \u2014 it loads nowhere; add --domains yoursite.com)", "dominios: (ninguno \u2014 no carga en ning\xFAn lado; agrega --domains tusitio.com)")),
|
|
9426
|
-
...e.engineOk ? [] : [red(t("engine: only
|
|
9750
|
+
...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`"))],
|
|
9427
9751
|
dim(t(`cap ${e.perVisitorDay} messages per visitor per day \xB7 ${e.perIpDay} per IP per day \xB7 color ${e.color}`, `tope ${e.perVisitorDay} mensajes por visitante al d\xEDa \xB7 ${e.perIpDay} por IP al d\xEDa \xB7 color ${e.color}`)),
|
|
9428
9752
|
`${t("first message", "primer mensaje")}: ${e.welcome ?? dim(e.welcomeShown ?? t("(default greeting)", "(saludo por defecto)"))}`,
|
|
9429
9753
|
`${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"}`)}`,
|
|
@@ -9433,12 +9757,42 @@ async function agentsEmbed(id, v, action) {
|
|
|
9433
9757
|
e.starters?.length ? `${t("suggestions", "sugerencias")}: ${e.starters.join(" | ")}` : dim(t("suggestions: (none)", "sugerencias: (ninguna)")),
|
|
9434
9758
|
`${t("theme", "tema")}: ${e.themeSource ?? "custom"}${e.themeFrom ? dim(` \xB7 ${e.themeFrom}`) : ""}`,
|
|
9435
9759
|
...styleSummary(e.style) ? [dim(`${t("style", "estilo")}: ${styleSummary(e.style)}`)] : [],
|
|
9760
|
+
...copySummary(e.copy) ? [dim(`${t("texts", "textos")}: ${copySummary(e.copy)}`)] : [],
|
|
9436
9761
|
...e.snippet ? ["", t("Paste before </body> on your site:", "Pega antes de </body> en tu sitio:"), e.snippet, ""] : [],
|
|
9762
|
+
...react ? [t("React:", "React:"), dim(` ${react.install}`), ...react.code.split("\n").map((l) => ` ${l}`), ""] : [],
|
|
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)`)}`, ""] : [],
|
|
9437
9764
|
t(`visitors ${e.stats.visitantes} \xB7 messages ${e.stats.mensajes}`, `visitantes ${e.stats.visitantes} \xB7 mensajes ${e.stats.mensajes}`),
|
|
9438
9765
|
...e.lastError ? [red(`${t("last failure", "\xFAltima falla")} ${e.lastError.at}: ${serverText(e.lastError.message ?? e.lastError.error ?? "")}`)] : []
|
|
9439
9766
|
].join("\n")
|
|
9440
9767
|
);
|
|
9441
9768
|
}
|
|
9769
|
+
async function writeEmbedComponent(e, baseUrl2, file, force) {
|
|
9770
|
+
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"));
|
|
9771
|
+
const ext = file.toLowerCase().match(/\.(tsx|jsx)$/)?.[1];
|
|
9772
|
+
if (!ext) throw usage(t("--out must end in .tsx or .jsx", "--out debe terminar en .tsx o .jsx"));
|
|
9773
|
+
const path = resolve(file);
|
|
9774
|
+
const exists = await readFile3(path).then(() => true, () => false);
|
|
9775
|
+
if (exists && !force) throw usage(t(`${file} already exists; add --force to overwrite it`, `${file} ya existe; agrega --force para pisarlo`));
|
|
9776
|
+
const src = reactComponentSource(
|
|
9777
|
+
{
|
|
9778
|
+
token: e.token,
|
|
9779
|
+
baseUrl: baseUrl2,
|
|
9780
|
+
title: e.title ?? e.agentName ?? "Asistente",
|
|
9781
|
+
subtitle: e.subtitle ?? null,
|
|
9782
|
+
placeholder: e.placeholder ?? null,
|
|
9783
|
+
welcome: e.welcome ?? e.welcomeShown ?? "\xA1Hola! \xBFEn qu\xE9 te ayudo?",
|
|
9784
|
+
color: e.color,
|
|
9785
|
+
textColor: e.textColor ?? "#ffffff",
|
|
9786
|
+
starters: e.starters ?? [],
|
|
9787
|
+
style: e.style ?? {},
|
|
9788
|
+
copy: { ...COPY_DEFAULTS, ...e.copy ?? {} }
|
|
9789
|
+
},
|
|
9790
|
+
ext === "tsx"
|
|
9791
|
+
);
|
|
9792
|
+
await mkdir3(dirname2(path), { recursive: true });
|
|
9793
|
+
await writeFile3(path, src);
|
|
9794
|
+
return relative(process.cwd(), path) || path;
|
|
9795
|
+
}
|
|
9442
9796
|
async function agentsBox(id, v) {
|
|
9443
9797
|
id = need(id, "agent id", "ghosty agents box <agent-id> [--check PATH] [--contains PATH=TEXT] [--recycle]");
|
|
9444
9798
|
if (v.recycle) {
|
|
@@ -9647,9 +10001,9 @@ async function chat(agent, message, v) {
|
|
|
9647
10001
|
rl.close();
|
|
9648
10002
|
}
|
|
9649
10003
|
async function readAll() {
|
|
9650
|
-
let
|
|
9651
|
-
for await (const c of process.stdin)
|
|
9652
|
-
return
|
|
10004
|
+
let s2 = "";
|
|
10005
|
+
for await (const c of process.stdin) s2 += c;
|
|
10006
|
+
return s2;
|
|
9653
10007
|
}
|
|
9654
10008
|
async function meMemories(sub, arg, text) {
|
|
9655
10009
|
const ex = 'ghosty me memories ls | add "texto" | edit <id> "texto" | rm <id>';
|
|
@@ -9681,7 +10035,7 @@ async function meMemories(sub, arg, text) {
|
|
|
9681
10035
|
throw usage(t(`Unknown memories subcommand: ${sub}.`, `Subcomando de memories desconocido: ${sub}.`), ex);
|
|
9682
10036
|
}
|
|
9683
10037
|
}
|
|
9684
|
-
var safeName = (
|
|
10038
|
+
var safeName = (s2) => s2.replace(/[\\/:*?"<>|\u0000-\u001f]+/g, "_").trim() || "archivo";
|
|
9685
10039
|
async function meFilesExport(v) {
|
|
9686
10040
|
const dir = v.out ?? "ghosty-archivos";
|
|
9687
10041
|
const all = [];
|
|
@@ -9811,20 +10165,20 @@ async function meUsage() {
|
|
|
9811
10165
|
|
|
9812
10166
|
// src/commands/schedule.ts
|
|
9813
10167
|
var enc4 = encodeURIComponent;
|
|
9814
|
-
function minutesOf(
|
|
9815
|
-
const m = /^(\d+(?:\.\d+)?)\s*(m|min|h|d)?$/i.exec(
|
|
9816
|
-
if (!m) throw usage(t(`Invalid ${flag}: ${
|
|
10168
|
+
function minutesOf(s2, flag) {
|
|
10169
|
+
const m = /^(\d+(?:\.\d+)?)\s*(m|min|h|d)?$/i.exec(s2.trim());
|
|
10170
|
+
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"));
|
|
9817
10171
|
const n = Number(m[1]);
|
|
9818
10172
|
const unit = (m[2] ?? "m").toLowerCase();
|
|
9819
10173
|
return Math.round(unit === "d" ? n * 1440 : unit === "h" ? n * 60 : n);
|
|
9820
10174
|
}
|
|
9821
|
-
function isoOf(
|
|
9822
|
-
const d = new Date(
|
|
9823
|
-
if (Number.isNaN(d.getTime())) throw usage(t(`Invalid ${flag}: ${
|
|
10175
|
+
function isoOf(s2, flag) {
|
|
10176
|
+
const d = new Date(s2);
|
|
10177
|
+
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"));
|
|
9824
10178
|
return d.toISOString();
|
|
9825
10179
|
}
|
|
9826
10180
|
var every = (min) => min == null ? "once" : min % 1440 === 0 ? `${min / 1440}d` : min % 60 === 0 ? `${min / 60}h` : `${min}m`;
|
|
9827
|
-
var short = (
|
|
10181
|
+
var short = (s2, n = 60) => s2.length > n ? s2.slice(0, n - 1) + "\u2026" : s2;
|
|
9828
10182
|
async function agentSchedule(agent) {
|
|
9829
10183
|
return api(`/api/v2/me/agents/${enc4(agent)}/schedule`);
|
|
9830
10184
|
}
|
|
@@ -10050,7 +10404,7 @@ async function call(path, v, init = {}) {
|
|
|
10050
10404
|
"x-ghosty-ts": ts,
|
|
10051
10405
|
"x-ghosty-sig": sig,
|
|
10052
10406
|
"content-type": "application/json",
|
|
10053
|
-
"user-agent": `ghosty-cli/${"0.
|
|
10407
|
+
"user-agent": `ghosty-cli/${"0.54.0"}`
|
|
10054
10408
|
},
|
|
10055
10409
|
body: method === "GET" ? void 0 : raw
|
|
10056
10410
|
});
|
|
@@ -10311,10 +10665,10 @@ import { basename as basename3, join as join5 } from "node:path";
|
|
|
10311
10665
|
// src/rows.ts
|
|
10312
10666
|
var IDENT = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
10313
10667
|
function parseRowScope(raw) {
|
|
10314
|
-
const
|
|
10315
|
-
if (!
|
|
10668
|
+
const s2 = raw.trim();
|
|
10669
|
+
if (!s2) return null;
|
|
10316
10670
|
const out2 = {};
|
|
10317
|
-
for (const part of
|
|
10671
|
+
for (const part of s2.split(",").map((x) => x.trim()).filter(Boolean)) {
|
|
10318
10672
|
const [table2, cols = ""] = part.split(":").map((x) => x.trim());
|
|
10319
10673
|
const list = cols.split("+").map((x) => x.trim()).filter(Boolean);
|
|
10320
10674
|
if (!IDENT.test(table2 ?? "") || !list.length || !list.every((c) => IDENT.test(c))) {
|
|
@@ -10333,7 +10687,7 @@ var need2 = (v, what, example) => {
|
|
|
10333
10687
|
if (!v) throw usage(t(`Missing ${what}.`, `Falta ${what}.`), `${t("Example", "Ejemplo")}: ${example}`);
|
|
10334
10688
|
return v;
|
|
10335
10689
|
};
|
|
10336
|
-
var csv = (
|
|
10690
|
+
var csv = (s2) => (s2 ?? "").split(",").map((x) => x.trim()).filter(Boolean);
|
|
10337
10691
|
function importWarnings(ws) {
|
|
10338
10692
|
if (!ws?.length) return "";
|
|
10339
10693
|
const flag = ws.map((w) => `${w.table}.${w.column}`).join(",");
|
|
@@ -10364,9 +10718,9 @@ function shrinking(plan) {
|
|
|
10364
10718
|
return plan.filter((p) => p.action === "replace" && p.targetRows > 0 && (p.resultRows === 0 || p.resultRows < p.targetRows / 2));
|
|
10365
10719
|
}
|
|
10366
10720
|
function shrinkWarnings(plan) {
|
|
10367
|
-
const
|
|
10368
|
-
if (!
|
|
10369
|
-
const lines =
|
|
10721
|
+
const s2 = shrinking(plan);
|
|
10722
|
+
if (!s2.length) return "";
|
|
10723
|
+
const lines = s2.map(
|
|
10370
10724
|
(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`))
|
|
10371
10725
|
);
|
|
10372
10726
|
return `
|
|
@@ -10731,7 +11085,7 @@ async function turnsCmd(sub, id, turnId, v) {
|
|
|
10731
11085
|
if (v.session) q.set("session", v.session);
|
|
10732
11086
|
q.set("limit", v.limit ?? "20");
|
|
10733
11087
|
const r = await api(agentPath2(id, `/turns?${q}`));
|
|
10734
|
-
const when = (
|
|
11088
|
+
const when = (s2) => s2.slice(0, 16).replace("T", " ");
|
|
10735
11089
|
const rows = r.turns.map((tu) => ({
|
|
10736
11090
|
id: tu.id,
|
|
10737
11091
|
started: when(tu.startedAt),
|
|
@@ -10770,7 +11124,7 @@ async function turnShow(id, turnId, full = false) {
|
|
|
10770
11124
|
const r = await api(agentPath2(id, `/turns/${enc6(turnId ?? "last")}`));
|
|
10771
11125
|
out(r, () => {
|
|
10772
11126
|
const tu = r.turn;
|
|
10773
|
-
const clip = (
|
|
11127
|
+
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)`))}`;
|
|
10774
11128
|
const io = [
|
|
10775
11129
|
`${lavender("in ")} ${r.input ? clip(r.input) : dim(t("(no copy of the message)", "(sin copia del mensaje)"))}`,
|
|
10776
11130
|
`${lavender("out")} ${r.output ? clip(r.output) : dim(t("(no answer saved)", "(sin respuesta guardada)"))}`
|
|
@@ -10779,11 +11133,11 @@ async function turnShow(id, turnId, full = false) {
|
|
|
10779
11133
|
const state = `${bold(tu.id)} ${tu.state === "error" ? red(tu.state) : tu.state} ${tu.secs ?? "?"} s${tu.session ? dim(` ${tu.session}`) : ""}${sdk}`;
|
|
10780
11134
|
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");
|
|
10781
11135
|
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`))}`;
|
|
10782
|
-
const line2 = (
|
|
10783
|
-
const at = `+${
|
|
10784
|
-
if (
|
|
10785
|
-
const tok =
|
|
10786
|
-
return
|
|
11136
|
+
const line2 = (s2) => {
|
|
11137
|
+
const at = `+${s2.at.toFixed(1)}s`.padStart(8);
|
|
11138
|
+
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") : ""}`;
|
|
11139
|
+
const tok = s2.outTokens ? dim(` [${s2.outTokens} out]`) : "";
|
|
11140
|
+
return s2.kind === "thinking" ? `${at} ${dim(t("thinking", "pensando"))}${tok}` : `${at} ${s2.text}${tok}`;
|
|
10787
11141
|
};
|
|
10788
11142
|
const top = r.byTool.slice(0, 5).map((b) => `${b.tool} ${b.secs} s (${b.calls})`).join(" \xB7 ");
|
|
10789
11143
|
return [head, ...io, ...r.steps.map(line2), dim(`${t("slowest", "lo m\xE1s lento")}: ${top || "\u2014"}`)].join("\n");
|
|
@@ -10842,7 +11196,7 @@ async function dbsPhotosCmd(verb, id, db, v) {
|
|
|
10842
11196
|
results.push(...r.items);
|
|
10843
11197
|
if (!globals.json && files.length > 8) info(dim(` ${Math.min(i + 8, files.length)}/${files.length}`));
|
|
10844
11198
|
}
|
|
10845
|
-
const by = (
|
|
11199
|
+
const by = (s2) => results.filter((r) => r.status === s2);
|
|
10846
11200
|
const stored = by(v["dry-run"] ? "would_store" : "stored");
|
|
10847
11201
|
const missing = by("missing_row");
|
|
10848
11202
|
const bad = [...by("bad_type"), ...by("too_big"), ...by("failed")];
|
|
@@ -11102,7 +11456,7 @@ async function readMultiFileAuth(dir) {
|
|
|
11102
11456
|
|
|
11103
11457
|
// src/commands/channels.ts
|
|
11104
11458
|
var enc7 = encodeURIComponent;
|
|
11105
|
-
var digits = (
|
|
11459
|
+
var digits = (s2) => s2.replace(/\D/g, "");
|
|
11106
11460
|
function find2(nums, ref) {
|
|
11107
11461
|
const d = digits(ref);
|
|
11108
11462
|
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);
|
|
@@ -11239,9 +11593,9 @@ async function waCmd2(sub, id, v) {
|
|
|
11239
11593
|
if (!id) throw usage(t("Missing agent id.", "Falta el id del agente."), t("Example", "Ejemplo") + `: ghosty channels wa ${sub} <agent-id>`);
|
|
11240
11594
|
const base = `/api/v2/agents/${enc7(id)}/wa`;
|
|
11241
11595
|
if (sub === "status") {
|
|
11242
|
-
const
|
|
11243
|
-
return out(
|
|
11244
|
-
${dim(serverText(
|
|
11596
|
+
const s2 = await api(base);
|
|
11597
|
+
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 ? `
|
|
11598
|
+
${dim(serverText(s2.reason))}` : ""}`);
|
|
11245
11599
|
}
|
|
11246
11600
|
if (sub === "pair") {
|
|
11247
11601
|
const phone = (v.phone ?? "").replace(/\D/g, "");
|
|
@@ -11251,13 +11605,13 @@ ${dim(serverText(s.reason))}` : ""}`);
|
|
|
11251
11605
|
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}.`)));
|
|
11252
11606
|
}
|
|
11253
11607
|
await api(base, { method: "POST", body: { phone } });
|
|
11254
|
-
let
|
|
11255
|
-
for (let i = 0; i < 15 && !
|
|
11608
|
+
let s2 = { status: "connecting", phone: null, live: false, hasCreds: false };
|
|
11609
|
+
for (let i = 0; i < 15 && !s2.pairingCode && s2.status !== "connected"; i++) {
|
|
11256
11610
|
await new Promise((r2) => setTimeout(r2, 1e3));
|
|
11257
|
-
|
|
11611
|
+
s2 = await api(base);
|
|
11258
11612
|
}
|
|
11259
|
-
return out(
|
|
11260
|
-
${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: ${
|
|
11613
|
+
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)}
|
|
11614
|
+
${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.`)));
|
|
11261
11615
|
}
|
|
11262
11616
|
if (sub === "pause") {
|
|
11263
11617
|
await api(base, { method: "DELETE" });
|
|
@@ -11327,11 +11681,11 @@ function bareMediaFlag(argv) {
|
|
|
11327
11681
|
if (argv[0] !== "channels" || !argv.includes("history")) return argv;
|
|
11328
11682
|
return argv.map((a, i) => a === "--media" && (argv[i + 1] === void 0 || argv[i + 1].startsWith("-")) ? "--media=" : a);
|
|
11329
11683
|
}
|
|
11330
|
-
function pausedLine(
|
|
11331
|
-
if (!
|
|
11684
|
+
function pausedLine(s2, id) {
|
|
11685
|
+
if (!s2.paused && !s2.manual) return "";
|
|
11332
11686
|
const txt = t(
|
|
11333
|
-
`${
|
|
11334
|
-
`${
|
|
11687
|
+
`${s2.paused} paused by Formmy${s2.pausedForever ? ` (${s2.pausedForever} indefinitely)` : ""}, ${s2.manual} in manual mode: the agent doesn't answer them`,
|
|
11688
|
+
`${s2.paused} pausada(s) por Formmy${s2.pausedForever ? ` (${s2.pausedForever} sin fecha)` : ""}, ${s2.manual} en modo manual: el agente no las contesta`
|
|
11335
11689
|
);
|
|
11336
11690
|
return `
|
|
11337
11691
|
${lavender(txt)}
|
|
@@ -11659,6 +12013,8 @@ var HELPS_ES = {
|
|
|
11659
12013
|
["# its theme", "# su tema"],
|
|
11660
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)"],
|
|
11661
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"],
|
|
11662
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)"],
|
|
11663
12019
|
["# change one piece of the prompt, on the server's current one", "# cambia un pedazo del prompt, sobre el actual del servidor"],
|
|
11664
12020
|
["# your account: merge copies with the same content", "# tu cuenta: une las copias con el mismo contenido"],
|
|
@@ -11724,10 +12080,13 @@ var HELPS_ES = {
|
|
|
11724
12080
|
["--auto-open S|off", "embed: se abre solo a los S segundos (1-120), una vez por visita, nunca en el tel\xE9fono"],
|
|
11725
12081
|
['--starters "a|b|c"', 'embed: hasta 4 sugerencias TUYAS ("" las quita; nunca salen las de Ghosty)'],
|
|
11726
12082
|
["--style '{json}'|none", "embed: estilo fino, s\xF3lo llaves conocidas: fontFamily, radius, headerBg, headerText, titleUppercase, statusDot, headerAvatar, panelBg, userBubbleBg/Text, agentBubbleBg/Text, inputBg, sendBg, sendIcon, sendArrow (up|right), launcherBg, launcherIcon. PATCH por llave (null resetea una); none resetea todo"],
|
|
11727
|
-
["--font
|
|
12083
|
+
["--font NAME / --radius 0-24", "embed: atajos de style.fontFamily (lista de Google Fonts: Barlow, Inter, Poppins\u2026) y style.radius"],
|
|
11728
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"],
|
|
11729
12085
|
["--site-url URL", "embed: el sitio del que se sacan los colores (implica --theme-source site)"],
|
|
11730
|
-
["--style-file
|
|
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)"],
|
|
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"],
|
|
11731
12090
|
["--check PATH", "box: este archivo debe existir en la m\xE1quina (repetible), p. ej. un archivo que trae la imagen nueva"],
|
|
11732
12091
|
["--contains PATH=TEXT", "box: ese archivo debe contener TEXT (un cambio que no agrega archivos)"],
|
|
11733
12092
|
["--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"],
|
|
@@ -12191,7 +12550,9 @@ var HELPS = {
|
|
|
12191
12550
|
"ghosty agents embed <agent-id> [--domains \u2026] [--per-visitor N] [--per-ip N] [--welcome TEXT] [--color #hex] [--rotate] # state + the <script> to paste",
|
|
12192
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',
|
|
12193
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",
|
|
12194
12554
|
"ghosty agents embed <agent-id> theme # export the theme as JSON (edit it, apply it back with --style-file)",
|
|
12555
|
+
"ghosty agents embed <agent-id> --out src/components/GhostyChat.tsx [--force] # your own React component, token and theme baked in",
|
|
12195
12556
|
"ghosty agents rm <agent-id> [--from-workspace] [--yes]"
|
|
12196
12557
|
],
|
|
12197
12558
|
flags: [
|
|
@@ -12251,6 +12612,9 @@ var HELPS = {
|
|
|
12251
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"],
|
|
12252
12613
|
["--site-url URL", "embed: the site to take colors from (implies --theme-source site)"],
|
|
12253
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)"],
|
|
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"],
|
|
12254
12618
|
["--check PATH", "box: this file must exist in the machine (repeatable), e.g. a file the new image brings"],
|
|
12255
12619
|
["--contains PATH=TEXT", "box: that file must contain TEXT (a change that adds no files)"],
|
|
12256
12620
|
["--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"],
|
|
@@ -12290,6 +12654,7 @@ var HELPS = {
|
|
|
12290
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?"',
|
|
12291
12655
|
"ghosty agents embed <id> --theme-source site --site-url https://palmeralegal.mx",
|
|
12292
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}'`,
|
|
12293
12658
|
"ghosty agents box <id> --contains /opt/claude-worker/skills/pdf-doc/render-doc.mjs=pdf-templates",
|
|
12294
12659
|
"ghosty agents set <id> --tool wa:actualizar_identidad=off,teams:actualizar_identidad=on",
|
|
12295
12660
|
'ghosty agents set minighosty --bag "MiniGhosty" --avatar https://www.ghosty.studio/avatars/minighosty.png',
|
|
@@ -12954,7 +13319,7 @@ async function updateNotice(current2) {
|
|
|
12954
13319
|
}
|
|
12955
13320
|
|
|
12956
13321
|
// src/resolve.ts
|
|
12957
|
-
var looksLikeId = (
|
|
13322
|
+
var looksLikeId = (s2) => /^c[a-z0-9]{20,}$/.test(s2) || /^[0-9a-f]{24}$/.test(s2);
|
|
12958
13323
|
function pickAgent(list, ref, workspace) {
|
|
12959
13324
|
const want = ref.trim().toLowerCase();
|
|
12960
13325
|
let hits = list.filter((a) => a.nombre.trim().toLowerCase() === want);
|
|
@@ -13028,7 +13393,7 @@ function subcommandsOf(cmd) {
|
|
|
13028
13393
|
for (const u of HELPS[cmd]?.usage ?? []) {
|
|
13029
13394
|
const m = new RegExp(`^ghosty ${cmd} ([a-z][a-z|-]*)`).exec(u);
|
|
13030
13395
|
if (m) {
|
|
13031
|
-
for (const
|
|
13396
|
+
for (const s2 of m[1].split("|")) if (!s2.includes("-") || /^[a-z]+-[a-z]+$/.test(s2)) subs.add(s2);
|
|
13032
13397
|
}
|
|
13033
13398
|
}
|
|
13034
13399
|
return [...subs];
|
|
@@ -13184,7 +13549,7 @@ function fillSecrets(file) {
|
|
|
13184
13549
|
};
|
|
13185
13550
|
const x = file["x-ghosty"];
|
|
13186
13551
|
fill(x?.env);
|
|
13187
|
-
for (const
|
|
13552
|
+
for (const s2 of x?.mcp ?? []) fill(s2.env), fill(s2.headers);
|
|
13188
13553
|
return missing;
|
|
13189
13554
|
}
|
|
13190
13555
|
async function readSkills(dir) {
|
|
@@ -13267,7 +13632,7 @@ async function boxesUsage(slug) {
|
|
|
13267
13632
|
const r = await api(`/api/v2/workspaces/${encodeURIComponent(slug)}/boxes`);
|
|
13268
13633
|
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 } : {} }));
|
|
13269
13634
|
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)`))}`;
|
|
13270
|
-
const space = r.space ? (r.space.used > r.space.limit ? (
|
|
13635
|
+
const space = r.space ? (r.space.used > r.space.limit ? (s2) => s2 : dim)(
|
|
13271
13636
|
t(
|
|
13272
13637
|
`${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" : ""}`,
|
|
13273
13638
|
`${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" : ""}`
|
|
@@ -13538,7 +13903,7 @@ async function history(agent, v) {
|
|
|
13538
13903
|
lines.push("", bold(`${c.sessionId}`) + dim(` \xB7 ${hora(c.createdAt)} \u2192 ${hora(c.updatedAt)}`));
|
|
13539
13904
|
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)}`) : ""}`);
|
|
13540
13905
|
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(", ")}]`) : ""}`);
|
|
13541
|
-
for (const
|
|
13906
|
+
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`}`));
|
|
13542
13907
|
}
|
|
13543
13908
|
if (!r.conversations.length) lines.push("", dim(t("No visitors in that range.", "Sin visitantes en ese rango.")));
|
|
13544
13909
|
return lines.join("\n");
|
|
@@ -13670,8 +14035,8 @@ async function collectSiteFiles(target) {
|
|
|
13670
14035
|
}
|
|
13671
14036
|
function titleOf(html) {
|
|
13672
14037
|
const m = html.match(/<title[^>]*>([^<]*)<\/title>/i);
|
|
13673
|
-
const
|
|
13674
|
-
return
|
|
14038
|
+
const s2 = m?.[1]?.replace(/\s+/g, " ").trim();
|
|
14039
|
+
return s2 || null;
|
|
13675
14040
|
}
|
|
13676
14041
|
var kb = (n) => n ? `${Math.max(1, Math.round(n / 1024))} KB` : "";
|
|
13677
14042
|
var day = (iso) => iso ? iso.slice(0, 10) : "";
|
|
@@ -13681,7 +14046,7 @@ async function sitesCmd(sub, a1, a2, v) {
|
|
|
13681
14046
|
case "ls":
|
|
13682
14047
|
case "list": {
|
|
13683
14048
|
const r = await api(`/api/v2/sites?scope=${v.house ? "house" : "mine"}`);
|
|
13684
|
-
const rows = r.sites.map((
|
|
14049
|
+
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") }));
|
|
13685
14050
|
return out(r, () => `${bold(v.house ? t("House sites", "Sitios de la casa") : t("Your sites", "Tus sitios"))}
|
|
13686
14051
|
${table(rows, ["slug", "title", "url", "files", "size", "published", "expires"])}`);
|
|
13687
14052
|
}
|
|
@@ -13717,8 +14082,8 @@ function detailLine(d, max = 90) {
|
|
|
13717
14082
|
if (d === null || d === void 0) return "";
|
|
13718
14083
|
if (typeof d !== "object") return String(d).slice(0, max);
|
|
13719
14084
|
const parts = Object.entries(d).filter(([k]) => k !== "via").map(([k, v]) => `${k}=${v !== null && typeof v === "object" ? JSON.stringify(v) : String(v)}`);
|
|
13720
|
-
const
|
|
13721
|
-
return
|
|
14085
|
+
const s2 = parts.join(" ");
|
|
14086
|
+
return s2.length > max ? `${s2.slice(0, max - 1)}\u2026` : s2;
|
|
13722
14087
|
}
|
|
13723
14088
|
async function auditCmd(sub, v, context) {
|
|
13724
14089
|
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]");
|
|
@@ -13745,7 +14110,7 @@ ${dim(t("Times in UTC. --full for the whole detail; --json for everything.", "Ho
|
|
|
13745
14110
|
|
|
13746
14111
|
// src/commands/factory.ts
|
|
13747
14112
|
var enc12 = encodeURIComponent;
|
|
13748
|
-
var short2 = (
|
|
14113
|
+
var short2 = (s2) => (s2 ?? "").slice(0, 16).replace("T", " ");
|
|
13749
14114
|
async function factoryCmd(sub, verb, ref, title, v, context) {
|
|
13750
14115
|
if (sub !== "runs") throw usage(t(`Unknown factory subcommand: ${sub ?? "(none)"}.`, `Subcomando de factory desconocido: ${sub ?? "(ninguno)"}.`), "ghosty factory runs ls|show|rename \u2026");
|
|
13751
14116
|
const ws = v.workspace ?? context;
|
|
@@ -13863,7 +14228,7 @@ async function supportThread(agent, sid, v) {
|
|
|
13863
14228
|
const lines = [
|
|
13864
14229
|
`${bold(r.agent.name ?? r.agent.id)} \xB7 ${r.title ?? sid} ${dim(`(${t("from", "de")} ${x.source})`)}`,
|
|
13865
14230
|
...x.note ? [dim(` ${serverText(x.note)}`)] : [],
|
|
13866
|
-
...x.tools.map((
|
|
14231
|
+
...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}]`) : ""}`),
|
|
13867
14232
|
"",
|
|
13868
14233
|
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."))
|
|
13869
14234
|
];
|
|
@@ -14120,7 +14485,9 @@ ${bold("Agents")}
|
|
|
14120
14485
|
look: [--title T] [--subtitle T] [--avatar URL|none] [--icon chat|avatar|ghost] [--placeholder T] [--position right|left] [--offset X[,Y]]
|
|
14121
14486
|
[--theme light|dark|auto] [--bubble-text T] [--auto-open SECONDS|off] [--starters "a|b|c"] [--font NAME] [--radius 0-24] [--style '{json}'|none]
|
|
14122
14487
|
theme: [--theme-source custom|teams|site] [--workspace SLUG] [--site-url URL] [--style-file theme.json]
|
|
14123
|
-
|
|
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)
|
|
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
|
|
14124
14491
|
agents embed <id> Its state and the snippet to paste (claude engine only: it isolates each visitor)
|
|
14125
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]
|
|
14126
14493
|
|
|
@@ -14282,8 +14649,10 @@ ${bold("Agentes")}
|
|
|
14282
14649
|
look: [--title T] [--subtitle T] [--avatar URL|none] [--icon chat|avatar|ghost] [--placeholder T] [--position right|left] [--offset X[,Y]]
|
|
14283
14650
|
[--theme light|dark|auto] [--bubble-text T] [--auto-open SEGUNDOS|off] [--starters "a|b|c"] [--font NOMBRE] [--radius 0-24] [--style '{json}'|none]
|
|
14284
14651
|
tema: [--theme-source custom|teams|site] [--workspace SLUG] [--site-url URL] [--style-file tema.json]
|
|
14285
|
-
|
|
14286
|
-
agents embed <id>
|
|
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)
|
|
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
|
|
14655
|
+
agents embed <id> Su estado y el snippet para pegar (Claude o MiniGhosty: a\xEDslan a cada visitante)
|
|
14287
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]
|
|
14288
14657
|
|
|
14289
14658
|
${bold("Configurar un agente")}
|
|
@@ -14379,8 +14748,8 @@ ${dim("Documentaci\xF3n: https://www.ghosty.studio/docs/cli")}`;
|
|
|
14379
14748
|
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"];
|
|
14380
14749
|
var AGENTS_SUBS = ["ls", "create", "get", "set", "export", "doctor", "docs", "share", "unshare", "shares", "teams", "rm", "restart", "demo", "embed", "box"];
|
|
14381
14750
|
function unknown(what, input, known, helpCmd) {
|
|
14382
|
-
const
|
|
14383
|
-
return usage(t(`Unknown command: ${what}.`, `Comando desconocido: ${what}.`),
|
|
14751
|
+
const s2 = suggest(input, known);
|
|
14752
|
+
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}`));
|
|
14384
14753
|
}
|
|
14385
14754
|
function showHelp() {
|
|
14386
14755
|
if (process.stdout.isTTY && !process.env.NO_COLOR && !globals.json) process.stdout.write(BANNER + "\n\n");
|
|
@@ -14479,6 +14848,8 @@ async function main() {
|
|
|
14479
14848
|
font: { type: "string" },
|
|
14480
14849
|
radius: { type: "string" },
|
|
14481
14850
|
"style-file": { type: "string" },
|
|
14851
|
+
copy: { type: "string" },
|
|
14852
|
+
"copy-file": { type: "string" },
|
|
14482
14853
|
"theme-source": { type: "string" },
|
|
14483
14854
|
"site-url": { type: "string" },
|
|
14484
14855
|
sponsor: { type: "string" },
|
|
@@ -14633,7 +15004,7 @@ async function main() {
|
|
|
14633
15004
|
const tokens = splitTokens(pos, v);
|
|
14634
15005
|
globals.token = tokens.session;
|
|
14635
15006
|
globals.profile = v.profile;
|
|
14636
|
-
if (v.version) return out({ version: "0.
|
|
15007
|
+
if (v.version) return out({ version: "0.54.0" }, () => "0.54.0");
|
|
14637
15008
|
for (const n of normalizeArgs(pos, v)) if (!globals.json) info(dim(n));
|
|
14638
15009
|
const context = (await readConfig()).workspace;
|
|
14639
15010
|
if (!explicitToken()?.startsWith("gat_")) {
|
|
@@ -14799,7 +15170,7 @@ async function main() {
|
|
|
14799
15170
|
throw unknown(cmd, cmd, COMMANDS, "ghosty --help");
|
|
14800
15171
|
}
|
|
14801
15172
|
}
|
|
14802
|
-
var notice = updateNotice("0.
|
|
15173
|
+
var notice = updateNotice("0.54.0").catch(() => null);
|
|
14803
15174
|
main().then(
|
|
14804
15175
|
async () => {
|
|
14805
15176
|
const n = await notice;
|
package/package.json
CHANGED