@ghostystudio/cli 0.27.1 → 0.29.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 +18 -1
- package/dist/index.js +340 -72
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ ghosty engines ls [--models] # valid --engine / --model
|
|
|
20
20
|
ghosty bags ls|create|set|rm <workspace> ["bag"] [--tokens 20M] [--recurring on|off] [--hard-cap on|off] [--note] # token bags (changing: staff)
|
|
21
21
|
ghosty spaces renew <workspace> --until YYYY-MM-DD [--note] [--dry-run] # staff
|
|
22
22
|
ghosty spaces rename <old> <new> [--dry-run] # staff, slug only
|
|
23
|
-
ghosty agents ls
|
|
23
|
+
ghosty agents ls [--engine E] [--ids] [--sponsor EMAIL | --owner EMAIL] # --sponsor/--owner: any account (Ghosty staff)
|
|
24
24
|
ghosty agents create --name NAME [--engine E] [--model M] [--prompt TEXT | --prompt-file FILE] [--env K=V,…] [--workspace SLUG] [--channels teams=off]
|
|
25
25
|
ghosty agents get <id> [--fields name,model,prompt] [--prompt-out FILE]
|
|
26
26
|
ghosty agents export <id> [--out DIR] # the agent as a YAML file (+ its skills with --out)
|
|
@@ -77,6 +77,8 @@ ghosty credentials ls|set|rm [provider] [--from-env VAR] # never as an argume
|
|
|
77
77
|
ghosty docs
|
|
78
78
|
|
|
79
79
|
ghosty partner ls|enable|credentials|agent|tenants|audit|try # one agent for each of YOUR customers: ghosty partner --help
|
|
80
|
+
ghosty demos try <slug> "message" ["message 2"…] [--new] [--recycle-between --agent X] # try a public demo as a visitor
|
|
81
|
+
ghosty demos history <id> [--since 2d] [--session web:…] [--timeline] [--full] # what demo visitors did (Ghosty staff)
|
|
80
82
|
```
|
|
81
83
|
|
|
82
84
|
Global: `--json` (stdout is JSON only), `--token <token>` / `GHOSTY_TOKEN` (a `gat_…` agent token reaches only that agent), `--profile <name>` / `GHOSTY_PROFILE` (a separate saved session, e.g. one per account), `--yes`, `--help`, `--version`. Env: `GHOSTY_URL`, `XDG_CONFIG_HOME`, `NO_COLOR`, `GHOSTY_NO_UPDATE_CHECK`; partner commands sign with `GHOSTY_PARTNER_KEY` + `GHOSTY_PARTNER_SECRET` when set.
|
|
@@ -87,6 +89,21 @@ Every command has its own help with examples: `ghosty <command> --help` or `ghos
|
|
|
87
89
|
|
|
88
90
|
Exit codes: `0` ok · `1` API error · `2` usage error · `3` not signed in.
|
|
89
91
|
|
|
92
|
+
### Changes in 0.29.0
|
|
93
|
+
|
|
94
|
+
- `agents ls --engine E --ids` (ids only, for `xargs`); staff: `agents ls --sponsor EMAIL | --owner EMAIL` lists any account's agents, and `agents get|set|box` work on them by id (logged).
|
|
95
|
+
- `try --media FILE` and `chat --media FILE` attach files as from the app (no `--waba` needed).
|
|
96
|
+
- `turns cancel <agent> <conversation>`: stops the turn AND that conversation's background jobs (video, subagents, pending wake-ups).
|
|
97
|
+
- `agents box --recycle` on Ghosty · Lite / Goose recreates its box with the current image (`--reenv`, `--force`); `agents set --prompt-mode append|replace`.
|
|
98
|
+
|
|
99
|
+
### Changes in 0.28.0
|
|
100
|
+
|
|
101
|
+
- `ghosty demos try <slug> "…"`: try a public demo as a visitor from the terminal (keeps the thread; `--new`; `--recycle-between --agent X` checks memory across boxes). `ghosty demos history <agent>` (Ghosty staff): visitors' turns, messages, files and clones, `--timeline`.
|
|
102
|
+
- `turns ls --session S`, and on Claude agents a «resumed» column: ✓ when the turn kept its SDK session, ⚠ from scratch (orphan|stale|replaced) when it lost its memory. `turns show` says the same, with the Claude CLI version.
|
|
103
|
+
- `partner credentials create|rotate --env-file PATH` (mode 600, nothing printed) or `--format env` (pipe to `fly secrets import`). The secret's env var is now `GHOSTY_PARTNER_KEY_SECRET` (`GHOSTY_PARTNER_SECRET` still works).
|
|
104
|
+
- `partner agent apply --prompt-file prompt.md`; `partner try --verbose` prints each tool's arguments and milliseconds.
|
|
105
|
+
- Empty `partner ls|credentials ls|tenants ls` say what to do next. `login --device --profile <client>` explains how to sign in a client's account (they approve the link).
|
|
106
|
+
|
|
90
107
|
### Changes in 0.27.1
|
|
91
108
|
|
|
92
109
|
- A 404 with a reason (no such bag, room or thread) prints that reason instead of a generic «Not found».
|
package/dist/index.js
CHANGED
|
@@ -8014,6 +8014,12 @@ var SERVER_MESSAGES = [
|
|
|
8014
8014
|
["el mensaje {} es una respuesta; usa el del inicio del hilo ({})", "message {} is a reply; use the one that starts the thread ({})"],
|
|
8015
8015
|
["dueAt tiene que ser una fecha futura (epoch en segundos)", "dueAt must be a future date (epoch seconds)"],
|
|
8016
8016
|
["no hay un turno pendiente con ese id (\xBFya corri\xF3?)", "no pending turn with that id (did it already run?)"],
|
|
8017
|
+
["los chats de los visitantes de un demo: s\xF3lo el staff de Ghosty", "a demo's visitor chats: Ghosty staff only"],
|
|
8018
|
+
["since inv\xE1lido: \xAB{}\xBB (7d, 12h o una fecha ISO)", "invalid since: \xAB{}\xBB (7d, 12h or an ISO date)"],
|
|
8019
|
+
["buscar agentes de otras cuentas: s\xF3lo el staff de Ghosty", "searching other accounts' agents: Ghosty staff only"],
|
|
8020
|
+
["pon al menos un filtro: sponsor, owner, workspace o engine", "add at least one filter: sponsor, owner, workspace or engine"],
|
|
8021
|
+
["no hay una cuenta con ese correo", "there's no account with that email"],
|
|
8022
|
+
["files: [{ name, mimeType, data (base64) }], hasta 8 de 10 MB", "files: [{ name, mimeType, data (base64) }], up to 8 of 10 MB"],
|
|
8017
8023
|
// — Infra interna (casi nunca llega al CLI, pero por si acaso)
|
|
8018
8024
|
["SESSION_SECRET ausente en producci\xF3n \u2014 las cookies quedar\xEDan firmadas con un valor p\xFAblico. Inyectarlo con ./scripts/set_gs_secret.sh SESSION_SECRET (openssl rand -hex 32).", "SESSION_SECRET missing in production \u2014 cookies would be signed with a public value. Inject it with ./scripts/set_gs_secret.sh SESSION_SECRET (openssl rand -hex 32)."],
|
|
8019
8025
|
["sqld: fetch sin namespace", "sqld: fetch without namespace"],
|
|
@@ -8232,6 +8238,7 @@ async function loginDevice() {
|
|
|
8232
8238
|
if (globals.json) process.stdout.write(JSON.stringify({ event: "login_url", url: d.verification_uri_complete, code: d.user_code, method: "device" }) + "\n");
|
|
8233
8239
|
info(`${lavender("\u203A")} ${t("Open this link on any device and confirm the code", "Abre esta liga en cualquier dispositivo y confirma el c\xF3digo")} ${lavender(d.user_code)}:`);
|
|
8234
8240
|
info(` ${d.verification_uri_complete}`);
|
|
8241
|
+
if (globals.profile) info(dim(t(` Signing in someone else? Send them this link and code; when they approve, the session lands in profile \xAB${globals.profile}\xBB (use it with --profile ${globals.profile}; ghosty logout --profile ${globals.profile} revokes it).`, ` \xBFEs la cuenta de otra persona? M\xE1ndale esta liga y el c\xF3digo; cuando apruebe, la sesi\xF3n queda en el perfil \xAB${globals.profile}\xBB (\xFAsalo con --profile ${globals.profile}; ghosty logout --profile ${globals.profile} la revoca).`)));
|
|
8235
8242
|
let interval = (d.interval ?? 5) * 1e3;
|
|
8236
8243
|
const deadline = Date.now() + (d.expires_in ?? 600) * 1e3;
|
|
8237
8244
|
while (Date.now() < deadline) {
|
|
@@ -8315,7 +8322,7 @@ async function postLong(path, body, timeoutMs = 9e5) {
|
|
|
8315
8322
|
const req = httpsRequest(`${base}${path}`, {
|
|
8316
8323
|
method: "POST",
|
|
8317
8324
|
timeout: timeoutMs,
|
|
8318
|
-
headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.
|
|
8325
|
+
headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.29.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
|
|
8319
8326
|
}, (res) => {
|
|
8320
8327
|
let buf = "";
|
|
8321
8328
|
res.on("data", (c) => buf += c);
|
|
@@ -8357,7 +8364,7 @@ async function request(path, opts = {}, retried = false) {
|
|
|
8357
8364
|
method: opts.method ?? (opts.body !== void 0 || opts.raw !== void 0 ? "POST" : "GET"),
|
|
8358
8365
|
headers: {
|
|
8359
8366
|
authorization: `Bearer ${token}`,
|
|
8360
|
-
"user-agent": `ghosty-cli/${"0.
|
|
8367
|
+
"user-agent": `ghosty-cli/${"0.29.0"}`,
|
|
8361
8368
|
"accept-language": lang(),
|
|
8362
8369
|
...opts.body !== void 0 ? { "content-type": "application/json" } : {},
|
|
8363
8370
|
...opts.headers
|
|
@@ -8435,6 +8442,16 @@ import { basename, join as join2 } from "node:path";
|
|
|
8435
8442
|
var enc = encodeURIComponent;
|
|
8436
8443
|
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
8437
8444
|
var MIME = { jpg: "image/jpeg", jpeg: "image/jpeg", png: "image/png", webp: "image/webp", gif: "image/gif", pdf: "application/pdf", ogg: "audio/ogg", opus: "audio/ogg", mp3: "audio/mpeg", m4a: "audio/mp4", wav: "audio/wav", mp4: "video/mp4" };
|
|
8445
|
+
async function localFiles(paths) {
|
|
8446
|
+
const { readFile: readFile13 } = await import("node:fs/promises");
|
|
8447
|
+
const { basename: basename4 } = await import("node:path");
|
|
8448
|
+
return Promise.all(
|
|
8449
|
+
paths.map(async (p) => {
|
|
8450
|
+
const ext = p.toLowerCase().split(".").pop() ?? "";
|
|
8451
|
+
return { name: basename4(p), mimeType: MIME[ext] ?? "application/octet-stream", data: (await readFile13(p)).toString("base64") };
|
|
8452
|
+
})
|
|
8453
|
+
);
|
|
8454
|
+
}
|
|
8438
8455
|
async function tryPost(id, body) {
|
|
8439
8456
|
for (let attempt = 0; ; attempt++) {
|
|
8440
8457
|
const r = await postLong(`/api/v2/agents/${enc(id)}/try`, body);
|
|
@@ -8487,16 +8504,16 @@ async function tryWaba(id, text, v) {
|
|
|
8487
8504
|
if ((v.media?.length ?? 0) > 1) throw usage(t("One --media per turn (WhatsApp sends one file per message).", "Un --media por turno (WhatsApp manda un archivo por mensaje)."), t("Send several turns, one file each.", "Manda varios turnos, un archivo en cada uno."));
|
|
8488
8505
|
if (v["as-formmy"] && !v.media?.length) throw usage(t("--as-formmy goes with --media FILE.", "--as-formmy va con --media ARCHIVO."), t("Example", "Ejemplo") + ': ghosty try <agent-id> "" --waba --session 5210000000009 --media nota.ogg --as-formmy');
|
|
8489
8506
|
if (!text && !v.media?.length && !v.reset) throw usage(t("Missing message.", "Falta el mensaje."), t("Example", "Ejemplo") + ': ghosty try <agent-id> "hola" --waba --session 5210000000009');
|
|
8490
|
-
let
|
|
8507
|
+
let history2;
|
|
8491
8508
|
if (v.history) {
|
|
8492
8509
|
const raw = JSON.parse(await readFile2(v.history, "utf8"));
|
|
8493
|
-
|
|
8494
|
-
if (!Array.isArray(
|
|
8510
|
+
history2 = Array.isArray(raw) ? raw : raw.messages;
|
|
8511
|
+
if (!Array.isArray(history2)) throw usage(t(`${v.history}: expected [{role, text, at?}] or {messages: [...]}.`, `${v.history}: se esperaba [{role, text, at?}] o {messages: [...]}.`));
|
|
8495
8512
|
if (!v.reset) info(dim(t("Note: history only counts on the first turn of a session; add --reset to start over.", "Nota: el historial s\xF3lo cuenta en el primer turno de una sesi\xF3n; agrega --reset para empezar de nuevo.")));
|
|
8496
8513
|
}
|
|
8497
8514
|
const media = v.media?.[0] ? await uploadMedia(v.media[0]) : void 0;
|
|
8498
8515
|
if (process.stderr.isTTY && !globals.json && (text || media)) info(dim(t("Thinking\u2026 (the real WhatsApp path; nothing is sent)", "Pensando\u2026 (el camino real de WhatsApp; no se manda nada)")));
|
|
8499
|
-
const r = await tryPost(id, { as: "waba", session: phone, reset: v.reset, text: text ?? "", ...media ? { media } : {}, ...
|
|
8516
|
+
const r = await tryPost(id, { as: "waba", session: phone, reset: v.reset, text: text ?? "", ...media ? { media } : {}, ...history2 ? { history: history2 } : {}, ...v["as-formmy"] ? { asFormmy: true } : {}, ...v.keep ? { keep: true } : {} });
|
|
8500
8517
|
if (r.status !== 200) throw Object.assign(new Error(`${r.status} ${r.body.error ? serverText(r.body.error) : t("turn failed", "el turno fall\xF3")}`), { code: 1 });
|
|
8501
8518
|
const via = r.body.media ? `
|
|
8502
8519
|
${r.body.media.copied ? dim(t("attachment: downloaded through the Formmy-like proxy and copied to gs", "adjunto: descargado por el proxy tipo Formmy y copiado a gs")) : red(t("attachment: NOT copied to gs (the proxy download failed)", "adjunto: NO se copi\xF3 a gs (fall\xF3 la descarga por el proxy)"))}` : "";
|
|
@@ -8560,9 +8577,16 @@ var need = (v, what, example) => {
|
|
|
8560
8577
|
if (!v) throw usage(t(`Missing ${what}.`, `Falta ${WHAT_ES[what] ?? what}.`), `${t("Example", "Ejemplo")}: ${example}`);
|
|
8561
8578
|
return v;
|
|
8562
8579
|
};
|
|
8563
|
-
async function agentsLs(workspace) {
|
|
8580
|
+
async function agentsLs(workspace, f = {}) {
|
|
8581
|
+
if (f.sponsor || f.owner) {
|
|
8582
|
+
const q = new URLSearchParams({ ...f.sponsor ? { sponsor: f.sponsor } : {}, ...f.owner ? { owner: f.owner } : {}, ...f.engine ? { engine: f.engine } : {}, ...workspace && !f.all ? { workspace } : {} });
|
|
8583
|
+
const s = await api(`/api/v2/agents?${q}`);
|
|
8584
|
+
if (f.ids) return void process.stdout.write(s.agents.map((a) => a.id).join("\n") + (s.agents.length ? "\n" : ""));
|
|
8585
|
+
return out(s.agents, () => table(s.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"]));
|
|
8586
|
+
}
|
|
8564
8587
|
const r = await api("/api/v2/me/agents?client=cli");
|
|
8565
|
-
const list = workspace ? r.agentes.filter((a) => a.espacio?.nombre === workspace) : r.agentes;
|
|
8588
|
+
const list = (workspace ? r.agentes.filter((a) => a.espacio?.nombre === workspace) : r.agentes).filter((a) => !f.engine || (a.motor ?? a.origen) === f.engine);
|
|
8589
|
+
if (f.ids) return void process.stdout.write(list.map((a) => a.id).join("\n") + (list.length ? "\n" : ""));
|
|
8566
8590
|
const spaceOf = (a) => a.espacio?.tipo === "workspace" ? a.espacio.nombre : a.espacio?.tipo ?? "";
|
|
8567
8591
|
const rows = list.map((a) => ({
|
|
8568
8592
|
id: a.id,
|
|
@@ -8661,12 +8685,24 @@ ${dim(t("Dry run: nothing changed.", "Simulacro: no se cambi\xF3 nada."))}`);
|
|
|
8661
8685
|
}
|
|
8662
8686
|
return out(r2, () => r2.unlinked ? `${lavender("\u2713")} ${t("Unlinked: the prompt stays as it is and is edited by hand again.", "Desenlazado: el prompt se queda como est\xE1 y se vuelve a editar a mano.")}` : `${lavender("\u2713")} Prompt = ${v["prompt-base"]} + ${v["prompt-overlay"]} ${t(`(${r2.chars} chars). It regenerates when the base changes; ghosty agents doctor ${id} tells if the overlay stops applying.`, `(${r2.chars} caracteres). Se regenera cuando cambia la base; ghosty agents doctor ${id} avisa si la capa deja de aplicar.`)}`);
|
|
8663
8687
|
}
|
|
8688
|
+
if (v.engine && v["dry-run"]) {
|
|
8689
|
+
const p = await api(agentPath(id), { method: "PATCH", body: { engine: v.engine, ...v.model ? { model: v.model } : {}, dryRun: true } });
|
|
8690
|
+
return out(p, () => [
|
|
8691
|
+
`${p.from.engine}/${p.from.model ?? "?"} \u2192 ${p.to ? `${p.to.engine}/${p.to.model ?? "?"}` : v.engine}`,
|
|
8692
|
+
...(p.steps ?? []).map((x) => ` \xB7 ${serverText(x)}`),
|
|
8693
|
+
p.ok ? dim(t("Dry run: nothing changed. Run it again without --dry-run to apply.", "Simulacro: no se cambi\xF3 nada. Repite sin --dry-run para aplicarlo.")) : `\u2717 ${serverText(p.error ?? "")}`
|
|
8694
|
+
].join("\n"));
|
|
8695
|
+
}
|
|
8664
8696
|
const body = {};
|
|
8665
8697
|
if (v.name) body.name = v.name;
|
|
8666
8698
|
if (v.engine) body.engine = v.engine;
|
|
8667
8699
|
if (v.model) body.model = v.model;
|
|
8668
8700
|
if (v.prompt) body.prompt = v.prompt;
|
|
8669
8701
|
if (v["prompt-file"]) body.prompt = await readFile3(v["prompt-file"], "utf8");
|
|
8702
|
+
if (v["prompt-mode"]) {
|
|
8703
|
+
if (v["prompt-mode"] !== "append" && v["prompt-mode"] !== "replace") throw usage(t("--prompt-mode takes append or replace.", "--prompt-mode acepta append o replace."));
|
|
8704
|
+
body.promptMode = v["prompt-mode"];
|
|
8705
|
+
}
|
|
8670
8706
|
const channels = parseChannels(v.channels);
|
|
8671
8707
|
if (channels) body.channels = channels;
|
|
8672
8708
|
if (v.toolsets) {
|
|
@@ -8778,11 +8814,12 @@ async function tryTurn(id, text, v) {
|
|
|
8778
8814
|
id = need(id, "agent id", 'ghosty try <agent-id> "hello"');
|
|
8779
8815
|
if (v.replay) return tryReplay(id, v);
|
|
8780
8816
|
if (v.whatsapp && !v.waba) info(dim(t("Note: --whatsapp is now --waba (the real WhatsApp path).", "Nota: --whatsapp ahora es --waba (el camino real de WhatsApp).")));
|
|
8781
|
-
if (v.waba || v.whatsapp || v.
|
|
8782
|
-
|
|
8817
|
+
if (v.waba || v.whatsapp || v.history) return tryWaba(id, text, v);
|
|
8818
|
+
const files = v.media?.length ? await localFiles(v.media) : [];
|
|
8819
|
+
if (!text && !v.reset && !files.length) throw usage(t("Missing message.", "Falta el mensaje."), t("Example", "Ejemplo") + ': ghosty try <agent-id> "hello"');
|
|
8783
8820
|
if (process.stderr.isTTY && text && !globals.json) info(dim(t("Thinking\u2026 (a test turn can take up to 3 minutes)", "Pensando\u2026 (un turno de prueba puede tardar hasta 3 minutos)")));
|
|
8784
8821
|
const r = await api(agentPath(id, "/try"), {
|
|
8785
|
-
body: { text, session: v.session, reset: v.reset }
|
|
8822
|
+
body: { text, session: v.session, reset: v.reset, ...files.length ? { files } : {} }
|
|
8786
8823
|
});
|
|
8787
8824
|
out(r, () => (r.text ?? "") + (r.error ? `
|
|
8788
8825
|
${dim(`(${t("warning", "aviso")}: ${serverText(r.error)})`)}` : ""));
|
|
@@ -9002,7 +9039,7 @@ async function agentsDemo(id, v) {
|
|
|
9002
9039
|
async function agentsBox(id, v) {
|
|
9003
9040
|
id = need(id, "agent id", "ghosty agents box <agent-id> [--check PATH] [--contains PATH=TEXT] [--recycle]");
|
|
9004
9041
|
if (v.recycle) {
|
|
9005
|
-
const r2 = await api(agentPath(id, "/box"), { method: "POST", body: { recycle: true } });
|
|
9042
|
+
const r2 = await api(agentPath(id, "/box"), { method: "POST", body: { recycle: true, ...v.force ? { force: true } : {}, ...v.reenv ? { reenv: true } : {} } });
|
|
9006
9043
|
out(r2, () => `${lavender("\u2713")} ${t("Recycled.", "Reciclada.")} ${dim(serverText(r2?.note ?? ""))}
|
|
9007
9044
|
${dim(`${t("Verify after one turn", "Verifica despu\xE9s de un turno")}: ghosty agents box ${id} --check <path>`)}`);
|
|
9008
9045
|
return;
|
|
@@ -9085,7 +9122,7 @@ async function conversationsShow(agent, sid, v) {
|
|
|
9085
9122
|
return [...head, ...lines, ...tailNote].join("\n\n") || dim(t("(empty)", "(vac\xEDa)"));
|
|
9086
9123
|
});
|
|
9087
9124
|
}
|
|
9088
|
-
async function turn(agent, sid, text, first) {
|
|
9125
|
+
async function turn(agent, sid, text, first, images = []) {
|
|
9089
9126
|
const turnId = randomUUID();
|
|
9090
9127
|
const ctrl = new AbortController();
|
|
9091
9128
|
const res = await request(convPath(agent, `/${enc3(sid)}/events`), { signal: ctrl.signal, headers: { accept: "text/event-stream" } });
|
|
@@ -9098,7 +9135,7 @@ async function turn(agent, sid, text, first) {
|
|
|
9098
9135
|
});
|
|
9099
9136
|
};
|
|
9100
9137
|
process.on("SIGINT", onSigint);
|
|
9101
|
-
const sent = api(convPath(agent, `/${enc3(sid)}/messages`), { body: { content: text, turnId, primerTurno: first } });
|
|
9138
|
+
const sent = api(convPath(agent, `/${enc3(sid)}/messages`), { body: { content: text, turnId, primerTurno: first, ...images.length ? { images } : {} } });
|
|
9102
9139
|
let answer = "";
|
|
9103
9140
|
let usageData;
|
|
9104
9141
|
let ours = false;
|
|
@@ -9140,8 +9177,9 @@ async function chat(agent, message, v) {
|
|
|
9140
9177
|
first = true;
|
|
9141
9178
|
if (!globals.json) info(dim(t(`conversation ${sid} (continue it with --conversation ${sid})`, `conversaci\xF3n ${sid} (s\xEDguela con --conversation ${sid})`)));
|
|
9142
9179
|
}
|
|
9143
|
-
|
|
9144
|
-
|
|
9180
|
+
const images = v.media?.length ? await localFiles(v.media) : [];
|
|
9181
|
+
if (message || images.length) {
|
|
9182
|
+
await turn(agent, sid, message ?? "", first, images);
|
|
9145
9183
|
return;
|
|
9146
9184
|
}
|
|
9147
9185
|
if (!process.stdin.isTTY) {
|
|
@@ -9340,7 +9378,7 @@ ${dim(`${t("conversation", "conversaci\xF3n")} ${p.sessionId}`)}`
|
|
|
9340
9378
|
|
|
9341
9379
|
// src/commands/partner.ts
|
|
9342
9380
|
import { createHmac } from "node:crypto";
|
|
9343
|
-
import { readFile as readFile5 } from "node:fs/promises";
|
|
9381
|
+
import { readFile as readFile5, writeFile as writeFile4 } from "node:fs/promises";
|
|
9344
9382
|
var partnerHelp = () => t(
|
|
9345
9383
|
`${bold("Usage:")} ghosty partner <command> [options]
|
|
9346
9384
|
|
|
@@ -9348,14 +9386,14 @@ ${bold("Partner space")} ${dim("(you, signed in as the space owner)")}
|
|
|
9348
9386
|
partner ls Your partner spaces
|
|
9349
9387
|
partner enable <workspace> Turn one of your spaces into a partner space
|
|
9350
9388
|
partner credentials ls Server credentials (no secrets)
|
|
9351
|
-
partner credentials create [--label L] Issue one \u2014 the secret is shown ONCE
|
|
9389
|
+
partner credentials create [--label L] Issue one \u2014 the secret is shown ONCE [--env-file PATH | --format env]
|
|
9352
9390
|
partner credentials rotate <keyId> [--grace MIN] New secret, same keyId (old one valid MIN, default 60)
|
|
9353
9391
|
partner credentials revoke <keyId> Revoke it (idempotent)
|
|
9354
9392
|
partner audit Who did what, when
|
|
9355
9393
|
|
|
9356
9394
|
${bold("Shared agent (solo-MCP)")}
|
|
9357
9395
|
partner agent get The agent template and its version
|
|
9358
|
-
partner agent apply --file agent.json Create or update it (idempotent)
|
|
9396
|
+
partner agent apply --file agent.json Create or update it (idempotent) [--prompt-file prompt.md]
|
|
9359
9397
|
|
|
9360
9398
|
${bold("Tenants")} ${dim("(your customers, by externalId \u2014 config only, no box)")}
|
|
9361
9399
|
partner tenants ls
|
|
@@ -9364,14 +9402,15 @@ ${bold("Tenants")} ${dim("(your customers, by externalId \u2014 config only, no
|
|
|
9364
9402
|
partner tenants rm <externalId> Deletes its config AND its memory
|
|
9365
9403
|
|
|
9366
9404
|
${bold("Try it")}
|
|
9367
|
-
partner try <externalId> "message" One turn [--group END_USER_ID] [--turn-token SHORT_LIVED_TOKEN]
|
|
9405
|
+
partner try <externalId> "message" One turn [--group END_USER_ID] [--turn-token SHORT_LIVED_TOKEN] [--verbose: tool args and ms]
|
|
9368
9406
|
(the turn token goes to YOUR MCP as \${turn.token}; --token also works here)
|
|
9369
9407
|
|
|
9370
9408
|
${bold("Options")}
|
|
9371
9409
|
--workspace SLUG Which partner space (only if you own more than one)
|
|
9372
9410
|
--json Machine-readable output
|
|
9373
9411
|
|
|
9374
|
-
${bold("Server credential mode")} GHOSTY_PARTNER_KEY=gpk_\u2026
|
|
9412
|
+
${bold("Server credential mode")} GHOSTY_PARTNER_KEY=gpk_\u2026 GHOSTY_PARTNER_KEY_SECRET=gps_\u2026 ghosty partner agent get
|
|
9413
|
+
${dim("(GHOSTY_PARTNER_SECRET also works; it is NOT the gs server's global secret)")}
|
|
9375
9414
|
${dim("Docs: https://www.ghosty.studio/docs/cli/partners")}`,
|
|
9376
9415
|
`${bold("Uso:")} ghosty partner <comando> [opciones]
|
|
9377
9416
|
|
|
@@ -9379,14 +9418,14 @@ ${bold("Espacio de partner")} ${dim("(t\xFA, con sesi\xF3n como due\xF1a del es
|
|
|
9379
9418
|
partner ls Tus espacios de partner
|
|
9380
9419
|
partner enable <workspace> Convierte uno de tus espacios en espacio de partner
|
|
9381
9420
|
partner credentials ls Credenciales de servidor (sin secretos)
|
|
9382
|
-
partner credentials create [--label L] Emite una \u2014 el secreto se muestra UNA vez
|
|
9421
|
+
partner credentials create [--label L] Emite una \u2014 el secreto se muestra UNA vez [--env-file RUTA | --format env]
|
|
9383
9422
|
partner credentials rotate <keyId> [--grace MIN] Secreto nuevo, mismo keyId (el viejo vale MIN, default 60)
|
|
9384
9423
|
partner credentials revoke <keyId> La revoca (idempotente)
|
|
9385
9424
|
partner audit Qui\xE9n hizo qu\xE9 y cu\xE1ndo
|
|
9386
9425
|
|
|
9387
9426
|
${bold("Agente compartido (solo-MCP)")}
|
|
9388
9427
|
partner agent get La plantilla del agente y su versi\xF3n
|
|
9389
|
-
partner agent apply --file agent.json Lo crea o lo actualiza (idempotente)
|
|
9428
|
+
partner agent apply --file agent.json Lo crea o lo actualiza (idempotente) [--prompt-file prompt.md]
|
|
9390
9429
|
|
|
9391
9430
|
${bold("Tenants")} ${dim("(tus clientes, por externalId \u2014 s\xF3lo config, sin caja)")}
|
|
9392
9431
|
partner tenants ls
|
|
@@ -9395,20 +9434,21 @@ ${bold("Tenants")} ${dim("(tus clientes, por externalId \u2014 s\xF3lo config,
|
|
|
9395
9434
|
partner tenants rm <externalId> Borra su config Y su memoria
|
|
9396
9435
|
|
|
9397
9436
|
${bold("Pru\xE9balo")}
|
|
9398
|
-
partner try <externalId> "mensaje" Un turno [--group END_USER_ID] [--turn-token TOKEN_DE_VIDA_CORTA]
|
|
9437
|
+
partner try <externalId> "mensaje" Un turno [--group END_USER_ID] [--turn-token TOKEN_DE_VIDA_CORTA] [--verbose: args y ms de cada tool]
|
|
9399
9438
|
(el token del turno llega a TU MCP como \${turn.token}; --token tambi\xE9n sirve aqu\xED)
|
|
9400
9439
|
|
|
9401
9440
|
${bold("Opciones")}
|
|
9402
9441
|
--workspace SLUG Qu\xE9 espacio de partner (s\xF3lo si tienes m\xE1s de uno)
|
|
9403
9442
|
--json Salida para m\xE1quinas
|
|
9404
9443
|
|
|
9405
|
-
${bold("Modo credencial de servidor")} GHOSTY_PARTNER_KEY=gpk_\u2026
|
|
9444
|
+
${bold("Modo credencial de servidor")} GHOSTY_PARTNER_KEY=gpk_\u2026 GHOSTY_PARTNER_KEY_SECRET=gps_\u2026 ghosty partner agent get
|
|
9445
|
+
${dim("(GHOSTY_PARTNER_SECRET tambi\xE9n vale; NO es el secreto global del servidor de gs)")}
|
|
9406
9446
|
${dim("Docs: https://www.ghosty.studio/docs/cli/partners")}`
|
|
9407
9447
|
);
|
|
9408
9448
|
var enc5 = encodeURIComponent;
|
|
9409
9449
|
function partnerCreds() {
|
|
9410
9450
|
const key = process.env.GHOSTY_PARTNER_KEY;
|
|
9411
|
-
const secret = process.env.GHOSTY_PARTNER_SECRET;
|
|
9451
|
+
const secret = process.env.GHOSTY_PARTNER_KEY_SECRET || process.env.GHOSTY_PARTNER_SECRET;
|
|
9412
9452
|
return key && secret ? { key, secret } : null;
|
|
9413
9453
|
}
|
|
9414
9454
|
async function call(path, v, init = {}) {
|
|
@@ -9425,7 +9465,7 @@ async function call(path, v, init = {}) {
|
|
|
9425
9465
|
"x-ghosty-ts": ts,
|
|
9426
9466
|
"x-ghosty-sig": sig,
|
|
9427
9467
|
"content-type": "application/json",
|
|
9428
|
-
"user-agent": `ghosty-cli/${"0.
|
|
9468
|
+
"user-agent": `ghosty-cli/${"0.29.0"}`
|
|
9429
9469
|
},
|
|
9430
9470
|
body: method === "GET" ? void 0 : raw
|
|
9431
9471
|
});
|
|
@@ -9444,13 +9484,23 @@ async function json(path, v, init = {}) {
|
|
|
9444
9484
|
return text ? JSON.parse(text) : void 0;
|
|
9445
9485
|
}
|
|
9446
9486
|
var fmt = (d) => d ? d.replace("T", " ").slice(0, 16) : "\u2014";
|
|
9447
|
-
|
|
9487
|
+
var envLines = (r) => `GHOSTY_PARTNER_KEY=${r.keyId}
|
|
9488
|
+
GHOSTY_PARTNER_KEY_SECRET=${r.secret}
|
|
9489
|
+
`;
|
|
9490
|
+
async function showSecret(r, v) {
|
|
9491
|
+
if (v["env-file"]) {
|
|
9492
|
+
await writeFile4(v["env-file"], envLines(r), { mode: 384 });
|
|
9493
|
+
return out({ keyId: r.keyId, envFile: v["env-file"] }, () => `${lavender("\u2713")} ${r.keyId} ${t(`\u2192 ${v["env-file"]} (mode 600; the secret was not printed)`, `\u2192 ${v["env-file"]} (modo 600; el secreto no se imprimi\xF3)`)}`);
|
|
9494
|
+
}
|
|
9495
|
+
if (v.format === "env") return void process.stdout.write(envLines(r));
|
|
9496
|
+
if (v.format) throw usage(t("--format takes env.", "--format acepta env."));
|
|
9448
9497
|
out(
|
|
9449
9498
|
r,
|
|
9450
9499
|
() => [
|
|
9451
9500
|
`${lavender("\u2713")} ${t("Save these now \u2014 the secret is not shown again:", "Gu\xE1rdalas ya \u2014 el secreto no se vuelve a mostrar:")}`,
|
|
9452
9501
|
` GHOSTY_PARTNER_KEY=${r.keyId}`,
|
|
9453
|
-
`
|
|
9502
|
+
` GHOSTY_PARTNER_KEY_SECRET=${r.secret}`,
|
|
9503
|
+
dim(t(" Next time: --env-file PATH (0600) or --format env | fly secrets import", " La pr\xF3xima: --env-file RUTA (0600) o --format env | fly secrets import"))
|
|
9454
9504
|
].join("\n")
|
|
9455
9505
|
);
|
|
9456
9506
|
}
|
|
@@ -9460,20 +9510,20 @@ async function credentialsCmd(sub, id, v) {
|
|
|
9460
9510
|
const r = await json("/api/v2/partner/credentials", v, owner);
|
|
9461
9511
|
return out(
|
|
9462
9512
|
r,
|
|
9463
|
-
() => table(
|
|
9513
|
+
() => r.credentials.length ? table(
|
|
9464
9514
|
r.credentials.map((c) => ({ keyId: c.keyId, label: c.label, status: c.status, lastUsed: fmt(c.lastUsedAt), created: fmt(c.createdAt) })),
|
|
9465
9515
|
["keyId", "label", "status", "lastUsed", "created"]
|
|
9466
|
-
)
|
|
9516
|
+
) : dim(t("No credentials yet. Run: ghosty partner credentials create --label prod --env-file .env.partner", "Todav\xEDa no hay credenciales. Corre: ghosty partner credentials create --label prod --env-file .env.partner"))
|
|
9467
9517
|
);
|
|
9468
9518
|
}
|
|
9469
9519
|
if (sub === "create" || sub === "new") {
|
|
9470
|
-
return showSecret(await json("/api/v2/partner/credentials", v, { ...owner, method: "POST", body: { label: v.label } }));
|
|
9520
|
+
return showSecret(await json("/api/v2/partner/credentials", v, { ...owner, method: "POST", body: { label: v.label } }), v);
|
|
9471
9521
|
}
|
|
9472
9522
|
if (!id) throw usage(t("Missing keyId.", "Falta el keyId."), `${t("Example", "Ejemplo")}: ghosty partner credentials ${sub} gpk_\u2026`);
|
|
9473
9523
|
if (sub === "rotate") {
|
|
9474
9524
|
const grace = v.grace !== void 0 ? Number(v.grace) : void 0;
|
|
9475
9525
|
if (grace !== void 0 && (!Number.isFinite(grace) || grace < 0)) throw usage(t("--grace must be minutes \u2265 0.", "--grace deben ser minutos \u2265 0."));
|
|
9476
|
-
return showSecret(await json(`/api/v2/partner/credentials/${enc5(id)}/rotate`, v, { ...owner, method: "POST", body: { graceMinutes: grace } }));
|
|
9526
|
+
return showSecret(await json(`/api/v2/partner/credentials/${enc5(id)}/rotate`, v, { ...owner, method: "POST", body: { graceMinutes: grace } }), v);
|
|
9477
9527
|
}
|
|
9478
9528
|
if (sub === "revoke" || sub === "rm") {
|
|
9479
9529
|
const r = await json(`/api/v2/partner/credentials/${enc5(id)}`, v, { ...owner, method: "DELETE" });
|
|
@@ -9496,6 +9546,10 @@ async function agentCmd(sub, v) {
|
|
|
9496
9546
|
if (sub === "apply" || sub === "set") {
|
|
9497
9547
|
if (!v.file) throw usage(t("Missing --file.", "Falta --file."), t("Example", "Ejemplo") + ": ghosty partner agent apply --file agent.json");
|
|
9498
9548
|
const body = JSON.parse(await readFile5(v.file, "utf8"));
|
|
9549
|
+
if (v["prompt-file"]) {
|
|
9550
|
+
body.prompt = await readFile5(v["prompt-file"], "utf8");
|
|
9551
|
+
if (body.prompt.length > 6e4) throw usage(t(`The prompt has ${body.prompt.length} chars; the limit is 60,000.`, `El prompt tiene ${body.prompt.length} caracteres; el tope es 60,000.`));
|
|
9552
|
+
}
|
|
9499
9553
|
const r = await json("/api/v2/partner/agent", v, { method: "PUT", body });
|
|
9500
9554
|
return out(r, () => `${lavender("\u2713")} ${r.created ? t("Created", "Creado") : r.changed ? t("Updated", "Actualizado") : t("No changes", "Sin cambios")} \xB7 v${r.agent.version}`);
|
|
9501
9555
|
}
|
|
@@ -9504,7 +9558,7 @@ async function agentCmd(sub, v) {
|
|
|
9504
9558
|
async function tenantsCmd(sub, id, v) {
|
|
9505
9559
|
if (!sub || sub === "ls" || sub === "list") {
|
|
9506
9560
|
const r = await json("/api/v2/partner/tenants", v);
|
|
9507
|
-
return out(r, () => table(r.tenants.map((tn) => ({ externalId: tn.externalId, config: JSON.stringify(tn.config ?? {}), updated: fmt(tn.updatedAt) })), ["externalId", "config", "updated"]));
|
|
9561
|
+
return out(r, () => r.tenants.length ? table(r.tenants.map((tn) => ({ externalId: tn.externalId, config: JSON.stringify(tn.config ?? {}), updated: fmt(tn.updatedAt) })), ["externalId", "config", "updated"]) : dim(t("No tenants yet: each one appears on its first turn, or create it with ghosty partner tenants set <externalId> --config '{\u2026}'", "Todav\xEDa no hay tenants: cada uno aparece en su primer turno, o cr\xE9alo con ghosty partner tenants set <externalId> --config '{\u2026}'")));
|
|
9508
9562
|
}
|
|
9509
9563
|
if (!id) throw usage(t("Missing externalId.", "Falta el externalId."), `${t("Example", "Ejemplo")}: ghosty partner tenants ${sub} org_123`);
|
|
9510
9564
|
const path = `/api/v2/partner/tenants/${enc5(id)}`;
|
|
@@ -9551,7 +9605,8 @@ async function tryCmd(externalId, text, v) {
|
|
|
9551
9605
|
if (ev.type === "chunk" && ev.value) {
|
|
9552
9606
|
full += ev.value;
|
|
9553
9607
|
process.stdout.write(ev.value);
|
|
9554
|
-
} else if (ev.type === "tool" && ev.phase === "start") info(dim(` \u21B3 ${ev.name}`));
|
|
9608
|
+
} else if (ev.type === "tool" && ev.phase === "start") info(dim(` \u21B3 ${ev.name}${v.verbose && ev.args !== void 0 ? ` ${JSON.stringify(ev.args)}` : ""}`));
|
|
9609
|
+
else if (ev.type === "tool" && ev.phase === "end" && v.verbose) info(dim(` ${ev.ok === false ? "\u2717" : "\u2713"} ${ev.name}${ev.ms != null ? ` ${ev.ms} ms` : ""}`));
|
|
9555
9610
|
else if (ev.type === "error") info(`Error: ${serverText(ev.message ?? "")}`);
|
|
9556
9611
|
}
|
|
9557
9612
|
}
|
|
@@ -9565,7 +9620,7 @@ async function partnerCmd(sub, a1, a2, v) {
|
|
|
9565
9620
|
case "ls":
|
|
9566
9621
|
case "list": {
|
|
9567
9622
|
const r = await json("/api/v2/partner", v, { ownerOnly: true });
|
|
9568
|
-
return out(r, () => table(r.partners.map((p) => ({ workspace: p.workspace, credentials: p.credentials, agent: p.agents ? "yes" : "no", status: p.disabledAt ? "off" : "on" })), ["workspace", "credentials", "agent", "status"]));
|
|
9623
|
+
return out(r, () => r.partners.length ? table(r.partners.map((p) => ({ workspace: p.workspace, credentials: p.credentials, agent: p.agents ? "yes" : "no", status: p.disabledAt ? "off" : "on" })), ["workspace", "credentials", "agent", "status"]) : dim(t("No partner spaces. Run: ghosty partner enable <workspace>", "No tienes espacios de partner. Corre: ghosty partner enable <espacio>")));
|
|
9569
9624
|
}
|
|
9570
9625
|
case "enable": {
|
|
9571
9626
|
if (!a1) throw usage(t("Missing workspace.", "Falta el workspace."), t("Example", "Ejemplo") + ": ghosty partner enable denik");
|
|
@@ -9917,6 +9972,11 @@ ${dim(t("Used by", "Lo usan"))}: ${used}` : ""}`);
|
|
|
9917
9972
|
throw usage(t(`Unknown agents docs subcommand: ${sub}.`, `Subcomando de agents docs desconocido: ${sub}.`), t("Try", "Prueba") + ": ghosty agents docs ls | add | grant | replace | history | restore | revoke");
|
|
9918
9973
|
}
|
|
9919
9974
|
}
|
|
9975
|
+
function resumedCell(sdk) {
|
|
9976
|
+
if (!sdk) return "";
|
|
9977
|
+
if (sdk.resumed) return "\u2713";
|
|
9978
|
+
return sdk.freshReason === "new" ? t("new", "nueva") : `\u26A0 ${t("from scratch", "desde cero")} (${sdk.freshReason ?? "?"})`;
|
|
9979
|
+
}
|
|
9920
9980
|
function skipReason(r) {
|
|
9921
9981
|
const es = {
|
|
9922
9982
|
manual_mode: ["operator active in Formmy (manual mode)", "operador activo en Formmy (modo manual)"],
|
|
@@ -9938,15 +9998,29 @@ async function turnsSkipped(id, v) {
|
|
|
9938
9998
|
out(r, () => rows.length ? `${table(rows, ["at", "reason", "phone", "message"])}
|
|
9939
9999
|
${dim(tally)}` : dim(t(`Every customer message${v.since ? ` since ${v.since}` : ""} opened a turn (or none arrived: check ghosty channels whatsapp ls ${id}, "formmy forwards").`, `Todo mensaje de cliente${v.since ? ` desde ${v.since}` : ""} abri\xF3 turno (o no lleg\xF3 ninguno: revisa ghosty channels whatsapp ls ${id}, \xABformmy forwards\xBB).`)));
|
|
9940
10000
|
}
|
|
10001
|
+
async function turnsCancel(id, sid) {
|
|
10002
|
+
const ex = "ghosty turns cancel <agent-id> <conversation-id>";
|
|
10003
|
+
id = need2(id, t("agent id", "el id del agente"), ex);
|
|
10004
|
+
sid = need2(sid, t("conversation id", "el id de la conversaci\xF3n"), ex);
|
|
10005
|
+
const r = await api(`/api/v2/me/agents/${enc6(id)}/conversations/${enc6(sid)}/cancel`, { method: "POST", body: {} });
|
|
10006
|
+
const b = r.background ?? { videos: 0, subagents: 0, wakes: 0 };
|
|
10007
|
+
const bg = b.videos + b.subagents + b.wakes;
|
|
10008
|
+
out(
|
|
10009
|
+
r,
|
|
10010
|
+
() => !r.detenido && !bg ? dim(t("Nothing running in that conversation.", "No hab\xEDa nada corriendo en esa conversaci\xF3n.")) : `${lavender("\u2713")} ${[r.detenido ? t("turn stopped", "turno detenido") : "", b.videos ? t(`${b.videos} video job(s)`, `${b.videos} encargo(s) de video`) : "", b.subagents ? t(`${b.subagents} subagent(s)`, `${b.subagents} subagente(s)`) : "", b.wakes ? t(`${b.wakes} pending wake-up(s)`, `${b.wakes} despertador(es) pendiente(s)`) : ""].filter(Boolean).join(" \xB7 ")}${bg ? dim(t(" \u2014 work already running in its box finishes on its own, but nothing is delivered and the agent isn't woken.", " \u2014 lo que ya corre en su caja termina solo, pero no se entrega ni despierta al agente.")) : ""}`
|
|
10011
|
+
);
|
|
10012
|
+
}
|
|
9941
10013
|
async function turnsCmd(sub, id, turnId, v) {
|
|
9942
10014
|
if (sub === "show") return turnShow(id, turnId, !!v.full);
|
|
9943
|
-
if (sub
|
|
10015
|
+
if (sub === "cancel" || sub === "stop") return turnsCancel(id, turnId);
|
|
10016
|
+
if (sub && sub !== "ls" && sub !== "list") throw usage(t(`Unknown turns subcommand: ${sub}.`, `Subcomando de turns desconocido: ${sub}.`), t("Try", "Prueba") + ": ghosty turns ls <agent-id> --errors --since 1h \xB7 ghosty turns show <agent-id> [turn-id|last] \xB7 ghosty turns cancel <agent-id> <conversation>");
|
|
9944
10017
|
id = need2(id, t("agent id", "el id del agente"), "ghosty turns ls <agent-id> --errors --since 1h");
|
|
9945
10018
|
if (v.skipped) return turnsSkipped(id, v);
|
|
9946
10019
|
const q = new URLSearchParams();
|
|
9947
10020
|
const status = v.errors ? "error" : v.status;
|
|
9948
10021
|
if (status) q.set("state", status);
|
|
9949
10022
|
if (v.since) q.set("since", v.since);
|
|
10023
|
+
if (v.session) q.set("session", v.session);
|
|
9950
10024
|
q.set("limit", v.limit ?? "20");
|
|
9951
10025
|
const r = await api(agentPath2(id, `/turns?${q}`));
|
|
9952
10026
|
const when = (s) => s.slice(0, 16).replace("T", " ");
|
|
@@ -9957,10 +10031,11 @@ async function turnsCmd(sub, id, turnId, v) {
|
|
|
9957
10031
|
secs: tu.ms == null ? "" : Math.round(tu.ms / 1e3),
|
|
9958
10032
|
from: tu.surface ?? "",
|
|
9959
10033
|
session: tu.sessionId.slice(0, 24),
|
|
10034
|
+
resumed: resumedCell(tu.sdk),
|
|
9960
10035
|
error: serverText(tu.error ?? "").replace(/\s+/g, " ").slice(0, 90)
|
|
9961
10036
|
}));
|
|
9962
10037
|
const tally = Object.entries(r.counts).map(([k, n]) => `${k} ${n}`).join(" \xB7 ");
|
|
9963
|
-
out(r, () => rows.length ? `${table(rows, ["id", "started", "state", "secs", "from", "session", "error"])}
|
|
10038
|
+
out(r, () => rows.length ? `${table(rows, ["id", "started", "state", "secs", "from", "session", ...rows.some((r2) => r2.resumed) ? ["resumed"] : [], "error"])}
|
|
9964
10039
|
${dim(`${v.since ? t(`since ${v.since}: `, `desde ${v.since}: `) : ""}${tally} \xB7 ${t("where the time went", "en qu\xE9 se fue el tiempo")}: ghosty turns show ${id} <turn-id>`)}` : dim(t(`No ${status ?? ""} turns${v.since ? ` since ${v.since}` : ""}.`, `Sin turnos${status ? ` ${status}` : ""}${v.since ? ` desde ${v.since}` : ""}.`)));
|
|
9965
10040
|
}
|
|
9966
10041
|
async function turnShow(id, turnId, full = false) {
|
|
@@ -9973,7 +10048,8 @@ async function turnShow(id, turnId, full = false) {
|
|
|
9973
10048
|
`${lavender("in ")} ${r.input ? clip(r.input) : dim(t("(no copy of the message)", "(sin copia del mensaje)"))}`,
|
|
9974
10049
|
`${lavender("out")} ${r.output ? clip(r.output) : dim(t("(no answer saved)", "(sin respuesta guardada)"))}`
|
|
9975
10050
|
];
|
|
9976
|
-
const
|
|
10051
|
+
const sdk = tu.sdk ? ` ${resumedCell(tu.sdk).startsWith("\u26A0") ? red(resumedCell(tu.sdk)) : dim(`sdk ${resumedCell(tu.sdk)}`)}${tu.sdk.cliVersion ? dim(` \xB7 CLI ${tu.sdk.cliVersion}`) : ""}` : "";
|
|
10052
|
+
const state = `${bold(tu.id)} ${tu.state === "error" ? red(tu.state) : tu.state} ${tu.secs ?? "?"} s${tu.session ? dim(` ${tu.session}`) : ""}${sdk}`;
|
|
9977
10053
|
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");
|
|
9978
10054
|
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`))}`;
|
|
9979
10055
|
const line2 = (s) => {
|
|
@@ -10568,6 +10644,7 @@ var HELPS_ES = {
|
|
|
10568
10644
|
["--no-notify", "create --like: lo comparte con las mismas personas SIN mandarles correo"],
|
|
10569
10645
|
["--workspace SLUG", "create: nace en ese espacio (del que eres due\xF1o o admin), a nombre de su due\xF1o y activo en su Teams"],
|
|
10570
10646
|
["--channels k=on|off,\u2026", "create/set: prende/apaga canales: teams, web, whatsapp, messenger, wa (WhatsApp personal)"],
|
|
10647
|
+
["--engine E --dry-run", "set: qu\xE9 hace cambiar de motor (sesiones que se olvidan, caja que se levanta y cu\xE1l se destruye, si est\xE1 trabajando) sin cambiarlo; cada hilo conserva su conversaci\xF3n"],
|
|
10571
10648
|
["--toolsets LIST", "set: grupos de tools de gs que recibe (crm, sales\u2026): una lista reemplaza; +x / -x agrega o quita"],
|
|
10572
10649
|
["--prompt-base ID", "set: el prompt de este agente = el prompt de ese agente + --prompt-overlay; se regenera cuando cambia la base. none = desliga (conserva el prompt de hoy)"],
|
|
10573
10650
|
["--prompt-overlay FILE", 'set: JSON {"edits":[{"what","find","replace","regex"?,"all"?}]}, aplicados en orden; cada uno debe encontrar su lugar o no cambia nada'],
|
|
@@ -10593,12 +10670,16 @@ var HELPS_ES = {
|
|
|
10593
10670
|
["--rotate", "demo: c\xF3digo nuevo; la liga vieja deja de servir"],
|
|
10594
10671
|
["--check PATH", "box: este archivo debe existir en la m\xE1quina (repetible), p. ej. un archivo que trae la imagen nueva"],
|
|
10595
10672
|
["--contains PATH=TEXT", "box: ese archivo debe contener TEXT (un cambio que no agrega archivos)"],
|
|
10596
|
-
["--recycle", "box: reemplaza sus m\xE1quinas (
|
|
10673
|
+
["--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"],
|
|
10674
|
+
["--reenv / --force", "box --recycle en ACP: --reenv s\xF3lo re-hornea el env en la misma caja; --force la recrea aunque est\xE9 ocupada (due\xF1o o staff)"],
|
|
10597
10675
|
["--unset-env K1,K2", "set: borra esas variables del env (--env K= tambi\xE9n borra K)"],
|
|
10598
10676
|
["--tool [ch:]tool=on|off", "set: prende/apaga una tool en un canal (wa:actualizar_identidad=off) o en todos (crm_buscar=off); separadas por comas"],
|
|
10599
10677
|
["--bag NAME|none", "set: de qu\xE9 bolsa de tokens de su espacio gasta; none = el saldo del espacio (s\xF3lo staff)"],
|
|
10600
10678
|
["--avatar URL|FILE", "set: su cara en Teams: una liga https o un JPEG/PNG/WebP local (se guarda en img.ghosty.studio)"],
|
|
10601
|
-
["--from-workspace", "rm: lo desactiva en cada Teams donde est\xE9 y luego lo borra (due\xF1o o staff)"]
|
|
10679
|
+
["--from-workspace", "rm: lo desactiva en cada Teams donde est\xE9 y luego lo borra (due\xF1o o staff)"],
|
|
10680
|
+
["--prompt-mode M", "set: append (default: su prompt + la persona de Ghosty) o replace (s\xF3lo su prompt)"],
|
|
10681
|
+
["--sponsor/--owner EMAIL", "ls: agentes de CUALQUIER cuenta que paga / de quien es ese correo (staff de Ghosty); luego se operan por id"],
|
|
10682
|
+
["--ids", "ls: s\xF3lo los ids, uno por rengl\xF3n (para xargs)"]
|
|
10602
10683
|
],
|
|
10603
10684
|
notes: [
|
|
10604
10685
|
"doctor sale con 1 cuando encuentra un problema, as\xED puede frenar un script o un CI.",
|
|
@@ -10652,7 +10733,7 @@ var HELPS_ES = {
|
|
|
10652
10733
|
},
|
|
10653
10734
|
turns: {
|
|
10654
10735
|
summary: "Los turnos recientes de un agente: qu\xE9 contest\xF3, qu\xE9 fall\xF3 y por qu\xE9, y en qu\xE9 se fue el tiempo de un turno.",
|
|
10655
|
-
replace: [["# last turn: each tool, its seconds, output tokens", "# \xFAltimo turno: cada tool, sus segundos, tokens de salida"]],
|
|
10736
|
+
replace: [["# last turn: each tool, its seconds, output tokens", "# \xFAltimo turno: cada tool, sus segundos, tokens de salida"], ["# stop the turn AND its background jobs", "# detiene el turno Y sus encargos en fondo"]],
|
|
10656
10737
|
flags: [
|
|
10657
10738
|
["--errors", "s\xF3lo turnos fallidos (igual que --status error)"],
|
|
10658
10739
|
["--skipped", "ls: mensajes de clientes de WhatsApp Business que NO abrieron turno, y por qu\xE9 (operador activo, s\xF3lo registra, pausa, reacci\xF3n, canal apagado, sin saldo)"],
|
|
@@ -10660,7 +10741,8 @@ var HELPS_ES = {
|
|
|
10660
10741
|
["--since T", "30m, 1h, 2d o una fecha ISO"],
|
|
10661
10742
|
["--limit N", "m\xE1ximo de filas (default 20, m\xE1x. 500)"],
|
|
10662
10743
|
["--full", "show: el mensaje que entr\xF3 y la respuesta completos (default: primeros 600 caracteres)"],
|
|
10663
|
-
["--quota-hits", "ls --workspace: mensajes (WhatsApp Business y Teams) que no abrieron turno porque se acab\xF3 el saldo"]
|
|
10744
|
+
["--quota-hits", "ls --workspace: mensajes (WhatsApp Business y Teams) que no abrieron turno porque se acab\xF3 el saldo"],
|
|
10745
|
+
["--session S", "ls: s\xF3lo los turnos de esa conversaci\xF3n. Los de Claude dicen \xABresumed\xBB: \u2713 = retom\xF3 la sesi\xF3n del SDK, \u26A0 desde cero (orphan|stale|replaced) = perdi\xF3 la memoria"]
|
|
10664
10746
|
],
|
|
10665
10747
|
notes: ["--skipped guarda 30 d\xEDas y s\xF3lo ve lo que Formmy reenvi\xF3: si Formmy no reenv\xEDa (ghosty channels whatsapp ls, \xABformmy forwards\xBB), a gs nunca le llega el mensaje.", "show imprime lo que entr\xF3 y lo que sali\xF3 en cualquier motor o canal (grupos de WhatsApp tambi\xE9n); la l\xEDnea de tiempo lee el transcript del worker, as\xED que s\xF3lo motores del pool (no ACP/goose)."]
|
|
10666
10748
|
},
|
|
@@ -10672,7 +10754,7 @@ var HELPS_ES = {
|
|
|
10672
10754
|
flags: [
|
|
10673
10755
|
["--waba", "el camino real de WhatsApp Business: contexto heredado, regla de precios, tablero, voz. Muestra lo que MANDAR\xCDA"],
|
|
10674
10756
|
["--session PHONE", "con --waba: un tel\xE9fono de cliente inventado (10-15 d\xEDgitos); cada tel\xE9fono es su propia conversaci\xF3n"],
|
|
10675
|
-
["--media FILE", "
|
|
10757
|
+
["--media FILE", "adjunta esta foto/PDF/audio. Solo: como desde la app (se repite, hasta 8). Con --waba: la manda el cliente (una por turno)"],
|
|
10676
10758
|
["--as-formmy", "con --media: llega como un reenv\xEDo real de Formmy (proxy con credencial, copia a gs, transcripci\xF3n) y dice si la copia funcion\xF3"],
|
|
10677
10759
|
["--keep", "con --waba: deja la tarjeta de prueba en el tablero (por default se archiva al terminar, y el tablero autom\xE1tico nunca corre en pruebas)"],
|
|
10678
10760
|
["--history FILE", "con --waba: lo que se dijo antes, como [{role: user|agent, text, at?}]; cuenta en el primer turno (--reset)"],
|
|
@@ -10850,6 +10932,19 @@ var HELPS_ES = {
|
|
|
10850
10932
|
],
|
|
10851
10933
|
notes: ["Un agente sin bolsa gasta del saldo del espacio. rm s\xF3lo borra una bolsa que ning\xFAn agente usa: mu\xE9velos primero (agents set --bag)."]
|
|
10852
10934
|
},
|
|
10935
|
+
demos: {
|
|
10936
|
+
summary: "Demos p\xFAblicos (/p/<slug>): pru\xE9balo como visitante desde la terminal y (staff de Ghosty) lee qu\xE9 hicieron los visitantes. El due\xF1o s\xF3lo ve n\xFAmeros: ghosty agents demo <id>.",
|
|
10937
|
+
flags: [
|
|
10938
|
+
["--new", "try: un visitante nuevo (si no, sigue el mismo hilo entre corridas)"],
|
|
10939
|
+
["--recycle-between", "try: recicla las cajas del agente entre mensajes, para probar que recuerda entre cajas (necesita --agent)"],
|
|
10940
|
+
["--agent X", "try: el agente del demo, para --recycle-between"],
|
|
10941
|
+
["--since T", "history: desde cu\xE1ndo (2d, 12h o una fecha ISO; default 7d)"],
|
|
10942
|
+
["--session S", "history: el hilo de un visitante (web:\u2026)"],
|
|
10943
|
+
["--timeline", "history: en qu\xE9 se fue el tiempo de cada turno (tool \u2192 segundos), del transcript del worker"],
|
|
10944
|
+
["--full", "history: los mensajes completos (default: primeros 400 caracteres)"]
|
|
10945
|
+
],
|
|
10946
|
+
notes: ["try cuenta contra los topes del demo (mensajes por visitante, por IP al d\xEDa) y sale en sus n\xFAmeros y en el CRM.", "history es s\xF3lo para staff de Ghosty: los chats de los visitantes no se le ense\xF1an al due\xF1o."]
|
|
10947
|
+
},
|
|
10853
10948
|
spaces: {
|
|
10854
10949
|
summary: "Operaciones del staff de Ghosty sobre un espacio: mover \xABpagado hasta\xBB y renombrar su slug. Cada una queda en la bit\xE1cora del espacio.",
|
|
10855
10950
|
flags: [
|
|
@@ -10870,11 +10965,12 @@ var HELPS = {
|
|
|
10870
10965
|
agents: {
|
|
10871
10966
|
summary: "Create, inspect, change, check and delete agents.",
|
|
10872
10967
|
usage: [
|
|
10873
|
-
"ghosty agents ls",
|
|
10968
|
+
"ghosty agents ls [--engine E] [--ids] [--sponsor EMAIL | --owner EMAIL]",
|
|
10874
10969
|
"ghosty agents create --name NAME [--engine E] [--model M] [--prompt TEXT | --prompt-file FILE] [--env K=V,\u2026] [--workspace SLUG] [--channels teams=off,\u2026] [--like AGENT [--copy-dbs] [--board same|main|new|NAME] [--no-notify] [--dry-run]]",
|
|
10875
10970
|
"ghosty agents get <agent-id> [--fields name,model,prompt] [--prompt-out FILE]",
|
|
10876
10971
|
"ghosty agents export <agent-id> [--out DIR] # the agent as a file; see ghosty apply --help",
|
|
10877
10972
|
"ghosty agents set <agent-id> [--name] [--engine] [--model] [--prompt | --prompt-file] [--env K=V,\u2026] [--unset-env K1,K2] [--connect ID,\u2026] [--disconnect ID,\u2026] [--voice E:ID] [--voice-replies auto|off] [--auto-stage on|off] [--toolsets crm,sales | +crm,-sales] [--tool [channel:]tool=on|off,\u2026] [--bag NAME|none] [--avatar URL|FILE]",
|
|
10973
|
+
"ghosty agents set <agent-id> --engine E [--model M] --dry-run # what changing the engine does (sessions, machines, memory), without changing it",
|
|
10878
10974
|
"ghosty agents set <agent-id> --prompt-base <base-agent-id> --prompt-overlay overlay.json [--dry-run [--out FILE]] | --prompt-base none",
|
|
10879
10975
|
"ghosty agents doctor <agent-id> [--verbose]",
|
|
10880
10976
|
"ghosty agents docs ls|grant|revoke <agent-id> [doc\u2026] [--write] [--no-deliver] [--like AGENT]",
|
|
@@ -10899,6 +10995,7 @@ var HELPS = {
|
|
|
10899
10995
|
["--no-notify", "create --like: share it with the same people WITHOUT emailing them"],
|
|
10900
10996
|
["--workspace SLUG", "create: born in that workspace (you own or administer it), owned by its owner and active in its Teams"],
|
|
10901
10997
|
["--channels k=on|off,\u2026", "create/set: turn channels on/off: teams, web, whatsapp, messenger, wa (personal WhatsApp)"],
|
|
10998
|
+
["--engine E --dry-run", "set: what changing the engine does (sessions forgotten, machine started and which one is destroyed, whether it is busy) without changing it; each conversation keeps its history"],
|
|
10902
10999
|
["--toolsets LIST", "set: gs tool groups it gets (crm, sales\u2026): a list replaces; +x / -x add or remove"],
|
|
10903
11000
|
["--prompt-base ID", "set: this agent's prompt = that agent's prompt + --prompt-overlay; regenerates when the base changes. none = unlink (keeps today's prompt)"],
|
|
10904
11001
|
["--prompt-overlay FILE", 'set: JSON {"edits":[{"what","find","replace","regex"?,"all"?}]}, applied in order; each must find its place or nothing changes'],
|
|
@@ -10924,12 +11021,16 @@ var HELPS = {
|
|
|
10924
11021
|
["--rotate", "demo: new code; the old link stops working"],
|
|
10925
11022
|
["--check PATH", "box: this file must exist in the machine (repeatable), e.g. a file the new image brings"],
|
|
10926
11023
|
["--contains PATH=TEXT", "box: that file must contain TEXT (a change that adds no files)"],
|
|
10927
|
-
["--recycle", "box: replace its machines (not mid-turn); the new one starts on the next turn
|
|
11024
|
+
["--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"],
|
|
11025
|
+
["--reenv / --force", "box --recycle on ACP: --reenv only re-bakes the env in the same box; --force recreates it even if busy (owner or staff)"],
|
|
10928
11026
|
["--unset-env K1,K2", "set: delete these env variables (--env K= also deletes K)"],
|
|
10929
11027
|
["--tool [ch:]tool=on|off", "set: one tool on/off in one channel (wa:actualizar_identidad=off) or everywhere (crm_buscar=off); comma-separated"],
|
|
10930
11028
|
["--bag NAME|none", "set: the token bag of its workspace it spends from; none = the workspace balance (staff only)"],
|
|
10931
11029
|
["--avatar URL|FILE", "set: its face in Teams: an https link or a local JPEG/PNG/WebP (stored at img.ghosty.studio)"],
|
|
10932
|
-
["--from-workspace", "rm: turn it off in every Teams where it's active, then delete it (owner or staff)"]
|
|
11030
|
+
["--from-workspace", "rm: turn it off in every Teams where it's active, then delete it (owner or staff)"],
|
|
11031
|
+
["--prompt-mode M", "set: append (default: its prompt + Ghosty's persona) or replace (its prompt only)"],
|
|
11032
|
+
["--sponsor/--owner EMAIL", "ls: agents of ANY account paid by / owned by that email (Ghosty staff); then operate them by id"],
|
|
11033
|
+
["--ids", "ls: only the ids, one per line (for xargs)"]
|
|
10933
11034
|
],
|
|
10934
11035
|
examples: [
|
|
10935
11036
|
"ghosty agents doctor cmu7pgbyr0001gee3ruk172mm",
|
|
@@ -10953,7 +11054,8 @@ var HELPS = {
|
|
|
10953
11054
|
"ghosty agents box <id> --contains /opt/claude-worker/skills/pdf-doc/render-doc.mjs=pdf-templates",
|
|
10954
11055
|
"ghosty agents set <id> --tool wa:actualizar_identidad=off,teams:actualizar_identidad=on",
|
|
10955
11056
|
'ghosty agents set minighosty --bag "MiniGhosty" --avatar https://www.ghosty.studio/avatars/minighosty.png',
|
|
10956
|
-
"ghosty agents rm fable --from-workspace"
|
|
11057
|
+
"ghosty agents rm fable --from-workspace",
|
|
11058
|
+
"ghosty agents ls --sponsor testers@ghosty.studio --engine ghosty-lite --ids | xargs -I{} ghosty agents set {} --engine claude --dry-run"
|
|
10957
11059
|
],
|
|
10958
11060
|
notes: ["doctor exits 1 when it finds a problem, so it can gate a script or CI.", "A derived prompt can't be edited directly (409): change the base or the overlay, or unlink it with --prompt-base none.", "demo with no flags only prints its status: link, expiry, caps and numbers."],
|
|
10959
11061
|
learn: `${REF}#agentes`
|
|
@@ -11039,7 +11141,7 @@ var HELPS = {
|
|
|
11039
11141
|
},
|
|
11040
11142
|
turns: {
|
|
11041
11143
|
summary: "An agent's recent turns: what answered, what failed and why, and where one turn's time went.",
|
|
11042
|
-
usage: ["ghosty turns ls <agent-id> [--errors | --status STATE | --skipped] [--since 1h] [--limit N]", "ghosty turns ls --workspace <slug> --quota-hits [--days 14]", "ghosty turns show <agent-id> [turn-id | last] [--full]"],
|
|
11144
|
+
usage: ["ghosty turns ls <agent-id> [--errors | --status STATE | --skipped] [--since 1h] [--limit N]", "ghosty turns ls --workspace <slug> --quota-hits [--days 14]", "ghosty turns show <agent-id> [turn-id | last] [--full]", "ghosty turns cancel <agent-id> <conversation-id> # stop the turn AND its background jobs"],
|
|
11043
11145
|
flags: [
|
|
11044
11146
|
["--errors", "only failed turns (same as --status error)"],
|
|
11045
11147
|
["--skipped", "ls: WhatsApp Business customer messages that did NOT open a turn, and why (operator active, records only, paused, reaction, channel off, no balance)"],
|
|
@@ -11047,7 +11149,8 @@ var HELPS = {
|
|
|
11047
11149
|
["--since T", "30m, 1h, 2d or an ISO date"],
|
|
11048
11150
|
["--limit N", "max rows (default 20, max 500)"],
|
|
11049
11151
|
["--full", "show: the whole message that came in and the whole answer (default: first 600 chars)"],
|
|
11050
|
-
["--quota-hits", "ls --workspace: messages (WhatsApp Business and Teams) that got no turn because the balance ran out"]
|
|
11152
|
+
["--quota-hits", "ls --workspace: messages (WhatsApp Business and Teams) that got no turn because the balance ran out"],
|
|
11153
|
+
["--session S", "ls: only this conversation's turns. Claude turns show \xABresumed\xBB: \u2713 = it kept the SDK session, \u26A0 from scratch (orphan|stale|replaced) = it lost its memory"]
|
|
11051
11154
|
],
|
|
11052
11155
|
examples: ["ghosty turns ls <id> --errors --since 1h", "ghosty turns ls --workspace descti --quota-hits --days 30", "ghosty turns ls <id> --skipped --since 1d", "ghosty turns ls <id> --json --limit 100", "ghosty turns show <id> # last turn: each tool, its seconds, output tokens"],
|
|
11053
11156
|
notes: ["--skipped keeps 30 days and only sees what Formmy forwarded: if Formmy doesn't forward (ghosty channels whatsapp ls, \xABformmy forwards\xBB), gs never gets the message.", "show prints what came in and what went out on any engine or channel (WhatsApp groups too); the timeline reads the worker's transcript, so pool engines only (not ACP/goose)."],
|
|
@@ -11075,7 +11178,7 @@ var HELPS = {
|
|
|
11075
11178
|
flags: [
|
|
11076
11179
|
["--waba", "the real WhatsApp Business path: inherited context, price rule, board, voice. Shows what it WOULD send"],
|
|
11077
11180
|
["--session PHONE", "with --waba: a made-up customer phone (10-15 digits); each phone is its own conversation"],
|
|
11078
|
-
["--media FILE", "
|
|
11181
|
+
["--media FILE", "attach this photo/PDF/audio. Alone: as from the app (repeatable, up to 8). With --waba: the customer sends it (one per turn)"],
|
|
11079
11182
|
["--as-formmy", "with --media: it arrives like a real Formmy forward (proxy with credential, copy to gs, transcription) and says if the copy worked"],
|
|
11080
11183
|
["--keep", "with --waba: leave the test card on the board (by default it's archived when the test ends, and auto-stage never runs on tests)"],
|
|
11081
11184
|
["--history FILE", "with --waba: what was said before, as [{role: user|agent, text, at?}]; counts on the first turn (--reset)"],
|
|
@@ -11101,8 +11204,8 @@ var HELPS = {
|
|
|
11101
11204
|
},
|
|
11102
11205
|
chat: {
|
|
11103
11206
|
summary: "Stream a conversation; with no message it is interactive.",
|
|
11104
|
-
usage: ['ghosty chat <agent-id> ["message"] [--conversation ID]', "ghosty conversations ls <agent-id>", "ghosty conversations show <agent-id> <conversation-id> [--limit 20]"],
|
|
11105
|
-
examples: ['ghosty chat <id> "hola"', "ghosty chat <id>"],
|
|
11207
|
+
usage: ['ghosty chat <agent-id> ["message"] [--conversation ID] [--media FILE]\u2026', "ghosty conversations ls <agent-id>", "ghosty conversations show <agent-id> <conversation-id> [--limit 20]"],
|
|
11208
|
+
examples: ['ghosty chat <id> "hola"', "ghosty chat <id>", 'ghosty chat <id> "p\xE1salo a PDF" --media foto.jpg'],
|
|
11106
11209
|
learn: `${REF}#conversaciones`
|
|
11107
11210
|
},
|
|
11108
11211
|
schedule: {
|
|
@@ -11338,6 +11441,25 @@ HELPS.bags = {
|
|
|
11338
11441
|
notes: ["An agent with no bag spends from the workspace balance. rm only deletes a bag no agent uses: move them first (agents set --bag)."],
|
|
11339
11442
|
learn: `${REF}#espacios`
|
|
11340
11443
|
};
|
|
11444
|
+
HELPS.demos = {
|
|
11445
|
+
summary: "Public demos (/p/<slug>): try one as a visitor from the terminal, and (Ghosty staff) read what visitors did. The owner only sees numbers: ghosty agents demo <id>.",
|
|
11446
|
+
usage: [
|
|
11447
|
+
'ghosty demos try <slug> "message" ["message 2"\u2026] [--new] [--recycle-between --agent <agent>]',
|
|
11448
|
+
"ghosty demos history <agent> [--since 2d|ISO] [--session web:\u2026] [--timeline] [--full]"
|
|
11449
|
+
],
|
|
11450
|
+
flags: [
|
|
11451
|
+
["--new", "try: a new visitor (otherwise it keeps the same thread across runs)"],
|
|
11452
|
+
["--recycle-between", "try: recycle the agent's boxes between messages, to test that it remembers across boxes (needs --agent)"],
|
|
11453
|
+
["--agent X", "try: the demo's agent, for --recycle-between"],
|
|
11454
|
+
["--since T", "history: from when (2d, 12h or an ISO date; default 7d)"],
|
|
11455
|
+
["--session S", "history: one visitor's thread (web:\u2026)"],
|
|
11456
|
+
["--timeline", "history: where each turn's time went (tool \u2192 seconds), from the worker transcript"],
|
|
11457
|
+
["--full", "history: whole messages (default: first 400 chars)"]
|
|
11458
|
+
],
|
|
11459
|
+
examples: ['ghosty demos try guardias-k3x9q2 "recuerda MANZANA-47" "\xBFqu\xE9 palabra te dije?" --recycle-between --agent <id>', "ghosty demos history <id> --since 2d --timeline"],
|
|
11460
|
+
notes: ["try counts against the demo's caps (messages per visitor, per IP per day) and shows up in its numbers and CRM.", "history is Ghosty staff only: visitors' chats are not shown to the owner."],
|
|
11461
|
+
learn: `${REF}#demo-publico-agents-demo`
|
|
11462
|
+
};
|
|
11341
11463
|
HELPS.spaces = {
|
|
11342
11464
|
summary: "Ghosty staff operations on a workspace: move \xABpaid until\xBB and rename its slug. Each one is logged in the workspace's audit.",
|
|
11343
11465
|
usage: ["ghosty spaces renew <workspace> --until YYYY-MM-DD [--note TEXT] [--dry-run] [--yes]", "ghosty spaces rename <old> <new> [--dry-run] [--yes]"],
|
|
@@ -11374,7 +11496,7 @@ function helpText(cmd) {
|
|
|
11374
11496
|
}
|
|
11375
11497
|
|
|
11376
11498
|
// src/update.ts
|
|
11377
|
-
import { mkdir as mkdir4, readFile as readFile9, writeFile as
|
|
11499
|
+
import { mkdir as mkdir4, readFile as readFile9, writeFile as writeFile5 } from "node:fs/promises";
|
|
11378
11500
|
import { homedir as homedir3 } from "node:os";
|
|
11379
11501
|
import { dirname as dirname3, join as join6 } from "node:path";
|
|
11380
11502
|
var FILE = join6(process.env.XDG_CONFIG_HOME || join6(homedir3(), ".config"), "ghosty", "update.json");
|
|
@@ -11399,7 +11521,7 @@ async function latestVersion() {
|
|
|
11399
11521
|
const latest = (await r.json()).version;
|
|
11400
11522
|
if (latest) cache2 = { latest, checkedAt: Date.now() };
|
|
11401
11523
|
await mkdir4(dirname3(FILE), { recursive: true });
|
|
11402
|
-
await
|
|
11524
|
+
await writeFile5(FILE, JSON.stringify(cache2));
|
|
11403
11525
|
} catch {
|
|
11404
11526
|
}
|
|
11405
11527
|
}
|
|
@@ -11464,13 +11586,16 @@ function agentArgIndex(pos) {
|
|
|
11464
11586
|
return pos[2] === "groups" ? 4 : 3;
|
|
11465
11587
|
case "boards":
|
|
11466
11588
|
return sub === "assign" ? 2 : null;
|
|
11589
|
+
case "demos":
|
|
11590
|
+
case "demo":
|
|
11591
|
+
return sub === "history" ? 2 : null;
|
|
11467
11592
|
default:
|
|
11468
11593
|
return null;
|
|
11469
11594
|
}
|
|
11470
11595
|
}
|
|
11471
11596
|
|
|
11472
11597
|
// src/completion.ts
|
|
11473
|
-
import { mkdir as mkdir5, readFile as readFile10, writeFile as
|
|
11598
|
+
import { mkdir as mkdir5, readFile as readFile10, writeFile as writeFile6 } from "node:fs/promises";
|
|
11474
11599
|
import { homedir as homedir4 } from "node:os";
|
|
11475
11600
|
import { join as join7 } from "node:path";
|
|
11476
11601
|
var GLOBAL_FLAGS = ["--json", "--token", "--profile", "--yes", "--help", "--version", "--lang", "--dry-run"];
|
|
@@ -11522,7 +11647,7 @@ async function names() {
|
|
|
11522
11647
|
const workspaces = [...new Set(list.map((a) => a.espacio?.tipo === "workspace" ? a.espacio.nombre : void 0).filter((x) => !!x))];
|
|
11523
11648
|
await mkdir5(join7(cacheFile(), ".."), { recursive: true }).catch(() => {
|
|
11524
11649
|
});
|
|
11525
|
-
await
|
|
11650
|
+
await writeFile6(cacheFile(), JSON.stringify({ at: Date.now(), agents, workspaces })).catch(() => {
|
|
11526
11651
|
});
|
|
11527
11652
|
return { agents, workspaces };
|
|
11528
11653
|
}
|
|
@@ -11600,7 +11725,7 @@ function normalizeArgs(pos, v) {
|
|
|
11600
11725
|
|
|
11601
11726
|
// src/commands/spec.ts
|
|
11602
11727
|
var import_yaml = __toESM(require_dist(), 1);
|
|
11603
|
-
import { mkdir as mkdir6, readdir as readdir4, readFile as readFile11, stat, writeFile as
|
|
11728
|
+
import { mkdir as mkdir6, readdir as readdir4, readFile as readFile11, stat, writeFile as writeFile7 } from "node:fs/promises";
|
|
11604
11729
|
import { join as join8, resolve as resolve2 } from "node:path";
|
|
11605
11730
|
var enc9 = encodeURIComponent;
|
|
11606
11731
|
async function agentsExport(id, v) {
|
|
@@ -11612,7 +11737,7 @@ async function agentsExport(id, v) {
|
|
|
11612
11737
|
}
|
|
11613
11738
|
const dir = resolve2(v.out);
|
|
11614
11739
|
await mkdir6(dir, { recursive: true });
|
|
11615
|
-
await
|
|
11740
|
+
await writeFile7(join8(dir, "agent.yaml"), yaml);
|
|
11616
11741
|
const own = (0, import_yaml.parse)(yaml)["x-ghosty"]?.skills?.own ?? [];
|
|
11617
11742
|
for (const slug of own) {
|
|
11618
11743
|
const sk = await api(`/api/v2/agents/${enc9(id)}/skills/${enc9(slug)}`);
|
|
@@ -11893,6 +12018,122 @@ ${table(rows, cols)}
|
|
|
11893
12018
|
${dim(t(`${fmtTokens(total)} billable tokens \xB7 failed = error or stopped \xB7 no balance: ghosty turns ls --workspace ${r.workspace} --quota-hits`, `${fmtTokens(total)} tokens facturables \xB7 failed = error o detenido \xB7 sin saldo: ghosty turns ls --workspace ${r.workspace} --quota-hits`))}`);
|
|
11894
12019
|
}
|
|
11895
12020
|
|
|
12021
|
+
// src/commands/demos.ts
|
|
12022
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
12023
|
+
import { mkdir as mkdir7, readFile as readFile12, writeFile as writeFile8 } from "node:fs/promises";
|
|
12024
|
+
import { homedir as homedir5 } from "node:os";
|
|
12025
|
+
import { dirname as dirname4, join as join9 } from "node:path";
|
|
12026
|
+
var enc11 = encodeURIComponent;
|
|
12027
|
+
var hora = (iso) => new Date(iso).toLocaleString("es-MX", { timeZone: "America/Mexico_City", hour12: false, dateStyle: "short", timeStyle: "short" });
|
|
12028
|
+
async function history(agent, v) {
|
|
12029
|
+
if (!agent) throw usage(t("Missing agent.", "Falta el agente."), "ghosty demos history <agent> [--since 2d] [--session web:\u2026] [--timeline] [--full]");
|
|
12030
|
+
const q = new URLSearchParams({ since: v.since ?? "7d", ...v.session ? { session: v.session } : {}, ...v.timeline ? { timeline: "1" } : {}, ...v.full ? { full: "1" } : {} });
|
|
12031
|
+
const r = await api(`/api/v2/agents/${enc11(agent)}/demo/conversations?${q}`);
|
|
12032
|
+
out(r, () => {
|
|
12033
|
+
const lines = [`${bold(r.agent.name ?? r.agent.id)} ${dim(`/p/${r.agent.slug ?? "\u2014"} \xB7 ${t("since", "desde")} ${hora(r.since)} \xB7 ${t("clones", "clones")}: ${r.clones.length}`)}`];
|
|
12034
|
+
for (const c of r.clones) lines.push(dim(` ${t("clone", "clon")} ${c.id} (${hora(c.createdAt)})${c.sponsored ? t(" \xB7 on your key", " \xB7 con tu llave") : ""}`));
|
|
12035
|
+
for (const c of r.conversations) {
|
|
12036
|
+
lines.push("", bold(`${c.sessionId}`) + dim(` \xB7 ${hora(c.createdAt)} \u2192 ${hora(c.updatedAt)}`));
|
|
12037
|
+
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)}`) : ""}`);
|
|
12038
|
+
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(", ")}]`) : ""}`);
|
|
12039
|
+
for (const s of c.timeline ?? []) lines.push(dim(s.kind === "turn" ? ` \u2500\u2500 ${t("turn", "turno")} ${hora(s.at)}` : ` ${String(s.secs).padStart(4)}s ${s.kind === "tool" ? `${s.tool}: ${s.input} (out ${s.outTokens ?? "?"})` : `\xAB${s.text}\xBB`}`));
|
|
12040
|
+
}
|
|
12041
|
+
if (!r.conversations.length) lines.push("", dim(t("No visitors in that range.", "Sin visitantes en ese rango.")));
|
|
12042
|
+
return lines.join("\n");
|
|
12043
|
+
});
|
|
12044
|
+
}
|
|
12045
|
+
var jarPath = () => join9(process.env.XDG_CONFIG_HOME || join9(homedir5(), ".config"), "ghosty", "demo-cookies.json");
|
|
12046
|
+
async function readJar() {
|
|
12047
|
+
try {
|
|
12048
|
+
return JSON.parse(await readFile12(jarPath(), "utf8"));
|
|
12049
|
+
} catch {
|
|
12050
|
+
return {};
|
|
12051
|
+
}
|
|
12052
|
+
}
|
|
12053
|
+
function anonCookieOf(setCookie) {
|
|
12054
|
+
const m = /(?:^|,\s*)(gs_anon=[^;]+)/.exec(setCookie ?? "");
|
|
12055
|
+
return m ? m[1] : null;
|
|
12056
|
+
}
|
|
12057
|
+
async function visitorCookie(base, slug, fresh) {
|
|
12058
|
+
const jar = await readJar();
|
|
12059
|
+
if (jar[slug] && !fresh) return jar[slug];
|
|
12060
|
+
const res = await fetch(`${base}/p/${enc11(slug)}`, { redirect: "manual" });
|
|
12061
|
+
if (res.status === 404) throw new CliError(t(`No demo \xAB${slug}\xBB (the link is name-code, e.g. guardias-k3x9q2).`, `No hay demo \xAB${slug}\xBB (la liga es nombre-c\xF3digo, p. ej. guardias-k3x9q2).`), EXIT.API);
|
|
12062
|
+
const cookie = anonCookieOf(res.headers.get("set-cookie"));
|
|
12063
|
+
if (!cookie) throw new CliError(t("The demo didn't hand out a visitor cookie.", "El demo no entreg\xF3 cookie de visitante."), EXIT.API);
|
|
12064
|
+
jar[slug] = cookie;
|
|
12065
|
+
await mkdir7(dirname4(jarPath()), { recursive: true });
|
|
12066
|
+
await writeFile8(jarPath(), JSON.stringify(jar, null, 2), { mode: 384 });
|
|
12067
|
+
return cookie;
|
|
12068
|
+
}
|
|
12069
|
+
async function visitorTurn(base, slug, cookie, text) {
|
|
12070
|
+
const turnId = randomUUID2();
|
|
12071
|
+
const ctrl = new AbortController();
|
|
12072
|
+
const ev = await fetch(`${base}/p/${enc11(slug)}/events`, { headers: { cookie, accept: "text/event-stream" }, signal: ctrl.signal });
|
|
12073
|
+
if (!ev.ok) throw await toError(ev);
|
|
12074
|
+
const sent = await fetch(`${base}/p/${enc11(slug)}/messages`, { method: "POST", headers: { cookie, "content-type": "application/json" }, body: JSON.stringify({ content: text, turnId }) });
|
|
12075
|
+
if (!sent.ok) {
|
|
12076
|
+
ctrl.abort();
|
|
12077
|
+
const body = await sent.text();
|
|
12078
|
+
if (sent.headers.get("x-needs-login")) throw new CliError(t(`The demo cut this visitor off: ${body}`, `El demo le cort\xF3 a este visitante: ${body}`), EXIT.API, t("New visitor: add --new", "Visitante nuevo: agrega --new"));
|
|
12079
|
+
throw new CliError(`${sent.status} ${serverText(body.slice(0, 300))}`, EXIT.API);
|
|
12080
|
+
}
|
|
12081
|
+
let answer = "";
|
|
12082
|
+
let ours = false;
|
|
12083
|
+
try {
|
|
12084
|
+
for await (const e of readSse(ev.body)) {
|
|
12085
|
+
const d = parseData(e);
|
|
12086
|
+
if (d.turnId) ours = d.turnId === turnId;
|
|
12087
|
+
if (!ours) continue;
|
|
12088
|
+
if (e.event === "chunk" && d.text) {
|
|
12089
|
+
answer += d.text;
|
|
12090
|
+
if (!globals.json) process.stdout.write(d.text);
|
|
12091
|
+
} else if (e.event === "tool" && d.title && !globals.json) info(dim(` \u2699 ${serverText(d.title)}`));
|
|
12092
|
+
else if (e.event === "error") throw new CliError(d.message ? serverText(d.message) : t("The turn failed.", "El turno fall\xF3."), EXIT.API);
|
|
12093
|
+
else if (e.event === "done") break;
|
|
12094
|
+
}
|
|
12095
|
+
} finally {
|
|
12096
|
+
ctrl.abort();
|
|
12097
|
+
}
|
|
12098
|
+
if (!globals.json && !answer.endsWith("\n")) process.stdout.write("\n");
|
|
12099
|
+
return answer;
|
|
12100
|
+
}
|
|
12101
|
+
async function recycle(agent) {
|
|
12102
|
+
for (let i = 0; i < 10; i++) {
|
|
12103
|
+
try {
|
|
12104
|
+
await api(`/api/v2/agents/${enc11(agent)}/box`, { method: "POST", body: { recycle: true } });
|
|
12105
|
+
return;
|
|
12106
|
+
} catch (e) {
|
|
12107
|
+
if (!(e instanceof CliError) || !/turno|turn/i.test(e.message)) throw e;
|
|
12108
|
+
await new Promise((r) => setTimeout(r, 3e3));
|
|
12109
|
+
}
|
|
12110
|
+
}
|
|
12111
|
+
throw new CliError(t("Couldn't recycle: a turn kept running.", "No pude reciclar: segu\xEDa un turno en curso."), EXIT.API);
|
|
12112
|
+
}
|
|
12113
|
+
async function tryDemo(slug, texts, v) {
|
|
12114
|
+
if (!slug || !texts.length) throw usage(t("Missing slug or message.", "Falta el slug o el mensaje."), 'ghosty demos try <slug> "recuerda MANZANA-47" "\xBFqu\xE9 palabra te dije?" [--recycle-between --agent <agent>] [--new]');
|
|
12115
|
+
if (v["recycle-between"] && !v.agent) throw usage(t("--recycle-between needs --agent <agent> (its boxes get recycled).", "--recycle-between necesita --agent <agente> (se reciclan sus cajas)."));
|
|
12116
|
+
const base = await baseUrl();
|
|
12117
|
+
const cookie = await visitorCookie(base, slug, !!v.new);
|
|
12118
|
+
if (!globals.json) info(dim(t(`visitor ${v.new ? "new" : "saved"} \xB7 each message counts against the demo's caps and shows up in its stats and CRM`, `visitante ${v.new ? "nuevo" : "guardado"} \xB7 cada mensaje cuenta contra los topes del demo y sale en sus n\xFAmeros y el CRM`)));
|
|
12119
|
+
const answers = [];
|
|
12120
|
+
for (const [i, text] of texts.entries()) {
|
|
12121
|
+
const recycledBefore = i > 0 && !!v["recycle-between"];
|
|
12122
|
+
if (recycledBefore) {
|
|
12123
|
+
await recycle(v.agent);
|
|
12124
|
+
if (!globals.json) info(lavender(t("\u21BB boxes recycled: the next answer starts in a new box", "\u21BB cajas recicladas: la siguiente respuesta nace en caja nueva")));
|
|
12125
|
+
}
|
|
12126
|
+
if (!globals.json) info(bold(`\u{1F464} ${text}`));
|
|
12127
|
+
answers.push({ text, answer: await visitorTurn(base, slug, cookie, text), recycledBefore });
|
|
12128
|
+
}
|
|
12129
|
+
if (globals.json) out({ slug, turns: answers });
|
|
12130
|
+
}
|
|
12131
|
+
async function demosCmd(sub, a1, rest, v) {
|
|
12132
|
+
if (sub === "history") return history(a1, v);
|
|
12133
|
+
if (sub === "try") return tryDemo(a1, rest, v);
|
|
12134
|
+
throw usage(t(`Unknown demos subcommand: ${sub ?? "(none)"}.`, `Subcomando de demos desconocido: ${sub ?? "(ninguno)"}.`), 'ghosty demos history <agent> \xB7 ghosty demos try <slug> "message"');
|
|
12135
|
+
}
|
|
12136
|
+
|
|
11896
12137
|
// src/index.ts
|
|
11897
12138
|
var HELP_EN = `${bold("Usage:")} ghosty <command> [options]
|
|
11898
12139
|
|
|
@@ -11914,7 +12155,8 @@ ${bold("Workspaces")} ${dim("(changing them: Ghosty staff)")}
|
|
|
11914
12155
|
spaces rename <old> <new> Change its slug only [--dry-run]
|
|
11915
12156
|
|
|
11916
12157
|
${bold("Agents")}
|
|
11917
|
-
agents ls List your agents
|
|
12158
|
+
agents ls List your agents [--engine E] [--ids: only ids, for xargs]
|
|
12159
|
+
[--sponsor EMAIL | --owner EMAIL] any account's agents (Ghosty staff)
|
|
11918
12160
|
agents create Create an agent --name [--engine] [--model] [--prompt | --prompt-file FILE] [--env K=V,\u2026]
|
|
11919
12161
|
[--workspace SLUG] [--channels teams=off,\u2026] born in a workspace, active in its Teams
|
|
11920
12162
|
[--like AGENT [--copy-dbs] [--board same|main|new|NAME] [--no-notify] [--dry-run]] with another agent's setup (not its prompt, env or channels)
|
|
@@ -11928,6 +12170,7 @@ ${bold("Agents")}
|
|
|
11928
12170
|
[--toolsets crm,sales | +crm,-sales] gs tool groups it gets
|
|
11929
12171
|
[--prompt-base AGENT|none --prompt-overlay FILE [--dry-run]] prompt = another agent's + edits, kept in sync
|
|
11930
12172
|
[--tool wa:tool=off,\u2026] [--bag NAME|none] [--avatar URL|FILE] a tool per channel \xB7 its token bag (staff) \xB7 its face in Teams
|
|
12173
|
+
[--prompt-mode append|replace] replace = its prompt WITHOUT Ghosty's house persona
|
|
11931
12174
|
agents doctor <id> Check the config: overlapping skills, tools the prompt names, broken grants, failing turns
|
|
11932
12175
|
agents docs ls|grant|revoke <id> [doc] Documents it reads and may send [--write] [--no-deliver]
|
|
11933
12176
|
agents docs grant <id> A B\u2026 Grant several documents at once [--like AGENT] copies another agent's
|
|
@@ -11937,7 +12180,7 @@ ${bold("Agents")}
|
|
|
11937
12180
|
agents share|unshare <id> <email> Share it without handing it over [--role editor|admin] \xB7 agents shares <id> lists them
|
|
11938
12181
|
agents rm <id> Delete an agent (owner only; asks you to type its name, or --yes) [--from-workspace: turn it off in Teams first]
|
|
11939
12182
|
agents restart <id> Restart an ACP agent (needsRestart) so it loads new skills
|
|
11940
|
-
agents box <id> The machine serving it: is it on the new image? [--check PATH]\u2026 [--contains PATH=TEXT]\u2026 [--recycle]
|
|
12183
|
+
agents box <id> The machine serving it: is it on the new image? [--check PATH]\u2026 [--contains PATH=TEXT]\u2026 [--recycle [--reenv] [--force]]
|
|
11941
12184
|
agents demo enable|disable <id> Turn its public demo link on or off
|
|
11942
12185
|
agents demo <id> Public demo link /p/<slug>: anyone tries it without an account, "Keep it" clones it to theirs
|
|
11943
12186
|
[--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]
|
|
@@ -11964,16 +12207,21 @@ ${bold("Configure an agent")}
|
|
|
11964
12207
|
cards show <id> <card> A card's thread: who wrote, text, attachments and whether gs kept a copy (folio, phone or id)
|
|
11965
12208
|
cards archive <id> Archive cards in bulk (reversible) --integration X | --column X | --before 30d [--dry-run]
|
|
11966
12209
|
|
|
12210
|
+
${bold("Demos")}
|
|
12211
|
+
demos try <slug> "msg" ["msg2"\u2026] Try a public demo as a visitor (same thread across runs) [--new] [--recycle-between --agent X]
|
|
12212
|
+
demos history <id> What visitors did: turns, messages, files (Ghosty staff) [--since 2d] [--session web:\u2026] [--timeline] [--full]
|
|
12213
|
+
|
|
11967
12214
|
${bold("Talk to an agent")}
|
|
11968
|
-
try <id> "message" One test turn, prints the full answer [--session] [--reset]
|
|
12215
|
+
try <id> "message" One test turn, prints the full answer [--session] [--reset] [--media FILE: attach, as from the app]
|
|
11969
12216
|
--waba --session PHONE \u2026through the real WhatsApp path, nothing sent; lists what it would send (tools too) [--media FILE [--as-formmy]] [--history FILE]
|
|
11970
12217
|
--replay FILE --out DIR Replay real conversations; report written locally [--max 15] [--concurrency 8] [--resume]
|
|
11971
|
-
chat <id> ["message"] Stream a conversation (no message = interactive) [--conversation ID]
|
|
12218
|
+
chat <id> ["message"] Stream a conversation (no message = interactive) [--conversation ID] [--media FILE]
|
|
11972
12219
|
conversations ls <id> List conversations
|
|
11973
12220
|
conversations show <id> <conversation> What was said [--limit 20] (0 = all)
|
|
11974
12221
|
turns ls <id> Recent turns and why they failed [--errors] [--skipped] [--since 1h] [--limit N]
|
|
11975
12222
|
turns ls --workspace SLUG --quota-hits Messages that got no turn because the balance ran out [--days 14]
|
|
11976
12223
|
turns show <id> [turn] What came in, what it answered, and where the time went (default: last) [--full]
|
|
12224
|
+
turns cancel <id> <conv> Stop: the running turn AND that conversation's background jobs (video, subagents, pending wake-ups)
|
|
11977
12225
|
|
|
11978
12226
|
${bold("Schedule turns")} ${dim("(each run lands in a conversation and notifies your phone; an answer of exactly OK stays silent)")}
|
|
11979
12227
|
schedule ls <id> Pending turns and recent runs
|
|
@@ -12022,7 +12270,8 @@ ${bold("Espacios")} ${dim("(cambiarlos: staff de Ghosty)")}
|
|
|
12022
12270
|
spaces rename <viejo> <nuevo> Cambia s\xF3lo su slug [--dry-run]
|
|
12023
12271
|
|
|
12024
12272
|
${bold("Agentes")}
|
|
12025
|
-
agents ls Lista tus agentes
|
|
12273
|
+
agents ls Lista tus agentes [--engine E] [--ids: s\xF3lo ids, para xargs]
|
|
12274
|
+
[--sponsor CORREO | --owner CORREO] agentes de cualquier cuenta (staff de Ghosty)
|
|
12026
12275
|
agents create Crea un agente --name [--engine] [--model] [--prompt | --prompt-file ARCHIVO] [--env K=V,\u2026]
|
|
12027
12276
|
[--workspace SLUG] [--channels teams=off,\u2026] nace en un espacio, activo en su Teams
|
|
12028
12277
|
[--like AGENTE [--copy-dbs] [--board same|main|new|NOMBRE] [--no-notify] [--dry-run]] con la configuraci\xF3n de otro agente (no su prompt, env ni canales)
|
|
@@ -12036,6 +12285,7 @@ ${bold("Agentes")}
|
|
|
12036
12285
|
[--toolsets crm,sales | +crm,-sales] grupos de tools de gs que recibe
|
|
12037
12286
|
[--prompt-base AGENTE|none --prompt-overlay ARCHIVO [--dry-run]] prompt = el de otro agente + ediciones, sincronizado
|
|
12038
12287
|
[--tool wa:tool=off,\u2026] [--bag NOMBRE|none] [--avatar URL|ARCHIVO] una tool por canal \xB7 su bolsa de tokens (staff) \xB7 su cara en Teams
|
|
12288
|
+
[--prompt-mode append|replace] replace = su prompt SIN la persona de casa de Ghosty
|
|
12039
12289
|
agents doctor <id> Revisa la configuraci\xF3n: skills encimadas, tools que nombra el prompt, permisos rotos, turnos que fallan
|
|
12040
12290
|
agents docs ls|grant|revoke <id> [doc] Documentos que lee y puede enviar [--write] [--no-deliver]
|
|
12041
12291
|
agents docs grant <id> A B\u2026 Da varios documentos a la vez [--like AGENTE] copia los de otro agente
|
|
@@ -12045,7 +12295,7 @@ ${bold("Agentes")}
|
|
|
12045
12295
|
agents share|unshare <id> <correo> Comp\xE1rtelo sin cederlo [--role editor|admin] \xB7 agents shares <id> los lista
|
|
12046
12296
|
agents rm <id> Borra un agente (s\xF3lo el due\xF1o; te pide escribir su nombre, o --yes) [--from-workspace: antes lo desactiva en Teams]
|
|
12047
12297
|
agents restart <id> Reinicia un agente ACP (needsRestart) para que cargue skills nuevas
|
|
12048
|
-
agents box <id> La m\xE1quina que lo atiende: \xBFya trae la imagen nueva? [--check RUTA]\u2026 [--contains RUTA=TEXTO]\u2026 [--recycle]
|
|
12298
|
+
agents box <id> La m\xE1quina que lo atiende: \xBFya trae la imagen nueva? [--check RUTA]\u2026 [--contains RUTA=TEXTO]\u2026 [--recycle [--reenv] [--force]]
|
|
12049
12299
|
agents demo enable|disable <id> Prende o apaga su liga p\xFAblica de demo
|
|
12050
12300
|
agents demo <id> Liga p\xFAblica de demo /p/<slug>: cualquiera lo prueba sin cuenta y \xABQued\xE1rmelo\xBB lo clona a la suya
|
|
12051
12301
|
[--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]
|
|
@@ -12072,16 +12322,21 @@ ${bold("Configurar un agente")}
|
|
|
12072
12322
|
cards show <id> <tarjeta> El hilo de una tarjeta: qui\xE9n escribi\xF3, texto, adjuntos y si gs guard\xF3 copia (folio, tel\xE9fono o id)
|
|
12073
12323
|
cards archive <id> Archiva tarjetas en bloque (reversible) --integration X | --column X | --before 30d [--dry-run]
|
|
12074
12324
|
|
|
12325
|
+
${bold("Demos")}
|
|
12326
|
+
demos try <slug> "msg" ["msg2"\u2026] Prueba un demo p\xFAblico como visitante (mismo hilo entre corridas) [--new] [--recycle-between --agent X]
|
|
12327
|
+
demos history <id> Qu\xE9 hicieron los visitantes: turnos, mensajes, archivos (staff de Ghosty) [--since 2d] [--session web:\u2026] [--timeline] [--full]
|
|
12328
|
+
|
|
12075
12329
|
${bold("Hablar con un agente")}
|
|
12076
|
-
try <id> "mensaje" Un turno de prueba; imprime la respuesta completa [--session] [--reset]
|
|
12330
|
+
try <id> "mensaje" Un turno de prueba; imprime la respuesta completa [--session] [--reset] [--media ARCHIVO: adjunto, como desde la app]
|
|
12077
12331
|
--waba --session TEL \u2026por el camino real de WhatsApp, sin enviar nada; lista lo que mandar\xEDa (tools tambi\xE9n) [--media ARCHIVO [--as-formmy]] [--history ARCHIVO]
|
|
12078
12332
|
--replay ARCHIVO --out DIR Repite conversaciones reales; el reporte se escribe local [--max 15] [--concurrency 8] [--resume]
|
|
12079
|
-
chat <id> ["mensaje"] Conversaci\xF3n en streaming (sin mensaje = interactivo) [--conversation ID]
|
|
12333
|
+
chat <id> ["mensaje"] Conversaci\xF3n en streaming (sin mensaje = interactivo) [--conversation ID] [--media ARCHIVO]
|
|
12080
12334
|
conversations ls <id> Lista conversaciones
|
|
12081
12335
|
conversations show <id> <conversaci\xF3n> Qu\xE9 se dijo [--limit 20] (0 = todo)
|
|
12082
12336
|
turns ls <id> Turnos recientes y por qu\xE9 fallaron [--errors] [--skipped] [--since 1h] [--limit N]
|
|
12083
12337
|
turns ls --workspace SLUG --quota-hits Mensajes que no abrieron turno porque se acab\xF3 el saldo [--days 14]
|
|
12084
12338
|
turns show <id> [turno] Qu\xE9 lleg\xF3, qu\xE9 contest\xF3 y en qu\xE9 se fue el tiempo (default: el \xFAltimo) [--full]
|
|
12339
|
+
turns cancel <id> <conv> Detener: el turno en curso Y los encargos en fondo de esa conversaci\xF3n (video, subagentes, despertadores)
|
|
12085
12340
|
|
|
12086
12341
|
${bold("Turnos programados")} ${dim("(cada corrida cae en una conversaci\xF3n y avisa a tu tel\xE9fono; una respuesta de exactamente OK no avisa)")}
|
|
12087
12342
|
schedule ls <id> Turnos pendientes y corridas recientes
|
|
@@ -12110,7 +12365,7 @@ ${bold("Opciones globales")}
|
|
|
12110
12365
|
${bold("C\xF3digos de salida")} 0 bien \xB7 1 error de la API \xB7 2 uso incorrecto \xB7 3 sin sesi\xF3n
|
|
12111
12366
|
${dim("Nota: `ghosty` puede chocar con otro binario en tu PATH (p. ej. la TUI de Ghosty). Rev\xEDsalo con `which -a ghosty`; corre `npx @ghostystudio/cli` para ir a la segura.")}
|
|
12112
12367
|
${dim("Documentaci\xF3n: https://www.ghosty.studio/docs/cli")}`;
|
|
12113
|
-
var COMMANDS = ["login", "logout", "whoami", "usage", "use", "apply", "plan", "engines", "bags", "spaces", "completion", "cards", "agents", "files", "skills", "mcp", "dbs", "turns", "try", "chat", "conversations", "schedule", "partner", "me", "channels", "board", "boards", "credentials", "docs", "help"];
|
|
12368
|
+
var COMMANDS = ["login", "logout", "whoami", "usage", "use", "apply", "plan", "engines", "bags", "spaces", "demos", "completion", "cards", "agents", "files", "skills", "mcp", "dbs", "turns", "try", "chat", "conversations", "schedule", "partner", "me", "channels", "board", "boards", "credentials", "docs", "help"];
|
|
12114
12369
|
var AGENTS_SUBS = ["ls", "create", "get", "set", "export", "doctor", "docs", "share", "unshare", "shares", "rm", "restart", "demo", "box"];
|
|
12115
12370
|
function unknown(what, input, known, helpCmd) {
|
|
12116
12371
|
const s = suggest(input, known);
|
|
@@ -12281,7 +12536,17 @@ async function main() {
|
|
|
12281
12536
|
"quota-hits": { type: "boolean" },
|
|
12282
12537
|
by: { type: "string" },
|
|
12283
12538
|
models: { type: "boolean" },
|
|
12284
|
-
teams: { type: "string" }
|
|
12539
|
+
teams: { type: "string" },
|
|
12540
|
+
owner: { type: "string" },
|
|
12541
|
+
ids: { type: "boolean" },
|
|
12542
|
+
force: { type: "boolean" },
|
|
12543
|
+
reenv: { type: "boolean" },
|
|
12544
|
+
"prompt-mode": { type: "string" },
|
|
12545
|
+
format: { type: "string" },
|
|
12546
|
+
timeline: { type: "boolean" },
|
|
12547
|
+
new: { type: "boolean" },
|
|
12548
|
+
"recycle-between": { type: "boolean" },
|
|
12549
|
+
"env-file": { type: "string" }
|
|
12285
12550
|
}
|
|
12286
12551
|
});
|
|
12287
12552
|
globals.json = Boolean(v.json);
|
|
@@ -12289,7 +12554,7 @@ async function main() {
|
|
|
12289
12554
|
const tokens = splitTokens(pos, v);
|
|
12290
12555
|
globals.token = tokens.session;
|
|
12291
12556
|
globals.profile = v.profile;
|
|
12292
|
-
if (v.version) return out({ version: "0.
|
|
12557
|
+
if (v.version) return out({ version: "0.29.0" }, () => "0.29.0");
|
|
12293
12558
|
for (const n of normalizeArgs(pos, v)) if (!globals.json) info(dim(n));
|
|
12294
12559
|
const context = (await readConfig()).workspace;
|
|
12295
12560
|
if (!explicitToken()?.startsWith("gat_")) {
|
|
@@ -12335,7 +12600,7 @@ async function main() {
|
|
|
12335
12600
|
case "apply":
|
|
12336
12601
|
return applyCmd(sub, v);
|
|
12337
12602
|
case "agents":
|
|
12338
|
-
if (sub === "ls" || sub === "list" || !sub) return agentsLs(v.all ? void 0 : context);
|
|
12603
|
+
if (sub === "ls" || sub === "list" || !sub) return agentsLs(v.all ? void 0 : v.workspace ?? context, { sponsor: v.sponsor, owner: v.owner, engine: v.engine, ids: v.ids, all: v.all });
|
|
12339
12604
|
if (sub === "create" || sub === "new") {
|
|
12340
12605
|
if (v.workspace === "none") delete v.workspace;
|
|
12341
12606
|
else if (!v.workspace && context) {
|
|
@@ -12396,6 +12661,9 @@ async function main() {
|
|
|
12396
12661
|
return meUsage();
|
|
12397
12662
|
case "engines":
|
|
12398
12663
|
return enginesCmd(sub, v);
|
|
12664
|
+
case "demos":
|
|
12665
|
+
case "demo":
|
|
12666
|
+
return demosCmd(sub, a1, pos.slice(3).filter((x) => x !== void 0), v);
|
|
12399
12667
|
case "bags":
|
|
12400
12668
|
case "bag":
|
|
12401
12669
|
return bagsCmd(sub, a1, a2, v, context);
|
|
@@ -12418,7 +12686,7 @@ async function main() {
|
|
|
12418
12686
|
throw unknown(cmd, cmd, COMMANDS, "ghosty --help");
|
|
12419
12687
|
}
|
|
12420
12688
|
}
|
|
12421
|
-
var notice = updateNotice("0.
|
|
12689
|
+
var notice = updateNotice("0.29.0").catch(() => null);
|
|
12422
12690
|
main().then(
|
|
12423
12691
|
async () => {
|
|
12424
12692
|
const n = await notice;
|
package/package.json
CHANGED