@ghostystudio/cli 0.15.0 → 0.16.1

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 +13 -0
  2. package/dist/index.js +153 -19
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -44,6 +44,7 @@ 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]
47
48
  ghosty board ls <id> [--q TEXT] [--limit 20] # recent cards on its sales board
48
49
  ghosty board show <id> <folio|phone|id> [--limit 20] # a card's thread, attachments and whether gs kept a copy
49
50
  ghosty board archive <id> --integration X | --column X | --before 30d [--dry-run]
@@ -86,6 +87,18 @@ Exit codes: `0` ok · `1` API error · `2` usage error · `3` not signed in.
86
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.
87
88
  - `agents doctor` speaks English like the rest of the CLI; `channels whatsapp ls` returns a full URL.
88
89
 
90
+ ### Changes in 0.16.1
91
+
92
+ - `agents create --like`: `--board same|main|new|NAME` (`new` = its own board with the source's columns, one per number) and `--no-notify` (shared with the same people, no email).
93
+
94
+ ### Changes in 0.16.0
95
+
96
+ - `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.
97
+ - `skills house <id> --only a,b | --enable | --disable`: Ghosty's skills in bulk.
98
+ - `agents docs grant <id> A B …` and `--like <agent>`.
99
+ - `agents get --fields toolsets,connectors`: effective toolsets and each connector (connected, granted, effective).
100
+ - `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.
101
+
89
102
  ### Changes in 0.15.0
90
103
 
91
104
  - `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.
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.15.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
291
+ headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.16.1"}`, "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.15.0"}`,
333
+ "user-agent": `ghosty-cli/${"0.16.1"}`,
334
334
  ...opts.body !== void 0 ? { "content-type": "application/json" } : {},
335
335
  ...opts.headers
336
336
  },
@@ -573,11 +573,22 @@ async function agentsCreate(v) {
573
573
  if (v.workspace) body.workspace = v.workspace;
574
574
  const channels = parseChannels(v.channels);
575
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 } : {}, ...v.board ? { board: v.board } : {}, ...v["no-notify"] ? { notify: false } : {} });
577
+ else if (v["dry-run"] || v.board || v["no-notify"]) throw usage("--dry-run, --board and --no-notify go with --like.");
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
+ }
576
584
  const r = await api("/api/v2/me/agents", { method: "POST", body });
577
585
  const where = r.workspace ? `
578
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")}` : "";
579
590
  out(r, () => `${lavender("\u2713")} Created ${r.name} ${dim(`(${r.engine} \xB7 ${r.model ?? "default"})`)}
580
- id: ${r.id}${where}`);
591
+ id: ${r.id}${where}${copied}`);
581
592
  }
582
593
  async function agentsGet(id, fields, promptOut) {
583
594
  id = need(id, "agent id", "ghosty agents get <agent-id>");
@@ -772,7 +783,7 @@ async function readSkillDir(dir) {
772
783
  }
773
784
  var trashDir = (id, slug) => join3(process.env.XDG_CONFIG_HOME || join3(homedir2(), ".config"), "ghosty", "trash", id, `${slug}-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`);
774
785
  async function skillsCmd(sub, id, slug, v) {
775
- 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>";
776
787
  id = need(id, "agent id", ex);
777
788
  switch (sub ?? "ls") {
778
789
  case "ls":
@@ -788,6 +799,21 @@ async function skillsCmd(sub, id, slug, v) {
788
799
  ${dim(`${more} more in the community catalog: ghosty skills add <agent-id> <slug> (--json lists them) \xB7 overlaps: ghosty agents doctor ${id}`)}`);
789
800
  return;
790
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
+ }
791
817
  case "on":
792
818
  case "off": {
793
819
  slug = need(slug, "skill slug", `ghosty skills ${sub} <agent-id> <slug>`);
@@ -1243,7 +1269,7 @@ async function call(path, v, init = {}) {
1243
1269
  "x-ghosty-ts": ts,
1244
1270
  "x-ghosty-sig": sig,
1245
1271
  "content-type": "application/json",
1246
- "user-agent": `ghosty-cli/${"0.15.0"}`
1272
+ "user-agent": `ghosty-cli/${"0.16.1"}`
1247
1273
  },
1248
1274
  body: method === "GET" ? void 0 : raw
1249
1275
  });
@@ -1523,7 +1549,8 @@ ${dim(`${rows.length} row(s) \xB7 ${Math.round(r.ms)} ms`)}`;
1523
1549
  throw usage(`Unknown dbs subcommand: ${sub}.`, "Try: ghosty dbs ls | tables | query | import | grant | revoke (ghosty dbs --help)");
1524
1550
  }
1525
1551
  }
1526
- async function agentDocsCmd(sub, id, doc, v) {
1552
+ async function agentDocsCmd(sub, id, docs, v) {
1553
+ let doc = docs[0];
1527
1554
  id = need2(id, "agent id", "ghosty agents docs ls <agent-id>");
1528
1555
  switch (sub ?? "ls") {
1529
1556
  case "ls":
@@ -1533,13 +1560,23 @@ async function agentDocsCmd(sub, id, doc, v) {
1533
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."));
1534
1561
  }
1535
1562
  case "grant": {
1536
- doc = need2(doc, "document (id or exact name)", 'ghosty agents docs grant <agent-id> "Cat\xE1logo.pdf"');
1537
- const body = {};
1538
- if (v.write) body.mode = "write";
1539
- if (v["no-deliver"]) body.deliverable = false;
1540
- else if (v.deliver) body.deliverable = true;
1541
- const r = await api(agentPath2(id, `/documents/${enc6(doc)}`), { method: "PUT", body });
1542
- 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"));
1543
1580
  }
1544
1581
  case "add": {
1545
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"');
@@ -1963,6 +2000,57 @@ Archive ${plan.matched} card(s) on \xAB${plan.board}\xBB? (reversible: a new mes
1963
2000
  out(r, () => `${lavender("\u2713")} Archived ${r.archived} card(s) on \xAB${r.board}\xBB.`);
1964
2001
  }
1965
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
+
1966
2054
  // src/help.ts
1967
2055
  var DOCS = "https://www.ghosty.studio/docs/cli";
1968
2056
  var REF = `${DOCS}/comandos`;
@@ -1971,12 +2059,12 @@ var HELPS = {
1971
2059
  summary: "Create, inspect, change, check and delete agents.",
1972
2060
  usage: [
1973
2061
  "ghosty agents ls",
1974
- "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] [--board same|main|new|NAME] [--no-notify] [--dry-run]]",
1975
2063
  "ghosty agents get <agent-id> [--fields name,model,prompt] [--prompt-out FILE]",
1976
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]",
1977
2065
  "ghosty agents set <agent-id> --prompt-base <base-agent-id> --prompt-overlay overlay.json [--dry-run [--out FILE]] | --prompt-base none",
1978
2066
  "ghosty agents doctor <agent-id> [--verbose]",
1979
- "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]",
1980
2068
  "ghosty agents docs add <agent-id> --url URL | --file PATH [--name N] [--description TEXT] [--grant read|write|no] [--no-deliver]",
1981
2069
  "ghosty agents share <agent-id> <email> [--role editor|admin]",
1982
2070
  "ghosty agents unshare <agent-id> <email> [--yes]",
@@ -1989,6 +2077,10 @@ var HELPS = {
1989
2077
  flags: [
1990
2078
  ["--fields a,b", "get: return only these fields"],
1991
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"],
2082
+ ["--board B", "create --like: same (the source's board, default) \xB7 main \xB7 new (a board named like the agent, with the source's columns: one board per number) \xB7 or an existing board"],
2083
+ ["--no-notify", "create --like: share it with the same people WITHOUT emailing them"],
1992
2084
  ["--workspace SLUG", "create: born in that workspace (you own or administer it), owned by its owner and active in its Teams"],
1993
2085
  ["--channels k=on|off,\u2026", "create/set: turn channels on/off: teams, web, whatsapp, messenger, wa (personal WhatsApp)"],
1994
2086
  ["--toolsets LIST", "set: gs tool groups it gets (crm, sales\u2026): a list replaces; +x / -x add or remove"],
@@ -2029,6 +2121,9 @@ var HELPS = {
2029
2121
  "ghosty agents set <id> --toolsets +crm",
2030
2122
  "ghosty agents share <id> ana@empresa.com --role admin",
2031
2123
  "ghosty agents get <id> --prompt-out PROMPT.md",
2124
+ "ghosty agents get <id> --fields toolsets,connectors # what it really has: effective toolsets, connectors connected + granted",
2125
+ "ghosty agents create --name tania-mkt --like <pia-id> --board new --no-notify --dry-run",
2126
+ 'ghosty agents docs grant <id> "Cat\xE1logo.pdf" "Precios.xlsx" \xB7 ghosty agents docs grant <id> --like <other-id>',
2032
2127
  "ghosty agents set <pia-id> --prompt-base <tania-id> --prompt-overlay capa.json --dry-run --out /tmp/pia.md",
2033
2128
  'ghosty agents demo <id> --slug guardias --on --vence "2026-10-15T20:00" --starter "Ens\xE9\xF1ame c\xF3mo empezar"',
2034
2129
  "ghosty agents box <id> --contains /opt/claude-worker/skills/pdf-doc/render-doc.mjs=pdf-templates"
@@ -2041,6 +2136,7 @@ var HELPS = {
2041
2136
  usage: [
2042
2137
  "ghosty skills ls <agent-id>",
2043
2138
  "ghosty skills on|off <agent-id> <slug>",
2139
+ "ghosty skills house <agent-id> [--only a,b | --enable a,b | --disable a,b] [--dry-run]",
2044
2140
  "ghosty skills get <agent-id> <slug> [--out DIR]",
2045
2141
  "ghosty skills templates <agent-id>",
2046
2142
  "ghosty skills add <agent-id> <slug> [--file SKILL.md | --dir FOLDER]",
@@ -2048,6 +2144,8 @@ var HELPS = {
2048
2144
  ],
2049
2145
  flags: [
2050
2146
  ["--out DIR", "get: folder to write into; without it, get only lists the files"],
2147
+ ["--only a,b", "house: leave ONLY these of Ghosty's skills on (the rest off)"],
2148
+ ["--enable / --disable a,b", "house: turn these of Ghosty's skills on / off"],
2051
2149
  ["--file FILE", "add: upload one SKILL.md"],
2052
2150
  ["--dir FOLDER", "add: upload a whole skill folder (SKILL.md + assets); restores a backup"],
2053
2151
  ["--yes", "rm: skip the confirmation (required without a terminal)"]
@@ -2241,6 +2339,26 @@ HELPS.channels = {
2241
2339
  ],
2242
2340
  learn: `${REF}#canales-de-whatsapp`
2243
2341
  };
2342
+ HELPS.boards = {
2343
+ 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.",
2344
+ usage: [
2345
+ "ghosty boards ls <workspace>",
2346
+ 'ghosty boards create <workspace> "Name" [--columns-from main|<board>]',
2347
+ "ghosty boards assign <agent-id> <board|main>",
2348
+ "ghosty boards move-cards <workspace> --from <board> --to <board> [--integration ID | --phone N | --all] [--dry-run] [--yes]",
2349
+ 'ghosty boards rm <workspace> "Name" [--yes]'
2350
+ ],
2351
+ flags: [
2352
+ ["--columns-from B", "create: copy the columns of that board (main, a name or an id) instead of the default ones"],
2353
+ ["--from / --to B", "move-cards: boards by name, id or main"],
2354
+ ["--integration ID | --phone N | --all", "move-cards: which cards (a filter is required)"],
2355
+ ["-n, --dry-run", "move-cards: count and show a sample, change nothing"],
2356
+ ["--yes", "move-cards: skip the confirmation (required without a terminal)"]
2357
+ ],
2358
+ 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'],
2359
+ 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."],
2360
+ learn: `${REF}#tablero`
2361
+ };
2244
2362
  HELPS.board = {
2245
2363
  summary: "The agent's sales board. It belongs to the WORKSPACE: you must be a member, a shared agent is not enough.",
2246
2364
  usage: [
@@ -2332,6 +2450,7 @@ ${bold("Agents")}
2332
2450
  agents ls List your agents
2333
2451
  agents create Create an agent --name [--engine] [--model] [--prompt | --prompt-file FILE] [--env K=V,\u2026]
2334
2452
  [--workspace SLUG] [--channels teams=off,\u2026] born in a workspace, active in its Teams
2453
+ [--like AGENT [--copy-dbs] [--board same|main|new|NAME] [--no-notify] [--dry-run]] with another agent's setup (not its prompt, env or channels)
2335
2454
  agents get <id> Show an agent's config [--fields name,model,prompt] [--prompt-out FILE]
2336
2455
  agents set <id> Change identity or engine [--name] [--engine] [--model] [--prompt | --prompt-file FILE] [--env K=V,\u2026]
2337
2456
  [--connect google-drive,\u2026] [--disconnect \u2026] give/remove access to your connectors
@@ -2341,6 +2460,7 @@ ${bold("Agents")}
2341
2460
  [--prompt-base AGENT|none --prompt-overlay FILE [--dry-run]] prompt = another agent's + edits, kept in sync
2342
2461
  agents doctor <id> Check the config: overlapping skills, tools the prompt names, broken grants, failing turns
2343
2462
  agents docs ls|grant|revoke <id> [doc] Documents it reads and may send [--write] [--no-deliver]
2463
+ agents docs grant <id> A B\u2026 Grant several documents at once [--like AGENT] copies another agent's
2344
2464
  agents docs add <id> Upload a document and grant it --url URL | --file PATH [--description] [--grant read|write|no]
2345
2465
  agents share|unshare <id> <email> Share it without handing it over [--role editor|admin] \xB7 agents shares <id> lists them
2346
2466
  agents rm <id> Delete an agent (owner only; asks you to type its name, or --yes)
@@ -2352,6 +2472,7 @@ ${bold("Agents")}
2352
2472
  ${bold("Configure an agent")}
2353
2473
  files ls|get|put|rm <id> [path] Files in the agent's machine [--file LOCAL] [--out LOCAL]
2354
2474
  skills on|off <id> <slug> Turn one of its skills (or one of Ghosty's) on or off
2475
+ skills house <id> Ghosty's skills in bulk [--only a,b | --enable a,b | --disable a,b] [--dry-run]
2355
2476
  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)
2356
2477
  skills templates <id> pdf-doc templates it sees: its own and Ghosty's, and which folder wins
2357
2478
  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]
@@ -2363,6 +2484,7 @@ ${bold("Configure an agent")}
2363
2484
  channels wa pair <id> --phone N Pair a personal WhatsApp by code (no QR)
2364
2485
  channels wa status|pause <id> Personal WhatsApp: how it is / pause it (keeps the session)
2365
2486
  channels wa groups ls|set|create <id> [group] Personal WhatsApp groups it answers, and as whom [--role equipo|cliente] [--on|--off] [--dry-run]
2487
+ boards ls|create|assign|move-cards|rm A workspace's boards: create (--columns-from), where an agent sends cards, move cards
2366
2488
  board ls <id> Recent cards on its sales board [--q TEXT] [--limit 20]
2367
2489
  board show <id> <card> A card's thread: who wrote, text, attachments and whether gs kept a copy (folio, phone or id)
2368
2490
  board archive <id> Archive cards in bulk (reversible) --integration X | --column X | --before 30d [--dry-run]
@@ -2401,7 +2523,7 @@ ${bold("Global options")}
2401
2523
  ${bold("Exit codes")} 0 ok \xB7 1 API error \xB7 2 usage error \xB7 3 not signed in
2402
2524
  ${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.")}
2403
2525
  ${dim("Docs: https://www.ghosty.studio/docs/cli")}`;
2404
- var COMMANDS = ["login", "logout", "whoami", "usage", "agents", "files", "skills", "mcp", "dbs", "turns", "try", "chat", "conversations", "schedule", "partner", "me", "channels", "board", "credentials", "docs", "help"];
2526
+ var COMMANDS = ["login", "logout", "whoami", "usage", "agents", "files", "skills", "mcp", "dbs", "turns", "try", "chat", "conversations", "schedule", "partner", "me", "channels", "board", "boards", "credentials", "docs", "help"];
2405
2527
  var AGENTS_SUBS = ["ls", "create", "get", "set", "doctor", "docs", "share", "unshare", "shares", "rm", "restart", "demo", "box"];
2406
2528
  function unknown(what, input, known, helpCmd) {
2407
2529
  const s = suggest(input, known);
@@ -2482,6 +2604,16 @@ async function main() {
2482
2604
  verbose: { type: "boolean" },
2483
2605
  full: { type: "boolean" },
2484
2606
  "as-formmy": { type: "boolean" },
2607
+ like: { type: "string" },
2608
+ "copy-dbs": { type: "boolean" },
2609
+ "no-notify": { type: "boolean" },
2610
+ board: { type: "string" },
2611
+ only: { type: "string" },
2612
+ enable: { type: "string" },
2613
+ disable: { type: "string" },
2614
+ "columns-from": { type: "string" },
2615
+ to: { type: "string" },
2616
+ all: { type: "boolean" },
2485
2617
  q: { type: "string" },
2486
2618
  "prompt-out": { type: "string" },
2487
2619
  toolsets: { type: "string" },
@@ -2522,7 +2654,7 @@ async function main() {
2522
2654
  const tokens = splitTokens(pos, v);
2523
2655
  globals.token = tokens.session;
2524
2656
  globals.profile = v.profile;
2525
- if (v.version) return out({ version: "0.15.0" }, () => "0.15.0");
2657
+ if (v.version) return out({ version: "0.16.1" }, () => "0.16.1");
2526
2658
  const [cmd, sub, a1, a2] = pos;
2527
2659
  if (cmd === "partner" && (v.help || !sub || sub === "help")) return out({ help: PARTNER_HELP }, () => PARTNER_HELP);
2528
2660
  const topic = cmd === "help" ? sub : v.help ? cmd : void 0;
@@ -2556,7 +2688,7 @@ async function main() {
2556
2688
  if (sub === "set") return agentsSet(a1, v);
2557
2689
  if (sub === "rm" || sub === "delete") return agentsRm(a1, v);
2558
2690
  if (sub === "doctor") return agentsDoctor(a1, v);
2559
- if (sub === "docs" || sub === "documents") return agentDocsCmd(a1, a2, pos[4], v);
2691
+ if (sub === "docs" || sub === "documents") return agentDocsCmd(a1, a2, pos.slice(4).filter((x) => x !== void 0), v);
2560
2692
  if (sub === "restart") return agentsRestart(a1);
2561
2693
  if (sub === "demo") return agentsDemo(a1, v);
2562
2694
  if (sub === "box") return agentsBox(a1, v);
@@ -2574,6 +2706,8 @@ async function main() {
2574
2706
  return turnsCmd(sub, a1, a2, v);
2575
2707
  case "board":
2576
2708
  return boardCmd(sub, a1, v, a2);
2709
+ case "boards":
2710
+ return boardsCmd(sub, a1, a2, v);
2577
2711
  case "channels":
2578
2712
  if (a1 === "groups") return groupsCmd(pos.slice(3).filter((x) => x !== void 0), v);
2579
2713
  return channelsCmd(sub, a1, a2, pos.slice(4).join(" ") || void 0, v);
@@ -2608,7 +2742,7 @@ async function main() {
2608
2742
  throw unknown(cmd, cmd, COMMANDS, "ghosty --help");
2609
2743
  }
2610
2744
  }
2611
- var notice = updateNotice("0.15.0").catch(() => null);
2745
+ var notice = updateNotice("0.16.1").catch(() => null);
2612
2746
  main().then(
2613
2747
  async () => {
2614
2748
  const n = await notice;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghostystudio/cli",
3
- "version": "0.15.0",
3
+ "version": "0.16.1",
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": {