@ghostystudio/cli 0.13.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.
- package/README.md +5 -1
- package/dist/index.js +58 -16
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -36,7 +36,7 @@ ghosty turns ls <id> [--errors | --status running|done|stopp
|
|
|
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
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
|
|
39
|
+
ghosty channels wa import <id> --from-file F | --from-dir DIR --own-number yes|no [--dry-run] # personal WhatsApp session, no QR
|
|
40
40
|
ghosty channels wa pair|status|pause <id> [--phone N]
|
|
41
41
|
ghosty channels wa groups ls|set|create <id> [group] [--role equipo|cliente] [--on|--off] [--dry-run] # personal WhatsApp groups
|
|
42
42
|
ghosty board archive <id> --integration X | --column X | --before 30d [--dry-run]
|
|
@@ -79,6 +79,10 @@ Exit codes: `0` ok · `1` API error · `2` usage error · `3` not signed in.
|
|
|
79
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.
|
|
80
80
|
- `agents doctor` speaks English like the rest of the CLI; `channels whatsapp ls` returns a full URL.
|
|
81
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
|
+
|
|
82
86
|
### Changes in 0.13.0
|
|
83
87
|
|
|
84
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`.
|
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.13.
|
|
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.13.
|
|
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.13.
|
|
1139
|
+
"user-agent": `ghosty-cli/${"0.13.1"}`
|
|
1140
1140
|
},
|
|
1141
1141
|
body: method === "GET" ? void 0 : raw
|
|
1142
1142
|
});
|
|
@@ -1574,7 +1574,42 @@ async function credentialsCmd2(sub, provider, v) {
|
|
|
1574
1574
|
}
|
|
1575
1575
|
|
|
1576
1576
|
// src/commands/channels.ts
|
|
1577
|
-
import { readFile as
|
|
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
|
+
|
|
1612
|
+
// src/commands/channels.ts
|
|
1578
1613
|
var enc7 = encodeURIComponent;
|
|
1579
1614
|
var digits = (s) => s.replace(/\D/g, "");
|
|
1580
1615
|
function find(nums, ref) {
|
|
@@ -1678,17 +1713,22 @@ ${dim("On the phone: WhatsApp \u2192 Linked devices \u2192 Link with phone numbe
|
|
|
1678
1713
|
await api(base, { method: "DELETE" });
|
|
1679
1714
|
return out({ paused: true }, () => `${lavender("\u2713")} Paused. The session is kept: ${dim(`ghosty channels wa import \u2026 or the Channels page reconnects it`)}`);
|
|
1680
1715
|
}
|
|
1681
|
-
const file = v["from-file"];
|
|
1682
|
-
if (!file) throw usage("
|
|
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`);
|
|
1683
1718
|
const own = v["own-number"];
|
|
1684
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)");
|
|
1685
|
-
|
|
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"));
|
|
1686
1726
|
const body = { authCreds: data.authCreds, authKeys: data.authKeys, phone: data.phone, groups: data.groups, ownNumber: own === "yes", replace: !!v.replace };
|
|
1687
1727
|
const plan = await api(`${base}/import`, { method: "POST", body: { ...body, dryRun: true } });
|
|
1688
1728
|
if (v["dry-run"]) return out(plan, () => `${plan.plan.join("\n")}
|
|
1689
1729
|
${dim("Dry run: nothing changed.")}`);
|
|
1690
1730
|
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" : ""}` });
|
|
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" : ""}` });
|
|
1692
1732
|
const r = await api(`${base}/import`, { method: "POST", body: { ...body, connect: !v["no-connect"] } });
|
|
1693
1733
|
return out(r, () => `${lavender("\u2713")} +${r.phone} imported \xB7 status: ${r.status === "connected" ? lavender("connected") : r.status}${r.reason ? dim(` (${r.reason})`) : ""}
|
|
1694
1734
|
${dim(`Next: ghosty channels wa groups ls ${id} \xB7 delete ${file}: it holds the account's keys`)}`);
|
|
@@ -1941,7 +1981,7 @@ HELPS.channels = {
|
|
|
1941
1981
|
"ghosty channels whatsapp disable <agent-id> <number> [--dry-run]",
|
|
1942
1982
|
"ghosty channels whatsapp link <agent-id> --integration <formmy-id> --answer on|off [--forward] [--dry-run] [--yes]",
|
|
1943
1983
|
"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]",
|
|
1984
|
+
"ghosty channels wa import <agent-id> --from-file creds.json | --from-dir auth/ --own-number yes|no [--replace] [--no-connect] [--dry-run] [--yes]",
|
|
1945
1985
|
"ghosty channels wa pair <agent-id> --phone 5215512345678",
|
|
1946
1986
|
"ghosty channels wa status|pause <agent-id>",
|
|
1947
1987
|
"ghosty channels wa groups ls <agent-id> [--cached]",
|
|
@@ -1958,6 +1998,7 @@ HELPS.channels = {
|
|
|
1958
1998
|
["--answer on|off", "link (required, no default): on = this agent answers real customers; off = it only records"],
|
|
1959
1999
|
["--forward", "link --answer off: still make Formmy forward, so gs sees the conversations"],
|
|
1960
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)"],
|
|
1961
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)"],
|
|
1962
2003
|
["--replace", "import: replace a session already saved in gs"],
|
|
1963
2004
|
["--no-connect", "import: save it but don't connect yet"]
|
|
@@ -2015,10 +2056,10 @@ function helpText(cmd) {
|
|
|
2015
2056
|
}
|
|
2016
2057
|
|
|
2017
2058
|
// src/update.ts
|
|
2018
|
-
import { mkdir as mkdir4, readFile as
|
|
2059
|
+
import { mkdir as mkdir4, readFile as readFile8, writeFile as writeFile4 } from "node:fs/promises";
|
|
2019
2060
|
import { homedir as homedir3 } from "node:os";
|
|
2020
|
-
import { dirname as dirname3, join as
|
|
2021
|
-
var FILE =
|
|
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");
|
|
2022
2063
|
var DAY = 24 * 60 * 60 * 1e3;
|
|
2023
2064
|
var UPGRADE = "npm i -g @ghostystudio/cli@latest";
|
|
2024
2065
|
function newer(a, b) {
|
|
@@ -2031,7 +2072,7 @@ async function latestVersion() {
|
|
|
2031
2072
|
if (process.env.GHOSTY_NO_UPDATE_CHECK || process.env.CI) return null;
|
|
2032
2073
|
let cache = {};
|
|
2033
2074
|
try {
|
|
2034
|
-
cache = JSON.parse(await
|
|
2075
|
+
cache = JSON.parse(await readFile8(FILE, "utf8"));
|
|
2035
2076
|
} catch {
|
|
2036
2077
|
}
|
|
2037
2078
|
if (!cache.checkedAt || Date.now() - cache.checkedAt > DAY) {
|
|
@@ -2084,7 +2125,7 @@ ${bold("Configure an agent")}
|
|
|
2084
2125
|
mcp get|set <id> MCP servers [--file servers.json]
|
|
2085
2126
|
channels whatsapp ls|enable|disable <id> [number] Which WhatsApp Business numbers it answers [--dry-run]
|
|
2086
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]
|
|
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]
|
|
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]
|
|
2088
2129
|
channels wa pair <id> --phone N Pair a personal WhatsApp by code (no QR)
|
|
2089
2130
|
channels wa status|pause <id> Personal WhatsApp: how it is / pause it (keeps the session)
|
|
2090
2131
|
channels wa groups ls|set|create <id> [group] Personal WhatsApp groups it answers, and as whom [--role equipo|cliente] [--on|--off] [--dry-run]
|
|
@@ -2217,6 +2258,7 @@ async function main() {
|
|
|
2217
2258
|
recycle: { type: "boolean" },
|
|
2218
2259
|
role: { type: "string" },
|
|
2219
2260
|
"from-file": { type: "string" },
|
|
2261
|
+
"from-dir": { type: "string" },
|
|
2220
2262
|
"own-number": { type: "string" },
|
|
2221
2263
|
answer: { type: "string" },
|
|
2222
2264
|
phone: { type: "string" },
|
|
@@ -2232,7 +2274,7 @@ async function main() {
|
|
|
2232
2274
|
const tokens = splitTokens(pos, v);
|
|
2233
2275
|
globals.token = tokens.session;
|
|
2234
2276
|
globals.profile = v.profile;
|
|
2235
|
-
if (v.version) return out({ version: "0.13.
|
|
2277
|
+
if (v.version) return out({ version: "0.13.1" }, () => "0.13.1");
|
|
2236
2278
|
const [cmd, sub, a1, a2] = pos;
|
|
2237
2279
|
if (cmd === "partner" && (v.help || !sub || sub === "help")) return out({ help: PARTNER_HELP }, () => PARTNER_HELP);
|
|
2238
2280
|
const topic = cmd === "help" ? sub : v.help ? cmd : void 0;
|
|
@@ -2317,7 +2359,7 @@ async function main() {
|
|
|
2317
2359
|
throw unknown(cmd, cmd, COMMANDS, "ghosty --help");
|
|
2318
2360
|
}
|
|
2319
2361
|
}
|
|
2320
|
-
var notice = updateNotice("0.13.
|
|
2362
|
+
var notice = updateNotice("0.13.1").catch(() => null);
|
|
2321
2363
|
main().then(
|
|
2322
2364
|
async () => {
|
|
2323
2365
|
const n = await notice;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ghostystudio/cli",
|
|
3
|
-
"version": "0.13.
|
|
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",
|