@ghostystudio/cli 0.24.1 → 0.25.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 +8 -1
- package/dist/index.js +105 -33
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -90,8 +90,15 @@ Exit codes: `0` ok · `1` API error · `2` usage error · `3` not signed in.
|
|
|
90
90
|
- `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.
|
|
91
91
|
- `agents doctor` speaks English like the rest of the CLI; `channels whatsapp ls` returns a full URL.
|
|
92
92
|
|
|
93
|
-
### Changes in 0.
|
|
93
|
+
### Changes in 0.25.0
|
|
94
94
|
|
|
95
|
+
- `channels whatsapp history` says how many conversations arrive paused by Formmy (and how many "until 9999") or in manual mode; `cards ls --paused` lists them.
|
|
96
|
+
- `history` retries by itself on 502/503/504 or a dropped connection (a gs deploy mid-import) and says where it resumes.
|
|
97
|
+
- `channels wa import --from easybits:<agent-id>`: straight from EasyBits (no loose script); first prints whether the origin session is alive, its keys and groups. The keys stay in memory.
|
|
98
|
+
|
|
99
|
+
### Changes in 0.24.2
|
|
100
|
+
|
|
101
|
+
- `channels whatsapp integrations` also lists numbers the workspace already linked in gs (even if another Formmy account owns them); staff: `--email`.
|
|
95
102
|
- `dbs rename` and `dbs rm` also work for Ghosty staff (migrations), not only the database's owner.
|
|
96
103
|
|
|
97
104
|
### Changes in 0.24.0
|
package/dist/index.js
CHANGED
|
@@ -7600,6 +7600,7 @@ var SERVER_MESSAGES = [
|
|
|
7600
7600
|
["falta name (el nombre nuevo)", "missing name (the new name)"],
|
|
7601
7601
|
["s\xF3lo el due\xF1o, un admin del agente o staff ven los n\xFAmeros de Formmy", "only the owner, an admin of the agent or staff can see the Formmy numbers"],
|
|
7602
7602
|
["no pude leer sus turnos", "couldn't read its turns"],
|
|
7603
|
+
["s\xF3lo staff lista los n\xFAmeros de otro correo", "only staff can list another email's numbers"],
|
|
7603
7604
|
["Tu plan incluye {} base{} de datos.", "Your plan includes {} database{}."],
|
|
7604
7605
|
["El nombre lleva letras min\xFAsculas, n\xFAmeros, guiones o guiones bajos (m\xE1x. 64).", "The name takes lowercase letters, numbers, hyphens or underscores (max 64)."],
|
|
7605
7606
|
["Nombre de tabla inv\xE1lido (letras, n\xFAmeros y _).", "Invalid table name (letters, numbers and _)."],
|
|
@@ -8267,7 +8268,7 @@ async function postLong(path, body, timeoutMs = 9e5) {
|
|
|
8267
8268
|
const req = httpsRequest(`${base}${path}`, {
|
|
8268
8269
|
method: "POST",
|
|
8269
8270
|
timeout: timeoutMs,
|
|
8270
|
-
headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.
|
|
8271
|
+
headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.25.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
|
|
8271
8272
|
}, (res) => {
|
|
8272
8273
|
let buf = "";
|
|
8273
8274
|
res.on("data", (c) => buf += c);
|
|
@@ -8309,7 +8310,7 @@ async function request(path, opts = {}, retried = false) {
|
|
|
8309
8310
|
method: opts.method ?? (opts.body !== void 0 || opts.raw !== void 0 ? "POST" : "GET"),
|
|
8310
8311
|
headers: {
|
|
8311
8312
|
authorization: `Bearer ${token}`,
|
|
8312
|
-
"user-agent": `ghosty-cli/${"0.
|
|
8313
|
+
"user-agent": `ghosty-cli/${"0.25.0"}`,
|
|
8313
8314
|
"accept-language": lang(),
|
|
8314
8315
|
...opts.body !== void 0 ? { "content-type": "application/json" } : {},
|
|
8315
8316
|
...opts.headers
|
|
@@ -9304,7 +9305,7 @@ async function call(path, v, init = {}) {
|
|
|
9304
9305
|
"x-ghosty-ts": ts,
|
|
9305
9306
|
"x-ghosty-sig": sig,
|
|
9306
9307
|
"content-type": "application/json",
|
|
9307
|
-
"user-agent": `ghosty-cli/${"0.
|
|
9308
|
+
"user-agent": `ghosty-cli/${"0.25.0"}`
|
|
9308
9309
|
},
|
|
9309
9310
|
body: method === "GET" ? void 0 : raw
|
|
9310
9311
|
});
|
|
@@ -10011,7 +10012,7 @@ async function channelsCmd(kind, sub, id, ref, v) {
|
|
|
10011
10012
|
if (!id) throw usage(t("Missing agent id.", "Falta el id del agente."), t("Example", "Ejemplo") + ": ghosty channels whatsapp ls <agent-id>");
|
|
10012
10013
|
if (sub === "link" || sub === "unlink") return linkCmd(sub, id, v);
|
|
10013
10014
|
if (sub === "history") return historyCmd(id, v);
|
|
10014
|
-
if (sub === "integrations") return integrationsCmd(id);
|
|
10015
|
+
if (sub === "integrations") return integrationsCmd(id, v.email);
|
|
10015
10016
|
if (sub === "import" || sub === "status" || sub === "pause" || sub === "pair") return waCmd(sub, id, v);
|
|
10016
10017
|
if (sub === "enable" && !v["dry-run"]) requireYesIfHeadless(v.yes, `ghosty channels whatsapp enable ${id} ${ref ?? "<number>"}`);
|
|
10017
10018
|
const path = `/api/v2/agents/${enc7(id)}/whatsapp`;
|
|
@@ -10086,6 +10087,31 @@ ${t(`The agent will start answering in this group as ${role}.`, `El agente empez
|
|
|
10086
10087
|
const now = r.groups.find((x) => x.jid === g.jid);
|
|
10087
10088
|
return out({ group: now ?? null, plan: [plan] }, () => `${lavender("\u2713")} ${g.name}: ${now?.enabled ? "on" : "off"} ${t("as", "como")} ${now?.role ?? role}`);
|
|
10088
10089
|
}
|
|
10090
|
+
var EB_EXPORT = (agentId) => `${(process.env.EASYBITS_URL ?? "https://www.easybits.cloud").replace(/\/+$/, "")}/api/v2/fleet-agents/${enc7(agentId)}/baileys/export`;
|
|
10091
|
+
async function easybitsSession(agentId, keys) {
|
|
10092
|
+
const key = process.env.EASYBITS_API_KEY;
|
|
10093
|
+
if (!key) throw usage(t("EASYBITS_API_KEY is not set.", "EASYBITS_API_KEY no est\xE1 definida."), t("Export the EasyBits API key of the agent's owner and run again.", "Exporta la API key de EasyBits del due\xF1o del agente y vuelve a correrlo."));
|
|
10094
|
+
const res = await fetch(EB_EXPORT(agentId), { method: "POST", headers: { authorization: `Bearer ${key}`, "content-type": "application/json" }, body: JSON.stringify(keys ? { confirm: "export" } : { dryRun: true }) });
|
|
10095
|
+
const body = await res.json().catch(() => ({}));
|
|
10096
|
+
if (res.status === 404) throw usage(t(`EasyBits has no agent ${agentId} for that key.`, `EasyBits no tiene el agente ${agentId} con esa llave.`), t("The key must be the agent owner's.", "La llave tiene que ser del due\xF1o del agente."));
|
|
10097
|
+
if (!res.ok) throw usage(`EasyBits ${res.status}: ${body.error ?? ""}`);
|
|
10098
|
+
return body;
|
|
10099
|
+
}
|
|
10100
|
+
function originText(o) {
|
|
10101
|
+
const label = {
|
|
10102
|
+
alive: lavender(t("\u25CF alive", "\u25CF viva")),
|
|
10103
|
+
stale: t("! stale (no recent activity)", "! sin actividad reciente"),
|
|
10104
|
+
logged_out: t("\u2717 logged out", "\u2717 cerr\xF3 sesi\xF3n"),
|
|
10105
|
+
never_paired: t("\u2717 never paired", "\u2717 nunca se pare\xF3")
|
|
10106
|
+
};
|
|
10107
|
+
const seen = o.status.lastSeenAt ? ` \xB7 ${t("last seen", "\xFAltima se\xF1al")} ${o.status.lastSeenAt.slice(0, 16).replace("T", " ")}` : "";
|
|
10108
|
+
const groups = o.groupDetails.length ? o.groupDetails.map((g) => ` ${g.enabled ? "\u2713" : dim("\xB7")} ${g.name ?? g.jid}${g.main ? dim(" (main)") : ""}`).join("\n") : dim(t(" no groups", " sin grupos"));
|
|
10109
|
+
return [
|
|
10110
|
+
`${bold(t("Origin (EasyBits)", "Origen (EasyBits)"))} ${o.name ?? o.agentId} ${o.phone ? `+${o.phone}` : ""}: ${label[o.health] ?? o.health}${seen}`,
|
|
10111
|
+
dim(` ${t("keys", "llaves")} ${o.keyCount} \xB7 ${o.registered ? t("registered", "registrada") : t("not registered", "sin registrar")}${o.status.lastError ? ` \xB7 ${o.status.lastError}` : ""}`),
|
|
10112
|
+
groups
|
|
10113
|
+
].join("\n");
|
|
10114
|
+
}
|
|
10089
10115
|
async function waCmd(sub, id, v) {
|
|
10090
10116
|
if (!id) throw usage(t("Missing agent id.", "Falta el id del agente."), t("Example", "Ejemplo") + `: ghosty channels wa ${sub} <agent-id>`);
|
|
10091
10117
|
const base = `/api/v2/agents/${enc7(id)}/wa`;
|
|
@@ -10110,25 +10136,36 @@ ${dim(t("On the phone: WhatsApp \u2192 Linked devices \u2192 Link with phone num
|
|
|
10110
10136
|
await api(base, { method: "DELETE" });
|
|
10111
10137
|
return out({ paused: true }, () => `${lavender("\u2713")} ${t("Paused. The session is kept:", "En pausa. La sesi\xF3n se conserva:")} ${dim(t("ghosty channels wa import \u2026 or the Channels page reconnects it", "ghosty channels wa import \u2026 o la p\xE1gina Canales la reconecta"))}`);
|
|
10112
10138
|
}
|
|
10113
|
-
const
|
|
10114
|
-
if (
|
|
10139
|
+
const eb = /^easybits:(.+)$/.exec(v.from ?? "")?.[1];
|
|
10140
|
+
if (v.from && !eb) throw usage(t("--from takes easybits:<agent-id>.", "--from acepta easybits:<id-del-agente>."));
|
|
10141
|
+
const file = eb ? `easybits:${eb}` : v["from-file"] ?? v["from-dir"];
|
|
10142
|
+
if (!file || [eb, v["from-file"], v["from-dir"]].filter(Boolean).length > 1) throw usage(t("Use --from easybits:<agent-id>, --from-file F (EasyBits JSON) or --from-dir DIR (Baileys multi-file folder).", "Usa --from easybits:<id-del-agente>, --from-file F (JSON de EasyBits) o --from-dir DIR (carpeta multi-archivo de Baileys)."), t("Example", "Ejemplo") + `: ghosty channels wa import ${id} --from easybits:<agent-id> --own-number no --dry-run`);
|
|
10143
|
+
let origin;
|
|
10144
|
+
if (eb) {
|
|
10145
|
+
origin = await easybitsSession(eb, false);
|
|
10146
|
+
if (!globals.json) info(originText(origin));
|
|
10147
|
+
if (origin.health !== "alive" && !globals.json) info(lavender(t("The origin session is not alive: importing it will not connect. Pair the number again instead (ghosty channels wa pair).", "La sesi\xF3n del origen no est\xE1 viva: importarla no va a conectar. Mejor parea el n\xFAmero de nuevo (ghosty channels wa pair).")));
|
|
10148
|
+
}
|
|
10115
10149
|
const own = v["own-number"];
|
|
10116
10150
|
if (own !== "yes" && own !== "no") throw usage(t("Missing --own-number yes|no.", "Falta --own-number yes|no."), t("yes = the number belongs to the agent \xB7 no = it is a person's number (only their chat with themselves is the owner's)", "yes = el n\xFAmero es del agente \xB7 no = es el n\xFAmero de una persona (s\xF3lo su chat consigo misma es \xABdel due\xF1o\xBB)"));
|
|
10117
10151
|
let data;
|
|
10118
|
-
if (
|
|
10152
|
+
if (eb) {
|
|
10153
|
+
const full = await easybitsSession(eb, true);
|
|
10154
|
+
data = { authCreds: full.authCreds, authKeys: full.authKeys, phone: full.phone, groups: full.groups };
|
|
10155
|
+
} else if (v["from-dir"]) {
|
|
10119
10156
|
const m = await readMultiFileAuth(v["from-dir"]);
|
|
10120
10157
|
info(dim(`${v["from-dir"]}: ${Object.entries(m.counts).map(([k, n]) => `${k} ${n}`).join(" \xB7 ") || t("no keys", "sin llaves")}${m.skipped.length ? t(` \xB7 skipped ${m.skipped.length} unknown file(s)`, ` \xB7 se omitieron ${m.skipped.length} archivo(s) desconocido(s)`) : ""}`));
|
|
10121
10158
|
data = { authCreds: m.authCreds, authKeys: m.authKeys };
|
|
10122
10159
|
} else data = JSON.parse(await readFile8(file, "utf8"));
|
|
10123
10160
|
const body = { authCreds: data.authCreds, authKeys: data.authKeys, phone: data.phone, groups: data.groups, ownNumber: own === "yes", replace: !!v.replace };
|
|
10124
10161
|
const plan = await api(`${base}/import`, { method: "POST", body: { ...body, dryRun: true } });
|
|
10125
|
-
if (v["dry-run"]) return out(plan, () => `${plan.plan.map((x) => serverText(x)).join("\n")}
|
|
10162
|
+
if (v["dry-run"]) return out(origin ? { origin, ...plan } : plan, () => `${plan.plan.map((x) => serverText(x)).join("\n")}
|
|
10126
10163
|
${dim(t("Dry run: nothing changed.", "Simulacro: no se cambi\xF3 nada."))}`);
|
|
10127
10164
|
await confirm(`${bold(plan.plan.map((x) => serverText(x)).join("\n"))}
|
|
10128
|
-
${t("This number starts living in gs. Turn off its EasyBits socket first, WITHOUT logout (two sockets knock each other off).", "Este n\xFAmero empieza a vivir en gs. Apaga antes su socket de EasyBits, SIN logout (dos sockets se tumban entre s\xED).")}`, { 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" : ""}` });
|
|
10165
|
+
${t("This number starts living in gs. Turn off its EasyBits socket first, WITHOUT logout (two sockets knock each other off).", "Este n\xFAmero empieza a vivir en gs. Apaga antes su socket de EasyBits, SIN logout (dos sockets se tumban entre s\xED).")}`, { yes: v.yes, expect: plan.phone.slice(-4), retry: `ghosty channels wa import ${id} ${eb ? "--from" : v["from-dir"] ? "--from-dir" : "--from-file"} ${file} --own-number ${own}${v.replace ? " --replace" : ""}` });
|
|
10129
10166
|
const r = await api(`${base}/import`, { method: "POST", body: { ...body, connect: !v["no-connect"] } });
|
|
10130
10167
|
return out(r, () => `${lavender("\u2713")} +${r.phone} ${t("imported \xB7 status", "importado \xB7 estado")}: ${r.status === "connected" ? lavender(t("connected", "conectado")) : r.status}${r.reason ? dim(` (${serverText(r.reason)})`) : ""}
|
|
10131
|
-
${dim(t(`Next: ghosty channels wa groups ls ${id} \xB7 delete ${file}: it holds the account's keys`, `Sigue: ghosty channels wa groups ls ${id} \xB7 borra ${file}: guarda las llaves de la cuenta`))}`);
|
|
10168
|
+
${dim(eb ? t(`Next: ghosty channels wa groups ls ${id}`, `Sigue: ghosty channels wa groups ls ${id}`) : t(`Next: ghosty channels wa groups ls ${id} \xB7 delete ${file}: it holds the account's keys`, `Sigue: ghosty channels wa groups ls ${id} \xB7 borra ${file}: guarda las llaves de la cuenta`))}`);
|
|
10132
10169
|
}
|
|
10133
10170
|
async function linkCmd(sub, id, v) {
|
|
10134
10171
|
if (!id || !v.integration) throw usage(t("Missing agent id or --integration.", "Falta el id del agente o --integration."), t("Example", "Ejemplo") + `: ghosty channels whatsapp ${sub} <agent-id> --integration <formmy-integration-id> --answer off --dry-run`);
|
|
@@ -10150,8 +10187,31 @@ function bareMediaFlag(argv) {
|
|
|
10150
10187
|
if (argv[0] !== "channels" || !argv.includes("history")) return argv;
|
|
10151
10188
|
return argv.map((a, i) => a === "--media" && (argv[i + 1] === void 0 || argv[i + 1].startsWith("-")) ? "--media=" : a);
|
|
10152
10189
|
}
|
|
10153
|
-
|
|
10154
|
-
|
|
10190
|
+
function pausedLine(s, id) {
|
|
10191
|
+
if (!s.paused && !s.manual) return "";
|
|
10192
|
+
const txt = t(
|
|
10193
|
+
`${s.paused} paused by Formmy${s.pausedForever ? ` (${s.pausedForever} indefinitely)` : ""}, ${s.manual} in manual mode: the agent doesn't answer them`,
|
|
10194
|
+
`${s.paused} pausada(s) por Formmy${s.pausedForever ? ` (${s.pausedForever} sin fecha)` : ""}, ${s.manual} en modo manual: el agente no las contesta`
|
|
10195
|
+
);
|
|
10196
|
+
return `
|
|
10197
|
+
${lavender(txt)}
|
|
10198
|
+
${dim(`${t("See them", "Verlas")}: ghosty board ls ${id} --paused --limit 200`)}`;
|
|
10199
|
+
}
|
|
10200
|
+
async function pageWithRetry(fetchPage, where, waitMs = (n) => 5e3 * n, tries = 6) {
|
|
10201
|
+
for (let n = 1; ; n++) {
|
|
10202
|
+
try {
|
|
10203
|
+
return await fetchPage();
|
|
10204
|
+
} catch (e) {
|
|
10205
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
10206
|
+
const transient = e instanceof TypeError || /^(502|503|504)\b/.test(msg) || /fetch failed|ECONNRESET|ETIMEDOUT/i.test(msg);
|
|
10207
|
+
if (!transient || n >= tries) throw e;
|
|
10208
|
+
if (!globals.json) info(dim(t(` ${msg.slice(0, 60)} \u2014 resuming at ${where} in ${Math.round(waitMs(n) / 1e3)} s (${n}/${tries - 1})`, ` ${msg.slice(0, 60)} \u2014 reanudando en ${where} en ${Math.round(waitMs(n) / 1e3)} s (${n}/${tries - 1})`)));
|
|
10209
|
+
await new Promise((r) => setTimeout(r, waitMs(n)));
|
|
10210
|
+
}
|
|
10211
|
+
}
|
|
10212
|
+
}
|
|
10213
|
+
async function integrationsCmd(id, email) {
|
|
10214
|
+
const r = await api(`/api/v2/agents/${enc7(id)}/whatsapp/integrations${email ? `?email=${enc7(email)}` : ""}`);
|
|
10155
10215
|
const who2 = { gs: "gs", easybits: "EasyBits", droplet: "droplet", formmy: "Formmy", other: t("other", "otro") };
|
|
10156
10216
|
const rows = r.integrations.map((i) => ({
|
|
10157
10217
|
number: i.phoneNumber ? `+${i.phoneNumber}` : dim("?"),
|
|
@@ -10164,7 +10224,7 @@ async function integrationsCmd(id) {
|
|
|
10164
10224
|
return out(
|
|
10165
10225
|
r,
|
|
10166
10226
|
() => rows.length ? `${table(rows, ["number", "name", "answered by", "formmy forwards", "formmy agent", "integration"])}
|
|
10167
|
-
${dim(`${t("Next", "Siguiente")}: ghosty channels whatsapp link ${id} --integration <integration> --answer on|off --dry-run`)}` : dim(t(`${r.ownerEmail} has no WhatsApp Business numbers in Formmy.`, `${r.ownerEmail} no tiene n\xFAmeros de WhatsApp Business en Formmy.`))
|
|
10227
|
+
${dim(`${t("Next", "Siguiente")}: ghosty channels whatsapp link ${id} --integration <integration> --answer on|off --dry-run`)}` : dim(t(`${r.ownerEmail} has no WhatsApp Business numbers in Formmy (staff: --email <other Formmy account>).`, `${r.ownerEmail} no tiene n\xFAmeros de WhatsApp Business en Formmy (staff: --email <otra cuenta de Formmy>).`))
|
|
10168
10228
|
);
|
|
10169
10229
|
}
|
|
10170
10230
|
async function historyCmd(id, v) {
|
|
@@ -10187,7 +10247,8 @@ ${dim(v["dry-run"] ? t("Dry run: nothing copied.", "Simulacro: no se copi\xF3 na
|
|
|
10187
10247
|
let cursor2 = 0;
|
|
10188
10248
|
let done2 = 0;
|
|
10189
10249
|
while (cursor2 !== null) {
|
|
10190
|
-
const
|
|
10250
|
+
const at = cursor2;
|
|
10251
|
+
const p = await pageWithRetry(() => api(path, { method: "POST", body: { integrationId: v.integration, days, cursor: at, media: true } }), `${done2}/${c.conversations}`);
|
|
10191
10252
|
sum2.mediaCopied += p.mediaCopied ?? 0;
|
|
10192
10253
|
sum2.mediaFailed += p.mediaFailed ?? 0;
|
|
10193
10254
|
done2 += p.page;
|
|
@@ -10208,27 +10269,29 @@ ${dim(t("Dry run: nothing imported. Re-running later never duplicates.", "Simula
|
|
|
10208
10269
|
if (!c.conversations) return out({ ...c, imported: 0 }, () => dim(t(`Nothing to import: ${plan}.`, `Nada que importar: ${plan}.`)));
|
|
10209
10270
|
await confirm(`${bold(plan)}
|
|
10210
10271
|
${t(`Cards appear on the team's board (no "new lead" alerts).`, "Las tarjetas aparecen en el tablero del equipo (sin avisos de \xABlead nuevo\xBB).")}`, { yes: v.yes, retry: `ghosty channels whatsapp history ${id} --integration ${v.integration} --days ${days}` });
|
|
10211
|
-
const sum = { newCards: 0, updatedCards: 0, messages: 0, orders: 0, suppressed: 0, mediaCopied: 0, mediaFailed: 0 };
|
|
10272
|
+
const sum = { newCards: 0, updatedCards: 0, messages: 0, orders: 0, suppressed: 0, mediaCopied: 0, mediaFailed: 0, paused: 0, pausedForever: 0, manual: 0 };
|
|
10212
10273
|
let cursor = 0;
|
|
10213
10274
|
let done = 0;
|
|
10214
10275
|
while (cursor !== null) {
|
|
10215
|
-
const
|
|
10276
|
+
const at = cursor;
|
|
10277
|
+
const p = await pageWithRetry(() => api(path, { method: "POST", body: { integrationId: v.integration, days, cursor: at } }), `${done}/${c.conversations}`);
|
|
10216
10278
|
for (const k of Object.keys(sum)) sum[k] += p[k] ?? 0;
|
|
10217
10279
|
done += p.page;
|
|
10218
10280
|
if (!globals.json) info(dim(` ${done}/${p.total} ${t("conversations", "conversaciones")}`));
|
|
10219
10281
|
cursor = p.nextCursor;
|
|
10220
10282
|
}
|
|
10221
|
-
out({ ...sum, conversations: done }, () => `${lavender("\u2713")} ${t(`${sum.newCards} new card(s), ${sum.updatedCards} updated, ${sum.messages} message(s), ${sum.orders} order(s)`, `${sum.newCards} tarjeta(s) nueva(s), ${sum.updatedCards} actualizada(s), ${sum.messages} mensaje(s), ${sum.orders} pedido(s)`)}${files(sum)}${sum.suppressed ? dim(t(` \xB7 ${sum.suppressed} bot draft(s) the customer never saw, skipped`, ` \xB7 se omitieron ${sum.suppressed} borrador(es) del bot que el cliente nunca vio`)) : ""}`);
|
|
10283
|
+
out({ ...sum, conversations: done }, () => `${lavender("\u2713")} ${t(`${sum.newCards} new card(s), ${sum.updatedCards} updated, ${sum.messages} message(s), ${sum.orders} order(s)`, `${sum.newCards} tarjeta(s) nueva(s), ${sum.updatedCards} actualizada(s), ${sum.messages} mensaje(s), ${sum.orders} pedido(s)`)}${files(sum)}${sum.suppressed ? dim(t(` \xB7 ${sum.suppressed} bot draft(s) the customer never saw, skipped`, ` \xB7 se omitieron ${sum.suppressed} borrador(es) del bot que el cliente nunca vio`)) : ""}${pausedLine(sum, id)}`);
|
|
10222
10284
|
}
|
|
10223
10285
|
|
|
10224
10286
|
// src/commands/board.ts
|
|
10225
10287
|
var who = (role) => ({ user: t("customer", "cliente"), assistant: t("agent", "agente"), operator: t("operator", "operador") })[role] ?? role;
|
|
10226
10288
|
async function boardLs(id, v) {
|
|
10227
|
-
const qs = new URLSearchParams({ ...v.q ? { q: v.q } : {}, ...v.limit ? { limit: v.limit } : {} });
|
|
10289
|
+
const qs = new URLSearchParams({ ...v.q ? { q: v.q } : {}, ...v.limit ? { limit: v.limit } : {}, ...v.paused ? { paused: "1" } : {} });
|
|
10228
10290
|
const r = await api(`/api/v2/agents/${encodeURIComponent(id)}/board/conversations?${qs}`);
|
|
10229
|
-
const
|
|
10230
|
-
|
|
10231
|
-
|
|
10291
|
+
const paused = (c) => c.manual ? t("manual", "manual") : c.pausedUntil ? Date.parse(c.pausedUntil) - Date.now() > 365 * 864e5 ? t("indefinite", "indefinida") : c.pausedUntil.slice(0, 16).replace("T", " ") : "";
|
|
10292
|
+
const rows = r.conversations.map((c) => ({ folio: c.folio ? `#${c.folio}` : "", name: c.name ?? "", phone: c.phone, column: c.column ?? "", msgs: c.messages, last: (c.lastActivity ?? "").slice(0, 16).replace("T", " "), paused: paused(c) }));
|
|
10293
|
+
out(r, () => rows.length ? `${table(rows, ["folio", "name", "phone", "column", "msgs", "last", ...v.paused || rows.some((x) => x.paused) ? ["paused"] : []])}
|
|
10294
|
+
${dim(`\xAB${r.board}\xBB \xB7 ${t("thread", "hilo")}: ghosty board show ${id} <folio|phone>`)}` : dim(t(`No ${v.paused ? "paused " : ""}cards on \xAB${r.board}\xBB${v.q ? ` matching "${v.q}"` : ""}.`, `No hay tarjetas${v.paused ? " pausadas" : ""} en \xAB${r.board}\xBB${v.q ? ` que coincidan con "${v.q}"` : ""}.`)));
|
|
10232
10295
|
}
|
|
10233
10296
|
async function boardShow(id, ref, v) {
|
|
10234
10297
|
if (!ref) throw usage(t("Missing conversation.", "Falta la conversaci\xF3n."), t(`Example: ghosty board show ${id} "#12" (folio, phone, session or id; list them with ghosty board ls ${id})`, `Ejemplo: ghosty board show ${id} "#12" (folio, tel\xE9fono, sesi\xF3n o id; l\xEDstalas con ghosty board ls ${id})`));
|
|
@@ -10485,10 +10548,12 @@ var HELPS_ES = {
|
|
|
10485
10548
|
["--role R", "groups: equipo (el equipo: escribe en bases de datos, tools de admin) o cliente (como cualquier cliente)"],
|
|
10486
10549
|
["--cached", "groups ls: no le pide a WhatsApp la lista en vivo (m\xE1s r\xE1pido; puede faltar alg\xFAn grupo nuevo)"],
|
|
10487
10550
|
["--integration ID", "link/unlink/history: el id de la Integration del n\xFAmero de WhatsApp Business en Formmy"],
|
|
10551
|
+
["--email E", "integrations (staff): lista los n\xFAmeros de otra cuenta de Formmy"],
|
|
10488
10552
|
["--days N", "history: conversaciones activas en los \xFAltimos N d\xEDas (1-30, default 7)"],
|
|
10489
10553
|
["--media", "history: s\xF3lo re-copia los archivos que faltan (fotos, PDF, audios) de las conversaciones ya importadas"],
|
|
10490
10554
|
["--answer on|off", "link (obligatorio, sin default): on = este agente contesta a clientes reales; off = s\xF3lo registra"],
|
|
10491
10555
|
["--forward", "link --answer off: igual hace que Formmy reenv\xEDe, para que gs vea las conversaciones"],
|
|
10556
|
+
["--from easybits:ID", "import: directo del agente de EasyBits ($EASYBITS_API_KEY del due\xF1o); primero dice si su sesi\xF3n est\xE1 viva, sus llaves y grupos; las llaves s\xF3lo viven en memoria"],
|
|
10492
10557
|
["--from-file F", "import: {authCreds, authKeys, phone, groups} exportado de EasyBits"],
|
|
10493
10558
|
["--from-dir DIR", "import: una carpeta de auth multi-archivo de Baileys (creds.json + un archivo por llave; p. ej. store/auth de nanoclaw)"],
|
|
10494
10559
|
["--own-number yes|no", "import (obligatorio): el n\xFAmero es del agente, o de una persona (s\xF3lo su chat consigo misma es del due\xF1o)"],
|
|
@@ -10500,7 +10565,7 @@ var HELPS_ES = {
|
|
|
10500
10565
|
"Apagarlo sigue registrando los mensajes; nadie los contesta.",
|
|
10501
10566
|
"Los grupos son del WhatsApp PERSONAL emparejado al agente (no de WhatsApp Business). <group> es su nombre o su jid.",
|
|
10502
10567
|
"link mueve un n\xFAmero que ya vive en Formmy (EasyBits, un droplet) sin rehacer el alta de Meta; unlink lo regresa. S\xF3lo el due\xF1o, un admin del agente o el staff de Ghosty.",
|
|
10503
|
-
"integrations lista los n\xFAmeros de WhatsApp Business del due\xF1o en Formmy (n\xFAmero, nombre, qui\xE9n contesta hoy: gs, EasyBits, un droplet o Formmy) con el id de la Integration que piden link e history.",
|
|
10568
|
+
"integrations lista los n\xFAmeros de WhatsApp Business del due\xF1o en Formmy (n\xFAmero, nombre, qui\xE9n contesta hoy: gs, EasyBits, un droplet o Formmy) con el id de la Integration que piden link e history. Tambi\xE9n salen los n\xFAmeros que el espacio ya enlaz\xF3 en gs, aunque en Formmy sean de otra cuenta.",
|
|
10504
10569
|
"history trae al tablero las tarjetas, mensajes y pedidos recientes del n\xFAmero desde Formmy (enl\xE1zalo antes; --dry-run funciona sin enlazar, para decidir --days). Repetirlo nunca duplica: c\xF3rrelo antes del corte y otra vez justo en el corte. Los archivos (fotos, PDF, audios) tambi\xE9n se copian a gs: Formmy los borra a los 7 d\xEDas, as\xED que trae el n\xFAmero dentro de esa semana; una copia que fall\xF3 se reintenta con --media.",
|
|
10505
10570
|
"import: apaga primero el socket de EasyBits SIN logout (dos sockets se tumban entre s\xED; un logout invalida las llaves). Borra el archivo despu\xE9s."
|
|
10506
10571
|
]
|
|
@@ -10577,6 +10642,7 @@ var HELPS_ES = {
|
|
|
10577
10642
|
summary: "Las tarjetas del tablero de ventas de un agente (su tablero es del ESPACIO: debes ser miembro, un agente compartido no basta). Los tableros del espacio en s\xED: ghosty boards.",
|
|
10578
10643
|
flags: [
|
|
10579
10644
|
["--q TEXT", "ls: tarjetas cuyo nombre, tel\xE9fono o \xFAltimo mensaje contiene TEXT"],
|
|
10645
|
+
["--paused", "ls: s\xF3lo las tarjetas que el agente no contesta (pausadas, p. ej. las que trae history de Formmy \xABhasta 9999\xBB, o en modo manual)"],
|
|
10580
10646
|
["--limit N", "ls: tarjetas (default 20) \xB7 show: \xFAltimos N mensajes (default 20, m\xE1x. 500)"],
|
|
10581
10647
|
["--integration ID", "tarjetas de este canal/integraci\xF3n (p. ej. prueba)"],
|
|
10582
10648
|
["--column NAME", "tarjetas en esta columna"],
|
|
@@ -10860,11 +10926,11 @@ HELPS.channels = {
|
|
|
10860
10926
|
"ghosty channels whatsapp ls <agent-id>",
|
|
10861
10927
|
"ghosty channels whatsapp enable <agent-id> <number> [--dry-run] [--yes]",
|
|
10862
10928
|
"ghosty channels whatsapp disable <agent-id> <number> [--dry-run]",
|
|
10863
|
-
"ghosty channels whatsapp integrations <agent-id>",
|
|
10929
|
+
"ghosty channels whatsapp integrations <agent-id> [--email OTHER]",
|
|
10864
10930
|
"ghosty channels whatsapp link <agent-id> --integration <formmy-id> --answer on|off [--forward] [--dry-run] [--yes]",
|
|
10865
10931
|
"ghosty channels whatsapp unlink <agent-id> --integration <formmy-id> [--dry-run] [--yes]",
|
|
10866
10932
|
"ghosty channels whatsapp history <agent-id> --integration <formmy-id> [--days 7] [--media] [--dry-run] [--yes]",
|
|
10867
|
-
"ghosty channels wa import <agent-id> --from-file creds.json | --from-dir auth/ --own-number yes|no [--replace] [--no-connect] [--dry-run] [--yes]",
|
|
10933
|
+
"ghosty channels wa import <agent-id> --from easybits:<agent-id> | --from-file creds.json | --from-dir auth/ --own-number yes|no [--replace] [--no-connect] [--dry-run] [--yes]",
|
|
10868
10934
|
"ghosty channels wa pair <agent-id> --phone 5215512345678",
|
|
10869
10935
|
"ghosty channels wa status|pause <agent-id>",
|
|
10870
10936
|
"ghosty channels wa groups ls <agent-id> [--cached]",
|
|
@@ -10878,10 +10944,12 @@ HELPS.channels = {
|
|
|
10878
10944
|
["--role R", "groups: equipo (the team: writes to databases, admin tools) or cliente (like any customer)"],
|
|
10879
10945
|
["--cached", "groups ls: don't ask WhatsApp for the live list (faster; may miss new groups)"],
|
|
10880
10946
|
["--integration ID", "link/unlink/history: the WhatsApp Business number's Integration id in Formmy"],
|
|
10947
|
+
["--email E", "integrations (staff): list the numbers of another Formmy account"],
|
|
10881
10948
|
["--days N", "history: conversations active in the last N days (1-30, default 7)"],
|
|
10882
10949
|
["--media", "history: only re-copy the missing files (photos, PDFs, audio) of conversations already imported"],
|
|
10883
10950
|
["--answer on|off", "link (required, no default): on = this agent answers real customers; off = it only records"],
|
|
10884
10951
|
["--forward", "link --answer off: still make Formmy forward, so gs sees the conversations"],
|
|
10952
|
+
["--from easybits:ID", "import: straight from the EasyBits agent (owner's $EASYBITS_API_KEY); first prints whether its session is alive, its keys and groups; the keys stay in memory"],
|
|
10885
10953
|
["--from-file F", "import: {authCreds, authKeys, phone, groups} exported from EasyBits"],
|
|
10886
10954
|
["--from-dir DIR", "import: a Baileys multi-file auth folder (creds.json + one file per key; e.g. nanoclaw's store/auth)"],
|
|
10887
10955
|
["--own-number yes|no", "import (required): the number is the agent's, or a person's (only their self-chat is the owner's)"],
|
|
@@ -10898,6 +10966,7 @@ HELPS.channels = {
|
|
|
10898
10966
|
"ghosty channels whatsapp link <id> --integration 6a3af395\u2026 --answer off --dry-run",
|
|
10899
10967
|
"ghosty channels whatsapp history <id> --integration 6a3af395\u2026 --days 7 --dry-run",
|
|
10900
10968
|
"ghosty channels whatsapp history <id> --integration 6a3af395\u2026 --days 7 --media",
|
|
10969
|
+
"ghosty channels wa import <id> --from easybits:6a50056c\u2026 --own-number no --dry-run # reads $EASYBITS_API_KEY",
|
|
10901
10970
|
"ghosty channels wa import <id> --from-file pia-wa.json --own-number no --dry-run"
|
|
10902
10971
|
],
|
|
10903
10972
|
notes: [
|
|
@@ -10905,7 +10974,7 @@ HELPS.channels = {
|
|
|
10905
10974
|
"Disabling keeps logging messages; nobody answers them.",
|
|
10906
10975
|
"Groups belong to the PERSONAL WhatsApp paired to the agent (not WhatsApp Business). <group> is its name or its jid.",
|
|
10907
10976
|
"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.",
|
|
10908
|
-
"integrations lists the owner's WhatsApp Business numbers in Formmy (number, name, who answers today: gs, EasyBits, a droplet or Formmy) with the Integration id that link and history take.",
|
|
10977
|
+
"integrations lists the owner's WhatsApp Business numbers in Formmy (number, name, who answers today: gs, EasyBits, a droplet or Formmy) with the Integration id that link and history take. Numbers the workspace already linked in gs show up too, even if another Formmy account owns them.",
|
|
10909
10978
|
"history brings the number's recent cards, messages and orders from Formmy to the board (link it first; --dry-run works before linking, to choose --days). Re-running never duplicates: run it before the cutover and again right at it. Files (photos, PDFs, audio) are copied to gs too: Formmy deletes them after 7 days, so bring the number within that week; a copy that failed is retried with --media.",
|
|
10910
10979
|
"import: turn off the EasyBits socket first WITHOUT logout (two sockets knock each other off; a logout invalidates the keys). Delete the file afterwards."
|
|
10911
10980
|
],
|
|
@@ -10993,12 +11062,13 @@ HELPS.boards = {
|
|
|
10993
11062
|
HELPS.cards = {
|
|
10994
11063
|
summary: "The cards on an agent's sales board (its board belongs to the WORKSPACE: you must be a member, a shared agent is not enough). The workspace's boards themselves: ghosty boards.",
|
|
10995
11064
|
usage: [
|
|
10996
|
-
"ghosty cards ls <agent-id> [--q TEXT] [--limit 20]",
|
|
11065
|
+
"ghosty cards ls <agent-id> [--q TEXT] [--paused] [--limit 20]",
|
|
10997
11066
|
"ghosty cards show <agent-id> <folio|phone|session|id> [--limit 20]",
|
|
10998
11067
|
"ghosty cards archive <agent-id> [--integration ID] [--column NAME] [--before 30d|ISO] [--dry-run] [--yes]"
|
|
10999
11068
|
],
|
|
11000
11069
|
flags: [
|
|
11001
11070
|
["--q TEXT", "ls: cards whose name, phone or last message contains TEXT"],
|
|
11071
|
+
["--paused", "ls: only cards the agent doesn't answer (paused, e.g. the ones history brings from Formmy until 9999, or in manual mode)"],
|
|
11002
11072
|
["--limit N", "ls: cards (default 20) \xB7 show: last N messages (default 20, max 500)"],
|
|
11003
11073
|
["--integration ID", "cards from this channel/integration (e.g. prueba)"],
|
|
11004
11074
|
["--column NAME", "cards in this column"],
|
|
@@ -11006,7 +11076,7 @@ HELPS.cards = {
|
|
|
11006
11076
|
["-n, --dry-run", "count and show a sample, change nothing"],
|
|
11007
11077
|
["--yes", "skip the confirmation (required without a terminal)"]
|
|
11008
11078
|
],
|
|
11009
|
-
examples: ["ghosty cards ls <id> --q 7712", 'ghosty cards show <id> "#12"', "ghosty cards show <id> 5210000009922 --limit 50", "ghosty cards archive <id> --integration prueba --dry-run", "ghosty cards archive <id> --column Perdido --before 30d --yes"],
|
|
11079
|
+
examples: ["ghosty cards ls <id> --q 7712", "ghosty cards ls <id> --paused --limit 200", 'ghosty cards show <id> "#12"', "ghosty cards show <id> 5210000009922 --limit 50", "ghosty cards archive <id> --integration prueba --dry-run", "ghosty cards archive <id> --column Perdido --before 30d --yes"],
|
|
11010
11080
|
notes: ["show prints each message with who wrote it (customer, agent, operator), its attachment and whether gs keeps a copy of it.", "Archiving is reversible: when the customer writes again the card comes back. At least one filter is required."],
|
|
11011
11081
|
learn: `${REF}#tablero`
|
|
11012
11082
|
};
|
|
@@ -11449,12 +11519,12 @@ ${bold("Configure an agent")}
|
|
|
11449
11519
|
channels whatsapp integrations <id> Its owner's WhatsApp Business numbers in Formmy (number, who answers today, Integration id)
|
|
11450
11520
|
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]
|
|
11451
11521
|
channels whatsapp history <id> --integration ID [--days 7] Bring its recent cards, messages, orders and files from Formmy (no duplicates; Formmy keeps files 7 days) [--media] [--dry-run]
|
|
11452
|
-
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]
|
|
11522
|
+
channels wa import <id> --from easybits:ID | --from-file F | --from-dir DIR --own-number yes|no Move a personal WhatsApp session here, no QR [--dry-run] [--replace] [--no-connect]
|
|
11453
11523
|
channels wa pair <id> --phone N Pair a personal WhatsApp by code (no QR)
|
|
11454
11524
|
channels wa status|pause <id> Personal WhatsApp: how it is / pause it (keeps the session)
|
|
11455
11525
|
channels wa groups ls|enable|disable|set|create <id> [group] Personal WhatsApp groups it answers, and as whom [--role equipo|cliente] [--dry-run]
|
|
11456
11526
|
boards ls|create|assign|move-cards|rm A workspace's boards: create (--columns-from), where an agent sends cards, move cards
|
|
11457
|
-
cards ls <id> Recent cards on its sales board [--q TEXT] [--limit 20]
|
|
11527
|
+
cards ls <id> Recent cards on its sales board [--q TEXT] [--paused] [--limit 20]
|
|
11458
11528
|
cards show <id> <card> A card's thread: who wrote, text, attachments and whether gs kept a copy (folio, phone or id)
|
|
11459
11529
|
cards archive <id> Archive cards in bulk (reversible) --integration X | --column X | --before 30d [--dry-run]
|
|
11460
11530
|
|
|
@@ -11545,12 +11615,12 @@ ${bold("Configurar un agente")}
|
|
|
11545
11615
|
channels whatsapp integrations <id> Los n\xFAmeros de WhatsApp Business de su due\xF1o en Formmy (n\xFAmero, qui\xE9n contesta hoy, id de la Integration)
|
|
11546
11616
|
channels whatsapp link|unlink <id> --integration ID --answer on|off Trae un n\xFAmero de WhatsApp Business de Formmy/EasyBits a este agente, o devu\xE9lvelo [--forward] [--dry-run]
|
|
11547
11617
|
channels whatsapp history <id> --integration ID [--days 7] Trae de Formmy sus tarjetas, mensajes, pedidos y archivos recientes (sin duplicar; Formmy guarda los archivos 7 d\xEDas) [--media] [--dry-run]
|
|
11548
|
-
channels wa import <id> --from-file F | --from-dir DIR --own-number yes|no Mueve aqu\xED una sesi\xF3n de WhatsApp personal, sin QR [--dry-run] [--replace] [--no-connect]
|
|
11618
|
+
channels wa import <id> --from easybits:ID | --from-file F | --from-dir DIR --own-number yes|no Mueve aqu\xED una sesi\xF3n de WhatsApp personal, sin QR [--dry-run] [--replace] [--no-connect]
|
|
11549
11619
|
channels wa pair <id> --phone N Vincula un WhatsApp personal con c\xF3digo (sin QR)
|
|
11550
11620
|
channels wa status|pause <id> WhatsApp personal: c\xF3mo est\xE1 / pausarlo (conserva la sesi\xF3n)
|
|
11551
11621
|
channels wa groups ls|enable|disable|set|create <id> [grupo] Grupos de WhatsApp personal que contesta, y como qui\xE9n [--role equipo|cliente] [--dry-run]
|
|
11552
11622
|
boards ls|create|assign|move-cards|rm Los tableros de un espacio: crear (--columns-from), a d\xF3nde manda tarjetas un agente, mover tarjetas
|
|
11553
|
-
cards ls <id> Tarjetas recientes de su tablero de ventas [--q TEXTO] [--limit 20]
|
|
11623
|
+
cards ls <id> Tarjetas recientes de su tablero de ventas [--q TEXTO] [--paused] [--limit 20]
|
|
11554
11624
|
cards show <id> <tarjeta> El hilo de una tarjeta: qui\xE9n escribi\xF3, texto, adjuntos y si gs guard\xF3 copia (folio, tel\xE9fono o id)
|
|
11555
11625
|
cards archive <id> Archiva tarjetas en bloque (reversible) --integration X | --column X | --before 30d [--dry-run]
|
|
11556
11626
|
|
|
@@ -11694,6 +11764,8 @@ async function main() {
|
|
|
11694
11764
|
"unset-env": { type: "string" },
|
|
11695
11765
|
orphans: { type: "boolean" },
|
|
11696
11766
|
skipped: { type: "boolean" },
|
|
11767
|
+
paused: { type: "boolean" },
|
|
11768
|
+
email: { type: "string" },
|
|
11697
11769
|
keep: { type: "boolean" },
|
|
11698
11770
|
boxes: { type: "boolean" },
|
|
11699
11771
|
like: { type: "string" },
|
|
@@ -11755,7 +11827,7 @@ async function main() {
|
|
|
11755
11827
|
const tokens = splitTokens(pos, v);
|
|
11756
11828
|
globals.token = tokens.session;
|
|
11757
11829
|
globals.profile = v.profile;
|
|
11758
|
-
if (v.version) return out({ version: "0.
|
|
11830
|
+
if (v.version) return out({ version: "0.25.0" }, () => "0.25.0");
|
|
11759
11831
|
for (const n of normalizeArgs(pos, v)) if (!globals.json) info(dim(n));
|
|
11760
11832
|
const context = (await readConfig()).workspace;
|
|
11761
11833
|
if (!explicitToken()?.startsWith("gat_")) {
|
|
@@ -11874,7 +11946,7 @@ async function main() {
|
|
|
11874
11946
|
throw unknown(cmd, cmd, COMMANDS, "ghosty --help");
|
|
11875
11947
|
}
|
|
11876
11948
|
}
|
|
11877
|
-
var notice = updateNotice("0.
|
|
11949
|
+
var notice = updateNotice("0.25.0").catch(() => null);
|
|
11878
11950
|
main().then(
|
|
11879
11951
|
async () => {
|
|
11880
11952
|
const n = await notice;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ghostystudio/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.0",
|
|
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\nimport { createRequire as __cr } from \"node:module\"; const require = __cr(import.meta.url);' --define:__VERSION__=\\\"$npm_package_version\\\"",
|
|
18
18
|
"prepack": "npm run build",
|
|
19
|
-
"test": "for t in flags prompt api rows wa-auth resolve aliases i18n help completion photos; 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 resolve aliases i18n help completion photos channels-retry; 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",
|