@ghostystudio/cli 0.10.2 → 0.12.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 +10 -0
- package/dist/index.js +94 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -35,6 +35,7 @@ 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 wa groups ls|set|create <id> [group] [--role equipo|cliente] [--on|--off] [--dry-run] # personal WhatsApp groups
|
|
38
39
|
ghosty board archive <id> --integration X | --column X | --before 30d [--dry-run]
|
|
39
40
|
ghosty mcp get|set <id> [--file servers.json]
|
|
40
41
|
|
|
@@ -43,6 +44,7 @@ ghosty try <id> "message" --waba --session PHONE [--media FILE] [--history FILE]
|
|
|
43
44
|
ghosty try <id> --replay FILE --out DIR [--max 15] [--concurrency 8] [--resume]
|
|
44
45
|
ghosty chat <id> ["message"] [--conversation ID]
|
|
45
46
|
ghosty conversations ls <id>
|
|
47
|
+
ghosty conversations show <id> <conversation-id> [--limit 20] # what was said
|
|
46
48
|
|
|
47
49
|
ghosty schedule add <id> "what to do" --in 30m | --at ISO [--every 12h --until ISO] [--conversation ID] [--title T]
|
|
48
50
|
ghosty schedule ls <id>
|
|
@@ -74,6 +76,14 @@ Exit codes: `0` ok · `1` API error · `2` usage error · `3` not signed in.
|
|
|
74
76
|
- `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.
|
|
75
77
|
- `agents doctor` speaks English like the rest of the CLI; `channels whatsapp ls` returns a full URL.
|
|
76
78
|
|
|
79
|
+
### Changes in 0.12.0
|
|
80
|
+
|
|
81
|
+
- 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.
|
|
82
|
+
|
|
83
|
+
### Changes in 0.11.0
|
|
84
|
+
|
|
85
|
+
- New: `conversations show` (what was said in a conversation; `--limit 0` for all).
|
|
86
|
+
|
|
77
87
|
### Changes in 0.10.0
|
|
78
88
|
|
|
79
89
|
- Tells you when a new version is out (checks npm once a day, on stderr; `GHOSTY_NO_UPDATE_CHECK=1` or `CI` turns it off). An unknown option on an old version says to update.
|
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.12.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.12.0"}`,
|
|
328
328
|
...opts.body !== void 0 ? { "content-type": "application/json" } : {},
|
|
329
329
|
...opts.headers
|
|
330
330
|
},
|
|
@@ -851,6 +851,17 @@ async function conversationsLs(agent) {
|
|
|
851
851
|
const rows = r.conversaciones.map((c) => ({ id: c.id, title: c.titulo, updated: c.actualizada }));
|
|
852
852
|
out(rows, () => table(rows, ["id", "title", "updated"]));
|
|
853
853
|
}
|
|
854
|
+
async function conversationsShow(agent, sid, v) {
|
|
855
|
+
if (!agent || !sid) throw usage("Missing agent id or conversation id.", "Example: ghosty conversations show <agent-id> <conversation-id> [--limit 20]");
|
|
856
|
+
const tail = v.limit ?? "20";
|
|
857
|
+
const r = await api(convPath(agent, `/${enc3(sid)}?tail=${enc3(tail)}`));
|
|
858
|
+
out(r, () => {
|
|
859
|
+
const lines = r.messages.map((m) => `${bold(m.role === "user" ? "User" : m.role === "assistant" ? "Agent" : m.role)} ${m.text.trim()}`);
|
|
860
|
+
const head = r.saltados ? [dim(`(${r.saltados} earlier messages not shown \xB7 --limit 0 for all)`)] : [];
|
|
861
|
+
const tailNote = r.enCurso ? [dim("(a turn is running: its answer is not here yet)")] : [];
|
|
862
|
+
return [...head, ...lines, ...tailNote].join("\n\n") || dim("(empty)");
|
|
863
|
+
});
|
|
864
|
+
}
|
|
854
865
|
async function turn(agent, sid, text, first) {
|
|
855
866
|
const turnId = randomUUID();
|
|
856
867
|
const ctrl = new AbortController();
|
|
@@ -1125,7 +1136,7 @@ async function call(path, v, init = {}) {
|
|
|
1125
1136
|
"x-ghosty-ts": ts,
|
|
1126
1137
|
"x-ghosty-sig": sig,
|
|
1127
1138
|
"content-type": "application/json",
|
|
1128
|
-
"user-agent": `ghosty-cli/${"0.
|
|
1139
|
+
"user-agent": `ghosty-cli/${"0.12.0"}`
|
|
1129
1140
|
},
|
|
1130
1141
|
body: method === "GET" ? void 0 : raw
|
|
1131
1142
|
});
|
|
@@ -1595,6 +1606,51 @@ Real customers will get answers from this agent.`, { yes: v.yes, expect: digits(
|
|
|
1595
1606
|
const r = await api(path, { method: "PUT", body: { numbers: wanted } });
|
|
1596
1607
|
return out(r, () => `${lavender("\u2713")} ${plan.slice(2)}`);
|
|
1597
1608
|
}
|
|
1609
|
+
function findGroup(groups, ref) {
|
|
1610
|
+
const byJid = groups.find((g) => g.jid === ref);
|
|
1611
|
+
if (byJid) return byJid;
|
|
1612
|
+
const byName = groups.filter((g) => g.name.toLowerCase() === ref.toLowerCase());
|
|
1613
|
+
if (byName.length === 1) return byName[0];
|
|
1614
|
+
if (byName.length > 1) throw usage(`${byName.length} groups are called "${ref}".`, `Use its jid: ${byName.map((g) => g.jid).join(", ")}`);
|
|
1615
|
+
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.");
|
|
1616
|
+
}
|
|
1617
|
+
async function groupsCmd(args, v) {
|
|
1618
|
+
const [sub, id, ...rest] = args;
|
|
1619
|
+
const ex = 'ghosty channels wa groups ls <agent-id> | set <agent-id> <group> [--role equipo|cliente] [--on|--off] | create <agent-id> "Name"';
|
|
1620
|
+
if (!id) throw usage("Missing agent id.", `Example: ${ex}`);
|
|
1621
|
+
const path = `/api/v2/agents/${enc7(id)}/wa/groups`;
|
|
1622
|
+
if (v.role !== void 0 && v.role !== "equipo" && v.role !== "cliente") throw usage("--role takes equipo or cliente.");
|
|
1623
|
+
if (!sub || sub === "ls" || sub === "list") {
|
|
1624
|
+
const r2 = await api(`${path}${v.cached ? "?live=0" : ""}`);
|
|
1625
|
+
const rows = r2.groups.map((g2) => ({ name: g2.name, on: g2.enabled ? lavender("on") : dim("off"), role: g2.role, jid: g2.jid }));
|
|
1626
|
+
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"'));
|
|
1627
|
+
}
|
|
1628
|
+
if (sub === "create") {
|
|
1629
|
+
const name = rest.join(" ").trim();
|
|
1630
|
+
if (!name) throw usage("Missing group name.", `Example: ghosty channels wa groups create ${id} "Equipo ventas" --role equipo`);
|
|
1631
|
+
const r2 = await api(path, { method: "POST", body: { create: name, role: v.role ?? "equipo" } });
|
|
1632
|
+
return out(r2, () => `${lavender("\u2713")} Created ${bold(name)} (${v.role ?? "equipo"}, on)
|
|
1633
|
+
invite: ${r2.inviteUrl}`);
|
|
1634
|
+
}
|
|
1635
|
+
if (sub !== "set") throw usage(`Unknown groups subcommand: ${sub}.`, ex);
|
|
1636
|
+
const ref = rest.join(" ").trim();
|
|
1637
|
+
if (!ref) throw usage("Missing group (name or jid).", `Example: ghosty channels wa groups set ${id} "Pia-cotizadora" --role equipo --on`);
|
|
1638
|
+
if (v.on && v.off) throw usage("Use --on or --off, not both.");
|
|
1639
|
+
const cur = await api(`${path}?live=0`);
|
|
1640
|
+
const g = findGroup(cur.groups, ref);
|
|
1641
|
+
const enabled = v.on ? true : v.off ? false : g.enabled;
|
|
1642
|
+
const role = v.role ?? g.role;
|
|
1643
|
+
const changes = [...enabled !== g.enabled ? [`${g.enabled ? "on" : "off"}\u2192${enabled ? "on" : "off"}`] : [], ...role !== g.role ? [`${g.role}\u2192${role}`] : []];
|
|
1644
|
+
if (!changes.length) return out({ changed: 0, group: g }, () => dim(`Nothing to do: ${g.name} is already ${g.enabled ? "on" : "off"} as ${g.role}.`));
|
|
1645
|
+
const plan = `~ ${g.name}: ${changes.join(", ")}`;
|
|
1646
|
+
if (v["dry-run"]) return out({ dryRun: true, plan: [plan] }, () => `${plan}
|
|
1647
|
+
${dim("Dry run: nothing changed.")}`);
|
|
1648
|
+
if (enabled && !g.enabled) await confirm(`${bold(plan)}
|
|
1649
|
+
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` });
|
|
1650
|
+
const r = await api(path, { method: "POST", body: { jid: g.jid, enabled, role } });
|
|
1651
|
+
const now = r.groups.find((x) => x.jid === g.jid);
|
|
1652
|
+
return out({ group: now ?? null, plan: [plan] }, () => `${lavender("\u2713")} ${g.name}: ${now?.enabled ? "on" : "off"} as ${now?.role ?? role}`);
|
|
1653
|
+
}
|
|
1598
1654
|
|
|
1599
1655
|
// src/commands/board.ts
|
|
1600
1656
|
async function boardCmd(sub, id, v) {
|
|
@@ -1786,7 +1842,7 @@ var HELPS = {
|
|
|
1786
1842
|
},
|
|
1787
1843
|
chat: {
|
|
1788
1844
|
summary: "Stream a conversation; with no message it is interactive.",
|
|
1789
|
-
usage: ['ghosty chat <agent-id> ["message"] [--conversation ID]', "ghosty conversations ls <agent-id>"],
|
|
1845
|
+
usage: ['ghosty chat <agent-id> ["message"] [--conversation ID]', "ghosty conversations ls <agent-id>", "ghosty conversations show <agent-id> <conversation-id> [--limit 20]"],
|
|
1790
1846
|
examples: ['ghosty chat <id> "hola"', "ghosty chat <id>"],
|
|
1791
1847
|
learn: `${REF}#conversaciones`
|
|
1792
1848
|
},
|
|
@@ -1820,14 +1876,34 @@ HELPS.credentials = {
|
|
|
1820
1876
|
};
|
|
1821
1877
|
HELPS.creds = HELPS.credentials;
|
|
1822
1878
|
HELPS.channels = {
|
|
1823
|
-
summary: "Which WhatsApp Business numbers an agent answers. Pair
|
|
1824
|
-
usage: [
|
|
1879
|
+
summary: "Which WhatsApp Business numbers an agent answers, and which groups of its personal WhatsApp (and as whom). Pair numbers in the web.",
|
|
1880
|
+
usage: [
|
|
1881
|
+
"ghosty channels whatsapp ls <agent-id>",
|
|
1882
|
+
"ghosty channels whatsapp enable <agent-id> <number> [--dry-run] [--yes]",
|
|
1883
|
+
"ghosty channels whatsapp disable <agent-id> <number> [--dry-run]",
|
|
1884
|
+
"ghosty channels wa groups ls <agent-id> [--cached]",
|
|
1885
|
+
"ghosty channels wa groups set <agent-id> <group> [--role equipo|cliente] [--on | --off] [--dry-run] [--yes]",
|
|
1886
|
+
'ghosty channels wa groups create <agent-id> "Name" [--role equipo|cliente]'
|
|
1887
|
+
],
|
|
1825
1888
|
flags: [
|
|
1826
1889
|
["-n, --dry-run", "print the plan (+ starts answering, - stops) and change nothing"],
|
|
1827
|
-
["--yes", "enable: skip
|
|
1890
|
+
["--yes", "enable / groups set --on: skip the confirmation (required without a terminal)"],
|
|
1891
|
+
["--role R", "groups: equipo (the team: writes to databases, admin tools) or cliente (like any customer)"],
|
|
1892
|
+
["--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)"]
|
|
1894
|
+
],
|
|
1895
|
+
examples: [
|
|
1896
|
+
"ghosty channels whatsapp ls <id>",
|
|
1897
|
+
"ghosty channels whatsapp enable <id> 5512345678 --dry-run",
|
|
1898
|
+
"ghosty channels whatsapp disable <id> 5512345678",
|
|
1899
|
+
"ghosty channels wa groups ls <id>",
|
|
1900
|
+
'ghosty channels wa groups set <id> "Pia-cotizadora" --role equipo --on --dry-run'
|
|
1901
|
+
],
|
|
1902
|
+
notes: [
|
|
1903
|
+
"<number> is its id, its label or its last digits. One number, one agent: enabling it takes it from the other agent.",
|
|
1904
|
+
"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."
|
|
1828
1906
|
],
|
|
1829
|
-
examples: ["ghosty channels whatsapp ls <id>", "ghosty channels whatsapp enable <id> 5512345678 --dry-run", "ghosty channels whatsapp disable <id> 5512345678"],
|
|
1830
|
-
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."],
|
|
1831
1907
|
learn: `${REF}#canales-de-whatsapp`
|
|
1832
1908
|
};
|
|
1833
1909
|
HELPS.board = {
|
|
@@ -1933,6 +2009,7 @@ ${bold("Configure an agent")}
|
|
|
1933
2009
|
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]
|
|
1934
2010
|
mcp get|set <id> MCP servers [--file servers.json]
|
|
1935
2011
|
channels whatsapp ls|enable|disable <id> [number] Which WhatsApp Business numbers it answers [--dry-run]
|
|
2012
|
+
channels wa groups ls|set|create <id> [group] Personal WhatsApp groups it answers, and as whom [--role equipo|cliente] [--on|--off] [--dry-run]
|
|
1936
2013
|
board archive <id> Archive cards in bulk (reversible) --integration X | --column X | --before 30d [--dry-run]
|
|
1937
2014
|
|
|
1938
2015
|
${bold("Talk to an agent")}
|
|
@@ -1941,6 +2018,7 @@ ${bold("Talk to an agent")}
|
|
|
1941
2018
|
--replay FILE --out DIR Replay real conversations; report written locally [--max 15] [--concurrency 8] [--resume]
|
|
1942
2019
|
chat <id> ["message"] Stream a conversation (no message = interactive) [--conversation ID]
|
|
1943
2020
|
conversations ls <id> List conversations
|
|
2021
|
+
conversations show <id> <conversation> What was said [--limit 20] (0 = all)
|
|
1944
2022
|
turns ls <id> Recent turns and why they failed [--errors] [--since 1h] [--limit N]
|
|
1945
2023
|
turns show <id> [turn] Where a turn's time went: each tool with its seconds, output tokens (default: last)
|
|
1946
2024
|
|
|
@@ -2059,6 +2137,8 @@ async function main() {
|
|
|
2059
2137
|
column: { type: "string" },
|
|
2060
2138
|
before: { type: "string" },
|
|
2061
2139
|
recycle: { type: "boolean" },
|
|
2140
|
+
role: { type: "string" },
|
|
2141
|
+
cached: { type: "boolean" },
|
|
2062
2142
|
check: { type: "string", multiple: true },
|
|
2063
2143
|
contains: { type: "string", multiple: true }
|
|
2064
2144
|
}
|
|
@@ -2067,7 +2147,7 @@ async function main() {
|
|
|
2067
2147
|
const tokens = splitTokens(pos, v);
|
|
2068
2148
|
globals.token = tokens.session;
|
|
2069
2149
|
globals.profile = v.profile;
|
|
2070
|
-
if (v.version) return out({ version: "0.
|
|
2150
|
+
if (v.version) return out({ version: "0.12.0" }, () => "0.12.0");
|
|
2071
2151
|
const [cmd, sub, a1, a2] = pos;
|
|
2072
2152
|
if (cmd === "partner" && (v.help || !sub || sub === "help")) return out({ help: PARTNER_HELP }, () => PARTNER_HELP);
|
|
2073
2153
|
const topic = cmd === "help" ? sub : v.help ? cmd : void 0;
|
|
@@ -2119,6 +2199,7 @@ async function main() {
|
|
|
2119
2199
|
case "board":
|
|
2120
2200
|
return boardCmd(sub, a1, v);
|
|
2121
2201
|
case "channels":
|
|
2202
|
+
if (a1 === "groups") return groupsCmd(pos.slice(3).filter((x) => x !== void 0), v);
|
|
2122
2203
|
return channelsCmd(sub, a1, a2, pos.slice(4).join(" ") || void 0, v);
|
|
2123
2204
|
case "credentials":
|
|
2124
2205
|
case "creds":
|
|
@@ -2129,7 +2210,8 @@ async function main() {
|
|
|
2129
2210
|
return chat(sub, a1, v);
|
|
2130
2211
|
case "conversations":
|
|
2131
2212
|
if (sub === "ls" || sub === "list") return conversationsLs(a1);
|
|
2132
|
-
|
|
2213
|
+
if (sub === "show") return conversationsShow(a1, a2, v);
|
|
2214
|
+
throw usage(`Unknown conversations subcommand: ${sub ?? "(none)"}.`, "Try: ghosty conversations ls <agent-id> \xB7 ghosty conversations show <agent-id> <conversation-id>");
|
|
2133
2215
|
case "schedule":
|
|
2134
2216
|
return scheduleCmd(sub, a1, a2, v);
|
|
2135
2217
|
case "partner":
|
|
@@ -2150,7 +2232,7 @@ async function main() {
|
|
|
2150
2232
|
throw unknown(cmd, cmd, COMMANDS, "ghosty --help");
|
|
2151
2233
|
}
|
|
2152
2234
|
}
|
|
2153
|
-
var notice = updateNotice("0.
|
|
2235
|
+
var notice = updateNotice("0.12.0").catch(() => null);
|
|
2154
2236
|
main().then(
|
|
2155
2237
|
async () => {
|
|
2156
2238
|
const n = await notice;
|
package/package.json
CHANGED