@ghostystudio/cli 0.11.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/dist/index.js +167 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -35,6 +35,10 @@ 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 --own-number yes|no [--dry-run] # personal WhatsApp session, no QR
|
|
40
|
+
ghosty channels wa pair|status|pause <id> [--phone N]
|
|
41
|
+
ghosty channels wa groups ls|set|create <id> [group] [--role equipo|cliente] [--on|--off] [--dry-run] # personal WhatsApp groups
|
|
38
42
|
ghosty board archive <id> --integration X | --column X | --before 30d [--dry-run]
|
|
39
43
|
ghosty mcp get|set <id> [--file servers.json]
|
|
40
44
|
|
|
@@ -75,6 +79,14 @@ Exit codes: `0` ok · `1` API error · `2` usage error · `3` not signed in.
|
|
|
75
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.
|
|
76
80
|
- `agents doctor` speaks English like the rest of the CLI; `channels whatsapp ls` returns a full URL.
|
|
77
81
|
|
|
82
|
+
### Changes in 0.13.0
|
|
83
|
+
|
|
84
|
+
- 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`.
|
|
85
|
+
|
|
86
|
+
### Changes in 0.12.0
|
|
87
|
+
|
|
88
|
+
- 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.
|
|
89
|
+
|
|
78
90
|
### Changes in 0.11.0
|
|
79
91
|
|
|
80
92
|
- New: `conversations show` (what was said in a conversation; `--limit 0` for all).
|
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.
|
|
285
|
+
headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.13.0"}`, "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.
|
|
327
|
+
"user-agent": `ghosty-cli/${"0.13.0"}`,
|
|
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.
|
|
1139
|
+
"user-agent": `ghosty-cli/${"0.13.0"}`
|
|
1140
1140
|
},
|
|
1141
1141
|
body: method === "GET" ? void 0 : raw
|
|
1142
1142
|
});
|
|
@@ -1574,6 +1574,7 @@ async function credentialsCmd2(sub, provider, v) {
|
|
|
1574
1574
|
}
|
|
1575
1575
|
|
|
1576
1576
|
// src/commands/channels.ts
|
|
1577
|
+
import { readFile as readFile6 } from "node:fs/promises";
|
|
1577
1578
|
var enc7 = encodeURIComponent;
|
|
1578
1579
|
var digits = (s) => s.replace(/\D/g, "");
|
|
1579
1580
|
function find(nums, ref) {
|
|
@@ -1585,6 +1586,8 @@ function find(nums, ref) {
|
|
|
1585
1586
|
async function channelsCmd(kind, sub, id, ref, v) {
|
|
1586
1587
|
if (kind !== "whatsapp" && kind !== "wa") throw usage(`Unknown channel: ${kind ?? "(none)"}.`, "Try: ghosty channels whatsapp ls <agent-id>");
|
|
1587
1588
|
if (!id) throw usage("Missing agent id.", "Example: ghosty channels whatsapp ls <agent-id>");
|
|
1589
|
+
if (sub === "link" || sub === "unlink") return linkCmd(sub, id, v);
|
|
1590
|
+
if (sub === "import" || sub === "status" || sub === "pause" || sub === "pair") return waCmd(sub, id, v);
|
|
1588
1591
|
if (sub === "enable" && !v["dry-run"]) requireYesIfHeadless(v.yes, `ghosty channels whatsapp enable ${id} ${ref ?? "<number>"}`);
|
|
1589
1592
|
const path = `/api/v2/agents/${enc7(id)}/whatsapp`;
|
|
1590
1593
|
const cur = await api(path);
|
|
@@ -1592,7 +1595,7 @@ async function channelsCmd(kind, sub, id, ref, v) {
|
|
|
1592
1595
|
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
1596
|
return out(cur, () => rows.length ? table(rows, ["number", "answers", "id"]) : dim(`No WhatsApp Business numbers yet. Pair one at ${cur.salesUrl}`));
|
|
1594
1597
|
}
|
|
1595
|
-
if (sub !== "enable" && sub !== "disable") throw usage(`Unknown subcommand: ${sub}.`, "Try: ghosty channels whatsapp ls | enable | disable
|
|
1598
|
+
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
1599
|
if (!ref) throw usage("Missing number.", `Example: ghosty channels whatsapp ${sub} ${id} +52 55 1234 5678`);
|
|
1597
1600
|
const n = find(cur.numbers, ref);
|
|
1598
1601
|
const on = sub === "enable";
|
|
@@ -1606,6 +1609,106 @@ Real customers will get answers from this agent.`, { yes: v.yes, expect: digits(
|
|
|
1606
1609
|
const r = await api(path, { method: "PUT", body: { numbers: wanted } });
|
|
1607
1610
|
return out(r, () => `${lavender("\u2713")} ${plan.slice(2)}`);
|
|
1608
1611
|
}
|
|
1612
|
+
function findGroup(groups, ref) {
|
|
1613
|
+
const byJid = groups.find((g) => g.jid === ref);
|
|
1614
|
+
if (byJid) return byJid;
|
|
1615
|
+
const byName = groups.filter((g) => g.name.toLowerCase() === ref.toLowerCase());
|
|
1616
|
+
if (byName.length === 1) return byName[0];
|
|
1617
|
+
if (byName.length > 1) throw usage(`${byName.length} groups are called "${ref}".`, `Use its jid: ${byName.map((g) => g.jid).join(", ")}`);
|
|
1618
|
+
throw usage(`No group "${ref}" for this agent.`, groups.length ? `Groups: ${groups.map((g) => g.name).join(", ")}` : "Is its personal WhatsApp paired and in some group? See the agent's Channels page.");
|
|
1619
|
+
}
|
|
1620
|
+
async function groupsCmd(args, v) {
|
|
1621
|
+
const [sub, id, ...rest] = args;
|
|
1622
|
+
const ex = 'ghosty channels wa groups ls <agent-id> | set <agent-id> <group> [--role equipo|cliente] [--on|--off] | create <agent-id> "Name"';
|
|
1623
|
+
if (!id) throw usage("Missing agent id.", `Example: ${ex}`);
|
|
1624
|
+
const path = `/api/v2/agents/${enc7(id)}/wa/groups`;
|
|
1625
|
+
if (v.role !== void 0 && v.role !== "equipo" && v.role !== "cliente") throw usage("--role takes equipo or cliente.");
|
|
1626
|
+
if (!sub || sub === "ls" || sub === "list") {
|
|
1627
|
+
const r2 = await api(`${path}${v.cached ? "?live=0" : ""}`);
|
|
1628
|
+
const rows = r2.groups.map((g2) => ({ name: g2.name, on: g2.enabled ? lavender("on") : dim("off"), role: g2.role, jid: g2.jid }));
|
|
1629
|
+
return out(r2, () => rows.length ? table(rows, ["name", "on", "role", "jid"]) : dim('No groups. Pair its personal WhatsApp in the web, or: ghosty channels wa groups create <agent-id> "Name"'));
|
|
1630
|
+
}
|
|
1631
|
+
if (sub === "create") {
|
|
1632
|
+
const name = rest.join(" ").trim();
|
|
1633
|
+
if (!name) throw usage("Missing group name.", `Example: ghosty channels wa groups create ${id} "Equipo ventas" --role equipo`);
|
|
1634
|
+
const r2 = await api(path, { method: "POST", body: { create: name, role: v.role ?? "equipo" } });
|
|
1635
|
+
return out(r2, () => `${lavender("\u2713")} Created ${bold(name)} (${v.role ?? "equipo"}, on)
|
|
1636
|
+
invite: ${r2.inviteUrl}`);
|
|
1637
|
+
}
|
|
1638
|
+
if (sub !== "set") throw usage(`Unknown groups subcommand: ${sub}.`, ex);
|
|
1639
|
+
const ref = rest.join(" ").trim();
|
|
1640
|
+
if (!ref) throw usage("Missing group (name or jid).", `Example: ghosty channels wa groups set ${id} "Pia-cotizadora" --role equipo --on`);
|
|
1641
|
+
if (v.on && v.off) throw usage("Use --on or --off, not both.");
|
|
1642
|
+
const cur = await api(`${path}?live=0`);
|
|
1643
|
+
const g = findGroup(cur.groups, ref);
|
|
1644
|
+
const enabled = v.on ? true : v.off ? false : g.enabled;
|
|
1645
|
+
const role = v.role ?? g.role;
|
|
1646
|
+
const changes = [...enabled !== g.enabled ? [`${g.enabled ? "on" : "off"}\u2192${enabled ? "on" : "off"}`] : [], ...role !== g.role ? [`${g.role}\u2192${role}`] : []];
|
|
1647
|
+
if (!changes.length) return out({ changed: 0, group: g }, () => dim(`Nothing to do: ${g.name} is already ${g.enabled ? "on" : "off"} as ${g.role}.`));
|
|
1648
|
+
const plan = `~ ${g.name}: ${changes.join(", ")}`;
|
|
1649
|
+
if (v["dry-run"]) return out({ dryRun: true, plan: [plan] }, () => `${plan}
|
|
1650
|
+
${dim("Dry run: nothing changed.")}`);
|
|
1651
|
+
if (enabled && !g.enabled) await confirm(`${bold(plan)}
|
|
1652
|
+
The agent will start answering in this group as ${role}.`, { yes: v.yes, retry: `ghosty channels wa groups set ${id} "${ref}"${v.role ? ` --role ${v.role}` : ""} --on` });
|
|
1653
|
+
const r = await api(path, { method: "POST", body: { jid: g.jid, enabled, role } });
|
|
1654
|
+
const now = r.groups.find((x) => x.jid === g.jid);
|
|
1655
|
+
return out({ group: now ?? null, plan: [plan] }, () => `${lavender("\u2713")} ${g.name}: ${now?.enabled ? "on" : "off"} as ${now?.role ?? role}`);
|
|
1656
|
+
}
|
|
1657
|
+
async function waCmd(sub, id, v) {
|
|
1658
|
+
if (!id) throw usage("Missing agent id.", `Example: ghosty channels wa ${sub} <agent-id>`);
|
|
1659
|
+
const base = `/api/v2/agents/${enc7(id)}/wa`;
|
|
1660
|
+
if (sub === "status") {
|
|
1661
|
+
const s = await api(base);
|
|
1662
|
+
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 ? `
|
|
1663
|
+
${dim(s.reason)}` : ""}`);
|
|
1664
|
+
}
|
|
1665
|
+
if (sub === "pair") {
|
|
1666
|
+
const phone = (v.phone ?? "").replace(/\D/g, "");
|
|
1667
|
+
if (phone.length < 10) throw usage("Missing --phone (10-15 digits, with country code).", `Example: ghosty channels wa pair ${id} --phone 5215512345678`);
|
|
1668
|
+
await api(base, { method: "POST", body: { phone } });
|
|
1669
|
+
let s = { status: "connecting", phone: null, live: false, hasCreds: false };
|
|
1670
|
+
for (let i = 0; i < 15 && !s.pairingCode && s.status !== "connected"; i++) {
|
|
1671
|
+
await new Promise((r2) => setTimeout(r2, 1e3));
|
|
1672
|
+
s = await api(base);
|
|
1673
|
+
}
|
|
1674
|
+
return out(s, () => s.status === "connected" ? `${lavender("\u2713")} Already connected (+${s.phone}).` : s.pairingCode ? `Code: ${bold(s.pairingCode)}
|
|
1675
|
+
${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.`));
|
|
1676
|
+
}
|
|
1677
|
+
if (sub === "pause") {
|
|
1678
|
+
await api(base, { method: "DELETE" });
|
|
1679
|
+
return out({ paused: true }, () => `${lavender("\u2713")} Paused. The session is kept: ${dim(`ghosty channels wa import \u2026 or the Channels page reconnects it`)}`);
|
|
1680
|
+
}
|
|
1681
|
+
const file = v["from-file"];
|
|
1682
|
+
if (!file) throw usage("Missing --from-file.", `Example: ghosty channels wa import ${id} --from-file creds.json --own-number no --dry-run`);
|
|
1683
|
+
const own = v["own-number"];
|
|
1684
|
+
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)");
|
|
1685
|
+
const data = JSON.parse(await readFile6(file, "utf8"));
|
|
1686
|
+
const body = { authCreds: data.authCreds, authKeys: data.authKeys, phone: data.phone, groups: data.groups, ownNumber: own === "yes", replace: !!v.replace };
|
|
1687
|
+
const plan = await api(`${base}/import`, { method: "POST", body: { ...body, dryRun: true } });
|
|
1688
|
+
if (v["dry-run"]) return out(plan, () => `${plan.plan.join("\n")}
|
|
1689
|
+
${dim("Dry run: nothing changed.")}`);
|
|
1690
|
+
await confirm(`${bold(plan.plan.join("\n"))}
|
|
1691
|
+
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} --from-file ${file} --own-number ${own}${v.replace ? " --replace" : ""}` });
|
|
1692
|
+
const r = await api(`${base}/import`, { method: "POST", body: { ...body, connect: !v["no-connect"] } });
|
|
1693
|
+
return out(r, () => `${lavender("\u2713")} +${r.phone} imported \xB7 status: ${r.status === "connected" ? lavender("connected") : r.status}${r.reason ? dim(` (${r.reason})`) : ""}
|
|
1694
|
+
${dim(`Next: ghosty channels wa groups ls ${id} \xB7 delete ${file}: it holds the account's keys`)}`);
|
|
1695
|
+
}
|
|
1696
|
+
async function linkCmd(sub, id, v) {
|
|
1697
|
+
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`);
|
|
1698
|
+
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)");
|
|
1699
|
+
const path = `/api/v2/agents/${enc7(id)}/whatsapp/link`;
|
|
1700
|
+
const body = { integrationId: v.integration, ...sub === "unlink" ? { unlink: true } : { answer: v.answer === "on", ...v.forward ? { forward: true } : {} } };
|
|
1701
|
+
const plan = await api(path, { method: "POST", body: { ...body, dryRun: true } });
|
|
1702
|
+
const text = `${plan.plan.join("\n")}
|
|
1703
|
+
${dim(`Formmy: ${plan.formmy.before.url ?? "null"} \u2192 ${plan.formmy.after.url ?? "null"} (as ${plan.formmy.by})`)}`;
|
|
1704
|
+
if (v["dry-run"]) return out(plan, () => `${text}
|
|
1705
|
+
${dim("Dry run: nothing changed.")}`);
|
|
1706
|
+
const digits4 = (plan.number.phoneNumberId ?? v.integration).slice(-4);
|
|
1707
|
+
await confirm(`${bold(text)}
|
|
1708
|
+
${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" : ""}` : ""}` });
|
|
1709
|
+
const r = await api(path, { method: "POST", body });
|
|
1710
|
+
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."}`);
|
|
1711
|
+
}
|
|
1609
1712
|
|
|
1610
1713
|
// src/commands/board.ts
|
|
1611
1714
|
async function boardCmd(sub, id, v) {
|
|
@@ -1831,14 +1934,50 @@ HELPS.credentials = {
|
|
|
1831
1934
|
};
|
|
1832
1935
|
HELPS.creds = HELPS.credentials;
|
|
1833
1936
|
HELPS.channels = {
|
|
1834
|
-
summary: "Which WhatsApp Business numbers an agent answers. Pair
|
|
1835
|
-
usage: [
|
|
1937
|
+
summary: "Which WhatsApp Business numbers an agent answers, and which groups of its personal WhatsApp (and as whom). Pair numbers in the web.",
|
|
1938
|
+
usage: [
|
|
1939
|
+
"ghosty channels whatsapp ls <agent-id>",
|
|
1940
|
+
"ghosty channels whatsapp enable <agent-id> <number> [--dry-run] [--yes]",
|
|
1941
|
+
"ghosty channels whatsapp disable <agent-id> <number> [--dry-run]",
|
|
1942
|
+
"ghosty channels whatsapp link <agent-id> --integration <formmy-id> --answer on|off [--forward] [--dry-run] [--yes]",
|
|
1943
|
+
"ghosty channels whatsapp unlink <agent-id> --integration <formmy-id> [--dry-run] [--yes]",
|
|
1944
|
+
"ghosty channels wa import <agent-id> --from-file creds.json --own-number yes|no [--replace] [--no-connect] [--dry-run] [--yes]",
|
|
1945
|
+
"ghosty channels wa pair <agent-id> --phone 5215512345678",
|
|
1946
|
+
"ghosty channels wa status|pause <agent-id>",
|
|
1947
|
+
"ghosty channels wa groups ls <agent-id> [--cached]",
|
|
1948
|
+
"ghosty channels wa groups set <agent-id> <group> [--role equipo|cliente] [--on | --off] [--dry-run] [--yes]",
|
|
1949
|
+
'ghosty channels wa groups create <agent-id> "Name" [--role equipo|cliente]'
|
|
1950
|
+
],
|
|
1836
1951
|
flags: [
|
|
1837
1952
|
["-n, --dry-run", "print the plan (+ starts answering, - stops) and change nothing"],
|
|
1838
|
-
["--yes", "enable: skip
|
|
1953
|
+
["--yes", "enable / groups set --on: skip the confirmation (required without a terminal)"],
|
|
1954
|
+
["--role R", "groups: equipo (the team: writes to databases, admin tools) or cliente (like any customer)"],
|
|
1955
|
+
["--on | --off", "groups set: the agent answers in that group, or stops"],
|
|
1956
|
+
["--cached", "groups ls: don't ask WhatsApp for the live list (faster; may miss new groups)"],
|
|
1957
|
+
["--integration ID", "link/unlink: the WhatsApp Business number's Integration id in Formmy"],
|
|
1958
|
+
["--answer on|off", "link (required, no default): on = this agent answers real customers; off = it only records"],
|
|
1959
|
+
["--forward", "link --answer off: still make Formmy forward, so gs sees the conversations"],
|
|
1960
|
+
["--from-file F", "import: {authCreds, authKeys, phone, groups} exported from EasyBits"],
|
|
1961
|
+
["--own-number yes|no", "import (required): the number is the agent's, or a person's (only their self-chat is the owner's)"],
|
|
1962
|
+
["--replace", "import: replace a session already saved in gs"],
|
|
1963
|
+
["--no-connect", "import: save it but don't connect yet"]
|
|
1964
|
+
],
|
|
1965
|
+
examples: [
|
|
1966
|
+
"ghosty channels whatsapp ls <id>",
|
|
1967
|
+
"ghosty channels whatsapp enable <id> 5512345678 --dry-run",
|
|
1968
|
+
"ghosty channels whatsapp disable <id> 5512345678",
|
|
1969
|
+
"ghosty channels wa groups ls <id>",
|
|
1970
|
+
'ghosty channels wa groups set <id> "Pia-cotizadora" --role equipo --on --dry-run',
|
|
1971
|
+
"ghosty channels whatsapp link <id> --integration 6a3af395\u2026 --answer off --dry-run",
|
|
1972
|
+
"ghosty channels wa import <id> --from-file pia-wa.json --own-number no --dry-run"
|
|
1973
|
+
],
|
|
1974
|
+
notes: [
|
|
1975
|
+
"<number> is its id, its label or its last digits. One number, one agent: enabling it takes it from the other agent.",
|
|
1976
|
+
"Disabling keeps logging messages; nobody answers them.",
|
|
1977
|
+
"Groups belong to the PERSONAL WhatsApp paired to the agent (not WhatsApp Business). <group> is its name or its jid.",
|
|
1978
|
+
"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.",
|
|
1979
|
+
"import: turn off the EasyBits socket first WITHOUT logout (two sockets knock each other off; a logout invalidates the keys). Delete the file afterwards."
|
|
1839
1980
|
],
|
|
1840
|
-
examples: ["ghosty channels whatsapp ls <id>", "ghosty channels whatsapp enable <id> 5512345678 --dry-run", "ghosty channels whatsapp disable <id> 5512345678"],
|
|
1841
|
-
notes: ["<number> is its id, its label or its last digits. One number, one agent: enabling it takes it from the other agent.", "Disabling keeps logging messages; nobody answers them."],
|
|
1842
1981
|
learn: `${REF}#canales-de-whatsapp`
|
|
1843
1982
|
};
|
|
1844
1983
|
HELPS.board = {
|
|
@@ -1876,7 +2015,7 @@ function helpText(cmd) {
|
|
|
1876
2015
|
}
|
|
1877
2016
|
|
|
1878
2017
|
// src/update.ts
|
|
1879
|
-
import { mkdir as mkdir4, readFile as
|
|
2018
|
+
import { mkdir as mkdir4, readFile as readFile7, writeFile as writeFile4 } from "node:fs/promises";
|
|
1880
2019
|
import { homedir as homedir3 } from "node:os";
|
|
1881
2020
|
import { dirname as dirname3, join as join4 } from "node:path";
|
|
1882
2021
|
var FILE = join4(process.env.XDG_CONFIG_HOME || join4(homedir3(), ".config"), "ghosty", "update.json");
|
|
@@ -1892,7 +2031,7 @@ async function latestVersion() {
|
|
|
1892
2031
|
if (process.env.GHOSTY_NO_UPDATE_CHECK || process.env.CI) return null;
|
|
1893
2032
|
let cache = {};
|
|
1894
2033
|
try {
|
|
1895
|
-
cache = JSON.parse(await
|
|
2034
|
+
cache = JSON.parse(await readFile7(FILE, "utf8"));
|
|
1896
2035
|
} catch {
|
|
1897
2036
|
}
|
|
1898
2037
|
if (!cache.checkedAt || Date.now() - cache.checkedAt > DAY) {
|
|
@@ -1944,6 +2083,11 @@ ${bold("Configure an agent")}
|
|
|
1944
2083
|
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]
|
|
1945
2084
|
mcp get|set <id> MCP servers [--file servers.json]
|
|
1946
2085
|
channels whatsapp ls|enable|disable <id> [number] Which WhatsApp Business numbers it answers [--dry-run]
|
|
2086
|
+
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]
|
|
2087
|
+
channels wa import <id> --from-file F --own-number yes|no Move a personal WhatsApp session here, no QR [--dry-run] [--replace] [--no-connect]
|
|
2088
|
+
channels wa pair <id> --phone N Pair a personal WhatsApp by code (no QR)
|
|
2089
|
+
channels wa status|pause <id> Personal WhatsApp: how it is / pause it (keeps the session)
|
|
2090
|
+
channels wa groups ls|set|create <id> [group] Personal WhatsApp groups it answers, and as whom [--role equipo|cliente] [--on|--off] [--dry-run]
|
|
1947
2091
|
board archive <id> Archive cards in bulk (reversible) --integration X | --column X | --before 30d [--dry-run]
|
|
1948
2092
|
|
|
1949
2093
|
${bold("Talk to an agent")}
|
|
@@ -2071,6 +2215,15 @@ async function main() {
|
|
|
2071
2215
|
column: { type: "string" },
|
|
2072
2216
|
before: { type: "string" },
|
|
2073
2217
|
recycle: { type: "boolean" },
|
|
2218
|
+
role: { type: "string" },
|
|
2219
|
+
"from-file": { type: "string" },
|
|
2220
|
+
"own-number": { type: "string" },
|
|
2221
|
+
answer: { type: "string" },
|
|
2222
|
+
phone: { type: "string" },
|
|
2223
|
+
forward: { type: "boolean" },
|
|
2224
|
+
replace: { type: "boolean" },
|
|
2225
|
+
"no-connect": { type: "boolean" },
|
|
2226
|
+
cached: { type: "boolean" },
|
|
2074
2227
|
check: { type: "string", multiple: true },
|
|
2075
2228
|
contains: { type: "string", multiple: true }
|
|
2076
2229
|
}
|
|
@@ -2079,7 +2232,7 @@ async function main() {
|
|
|
2079
2232
|
const tokens = splitTokens(pos, v);
|
|
2080
2233
|
globals.token = tokens.session;
|
|
2081
2234
|
globals.profile = v.profile;
|
|
2082
|
-
if (v.version) return out({ version: "0.
|
|
2235
|
+
if (v.version) return out({ version: "0.13.0" }, () => "0.13.0");
|
|
2083
2236
|
const [cmd, sub, a1, a2] = pos;
|
|
2084
2237
|
if (cmd === "partner" && (v.help || !sub || sub === "help")) return out({ help: PARTNER_HELP }, () => PARTNER_HELP);
|
|
2085
2238
|
const topic = cmd === "help" ? sub : v.help ? cmd : void 0;
|
|
@@ -2131,6 +2284,7 @@ async function main() {
|
|
|
2131
2284
|
case "board":
|
|
2132
2285
|
return boardCmd(sub, a1, v);
|
|
2133
2286
|
case "channels":
|
|
2287
|
+
if (a1 === "groups") return groupsCmd(pos.slice(3).filter((x) => x !== void 0), v);
|
|
2134
2288
|
return channelsCmd(sub, a1, a2, pos.slice(4).join(" ") || void 0, v);
|
|
2135
2289
|
case "credentials":
|
|
2136
2290
|
case "creds":
|
|
@@ -2163,7 +2317,7 @@ async function main() {
|
|
|
2163
2317
|
throw unknown(cmd, cmd, COMMANDS, "ghosty --help");
|
|
2164
2318
|
}
|
|
2165
2319
|
}
|
|
2166
|
-
var notice = updateNotice("0.
|
|
2320
|
+
var notice = updateNotice("0.13.0").catch(() => null);
|
|
2167
2321
|
main().then(
|
|
2168
2322
|
async () => {
|
|
2169
2323
|
const n = await notice;
|
package/package.json
CHANGED