@ghostystudio/cli 0.12.0 → 0.13.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 +11 -0
  2. package/dist/index.js +140 -13
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -35,6 +35,9 @@ ghosty dbs ls|tables|query|import|grant|revoke <id> [db] [--write] [--arg V]
35
35
  ghosty turns ls <id> [--errors | --status running|done|stopped|error] [--since 1h] [--limit N]
36
36
  ghosty turns show <id> [turn-id|last] # where a turn's time went: each tool with its seconds, output tokens
37
37
  ghosty channels whatsapp ls|enable|disable <id> [number] [--dry-run]
38
+ ghosty channels whatsapp link|unlink <id> --integration ID --answer on|off [--forward] [--dry-run] # bring a WABA number from Formmy
39
+ ghosty channels wa import <id> --from-file F | --from-dir DIR --own-number yes|no [--dry-run] # personal WhatsApp session, no QR
40
+ ghosty channels wa pair|status|pause <id> [--phone N]
38
41
  ghosty channels wa groups ls|set|create <id> [group] [--role equipo|cliente] [--on|--off] [--dry-run] # personal WhatsApp groups
39
42
  ghosty board archive <id> --integration X | --column X | --before 30d [--dry-run]
40
43
  ghosty mcp get|set <id> [--file servers.json]
@@ -76,6 +79,14 @@ Exit codes: `0` ok · `1` API error · `2` usage error · `3` not signed in.
76
79
  - `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.
77
80
  - `agents doctor` speaks English like the rest of the CLI; `channels whatsapp ls` returns a full URL.
78
81
 
82
+ ### Changes in 0.13.1
83
+
84
+ - `channels wa import --from-dir DIR`: import a Baileys multi-file auth folder (`creds.json` + one file per key, e.g. nanoclaw's `store/auth`); it undoes Baileys' file-name escaping per key type.
85
+
86
+ ### Changes in 0.13.0
87
+
88
+ - Migrate WhatsApp numbers without signing up again: `channels whatsapp link|unlink` (a WhatsApp Business number that lives in Formmy; `--answer on|off` is required, `--dry-run` shows what changes in gs and in Formmy) and `channels wa import` (a personal WhatsApp session exported from EasyBits, no QR). Also `channels wa pair --phone` (pairing code), `wa status` and `wa pause`.
89
+
79
90
  ### Changes in 0.12.0
80
91
 
81
92
  - New: `channels wa groups ls|set|create`: which groups of the agent's personal WhatsApp it answers, and as `equipo` or `cliente`. Turning a group on asks first (`--yes` without a terminal); `--dry-run` shows the plan.
package/dist/index.js CHANGED
@@ -282,7 +282,7 @@ async function postLong(path, body, timeoutMs = 9e5) {
282
282
  const req = httpsRequest(`${base}${path}`, {
283
283
  method: "POST",
284
284
  timeout: timeoutMs,
285
- headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.12.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
285
+ headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.13.1"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
286
286
  }, (res) => {
287
287
  let buf = "";
288
288
  res.on("data", (c) => buf += c);
@@ -324,7 +324,7 @@ async function request(path, opts = {}, retried = false) {
324
324
  method: opts.method ?? (opts.body !== void 0 || opts.raw !== void 0 ? "POST" : "GET"),
325
325
  headers: {
326
326
  authorization: `Bearer ${token}`,
327
- "user-agent": `ghosty-cli/${"0.12.0"}`,
327
+ "user-agent": `ghosty-cli/${"0.13.1"}`,
328
328
  ...opts.body !== void 0 ? { "content-type": "application/json" } : {},
329
329
  ...opts.headers
330
330
  },
@@ -1136,7 +1136,7 @@ async function call(path, v, init = {}) {
1136
1136
  "x-ghosty-ts": ts,
1137
1137
  "x-ghosty-sig": sig,
1138
1138
  "content-type": "application/json",
1139
- "user-agent": `ghosty-cli/${"0.12.0"}`
1139
+ "user-agent": `ghosty-cli/${"0.13.1"}`
1140
1140
  },
1141
1141
  body: method === "GET" ? void 0 : raw
1142
1142
  });
@@ -1573,6 +1573,42 @@ async function credentialsCmd2(sub, provider, v) {
1573
1573
  }
1574
1574
  }
1575
1575
 
1576
+ // src/commands/channels.ts
1577
+ import { readFile as readFile7 } from "node:fs/promises";
1578
+
1579
+ // src/wa-auth.ts
1580
+ import { readdir as readdir2, readFile as readFile6 } from "node:fs/promises";
1581
+ import { join as join4 } from "node:path";
1582
+ var TYPES = ["app-state-sync-version", "app-state-sync-key", "sender-key-memory", "sender-key", "lid-mapping", "device-list", "identity-key", "pre-key", "session", "tctoken"];
1583
+ function keyOfFileName(name) {
1584
+ for (const type of TYPES) {
1585
+ if (!name.startsWith(`${type}-`)) continue;
1586
+ let id = name.slice(type.length + 1).replace(/__/g, "/");
1587
+ if (type === "sender-key" || type === "sender-key-memory") id = id.replace(/--/g, "::");
1588
+ return id ? { type, id } : null;
1589
+ }
1590
+ return null;
1591
+ }
1592
+ async function readMultiFileAuth(dir) {
1593
+ const files = (await readdir2(dir)).filter((f) => f.endsWith(".json"));
1594
+ if (!files.includes("creds.json")) throw new Error(`${dir} has no creds.json: not a Baileys multi-file auth folder`);
1595
+ const authCreds = JSON.parse(await readFile6(join4(dir, "creds.json"), "utf8"));
1596
+ const authKeys = {};
1597
+ const counts = {};
1598
+ const skipped = [];
1599
+ for (const f of files) {
1600
+ if (f === "creds.json") continue;
1601
+ const k = keyOfFileName(f.slice(0, -".json".length));
1602
+ if (!k) {
1603
+ skipped.push(f);
1604
+ continue;
1605
+ }
1606
+ (authKeys[k.type] ??= {})[k.id] = JSON.parse(await readFile6(join4(dir, f), "utf8"));
1607
+ counts[k.type] = (counts[k.type] ?? 0) + 1;
1608
+ }
1609
+ return { authCreds, authKeys, counts, skipped };
1610
+ }
1611
+
1576
1612
  // src/commands/channels.ts
1577
1613
  var enc7 = encodeURIComponent;
1578
1614
  var digits = (s) => s.replace(/\D/g, "");
@@ -1585,6 +1621,8 @@ function find(nums, ref) {
1585
1621
  async function channelsCmd(kind, sub, id, ref, v) {
1586
1622
  if (kind !== "whatsapp" && kind !== "wa") throw usage(`Unknown channel: ${kind ?? "(none)"}.`, "Try: ghosty channels whatsapp ls <agent-id>");
1587
1623
  if (!id) throw usage("Missing agent id.", "Example: ghosty channels whatsapp ls <agent-id>");
1624
+ if (sub === "link" || sub === "unlink") return linkCmd(sub, id, v);
1625
+ if (sub === "import" || sub === "status" || sub === "pause" || sub === "pair") return waCmd(sub, id, v);
1588
1626
  if (sub === "enable" && !v["dry-run"]) requireYesIfHeadless(v.yes, `ghosty channels whatsapp enable ${id} ${ref ?? "<number>"}`);
1589
1627
  const path = `/api/v2/agents/${enc7(id)}/whatsapp`;
1590
1628
  const cur = await api(path);
@@ -1592,7 +1630,7 @@ async function channelsCmd(kind, sub, id, ref, v) {
1592
1630
  const rows = cur.numbers.map((n2) => ({ number: n2.label, answers: n2.mine ? lavender("this agent") : n2.takenByOther ? "another agent" : dim("nobody (silent)"), id: n2.id }));
1593
1631
  return out(cur, () => rows.length ? table(rows, ["number", "answers", "id"]) : dim(`No WhatsApp Business numbers yet. Pair one at ${cur.salesUrl}`));
1594
1632
  }
1595
- if (sub !== "enable" && sub !== "disable") throw usage(`Unknown subcommand: ${sub}.`, "Try: ghosty channels whatsapp ls | enable | disable <agent-id> <number>");
1633
+ if (sub !== "enable" && sub !== "disable") throw usage(`Unknown subcommand: ${sub}.`, "Try: ghosty channels whatsapp ls | enable | disable | link | unlink \xB7 ghosty channels wa import | status | pause | groups");
1596
1634
  if (!ref) throw usage("Missing number.", `Example: ghosty channels whatsapp ${sub} ${id} +52 55 1234 5678`);
1597
1635
  const n = find(cur.numbers, ref);
1598
1636
  const on = sub === "enable";
@@ -1651,6 +1689,66 @@ The agent will start answering in this group as ${role}.`, { yes: v.yes, retry:
1651
1689
  const now = r.groups.find((x) => x.jid === g.jid);
1652
1690
  return out({ group: now ?? null, plan: [plan] }, () => `${lavender("\u2713")} ${g.name}: ${now?.enabled ? "on" : "off"} as ${now?.role ?? role}`);
1653
1691
  }
1692
+ async function waCmd(sub, id, v) {
1693
+ if (!id) throw usage("Missing agent id.", `Example: ghosty channels wa ${sub} <agent-id>`);
1694
+ const base = `/api/v2/agents/${enc7(id)}/wa`;
1695
+ if (sub === "status") {
1696
+ const s = await api(base);
1697
+ return out(s, () => `${s.status === "connected" ? lavender("\u25CF connected") : dim(`\u25CB ${s.status}`)} ${s.phone ? `+${s.phone}` : dim("(no number)")}${s.live ? "" : dim(" \xB7 no socket in gs")}${s.hasCreds ? "" : dim(" \xB7 no saved session")}${s.reason ? `
1698
+ ${dim(s.reason)}` : ""}`);
1699
+ }
1700
+ if (sub === "pair") {
1701
+ const phone = (v.phone ?? "").replace(/\D/g, "");
1702
+ if (phone.length < 10) throw usage("Missing --phone (10-15 digits, with country code).", `Example: ghosty channels wa pair ${id} --phone 5215512345678`);
1703
+ await api(base, { method: "POST", body: { phone } });
1704
+ let s = { status: "connecting", phone: null, live: false, hasCreds: false };
1705
+ for (let i = 0; i < 15 && !s.pairingCode && s.status !== "connected"; i++) {
1706
+ await new Promise((r2) => setTimeout(r2, 1e3));
1707
+ s = await api(base);
1708
+ }
1709
+ return out(s, () => s.status === "connected" ? `${lavender("\u2713")} Already connected (+${s.phone}).` : s.pairingCode ? `Code: ${bold(s.pairingCode)}
1710
+ ${dim("On the phone: WhatsApp \u2192 Linked devices \u2192 Link with phone number \u2192 type it. Then: ghosty channels wa status " + id)}` : dim(`No code yet (status: ${s.status}${s.reason ? `, ${s.reason}` : ""}). Try again in a minute.`));
1711
+ }
1712
+ if (sub === "pause") {
1713
+ await api(base, { method: "DELETE" });
1714
+ return out({ paused: true }, () => `${lavender("\u2713")} Paused. The session is kept: ${dim(`ghosty channels wa import \u2026 or the Channels page reconnects it`)}`);
1715
+ }
1716
+ const file = v["from-file"] ?? v["from-dir"];
1717
+ if (!file || v["from-file"] && v["from-dir"]) throw usage("Use --from-file F (EasyBits JSON) or --from-dir DIR (Baileys multi-file folder).", `Example: ghosty channels wa import ${id} --from-file creds.json --own-number no --dry-run`);
1718
+ const own = v["own-number"];
1719
+ if (own !== "yes" && own !== "no") throw usage("Missing --own-number yes|no.", "yes = the number belongs to the agent \xB7 no = it is a person's number (only their chat with themselves is the owner's)");
1720
+ let data;
1721
+ if (v["from-dir"]) {
1722
+ const m = await readMultiFileAuth(v["from-dir"]);
1723
+ info(dim(`${v["from-dir"]}: ${Object.entries(m.counts).map(([t, n]) => `${t} ${n}`).join(" \xB7 ") || "no keys"}${m.skipped.length ? ` \xB7 skipped ${m.skipped.length} unknown file(s)` : ""}`));
1724
+ data = { authCreds: m.authCreds, authKeys: m.authKeys };
1725
+ } else data = JSON.parse(await readFile7(file, "utf8"));
1726
+ const body = { authCreds: data.authCreds, authKeys: data.authKeys, phone: data.phone, groups: data.groups, ownNumber: own === "yes", replace: !!v.replace };
1727
+ const plan = await api(`${base}/import`, { method: "POST", body: { ...body, dryRun: true } });
1728
+ if (v["dry-run"]) return out(plan, () => `${plan.plan.join("\n")}
1729
+ ${dim("Dry run: nothing changed.")}`);
1730
+ await confirm(`${bold(plan.plan.join("\n"))}
1731
+ This number starts living in gs. Turn off its EasyBits socket first, WITHOUT logout (two sockets knock each other off).`, { yes: v.yes, expect: plan.phone.slice(-4), retry: `ghosty channels wa import ${id} ${v["from-dir"] ? "--from-dir" : "--from-file"} ${file} --own-number ${own}${v.replace ? " --replace" : ""}` });
1732
+ const r = await api(`${base}/import`, { method: "POST", body: { ...body, connect: !v["no-connect"] } });
1733
+ return out(r, () => `${lavender("\u2713")} +${r.phone} imported \xB7 status: ${r.status === "connected" ? lavender("connected") : r.status}${r.reason ? dim(` (${r.reason})`) : ""}
1734
+ ${dim(`Next: ghosty channels wa groups ls ${id} \xB7 delete ${file}: it holds the account's keys`)}`);
1735
+ }
1736
+ async function linkCmd(sub, id, v) {
1737
+ if (!id || !v.integration) throw usage("Missing agent id or --integration.", `Example: ghosty channels whatsapp ${sub} <agent-id> --integration <formmy-integration-id> --answer off --dry-run`);
1738
+ if (sub === "link" && v.answer !== "on" && v.answer !== "off") throw usage("Missing --answer on|off.", "on = this agent answers real customers \xB7 off = it only records (add --forward so gs still sees the conversations)");
1739
+ const path = `/api/v2/agents/${enc7(id)}/whatsapp/link`;
1740
+ const body = { integrationId: v.integration, ...sub === "unlink" ? { unlink: true } : { answer: v.answer === "on", ...v.forward ? { forward: true } : {} } };
1741
+ const plan = await api(path, { method: "POST", body: { ...body, dryRun: true } });
1742
+ const text = `${plan.plan.join("\n")}
1743
+ ${dim(`Formmy: ${plan.formmy.before.url ?? "null"} \u2192 ${plan.formmy.after.url ?? "null"} (as ${plan.formmy.by})`)}`;
1744
+ if (v["dry-run"]) return out(plan, () => `${text}
1745
+ ${dim("Dry run: nothing changed.")}`);
1746
+ const digits4 = (plan.number.phoneNumberId ?? v.integration).slice(-4);
1747
+ await confirm(`${bold(text)}
1748
+ ${sub === "link" ? "From now on this number's messages reach this agent in gs." : "This number goes back to where it was before."}`, { yes: v.yes, expect: digits4, retry: `ghosty channels whatsapp ${sub} ${id} --integration ${v.integration}${sub === "link" ? ` --answer ${v.answer}${v.forward ? " --forward" : ""}` : ""}` });
1749
+ const r = await api(path, { method: "POST", body });
1750
+ return out(r, () => `${lavender("\u2713")} ${sub === "link" ? `Linked (link ${r.linkId}). Check: ghosty channels whatsapp ls ${id} \xB7 ghosty turns ls ${id} --since 10m` : "Unlinked: Formmy points where it did before."}`);
1751
+ }
1654
1752
 
1655
1753
  // src/commands/board.ts
1656
1754
  async function boardCmd(sub, id, v) {
@@ -1881,6 +1979,11 @@ HELPS.channels = {
1881
1979
  "ghosty channels whatsapp ls <agent-id>",
1882
1980
  "ghosty channels whatsapp enable <agent-id> <number> [--dry-run] [--yes]",
1883
1981
  "ghosty channels whatsapp disable <agent-id> <number> [--dry-run]",
1982
+ "ghosty channels whatsapp link <agent-id> --integration <formmy-id> --answer on|off [--forward] [--dry-run] [--yes]",
1983
+ "ghosty channels whatsapp unlink <agent-id> --integration <formmy-id> [--dry-run] [--yes]",
1984
+ "ghosty channels wa import <agent-id> --from-file creds.json | --from-dir auth/ --own-number yes|no [--replace] [--no-connect] [--dry-run] [--yes]",
1985
+ "ghosty channels wa pair <agent-id> --phone 5215512345678",
1986
+ "ghosty channels wa status|pause <agent-id>",
1884
1987
  "ghosty channels wa groups ls <agent-id> [--cached]",
1885
1988
  "ghosty channels wa groups set <agent-id> <group> [--role equipo|cliente] [--on | --off] [--dry-run] [--yes]",
1886
1989
  'ghosty channels wa groups create <agent-id> "Name" [--role equipo|cliente]'
@@ -1890,19 +1993,31 @@ HELPS.channels = {
1890
1993
  ["--yes", "enable / groups set --on: skip the confirmation (required without a terminal)"],
1891
1994
  ["--role R", "groups: equipo (the team: writes to databases, admin tools) or cliente (like any customer)"],
1892
1995
  ["--on | --off", "groups set: the agent answers in that group, or stops"],
1893
- ["--cached", "groups ls: don't ask WhatsApp for the live list (faster; may miss new groups)"]
1996
+ ["--cached", "groups ls: don't ask WhatsApp for the live list (faster; may miss new groups)"],
1997
+ ["--integration ID", "link/unlink: the WhatsApp Business number's Integration id in Formmy"],
1998
+ ["--answer on|off", "link (required, no default): on = this agent answers real customers; off = it only records"],
1999
+ ["--forward", "link --answer off: still make Formmy forward, so gs sees the conversations"],
2000
+ ["--from-file F", "import: {authCreds, authKeys, phone, groups} exported from EasyBits"],
2001
+ ["--from-dir DIR", "import: a Baileys multi-file auth folder (creds.json + one file per key; e.g. nanoclaw's store/auth)"],
2002
+ ["--own-number yes|no", "import (required): the number is the agent's, or a person's (only their self-chat is the owner's)"],
2003
+ ["--replace", "import: replace a session already saved in gs"],
2004
+ ["--no-connect", "import: save it but don't connect yet"]
1894
2005
  ],
1895
2006
  examples: [
1896
2007
  "ghosty channels whatsapp ls <id>",
1897
2008
  "ghosty channels whatsapp enable <id> 5512345678 --dry-run",
1898
2009
  "ghosty channels whatsapp disable <id> 5512345678",
1899
2010
  "ghosty channels wa groups ls <id>",
1900
- 'ghosty channels wa groups set <id> "Pia-cotizadora" --role equipo --on --dry-run'
2011
+ 'ghosty channels wa groups set <id> "Pia-cotizadora" --role equipo --on --dry-run',
2012
+ "ghosty channels whatsapp link <id> --integration 6a3af395\u2026 --answer off --dry-run",
2013
+ "ghosty channels wa import <id> --from-file pia-wa.json --own-number no --dry-run"
1901
2014
  ],
1902
2015
  notes: [
1903
2016
  "<number> is its id, its label or its last digits. One number, one agent: enabling it takes it from the other agent.",
1904
2017
  "Disabling keeps logging messages; nobody answers them.",
1905
- "Groups belong to the PERSONAL WhatsApp paired to the agent (not WhatsApp Business). <group> is its name or its jid."
2018
+ "Groups belong to the PERSONAL WhatsApp paired to the agent (not WhatsApp Business). <group> is its name or its jid.",
2019
+ "link moves a number that already lives in Formmy (EasyBits, a droplet) without redoing Meta's signup; unlink sends it back. Only the owner, an admin of the agent or Ghosty staff.",
2020
+ "import: turn off the EasyBits socket first WITHOUT logout (two sockets knock each other off; a logout invalidates the keys). Delete the file afterwards."
1906
2021
  ],
1907
2022
  learn: `${REF}#canales-de-whatsapp`
1908
2023
  };
@@ -1941,10 +2056,10 @@ function helpText(cmd) {
1941
2056
  }
1942
2057
 
1943
2058
  // src/update.ts
1944
- import { mkdir as mkdir4, readFile as readFile6, writeFile as writeFile4 } from "node:fs/promises";
2059
+ import { mkdir as mkdir4, readFile as readFile8, writeFile as writeFile4 } from "node:fs/promises";
1945
2060
  import { homedir as homedir3 } from "node:os";
1946
- import { dirname as dirname3, join as join4 } from "node:path";
1947
- var FILE = join4(process.env.XDG_CONFIG_HOME || join4(homedir3(), ".config"), "ghosty", "update.json");
2061
+ import { dirname as dirname3, join as join5 } from "node:path";
2062
+ var FILE = join5(process.env.XDG_CONFIG_HOME || join5(homedir3(), ".config"), "ghosty", "update.json");
1948
2063
  var DAY = 24 * 60 * 60 * 1e3;
1949
2064
  var UPGRADE = "npm i -g @ghostystudio/cli@latest";
1950
2065
  function newer(a, b) {
@@ -1957,7 +2072,7 @@ async function latestVersion() {
1957
2072
  if (process.env.GHOSTY_NO_UPDATE_CHECK || process.env.CI) return null;
1958
2073
  let cache = {};
1959
2074
  try {
1960
- cache = JSON.parse(await readFile6(FILE, "utf8"));
2075
+ cache = JSON.parse(await readFile8(FILE, "utf8"));
1961
2076
  } catch {
1962
2077
  }
1963
2078
  if (!cache.checkedAt || Date.now() - cache.checkedAt > DAY) {
@@ -2009,6 +2124,10 @@ ${bold("Configure an agent")}
2009
2124
  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]
2010
2125
  mcp get|set <id> MCP servers [--file servers.json]
2011
2126
  channels whatsapp ls|enable|disable <id> [number] Which WhatsApp Business numbers it answers [--dry-run]
2127
+ channels whatsapp link|unlink <id> --integration ID --answer on|off Bring a WhatsApp Business number from Formmy/EasyBits to this agent, or send it back [--forward] [--dry-run]
2128
+ channels wa import <id> --from-file F | --from-dir DIR --own-number yes|no Move a personal WhatsApp session here, no QR [--dry-run] [--replace] [--no-connect]
2129
+ channels wa pair <id> --phone N Pair a personal WhatsApp by code (no QR)
2130
+ channels wa status|pause <id> Personal WhatsApp: how it is / pause it (keeps the session)
2012
2131
  channels wa groups ls|set|create <id> [group] Personal WhatsApp groups it answers, and as whom [--role equipo|cliente] [--on|--off] [--dry-run]
2013
2132
  board archive <id> Archive cards in bulk (reversible) --integration X | --column X | --before 30d [--dry-run]
2014
2133
 
@@ -2138,6 +2257,14 @@ async function main() {
2138
2257
  before: { type: "string" },
2139
2258
  recycle: { type: "boolean" },
2140
2259
  role: { type: "string" },
2260
+ "from-file": { type: "string" },
2261
+ "from-dir": { type: "string" },
2262
+ "own-number": { type: "string" },
2263
+ answer: { type: "string" },
2264
+ phone: { type: "string" },
2265
+ forward: { type: "boolean" },
2266
+ replace: { type: "boolean" },
2267
+ "no-connect": { type: "boolean" },
2141
2268
  cached: { type: "boolean" },
2142
2269
  check: { type: "string", multiple: true },
2143
2270
  contains: { type: "string", multiple: true }
@@ -2147,7 +2274,7 @@ async function main() {
2147
2274
  const tokens = splitTokens(pos, v);
2148
2275
  globals.token = tokens.session;
2149
2276
  globals.profile = v.profile;
2150
- if (v.version) return out({ version: "0.12.0" }, () => "0.12.0");
2277
+ if (v.version) return out({ version: "0.13.1" }, () => "0.13.1");
2151
2278
  const [cmd, sub, a1, a2] = pos;
2152
2279
  if (cmd === "partner" && (v.help || !sub || sub === "help")) return out({ help: PARTNER_HELP }, () => PARTNER_HELP);
2153
2280
  const topic = cmd === "help" ? sub : v.help ? cmd : void 0;
@@ -2232,7 +2359,7 @@ async function main() {
2232
2359
  throw unknown(cmd, cmd, COMMANDS, "ghosty --help");
2233
2360
  }
2234
2361
  }
2235
- var notice = updateNotice("0.12.0").catch(() => null);
2362
+ var notice = updateNotice("0.13.1").catch(() => null);
2236
2363
  main().then(
2237
2364
  async () => {
2238
2365
  const n = await notice;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghostystudio/cli",
3
- "version": "0.12.0",
3
+ "version": "0.13.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": {
@@ -16,7 +16,7 @@
16
16
  "scripts": {
17
17
  "build": "esbuild src/index.ts --bundle --platform=node --format=esm --target=node22 --outfile=dist/index.js --banner:js='#!/usr/bin/env node' --define:__VERSION__=\\\"$npm_package_version\\\"",
18
18
  "prepack": "npm run build",
19
- "test": "for t in flags prompt api rows; do esbuild src/$t.test.ts --bundle --platform=node --format=esm --outfile=/tmp/ghosty-cli-$t.test.mjs --log-level=error || exit 1; done && node --test /tmp/ghosty-cli-*.test.mjs"
19
+ "test": "for t in flags prompt api rows wa-auth; do esbuild src/$t.test.ts --bundle --platform=node --format=esm --outfile=/tmp/ghosty-cli-$t.test.mjs --log-level=error || exit 1; done && node --test /tmp/ghosty-cli-*.test.mjs"
20
20
  },
21
21
  "keywords": [
22
22
  "ghosty",