@ghostystudio/cli 0.53.0 → 0.55.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 +16 -1
- package/dist/index.js +173 -25
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -42,6 +42,7 @@ ghosty agents embed enable|disable <id> [--domains a.com,*.b.com] # web widget
|
|
|
42
42
|
ghosty agents embed <id> [--per-visitor N] [--per-ip N] [--welcome TEXT] [--color #hex] [--rotate] # state + the <script> to paste
|
|
43
43
|
ghosty agents embed <id> [--title T] [--avatar URL] [--icon chat|avatar|ghost] [--position left] [--theme auto] [--bubble-text T] [--auto-open S] [--starters "a|b"] # its look
|
|
44
44
|
ghosty agents embed <id> [--style '{json}'] [--theme-source teams|site --site-url URL] [--style-file theme.json] # its theme · `theme` exports it
|
|
45
|
+
ghosty agents embed <id> [--copy '{"waking":"One moment, please","thinking":["Reviewing your question…"]}'|none] [--copy-file texts.json] # what it says while it works
|
|
45
46
|
ghosty agents embed <id> --out src/components/GhostyChat.tsx [--force] # your own React chat component (no deps), token + theme baked in · the output also prints the React snippet (`react` in --json)
|
|
46
47
|
ghosty agents doctor <id> [--verbose] # exits 1 when it finds a problem
|
|
47
48
|
ghosty agents docs ls|grant|revoke <id> [doc] [--write] [--no-deliver]
|
|
@@ -108,6 +109,20 @@ Every command has its own help with examples: `ghosty <command> --help` or `ghos
|
|
|
108
109
|
|
|
109
110
|
Exit codes: `0` ok · `1` API error · `2` usage error · `3` not signed in.
|
|
110
111
|
|
|
112
|
+
### Changes in 0.55.0
|
|
113
|
+
|
|
114
|
+
- `factory runs show <run> --timing`: where a run's time and money went — each turn of each role (critic, @plan, @build, @check) with minutes, minutes in tools, model and USD; the clock split into working, waiting for a person and the rest (CI, queues); and the commands that took 60 s or more.
|
|
115
|
+
- `factory runs stats [--since 30d] [--repo owner/repo]`: runs, % merged, median minutes to PR and to merge, @check→@build loops, red CI per run, and cost per run and per merged PR.
|
|
116
|
+
- `factory runs ls` shows each run's time and cost.
|
|
117
|
+
|
|
118
|
+
### Changes in 0.54.0
|
|
119
|
+
|
|
120
|
+
- `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.
|
|
121
|
+
|
|
122
|
+
### Changes in 0.53.0
|
|
123
|
+
|
|
124
|
+
- `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.
|
|
125
|
+
|
|
111
126
|
### Changes in 0.52.0
|
|
112
127
|
|
|
113
128
|
- `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.
|
|
@@ -125,7 +140,7 @@ Exit codes: `0` ok · `1` API error · `2` usage error · `3` not signed in.
|
|
|
125
140
|
- `channels whatsapp integrations` shows who answered each number last; `--orphans` and `channels messenger ls [--orphans]` list numbers/pages nobody answers today that had turns.
|
|
126
141
|
- `agents set --prompt-patch overlay.json` / `--prompt-append FILE [--dry-run]`: change one piece of the prompt on the server's current copy (dry run prints the diff).
|
|
127
142
|
- `audit ls [--workspace] [--agent] [--actor] [--action] [--since]`: the audit log. The agent page's Save and channel changes are logged now.
|
|
128
|
-
- `factory runs ls|show|rename` (a workspace's Software Factory runs) and `agents ls --handle build`.
|
|
143
|
+
- `factory runs ls|show|rename` (a workspace's Software Factory runs) and `agents ls --handle build`. Since 0.55: `show --timing` and `stats`.
|
|
129
144
|
|
|
130
145
|
### Changes in 0.49.0
|
|
131
146
|
|
package/dist/index.js
CHANGED
|
@@ -8468,7 +8468,7 @@ async function postLong(path, body, timeoutMs = 9e5) {
|
|
|
8468
8468
|
const req = httpsRequest(`${base}${path}`, {
|
|
8469
8469
|
method: "POST",
|
|
8470
8470
|
timeout: timeoutMs,
|
|
8471
|
-
headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.
|
|
8471
|
+
headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.55.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.55.0"}`,
|
|
8514
8514
|
"accept-language": lang(),
|
|
8515
8515
|
...opts.body !== void 0 ? { "content-type": "application/json" } : {},
|
|
8516
8516
|
...opts.headers
|
|
@@ -8765,6 +8765,24 @@ function lineDiff(before, after, context = 2) {
|
|
|
8765
8765
|
}
|
|
8766
8766
|
|
|
8767
8767
|
// src/embed-component.ts
|
|
8768
|
+
var COPY_DEFAULTS = {
|
|
8769
|
+
waking: "Me estoy poniendo los lentes \u{1F453}",
|
|
8770
|
+
connecting: "Estoy abriendo mi libreta",
|
|
8771
|
+
thinking: [
|
|
8772
|
+
"Estoy pensando",
|
|
8773
|
+
"Estoy leyendo tu mensaje",
|
|
8774
|
+
"Estoy revisando lo que me mandaste",
|
|
8775
|
+
"Estoy ordenando las ideas",
|
|
8776
|
+
"Estoy armando tu respuesta",
|
|
8777
|
+
"Estoy afinando los detalles"
|
|
8778
|
+
],
|
|
8779
|
+
stillWorking: "Sigo trabajando",
|
|
8780
|
+
cantStart: "No pude arrancar.",
|
|
8781
|
+
interrupted: "Me qued\xE9 a medias",
|
|
8782
|
+
retryLabel: "Reintentar",
|
|
8783
|
+
showDuration: true,
|
|
8784
|
+
showCopyButton: true
|
|
8785
|
+
};
|
|
8768
8786
|
var DEFAULT_BASE = "https://www.ghosty.studio";
|
|
8769
8787
|
function baseFromSnippet(snippet) {
|
|
8770
8788
|
const m = snippet?.match(/src="(https?:\/\/[^"]+?)\/widget\.js"/);
|
|
@@ -8786,6 +8804,7 @@ function reactComponentSource(th, ts) {
|
|
|
8786
8804
|
const st = th.style ?? {};
|
|
8787
8805
|
const radius = typeof st.radius === "number" ? st.radius : 16;
|
|
8788
8806
|
const font = typeof st.fontFamily === "string" ? st.fontFamily : null;
|
|
8807
|
+
const cp = { ...COPY_DEFAULTS, ...th.copy ?? {} };
|
|
8789
8808
|
return `// Chat de Ghosty Studio \u2014 generado por \`ghosty agents embed --out\`. Este archivo es TUYO:
|
|
8790
8809
|
// c\xE1mbialo como quieras; no depende de ning\xFAn paquete, s\xF3lo de React.
|
|
8791
8810
|
// Habla con tu agente por el contrato del widget web (/embed/w/<token>/v/<visitante>/\u2026).
|
|
@@ -8805,6 +8824,16 @@ const WELCOME = ${s(th.welcome)}; // el primer mensaje que ve el visitante
|
|
|
8805
8824
|
const PLACEHOLDER = ${s(th.placeholder ?? `Escr\xEDbele a ${th.title}`)};
|
|
8806
8825
|
const STARTERS${T(": string[]")} = ${s(th.starters)}; // sugerencias (se ocultan al primer mensaje)
|
|
8807
8826
|
|
|
8827
|
+
// Lo que dice mientras trabaja (ficha \u2192 Canales \u2192 Widget web \u2192 Textos, o \`--copy\`).
|
|
8828
|
+
const COPY = {
|
|
8829
|
+
connecting: ${s(cp.connecting)}, // mientras se conecta
|
|
8830
|
+
thinking: ${s(cp.thinking)}, // rotan cada 4 s mientras piensa
|
|
8831
|
+
stillWorking: ${s(cp.stillWorking)}, // pasados 90 s
|
|
8832
|
+
interrupted: ${s(cp.interrupted)}, // si la respuesta se cort\xF3
|
|
8833
|
+
showDuration: ${cp.showDuration === true}, // el \xAB\xB7 12 s\xBB de la espera
|
|
8834
|
+
showCopyButton: ${cp.showCopyButton === true}, // \xABCopiar\xBB bajo cada respuesta
|
|
8835
|
+
};
|
|
8836
|
+
|
|
8808
8837
|
// \u2500\u2500 Tema (tu tema actual del widget) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
8809
8838
|
const THEME = {
|
|
8810
8839
|
font: ${s(font ? `"${font}", ui-sans-serif, system-ui, sans-serif` : "ui-sans-serif, system-ui, sans-serif")},
|
|
@@ -8890,7 +8919,7 @@ export function useGhostyChat() {
|
|
|
8890
8919
|
// \`reposo\`: llega al conectar (no hab\xEDa turno). S\xF3lo cuenta si esper\xE1bamos respuesta.
|
|
8891
8920
|
if (d.reposo && !waiting.current) return;
|
|
8892
8921
|
if (d.reposo && d.ultimo?.text) put(d.ultimo.text, true);
|
|
8893
|
-
if (d.interrumpido) setError(
|
|
8922
|
+
if (d.interrumpido) setError(COPY.interrupted);
|
|
8894
8923
|
waiting.current = false;
|
|
8895
8924
|
settle();
|
|
8896
8925
|
setStatus("idle");
|
|
@@ -8929,9 +8958,23 @@ export function useGhostyChat() {
|
|
|
8929
8958
|
return { messages, send, stop, status, error };
|
|
8930
8959
|
}
|
|
8931
8960
|
|
|
8961
|
+
// La frase de espera: rota mientras piensa y, pasado minuto y medio, dice que sigue.
|
|
8962
|
+
function useWaitLabel(active${T(": boolean")}) {
|
|
8963
|
+
const [secs, setSecs] = useState(0);
|
|
8964
|
+
useEffect(() => {
|
|
8965
|
+
if (!active) return;
|
|
8966
|
+
setSecs(0);
|
|
8967
|
+
const t = setInterval(() => setSecs((x) => x + 1), 1000);
|
|
8968
|
+
return () => clearInterval(t);
|
|
8969
|
+
}, [active]);
|
|
8970
|
+
const phrase = secs >= 90 ? COPY.stillWorking : COPY.thinking[Math.floor(secs / 4) % COPY.thinking.length];
|
|
8971
|
+
return COPY.showDuration ? \`\${phrase} \xB7 \${secs} s\` : phrase;
|
|
8972
|
+
}
|
|
8973
|
+
|
|
8932
8974
|
// \u2500\u2500 La UI: c\xE1mbiala a tu gusto \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
8933
8975
|
export default function GhostyChat() {
|
|
8934
8976
|
const { messages, send, stop, status, error } = useGhostyChat();
|
|
8977
|
+
const waitLabel = useWaitLabel(status === "thinking");
|
|
8935
8978
|
const [text, setText] = useState("");
|
|
8936
8979
|
const end = useRef${T("<HTMLDivElement>")}(null);
|
|
8937
8980
|
const busy = status === "thinking" || status === "streaming";
|
|
@@ -8975,9 +9018,17 @@ export default function GhostyChat() {
|
|
|
8975
9018
|
<div style={{ flex: 1, overflowY: "auto", padding: 16, display: "flex", flexDirection: "column", gap: 8, fontSize: 14, lineHeight: 1.45 }}>
|
|
8976
9019
|
<div style={bubble("assistant")}>{WELCOME}</div>
|
|
8977
9020
|
{messages.map((m) => (
|
|
8978
|
-
<div key={m.id} style={
|
|
9021
|
+
<div key={m.id} style={{ display: "flex", flexDirection: "column", alignItems: m.role === "user" ? "flex-end" : "flex-start" }}>
|
|
9022
|
+
<div style={bubble(m.role)}>{m.text}</div>
|
|
9023
|
+
{COPY.showCopyButton && m.role === "assistant" && !m.pending && m.text && (
|
|
9024
|
+
<button type="button" onClick={() => void navigator.clipboard?.writeText(m.text)} style={{ border: 0, background: "transparent", color: "#6b7280", fontSize: 12, cursor: "pointer", padding: "2px 0" }}>
|
|
9025
|
+
Copiar
|
|
9026
|
+
</button>
|
|
9027
|
+
)}
|
|
9028
|
+
</div>
|
|
8979
9029
|
))}
|
|
8980
|
-
{status === "
|
|
9030
|
+
{status === "connecting" && <div style={{ ...bubble("assistant"), opacity: 0.6 }}>{COPY.connecting}</div>}
|
|
9031
|
+
{status === "thinking" && <div style={{ ...bubble("assistant"), opacity: 0.6 }}>{waitLabel}</div>}
|
|
8981
9032
|
{!messages.length && STARTERS.length > 0 && (
|
|
8982
9033
|
<div style={{ display: "flex", flexWrap: "wrap", gap: 6 }}>
|
|
8983
9034
|
{STARTERS.map((s) => (
|
|
@@ -9590,6 +9641,10 @@ async function agentsDemo(id, v) {
|
|
|
9590
9641
|
].join("\n")
|
|
9591
9642
|
);
|
|
9592
9643
|
}
|
|
9644
|
+
function copySummary(c) {
|
|
9645
|
+
if (!c) return "";
|
|
9646
|
+
return Object.entries(c).filter(([k, x]) => JSON.stringify(x) !== JSON.stringify(COPY_DEFAULTS[k])).map(([k, x]) => `${k}=${Array.isArray(x) ? x.join(" | ") : x}`).join(" \xB7 ");
|
|
9647
|
+
}
|
|
9593
9648
|
function styleSummary(st) {
|
|
9594
9649
|
if (!st) return "";
|
|
9595
9650
|
return Object.entries(st).filter(([k, x]) => x !== null && !(k === "headerAvatar" && x === true) && !(k === "titleUppercase" && x === false) && !(k === "sendArrow" && x === "up")).map(([k, x]) => `${k}=${x}`).join(" ");
|
|
@@ -9598,7 +9653,7 @@ async function agentsEmbed(id, v, action) {
|
|
|
9598
9653
|
id = need(id, "agent id", "ghosty agents embed enable <agent-id> --domains tusitio.com");
|
|
9599
9654
|
if (action === "theme") {
|
|
9600
9655
|
const e2 = await api(agentPath(id, "/embed"));
|
|
9601
|
-
const theme = { themeSource: e2.themeSource ?? "custom", themeFrom: e2.themeFrom ?? null, siteUrl: e2.siteUrl ?? null, color: e2.color, theme: e2.theme ?? "light", style: e2.style ?? {} };
|
|
9656
|
+
const theme = { themeSource: e2.themeSource ?? "custom", themeFrom: e2.themeFrom ?? null, siteUrl: e2.siteUrl ?? null, color: e2.color, theme: e2.theme ?? "light", style: e2.style ?? {}, copy: e2.copy ?? {} };
|
|
9602
9657
|
return out(theme, () => JSON.stringify(theme, null, 2));
|
|
9603
9658
|
}
|
|
9604
9659
|
if (action) throw usage(t(`Unknown: agents embed <id> ${action}`, `No existe: agents embed <id> ${action}`), "ghosty agents embed <id> theme");
|
|
@@ -9649,6 +9704,26 @@ async function agentsEmbed(id, v, action) {
|
|
|
9649
9704
|
body.style = isTheme ? j.style : j;
|
|
9650
9705
|
if (isTheme && typeof j.color === "string" && body.color === void 0) body.color = j.color;
|
|
9651
9706
|
if (isTheme && typeof j.theme === "string" && body.theme === void 0) body.theme = j.theme;
|
|
9707
|
+
if (isTheme && j.copy && typeof j.copy === "object" && body.copy === void 0) body.copy = j.copy;
|
|
9708
|
+
}
|
|
9709
|
+
if (v.copy !== void 0) {
|
|
9710
|
+
if (v.copy.toLowerCase() === "none") body.copy = null;
|
|
9711
|
+
else {
|
|
9712
|
+
try {
|
|
9713
|
+
body.copy = JSON.parse(v.copy);
|
|
9714
|
+
} catch {
|
|
9715
|
+
throw usage(t(`--copy takes JSON: '{"waking":"One moment, please","thinking":["Reviewing your question\u2026"]}'`, `--copy va en JSON: '{"waking":"Un momento, por favor","thinking":["Revisando su consulta\u2026"]}'`));
|
|
9716
|
+
}
|
|
9717
|
+
}
|
|
9718
|
+
}
|
|
9719
|
+
if (v["copy-file"]) {
|
|
9720
|
+
let j;
|
|
9721
|
+
try {
|
|
9722
|
+
j = JSON.parse(await readFile3(v["copy-file"], "utf8"));
|
|
9723
|
+
} catch (err) {
|
|
9724
|
+
throw usage(t(`Could not read ${v["copy-file"]} as JSON: ${err.message}`, `No pude leer ${v["copy-file"]} como JSON: ${err.message}`));
|
|
9725
|
+
}
|
|
9726
|
+
body.copy = j && typeof j === "object" && "copy" in j && !("waking" in j) ? j.copy : j;
|
|
9652
9727
|
}
|
|
9653
9728
|
if (v["theme-source"] !== void 0) body.themeSource = v["theme-source"];
|
|
9654
9729
|
if (v["site-url"] !== void 0) {
|
|
@@ -9682,6 +9757,7 @@ async function agentsEmbed(id, v, action) {
|
|
|
9682
9757
|
e.starters?.length ? `${t("suggestions", "sugerencias")}: ${e.starters.join(" | ")}` : dim(t("suggestions: (none)", "sugerencias: (ninguna)")),
|
|
9683
9758
|
`${t("theme", "tema")}: ${e.themeSource ?? "custom"}${e.themeFrom ? dim(` \xB7 ${e.themeFrom}`) : ""}`,
|
|
9684
9759
|
...styleSummary(e.style) ? [dim(`${t("style", "estilo")}: ${styleSummary(e.style)}`)] : [],
|
|
9760
|
+
...copySummary(e.copy) ? [dim(`${t("texts", "textos")}: ${copySummary(e.copy)}`)] : [],
|
|
9685
9761
|
...e.snippet ? ["", t("Paste before </body> on your site:", "Pega antes de </body> en tu sitio:"), e.snippet, ""] : [],
|
|
9686
9762
|
...react ? [t("React:", "React:"), dim(` ${react.install}`), ...react.code.split("\n").map((l) => ` ${l}`), ""] : [],
|
|
9687
9763
|
...wrote ? [`${lavender("\u2713")} ${t(`Component written to ${wrote} (yours to edit: token and theme are at the top)`, `Componente escrito en ${wrote} (es tuyo: el token y el tema van arriba)`)}`, ""] : [],
|
|
@@ -9708,7 +9784,8 @@ async function writeEmbedComponent(e, baseUrl2, file, force) {
|
|
|
9708
9784
|
color: e.color,
|
|
9709
9785
|
textColor: e.textColor ?? "#ffffff",
|
|
9710
9786
|
starters: e.starters ?? [],
|
|
9711
|
-
style: e.style ?? {}
|
|
9787
|
+
style: e.style ?? {},
|
|
9788
|
+
copy: { ...COPY_DEFAULTS, ...e.copy ?? {} }
|
|
9712
9789
|
},
|
|
9713
9790
|
ext === "tsx"
|
|
9714
9791
|
);
|
|
@@ -10327,7 +10404,7 @@ async function call(path, v, init = {}) {
|
|
|
10327
10404
|
"x-ghosty-ts": ts,
|
|
10328
10405
|
"x-ghosty-sig": sig,
|
|
10329
10406
|
"content-type": "application/json",
|
|
10330
|
-
"user-agent": `ghosty-cli/${"0.
|
|
10407
|
+
"user-agent": `ghosty-cli/${"0.55.0"}`
|
|
10331
10408
|
},
|
|
10332
10409
|
body: method === "GET" ? void 0 : raw
|
|
10333
10410
|
});
|
|
@@ -11936,6 +12013,8 @@ var HELPS_ES = {
|
|
|
11936
12013
|
["# its theme", "# su tema"],
|
|
11937
12014
|
["# export the theme as JSON (edit it, apply it back with --style-file)", "# exporta el tema en JSON (ed\xEDtalo y vuelve a aplicarlo con --style-file)"],
|
|
11938
12015
|
["--style-file theme.json", "--style-file tema.json"],
|
|
12016
|
+
["# what it says while it works", "# lo que dice mientras trabaja"],
|
|
12017
|
+
["--copy-file texts.json", "--copy-file textos.json"],
|
|
11939
12018
|
["# who is @build in Teams (that workspace, or all of yours)", "# qui\xE9n es @build en Teams (en ese espacio, o en todos los tuyos)"],
|
|
11940
12019
|
["# change one piece of the prompt, on the server's current one", "# cambia un pedazo del prompt, sobre el actual del servidor"],
|
|
11941
12020
|
["# your account: merge copies with the same content", "# tu cuenta: une las copias con el mismo contenido"],
|
|
@@ -12005,6 +12084,8 @@ var HELPS_ES = {
|
|
|
12005
12084
|
["--theme-source custom|teams|site", "embed: importa el tema de la marca de tu espacio de Ghosty Teams (--workspace SLUG para elegir) o de tu sitio (--site-url); queda editable"],
|
|
12006
12085
|
["--site-url URL", "embed: el sitio del que se sacan los colores (implica --theme-source site)"],
|
|
12007
12086
|
["--style-file FILE", "embed: aplica un tema exportado con `agents embed <id> theme` (o s\xF3lo el objeto style)"],
|
|
12087
|
+
["--copy '{json}'|none", "embed: las frases mientras el agente trabaja, texto plano \u2264 80 car.: waking, connecting, thinking (lista de 1 a 8 que rotan), stillWorking, cantStart, interrupted, retryLabel, showDuration, showCopyButton (true|false). PATCH por llave (null = la frase de Ghosty); none las regresa todas"],
|
|
12088
|
+
["--copy-file FILE", "embed: aplica el objeto copy desde un archivo (o el `copy` de un tema exportado)"],
|
|
12008
12089
|
["--out FILE.tsx|.jsx [--force]", "embed: escribe en tu proyecto un componente de React COMPLETO y editable (sin depender de ning\xFAn paquete) con tu token y tu tema horneados arriba; no pisa un archivo que ya existe sin --force"],
|
|
12009
12090
|
["--check PATH", "box: este archivo debe existir en la m\xE1quina (repetible), p. ej. un archivo que trae la imagen nueva"],
|
|
12010
12091
|
["--contains PATH=TEXT", "box: ese archivo debe contener TEXT (un cambio que no agrega archivos)"],
|
|
@@ -12411,7 +12492,7 @@ var HELPS_ES = {
|
|
|
12411
12492
|
]
|
|
12412
12493
|
},
|
|
12413
12494
|
factory: {
|
|
12414
|
-
summary: "Los pedidos de la Software Factory de un espacio (viven en su Teams): listarlos, ver uno (plan vigente, PR, preview, \xFAltimos eventos, qu\xE9 rol trabaja) y corregir el t\xEDtulo de un pedido.",
|
|
12495
|
+
summary: "Los pedidos de la Software Factory de un espacio (viven en su Teams): listarlos con tiempo y costo, ver uno (plan vigente, PR, preview, \xFAltimos eventos, qu\xE9 rol trabaja), ver en qu\xE9 se fue su tiempo y su dinero, los totales del espacio y corregir el t\xEDtulo de un pedido.",
|
|
12415
12496
|
replace: [['"Title"', '"T\xEDtulo"']],
|
|
12416
12497
|
flags: [
|
|
12417
12498
|
["--workspace SLUG", "el espacio (default: el de ghosty use)"],
|
|
@@ -12419,12 +12500,17 @@ var HELPS_ES = {
|
|
|
12419
12500
|
["--status S", "ls: s\xF3lo esa etapa (planning, building, checking, pr_review, merged\u2026)"],
|
|
12420
12501
|
["--limit N", "ls: cu\xE1ntos (default 30)"],
|
|
12421
12502
|
["--full", "show: imprime el plan vigente"],
|
|
12503
|
+
["--timing", "show: cada turno de cada rol (cr\xEDtico, @plan, @build, @check) con minutos, minutos en tools, modelo y USD; el reloj del pedido partido en trabajando, esperando a una persona (plan o PR por aprobar) y lo dem\xE1s (CI, colas); y los comandos de 60 s o m\xE1s"],
|
|
12504
|
+
["--since 30d", "stats: cu\xE1nto hacia atr\xE1s (d\xEDas, default 30)"],
|
|
12505
|
+
["--repo OWNER/REPO", "stats: s\xF3lo ese repo (due\xF1o/repo)"],
|
|
12422
12506
|
["-n, --dry-run", "rename: ense\xF1a el cambio, no cambia nada"],
|
|
12423
12507
|
["--yes", "rename: se salta la confirmaci\xF3n (obligatorio sin terminal)"]
|
|
12424
12508
|
],
|
|
12425
12509
|
notes: [
|
|
12426
12510
|
"Leer pide ser miembro del espacio; renombrar, ser due\xF1o, admin o staff de Ghosty, y queda en bit\xE1cora (factory.run_rename). La tarjeta del room toma el t\xEDtulo nuevo en su siguiente refresco.",
|
|
12427
|
-
"Qui\xE9n es @plan o @build: ghosty agents ls --handle plan --workspace SLUG."
|
|
12511
|
+
"Qui\xE9n es @plan o @build: ghosty agents ls --handle plan --workspace SLUG.",
|
|
12512
|
+
"stats: pedidos, % mezclados, mediana de minutos hasta el PR y hasta el merge, vueltas @check\u2192@build y CI rojo por pedido, y costo por pedido y por PR mezclado. El costo incluye el primer turno de @plan en el hilo, antes de que naciera el pedido.",
|
|
12513
|
+
"--timing lee la transcripci\xF3n de cada rol: los motores sin una (ACP) salen con minutos y costo, pero sin comandos."
|
|
12428
12514
|
]
|
|
12429
12515
|
}
|
|
12430
12516
|
};
|
|
@@ -12469,6 +12555,7 @@ var HELPS = {
|
|
|
12469
12555
|
"ghosty agents embed <agent-id> [--domains \u2026] [--per-visitor N] [--per-ip N] [--welcome TEXT] [--color #hex] [--rotate] # state + the <script> to paste",
|
|
12470
12556
|
'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
12557
|
"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",
|
|
12558
|
+
"ghosty agents embed <agent-id> [--copy '{json}'|none] [--copy-file texts.json] # what it says while it works",
|
|
12472
12559
|
"ghosty agents embed <agent-id> theme # export the theme as JSON (edit it, apply it back with --style-file)",
|
|
12473
12560
|
"ghosty agents embed <agent-id> --out src/components/GhostyChat.tsx [--force] # your own React component, token and theme baked in",
|
|
12474
12561
|
"ghosty agents rm <agent-id> [--from-workspace] [--yes]"
|
|
@@ -12530,6 +12617,8 @@ var HELPS = {
|
|
|
12530
12617
|
["--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
12618
|
["--site-url URL", "embed: the site to take colors from (implies --theme-source site)"],
|
|
12532
12619
|
["--style-file FILE", "embed: apply a theme exported with `agents embed <id> theme` (or a plain style object)"],
|
|
12620
|
+
["--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"],
|
|
12621
|
+
["--copy-file FILE", "embed: apply the copy object from a file (or the `copy` of an exported theme)"],
|
|
12533
12622
|
["--out FILE.tsx|.jsx [--force]", "embed: write a COMPLETE, editable React component into your project (no package needed) with your token and theme baked in at the top; won't overwrite an existing file without --force"],
|
|
12534
12623
|
["--check PATH", "box: this file must exist in the machine (repeatable), e.g. a file the new image brings"],
|
|
12535
12624
|
["--contains PATH=TEXT", "box: that file must contain TEXT (a change that adds no files)"],
|
|
@@ -12570,6 +12659,7 @@ var HELPS = {
|
|
|
12570
12659
|
'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
12660
|
"ghosty agents embed <id> --theme-source site --site-url https://palmeralegal.mx",
|
|
12572
12661
|
"ghosty agents embed <id> theme > tema.json \xB7 ghosty agents embed <id> --style-file tema.json",
|
|
12662
|
+
`ghosty agents embed <id> --copy '{"waking":"Un momento, por favor","thinking":["Revisando su consulta\u2026","Consultando el expediente\u2026"],"showDuration":false}'`,
|
|
12573
12663
|
"ghosty agents box <id> --contains /opt/claude-worker/skills/pdf-doc/render-doc.mjs=pdf-templates",
|
|
12574
12664
|
"ghosty agents set <id> --tool wa:actualizar_identidad=off,teams:actualizar_identidad=on",
|
|
12575
12665
|
'ghosty agents set minighosty --bag "MiniGhosty" --avatar https://www.ghosty.studio/avatars/minighosty.png',
|
|
@@ -13154,10 +13244,12 @@ HELPS.audit = {
|
|
|
13154
13244
|
learn: `${REF}#bitacora`
|
|
13155
13245
|
};
|
|
13156
13246
|
HELPS.factory = {
|
|
13157
|
-
summary: "A workspace's Software Factory runs (they live in its Teams): list them, see one (current plan, PR, preview, latest events, which role is working) and fix a run's title.",
|
|
13247
|
+
summary: "A workspace's Software Factory runs (they live in its Teams): list them with time and cost, see one (current plan, PR, preview, latest events, which role is working), see where its time and money went, totals for the workspace, and fix a run's title.",
|
|
13158
13248
|
usage: [
|
|
13159
13249
|
"ghosty factory runs ls [--workspace SLUG] [--room R] [--status S] [--limit 30]",
|
|
13160
13250
|
"ghosty factory runs show <run> [--workspace SLUG] [--full]",
|
|
13251
|
+
"ghosty factory runs show <run> --timing [--workspace SLUG]",
|
|
13252
|
+
"ghosty factory runs stats [--since 30d] [--repo OWNER/REPO] [--workspace SLUG]",
|
|
13161
13253
|
'ghosty factory runs rename <run> "Title" [--workspace SLUG] [--dry-run] [--yes]'
|
|
13162
13254
|
],
|
|
13163
13255
|
flags: [
|
|
@@ -13166,11 +13258,14 @@ HELPS.factory = {
|
|
|
13166
13258
|
["--status S", "ls: only that stage (planning, building, checking, pr_review, merged\u2026)"],
|
|
13167
13259
|
["--limit N", "ls: how many (default 30)"],
|
|
13168
13260
|
["--full", "show: print the current plan"],
|
|
13261
|
+
["--timing", "show: each turn of each role (critic, @plan, @build, @check) with minutes, minutes in tools, model and USD; the run's clock split into working, waiting for a person (plan or PR to approve) and the rest (CI, queues); and the commands that took 60 s or more"],
|
|
13262
|
+
["--since 30d", "stats: how far back (days, default 30)"],
|
|
13263
|
+
["--repo OWNER/REPO", "stats: only that repo"],
|
|
13169
13264
|
["-n, --dry-run", "rename: show the change, change nothing"],
|
|
13170
13265
|
["--yes", "rename: skip the confirmation (required without a terminal)"]
|
|
13171
13266
|
],
|
|
13172
|
-
examples: ["ghosty factory runs ls --workspace business --room mailmask", "ghosty factory runs show 15 --workspace business", 'ghosty factory runs rename 15 "Docs del CLI al d\xEDa" --workspace business --dry-run'],
|
|
13173
|
-
notes: ["Reading needs membership in the workspace; rename needs owner, admin or Ghosty staff, and is logged (factory.run_rename). The room's card picks the new title up on its next refresh.", "Who is @plan or @build: ghosty agents ls --handle plan --workspace SLUG."],
|
|
13267
|
+
examples: ["ghosty factory runs ls --workspace business --room mailmask", "ghosty factory runs show 15 --workspace business", "ghosty factory runs show 31 --timing --workspace mercadito-verde", "ghosty factory runs stats --since 7d --workspace mercadito-verde", 'ghosty factory runs rename 15 "Docs del CLI al d\xEDa" --workspace business --dry-run'],
|
|
13268
|
+
notes: ["Reading needs membership in the workspace; rename needs owner, admin or Ghosty staff, and is logged (factory.run_rename). The room's card picks the new title up on its next refresh.", "Who is @plan or @build: ghosty agents ls --handle plan --workspace SLUG.", "stats: runs, % merged, median minutes to PR and to merge, @check\u2192@build loops and red CI per run, and cost per run and per merged PR. The cost counts @plan's first turn in the thread, before the run existed.", "--timing reads each role's transcript: engines without one (ACP) show minutes and cost but no commands."],
|
|
13174
13269
|
learn: `${REF}#fabrica`
|
|
13175
13270
|
};
|
|
13176
13271
|
function helpText(cmd) {
|
|
@@ -13528,11 +13623,11 @@ async function planCmd(sub, slug, v, context) {
|
|
|
13528
13623
|
if (!slug) throw usage(t("Missing workspace.", "Falta el espacio."), "ghosty plan apply <workspace> [--dry-run] (or ghosty use <workspace>)");
|
|
13529
13624
|
const path = `/api/v2/workspaces/${encodeURIComponent(slug)}/plan/apply`;
|
|
13530
13625
|
const plan = await api(path, { method: "POST", body: { dryRun: true } });
|
|
13531
|
-
const
|
|
13626
|
+
const money2 = (n) => `$${n.toLocaleString("es-MX")} MXN`;
|
|
13532
13627
|
const text = [
|
|
13533
13628
|
`${bold(plan.workspace)} ${dim(plan.scheduledFor ? t(`scheduled for ${plan.scheduledFor.slice(0, 10)}`, `programado para el ${plan.scheduledFor.slice(0, 10)}`) : "")}`,
|
|
13534
13629
|
...plan.changes.map((c) => ` ~ ${c.label}: ${String(c.from)} \u2192 ${String(c.to)}`),
|
|
13535
|
-
` ${t("monthly", "al mes")}: ${
|
|
13630
|
+
` ${t("monthly", "al mes")}: ${money2(plan.monthlyTotal.from)} \u2192 ${money2(plan.monthlyTotal.to)}`
|
|
13536
13631
|
].join("\n");
|
|
13537
13632
|
if (v["dry-run"]) return out(plan, () => `${text}
|
|
13538
13633
|
${dim(t("Dry run: nothing applied.", "Simulaci\xF3n: no se aplic\xF3 nada."))}`);
|
|
@@ -14025,21 +14120,68 @@ ${dim(t("Times in UTC. --full for the whole detail; --json for everything.", "Ho
|
|
|
14025
14120
|
|
|
14026
14121
|
// src/commands/factory.ts
|
|
14027
14122
|
var enc12 = encodeURIComponent;
|
|
14123
|
+
var money = (n) => n === null || n === void 0 ? "\u2014" : `$${n.toFixed(2)}`;
|
|
14124
|
+
var mins = (n) => n === null || n === void 0 ? "\u2014" : n >= 120 ? `${(n / 60).toFixed(1)} h` : `${n} min`;
|
|
14125
|
+
var hhmm = (iso) => new Date(iso).toLocaleTimeString("es-MX", { hour: "2-digit", minute: "2-digit", hour12: false });
|
|
14028
14126
|
var short2 = (s2) => (s2 ?? "").slice(0, 16).replace("T", " ");
|
|
14029
14127
|
async function factoryCmd(sub, verb, ref, title, v, context) {
|
|
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");
|
|
14128
|
+
if (sub !== "runs") throw usage(t(`Unknown factory subcommand: ${sub ?? "(none)"}.`, `Subcomando de factory desconocido: ${sub ?? "(ninguno)"}.`), "ghosty factory runs ls|show|stats|rename \u2026");
|
|
14031
14129
|
const ws = v.workspace ?? context;
|
|
14032
14130
|
if (!ws) throw usage(t("Which workspace?", "\xBFQu\xE9 espacio?"), t("Add --workspace SLUG (or set one with ghosty use SLUG).", "Agrega --workspace SLUG (o fija uno con ghosty use SLUG)."));
|
|
14033
14131
|
const base = `/api/v2/workspaces/${enc12(ws)}/factory/runs`;
|
|
14034
14132
|
if (!verb || verb === "ls" || verb === "list") {
|
|
14035
14133
|
const qs = new URLSearchParams({ ...v.room ? { room: v.room } : {}, ...v.status ? { status: v.status } : {}, ...v.limit ? { limit: v.limit } : {} });
|
|
14036
14134
|
const r = await api(`${base}?${qs}`);
|
|
14037
|
-
const rows = r.runs.map((x) => ({ run: `#${x.id}`, title: x.title.slice(0, 60), status: x.status, room: x.room ? `#${x.room}` : "", pr: x.pr ? x.pr.replace(/^https:\/\/github\.com\//, "") : "", updated: short2(x.updatedAt) }));
|
|
14038
|
-
return out(r, () => rows.length ? `${table(rows, ["run", "title", "status", "room", "pr", "updated"])}
|
|
14135
|
+
const rows = r.runs.map((x) => ({ run: `#${x.id}`, title: x.title.slice(0, 60), status: x.status, room: x.room ? `#${x.room}` : "", pr: x.pr ? x.pr.replace(/^https:\/\/github\.com\//, "") : "", time: mins(x.durationMin), usd: money(x.usd), updated: short2(x.updatedAt) }));
|
|
14136
|
+
return out(r, () => rows.length ? `${table(rows, ["run", "title", "status", "room", "pr", "time", "usd", "updated"])}
|
|
14039
14137
|
${dim(`${t("Detail", "Detalle")}: ghosty factory runs show <run> --workspace ${r.workspace}`)}` : dim(t(`No factory runs in ${r.workspace}.`, `No hay pedidos de la f\xE1brica en ${r.workspace}.`)));
|
|
14040
14138
|
}
|
|
14139
|
+
if (verb === "stats") {
|
|
14140
|
+
const qs = new URLSearchParams({ since: v.since ?? "30d", ...v.repo ? { repo: v.repo } : {} });
|
|
14141
|
+
const { stats: s2, workspace } = await api(`/api/v2/workspaces/${enc12(ws)}/factory/stats?${qs}`);
|
|
14142
|
+
return out({ workspace, stats: s2 }, () => {
|
|
14143
|
+
if (!s2.runs) return dim(t(`No factory runs in ${workspace} in the last ${s2.sinceDays} days.`, `No hay pedidos de la f\xE1brica en ${workspace} en los \xFAltimos ${s2.sinceDays} d\xEDas.`));
|
|
14144
|
+
return [
|
|
14145
|
+
bold(t(`${workspace} \xB7 last ${s2.sinceDays} days${s2.repo ? ` \xB7 ${s2.repo}` : ""}`, `${workspace} \xB7 \xFAltimos ${s2.sinceDays} d\xEDas${s2.repo ? ` \xB7 ${s2.repo}` : ""}`)),
|
|
14146
|
+
`${t("runs", "pedidos")}: ${s2.runs} \xB7 ${t("merged", "mezclados")}: ${s2.merged} (${s2.mergedPct ?? "\u2014"} %) ${dim(s2.byStatus.map((b) => `${b.status} ${b.runs}`).join(" \xB7 "))}`,
|
|
14147
|
+
`${t("median to PR", "mediana hasta el PR")}: ${mins(s2.medianMinutesToPr)} \xB7 ${t("to merge", "hasta el merge")}: ${mins(s2.medianMinutesToMerge)}`,
|
|
14148
|
+
`${t("@check\u2192@build loops per run", "vueltas @check\u2192@build por pedido")}: ${s2.avgLoops ?? "\u2014"} \xB7 ${t("red CI per run", "CI rojo por pedido")}: ${s2.avgCiFails ?? "\u2014"}`,
|
|
14149
|
+
`${t("cost", "costo")}: ${money(s2.usd)} USD \xB7 ${t("per run", "por pedido")} ${money(s2.usdPerRun)} \xB7 ${bold(t(`per merged PR ${money(s2.usdPerMergedPr)}`, `por PR mezclado ${money(s2.usdPerMergedPr)}`))}`,
|
|
14150
|
+
...s2.byRepo.length > 1 ? ["", table(s2.byRepo.map((b) => ({ repo: b.repo, runs: b.runs, merged: b.merged, usd: money(b.usd) })), ["repo", "runs", "merged", "usd"])] : []
|
|
14151
|
+
].join("\n");
|
|
14152
|
+
});
|
|
14153
|
+
}
|
|
14041
14154
|
if (!ref) throw usage(t("Missing run.", "Falta el pedido."), `ghosty factory runs ${verb} <run> --workspace ${ws}`);
|
|
14042
14155
|
const path = `${base}/${enc12(ref.replace(/^#/, ""))}`;
|
|
14156
|
+
if (verb === "show" && v.timing) {
|
|
14157
|
+
const { timing: tm, workspace } = await api(`${path}/timing`);
|
|
14158
|
+
return out({ workspace, timing: tm }, () => {
|
|
14159
|
+
const c = tm.clock;
|
|
14160
|
+
const head = [
|
|
14161
|
+
`${bold(`#${tm.run}`)} ${dim(`\xB7 ${tm.status} \xB7 ${t("loops", "vueltas")} ${tm.loops} \xB7 ${t("red CI", "CI rojo")} ${tm.ciFails}`)} \xB7 ${bold(money(tm.usd))} USD`,
|
|
14162
|
+
`${t("clock", "reloj")}: ${mins(c.totalMinutes)} = ${t("working", "trabajando")} ${mins(c.workingMinutes)} \xB7 ${t("waiting for a person", "esperando a una persona")} ${mins(c.waitingHumanMinutes)} \xB7 ${t("other (CI, queues)", "lo dem\xE1s (CI, colas)")} ${mins(c.otherMinutes)}`,
|
|
14163
|
+
dim(c.byState.map((b) => `${b.state} ${mins(b.minutes)}`).join(" \u2192 ")),
|
|
14164
|
+
""
|
|
14165
|
+
];
|
|
14166
|
+
const rows = tm.steps.map((s2) => ({
|
|
14167
|
+
[t("at", "hora")]: hhmm(s2.startedAt),
|
|
14168
|
+
[t("step", "paso")]: `@${s2.role ?? "?"}${s2.critic ? t(" (critic)", " (cr\xEDtico)") : ""}${s2.prePlan ? t(" (thread)", " (hilo)") : ""}`,
|
|
14169
|
+
[t("time", "tiempo")]: s2.state === "running" ? `${mins(s2.minutes)} \u2026` : mins(s2.minutes),
|
|
14170
|
+
[t("in tools", "en tools")]: mins(s2.toolMinutes),
|
|
14171
|
+
model: (s2.model ?? "\u2014").replace(/^claude-/, ""),
|
|
14172
|
+
usd: money(s2.usd),
|
|
14173
|
+
[t("state", "estado")]: s2.state === "error" ? red(s2.state) : s2.state
|
|
14174
|
+
}));
|
|
14175
|
+
const cols = Object.keys(rows[0] ?? {});
|
|
14176
|
+
const slow = tm.steps.flatMap((s2) => s2.slow.map((x) => ({ ...x, role: s2.role })));
|
|
14177
|
+
return [
|
|
14178
|
+
...head,
|
|
14179
|
+
rows.length ? table(rows, cols) : dim(t("No turns found for this run.", "No hay turnos de este pedido.")),
|
|
14180
|
+
...slow.length ? ["", bold(t("Slowest commands (\u2265 60 s)", "Comandos m\xE1s lentos (\u2265 60 s)")), ...slow.map((x) => ` ${hhmm(x.at)} @${x.role} ${x.secs === null ? red(t("no result", "sin resultado")) : mins(Math.round(x.secs / 6) / 10)} ${dim(`${x.tool}: ${x.input}`)}`)] : [],
|
|
14181
|
+
...tm.steps.filter((s2) => s2.why).map((s2) => dim(` @${s2.role}: ${t("no timeline", "sin l\xEDnea de tiempo")} (${s2.why})`))
|
|
14182
|
+
].join("\n");
|
|
14183
|
+
});
|
|
14184
|
+
}
|
|
14043
14185
|
if (verb === "show") {
|
|
14044
14186
|
const { run: r } = await api(path);
|
|
14045
14187
|
const lines = [
|
|
@@ -14067,7 +14209,7 @@ ${dim(t("Dry run: nothing changed.", "Simulacro: no se cambi\xF3 nada."))}`);
|
|
|
14067
14209
|
const r = await api(path, { body: { title } });
|
|
14068
14210
|
return out(r, () => `${lavender("\u2713")} ${line2} ${dim(t("(logged; the room card picks it up on its next refresh)", "(queda en bit\xE1cora; la tarjeta del room lo toma en su siguiente refresco)"))}`);
|
|
14069
14211
|
}
|
|
14070
|
-
throw usage(t(`Unknown runs subcommand: ${verb}.`, `Subcomando de runs desconocido: ${verb}.`), 'ghosty factory runs ls | show <run> | rename <run> "title"');
|
|
14212
|
+
throw usage(t(`Unknown runs subcommand: ${verb}.`, `Subcomando de runs desconocido: ${verb}.`), 'ghosty factory runs ls | show <run> [--timing] | stats [--since 30d] | rename <run> "title"');
|
|
14071
14213
|
}
|
|
14072
14214
|
async function agentsByHandle(handle, workspace) {
|
|
14073
14215
|
const qs = new URLSearchParams({ handle, ...workspace ? { workspace } : {} });
|
|
@@ -14359,7 +14501,7 @@ ${bold("Workspaces")} ${dim("(changing them: Ghosty staff)")}
|
|
|
14359
14501
|
staff ls [staff-email] [client-email] Ghosty staff assigned to client accounts (staff) [--all: also revoked]
|
|
14360
14502
|
staff assign|unassign <staff> <client> Staff sees and runs that account's agents, documents, databases and boards as themselves (no keys, billing or transfers; logged) [--reason]
|
|
14361
14503
|
audit ls The audit log: who changed what (agent page, channels, pauses, replies, transfers, "log in as") [--workspace SLUG] [--agent ID] [--actor EMAIL] [--action A] [--since 24h] [--limit N]
|
|
14362
|
-
factory runs ls|show|rename
|
|
14504
|
+
factory runs ls|show|stats|rename A workspace's Software Factory runs: list (time and cost), detail (plan, PR, preview, who is working), where the time and money went, totals, fix a title [--workspace SLUG] [--room R] [--status S] [show --timing] [stats --since 30d --repo R]
|
|
14363
14505
|
|
|
14364
14506
|
${bold("Agents")}
|
|
14365
14507
|
agents ls List your agents [--engine E] [--ids: only ids, for xargs] \xB7 --user EMAIL --app all|android|ios|desktop: as their app sees it (staff)
|
|
@@ -14400,7 +14542,8 @@ ${bold("Agents")}
|
|
|
14400
14542
|
look: [--title T] [--subtitle T] [--avatar URL|none] [--icon chat|avatar|ghost] [--placeholder T] [--position right|left] [--offset X[,Y]]
|
|
14401
14543
|
[--theme light|dark|auto] [--bubble-text T] [--auto-open SECONDS|off] [--starters "a|b|c"] [--font NAME] [--radius 0-24] [--style '{json}'|none]
|
|
14402
14544
|
theme: [--theme-source custom|teams|site] [--workspace SLUG] [--site-url URL] [--style-file theme.json]
|
|
14403
|
-
|
|
14545
|
+
texts while it works: [--copy '{"waking":"One moment, please","thinking":["Reviewing your question\u2026"]}'|none] [--copy-file copy.json]
|
|
14546
|
+
agents embed <id> theme Export the widget theme + texts as JSON (edit it and apply it back with --style-file)
|
|
14404
14547
|
agents embed <id> --out F.tsx Write your own React chat component (token + theme baked in; --force to overwrite) \xB7 the output also prints the React snippet
|
|
14405
14548
|
agents embed <id> Its state and the snippet to paste (claude engine only: it isolates each visitor)
|
|
14406
14549
|
[--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]
|
|
@@ -14522,7 +14665,7 @@ ${bold("Espacios")} ${dim("(cambiarlos: staff de Ghosty)")}
|
|
|
14522
14665
|
staff ls [correo-staff] [correo-cliente] Staff de Ghosty asignado a cuentas cliente (staff) [--all: tambi\xE9n revocadas]
|
|
14523
14666
|
staff assign|unassign <staff> <cliente> El staff ve y opera agentes, documentos, bases y tableros de esa cuenta como s\xED mismo (sin llaves, cobro ni traspasos; queda en bit\xE1cora) [--reason]
|
|
14524
14667
|
audit ls La bit\xE1cora: qui\xE9n cambi\xF3 qu\xE9 (ficha, canales, pausas, respuestas, traspasos, \xABentrar como\xBB) [--workspace SLUG] [--agent ID] [--actor CORREO] [--action A] [--since 24h] [--limit N]
|
|
14525
|
-
factory runs ls|show|rename
|
|
14668
|
+
factory runs ls|show|stats|rename Los pedidos de la Software Factory de un espacio: lista (tiempo y costo), detalle (plan, PR, preview, qui\xE9n trabaja), en qu\xE9 se fue el tiempo y el dinero, totales, corregir un t\xEDtulo [--workspace SLUG] [--room R] [--status S] [show --timing] [stats --since 30d --repo R]
|
|
14526
14669
|
|
|
14527
14670
|
${bold("Agentes")}
|
|
14528
14671
|
agents ls Lista tus agentes [--engine E] [--ids: s\xF3lo ids, para xargs] \xB7 --user CORREO --app all|android|ios|desktop: como lo ve su app (staff)
|
|
@@ -14563,7 +14706,8 @@ ${bold("Agentes")}
|
|
|
14563
14706
|
look: [--title T] [--subtitle T] [--avatar URL|none] [--icon chat|avatar|ghost] [--placeholder T] [--position right|left] [--offset X[,Y]]
|
|
14564
14707
|
[--theme light|dark|auto] [--bubble-text T] [--auto-open SEGUNDOS|off] [--starters "a|b|c"] [--font NOMBRE] [--radius 0-24] [--style '{json}'|none]
|
|
14565
14708
|
tema: [--theme-source custom|teams|site] [--workspace SLUG] [--site-url URL] [--style-file tema.json]
|
|
14566
|
-
|
|
14709
|
+
textos mientras trabaja: [--copy '{"waking":"Un momento, por favor","thinking":["Revisando su consulta\u2026"]}'|none] [--copy-file textos.json]
|
|
14710
|
+
agents embed <id> theme Exporta el tema y los textos del widget en JSON (ed\xEDtalo y vuelve a aplicarlo con --style-file)
|
|
14567
14711
|
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
14712
|
agents embed <id> Su estado y el snippet para pegar (Claude o MiniGhosty: a\xEDslan a cada visitante)
|
|
14569
14713
|
[--slug NOMBRE] [--expires "AAAA-MM-DDTHH:MM"|no] [--turns N] [--per-ip N] [--welcome TEXTO] [--chips "a|b|c"] [--starter TEXTO] [--sponsor on|off] [--rotate]
|
|
@@ -14761,6 +14905,8 @@ async function main() {
|
|
|
14761
14905
|
font: { type: "string" },
|
|
14762
14906
|
radius: { type: "string" },
|
|
14763
14907
|
"style-file": { type: "string" },
|
|
14908
|
+
copy: { type: "string" },
|
|
14909
|
+
"copy-file": { type: "string" },
|
|
14764
14910
|
"theme-source": { type: "string" },
|
|
14765
14911
|
"site-url": { type: "string" },
|
|
14766
14912
|
sponsor: { type: "string" },
|
|
@@ -14779,6 +14925,8 @@ async function main() {
|
|
|
14779
14925
|
action: { type: "string" },
|
|
14780
14926
|
verbose: { type: "boolean" },
|
|
14781
14927
|
full: { type: "boolean" },
|
|
14928
|
+
timing: { type: "boolean" },
|
|
14929
|
+
repo: { type: "string" },
|
|
14782
14930
|
"as-formmy": { type: "boolean" },
|
|
14783
14931
|
"key-column": { type: "string" },
|
|
14784
14932
|
"unset-env": { type: "string" },
|
|
@@ -14915,7 +15063,7 @@ async function main() {
|
|
|
14915
15063
|
const tokens = splitTokens(pos, v);
|
|
14916
15064
|
globals.token = tokens.session;
|
|
14917
15065
|
globals.profile = v.profile;
|
|
14918
|
-
if (v.version) return out({ version: "0.
|
|
15066
|
+
if (v.version) return out({ version: "0.55.0" }, () => "0.55.0");
|
|
14919
15067
|
for (const n of normalizeArgs(pos, v)) if (!globals.json) info(dim(n));
|
|
14920
15068
|
const context = (await readConfig()).workspace;
|
|
14921
15069
|
if (!explicitToken()?.startsWith("gat_")) {
|
|
@@ -15081,7 +15229,7 @@ async function main() {
|
|
|
15081
15229
|
throw unknown(cmd, cmd, COMMANDS, "ghosty --help");
|
|
15082
15230
|
}
|
|
15083
15231
|
}
|
|
15084
|
-
var notice = updateNotice("0.
|
|
15232
|
+
var notice = updateNotice("0.55.0").catch(() => null);
|
|
15085
15233
|
main().then(
|
|
15086
15234
|
async () => {
|
|
15087
15235
|
const n = await notice;
|
package/package.json
CHANGED