@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.
Files changed (3) hide show
  1. package/README.md +18 -1
  2. package/dist/index.js +340 -72
  3. 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.27.1"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
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.27.1"}`,
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 history;
8507
+ let history2;
8491
8508
  if (v.history) {
8492
8509
  const raw = JSON.parse(await readFile2(v.history, "utf8"));
8493
- history = Array.isArray(raw) ? raw : raw.messages;
8494
- if (!Array.isArray(history)) throw usage(t(`${v.history}: expected [{role, text, at?}] or {messages: [...]}.`, `${v.history}: se esperaba [{role, text, at?}] o {messages: [...]}.`));
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 } : {}, ...history ? { history } : {}, ...v["as-formmy"] ? { asFormmy: true } : {}, ...v.keep ? { keep: true } : {} });
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.media?.length || v.history) return tryWaba(id, text, v);
8782
- if (!text && !v.reset) throw usage(t("Missing message.", "Falta el mensaje."), t("Example", "Ejemplo") + ': ghosty try <agent-id> "hello"');
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
- if (message) {
9144
- await turn(agent, sid, message, first);
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 GHOSTY_PARTNER_SECRET=gps_\u2026 ghosty partner agent get
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 GHOSTY_PARTNER_SECRET=gps_\u2026 ghosty partner agent get
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.27.1"}`
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
- function showSecret(r) {
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
- ` GHOSTY_PARTNER_SECRET=${r.secret}`
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 && 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]");
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 state = `${bold(tu.id)} ${tu.state === "error" ? red(tu.state) : tu.state} ${tu.secs ?? "?"} s${tu.session ? dim(` ${tu.session}`) : ""}`;
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 (no a media respuesta); la nueva arranca en el siguiente turno. ACP: usa restart"],
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", "con --waba: el cliente manda esta foto/audio/PDF (uno por turno)"],
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. ACP: use restart"],
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", "with --waba: the customer sends this photo/audio/PDF (one per turn)"],
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 writeFile4 } from "node:fs/promises";
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 writeFile4(FILE, JSON.stringify(cache2));
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 writeFile5 } from "node:fs/promises";
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 writeFile5(cacheFile(), JSON.stringify({ at: Date.now(), agents, workspaces })).catch(() => {
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 writeFile6 } from "node:fs/promises";
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 writeFile6(join8(dir, "agent.yaml"), yaml);
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.27.1" }, () => "0.27.1");
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.27.1").catch(() => null);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghostystudio/cli",
3
- "version": "0.27.1",
3
+ "version": "0.29.0",
4
4
  "description": "Official Ghosty Studio CLI — configure, test and chat with your agents from the terminal. Built for humans and coding agents.",
5
5
  "type": "module",
6
6
  "bin": {