@ghostystudio/cli 0.14.0 → 0.16.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 +225 -31
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -44,11 +44,14 @@ ghosty channels whatsapp history <id> --integration ID [--days 7] [--dry-run]
44
44
  ghosty channels wa import <id> --from-file F | --from-dir DIR --own-number yes|no [--dry-run] # personal WhatsApp session, no QR
45
45
  ghosty channels wa pair|status|pause <id> [--phone N]
46
46
  ghosty channels wa groups ls|set|create <id> [group] [--role equipo|cliente] [--on|--off] [--dry-run] # personal WhatsApp groups
47
+ ghosty boards ls|create|assign|move-cards|rm <workspace|agent> … [--columns-from main] [--from A --to B --integration X --dry-run]
48
+ ghosty board ls <id> [--q TEXT] [--limit 20] # recent cards on its sales board
49
+ ghosty board show <id> <folio|phone|id> [--limit 20] # a card's thread, attachments and whether gs kept a copy
47
50
  ghosty board archive <id> --integration X | --column X | --before 30d [--dry-run]
48
51
  ghosty mcp get|set <id> [--file servers.json]
49
52
 
50
53
  ghosty try <id> "message" [--session] [--reset]
51
- ghosty try <id> "message" --waba --session PHONE [--media FILE] [--history FILE] # real WhatsApp path, nothing sent
54
+ ghosty try <id> "message" --waba --session PHONE [--media FILE [--as-formmy]] [--history FILE] # real WhatsApp path, nothing sent
52
55
  ghosty try <id> --replay FILE --out DIR [--max 15] [--concurrency 8] [--resume]
53
56
  ghosty chat <id> ["message"] [--conversation ID]
54
57
  ghosty conversations ls <id>
@@ -84,6 +87,20 @@ Exit codes: `0` ok · `1` API error · `2` usage error · `3` not signed in.
84
87
  - `try --replay FILE --out DIR`: replays real conversations from your machine. The report is written after every step, survives Ghosty deploys (it honors `Retry-After`), and `--resume` continues a partial run.
85
88
  - `agents doctor` speaks English like the rest of the CLI; `channels whatsapp ls` returns a full URL.
86
89
 
90
+ ### Changes in 0.16.0
91
+
92
+ - `agents create --like <agent> [--copy-dbs] [--dry-run]`: a new agent with another's setup (skills and their switches, toolsets, connectors, voice, databases, documents, board, shares), never its prompt, env or channels.
93
+ - `skills house <id> --only a,b | --enable | --disable`: Ghosty's skills in bulk.
94
+ - `agents docs grant <id> A B …` and `--like <agent>`.
95
+ - `agents get --fields toolsets,connectors`: effective toolsets and each connector (connected, granted, effective).
96
+ - `boards ls|create|assign|move-cards`: a workspace's boards, one per number if you like, with the columns of another and the cards moved over.
97
+
98
+ ### Changes in 0.15.0
99
+
100
+ - `try --waba` lists everything the agent would send, tools included: 📍 location, 📄 files, 🎤 voice, ↩️ quotes, 🔘 buttons, 👤 contacts, reactions. Quotes and reactions now find the test message to point at.
101
+ - `try --waba --media FILE --as-formmy`: the attachment arrives like a real Formmy forward (proxy with credential, copy to gs, transcription) and the output says whether the copy worked.
102
+ - `board ls` (recent cards) and `board show <folio|phone|id>` (a card's thread: who wrote, text, attachment, copy in gs).
103
+
87
104
  ### Changes in 0.14.0
88
105
 
89
106
  - Fix: `--json` output over a pipe no longer gets cut at 64 KB (an 80 KB prompt came out as invalid JSON). The CLI now waits for stdout to drain before exiting.
package/dist/index.js CHANGED
@@ -288,7 +288,7 @@ async function postLong(path, body, timeoutMs = 9e5) {
288
288
  const req = httpsRequest(`${base}${path}`, {
289
289
  method: "POST",
290
290
  timeout: timeoutMs,
291
- headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.14.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
291
+ headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.16.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
292
292
  }, (res) => {
293
293
  let buf = "";
294
294
  res.on("data", (c) => buf += c);
@@ -330,7 +330,7 @@ async function request(path, opts = {}, retried = false) {
330
330
  method: opts.method ?? (opts.body !== void 0 || opts.raw !== void 0 ? "POST" : "GET"),
331
331
  headers: {
332
332
  authorization: `Bearer ${token}`,
333
- "user-agent": `ghosty-cli/${"0.14.0"}`,
333
+ "user-agent": `ghosty-cli/${"0.16.0"}`,
334
334
  ...opts.body !== void 0 ? { "content-type": "application/json" } : {},
335
335
  ...opts.headers
336
336
  },
@@ -428,13 +428,34 @@ async function uploadMedia(file) {
428
428
  const mime = MIME[ext] ?? "application/octet-stream";
429
429
  return { url: r.url, mime, filename: r.name, type: mime.startsWith("image/") ? "image" : mime.startsWith("audio/") ? "audio" : mime.startsWith("video/") ? "video" : "document" };
430
430
  }
431
+ function richLine(s) {
432
+ const d = s.data ?? {};
433
+ const str = (k) => typeof d[k] === "string" ? d[k] : "";
434
+ switch (s.rich) {
435
+ case "ubicacion":
436
+ return `\u{1F4CD} ${str("nombre") || "location"} \xB7 ${Number(d.latitud).toFixed(4)},${Number(d.longitud).toFixed(4)}${str("direccion") ? dim(` ${str("direccion")}`) : ""}`;
437
+ case "boton":
438
+ return `\u{1F518} ${str("boton")} ${dim(str("url"))} ${str("texto")}`;
439
+ case "contacto":
440
+ return `\u{1F464} ${str("nombre")} ${str("telefono")}`;
441
+ case "encuesta":
442
+ return `\u{1F4CA} ${str("pregunta")}`;
443
+ case "reaccion":
444
+ return `${str("emoji")} ${dim(`reaction to ${str("mensajeId") || "the last message"}`)}`;
445
+ case "cita":
446
+ return `\u21A9\uFE0F ${str("texto")} ${dim(`quoting ${str("mensajeId") || "the last message"}`)}`;
447
+ }
448
+ }
431
449
  function sentText(sent) {
432
- return sent.map((s) => s.kind === "text" ? `${lavender("\u2192")} ${s.text}` : s.kind === "voice" ? `${lavender("\u{1F3A4}")} ${s.text}` : `${lavender("\u{1F4CE}")} ${s.name} ${dim(s.url)}`).join("\n\n");
450
+ return sent.map(
451
+ (s) => s.kind === "text" ? `${lavender("\u2192")} ${s.text}` : s.kind === "voice" ? `${lavender("\u{1F3A4}")} ${s.text}` : s.kind === "file" ? `${lavender(/^audio\//.test(s.mime ?? "") ? "\u{1F3A4}" : "\u{1F4C4}")} ${s.name} ${dim(s.url)}` : richLine(s)
452
+ ).join("\n\n");
433
453
  }
434
454
  async function tryWaba(id, text, v) {
435
455
  const phone = v.session ?? "";
436
456
  if (!/^\d{10,15}$/.test(phone)) throw usage("--waba needs --session <phone> (10-15 digits, a made-up number).", 'Example: ghosty try <agent-id> "hola" --waba --session 5210000000009');
437
457
  if ((v.media?.length ?? 0) > 1) throw usage("One --media per turn (WhatsApp sends one file per message).", "Send several turns, one file each.");
458
+ if (v["as-formmy"] && !v.media?.length) throw usage("--as-formmy goes with --media FILE.", 'Example: ghosty try <agent-id> "" --waba --session 5210000000009 --media nota.ogg --as-formmy');
438
459
  if (!text && !v.media?.length && !v.reset) throw usage("Missing message.", 'Example: ghosty try <agent-id> "hola" --waba --session 5210000000009');
439
460
  let history;
440
461
  if (v.history) {
@@ -445,9 +466,11 @@ async function tryWaba(id, text, v) {
445
466
  }
446
467
  const media = v.media?.[0] ? await uploadMedia(v.media[0]) : void 0;
447
468
  if (process.stderr.isTTY && !globals.json && (text || media)) info(dim("Thinking\u2026 (the real WhatsApp path; nothing is sent)"));
448
- const r = await tryPost(id, { as: "waba", session: phone, reset: v.reset, text: text ?? "", ...media ? { media } : {}, ...history ? { history } : {} });
469
+ const r = await tryPost(id, { as: "waba", session: phone, reset: v.reset, text: text ?? "", ...media ? { media } : {}, ...history ? { history } : {}, ...v["as-formmy"] ? { asFormmy: true } : {} });
449
470
  if (r.status !== 200) throw Object.assign(new Error(`${r.status} ${r.body.error ?? "turn failed"}`), { code: 1 });
450
- out(r.body, () => (r.body.sent ? sentText(r.body.sent) || dim("(the agent sent nothing)") : dim("Session reset.")) + (r.body.error ? `
471
+ const via = r.body.media ? `
472
+ ${r.body.media.copied ? dim("attachment: downloaded through the Formmy-like proxy and copied to gs") : red("attachment: NOT copied to gs (the proxy download failed)")}` : "";
473
+ out(r.body, () => (r.body.sent ? sentText(r.body.sent) || dim("(the agent sent nothing)") : dim("Session reset.")) + via + (r.body.error ? `
451
474
  ${dim(`(warning: ${r.body.error})`)}` : ""));
452
475
  }
453
476
  async function tryReplay(id, v) {
@@ -550,11 +573,22 @@ async function agentsCreate(v) {
550
573
  if (v.workspace) body.workspace = v.workspace;
551
574
  const channels = parseChannels(v.channels);
552
575
  if (channels) body.channels = channels;
576
+ if (v.like) Object.assign(body, { like: v.like, ...v["copy-dbs"] ? { copyDbs: true } : {}, ...v["dry-run"] ? { dryRun: true } : {} });
577
+ else if (v["dry-run"]) throw usage("--dry-run goes with --like (it lists what would be copied).");
578
+ if (v.like && v["dry-run"]) {
579
+ const p = await api("/api/v2/me/agents", { method: "POST", body });
580
+ return out(p, () => `${dim(`like ${v.like} \xB7 ${p.engine ?? "?"} \xB7 ${p.model ?? "default"}${p.workspace ? ` \xB7 workspace ${p.workspace}` : ""}`)}
581
+ ${p.plan.join("\n")}
582
+ ${dim("Not copied: prompt, env, channels. Dry run: nothing created.")}`);
583
+ }
553
584
  const r = await api("/api/v2/me/agents", { method: "POST", body });
554
585
  const where = r.workspace ? `
555
586
  workspace: ${r.workspace}${r.handle ? ` \xB7 @${r.handle} in Teams` : dim(" \xB7 not active in Teams")}` : "";
587
+ const copied = r.copied?.length ? `
588
+ ${r.copied.map((l) => ` ${l}`).join("\n")}
589
+ ${dim(" Now give it its own prompt: ghosty agents set " + r.id + " --prompt-file PROMPT.md")}` : "";
556
590
  out(r, () => `${lavender("\u2713")} Created ${r.name} ${dim(`(${r.engine} \xB7 ${r.model ?? "default"})`)}
557
- id: ${r.id}${where}`);
591
+ id: ${r.id}${where}${copied}`);
558
592
  }
559
593
  async function agentsGet(id, fields, promptOut) {
560
594
  id = need(id, "agent id", "ghosty agents get <agent-id>");
@@ -749,7 +783,7 @@ async function readSkillDir(dir) {
749
783
  }
750
784
  var trashDir = (id, slug) => join3(process.env.XDG_CONFIG_HOME || join3(homedir2(), ".config"), "ghosty", "trash", id, `${slug}-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`);
751
785
  async function skillsCmd(sub, id, slug, v) {
752
- const ex = "ghosty skills ls <agent-id> | on|off <agent-id> <slug> | get <agent-id> <slug> [--out DIR] | templates <agent-id> | add <agent-id> <slug> [--file SKILL.md | --dir FOLDER] | rm <agent-id> <slug>";
786
+ const ex = "ghosty skills ls <agent-id> | on|off <agent-id> <slug> | house <agent-id> [--only a,b] | get <agent-id> <slug> [--out DIR] | templates <agent-id> | add <agent-id> <slug> [--file SKILL.md | --dir FOLDER] | rm <agent-id> <slug>";
753
787
  id = need(id, "agent id", ex);
754
788
  switch (sub ?? "ls") {
755
789
  case "ls":
@@ -765,6 +799,21 @@ async function skillsCmd(sub, id, slug, v) {
765
799
  ${dim(`${more} more in the community catalog: ghosty skills add <agent-id> <slug> (--json lists them) \xB7 overlaps: ghosty agents doctor ${id}`)}`);
766
800
  return;
767
801
  }
802
+ case "house": {
803
+ const list = (x) => x ? x.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
804
+ const only = list(v.only), on = list(v.enable), off = list(v.disable);
805
+ if (!only && !on && !off) {
806
+ const r2 = await api(agentPath(id, "/skills/house"));
807
+ out(r2, () => `${table(r2.house.map((h) => ({ slug: h.slug, on: h.enabled ? "on" : "off" })), ["slug", "on"])}
808
+ ${dim(`Change: ghosty skills house ${id} --only a,b | --enable a | --disable b [--dry-run]`)}`);
809
+ return;
810
+ }
811
+ const body = { ...only ? { only } : {}, ...on ? { on } : {}, ...off ? { off } : {}, ...v["dry-run"] ? { dryRun: true } : {} };
812
+ const r = await api(agentPath(id, "/skills/house"), { method: "PUT", body });
813
+ out(r, () => `${r.changes.map((c) => `${c.enabled ? "+" : "-"} ${c.slug}`).join("\n") || dim("Nothing to change.")}
814
+ ${dim(`On now: ${r.on.join(", ") || "none"}${r.dryRun ? " \xB7 dry run: nothing changed" : ""}`)}`);
815
+ return;
816
+ }
768
817
  case "on":
769
818
  case "off": {
770
819
  slug = need(slug, "skill slug", `ghosty skills ${sub} <agent-id> <slug>`);
@@ -1220,7 +1269,7 @@ async function call(path, v, init = {}) {
1220
1269
  "x-ghosty-ts": ts,
1221
1270
  "x-ghosty-sig": sig,
1222
1271
  "content-type": "application/json",
1223
- "user-agent": `ghosty-cli/${"0.14.0"}`
1272
+ "user-agent": `ghosty-cli/${"0.16.0"}`
1224
1273
  },
1225
1274
  body: method === "GET" ? void 0 : raw
1226
1275
  });
@@ -1500,7 +1549,8 @@ ${dim(`${rows.length} row(s) \xB7 ${Math.round(r.ms)} ms`)}`;
1500
1549
  throw usage(`Unknown dbs subcommand: ${sub}.`, "Try: ghosty dbs ls | tables | query | import | grant | revoke (ghosty dbs --help)");
1501
1550
  }
1502
1551
  }
1503
- async function agentDocsCmd(sub, id, doc, v) {
1552
+ async function agentDocsCmd(sub, id, docs, v) {
1553
+ let doc = docs[0];
1504
1554
  id = need2(id, "agent id", "ghosty agents docs ls <agent-id>");
1505
1555
  switch (sub ?? "ls") {
1506
1556
  case "ls":
@@ -1510,13 +1560,23 @@ async function agentDocsCmd(sub, id, doc, v) {
1510
1560
  return out(r.documents, () => rows.length ? table(rows, ["name", "access", "delivers", "id"]) : dim("This account has no documents yet: upload them at /app/datos."));
1511
1561
  }
1512
1562
  case "grant": {
1513
- doc = need2(doc, "document (id or exact name)", 'ghosty agents docs grant <agent-id> "Cat\xE1logo.pdf"');
1514
- const body = {};
1515
- if (v.write) body.mode = "write";
1516
- if (v["no-deliver"]) body.deliverable = false;
1517
- else if (v.deliver) body.deliverable = true;
1518
- const r = await api(agentPath2(id, `/documents/${enc6(doc)}`), { method: "PUT", body });
1519
- return out(r, () => `${lavender("\u2713")} ${r.name}: ${r.mode === "write" ? "reads and replaces it" : "reads it"}${r.deliverable ? ", and can send it" : ", never sends it"}.`);
1563
+ let wanted;
1564
+ if (v.like) {
1565
+ if (docs.length) throw usage("Use document names or --like, not both.");
1566
+ const other = await api(agentPath2(v.like, "/documents"));
1567
+ wanted = other.documents.filter((d) => d.granted).map((d) => ({ ref: d.id, body: { mode: d.mode ?? "read", deliverable: d.deliverable ?? true } }));
1568
+ if (!wanted.length) return out({ granted: [] }, () => dim(`${v.like} has no documents granted.`));
1569
+ } else {
1570
+ if (!docs.length) throw usage("Missing document (id or exact name).", 'Example: ghosty agents docs grant <agent-id> "Cat\xE1logo.pdf" "Precios.xlsx" (or --like <other-agent>)');
1571
+ const body = {};
1572
+ if (v.write) body.mode = "write";
1573
+ if (v["no-deliver"]) body.deliverable = false;
1574
+ else if (v.deliver) body.deliverable = true;
1575
+ wanted = docs.map((ref) => ({ ref, body }));
1576
+ }
1577
+ const done = [];
1578
+ for (const w of wanted) done.push(await api(agentPath2(id, `/documents/${enc6(w.ref)}`), { method: "PUT", body: w.body }));
1579
+ return out(done.length === 1 ? done[0] : { granted: done }, () => done.map((r) => `${lavender("\u2713")} ${r.name}: ${r.mode === "write" ? "reads and replaces it" : "reads it"}${r.deliverable ? ", and can send it" : ", never sends it"}.`).join("\n"));
1520
1580
  }
1521
1581
  case "add": {
1522
1582
  if (!v.url === !v.file) throw usage("Use --url URL or --file PATH (one of them).", 'Example: ghosty agents docs add <agent-id> --url https://\u2026/lista.pdf --description "Precios 2026"');
@@ -1891,8 +1951,37 @@ Cards appear on the team's board (no "new lead" alerts).`, { yes: v.yes, retry:
1891
1951
  }
1892
1952
 
1893
1953
  // src/commands/board.ts
1894
- async function boardCmd(sub, id, v) {
1895
- if (sub !== "archive") throw usage(`Unknown board subcommand: ${sub ?? "(none)"}.`, "Try: ghosty board archive <agent-id> --integration prueba --dry-run");
1954
+ var WHO = { user: "customer", assistant: "agent", operator: "operator" };
1955
+ async function boardLs(id, v) {
1956
+ const qs = new URLSearchParams({ ...v.q ? { q: v.q } : {}, ...v.limit ? { limit: v.limit } : {} });
1957
+ const r = await api(`/api/v2/agents/${encodeURIComponent(id)}/board/conversations?${qs}`);
1958
+ const rows = r.conversations.map((c) => ({ folio: c.folio ? `#${c.folio}` : "", name: c.name ?? "", phone: c.phone, column: c.column ?? "", msgs: c.messages, last: (c.lastActivity ?? "").slice(0, 16).replace("T", " ") }));
1959
+ out(r, () => rows.length ? `${table(rows, ["folio", "name", "phone", "column", "msgs", "last"])}
1960
+ ${dim(`\xAB${r.board}\xBB \xB7 thread: ghosty board show ${id} <folio|phone>`)}` : dim(`No cards on \xAB${r.board}\xBB${v.q ? ` matching "${v.q}"` : ""}.`));
1961
+ }
1962
+ async function boardShow(id, ref, v) {
1963
+ if (!ref) throw usage("Missing conversation.", `Example: ghosty board show ${id} "#12" (folio, phone, session or id; list them with ghosty board ls ${id})`);
1964
+ const r = await api(`/api/v2/agents/${encodeURIComponent(id)}/board/conversations/${encodeURIComponent(ref)}${v.limit ? `?limit=${encodeURIComponent(v.limit)}` : ""}`);
1965
+ const c = r.conversation;
1966
+ const head = `${bold(c.name ?? c.phone)} ${dim(`${c.folio ? `#${c.folio} \xB7 ` : ""}+${c.phone} \xB7 ${c.column ?? "no column"} \xB7 ${c.session}`)}`;
1967
+ const line = (m) => {
1968
+ const at = m.createdAt.slice(5, 16).replace("T", " ");
1969
+ const media = m.mediaType || m.mediaFilename ? `
1970
+ ${dim(`\u{1F4CE} ${m.mediaType ?? "file"} ${m.mediaFilename ?? ""} \xB7 ${m.mediaCopied ? "copy in gs" : "no copy"}`)}` : "";
1971
+ return `${dim(at)} ${lavender((WHO[m.role] ?? m.role).padEnd(8))} ${m.content.replace(/\n/g, "\n ")}${media}`;
1972
+ };
1973
+ out(r, () => [head, ...r.messages.map(line)].join("\n"));
1974
+ }
1975
+ async function boardCmd(sub, id, v, ref) {
1976
+ if (sub === "ls" || sub === "list") {
1977
+ if (!id) throw usage("Missing agent id.", "Example: ghosty board ls <agent-id> [--q text] [--limit 20]");
1978
+ return boardLs(id, v);
1979
+ }
1980
+ if (sub === "show") {
1981
+ if (!id) throw usage("Missing agent id.", 'Example: ghosty board show <agent-id> "#12"');
1982
+ return boardShow(id, ref, v);
1983
+ }
1984
+ if (sub !== "archive") throw usage(`Unknown board subcommand: ${sub ?? "(none)"}.`, "Try: ghosty board ls <agent-id> \xB7 ghosty board show <agent-id> <folio|phone> \xB7 ghosty board archive <agent-id> --integration prueba --dry-run");
1896
1985
  if (!id) throw usage("Missing agent id.", "Example: ghosty board archive <agent-id> --integration prueba --dry-run");
1897
1986
  const filter = { ...v.integration ? { integration: v.integration } : {}, ...v.column ? { column: v.column } : {}, ...v.before ? { before: v.before } : {} };
1898
1987
  if (!Object.keys(filter).length) throw usage("Add at least one filter: --integration, --column or --before.", "A filter-less archive would empty the board.");
@@ -1911,6 +2000,57 @@ Archive ${plan.matched} card(s) on \xAB${plan.board}\xBB? (reversible: a new mes
1911
2000
  out(r, () => `${lavender("\u2713")} Archived ${r.archived} card(s) on \xAB${r.board}\xBB.`);
1912
2001
  }
1913
2002
 
2003
+ // src/commands/boards.ts
2004
+ var enc8 = encodeURIComponent;
2005
+ async function boardsCmd(sub, a1, a2, v) {
2006
+ switch (sub ?? "ls") {
2007
+ case "ls":
2008
+ case "list": {
2009
+ if (!a1) throw usage("Missing workspace.", "Example: ghosty boards ls <workspace-slug>");
2010
+ const r = await api(`/api/v2/workspaces/${enc8(a1)}/boards`);
2011
+ const rows = r.boards.map((b) => ({ name: b.main ? `${b.name} (main)` : b.name, cards: b.cards, agents: b.agents.map((x) => x.name ?? x.id).join(", "), columns: b.columns.length, id: b.id }));
2012
+ return out(r, () => table(rows, ["name", "cards", "agents", "columns", "id"]));
2013
+ }
2014
+ case "create": {
2015
+ if (!a1 || !a2) throw usage("Missing workspace or name.", 'Example: ghosty boards create <workspace> "Siiqtec - Ixmiquilpan" --columns-from main');
2016
+ const r = await api(`/api/v2/workspaces/${enc8(a1)}/boards`, { method: "POST", body: { name: a2, ...v["columns-from"] ? { columnsFrom: v["columns-from"] } : {} } });
2017
+ return out(r, () => `${lavender("\u2713")} Board \xAB${r.name}\xBB created${r.columnsFrom ? ` with the ${r.columns} column(s) of \xAB${r.columnsFrom}\xBB` : ""}. ${dim(`Send an agent's cards there: ghosty boards assign <agent-id> "${r.name}"`)}`);
2018
+ }
2019
+ case "rm": {
2020
+ if (!a1 || !a2) throw usage("Missing workspace or board.", 'Example: ghosty boards rm <workspace> "Prueba"');
2021
+ requireYesIfHeadless(v.yes, `ghosty boards rm ${a1} ${JSON.stringify(a2)}`);
2022
+ await confirm(`Delete the board \xAB${a2}\xBB of ${a1}? (only an empty one; its agents go back to the main board)`, { yes: v.yes, retry: `ghosty boards rm ${a1} ${JSON.stringify(a2)}` });
2023
+ const r = await api(`/api/v2/workspaces/${enc8(a1)}/boards`, { method: "DELETE", body: { board: a2 } });
2024
+ return out(r, () => `${lavender("\u2713")} Board \xAB${r.deleted}\xBB deleted.`);
2025
+ }
2026
+ case "assign": {
2027
+ if (!a1 || !a2) throw usage("Missing agent or board.", 'Example: ghosty boards assign <agent-id> "Siiqtec - Ixmiquilpan" (or main)');
2028
+ const r = await api(`/api/v2/agents/${enc8(a1)}`, { method: "PATCH", body: { board: a2 } });
2029
+ return out(r, () => `${lavender("\u2713")} New cards of ${a1} go to \xAB${r.board}\xBB. ${dim(`Existing ones stay where they are: ghosty boards move-cards ${r.workspace} --from \u2026 --to "${r.board}" \u2026`)}`);
2030
+ }
2031
+ case "move-cards": {
2032
+ if (!a1 || !v.from || !v.to) throw usage("Missing workspace, --from or --to.", 'Example: ghosty boards move-cards <workspace> --from main --to "Siiqtec - Ixmiquilpan" --integration 6a3b02ad\u2026 --dry-run');
2033
+ if (!v.integration && !v.phone && !v.all) throw usage("Add a filter: --integration ID, --phone N or --all.", "A filter-less move would empty the board.");
2034
+ const path = `/api/v2/workspaces/${enc8(a1)}/boards/move-cards`;
2035
+ const body = { from: v.from, to: v.to, ...v.integration ? { integration: v.integration } : {}, ...v.phone ? { phone: v.phone } : {}, ...v.all ? { all: true } : {} };
2036
+ const retry = `ghosty boards move-cards ${a1} --from ${JSON.stringify(v.from)} --to ${JSON.stringify(v.to)}${v.integration ? ` --integration ${v.integration}` : ""}${v.phone ? ` --phone ${v.phone}` : ""}${v.all ? " --all" : ""}`;
2037
+ if (!v["dry-run"]) requireYesIfHeadless(v.yes, retry);
2038
+ const plan = await api(path, { method: "POST", body: { ...body, dryRun: true } });
2039
+ const sample = table(plan.sample.map((c) => ({ folio: c.folio ? `#${c.folio}` : "", name: c.name ?? "", phone: c.phone })), ["folio", "name", "phone"]);
2040
+ const text = `${sample}
2041
+ ${plan.moved} of ${plan.matched} card(s) \xAB${plan.from}\xBB \u2192 \xAB${plan.to}\xBB${plan.skipped.length ? dim(` \xB7 ${plan.skipped.length} already in \xAB${plan.to}\xBB, skipped`) : ""}`;
2042
+ if (v["dry-run"] || !plan.moved) return out(plan, () => `${text}
2043
+ ${dim("Dry run: nothing changed.")}`);
2044
+ await confirm(`${text}
2045
+ Move them with their threads and orders? (they get a new folio there)`, { yes: v.yes, retry });
2046
+ const r = await api(path, { method: "POST", body });
2047
+ return out(r, () => `${lavender("\u2713")} Moved ${r.moved} card(s) (${r.messages} message(s), ${r.orders} order(s)) to \xAB${r.to}\xBB.${r.skipped.length ? dim(` Skipped ${r.skipped.length}.`) : ""}`);
2048
+ }
2049
+ default:
2050
+ throw usage(`Unknown boards subcommand: ${sub}.`, "Try: ghosty boards ls | create | assign | move-cards | rm");
2051
+ }
2052
+ }
2053
+
1914
2054
  // src/help.ts
1915
2055
  var DOCS = "https://www.ghosty.studio/docs/cli";
1916
2056
  var REF = `${DOCS}/comandos`;
@@ -1919,12 +2059,12 @@ var HELPS = {
1919
2059
  summary: "Create, inspect, change, check and delete agents.",
1920
2060
  usage: [
1921
2061
  "ghosty agents ls",
1922
- "ghosty agents create --name NAME [--engine E] [--model M] [--prompt TEXT | --prompt-file FILE] [--env K=V,\u2026] [--workspace SLUG] [--channels teams=off,\u2026]",
2062
+ "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] [--dry-run]]",
1923
2063
  "ghosty agents get <agent-id> [--fields name,model,prompt] [--prompt-out FILE]",
1924
2064
  "ghosty agents set <agent-id> [--name] [--engine] [--model] [--prompt | --prompt-file] [--env K=V,\u2026] [--connect ID,\u2026] [--disconnect ID,\u2026] [--voice E:ID] [--voice-replies auto|off] [--auto-stage on|off] [--toolsets crm,sales | +crm,-sales]",
1925
2065
  "ghosty agents set <agent-id> --prompt-base <base-agent-id> --prompt-overlay overlay.json [--dry-run [--out FILE]] | --prompt-base none",
1926
2066
  "ghosty agents doctor <agent-id> [--verbose]",
1927
- "ghosty agents docs ls|grant|revoke <agent-id> [doc] [--write] [--no-deliver]",
2067
+ "ghosty agents docs ls|grant|revoke <agent-id> [doc\u2026] [--write] [--no-deliver] [--like AGENT]",
1928
2068
  "ghosty agents docs add <agent-id> --url URL | --file PATH [--name N] [--description TEXT] [--grant read|write|no] [--no-deliver]",
1929
2069
  "ghosty agents share <agent-id> <email> [--role editor|admin]",
1930
2070
  "ghosty agents unshare <agent-id> <email> [--yes]",
@@ -1937,6 +2077,8 @@ var HELPS = {
1937
2077
  flags: [
1938
2078
  ["--fields a,b", "get: return only these fields"],
1939
2079
  ["--prompt-out FILE", "get: write the prompt to FILE (edit it and come back with set --prompt-file)"],
2080
+ ["--like AGENT", "create: copy that agent's setup (skills and their switches, toolsets, connectors, voice, databases, documents, board, shares) \u2014 not its prompt, env or channels. docs grant: the same documents"],
2081
+ ["--copy-dbs", "create --like: duplicate its databases with their rows instead of granting the same ones"],
1940
2082
  ["--workspace SLUG", "create: born in that workspace (you own or administer it), owned by its owner and active in its Teams"],
1941
2083
  ["--channels k=on|off,\u2026", "create/set: turn channels on/off: teams, web, whatsapp, messenger, wa (personal WhatsApp)"],
1942
2084
  ["--toolsets LIST", "set: gs tool groups it gets (crm, sales\u2026): a list replaces; +x / -x add or remove"],
@@ -1977,6 +2119,9 @@ var HELPS = {
1977
2119
  "ghosty agents set <id> --toolsets +crm",
1978
2120
  "ghosty agents share <id> ana@empresa.com --role admin",
1979
2121
  "ghosty agents get <id> --prompt-out PROMPT.md",
2122
+ "ghosty agents get <id> --fields toolsets,connectors # what it really has: effective toolsets, connectors connected + granted",
2123
+ "ghosty agents create --name tania-mkt --like <pia-id> --dry-run",
2124
+ 'ghosty agents docs grant <id> "Cat\xE1logo.pdf" "Precios.xlsx" \xB7 ghosty agents docs grant <id> --like <other-id>',
1980
2125
  "ghosty agents set <pia-id> --prompt-base <tania-id> --prompt-overlay capa.json --dry-run --out /tmp/pia.md",
1981
2126
  'ghosty agents demo <id> --slug guardias --on --vence "2026-10-15T20:00" --starter "Ens\xE9\xF1ame c\xF3mo empezar"',
1982
2127
  "ghosty agents box <id> --contains /opt/claude-worker/skills/pdf-doc/render-doc.mjs=pdf-templates"
@@ -1989,6 +2134,7 @@ var HELPS = {
1989
2134
  usage: [
1990
2135
  "ghosty skills ls <agent-id>",
1991
2136
  "ghosty skills on|off <agent-id> <slug>",
2137
+ "ghosty skills house <agent-id> [--only a,b | --enable a,b | --disable a,b] [--dry-run]",
1992
2138
  "ghosty skills get <agent-id> <slug> [--out DIR]",
1993
2139
  "ghosty skills templates <agent-id>",
1994
2140
  "ghosty skills add <agent-id> <slug> [--file SKILL.md | --dir FOLDER]",
@@ -1996,6 +2142,8 @@ var HELPS = {
1996
2142
  ],
1997
2143
  flags: [
1998
2144
  ["--out DIR", "get: folder to write into; without it, get only lists the files"],
2145
+ ["--only a,b", "house: leave ONLY these of Ghosty's skills on (the rest off)"],
2146
+ ["--enable / --disable a,b", "house: turn these of Ghosty's skills on / off"],
1999
2147
  ["--file FILE", "add: upload one SKILL.md"],
2000
2148
  ["--dir FOLDER", "add: upload a whole skill folder (SKILL.md + assets); restores a backup"],
2001
2149
  ["--yes", "rm: skip the confirmation (required without a terminal)"]
@@ -2073,13 +2221,14 @@ var HELPS = {
2073
2221
  summary: "Test turns: plain, through the real WhatsApp path (nothing is sent), or a replay of real conversations.",
2074
2222
  usage: [
2075
2223
  'ghosty try <agent-id> "message" [--session ID] [--reset]',
2076
- 'ghosty try <agent-id> "message" --waba --session PHONE [--media FILE] [--history FILE] [--reset]',
2224
+ 'ghosty try <agent-id> "message" --waba --session PHONE [--media FILE [--as-formmy]] [--history FILE] [--reset]',
2077
2225
  "ghosty try <agent-id> --replay FILE --out DIR [--max 15] [--concurrency 8] [--resume]"
2078
2226
  ],
2079
2227
  flags: [
2080
2228
  ["--waba", "the real WhatsApp Business path: inherited context, price rule, board, voice. Shows what it WOULD send"],
2081
2229
  ["--session PHONE", "with --waba: a made-up customer phone (10-15 digits); each phone is its own conversation"],
2082
2230
  ["--media FILE", "with --waba: the customer sends this photo/audio/PDF (one per turn)"],
2231
+ ["--as-formmy", "with --media: it arrives like a real Formmy forward (proxy with credential, copy to gs, transcription) and says if the copy worked"],
2083
2232
  ["--history FILE", "with --waba: what was said before, as [{role: user|agent, text, at?}]; counts on the first turn (--reset)"],
2084
2233
  ["--reset", "forget that test session (with --waba also its test thread)"],
2085
2234
  ["--replay FILE", "run the conversations in FILE ({sample: [{id, fakePhone, messages}]}) against the agent"],
@@ -2096,7 +2245,8 @@ var HELPS = {
2096
2245
  ],
2097
2246
  notes: [
2098
2247
  "Replays survive Ghosty deploys: a 503 is retried after its Retry-After; the report lives on your disk.",
2099
- "--whatsapp is now an alias of --waba."
2248
+ "--whatsapp is now an alias of --waba.",
2249
+ "--waba lists everything it would send, tools included: \u{1F4CD} location, \u{1F4C4} files, \u{1F3A4} voice, \u21A9\uFE0F quotes, \u{1F518} buttons, \u{1F464} contacts, reactions."
2100
2250
  ],
2101
2251
  learn: `${REF}#turnos-de-prueba`
2102
2252
  },
@@ -2187,18 +2337,44 @@ HELPS.channels = {
2187
2337
  ],
2188
2338
  learn: `${REF}#canales-de-whatsapp`
2189
2339
  };
2340
+ HELPS.boards = {
2341
+ summary: "A WORKSPACE's sales boards: list them, create one (copying another's columns), choose where an agent sends its cards, and move cards between boards.",
2342
+ usage: [
2343
+ "ghosty boards ls <workspace>",
2344
+ 'ghosty boards create <workspace> "Name" [--columns-from main|<board>]',
2345
+ "ghosty boards assign <agent-id> <board|main>",
2346
+ "ghosty boards move-cards <workspace> --from <board> --to <board> [--integration ID | --phone N | --all] [--dry-run] [--yes]",
2347
+ 'ghosty boards rm <workspace> "Name" [--yes]'
2348
+ ],
2349
+ flags: [
2350
+ ["--columns-from B", "create: copy the columns of that board (main, a name or an id) instead of the default ones"],
2351
+ ["--from / --to B", "move-cards: boards by name, id or main"],
2352
+ ["--integration ID | --phone N | --all", "move-cards: which cards (a filter is required)"],
2353
+ ["-n, --dry-run", "move-cards: count and show a sample, change nothing"],
2354
+ ["--yes", "move-cards: skip the confirmation (required without a terminal)"]
2355
+ ],
2356
+ examples: ["ghosty boards ls siiqtec", 'ghosty boards create siiqtec "Siiqtec - Ixmiquilpan" --columns-from main', 'ghosty boards assign <agent-id> "Siiqtec - Ixmiquilpan"', 'ghosty boards move-cards siiqtec --from main --to "Siiqtec - Ixmiquilpan" --integration 6a3b02ad\u2026 --dry-run'],
2357
+ notes: ["assign changes where NEW cards go; move-cards brings the existing ones (thread, orders, activity) with a new folio. A card whose conversation is already on the target board is skipped.", "Reading needs membership; changing needs owner or staff of the workspace.", "rm only deletes an empty board that is not the main one."],
2358
+ learn: `${REF}#tablero`
2359
+ };
2190
2360
  HELPS.board = {
2191
2361
  summary: "The agent's sales board. It belongs to the WORKSPACE: you must be a member, a shared agent is not enough.",
2192
- usage: ["ghosty board archive <agent-id> [--integration ID] [--column NAME] [--before 30d|ISO] [--dry-run] [--yes]"],
2362
+ usage: [
2363
+ "ghosty board ls <agent-id> [--q TEXT] [--limit 20]",
2364
+ "ghosty board show <agent-id> <folio|phone|session|id> [--limit 20]",
2365
+ "ghosty board archive <agent-id> [--integration ID] [--column NAME] [--before 30d|ISO] [--dry-run] [--yes]"
2366
+ ],
2193
2367
  flags: [
2368
+ ["--q TEXT", "ls: cards whose name, phone or last message contains TEXT"],
2369
+ ["--limit N", "ls: cards (default 20) \xB7 show: last N messages (default 20, max 500)"],
2194
2370
  ["--integration ID", "cards from this channel/integration (e.g. prueba)"],
2195
2371
  ["--column NAME", "cards in this column"],
2196
2372
  ["--before T", "cards with no activity since T (30d, 12h or an ISO date)"],
2197
2373
  ["-n, --dry-run", "count and show a sample, change nothing"],
2198
2374
  ["--yes", "skip the confirmation (required without a terminal)"]
2199
2375
  ],
2200
- examples: ["ghosty board archive <id> --integration prueba --dry-run", "ghosty board archive <id> --column Perdido --before 30d --yes"],
2201
- notes: ["Archiving is reversible: when the customer writes again the card comes back. At least one filter is required."],
2376
+ examples: ["ghosty board ls <id> --q 7712", 'ghosty board show <id> "#12"', "ghosty board show <id> 5210000009922 --limit 50", "ghosty board archive <id> --integration prueba --dry-run", "ghosty board archive <id> --column Perdido --before 30d --yes"],
2377
+ notes: ["show prints each message with who wrote it (customer, agent, operator), its attachment and whether gs keeps a copy of it.", "Archiving is reversible: when the customer writes again the card comes back. At least one filter is required."],
2202
2378
  learn: `${REF}#tablero`
2203
2379
  };
2204
2380
  HELPS.conversations = HELPS.chat;
@@ -2272,6 +2448,7 @@ ${bold("Agents")}
2272
2448
  agents ls List your agents
2273
2449
  agents create Create an agent --name [--engine] [--model] [--prompt | --prompt-file FILE] [--env K=V,\u2026]
2274
2450
  [--workspace SLUG] [--channels teams=off,\u2026] born in a workspace, active in its Teams
2451
+ [--like AGENT [--copy-dbs] [--dry-run]] with another agent's setup (not its prompt, env or channels)
2275
2452
  agents get <id> Show an agent's config [--fields name,model,prompt] [--prompt-out FILE]
2276
2453
  agents set <id> Change identity or engine [--name] [--engine] [--model] [--prompt | --prompt-file FILE] [--env K=V,\u2026]
2277
2454
  [--connect google-drive,\u2026] [--disconnect \u2026] give/remove access to your connectors
@@ -2281,6 +2458,7 @@ ${bold("Agents")}
2281
2458
  [--prompt-base AGENT|none --prompt-overlay FILE [--dry-run]] prompt = another agent's + edits, kept in sync
2282
2459
  agents doctor <id> Check the config: overlapping skills, tools the prompt names, broken grants, failing turns
2283
2460
  agents docs ls|grant|revoke <id> [doc] Documents it reads and may send [--write] [--no-deliver]
2461
+ agents docs grant <id> A B\u2026 Grant several documents at once [--like AGENT] copies another agent's
2284
2462
  agents docs add <id> Upload a document and grant it --url URL | --file PATH [--description] [--grant read|write|no]
2285
2463
  agents share|unshare <id> <email> Share it without handing it over [--role editor|admin] \xB7 agents shares <id> lists them
2286
2464
  agents rm <id> Delete an agent (owner only; asks you to type its name, or --yes)
@@ -2292,6 +2470,7 @@ ${bold("Agents")}
2292
2470
  ${bold("Configure an agent")}
2293
2471
  files ls|get|put|rm <id> [path] Files in the agent's machine [--file LOCAL] [--out LOCAL]
2294
2472
  skills on|off <id> <slug> Turn one of its skills (or one of Ghosty's) on or off
2473
+ skills house <id> Ghosty's skills in bulk [--only a,b | --enable a,b | --disable a,b] [--dry-run]
2295
2474
  skills ls|get|add|rm <id> [slug] Skills (add from catalog, --file SKILL.md or --dir FOLDER; get lists, --out DIR saves; rm keeps a copy)
2296
2475
  skills templates <id> pdf-doc templates it sees: its own and Ghosty's, and which folder wins
2297
2476
  dbs ls|tables|query|import|grant|revoke <id> [db] Databases it uses [--write] [--external-write t1,t2] [--external-rows t:col] [--from easybits:ID --dry-run]
@@ -2303,11 +2482,14 @@ ${bold("Configure an agent")}
2303
2482
  channels wa pair <id> --phone N Pair a personal WhatsApp by code (no QR)
2304
2483
  channels wa status|pause <id> Personal WhatsApp: how it is / pause it (keeps the session)
2305
2484
  channels wa groups ls|set|create <id> [group] Personal WhatsApp groups it answers, and as whom [--role equipo|cliente] [--on|--off] [--dry-run]
2485
+ boards ls|create|assign|move-cards|rm A workspace's boards: create (--columns-from), where an agent sends cards, move cards
2486
+ board ls <id> Recent cards on its sales board [--q TEXT] [--limit 20]
2487
+ board show <id> <card> A card's thread: who wrote, text, attachments and whether gs kept a copy (folio, phone or id)
2306
2488
  board archive <id> Archive cards in bulk (reversible) --integration X | --column X | --before 30d [--dry-run]
2307
2489
 
2308
2490
  ${bold("Talk to an agent")}
2309
2491
  try <id> "message" One test turn, prints the full answer [--session] [--reset]
2310
- --waba --session PHONE \u2026through the real WhatsApp path, nothing sent [--media FILE] [--history FILE]
2492
+ --waba --session PHONE \u2026through the real WhatsApp path, nothing sent; lists what it would send (tools too) [--media FILE [--as-formmy]] [--history FILE]
2311
2493
  --replay FILE --out DIR Replay real conversations; report written locally [--max 15] [--concurrency 8] [--resume]
2312
2494
  chat <id> ["message"] Stream a conversation (no message = interactive) [--conversation ID]
2313
2495
  conversations ls <id> List conversations
@@ -2339,7 +2521,7 @@ ${bold("Global options")}
2339
2521
  ${bold("Exit codes")} 0 ok \xB7 1 API error \xB7 2 usage error \xB7 3 not signed in
2340
2522
  ${dim("Note: `ghosty` may collide with another binary on your PATH (e.g. the Ghosty TUI). Check with `which -a ghosty`; run `npx @ghostystudio/cli` to be sure.")}
2341
2523
  ${dim("Docs: https://www.ghosty.studio/docs/cli")}`;
2342
- var COMMANDS = ["login", "logout", "whoami", "usage", "agents", "files", "skills", "mcp", "dbs", "turns", "try", "chat", "conversations", "schedule", "partner", "me", "channels", "board", "credentials", "docs", "help"];
2524
+ var COMMANDS = ["login", "logout", "whoami", "usage", "agents", "files", "skills", "mcp", "dbs", "turns", "try", "chat", "conversations", "schedule", "partner", "me", "channels", "board", "boards", "credentials", "docs", "help"];
2343
2525
  var AGENTS_SUBS = ["ls", "create", "get", "set", "doctor", "docs", "share", "unshare", "shares", "rm", "restart", "demo", "box"];
2344
2526
  function unknown(what, input, known, helpCmd) {
2345
2527
  const s = suggest(input, known);
@@ -2419,6 +2601,16 @@ async function main() {
2419
2601
  limit: { type: "string" },
2420
2602
  verbose: { type: "boolean" },
2421
2603
  full: { type: "boolean" },
2604
+ "as-formmy": { type: "boolean" },
2605
+ like: { type: "string" },
2606
+ "copy-dbs": { type: "boolean" },
2607
+ only: { type: "string" },
2608
+ enable: { type: "string" },
2609
+ disable: { type: "string" },
2610
+ "columns-from": { type: "string" },
2611
+ to: { type: "string" },
2612
+ all: { type: "boolean" },
2613
+ q: { type: "string" },
2422
2614
  "prompt-out": { type: "string" },
2423
2615
  toolsets: { type: "string" },
2424
2616
  "prompt-base": { type: "string" },
@@ -2458,7 +2650,7 @@ async function main() {
2458
2650
  const tokens = splitTokens(pos, v);
2459
2651
  globals.token = tokens.session;
2460
2652
  globals.profile = v.profile;
2461
- if (v.version) return out({ version: "0.14.0" }, () => "0.14.0");
2653
+ if (v.version) return out({ version: "0.16.0" }, () => "0.16.0");
2462
2654
  const [cmd, sub, a1, a2] = pos;
2463
2655
  if (cmd === "partner" && (v.help || !sub || sub === "help")) return out({ help: PARTNER_HELP }, () => PARTNER_HELP);
2464
2656
  const topic = cmd === "help" ? sub : v.help ? cmd : void 0;
@@ -2492,7 +2684,7 @@ async function main() {
2492
2684
  if (sub === "set") return agentsSet(a1, v);
2493
2685
  if (sub === "rm" || sub === "delete") return agentsRm(a1, v);
2494
2686
  if (sub === "doctor") return agentsDoctor(a1, v);
2495
- if (sub === "docs" || sub === "documents") return agentDocsCmd(a1, a2, pos[4], v);
2687
+ if (sub === "docs" || sub === "documents") return agentDocsCmd(a1, a2, pos.slice(4).filter((x) => x !== void 0), v);
2496
2688
  if (sub === "restart") return agentsRestart(a1);
2497
2689
  if (sub === "demo") return agentsDemo(a1, v);
2498
2690
  if (sub === "box") return agentsBox(a1, v);
@@ -2509,7 +2701,9 @@ async function main() {
2509
2701
  case "turns":
2510
2702
  return turnsCmd(sub, a1, a2, v);
2511
2703
  case "board":
2512
- return boardCmd(sub, a1, v);
2704
+ return boardCmd(sub, a1, v, a2);
2705
+ case "boards":
2706
+ return boardsCmd(sub, a1, a2, v);
2513
2707
  case "channels":
2514
2708
  if (a1 === "groups") return groupsCmd(pos.slice(3).filter((x) => x !== void 0), v);
2515
2709
  return channelsCmd(sub, a1, a2, pos.slice(4).join(" ") || void 0, v);
@@ -2544,7 +2738,7 @@ async function main() {
2544
2738
  throw unknown(cmd, cmd, COMMANDS, "ghosty --help");
2545
2739
  }
2546
2740
  }
2547
- var notice = updateNotice("0.14.0").catch(() => null);
2741
+ var notice = updateNotice("0.16.0").catch(() => null);
2548
2742
  main().then(
2549
2743
  async () => {
2550
2744
  const n = await notice;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghostystudio/cli",
3
- "version": "0.14.0",
3
+ "version": "0.16.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": {