@ghostystudio/cli 0.29.0 → 0.31.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 +23 -1
- package/dist/index.js +491 -80
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -8020,6 +8020,41 @@ var SERVER_MESSAGES = [
|
|
|
8020
8020
|
["pon al menos un filtro: sponsor, owner, workspace o engine", "add at least one filter: sponsor, owner, workspace or engine"],
|
|
8021
8021
|
["no hay una cuenta con ese correo", "there's no account with that email"],
|
|
8022
8022
|
["files: [{ name, mimeType, data (base64) }], hasta 8 de 10 MB", "files: [{ name, mimeType, data (base64) }], up to 8 of 10 MB"],
|
|
8023
|
+
["leer los hilos de otra persona: s\xF3lo el staff de Ghosty", "reading another person's conversations: Ghosty staff only"],
|
|
8024
|
+
["leer un hilo ajeno: s\xF3lo el staff de Ghosty", "reading someone else's conversation: Ghosty staff only"],
|
|
8025
|
+
["la salud de los turnos: s\xF3lo el staff de Ghosty", "turn health: Ghosty staff only"],
|
|
8026
|
+
["la bit\xE1cora de push: s\xF3lo el staff de Ghosty", "the push log: Ghosty staff only"],
|
|
8027
|
+
["las novedades de la app: s\xF3lo el staff de Ghosty", "app announcements: Ghosty staff only"],
|
|
8028
|
+
["falta user (correo)", "missing user (email)"],
|
|
8029
|
+
["no hay una cuenta con el correo {}", "there's no account with the email {}"],
|
|
8030
|
+
["limpiar el historial: due\xF1o, admin del agente o staff", "cleaning the history: the agent's owner, admin or staff"],
|
|
8031
|
+
["los hilos de cliente (channel) se archivan, no se borran: su memoria es la que los sigue atendiendo", "customer threads (channel) are archived, not deleted: their memory is what keeps serving them"],
|
|
8032
|
+
["kind: test | channel", "kind: test | channel"],
|
|
8033
|
+
["action: archive | rm", "action: archive | rm"],
|
|
8034
|
+
["before: 7d, 12h o una fecha ISO", "before: 7d, 12h or an ISO date"],
|
|
8035
|
+
["keep y drop van juntos", "keep and drop go together"],
|
|
8036
|
+
["los dos documentos tienen que ser tuyos y existir", "both documents must exist and be yours"],
|
|
8037
|
+
["es el mismo documento", "it's the same document"],
|
|
8038
|
+
["no se pudo borrar el duplicado", "couldn't delete the duplicate"],
|
|
8039
|
+
["t\xEDtulo y cuerpo son obligatorios", "title and body are required"],
|
|
8040
|
+
["Esa imagen ya la usa \xAB{}\xBB. Cada novedad lleva la suya.", "That image is already used by \xAB{}\xBB. Each announcement needs its own."],
|
|
8041
|
+
["Elige una hora futura.", "Pick a future time."],
|
|
8042
|
+
["S\xF3lo de 9 a 21 h (CDMX).", "Only from 9 to 21 h (CDMX)."],
|
|
8043
|
+
["Publ\xEDcala primero.", "Publish it first."],
|
|
8044
|
+
["Esta novedad ya se mand\xF3.", "This announcement was already sent."],
|
|
8045
|
+
["no hay novedad {}", "no announcement {}"],
|
|
8046
|
+
["lista, b\xFAsqueda y push: s\xF3lo kind=personal de quien lo abri\xF3; por liga: el suyo, y el due\xF1o/admin lee los de canal y prueba", "list, search and push: only kind=personal of whoever opened it; by link: their own, and the owner/admin reads channel and test threads"],
|
|
8047
|
+
["los espacios de otra persona: s\xF3lo el staff de Ghosty", "someone else's workspaces: Ghosty staff only"],
|
|
8048
|
+
["entrar al espacio de un cliente: s\xF3lo el staff de Ghosty", "entering a client's workspace: Ghosty staff only"],
|
|
8049
|
+
["cambiar el combo de un espacio: s\xF3lo el staff de Ghosty", "changing a workspace's combo: Ghosty staff only"],
|
|
8050
|
+
["ver la app de otra persona: s\xF3lo el staff de Ghosty", "seeing someone else's app: Ghosty staff only"],
|
|
8051
|
+
["las cajas de la flota: s\xF3lo el staff de Ghosty", "the fleet's boxes: Ghosty staff only"],
|
|
8052
|
+
["combo: {}", "combo: {}"],
|
|
8053
|
+
["{} no trae Ventas (combo {}): nadie va a ver este tablero; staff: ghosty spaces set {} --combo sales", "{} has no Sales (combo {}): nobody will see this board; staff: ghosty spaces set {} --combo sales"],
|
|
8054
|
+
["no hay columna \xAB{}\xBB en ese tablero; hay: {}", "no column \xAB{}\xBB on that board; there are: {}"],
|
|
8055
|
+
["~ WabaLink {}: s\xF3lo anuncios {} \u2192 {}", "~ WabaLink {}: ads only {} \u2192 {}"],
|
|
8056
|
+
[" s\xF3lo anuncios: on (s\xF3lo abre tarjeta lo que viene de un anuncio)", " ads only: on (only what comes from an ad opens a card)"],
|
|
8057
|
+
[" s\xF3lo anuncios: off", " ads only: off"],
|
|
8023
8058
|
// — Infra interna (casi nunca llega al CLI, pero por si acaso)
|
|
8024
8059
|
["SESSION_SECRET ausente en producci\xF3n \u2014 las cookies quedar\xEDan firmadas con un valor p\xFAblico. Inyectarlo con ./scripts/set_gs_secret.sh SESSION_SECRET (openssl rand -hex 32).", "SESSION_SECRET missing in production \u2014 cookies would be signed with a public value. Inject it with ./scripts/set_gs_secret.sh SESSION_SECRET (openssl rand -hex 32)."],
|
|
8025
8060
|
["sqld: fetch sin namespace", "sqld: fetch without namespace"],
|
|
@@ -8322,7 +8357,7 @@ async function postLong(path, body, timeoutMs = 9e5) {
|
|
|
8322
8357
|
const req = httpsRequest(`${base}${path}`, {
|
|
8323
8358
|
method: "POST",
|
|
8324
8359
|
timeout: timeoutMs,
|
|
8325
|
-
headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.
|
|
8360
|
+
headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.31.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
|
|
8326
8361
|
}, (res) => {
|
|
8327
8362
|
let buf = "";
|
|
8328
8363
|
res.on("data", (c) => buf += c);
|
|
@@ -8364,7 +8399,7 @@ async function request(path, opts = {}, retried = false) {
|
|
|
8364
8399
|
method: opts.method ?? (opts.body !== void 0 || opts.raw !== void 0 ? "POST" : "GET"),
|
|
8365
8400
|
headers: {
|
|
8366
8401
|
authorization: `Bearer ${token}`,
|
|
8367
|
-
"user-agent": `ghosty-cli/${"0.
|
|
8402
|
+
"user-agent": `ghosty-cli/${"0.31.0"}`,
|
|
8368
8403
|
"accept-language": lang(),
|
|
8369
8404
|
...opts.body !== void 0 ? { "content-type": "application/json" } : {},
|
|
8370
8405
|
...opts.headers
|
|
@@ -8391,7 +8426,7 @@ async function toError(res) {
|
|
|
8391
8426
|
if (res.status === 401) return new CliError(t("Your session is not valid anymore.", "Tu sesi\xF3n ya no es v\xE1lida."), EXIT.AUTH, t("Run: ghosty login", "Corre: ghosty login"));
|
|
8392
8427
|
if (res.status === 403) return new CliError(t(`Not allowed: ${detail}`, `No permitido: ${detail}`), EXIT.API, t("If the agent was shared with you, its workspace (board, channels, numbers) stays with the owner: ask them to add you to that workspace. Otherwise run: ghosty login", "Si el agente se comparti\xF3 contigo, su espacio (tablero, canales, n\xFAmeros) sigue siendo del due\xF1o: p\xEDdele que te agregue a ese espacio. Si no, corre: ghosty login"));
|
|
8393
8428
|
if (res.status === 404 && (body.message || body.error)) return new CliError(detail, EXIT.API);
|
|
8394
|
-
if (res.status === 404) return new CliError(t("Not found.", "No se encontr\xF3."), EXIT.API, t("Check the agent id (ghosty agents ls) or that your token belongs to that agent.", "Revisa el id del agente (ghosty agents ls) o que tu token sea de ese agente."));
|
|
8429
|
+
if (res.status === 404) return new CliError(t("Not found \u2014 or you don't have access to it.", "No se encontr\xF3, o no tienes acceso."), EXIT.API, t("Check the agent id (ghosty agents ls) or that your token belongs to that agent. Ghosty staff on a client's workspace: ghosty spaces join <workspace>", "Revisa el id del agente (ghosty agents ls) o que tu token sea de ese agente. Staff de Ghosty en el espacio de un cliente: ghosty spaces join <espacio>"));
|
|
8395
8430
|
if (body.error === "restart_no_aplica" || body.error === "agente_sin_maquina") {
|
|
8396
8431
|
return new CliError(t("This agent doesn't need a restart: new files, skills and MCP load on its next turn.", "Este agente no necesita reinicio: los archivos, skills y MCP nuevos se cargan en su siguiente turno."), EXIT.API);
|
|
8397
8432
|
}
|
|
@@ -8406,6 +8441,9 @@ import { dirname as dirname2, join as join3, relative, resolve } from "node:path
|
|
|
8406
8441
|
// src/prompt.ts
|
|
8407
8442
|
import { createInterface } from "node:readline/promises";
|
|
8408
8443
|
var canPrompt = () => Boolean(process.stdin.isTTY && process.stderr.isTTY);
|
|
8444
|
+
function applyHint(cmd) {
|
|
8445
|
+
return t(`Apply: ${cmd}${canPrompt() ? "" : " --yes"}`, `Para aplicarlo: ${cmd}${canPrompt() ? "" : " --yes"}`);
|
|
8446
|
+
}
|
|
8409
8447
|
function requireYesIfHeadless(yes, retry) {
|
|
8410
8448
|
if (!yes && !canPrompt()) throw usage(t("--yes required when not running interactively.", "Hace falta --yes cuando no corre en una terminal."), t(`Run: ${retry} --yes`, `Corre: ${retry} --yes`));
|
|
8411
8449
|
}
|
|
@@ -8443,12 +8481,12 @@ var enc = encodeURIComponent;
|
|
|
8443
8481
|
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
8444
8482
|
var MIME = { jpg: "image/jpeg", jpeg: "image/jpeg", png: "image/png", webp: "image/webp", gif: "image/gif", pdf: "application/pdf", ogg: "audio/ogg", opus: "audio/ogg", mp3: "audio/mpeg", m4a: "audio/mp4", wav: "audio/wav", mp4: "video/mp4" };
|
|
8445
8483
|
async function localFiles(paths) {
|
|
8446
|
-
const { readFile:
|
|
8484
|
+
const { readFile: readFile14 } = await import("node:fs/promises");
|
|
8447
8485
|
const { basename: basename4 } = await import("node:path");
|
|
8448
8486
|
return Promise.all(
|
|
8449
8487
|
paths.map(async (p) => {
|
|
8450
8488
|
const ext = p.toLowerCase().split(".").pop() ?? "";
|
|
8451
|
-
return { name: basename4(p), mimeType: MIME[ext] ?? "application/octet-stream", data: (await
|
|
8489
|
+
return { name: basename4(p), mimeType: MIME[ext] ?? "application/octet-stream", data: (await readFile14(p)).toString("base64") };
|
|
8452
8490
|
})
|
|
8453
8491
|
);
|
|
8454
8492
|
}
|
|
@@ -9067,7 +9105,7 @@ function capacityLine(c) {
|
|
|
9067
9105
|
// src/commands/chat.ts
|
|
9068
9106
|
import { randomUUID } from "node:crypto";
|
|
9069
9107
|
import { createInterface as createInterface2 } from "node:readline/promises";
|
|
9070
|
-
import { readFile as readFile4 } from "node:fs/promises";
|
|
9108
|
+
import { readFile as readFile4, writeFile as writeFile4 } from "node:fs/promises";
|
|
9071
9109
|
import { basename as basename2 } from "node:path";
|
|
9072
9110
|
|
|
9073
9111
|
// src/sse.ts
|
|
@@ -9177,7 +9215,10 @@ async function chat(agent, message, v) {
|
|
|
9177
9215
|
first = true;
|
|
9178
9216
|
if (!globals.json) info(dim(t(`conversation ${sid} (continue it with --conversation ${sid})`, `conversaci\xF3n ${sid} (s\xEDguela con --conversation ${sid})`)));
|
|
9179
9217
|
}
|
|
9180
|
-
const images =
|
|
9218
|
+
const images = [
|
|
9219
|
+
...v.media?.length ? await localFiles(v.media) : [],
|
|
9220
|
+
...(v.file ?? "").split(",").map((x) => x.trim()).filter(Boolean).map((fileId) => ({ fileId }))
|
|
9221
|
+
];
|
|
9181
9222
|
if (message || images.length) {
|
|
9182
9223
|
await turn(agent, sid, message ?? "", first, images);
|
|
9183
9224
|
return;
|
|
@@ -9211,9 +9252,20 @@ async function readAll() {
|
|
|
9211
9252
|
return s;
|
|
9212
9253
|
}
|
|
9213
9254
|
async function meFiles(sub, arg, v) {
|
|
9214
|
-
const ex = "ghosty me files ls [--kind image|video|audio|document|other] | upload <file> | rm <file-id>";
|
|
9255
|
+
const ex = "ghosty me files ls [--q TEXT] [--kind image|video|audio|document|other] [--cursor C] | get <file-id> [--out FILE] | upload <file> | rm <file-id>";
|
|
9215
9256
|
switch (sub) {
|
|
9216
9257
|
case "ls": {
|
|
9258
|
+
if (v.q !== void 0 || v.cursor) {
|
|
9259
|
+
const q = new URLSearchParams({ q: v.q ?? "", ...v.kind ? { kind: v.kind } : {}, ...v.cursor ? { cursor: v.cursor } : {}, ...v.limit ? { limit: v.limit } : {} });
|
|
9260
|
+
const r2 = await api(`/api/v2/me/files?${q}`);
|
|
9261
|
+
out(r2, () => {
|
|
9262
|
+
const rows = r2.files.map((f) => ({ id: f.id, name: f.name, kind: f.kind, size: f.size, created: f.createdAt.slice(0, 10) }));
|
|
9263
|
+
return `${table(rows, ["id", "name", "kind", "size", "created"])}${r2.nextCursor ? `
|
|
9264
|
+
${dim(t(`More: ghosty me files ls --q "${v.q ?? ""}" --cursor ${r2.nextCursor}`, `M\xE1s: ghosty me files ls --q "${v.q ?? ""}" --cursor ${r2.nextCursor}`))}` : ""}
|
|
9265
|
+
${dim(t('Attach one in a chat: ghosty chat <agent> "\u2026" --file <id>', 'Adj\xFAntalo en un chat: ghosty chat <agente> "\u2026" --file <id>'))}`;
|
|
9266
|
+
});
|
|
9267
|
+
return;
|
|
9268
|
+
}
|
|
9217
9269
|
const r = await api(
|
|
9218
9270
|
`/api/v2/me/files${v.kind ? `?kind=${enc3(v.kind)}` : ""}`
|
|
9219
9271
|
);
|
|
@@ -9224,6 +9276,18 @@ ${dim(t(`${(r.storage.usedBytes / 1e9).toFixed(2)} GB of ${r.storage.limitGb} GB
|
|
|
9224
9276
|
});
|
|
9225
9277
|
return;
|
|
9226
9278
|
}
|
|
9279
|
+
case "get": {
|
|
9280
|
+
if (!arg) throw usage(t("Missing file id.", "Falta el id del archivo."), ex);
|
|
9281
|
+
const r = await api(`/api/v2/me/files/${enc3(arg)}`);
|
|
9282
|
+
if (v.out) {
|
|
9283
|
+
const res = await fetch(r.url);
|
|
9284
|
+
if (!res.ok) throw new CliError(`${res.status} ${t("downloading the file", "al bajar el archivo")}`, EXIT.API);
|
|
9285
|
+
const bytes = Buffer.from(await res.arrayBuffer());
|
|
9286
|
+
await writeFile4(v.out, bytes);
|
|
9287
|
+
return out({ id: arg, saved: v.out, bytes: bytes.length }, () => `${lavender("\u2713")} ${v.out} (${bytes.length} bytes)`);
|
|
9288
|
+
}
|
|
9289
|
+
return out(r, () => r.url);
|
|
9290
|
+
}
|
|
9227
9291
|
case "upload": {
|
|
9228
9292
|
if (!arg) throw usage(t("Missing file.", "Falta el archivo."), ex);
|
|
9229
9293
|
const bytes = await readFile4(arg);
|
|
@@ -9378,7 +9442,7 @@ ${dim(`${t("conversation", "conversaci\xF3n")} ${p.sessionId}`)}`
|
|
|
9378
9442
|
|
|
9379
9443
|
// src/commands/partner.ts
|
|
9380
9444
|
import { createHmac } from "node:crypto";
|
|
9381
|
-
import { readFile as readFile5, writeFile as
|
|
9445
|
+
import { readFile as readFile5, writeFile as writeFile5 } from "node:fs/promises";
|
|
9382
9446
|
var partnerHelp = () => t(
|
|
9383
9447
|
`${bold("Usage:")} ghosty partner <command> [options]
|
|
9384
9448
|
|
|
@@ -9465,7 +9529,7 @@ async function call(path, v, init = {}) {
|
|
|
9465
9529
|
"x-ghosty-ts": ts,
|
|
9466
9530
|
"x-ghosty-sig": sig,
|
|
9467
9531
|
"content-type": "application/json",
|
|
9468
|
-
"user-agent": `ghosty-cli/${"0.
|
|
9532
|
+
"user-agent": `ghosty-cli/${"0.31.0"}`
|
|
9469
9533
|
},
|
|
9470
9534
|
body: method === "GET" ? void 0 : raw
|
|
9471
9535
|
});
|
|
@@ -9489,7 +9553,7 @@ GHOSTY_PARTNER_KEY_SECRET=${r.secret}
|
|
|
9489
9553
|
`;
|
|
9490
9554
|
async function showSecret(r, v) {
|
|
9491
9555
|
if (v["env-file"]) {
|
|
9492
|
-
await
|
|
9556
|
+
await writeFile5(v["env-file"], envLines(r), { mode: 384 });
|
|
9493
9557
|
return out({ keyId: r.keyId, envFile: v["env-file"] }, () => `${lavender("\u2713")} ${r.keyId} ${t(`\u2192 ${v["env-file"]} (mode 600; the secret was not printed)`, `\u2192 ${v["env-file"]} (modo 600; el secreto no se imprimi\xF3)`)}`);
|
|
9494
9558
|
}
|
|
9495
9559
|
if (v.format === "env") return void process.stdout.write(envLines(r));
|
|
@@ -9892,8 +9956,29 @@ ${dim(`${t(`${rows.length} row(s)`, `${rows.length} fila(s)`)} \xB7 ${Math.round
|
|
|
9892
9956
|
throw usage(t(`Unknown dbs subcommand: ${sub}.`, `Subcomando de dbs desconocido: ${sub}.`), t("Try", "Prueba") + ": ghosty dbs ls | tables | query | import | rehost | grant | revoke | rename | rm (ghosty dbs --help)");
|
|
9893
9957
|
}
|
|
9894
9958
|
}
|
|
9959
|
+
async function docsDedupe(v) {
|
|
9960
|
+
const path = "/api/v2/me/documents/dedupe";
|
|
9961
|
+
const pair = v.keep || v.drop ? { keep: v.keep, drop: v.drop } : {};
|
|
9962
|
+
const plan = await api(path, { method: "POST", body: { ...pair, dryRun: true } });
|
|
9963
|
+
const name = (d) => typeof d === "string" ? d : `\xAB${d.name}\xBB ${dim(`${d.id.slice(0, 10)} \xB7 ${d.agents} ${t("agent(s)", "agente(s)")}`)}`;
|
|
9964
|
+
const text = [
|
|
9965
|
+
...plan.merges.map((m) => `${t("keep", "queda")} ${name(m.keep)}
|
|
9966
|
+
${m.drop.map((d) => ` - ${name(d)}`).join("\n")}`),
|
|
9967
|
+
...plan.sameName?.length ? ["", dim(t("Same name, different content (not merged; if they're the same: ghosty agents docs dedupe <keep-id> <drop-id>):", "Mismo nombre, otro contenido (no se unen; si s\xED son el mismo: ghosty agents docs dedupe <id-que-queda> <id-que-se-va>):")), ...plan.sameName.map((g) => ` ${g.map(name).join(" \xB7 ")}`)] : []
|
|
9968
|
+
].join("\n");
|
|
9969
|
+
if (!plan.merges.length) return out(plan, () => text.trim() ? text : dim(t("No duplicate documents.", "Sin documentos duplicados.")));
|
|
9970
|
+
if (v["dry-run"]) return out(plan, () => `${text}
|
|
9971
|
+
${dim(t("Dry run: nothing changed.", "Simulaci\xF3n: no cambi\xF3 nada."))}`);
|
|
9972
|
+
const retry = `ghosty agents docs dedupe${v.keep ? ` ${v.keep} ${v.drop ?? ""}` : ""}`;
|
|
9973
|
+
requireYesIfHeadless(v.yes, retry);
|
|
9974
|
+
await confirm(`${text}
|
|
9975
|
+
${t("Merge? The extra copies are deleted and their agents keep access through the one that stays.", "\xBFUnir? Las copias sobrantes se borran y sus agentes siguen leyendo por la que queda.")}`, { yes: v.yes, retry });
|
|
9976
|
+
const r = await api(path, { method: "POST", body: pair });
|
|
9977
|
+
out(r, () => `${lavender("\u2713")} ${t(`${r.merged} duplicate(s) merged \xB7 ${r.grantsMoved} permission(s) moved.`, `${r.merged} duplicado(s) unido(s) \xB7 ${r.grantsMoved} permiso(s) movido(s).`)}`);
|
|
9978
|
+
}
|
|
9895
9979
|
async function agentDocsCmd(sub, id, docs, v) {
|
|
9896
9980
|
let doc = docs[0];
|
|
9981
|
+
if (sub === "dedupe") return docsDedupe(v);
|
|
9897
9982
|
id = need2(id, t("agent id", "el id del agente"), "ghosty agents docs ls <agent-id>");
|
|
9898
9983
|
switch (sub ?? "ls") {
|
|
9899
9984
|
case "ls":
|
|
@@ -10262,9 +10347,10 @@ async function channelsCmd(kind, sub, id, ref, v) {
|
|
|
10262
10347
|
number: n2.phoneNumber ? `+${n2.phoneNumber}${n2.label !== n2.phoneNumber && n2.label !== n2.id ? ` ${n2.label}` : ""}` : n2.label,
|
|
10263
10348
|
answers: n2.mine ? lavender(t("this agent", "este agente")) : n2.recordsOnly ? t("this agent (records only)", "este agente (s\xF3lo registra)") : n2.takenByOther ? t("another agent", "otro agente") : dim(t("nobody (silent)", "nadie (en silencio)")),
|
|
10264
10349
|
"formmy forwards": fwd(n2),
|
|
10350
|
+
"ads only": n2.adsOnly ? lavender(t("yes", "s\xED")) : "",
|
|
10265
10351
|
id: n2.id
|
|
10266
10352
|
}));
|
|
10267
|
-
return out(cur, () => rows.length ? table(rows, ["number", "answers", "formmy forwards", "id"]) : dim(t(`No WhatsApp Business numbers yet. Pair one at ${cur.salesUrl}`, `Todav\xEDa no hay n\xFAmeros de WhatsApp Business. Parea uno en ${cur.salesUrl}`)));
|
|
10353
|
+
return out(cur, () => rows.length ? table(rows, ["number", "answers", "formmy forwards", "ads only", "id"]) : dim(t(`No WhatsApp Business numbers yet. Pair one at ${cur.salesUrl}`, `Todav\xEDa no hay n\xFAmeros de WhatsApp Business. Parea uno en ${cur.salesUrl}`)));
|
|
10268
10354
|
}
|
|
10269
10355
|
if (sub !== "enable" && sub !== "disable") throw usage(t(`Unknown subcommand: ${sub}.`, `Subcomando desconocido: ${sub}.`), t("Try", "Prueba") + ": ghosty channels whatsapp ls | enable | disable | integrations | link | unlink | history \xB7 ghosty channels wa import | status | pause | groups");
|
|
10270
10356
|
if (!ref) throw usage(t("Missing number.", "Falta el n\xFAmero."), t("Example", "Ejemplo") + `: ghosty channels whatsapp ${sub} ${id} +52 55 1234 5678`);
|
|
@@ -10290,7 +10376,7 @@ function findGroup(groups, ref) {
|
|
|
10290
10376
|
}
|
|
10291
10377
|
async function groupsCmd(args, v) {
|
|
10292
10378
|
const [sub, id, ...rest] = args;
|
|
10293
|
-
const ex = 'ghosty channels wa groups ls <agent-id> | set <agent-id> <group> [--role equipo|cliente] [--on|--off] | create <agent-id> "Name"';
|
|
10379
|
+
const ex = 'ghosty channels wa groups ls <agent-id> | set <agent-id> <group> [--role equipo|cliente] [--on|--off] [--name "New name"] | create <agent-id> "Name"';
|
|
10294
10380
|
if (!id) throw usage(t("Missing agent id.", "Falta el id del agente."), `${t("Example", "Ejemplo")}: ${ex}`);
|
|
10295
10381
|
const path = `/api/v2/agents/${enc7(id)}/wa/groups`;
|
|
10296
10382
|
if (v.role !== void 0 && v.role !== "equipo" && v.role !== "cliente") throw usage(t("--role takes equipo or cliente.", "--role acepta equipo o cliente."));
|
|
@@ -10300,10 +10386,10 @@ async function groupsCmd(args, v) {
|
|
|
10300
10386
|
return out(r2, () => rows.length ? table(rows, ["name", "on", "role", "jid"]) : dim(t('No groups. Pair its personal WhatsApp in the web, or: ghosty channels wa groups create <agent-id> "Name"', 'Sin grupos. Parea su WhatsApp personal en la web, o: ghosty channels wa groups create <agent-id> "Nombre"')));
|
|
10301
10387
|
}
|
|
10302
10388
|
if (sub === "create") {
|
|
10303
|
-
const
|
|
10304
|
-
if (!
|
|
10305
|
-
const r2 = await api(path, { method: "POST", body: { create:
|
|
10306
|
-
return out(r2, () => `${lavender("\u2713")} ${t("Created", "Creado")} ${bold(
|
|
10389
|
+
const name2 = rest.join(" ").trim();
|
|
10390
|
+
if (!name2) throw usage(t("Missing group name.", "Falta el nombre del grupo."), t("Example", "Ejemplo") + `: ghosty channels wa groups create ${id} "Equipo ventas" --role equipo`);
|
|
10391
|
+
const r2 = await api(path, { method: "POST", body: { create: name2, role: v.role ?? "equipo" } });
|
|
10392
|
+
return out(r2, () => `${lavender("\u2713")} ${t("Created", "Creado")} ${bold(name2)} (${v.role ?? "equipo"}, on)
|
|
10307
10393
|
${t("invite", "invitaci\xF3n")}: ${r2.inviteUrl}`);
|
|
10308
10394
|
}
|
|
10309
10395
|
if (sub !== "set") throw usage(t(`Unknown groups subcommand: ${sub}.`, `Subcomando de groups desconocido: ${sub}.`), ex);
|
|
@@ -10314,16 +10400,17 @@ async function groupsCmd(args, v) {
|
|
|
10314
10400
|
const g = findGroup(cur.groups, ref);
|
|
10315
10401
|
const enabled = v.on ? true : v.off ? false : g.enabled;
|
|
10316
10402
|
const role = v.role ?? g.role;
|
|
10317
|
-
const
|
|
10403
|
+
const name = v.name?.trim() || void 0;
|
|
10404
|
+
const changes = [...enabled !== g.enabled ? [`${g.enabled ? "on" : "off"}\u2192${enabled ? "on" : "off"}`] : [], ...role !== g.role ? [`${g.role}\u2192${role}`] : [], ...name && name !== g.name ? [`\xAB${g.name}\xBB\u2192\xAB${name}\xBB`] : []];
|
|
10318
10405
|
if (!changes.length) return out({ changed: 0, group: g }, () => dim(t(`Nothing to do: ${g.name} is already ${g.enabled ? "on" : "off"} as ${g.role}.`, `Nada que hacer: ${g.name} ya est\xE1 ${g.enabled ? "encendido" : "apagado"} como ${g.role}.`)));
|
|
10319
10406
|
const plan = `~ ${g.name}: ${changes.join(", ")}`;
|
|
10320
10407
|
if (v["dry-run"]) return out({ dryRun: true, plan: [plan] }, () => `${plan}
|
|
10321
10408
|
${dim(t("Dry run: nothing changed.", "Simulacro: no se cambi\xF3 nada."))}`);
|
|
10322
10409
|
if (enabled && !g.enabled) await confirm(`${bold(plan)}
|
|
10323
10410
|
${t(`The agent will start answering in this group as ${role}.`, `El agente empezar\xE1 a contestar en este grupo como ${role}.`)}`, { yes: v.yes, retry: `ghosty channels wa groups set ${id} "${ref}"${v.role ? ` --role ${v.role}` : ""} --on` });
|
|
10324
|
-
const r = await api(path, { method: "POST", body: { jid: g.jid, enabled, role } });
|
|
10411
|
+
const r = await api(path, { method: "POST", body: { jid: g.jid, enabled, role, ...name ? { name } : {} } });
|
|
10325
10412
|
const now = r.groups.find((x) => x.jid === g.jid);
|
|
10326
|
-
return out({ group: now ?? null, plan: [plan] }, () => `${lavender("\u2713")} ${g.name}: ${now?.enabled ? "on" : "off"} ${t("as", "como")} ${now?.role ?? role}`);
|
|
10413
|
+
return out({ group: now ?? null, plan: [plan] }, () => `${lavender("\u2713")} ${now?.name ?? g.name}: ${now?.enabled ? "on" : "off"} ${t("as", "como")} ${now?.role ?? role}`);
|
|
10327
10414
|
}
|
|
10328
10415
|
var EB_EXPORT = (agentId) => `${(process.env.EASYBITS_URL ?? "https://www.easybits.cloud").replace(/\/+$/, "")}/api/v2/fleet-agents/${enc7(agentId)}/baileys/export`;
|
|
10329
10416
|
async function easybitsSession(agentId, keys) {
|
|
@@ -10407,17 +10494,27 @@ ${dim(eb ? t(`Next: ghosty channels wa groups ls ${id}`, `Sigue: ghosty channels
|
|
|
10407
10494
|
}
|
|
10408
10495
|
async function linkCmd(sub, id, v) {
|
|
10409
10496
|
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`);
|
|
10497
|
+
if (v["ads-only"] !== void 0 && v["ads-only"] !== "on" && v["ads-only"] !== "off") throw usage(t("--ads-only takes on or off.", "--ads-only acepta on u off."));
|
|
10498
|
+
const adsOnly = v["ads-only"] === void 0 ? void 0 : v["ads-only"] === "on";
|
|
10499
|
+
if (sub === "link" && adsOnly !== void 0 && v.answer === void 0) {
|
|
10500
|
+
const path2 = `/api/v2/agents/${enc7(id)}/whatsapp/link`;
|
|
10501
|
+
const r2 = await api(path2, { method: "POST", body: { integrationId: v.integration, adsOnly, ...v["dry-run"] ? { dryRun: true } : {} } });
|
|
10502
|
+
return out(r2, () => `${r2.plan.map((x) => serverText(x)).join("\n")}${v["dry-run"] ? `
|
|
10503
|
+
${dim(applyHint(`ghosty channels whatsapp link ${id} --integration ${v.integration} --ads-only ${v["ads-only"]}`))}` : ""}`);
|
|
10504
|
+
}
|
|
10410
10505
|
if (sub === "link" && v.answer !== "on" && v.answer !== "off") throw usage(t("Missing --answer on|off.", "Falta --answer on|off."), t("on = this agent answers real customers \xB7 off = it only records (add --forward so gs still sees the conversations)", "on = este agente contesta a clientes reales \xB7 off = s\xF3lo registra (agrega --forward para que gs siga viendo las conversaciones)"));
|
|
10411
10506
|
const path = `/api/v2/agents/${enc7(id)}/whatsapp/link`;
|
|
10412
|
-
const body = { integrationId: v.integration, ...sub === "unlink" ? { unlink: true } : { answer: v.answer === "on", ...v.forward ? { forward: true } : {} } };
|
|
10507
|
+
const body = { integrationId: v.integration, ...sub === "unlink" ? { unlink: true } : { answer: v.answer === "on", ...v.forward ? { forward: true } : {}, ...adsOnly !== void 0 ? { adsOnly } : {} } };
|
|
10508
|
+
const cmd = `ghosty channels whatsapp ${sub} ${id} --integration ${v.integration}${sub === "link" ? ` --answer ${v.answer}${v.forward ? " --forward" : ""}${v["ads-only"] ? ` --ads-only ${v["ads-only"]}` : ""}` : ""}`;
|
|
10413
10509
|
const plan = await api(path, { method: "POST", body: { ...body, dryRun: true } });
|
|
10414
10510
|
const text = `${plan.plan.map((x) => serverText(x)).join("\n")}
|
|
10415
10511
|
${dim(`Formmy: ${plan.formmy.before.url ?? "null"} \u2192 ${plan.formmy.after.url ?? "null"} (${t("as", "como")} ${plan.formmy.by})`)}`;
|
|
10416
10512
|
if (v["dry-run"]) return out(plan, () => `${text}
|
|
10417
|
-
${dim(t("Dry run: nothing changed.", "Simulacro: no se cambi\xF3 nada."))}
|
|
10513
|
+
${dim(t("Dry run: nothing changed.", "Simulacro: no se cambi\xF3 nada."))}
|
|
10514
|
+
${dim(applyHint(cmd))}`);
|
|
10418
10515
|
const digits4 = (plan.number.phoneNumberId ?? v.integration).slice(-4);
|
|
10419
10516
|
await confirm(`${bold(text)}
|
|
10420
|
-
${sub === "link" ? t("From now on this number's messages reach this agent in gs.", "Desde ahora los mensajes de este n\xFAmero le llegan a este agente en gs.") : t("This number goes back to where it was before.", "Este n\xFAmero regresa a donde estaba antes.")}`, { yes: v.yes, expect: digits4, retry:
|
|
10517
|
+
${sub === "link" ? t("From now on this number's messages reach this agent in gs.", "Desde ahora los mensajes de este n\xFAmero le llegan a este agente en gs.") : t("This number goes back to where it was before.", "Este n\xFAmero regresa a donde estaba antes.")}`, { yes: v.yes, expect: digits4, retry: cmd });
|
|
10421
10518
|
const r = await api(path, { method: "POST", body });
|
|
10422
10519
|
return out(r, () => `${lavender("\u2713")} ${sub === "link" ? t(`Linked (link ${r.linkId}). Check: ghosty channels whatsapp ls ${id} \xB7 ghosty turns ls ${id} --since 10m`, `Enlazado (enlace ${r.linkId}). Revisa: ghosty channels whatsapp ls ${id} \xB7 ghosty turns ls ${id} --since 10m`) : t("Unlinked: Formmy points where it did before.", "Desenlazado: Formmy apunta a donde apuntaba antes.")}`);
|
|
10423
10520
|
}
|
|
@@ -10471,7 +10568,9 @@ async function historyCmd(id, v) {
|
|
|
10471
10568
|
if (!Number.isInteger(days) || days < 1 || days > 30) throw usage(t("--days takes 1 to 30.", "--days acepta de 1 a 30."));
|
|
10472
10569
|
const path = `/api/v2/agents/${enc7(id)}/whatsapp/history`;
|
|
10473
10570
|
const media = v.media !== void 0;
|
|
10474
|
-
const
|
|
10571
|
+
const filters = { ...v["only-ads"] ? { onlyAds: true } : {}, ...v.phones ? { phones: v.phones.split(",").map((x) => x.trim()).filter(Boolean) } : {}, ...v.column ? { column: v.column } : {} };
|
|
10572
|
+
const cmd = `ghosty channels whatsapp history ${id} --integration ${v.integration} --days ${days}${v["only-ads"] ? " --only-ads" : ""}${v.phones ? ` --phones ${v.phones}` : ""}${v.column ? ` --column "${v.column}"` : ""}`;
|
|
10573
|
+
const c = await api(path, { method: "POST", body: { integrationId: v.integration, days, dryRun: true, media, ...v.list ? { list: true } : {}, ...filters } });
|
|
10475
10574
|
const files = (p) => (p.mediaCopied ?? 0) + (p.mediaFailed ?? 0) ? ` \xB7 ${t(`${p.mediaCopied ?? 0} file(s) copied`, `${p.mediaCopied ?? 0} archivo(s) copiado(s)`)}${p.mediaFailed ? lavender(t(`, ${p.mediaFailed} failed (retry: --media)`, `, ${p.mediaFailed} fallaron (reintenta: --media)`)) : ""}` : "";
|
|
10476
10575
|
if (media) {
|
|
10477
10576
|
const missing = c.mediaMissing ?? 0;
|
|
@@ -10502,17 +10601,23 @@ ${dim(v["dry-run"] ? t("Dry run: nothing copied.", "Simulacro: no se copi\xF3 na
|
|
|
10502
10601
|
);
|
|
10503
10602
|
const missingHint = c.mediaMissing ? `
|
|
10504
10603
|
${lavender(t(`${c.mediaMissing} file(s) already imported have no copy in gs: add --media to copy them (Formmy keeps them 7 days).`, `${c.mediaMissing} archivo(s) ya importado(s) no tienen copia en gs: agrega --media para copiarlos (Formmy los guarda 7 d\xEDas).`))}` : "";
|
|
10505
|
-
|
|
10506
|
-
${
|
|
10507
|
-
|
|
10508
|
-
|
|
10509
|
-
|
|
10604
|
+
const listText = c.list ? `
|
|
10605
|
+
${table(c.list.map((x) => ({ phone: x.phone, name: (x.name ?? "").slice(0, 20), ad: x.fromAd ? lavender(t("ad", "anuncio")) : "", msgs: x.messages, first: x.firstMessage.slice(0, 60) })), ["phone", "name", "ad", "msgs", "first"])}
|
|
10606
|
+
${dim(t(`${c.matching} match the filters`, `${c.matching} pasan los filtros`))}` : "";
|
|
10607
|
+
const filtered = Boolean(v["only-ads"] || v.phones);
|
|
10608
|
+
if (v["dry-run"]) return out(c, () => `${plan}${listText}${missingHint}
|
|
10609
|
+
${dim(t("Dry run: nothing imported. Re-running later never duplicates.", "Simulacro: no se import\xF3 nada. Correrlo otra vez nunca duplica."))}
|
|
10610
|
+
${dim(applyHint(cmd))}`);
|
|
10611
|
+
if (!c.conversations || filtered && c.matching === 0) return out({ ...c, imported: 0 }, () => dim(t(`Nothing to import: ${plan}.`, `Nada que importar: ${plan}.`)));
|
|
10612
|
+
await confirm(`${bold(plan)}${filtered ? `
|
|
10613
|
+
${t(`only the ${c.matching} that match the filters`, `s\xF3lo las ${c.matching} que pasan los filtros`)}` : ""}
|
|
10614
|
+
${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: cmd });
|
|
10510
10615
|
const sum = { newCards: 0, updatedCards: 0, messages: 0, orders: 0, suppressed: 0, mediaCopied: 0, mediaFailed: 0, paused: 0, pausedForever: 0, manual: 0 };
|
|
10511
10616
|
let cursor = 0;
|
|
10512
10617
|
let done = 0;
|
|
10513
10618
|
while (cursor !== null) {
|
|
10514
10619
|
const at = cursor;
|
|
10515
|
-
const p = await pageWithRetry(() => api(path, { method: "POST", body: { integrationId: v.integration, days, cursor: at } }), `${done}/${c.conversations}`);
|
|
10620
|
+
const p = await pageWithRetry(() => api(path, { method: "POST", body: { integrationId: v.integration, days, cursor: at, ...filters } }), `${done}/${c.conversations}`);
|
|
10516
10621
|
for (const k of Object.keys(sum)) sum[k] += p[k] ?? 0;
|
|
10517
10622
|
done += p.page;
|
|
10518
10623
|
if (!globals.json) info(dim(` ${done}/${p.total} ${t("conversations", "conversaciones")}`));
|
|
@@ -10590,7 +10695,8 @@ async function boardsCmd(sub, a1, a2, v, context) {
|
|
|
10590
10695
|
case "create": {
|
|
10591
10696
|
if (!a1 || !a2) throw usage(t("Missing workspace or name.", "Falta el espacio o el nombre."), t("Example", "Ejemplo") + ': ghosty boards create <workspace> "Siiqtec - Ixmiquilpan" --columns-from main');
|
|
10592
10697
|
const r = await api(`/api/v2/workspaces/${enc8(a1)}/boards`, { method: "POST", body: { name: a2, ...v["columns-from"] ? { columnsFrom: v["columns-from"] } : {} } });
|
|
10593
|
-
return out(r, () => `${lavender("\u2713")} ${t(`Board \xAB${r.name}\xBB created${r.columnsFrom ? ` with the ${r.columns} column(s) of \xAB${r.columnsFrom}\xBB` : ""}.`, `Tablero \xAB${r.name}\xBB creado${r.columnsFrom ? ` con las ${r.columns} columna(s) de \xAB${r.columnsFrom}\xBB` : ""}.`)} ${dim(`${t("Send an agent's cards there", "Manda ah\xED las tarjetas de un agente")}: ghosty boards assign <agent-id> "${r.name}"`)}`
|
|
10698
|
+
return out(r, () => `${lavender("\u2713")} ${t(`Board \xAB${r.name}\xBB created${r.columnsFrom ? ` with the ${r.columns} column(s) of \xAB${r.columnsFrom}\xBB` : ""}.`, `Tablero \xAB${r.name}\xBB creado${r.columnsFrom ? ` con las ${r.columns} columna(s) de \xAB${r.columnsFrom}\xBB` : ""}.`)} ${dim(`${t("Send an agent's cards there", "Manda ah\xED las tarjetas de un agente")}: ghosty boards assign <agent-id> "${r.name}"`)}${r.warning ? `
|
|
10699
|
+
${lavender(`\u26A0\uFE0F ${serverText(r.warning)}`)}` : ""}`);
|
|
10594
10700
|
}
|
|
10595
10701
|
case "rm": {
|
|
10596
10702
|
if (!a1 || !a2) throw usage(t("Missing workspace or board.", "Falta el espacio o el tablero."), t("Example", "Ejemplo") + ': ghosty boards rm <workspace> "Prueba"');
|
|
@@ -10633,6 +10739,7 @@ var HELPS_ES = {
|
|
|
10633
10739
|
summary: "Crea, revisa, cambia, diagnostica y borra agentes.",
|
|
10634
10740
|
replace: [
|
|
10635
10741
|
["# the agent as a file; see ghosty apply --help", "# el agente como archivo; ver ghosty apply --help"],
|
|
10742
|
+
["# your account: merge copies with the same content", "# tu cuenta: une las copias con el mismo contenido"],
|
|
10636
10743
|
["# what it really has: effective toolsets, connectors connected + granted", "# lo que de verdad tiene: toolsets efectivos, conectores conectados + concedidos"]
|
|
10637
10744
|
],
|
|
10638
10745
|
flags: [
|
|
@@ -10679,7 +10786,8 @@ var HELPS_ES = {
|
|
|
10679
10786
|
["--from-workspace", "rm: lo desactiva en cada Teams donde est\xE9 y luego lo borra (due\xF1o o staff)"],
|
|
10680
10787
|
["--prompt-mode M", "set: append (default: su prompt + la persona de Ghosty) o replace (s\xF3lo su prompt)"],
|
|
10681
10788
|
["--sponsor/--owner EMAIL", "ls: agentes de CUALQUIER cuenta que paga / de quien es ese correo (staff de Ghosty); luego se operan por id"],
|
|
10682
|
-
["--ids", "ls: s\xF3lo los ids, uno por rengl\xF3n (para xargs)"]
|
|
10789
|
+
["--ids", "ls: s\xF3lo los ids, uno por rengl\xF3n (para xargs)"],
|
|
10790
|
+
["--user EMAIL --app A", "ls (staff de Ghosty): la lista de esa persona tal como la recibe su app (tono, espacio, \xFAltimo uso); A = all|android|ios|desktop"]
|
|
10683
10791
|
],
|
|
10684
10792
|
notes: [
|
|
10685
10793
|
"doctor sale con 1 cuando encuentra un problema, as\xED puede frenar un script o un CI.",
|
|
@@ -10733,7 +10841,7 @@ var HELPS_ES = {
|
|
|
10733
10841
|
},
|
|
10734
10842
|
turns: {
|
|
10735
10843
|
summary: "Los turnos recientes de un agente: qu\xE9 contest\xF3, qu\xE9 fall\xF3 y por qu\xE9, y en qu\xE9 se fue el tiempo de un turno.",
|
|
10736
|
-
replace: [["# last turn: each tool, its seconds, output tokens", "# \xFAltimo turno: cada tool, sus segundos, tokens de salida"], ["# stop the turn AND its background jobs", "# detiene el turno Y sus encargos en fondo"]],
|
|
10844
|
+
replace: [["# last turn: each tool, its seconds, output tokens", "# \xFAltimo turno: cada tool, sus segundos, tokens de salida"], ["# stop the turn AND its background jobs", "# detiene el turno Y sus encargos en fondo"], ["# staff: real failure rate by engine and error", "# staff: tasa real de fallos por motor y por error"]],
|
|
10737
10845
|
flags: [
|
|
10738
10846
|
["--errors", "s\xF3lo turnos fallidos (igual que --status error)"],
|
|
10739
10847
|
["--skipped", "ls: mensajes de clientes de WhatsApp Business que NO abrieron turno, y por qu\xE9 (operador activo, s\xF3lo registra, pausa, reacci\xF3n, canal apagado, sin saldo)"],
|
|
@@ -10742,6 +10850,7 @@ var HELPS_ES = {
|
|
|
10742
10850
|
["--limit N", "m\xE1ximo de filas (default 20, m\xE1x. 500)"],
|
|
10743
10851
|
["--full", "show: el mensaje que entr\xF3 y la respuesta completos (default: primeros 600 caracteres)"],
|
|
10744
10852
|
["--quota-hits", "ls --workspace: mensajes (WhatsApp Business y Teams) que no abrieron turno porque se acab\xF3 el saldo"],
|
|
10853
|
+
["--all", "stats (staff): cuenta tambi\xE9n staff, canal prueba y agentes borrados"],
|
|
10745
10854
|
["--session S", "ls: s\xF3lo los turnos de esa conversaci\xF3n. Los de Claude dicen \xABresumed\xBB: \u2713 = retom\xF3 la sesi\xF3n del SDK, \u26A0 desde cero (orphan|stale|replaced) = perdi\xF3 la memoria"]
|
|
10746
10855
|
],
|
|
10747
10856
|
notes: ["--skipped guarda 30 d\xEDas y s\xF3lo ve lo que Formmy reenvi\xF3: si Formmy no reenv\xEDa (ghosty channels whatsapp ls, \xABformmy forwards\xBB), a gs nunca le llega el mensaje.", "show imprime lo que entr\xF3 y lo que sali\xF3 en cualquier motor o canal (grupos de WhatsApp tambi\xE9n); la l\xEDnea de tiempo lee el transcript del worker, as\xED que s\xF3lo motores del pool (no ACP/goose)."]
|
|
@@ -10773,7 +10882,15 @@ var HELPS_ES = {
|
|
|
10773
10882
|
},
|
|
10774
10883
|
chat: {
|
|
10775
10884
|
summary: "Conversa en streaming; sin mensaje es interactivo.",
|
|
10776
|
-
replace: [['["message"]', '["mensaje"]']]
|
|
10885
|
+
replace: [['["message"]', '["mensaje"]'], ["# staff: who sees it and why", "# staff: qui\xE9n lo ve y por qu\xE9"]],
|
|
10886
|
+
flags: [
|
|
10887
|
+
["--media FILE", "chat: adjunta un archivo local (se repite)"],
|
|
10888
|
+
["--file ID", "chat: adjunta de tus archivos (ghosty me files ls --q \u2026), separados por comas"],
|
|
10889
|
+
["--kind K", "archive|rm: test (pruebas) o channel (clientes: s\xF3lo archivar, su memoria los sigue atendiendo)"],
|
|
10890
|
+
["--before T", "archive|rm: s\xF3lo conversaciones sin mensajes desde T (7d, 12h o ISO)"],
|
|
10891
|
+
["--user EMAIL", "ls/show (staff de Ghosty): las conversaciones de esa persona en cualquier agente; queda en bit\xE1cora"],
|
|
10892
|
+
["--why", "show (staff de Ghosty): clase, de qui\xE9n es, qu\xE9 turno la fij\xF3, y si el due\xF1o / su usuario la ven en su lista o por liga"]
|
|
10893
|
+
]
|
|
10777
10894
|
},
|
|
10778
10895
|
schedule: {
|
|
10779
10896
|
summary: "Turnos que corren despu\xE9s, con el reloj propio del agente.",
|
|
@@ -10798,6 +10915,11 @@ var HELPS_ES = {
|
|
|
10798
10915
|
["--yes", "enable / groups enable: se salta la confirmaci\xF3n (obligatorio sin terminal)"],
|
|
10799
10916
|
["--role R", "groups: equipo (el equipo: escribe en bases de datos, tools de admin) o cliente (como cualquier cliente)"],
|
|
10800
10917
|
["--cached", "groups ls: no le pide a WhatsApp la lista en vivo (m\xE1s r\xE1pido; puede faltar alg\xFAn grupo nuevo)"],
|
|
10918
|
+
["--ads-only on|off", "link: s\xF3lo las conversaciones que vienen de un anuncio abren tarjeta (tambi\xE9n solo, para cambiarlo en un n\xFAmero ya ligado)"],
|
|
10919
|
+
["--list", "history --dry-run: CU\xC1LES conversaciones (tel\xE9fono, nombre, primer mensaje, mensajes, si vino de anuncio)"],
|
|
10920
|
+
["--only-ads / --phones a,b", "history: importa (o lista) s\xF3lo las de anuncio y/o esos tel\xE9fonos"],
|
|
10921
|
+
["--column NAME", "history: las tarjetas nuevas nacen en esa columna en vez de la primera"],
|
|
10922
|
+
["--name N", "groups set: el nombre con que lo ve gs (p. ej. un grupo guardado con su jid de nombre); el asunto en WhatsApp no cambia"],
|
|
10801
10923
|
["--integration ID", "link/unlink/history: el id de la Integration del n\xFAmero de WhatsApp Business en Formmy"],
|
|
10802
10924
|
["--email E", "integrations (staff): lista los n\xFAmeros de otra cuenta de Formmy"],
|
|
10803
10925
|
["--days N", "history: conversaciones activas en los \xFAltimos N d\xEDas (1-30, default 7)"],
|
|
@@ -10911,7 +11033,7 @@ var HELPS_ES = {
|
|
|
10911
11033
|
flags: [
|
|
10912
11034
|
["--boxes", "las cajas del espacio contra su tope (spares, dormidas) y, por agente: conversaciones por caja, cajas, despiertas, spares, turnos corriendo ahora"],
|
|
10913
11035
|
["--workspace SLUG", "qu\xE9 espacio (due\xF1o o staff); con --boxes el default es el de ghosty use"],
|
|
10914
|
-
["--by agent|day|agent,day", "--workspace: filas por agente (default), por d\xEDa (CDMX) o las dos"],
|
|
11036
|
+
["--by agent|day|agent,day", "--workspace: filas por agente (default), por d\xEDa (CDMX) o las dos \xB7 con --boxes: template = toda la flota por template (staff)"],
|
|
10915
11037
|
["--days N", "--workspace: cu\xE1ntos d\xEDas hacia atr\xE1s (default 14, m\xE1x. 92)"]
|
|
10916
11038
|
],
|
|
10917
11039
|
notes: ["failed = turnos que terminaron en error o se detuvieron. \xABno balance\xBB = mensajes que no abrieron turno porque se acab\xF3 la bolsa (ghosty turns ls --workspace X --quota-hits los lista)."]
|
|
@@ -10945,9 +11067,19 @@ var HELPS_ES = {
|
|
|
10945
11067
|
],
|
|
10946
11068
|
notes: ["try cuenta contra los topes del demo (mensajes por visitante, por IP al d\xEDa) y sale en sus n\xFAmeros y en el CRM.", "history es s\xF3lo para staff de Ghosty: los chats de los visitantes no se le ense\xF1an al due\xF1o."]
|
|
10947
11069
|
},
|
|
11070
|
+
push: {
|
|
11071
|
+
summary: "Staff de Ghosty: qu\xE9 avisos (push) se le mandaron a alguien (fin de turno, permiso, escalar, documento\u2026), a qu\xE9 dispositivo y qu\xE9 contest\xF3 Apple/Google. Se registran desde el 1-oct y se guardan 30 d\xEDas.",
|
|
11072
|
+
notes: ["thread ense\xF1a la clase de la conversaci\xF3n: un \xABchannel\xBB ah\xED es que le lleg\xF3 el aviso de un cliente."]
|
|
11073
|
+
},
|
|
11074
|
+
novedades: {
|
|
11075
|
+
summary: "Staff de Ghosty: las novedades de la app Android. Cada una con su ilustraci\xF3n; mismos frenos que el admin: pru\xE9bala en tu tel\xE9fono primero, push de 9 a 21 h CDMX, 72 h entre env\xEDos a todos.",
|
|
11076
|
+
notes: ["--image se publica en img.ghosty.studio (WebP 1200 px). --to vac\xEDo = todos. send sin --at la manda ya (pregunta); --at '' quita la programaci\xF3n."]
|
|
11077
|
+
},
|
|
10948
11078
|
spaces: {
|
|
10949
|
-
summary: "
|
|
11079
|
+
summary: "Espacios: lista los tuyos (o, staff, los de cualquiera) y operaciones del staff de Ghosty \u2014 entrar/salir del espacio de un cliente, cambiar su combo, mover \xABpagado hasta\xBB, renombrar su slug. Cada cambio queda en la bit\xE1cora del espacio.",
|
|
10950
11080
|
flags: [
|
|
11081
|
+
["--user EMAIL", "ls (staff): los espacios de esa persona"],
|
|
11082
|
+
["--combo C", "set (staff): teams | sales | both \u2014 sin Ventas sus tableros existen pero nadie los ve"],
|
|
10951
11083
|
["--until DATE", "renew: el nuevo \xABpagado hasta\xBB (AAAA-MM-DD)"],
|
|
10952
11084
|
["--note TEXT", "renew: por qu\xE9 (va a la bit\xE1cora)"],
|
|
10953
11085
|
["-n, --dry-run", "ense\xF1a qu\xE9 cambia, no cambia nada"],
|
|
@@ -10956,7 +11088,7 @@ var HELPS_ES = {
|
|
|
10956
11088
|
notes: ["Con suscripci\xF3n de Stripe renew se niega: la fecha la mueve la factura pagada.", "rename cambia s\xF3lo el slug (id y namespace se quedan, as\xED que tablero, Stripe y WhatsApp siguen funcionando); las ligas viejas <slug>.sales dejan de servir."]
|
|
10957
11089
|
}
|
|
10958
11090
|
};
|
|
10959
|
-
var HELP_ALIASES = { creds: "credentials", board: "cards", conversations: "chat" };
|
|
11091
|
+
var HELP_ALIASES = { creds: "credentials", board: "cards", conversations: "chat", workspaces: "spaces" };
|
|
10960
11092
|
|
|
10961
11093
|
// src/help.ts
|
|
10962
11094
|
var DOCS = "https://www.ghosty.studio/docs/cli";
|
|
@@ -10975,6 +11107,7 @@ var HELPS = {
|
|
|
10975
11107
|
"ghosty agents doctor <agent-id> [--verbose]",
|
|
10976
11108
|
"ghosty agents docs ls|grant|revoke <agent-id> [doc\u2026] [--write] [--no-deliver] [--like AGENT]",
|
|
10977
11109
|
"ghosty agents docs add <agent-id> --url URL | --file PATH [--name N] [--description TEXT] [--grant read|write|no] [--no-deliver]",
|
|
11110
|
+
"ghosty agents docs dedupe [<keep-id> <drop-id>] [--dry-run] # your account: merge copies with the same content",
|
|
10978
11111
|
"ghosty agents docs replace <agent-id> <doc> --url URL | --file PATH [--note TEXT] # new version: keeps name, access and the agents that use it",
|
|
10979
11112
|
"ghosty agents docs history|restore <agent-id> <doc> [n]",
|
|
10980
11113
|
"ghosty agents share <agent-id> <email> [--role editor|admin]",
|
|
@@ -11030,7 +11163,8 @@ var HELPS = {
|
|
|
11030
11163
|
["--from-workspace", "rm: turn it off in every Teams where it's active, then delete it (owner or staff)"],
|
|
11031
11164
|
["--prompt-mode M", "set: append (default: its prompt + Ghosty's persona) or replace (its prompt only)"],
|
|
11032
11165
|
["--sponsor/--owner EMAIL", "ls: agents of ANY account paid by / owned by that email (Ghosty staff); then operate them by id"],
|
|
11033
|
-
["--ids", "ls: only the ids, one per line (for xargs)"]
|
|
11166
|
+
["--ids", "ls: only the ids, one per line (for xargs)"],
|
|
11167
|
+
["--user EMAIL --app A", "ls (Ghosty staff): that person's list exactly as their app gets it (tone, space, last use); A = all|android|ios|desktop"]
|
|
11034
11168
|
],
|
|
11035
11169
|
examples: [
|
|
11036
11170
|
"ghosty agents doctor cmu7pgbyr0001gee3ruk172mm",
|
|
@@ -11141,7 +11275,7 @@ var HELPS = {
|
|
|
11141
11275
|
},
|
|
11142
11276
|
turns: {
|
|
11143
11277
|
summary: "An agent's recent turns: what answered, what failed and why, and where one turn's time went.",
|
|
11144
|
-
usage: ["ghosty turns ls <agent-id> [--errors | --status STATE | --skipped] [--since 1h] [--limit N]", "ghosty turns ls --workspace <slug> --quota-hits [--days 14]", "ghosty turns show <agent-id> [turn-id | last] [--full]", "ghosty turns cancel <agent-id> <conversation-id> # stop the turn AND its background jobs"],
|
|
11278
|
+
usage: ["ghosty turns ls <agent-id> [--errors | --status STATE | --skipped] [--since 1h] [--limit N]", "ghosty turns ls --workspace <slug> --quota-hits [--days 14]", "ghosty turns show <agent-id> [turn-id | last] [--full]", "ghosty turns cancel <agent-id> <conversation-id> # stop the turn AND its background jobs", "ghosty turns stats [--since 14d] [--all] # staff: real failure rate by engine and error"],
|
|
11145
11279
|
flags: [
|
|
11146
11280
|
["--errors", "only failed turns (same as --status error)"],
|
|
11147
11281
|
["--skipped", "ls: WhatsApp Business customer messages that did NOT open a turn, and why (operator active, records only, paused, reaction, channel off, no balance)"],
|
|
@@ -11150,6 +11284,7 @@ var HELPS = {
|
|
|
11150
11284
|
["--limit N", "max rows (default 20, max 500)"],
|
|
11151
11285
|
["--full", "show: the whole message that came in and the whole answer (default: first 600 chars)"],
|
|
11152
11286
|
["--quota-hits", "ls --workspace: messages (WhatsApp Business and Teams) that got no turn because the balance ran out"],
|
|
11287
|
+
["--all", "stats (staff): also count staff users, the test channel and deleted agents"],
|
|
11153
11288
|
["--session S", "ls: only this conversation's turns. Claude turns show \xABresumed\xBB: \u2713 = it kept the SDK session, \u26A0 from scratch (orphan|stale|replaced) = it lost its memory"]
|
|
11154
11289
|
],
|
|
11155
11290
|
examples: ["ghosty turns ls <id> --errors --since 1h", "ghosty turns ls --workspace descti --quota-hits --days 30", "ghosty turns ls <id> --skipped --since 1d", "ghosty turns ls <id> --json --limit 100", "ghosty turns show <id> # last turn: each tool, its seconds, output tokens"],
|
|
@@ -11204,8 +11339,23 @@ var HELPS = {
|
|
|
11204
11339
|
},
|
|
11205
11340
|
chat: {
|
|
11206
11341
|
summary: "Stream a conversation; with no message it is interactive.",
|
|
11207
|
-
usage: [
|
|
11208
|
-
|
|
11342
|
+
usage: [
|
|
11343
|
+
'ghosty chat <agent-id> ["message"] [--conversation ID] [--media FILE]\u2026 [--file ID[,ID\u2026]]',
|
|
11344
|
+
"ghosty conversations ls <agent-id>",
|
|
11345
|
+
"ghosty conversations show <agent-id> <conversation-id> [--limit 20]",
|
|
11346
|
+
"ghosty conversations archive|rm <agent-id> --kind test|channel [--before 7d] [--dry-run] [--yes]",
|
|
11347
|
+
"ghosty conversations ls --user <email> [--since 30d] [--surface android] # staff",
|
|
11348
|
+
"ghosty conversations show <agent-id> <conversation-id> --why # staff: who sees it and why"
|
|
11349
|
+
],
|
|
11350
|
+
flags: [
|
|
11351
|
+
["--media FILE", "chat: attach a local file (repeatable)"],
|
|
11352
|
+
["--file ID", "chat: attach from your files (ghosty me files ls --q \u2026), comma-separated"],
|
|
11353
|
+
["--kind K", "archive|rm: test (test chats) or channel (customers: archive only, their memory keeps serving them)"],
|
|
11354
|
+
["--before T", "archive|rm: only conversations with no message since T (7d, 12h or ISO)"],
|
|
11355
|
+
["--user EMAIL", "ls/show (Ghosty staff): that person's conversations in any agent; logged"],
|
|
11356
|
+
["--why", "show (Ghosty staff): kind, whose it is, which turn set it, and whether the owner / its user see it in their list or by link"]
|
|
11357
|
+
],
|
|
11358
|
+
examples: ['ghosty chat <id> "hola"', "ghosty chat <id>", 'ghosty chat <id> "p\xE1salo a PDF" --media foto.jpg', "ghosty conversations rm <id> --kind test --before 7d --dry-run", "ghosty conversations archive <id> --kind channel --before 30d", "ghosty conversations ls --user david@empresa.com --since 7d"],
|
|
11209
11359
|
learn: `${REF}#conversaciones`
|
|
11210
11360
|
},
|
|
11211
11361
|
schedule: {
|
|
@@ -11217,8 +11367,8 @@ var HELPS = {
|
|
|
11217
11367
|
},
|
|
11218
11368
|
me: {
|
|
11219
11369
|
summary: "Your account: files and usage.",
|
|
11220
|
-
usage: ["ghosty me files ls
|
|
11221
|
-
examples: ["ghosty me files ls --kind document"],
|
|
11370
|
+
usage: ["ghosty me files ls [--q TEXT] [--kind image|video|audio|document|other] [--cursor C]", "ghosty me files get <file-id> [--out FILE]", "ghosty me files upload <file> | rm <file-id>", "ghosty usage"],
|
|
11371
|
+
examples: ["ghosty me files ls --kind document", "ghosty me files ls --q contrato", "ghosty me files get <id> --out contrato.pdf", 'ghosty chat <agent> "res\xFAmelo" --file <id>'],
|
|
11222
11372
|
learn: `${REF}#tu-cuenta`
|
|
11223
11373
|
}
|
|
11224
11374
|
};
|
|
@@ -11253,7 +11403,7 @@ HELPS.channels = {
|
|
|
11253
11403
|
"ghosty channels wa status|pause <agent-id>",
|
|
11254
11404
|
"ghosty channels wa groups ls <agent-id> [--cached]",
|
|
11255
11405
|
"ghosty channels wa groups enable|disable <agent-id> <group> [--role equipo|cliente] [--dry-run] [--yes]",
|
|
11256
|
-
|
|
11406
|
+
'ghosty channels wa groups set <agent-id> <group> [--role equipo|cliente] [--name "New name"] [--dry-run]',
|
|
11257
11407
|
'ghosty channels wa groups create <agent-id> "Name" [--role equipo|cliente]'
|
|
11258
11408
|
],
|
|
11259
11409
|
flags: [
|
|
@@ -11261,6 +11411,11 @@ HELPS.channels = {
|
|
|
11261
11411
|
["--yes", "enable / groups enable: skip the confirmation (required without a terminal)"],
|
|
11262
11412
|
["--role R", "groups: equipo (the team: writes to databases, admin tools) or cliente (like any customer)"],
|
|
11263
11413
|
["--cached", "groups ls: don't ask WhatsApp for the live list (faster; may miss new groups)"],
|
|
11414
|
+
["--ads-only on|off", "link: only conversations that come from an ad open a card (also alone, to change it on a linked number)"],
|
|
11415
|
+
["--list", "history --dry-run: WHICH conversations (phone, name, first message, messages, from an ad)"],
|
|
11416
|
+
["--only-ads / --phones a,b", "history: import (or list) only the ones from an ad and/or those phones"],
|
|
11417
|
+
["--column NAME", "history: new cards are born in that column instead of the first one"],
|
|
11418
|
+
["--name N", "groups set: the name gs shows (e.g. a group saved with its jid as name); WhatsApp's subject doesn't change"],
|
|
11264
11419
|
["--integration ID", "link/unlink/history: the WhatsApp Business number's Integration id in Formmy"],
|
|
11265
11420
|
["--email E", "integrations (staff): list the numbers of another Formmy account"],
|
|
11266
11421
|
["--days N", "history: conversations active in the last N days (1-30, default 7)"],
|
|
@@ -11407,7 +11562,7 @@ HELPS.usage = {
|
|
|
11407
11562
|
flags: [
|
|
11408
11563
|
["--boxes", "the workspace's boxes against its limit (spares, asleep) and, per agent: conversations per box, boxes, awake, spares, turns running now"],
|
|
11409
11564
|
["--workspace SLUG", "which workspace (owner or staff); with --boxes the default is the one from ghosty use"],
|
|
11410
|
-
["--by agent|day|agent,day", "--workspace: rows per agent (default), per day (CDMX) or both"],
|
|
11565
|
+
["--by agent|day|agent,day", "--workspace: rows per agent (default), per day (CDMX) or both \xB7 with --boxes: template = the whole fleet by template (staff)"],
|
|
11411
11566
|
["--days N", "--workspace: how far back (default 14, max 92)"]
|
|
11412
11567
|
],
|
|
11413
11568
|
examples: ["ghosty usage", "ghosty usage --json", "ghosty usage --workspace descti --by agent,day --days 14", "ghosty usage --boxes --workspace siiqtec"],
|
|
@@ -11460,19 +11615,43 @@ HELPS.demos = {
|
|
|
11460
11615
|
notes: ["try counts against the demo's caps (messages per visitor, per IP per day) and shows up in its numbers and CRM.", "history is Ghosty staff only: visitors' chats are not shown to the owner."],
|
|
11461
11616
|
learn: `${REF}#demo-publico-agents-demo`
|
|
11462
11617
|
};
|
|
11618
|
+
HELPS.push = {
|
|
11619
|
+
summary: "Ghosty staff: which pushes were sent to someone (turn end, permission, escalate, document\u2026), to which device and what Apple/Google answered. Logged since 2026-10-01, kept 30 days.",
|
|
11620
|
+
usage: ["ghosty push log --user <email> [--since 2d] [--limit 100]"],
|
|
11621
|
+
examples: ["ghosty push log --user brenda@siiqtec.com --since 1d"],
|
|
11622
|
+
notes: ["thread shows the conversation's kind: a \xABchannel\xBB there means a customer's push reached them."],
|
|
11623
|
+
learn: `${REF}#soporte`
|
|
11624
|
+
};
|
|
11625
|
+
HELPS.novedades = {
|
|
11626
|
+
summary: "Ghosty staff: the Android app's announcements (what's new). Each one needs its own illustration; same brakes as the admin: test on your phone first, push 9\u201321 h CDMX, 72 h between pushes to everyone.",
|
|
11627
|
+
usage: [
|
|
11628
|
+
"ghosty novedades ls",
|
|
11629
|
+
'ghosty novedades create --title T --body B --image hero.png [--link ruta] [--button "Abrir"] [--to a@x.com,b@y.com] [--publish]',
|
|
11630
|
+
"ghosty novedades set <id> [--title] [--body] [--image FILE] [--link] [--button] [--to] [--publish|--draft]",
|
|
11631
|
+
"ghosty novedades test <id>",
|
|
11632
|
+
'ghosty novedades send <id> [--at "2026-10-02T12:00"]',
|
|
11633
|
+
"ghosty novedades rm <id>"
|
|
11634
|
+
],
|
|
11635
|
+
examples: ['ghosty novedades create --title "PDF y audio" --body "Ahora\u2026" --image hero.png --link chats --publish', "ghosty novedades test <id>", 'ghosty novedades send <id> --at "2026-10-02T12:00"'],
|
|
11636
|
+
notes: ["--image is published to img.ghosty.studio (WebP 1200 px). --to empty = everyone. send without --at pushes now (asks); --at '' removes the schedule."],
|
|
11637
|
+
learn: `${REF}#soporte`
|
|
11638
|
+
};
|
|
11463
11639
|
HELPS.spaces = {
|
|
11464
|
-
summary: "Ghosty staff operations
|
|
11465
|
-
usage: ["ghosty spaces renew <workspace> --until YYYY-MM-DD [--note TEXT] [--dry-run] [--yes]", "ghosty spaces rename <old> <new> [--dry-run] [--yes]"],
|
|
11640
|
+
summary: "Workspaces: list yours (or, staff, anyone's), and Ghosty staff operations \u2014 join/leave a client's workspace, change its combo, move \xABpaid until\xBB, rename its slug. Each change is logged in the workspace's audit.",
|
|
11641
|
+
usage: ["ghosty spaces ls [--user EMAIL]", "ghosty spaces join|leave <workspace>", "ghosty spaces set <workspace> --combo teams|sales|both [--dry-run]", "ghosty spaces renew <workspace> --until YYYY-MM-DD [--note TEXT] [--dry-run] [--yes]", "ghosty spaces rename <old> <new> [--dry-run] [--yes]"],
|
|
11466
11642
|
flags: [
|
|
11643
|
+
["--user EMAIL", "ls (staff): that person's workspaces"],
|
|
11644
|
+
["--combo C", "set (staff): teams | sales | both \u2014 without Sales its boards exist but nobody sees them"],
|
|
11467
11645
|
["--until DATE", "renew: the new \xABpaid until\xBB (YYYY-MM-DD)"],
|
|
11468
11646
|
["--note TEXT", "renew: why (goes into the audit)"],
|
|
11469
11647
|
["-n, --dry-run", "show what changes, change nothing"],
|
|
11470
11648
|
["--yes", "skip the confirmation (required without a terminal)"]
|
|
11471
11649
|
],
|
|
11472
|
-
examples: ['ghosty spaces renew descti --until 2026-11-01 --note "pag\xF3 octubre"', "ghosty spaces rename totequim siiqtec --dry-run"],
|
|
11650
|
+
examples: ["ghosty spaces ls", "ghosty spaces ls --user brenda@siiqtec.com", "ghosty spaces join denik", "ghosty spaces set denik --combo sales --dry-run", 'ghosty spaces renew descti --until 2026-11-01 --note "pag\xF3 octubre"', "ghosty spaces rename totequim siiqtec --dry-run"],
|
|
11473
11651
|
notes: ["With a Stripe subscription renew refuses: the paid invoice moves the date.", "rename changes only the slug (id and namespace stay, so board, Stripe and WhatsApp keep working); old <slug>.sales links stop working."],
|
|
11474
11652
|
learn: `${REF}#espacios`
|
|
11475
11653
|
};
|
|
11654
|
+
HELPS.workspaces = HELPS.spaces;
|
|
11476
11655
|
function helpText(cmd) {
|
|
11477
11656
|
const base = HELPS[cmd];
|
|
11478
11657
|
if (!base) return void 0;
|
|
@@ -11496,11 +11675,11 @@ function helpText(cmd) {
|
|
|
11496
11675
|
}
|
|
11497
11676
|
|
|
11498
11677
|
// src/update.ts
|
|
11499
|
-
import { mkdir as mkdir4, readFile as readFile9, writeFile as
|
|
11678
|
+
import { mkdir as mkdir4, readFile as readFile9, writeFile as writeFile6 } from "node:fs/promises";
|
|
11500
11679
|
import { homedir as homedir3 } from "node:os";
|
|
11501
11680
|
import { dirname as dirname3, join as join6 } from "node:path";
|
|
11502
11681
|
var FILE = join6(process.env.XDG_CONFIG_HOME || join6(homedir3(), ".config"), "ghosty", "update.json");
|
|
11503
|
-
var
|
|
11682
|
+
var TTL = 60 * 60 * 1e3;
|
|
11504
11683
|
var UPGRADE = "npm i -g @ghostystudio/cli@latest";
|
|
11505
11684
|
function newer(a, b) {
|
|
11506
11685
|
const pa = a.split(".").map(Number);
|
|
@@ -11508,27 +11687,28 @@ function newer(a, b) {
|
|
|
11508
11687
|
for (let i = 0; i < 3; i++) if ((pa[i] ?? 0) !== (pb[i] ?? 0)) return (pa[i] ?? 0) > (pb[i] ?? 0);
|
|
11509
11688
|
return false;
|
|
11510
11689
|
}
|
|
11511
|
-
async function latestVersion() {
|
|
11690
|
+
async function latestVersion(current2) {
|
|
11512
11691
|
if (process.env.GHOSTY_NO_UPDATE_CHECK || process.env.CI) return null;
|
|
11513
11692
|
let cache2 = {};
|
|
11514
11693
|
try {
|
|
11515
11694
|
cache2 = JSON.parse(await readFile9(FILE, "utf8"));
|
|
11516
11695
|
} catch {
|
|
11517
11696
|
}
|
|
11518
|
-
if (
|
|
11697
|
+
if (current2 && cache2.latest && newer(current2, cache2.latest)) cache2.checkedAt = 0;
|
|
11698
|
+
if (!cache2.checkedAt || Date.now() - cache2.checkedAt > TTL) {
|
|
11519
11699
|
try {
|
|
11520
11700
|
const r = await fetch("https://registry.npmjs.org/@ghostystudio/cli/latest", { signal: AbortSignal.timeout(1500) });
|
|
11521
11701
|
const latest = (await r.json()).version;
|
|
11522
11702
|
if (latest) cache2 = { latest, checkedAt: Date.now() };
|
|
11523
11703
|
await mkdir4(dirname3(FILE), { recursive: true });
|
|
11524
|
-
await
|
|
11704
|
+
await writeFile6(FILE, JSON.stringify(cache2));
|
|
11525
11705
|
} catch {
|
|
11526
11706
|
}
|
|
11527
11707
|
}
|
|
11528
11708
|
return cache2.latest ?? null;
|
|
11529
11709
|
}
|
|
11530
11710
|
async function updateNotice(current2) {
|
|
11531
|
-
const latest = await latestVersion();
|
|
11711
|
+
const latest = await latestVersion(current2);
|
|
11532
11712
|
return latest && newer(latest, current2) ? t(`Update available: ${current2} \u2192 ${latest}. Run: ${UPGRADE}`, `Hay versi\xF3n nueva: ${current2} \u2192 ${latest}. Corre: ${UPGRADE}`) : null;
|
|
11533
11713
|
}
|
|
11534
11714
|
|
|
@@ -11575,6 +11755,7 @@ function agentArgIndex(pos) {
|
|
|
11575
11755
|
case "db":
|
|
11576
11756
|
return sub === "photos" ? 3 : 2;
|
|
11577
11757
|
case "turns":
|
|
11758
|
+
return pos[1] === "stats" ? null : 2;
|
|
11578
11759
|
case "board":
|
|
11579
11760
|
case "conversations":
|
|
11580
11761
|
case "schedule":
|
|
@@ -11595,7 +11776,7 @@ function agentArgIndex(pos) {
|
|
|
11595
11776
|
}
|
|
11596
11777
|
|
|
11597
11778
|
// src/completion.ts
|
|
11598
|
-
import { mkdir as mkdir5, readFile as readFile10, writeFile as
|
|
11779
|
+
import { mkdir as mkdir5, readFile as readFile10, writeFile as writeFile7 } from "node:fs/promises";
|
|
11599
11780
|
import { homedir as homedir4 } from "node:os";
|
|
11600
11781
|
import { join as join7 } from "node:path";
|
|
11601
11782
|
var GLOBAL_FLAGS = ["--json", "--token", "--profile", "--yes", "--help", "--version", "--lang", "--dry-run"];
|
|
@@ -11647,7 +11828,7 @@ async function names() {
|
|
|
11647
11828
|
const workspaces = [...new Set(list.map((a) => a.espacio?.tipo === "workspace" ? a.espacio.nombre : void 0).filter((x) => !!x))];
|
|
11648
11829
|
await mkdir5(join7(cacheFile(), ".."), { recursive: true }).catch(() => {
|
|
11649
11830
|
});
|
|
11650
|
-
await
|
|
11831
|
+
await writeFile7(cacheFile(), JSON.stringify({ at: Date.now(), agents, workspaces })).catch(() => {
|
|
11651
11832
|
});
|
|
11652
11833
|
return { agents, workspaces };
|
|
11653
11834
|
}
|
|
@@ -11725,7 +11906,7 @@ function normalizeArgs(pos, v) {
|
|
|
11725
11906
|
|
|
11726
11907
|
// src/commands/spec.ts
|
|
11727
11908
|
var import_yaml = __toESM(require_dist(), 1);
|
|
11728
|
-
import { mkdir as mkdir6, readdir as readdir4, readFile as readFile11, stat, writeFile as
|
|
11909
|
+
import { mkdir as mkdir6, readdir as readdir4, readFile as readFile11, stat, writeFile as writeFile8 } from "node:fs/promises";
|
|
11729
11910
|
import { join as join8, resolve as resolve2 } from "node:path";
|
|
11730
11911
|
var enc9 = encodeURIComponent;
|
|
11731
11912
|
async function agentsExport(id, v) {
|
|
@@ -11737,7 +11918,7 @@ async function agentsExport(id, v) {
|
|
|
11737
11918
|
}
|
|
11738
11919
|
const dir = resolve2(v.out);
|
|
11739
11920
|
await mkdir6(dir, { recursive: true });
|
|
11740
|
-
await
|
|
11921
|
+
await writeFile8(join8(dir, "agent.yaml"), yaml);
|
|
11741
11922
|
const own = (0, import_yaml.parse)(yaml)["x-ghosty"]?.skills?.own ?? [];
|
|
11742
11923
|
for (const slug of own) {
|
|
11743
11924
|
const sk = await api(`/api/v2/agents/${enc9(id)}/skills/${enc9(slug)}`);
|
|
@@ -11952,7 +12133,33 @@ ${bagsText(r)}`);
|
|
|
11952
12133
|
}
|
|
11953
12134
|
}
|
|
11954
12135
|
async function spacesCmd(sub, slug, arg, v, context) {
|
|
11955
|
-
const ex = "ghosty spaces renew <workspace> --until 2026-11-01 [--note TEXT] \xB7 rename <old> <new> [--dry-run]";
|
|
12136
|
+
const ex = "ghosty spaces ls [--user EMAIL] \xB7 join|leave <workspace> \xB7 set <workspace> --combo teams|sales|both \xB7 renew <workspace> --until 2026-11-01 [--note TEXT] \xB7 rename <old> <new> [--dry-run]";
|
|
12137
|
+
if (!sub || sub === "ls" || sub === "list") {
|
|
12138
|
+
const r = await api(`/api/v2/workspaces${v.user ? `?user=${enc10(v.user)}` : ""}`);
|
|
12139
|
+
const rows = r.workspaces.map((w) => ({ workspace: w.slug, role: w.role ?? "", plan: w.plan, combo: w.combo, owner: w.owner ?? "", paid: w.paidUntil ?? "" }));
|
|
12140
|
+
return out(r, () => rows.length ? `${bold(r.user)}
|
|
12141
|
+
${table(rows, ["workspace", "role", "plan", "combo", "owner", "paid"])}` : dim(t(`${r.user} has no workspaces.`, `${r.user} no tiene espacios.`)));
|
|
12142
|
+
}
|
|
12143
|
+
if (sub === "join" || sub === "leave") {
|
|
12144
|
+
slug = needSpace(slug, `ghosty spaces ${sub} <workspace>`);
|
|
12145
|
+
const r = await api(wsPath(slug, "/staff"), { method: "POST", body: { join: sub === "join" } });
|
|
12146
|
+
return out(r, () => `${lavender("\u2713")} ${r.joined ? t(`You're in ${r.workspace} as staff (no seat; logged). Leave: ghosty spaces leave ${r.workspace}`, `Entraste a ${r.workspace} como staff (sin asiento; queda en bit\xE1cora). Salir: ghosty spaces leave ${r.workspace}`) : t(`You left ${r.workspace}.`, `Saliste de ${r.workspace}.`)}`);
|
|
12147
|
+
}
|
|
12148
|
+
if (sub === "set") {
|
|
12149
|
+
slug = needSpace(slug ?? context, "ghosty spaces set <workspace> --combo sales");
|
|
12150
|
+
if (!v.combo) throw usage(t("Nothing to change.", "Nada que cambiar."), "--combo teams|sales|both");
|
|
12151
|
+
const path = wsPath(slug, "/combo");
|
|
12152
|
+
const plan = await api(path, { method: "POST", body: { combo: v.combo, dryRun: true } });
|
|
12153
|
+
const line2 = `${plan.workspace}: combo ${plan.from} \u2192 ${plan.to}`;
|
|
12154
|
+
if (!plan.changed) return out(plan, () => dim(t(`Nothing to do: ${plan.workspace} is already ${plan.to}.`, `Nada que hacer: ${plan.workspace} ya es ${plan.to}.`)));
|
|
12155
|
+
if (v["dry-run"]) return out(plan, () => `${line2}
|
|
12156
|
+
${dim(t("Dry run: nothing changed.", "Simulaci\xF3n: no cambi\xF3 nada."))}`);
|
|
12157
|
+
requireYesIfHeadless(v.yes, `ghosty spaces set ${slug} --combo ${v.combo}`);
|
|
12158
|
+
await confirm(`${line2}
|
|
12159
|
+
${t("Change it?", "\xBFCambiarlo?")}`, { yes: v.yes, retry: `ghosty spaces set ${slug} --combo ${v.combo}` });
|
|
12160
|
+
const r = await api(path, { method: "POST", body: { combo: v.combo } });
|
|
12161
|
+
return out(r, () => `${lavender("\u2713")} ${r.workspace}: combo ${r.to}`);
|
|
12162
|
+
}
|
|
11956
12163
|
if (sub === "renew") {
|
|
11957
12164
|
slug = needSpace(slug ?? context, ex);
|
|
11958
12165
|
if (!v.until) throw usage(t("Missing --until YYYY-MM-DD.", "Falta --until AAAA-MM-DD."), `ghosty spaces renew ${slug} --until 2026-11-01`);
|
|
@@ -11987,9 +12194,9 @@ ${t("Rename?", "\xBFRenombrar?")}`, { yes: v.yes, expect: plan.from, retry: `gho
|
|
|
11987
12194
|
async function workspaceUsage(slug, v) {
|
|
11988
12195
|
slug = needSpace(slug, "ghosty usage --workspace <slug> --by agent,day --days 14");
|
|
11989
12196
|
const r = await api(wsPath(slug, `/usage?days=${enc10(v.days ?? "14")}`));
|
|
11990
|
-
const
|
|
12197
|
+
const cdmx2 = (iso) => new Date(Date.parse(iso) - 6 * 36e5).toISOString().slice(5, 16).replace("T", " ");
|
|
11991
12198
|
if (v["quota-hits"]) {
|
|
11992
|
-
const rows2 = r.quotaHits.map((q) => ({ at:
|
|
12199
|
+
const rows2 = r.quotaHits.map((q) => ({ at: cdmx2(q.at), agent: q.agent, channel: q.channel, from: q.phone ?? q.conversationId.slice(0, 24), message: (q.preview ?? "").slice(0, 60) }));
|
|
11993
12200
|
const total2 = r.agents.reduce((n, a) => n + a.noQuota, 0);
|
|
11994
12201
|
return out(r.quotaHits, () => rows2.length ? `${table(rows2, ["at", "agent", "channel", "from", "message"])}
|
|
11995
12202
|
${dim(t(`${total2} message(s) unanswered for lack of balance in ${r.days} day(s) (CDMX time)`, `${total2} mensaje(s) sin atender por saldo en ${r.days} d\xEDa(s) (hora CDMX)`))}` : dim(t(`No message ran out of balance in ${r.days} day(s).`, `Ning\xFAn mensaje se qued\xF3 sin saldo en ${r.days} d\xEDa(s).`)));
|
|
@@ -12020,7 +12227,7 @@ ${dim(t(`${fmtTokens(total)} billable tokens \xB7 failed = error or stopped \xB7
|
|
|
12020
12227
|
|
|
12021
12228
|
// src/commands/demos.ts
|
|
12022
12229
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
12023
|
-
import { mkdir as mkdir7, readFile as readFile12, writeFile as
|
|
12230
|
+
import { mkdir as mkdir7, readFile as readFile12, writeFile as writeFile9 } from "node:fs/promises";
|
|
12024
12231
|
import { homedir as homedir5 } from "node:os";
|
|
12025
12232
|
import { dirname as dirname4, join as join9 } from "node:path";
|
|
12026
12233
|
var enc11 = encodeURIComponent;
|
|
@@ -12063,7 +12270,7 @@ async function visitorCookie(base, slug, fresh) {
|
|
|
12063
12270
|
if (!cookie) throw new CliError(t("The demo didn't hand out a visitor cookie.", "El demo no entreg\xF3 cookie de visitante."), EXIT.API);
|
|
12064
12271
|
jar[slug] = cookie;
|
|
12065
12272
|
await mkdir7(dirname4(jarPath()), { recursive: true });
|
|
12066
|
-
await
|
|
12273
|
+
await writeFile9(jarPath(), JSON.stringify(jar, null, 2), { mode: 384 });
|
|
12067
12274
|
return cookie;
|
|
12068
12275
|
}
|
|
12069
12276
|
async function visitorTurn(base, slug, cookie, text) {
|
|
@@ -12134,6 +12341,158 @@ async function demosCmd(sub, a1, rest, v) {
|
|
|
12134
12341
|
throw usage(t(`Unknown demos subcommand: ${sub ?? "(none)"}.`, `Subcomando de demos desconocido: ${sub ?? "(ninguno)"}.`), 'ghosty demos history <agent> \xB7 ghosty demos try <slug> "message"');
|
|
12135
12342
|
}
|
|
12136
12343
|
|
|
12344
|
+
// src/commands/support.ts
|
|
12345
|
+
import { readFile as readFile13 } from "node:fs/promises";
|
|
12346
|
+
var enc12 = encodeURIComponent;
|
|
12347
|
+
var cdmx = (iso) => new Date(Date.parse(iso) - 6 * 36e5).toISOString().slice(5, 16).replace("T", " ");
|
|
12348
|
+
async function supportThreads(email, v) {
|
|
12349
|
+
const q = new URLSearchParams({ user: email, since: v.since ?? "30d", ...v.surface ? { surface: v.surface } : {} });
|
|
12350
|
+
const r = await api(`/api/v2/support/threads?${q}`);
|
|
12351
|
+
const rows = r.threads.map((x) => ({
|
|
12352
|
+
last: cdmx(x.lastTurn),
|
|
12353
|
+
agent: x.deletedAgent ? dim(t("(deleted)", "(borrado)")) : x.agent ?? x.agentId.slice(0, 8),
|
|
12354
|
+
conversation: x.sessionId,
|
|
12355
|
+
title: (x.title ?? "").slice(0, 40),
|
|
12356
|
+
kind: x.kind ?? "?",
|
|
12357
|
+
listed: x.listed ? t("yes", "s\xED") : "",
|
|
12358
|
+
turns: x.failed ? `${x.turns} (${red(String(x.failed))})` : x.turns,
|
|
12359
|
+
from: x.surfaces.join(",")
|
|
12360
|
+
}));
|
|
12361
|
+
out(r, () => `${bold(r.user)}
|
|
12362
|
+
${table(rows, ["last", "agent", "conversation", "title", "kind", "listed", "turns", "from"])}
|
|
12363
|
+
${dim(t("Read one: ghosty conversations show <agent> <conversation> --why \xB7 times in CDMX \xB7 listed = in their app list", "Leer uno: ghosty conversations show <agente> <conversaci\xF3n> --why \xB7 horas CDMX \xB7 listed = sale en su lista de la app"))}`);
|
|
12364
|
+
}
|
|
12365
|
+
async function supportThread(agent, sid, v) {
|
|
12366
|
+
const r = await api(`/api/v2/support/threads/${enc12(agent)}/${enc12(sid)}?tail=${enc12(v.limit ?? "40")}`);
|
|
12367
|
+
out(r, () => {
|
|
12368
|
+
const w = r.why;
|
|
12369
|
+
const who2 = (x, label) => x ? ` ${label} ${x.email}: ${x.listed ? lavender(t("in their list/search/push", "en su lista/b\xFAsqueda/push")) : dim(t("not listed", "no sale en su lista"))} \xB7 ${t("by link", "por liga")}: ${x.open || t("no", "no")}` : "";
|
|
12370
|
+
const why = [
|
|
12371
|
+
`${bold(r.agent.name ?? r.agent.id)}${r.agent.deleted ? red(t(" (deleted)", " (borrado)")) : ""} \xB7 ${r.title ?? ""}`,
|
|
12372
|
+
` kind ${bold(w.kind ?? "?")} \xB7 ${t("thread of", "hilo de")} ${w.user ?? "\u2014"}${w.archived ? dim(` \xB7 ${t("archived", "archivado")} ${cdmx(w.archived)}`) : ""}`,
|
|
12373
|
+
w.lastTurn ? dim(` ${t("last turn", "\xFAltimo turno")}: canal ${w.lastTurn.canal ?? "?"} \xB7 origen ${w.lastTurn.origen ?? "?"} \xB7 ${w.lastTurn.surface ?? "?"} \xB7 ${w.lastTurn.userId ?? "?"}`) : "",
|
|
12374
|
+
who2(w.owner, t("agent owner", "due\xF1o del agente")),
|
|
12375
|
+
who2(w.threadUser, t("thread user", "usuario del hilo")),
|
|
12376
|
+
dim(` ${serverText(w.rule)}`)
|
|
12377
|
+
].filter(Boolean);
|
|
12378
|
+
if (v.why) return why.join("\n");
|
|
12379
|
+
const msgs = r.messages.map((m) => `${dim(cdmx(m.at))} ${bold(m.role.padEnd(9))} ${m.text.replace(/\s+/g, " ").slice(0, 400)}`);
|
|
12380
|
+
return [...why, "", ...msgs].join("\n");
|
|
12381
|
+
});
|
|
12382
|
+
}
|
|
12383
|
+
async function cleanupConversations(action, agent, v) {
|
|
12384
|
+
const ex = `ghosty conversations ${action} <agent> --kind ${action === "rm" ? "test" : "test|channel"} [--before 7d] [--dry-run]`;
|
|
12385
|
+
if (!agent) throw usage(t("Missing agent.", "Falta el agente."), ex);
|
|
12386
|
+
if (v.kind !== "test" && v.kind !== "channel") throw usage(t("--kind takes test or channel.", "--kind acepta test o channel."), ex);
|
|
12387
|
+
const path = `/api/v2/agents/${enc12(agent)}/conversations/cleanup`;
|
|
12388
|
+
const body = { kind: v.kind, action, ...v.before ? { before: v.before } : {} };
|
|
12389
|
+
const plan = await api(path, { method: "POST", body: { ...body, dryRun: true } });
|
|
12390
|
+
const verb = action === "rm" ? t("delete (rows + agent memory)", "borrar (filas + memoria del agente)") : t("archive (hidden; memory stays)", "archivar (se ocultan; la memoria se queda)");
|
|
12391
|
+
const line2 = t(`${plan.threads} ${v.kind} conversation(s), ${plan.messages} message(s) to ${verb}`, `${plan.threads} conversaci\xF3n(es) ${v.kind}, ${plan.messages} mensaje(s) para ${verb}`);
|
|
12392
|
+
if (v["dry-run"] || !plan.threads) return out(plan, () => `${line2}${plan.sample.length ? `
|
|
12393
|
+
${dim(plan.sample.join("\n"))}` : ""}
|
|
12394
|
+
${dim(t("Dry run: nothing changed.", "Simulaci\xF3n: no cambi\xF3 nada."))}`);
|
|
12395
|
+
requireYesIfHeadless(v.yes, `ghosty conversations ${action} ${agent} --kind ${v.kind}${v.before ? ` --before ${v.before}` : ""}`);
|
|
12396
|
+
await confirm(`${line2}
|
|
12397
|
+
${t("Go ahead?", "\xBFSigo?")}`, { yes: v.yes, retry: `ghosty conversations ${action} ${agent} --kind ${v.kind}` });
|
|
12398
|
+
const r = await api(path, { method: "POST", body });
|
|
12399
|
+
out(r, () => `${lavender("\u2713")} ${t(`${r.threads} conversation(s) ${action === "rm" ? "deleted" : "archived"}.`, `${r.threads} conversaci\xF3n(es) ${action === "rm" ? "borradas" : "archivadas"}.`)}`);
|
|
12400
|
+
}
|
|
12401
|
+
async function turnStats(v) {
|
|
12402
|
+
const r = await api(`/api/v2/support/turn-stats?since=${enc12(v.since ?? "14d")}${v.all ? "&all=1" : ""}`);
|
|
12403
|
+
out(
|
|
12404
|
+
r,
|
|
12405
|
+
() => [
|
|
12406
|
+
`${bold(`${r.failRate}%`)} ${t("of turns failed", "de los turnos fallaron")} ${dim(`(${r.failed}/${r.total}, ${v.since ?? "14d"}${r.filtered ? t(" \xB7 without staff, test channel or deleted agents (--all to include)", " \xB7 sin staff, canal prueba ni agentes borrados (--all los incluye)") : ""})`)}`,
|
|
12407
|
+
dim(Object.entries(r.byState).map(([k, n]) => `${k} ${n}`).join(" \xB7 ")),
|
|
12408
|
+
"",
|
|
12409
|
+
table(r.byEngine.map((e) => ({ engine: e.engine, turns: e.turns, failed: e.failed, rate: `${e.failRate}%` })), ["engine", "turns", "failed", "rate"]),
|
|
12410
|
+
...r.topErrors.length ? ["", table(r.topErrors.map((e) => ({ n: e.n, error: e.error })), ["n", "error"])] : []
|
|
12411
|
+
].join("\n")
|
|
12412
|
+
);
|
|
12413
|
+
}
|
|
12414
|
+
async function fleetBoxes() {
|
|
12415
|
+
const r = await api("/api/v2/support/boxes?by=template");
|
|
12416
|
+
const rows = r.templates.map((x) => ({ template: x.template, boxes: x.boxes, on: x.on, asleep: x.asleep, ram: x.memMb.map((m) => `${m / 1024} GB`).join(", "), vcpu: x.vcpus.join(", "), "ram on": `${(x.ramOnMb / 1024).toFixed(1)} GB` }));
|
|
12417
|
+
out(r, () => `${bold(t(`${r.total} box(es), ${r.on} awake`, `${r.total} caja(s), ${r.on} despiertas`))}
|
|
12418
|
+
${table(rows, ["template", "boxes", "on", "asleep", "ram", "vcpu", "ram on"])}`);
|
|
12419
|
+
}
|
|
12420
|
+
async function appAgentsOf(email, client) {
|
|
12421
|
+
const r = await api(`/api/v2/support/app-agents?user=${enc12(email)}${client && client !== "all" ? `&client=${enc12(client)}` : ""}`);
|
|
12422
|
+
const rows = r.agentes.map((a) => ({ id: a.id, name: a.nombre, engine: a.motor ?? "", model: a.modelo ?? "", tone: a.tono ?? t("(by position)", "(por posici\xF3n)"), space: a.espacio?.tipo === "workspace" ? a.espacio.nombre : a.espacio?.tipo ?? "", last: a.ultimaActividad ? cdmx(a.ultimaActividad) : "" }));
|
|
12423
|
+
out(r, () => `${bold(r.user)} ${dim(r.client ? t(`as the ${r.client} app sees it`, `como lo ve la app de ${r.client}`) : t("all apps", "todas las apps"))}
|
|
12424
|
+
${table(rows, ["id", "name", "engine", "model", "tone", "space", "last"])}`);
|
|
12425
|
+
}
|
|
12426
|
+
async function pushLog(email, v) {
|
|
12427
|
+
if (!email) throw usage(t("Missing --user.", "Falta --user."), "ghosty push log --user ana@empresa.com [--since 2d]");
|
|
12428
|
+
const r = await api(`/api/v2/support/push-log?user=${enc12(email)}&since=${enc12(v.since ?? "2d")}${v.limit ? `&limit=${enc12(v.limit)}` : ""}`);
|
|
12429
|
+
const rows = r.pushes.map((p) => ({ at: cdmx(p.at), kind: p.kind, to: p.platform, status: p.status === "sent" ? p.status : red(p.status), thread: p.threadKind ? `${p.threadId?.slice(0, 18)} (${p.threadKind})` : (p.threadId ?? "").slice(0, 24), title: (p.title ?? "").slice(0, 40), detail: p.detail ?? "" }));
|
|
12430
|
+
out(r, () => rows.length ? `${bold(r.user)}
|
|
12431
|
+
${table(rows, ["at", "kind", "to", "status", "thread", "title", "detail"])}` : dim(t(`No pushes for ${r.user} in that range (logged since 2026-10-01).`, `Sin avisos para ${r.user} en ese rango (se registran desde el 1-oct).`)));
|
|
12432
|
+
}
|
|
12433
|
+
async function novedadesCmd(sub, id, v) {
|
|
12434
|
+
const base = "/api/v2/support/announcements";
|
|
12435
|
+
const ex = 'ghosty novedades ls \xB7 create --title T --body B --image hero.png [--link ruta] [--button "Abrir"] [--to a@x.com,b@y.com] [--publish] \xB7 set <id> \u2026 \xB7 test <id> \xB7 send <id> [--at "2026-10-02T12:00"] \xB7 rm <id>';
|
|
12436
|
+
const fields = async () => ({
|
|
12437
|
+
...v.title !== void 0 ? { title: v.title } : {},
|
|
12438
|
+
...v.body !== void 0 ? { body: v.body } : {},
|
|
12439
|
+
...v.image ? { image: (await readFile13(v.image)).toString("base64") } : {},
|
|
12440
|
+
...v.link !== void 0 ? { enlace: v.link || null } : {},
|
|
12441
|
+
...v.button !== void 0 ? { boton: v.button || null } : {},
|
|
12442
|
+
...v.to !== void 0 ? { destinatarios: v.to || null } : {},
|
|
12443
|
+
...v.publish ? { published: true } : v.draft ? { published: false } : {}
|
|
12444
|
+
});
|
|
12445
|
+
const line2 = (a) => `${bold(a.title)} ${dim(a.id)}
|
|
12446
|
+
${a.published ? lavender(t("published", "publicada")) : dim(t("draft", "borrador"))} \xB7 ${a.pushedAt ? t(`pushed ${cdmx(a.pushedAt)} (${a.pushCount})`, `enviada ${cdmx(a.pushedAt)} (${a.pushCount})`) : a.schedule ? t(`push scheduled ${cdmx(a.schedule)} CDMX`, `push programado ${cdmx(a.schedule)} CDMX`) : t("not pushed", "sin enviar")} \xB7 ${a.destinatarios ? t(`to ${a.destinatarios.split(",").length}`, `para ${a.destinatarios.split(",").length}`) : t("everyone", "todos")}`;
|
|
12447
|
+
switch (sub ?? "ls") {
|
|
12448
|
+
case "ls":
|
|
12449
|
+
case "list": {
|
|
12450
|
+
const r = await api(base);
|
|
12451
|
+
return out(r, () => r.announcements.map(line2).join("\n") || dim(t("No announcements.", "Sin novedades.")));
|
|
12452
|
+
}
|
|
12453
|
+
case "create":
|
|
12454
|
+
case "new": {
|
|
12455
|
+
if (!v.title || !v.body) throw usage(t("Missing --title and --body.", "Faltan --title y --body."), ex);
|
|
12456
|
+
const r = await api(base, { method: "POST", body: await fields() });
|
|
12457
|
+
return out(r, () => `${lavender("\u2713")} ${line2(r)}
|
|
12458
|
+
${dim(t(`Try it on your phone: ghosty novedades test ${r.id}`, `Pru\xE9bala en tu tel\xE9fono: ghosty novedades test ${r.id}`))}`);
|
|
12459
|
+
}
|
|
12460
|
+
case "set": {
|
|
12461
|
+
if (!id) throw usage(t("Missing id.", "Falta el id."), ex);
|
|
12462
|
+
const b = await fields();
|
|
12463
|
+
if (!Object.keys(b).length) throw usage(t("Nothing to change.", "Nada que cambiar."), ex);
|
|
12464
|
+
const r = await api(`${base}/${enc12(id)}`, { method: "PATCH", body: b });
|
|
12465
|
+
return out(r, () => `${lavender("\u2713")} ${line2(r)}`);
|
|
12466
|
+
}
|
|
12467
|
+
case "test": {
|
|
12468
|
+
if (!id) throw usage(t("Missing id.", "Falta el id."), ex);
|
|
12469
|
+
const r = await api(`${base}/${enc12(id)}`, { method: "POST", body: { push: "test" } });
|
|
12470
|
+
return out(r, () => `${lavender("\u2713")} ${t(`Sent to your phone (${r.enviados} device(s)).`, `Enviada a tu tel\xE9fono (${r.enviados} dispositivo(s)).`)}`);
|
|
12471
|
+
}
|
|
12472
|
+
case "send":
|
|
12473
|
+
case "schedule": {
|
|
12474
|
+
if (!id) throw usage(t("Missing id.", "Falta el id."), ex);
|
|
12475
|
+
if (v.at !== void 0) {
|
|
12476
|
+
const r2 = await api(`${base}/${enc12(id)}`, { method: "PATCH", body: { schedule: v.at || null } });
|
|
12477
|
+
return out(r2, () => `${lavender("\u2713")} ${line2(r2)}`);
|
|
12478
|
+
}
|
|
12479
|
+
requireYesIfHeadless(v.yes, `ghosty novedades send ${id}`);
|
|
12480
|
+
await confirm(t("Push this announcement to every recipient now?", "\xBFMandar ya el push de esta novedad a todos sus destinatarios?"), { yes: v.yes, retry: `ghosty novedades send ${id}` });
|
|
12481
|
+
const r = await api(`${base}/${enc12(id)}`, { method: "POST", body: { push: "all" } });
|
|
12482
|
+
return out(r, () => `${lavender("\u2713")} ${t(`Pushed to ${r.enviados} device(s).`, `Enviada a ${r.enviados} dispositivo(s).`)}`);
|
|
12483
|
+
}
|
|
12484
|
+
case "rm":
|
|
12485
|
+
case "delete": {
|
|
12486
|
+
if (!id) throw usage(t("Missing id.", "Falta el id."), ex);
|
|
12487
|
+
requireYesIfHeadless(v.yes, `ghosty novedades rm ${id}`);
|
|
12488
|
+
await confirm(t(`Delete announcement ${id}?`, `\xBFBorrar la novedad ${id}?`), { yes: v.yes, retry: `ghosty novedades rm ${id}` });
|
|
12489
|
+
return out(await api(`${base}/${enc12(id)}`, { method: "DELETE" }), () => `${lavender("\u2713")} ${t("Deleted.", "Borrada.")}`);
|
|
12490
|
+
}
|
|
12491
|
+
default:
|
|
12492
|
+
throw usage(t(`Unknown novedades subcommand: ${sub}.`, `Subcomando de novedades desconocido: ${sub}.`), ex);
|
|
12493
|
+
}
|
|
12494
|
+
}
|
|
12495
|
+
|
|
12137
12496
|
// src/index.ts
|
|
12138
12497
|
var HELP_EN = `${bold("Usage:")} ghosty <command> [options]
|
|
12139
12498
|
|
|
@@ -12144,18 +12503,21 @@ ${bold("Session")}
|
|
|
12144
12503
|
whoami Show who you are signed in as
|
|
12145
12504
|
use [workspace] Default workspace for boards, agents create and agents ls [--clear]
|
|
12146
12505
|
completion zsh|bash|fish Tab completion for commands, flags, agents and workspaces
|
|
12147
|
-
usage Your plan and how much you've used this week and month [--boxes: boxes vs the workspace limit]
|
|
12506
|
+
usage Your plan and how much you've used this week and month [--boxes: boxes vs the workspace limit \xB7 --boxes --by template: the whole fleet (staff)]
|
|
12148
12507
|
usage --workspace SLUG A workspace's turns and tokens per agent/day [--by agent,day] [--days 14]
|
|
12149
12508
|
plan apply [workspace] Apply a scheduled plan now, showing what changes [--dry-run]
|
|
12150
12509
|
engines ls Engines and models valid for --engine/--model [--models]
|
|
12151
12510
|
|
|
12152
12511
|
${bold("Workspaces")} ${dim("(changing them: Ghosty staff)")}
|
|
12153
12512
|
bags ls|create|set|rm <workspace> ["bag"] Token bags its agents spend from [--tokens 20M] [--recurring on|off] [--hard-cap on|off] [--note]
|
|
12513
|
+
spaces ls Your workspaces (role, plan, combo) [--user EMAIL: someone else's]
|
|
12514
|
+
spaces join|leave <workspace> Enter or leave a client's workspace as staff (no seat; logged)
|
|
12515
|
+
spaces set <workspace> --combo sales teams | sales | both (without Sales its boards are invisible)
|
|
12154
12516
|
spaces renew <workspace> --until DATE Move \xABpaid until\xBB (logged) [--note] [--dry-run]
|
|
12155
12517
|
spaces rename <old> <new> Change its slug only [--dry-run]
|
|
12156
12518
|
|
|
12157
12519
|
${bold("Agents")}
|
|
12158
|
-
agents ls List your agents [--engine E] [--ids: only ids, for xargs]
|
|
12520
|
+
agents ls List your agents [--engine E] [--ids: only ids, for xargs] \xB7 --user EMAIL --app all|android|ios|desktop: as their app sees it (staff)
|
|
12159
12521
|
[--sponsor EMAIL | --owner EMAIL] any account's agents (Ghosty staff)
|
|
12160
12522
|
agents create Create an agent --name [--engine] [--model] [--prompt | --prompt-file FILE] [--env K=V,\u2026]
|
|
12161
12523
|
[--workspace SLUG] [--channels teams=off,\u2026] born in a workspace, active in its Teams
|
|
@@ -12175,6 +12537,7 @@ ${bold("Agents")}
|
|
|
12175
12537
|
agents docs ls|grant|revoke <id> [doc] Documents it reads and may send [--write] [--no-deliver]
|
|
12176
12538
|
agents docs grant <id> A B\u2026 Grant several documents at once [--like AGENT] copies another agent's
|
|
12177
12539
|
agents docs add <id> Upload a document and grant it --url URL | --file PATH [--description] [--grant read|write|no]
|
|
12540
|
+
agents docs dedupe Your account: merge documents with the same content (keeps every agent's access) [--dry-run] \xB7 dedupe <keep> <drop>
|
|
12178
12541
|
agents docs replace <id> <doc> New version (keeps name and access) --url URL | --file PATH [--note]
|
|
12179
12542
|
agents docs history|restore <id> <doc> [n] Its versions; restore n as a new version
|
|
12180
12543
|
agents share|unshare <id> <email> Share it without handing it over [--role editor|admin] \xB7 agents shares <id> lists them
|
|
@@ -12196,17 +12559,24 @@ ${bold("Configure an agent")}
|
|
|
12196
12559
|
mcp get|set <id> MCP servers [--file servers.json]
|
|
12197
12560
|
channels whatsapp ls|enable|disable <id> [number] Which WhatsApp Business numbers it answers [--dry-run]
|
|
12198
12561
|
channels whatsapp integrations <id> Its owner's WhatsApp Business numbers in Formmy (number, who answers today, Integration id)
|
|
12199
|
-
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]
|
|
12200
|
-
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]
|
|
12562
|
+
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] [--ads-only on|off] [--dry-run]
|
|
12563
|
+
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 [--list]] [--only-ads] [--phones a,b] [--column NAME]
|
|
12201
12564
|
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]
|
|
12202
12565
|
channels wa pair <id> --phone N Pair a personal WhatsApp by code (no QR)
|
|
12203
12566
|
channels wa status|pause <id> Personal WhatsApp: how it is / pause it (keeps the session)
|
|
12204
|
-
channels wa groups ls|enable|disable|set|create <id> [group] Personal WhatsApp groups it answers, and as whom [--role equipo|cliente] [--dry-run]
|
|
12567
|
+
channels wa groups ls|enable|disable|set|create <id> [group] Personal WhatsApp groups it answers, and as whom [--role equipo|cliente] [--name "New name"] [--dry-run]
|
|
12205
12568
|
boards ls|create|assign|move-cards|rm A workspace's boards: create (--columns-from), where an agent sends cards, move cards
|
|
12206
12569
|
cards ls <id> Recent cards on its sales board [--q TEXT] [--paused] [--limit 20]
|
|
12207
12570
|
cards show <id> <card> A card's thread: who wrote, text, attachments and whether gs kept a copy (folio, phone or id)
|
|
12208
12571
|
cards archive <id> Archive cards in bulk (reversible) --integration X | --column X | --before 30d [--dry-run]
|
|
12209
12572
|
|
|
12573
|
+
${bold("Support")} ${dim("(Ghosty staff)")}
|
|
12574
|
+
conversations ls --user EMAIL Someone's conversations in any agent [--since 30d] [--surface android]
|
|
12575
|
+
conversations show <id> <conv> --why A conversation: who sees it in their list / by link, and why
|
|
12576
|
+
turns stats Real failure rate by engine and error [--since 14d] [--all]
|
|
12577
|
+
push log --user EMAIL Pushes sent to someone and what Apple/Google answered [--since 2d]
|
|
12578
|
+
novedades ls|create|set|test|send|rm Android app announcements [--title] [--body] [--image FILE] [--link] [--to] [--publish] [--at]
|
|
12579
|
+
|
|
12210
12580
|
${bold("Demos")}
|
|
12211
12581
|
demos try <slug> "msg" ["msg2"\u2026] Try a public demo as a visitor (same thread across runs) [--new] [--recycle-between --agent X]
|
|
12212
12582
|
demos history <id> What visitors did: turns, messages, files (Ghosty staff) [--since 2d] [--session web:\u2026] [--timeline] [--full]
|
|
@@ -12215,9 +12585,10 @@ ${bold("Talk to an agent")}
|
|
|
12215
12585
|
try <id> "message" One test turn, prints the full answer [--session] [--reset] [--media FILE: attach, as from the app]
|
|
12216
12586
|
--waba --session PHONE \u2026through the real WhatsApp path, nothing sent; lists what it would send (tools too) [--media FILE [--as-formmy]] [--history FILE]
|
|
12217
12587
|
--replay FILE --out DIR Replay real conversations; report written locally [--max 15] [--concurrency 8] [--resume]
|
|
12218
|
-
chat <id> ["message"] Stream a conversation (no message = interactive) [--conversation ID] [--media FILE]
|
|
12588
|
+
chat <id> ["message"] Stream a conversation (no message = interactive) [--conversation ID] [--media FILE] [--file ID: from your files]
|
|
12219
12589
|
conversations ls <id> List conversations
|
|
12220
12590
|
conversations show <id> <conversation> What was said [--limit 20] (0 = all)
|
|
12591
|
+
conversations archive|rm <id> --kind test|channel Clean its history (rm: tests only) [--before 7d] [--dry-run]
|
|
12221
12592
|
turns ls <id> Recent turns and why they failed [--errors] [--skipped] [--since 1h] [--limit N]
|
|
12222
12593
|
turns ls --workspace SLUG --quota-hits Messages that got no turn because the balance ran out [--days 14]
|
|
12223
12594
|
turns show <id> [turn] What came in, what it answered, and where the time went (default: last) [--full]
|
|
@@ -12233,7 +12604,7 @@ ${bold("Partners")} ${dim("(give an agent to each of YOUR customers \u2014 see:
|
|
|
12233
12604
|
partner ls | enable | credentials | agent | tenants | audit | try
|
|
12234
12605
|
|
|
12235
12606
|
${bold("Your account")}
|
|
12236
|
-
me files ls|upload|rm
|
|
12607
|
+
me files ls|get|upload|rm Files in your account [--q TEXT] [--kind image|video|audio|document|other] [--cursor C] \xB7 get <id> [--out FILE]
|
|
12237
12608
|
credentials ls|set|rm [provider] Provider keys in your vault (hidden prompt, stdin or --from-env VAR; never as an argument)
|
|
12238
12609
|
docs [topic] Open the docs
|
|
12239
12610
|
|
|
@@ -12259,18 +12630,21 @@ ${bold("Sesi\xF3n")}
|
|
|
12259
12630
|
whoami Muestra con qu\xE9 cuenta entraste
|
|
12260
12631
|
use [espacio] Espacio por default para boards, agents create y agents ls [--clear]
|
|
12261
12632
|
completion zsh|bash|fish Autocompletado con TAB: comandos, opciones, agentes y espacios
|
|
12262
|
-
usage Tu plan y cu\xE1nto llevas usado esta semana y este mes [--boxes: cajas contra el tope del espacio]
|
|
12633
|
+
usage Tu plan y cu\xE1nto llevas usado esta semana y este mes [--boxes: cajas contra el tope del espacio \xB7 --boxes --by template: toda la flota (staff)]
|
|
12263
12634
|
usage --workspace SLUG Turnos y tokens de un espacio por agente/d\xEDa [--by agent,day] [--days 14]
|
|
12264
12635
|
plan apply [espacio] Aplica ya un plan programado, ense\xF1ando qu\xE9 cambia [--dry-run]
|
|
12265
12636
|
engines ls Motores y modelos v\xE1lidos para --engine/--model [--models]
|
|
12266
12637
|
|
|
12267
12638
|
${bold("Espacios")} ${dim("(cambiarlos: staff de Ghosty)")}
|
|
12268
12639
|
bags ls|create|set|rm <espacio> ["bolsa"] Bolsas de tokens de las que gastan sus agentes [--tokens 20M] [--recurring on|off] [--hard-cap on|off] [--note]
|
|
12640
|
+
spaces ls Tus espacios (rol, plan, combo) [--user CORREO: los de alguien m\xE1s]
|
|
12641
|
+
spaces join|leave <espacio> Entra o sal del espacio de un cliente como staff (sin asiento; queda en bit\xE1cora)
|
|
12642
|
+
spaces set <espacio> --combo sales teams | sales | both (sin Ventas sus tableros no se ven)
|
|
12269
12643
|
spaces renew <espacio> --until FECHA Mueve \xABpagado hasta\xBB (queda en bit\xE1cora) [--note] [--dry-run]
|
|
12270
12644
|
spaces rename <viejo> <nuevo> Cambia s\xF3lo su slug [--dry-run]
|
|
12271
12645
|
|
|
12272
12646
|
${bold("Agentes")}
|
|
12273
|
-
agents ls Lista tus agentes [--engine E] [--ids: s\xF3lo ids, para xargs]
|
|
12647
|
+
agents ls Lista tus agentes [--engine E] [--ids: s\xF3lo ids, para xargs] \xB7 --user CORREO --app all|android|ios|desktop: como lo ve su app (staff)
|
|
12274
12648
|
[--sponsor CORREO | --owner CORREO] agentes de cualquier cuenta (staff de Ghosty)
|
|
12275
12649
|
agents create Crea un agente --name [--engine] [--model] [--prompt | --prompt-file ARCHIVO] [--env K=V,\u2026]
|
|
12276
12650
|
[--workspace SLUG] [--channels teams=off,\u2026] nace en un espacio, activo en su Teams
|
|
@@ -12290,6 +12664,7 @@ ${bold("Agentes")}
|
|
|
12290
12664
|
agents docs ls|grant|revoke <id> [doc] Documentos que lee y puede enviar [--write] [--no-deliver]
|
|
12291
12665
|
agents docs grant <id> A B\u2026 Da varios documentos a la vez [--like AGENTE] copia los de otro agente
|
|
12292
12666
|
agents docs add <id> Sube un documento y d\xE1selo --url URL | --file RUTA [--description] [--grant read|write|no]
|
|
12667
|
+
agents docs dedupe Tu cuenta: une los documentos con el mismo contenido (conserva el acceso de cada agente) [--dry-run] \xB7 dedupe <queda> <se-va>
|
|
12293
12668
|
agents docs replace <id> <doc> Versi\xF3n nueva (conserva nombre y accesos) --url URL | --file RUTA [--note]
|
|
12294
12669
|
agents docs history|restore <id> <doc> [n] Sus versiones; restaura la n como versi\xF3n nueva
|
|
12295
12670
|
agents share|unshare <id> <correo> Comp\xE1rtelo sin cederlo [--role editor|admin] \xB7 agents shares <id> los lista
|
|
@@ -12311,17 +12686,24 @@ ${bold("Configurar un agente")}
|
|
|
12311
12686
|
mcp get|set <id> Servidores MCP [--file servers.json]
|
|
12312
12687
|
channels whatsapp ls|enable|disable <id> [n\xFAmero] Qu\xE9 n\xFAmeros de WhatsApp Business contesta [--dry-run]
|
|
12313
12688
|
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)
|
|
12314
|
-
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]
|
|
12315
|
-
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]
|
|
12689
|
+
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] [--ads-only on|off] [--dry-run]
|
|
12690
|
+
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 [--list]] [--only-ads] [--phones a,b] [--column NOMBRE]
|
|
12316
12691
|
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]
|
|
12317
12692
|
channels wa pair <id> --phone N Vincula un WhatsApp personal con c\xF3digo (sin QR)
|
|
12318
12693
|
channels wa status|pause <id> WhatsApp personal: c\xF3mo est\xE1 / pausarlo (conserva la sesi\xF3n)
|
|
12319
|
-
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]
|
|
12694
|
+
channels wa groups ls|enable|disable|set|create <id> [grupo] Grupos de WhatsApp personal que contesta, y como qui\xE9n [--role equipo|cliente] [--name "Nombre nuevo"] [--dry-run]
|
|
12320
12695
|
boards ls|create|assign|move-cards|rm Los tableros de un espacio: crear (--columns-from), a d\xF3nde manda tarjetas un agente, mover tarjetas
|
|
12321
12696
|
cards ls <id> Tarjetas recientes de su tablero de ventas [--q TEXTO] [--paused] [--limit 20]
|
|
12322
12697
|
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)
|
|
12323
12698
|
cards archive <id> Archiva tarjetas en bloque (reversible) --integration X | --column X | --before 30d [--dry-run]
|
|
12324
12699
|
|
|
12700
|
+
${bold("Soporte")} ${dim("(staff de Ghosty)")}
|
|
12701
|
+
conversations ls --user CORREO Las conversaciones de alguien en cualquier agente [--since 30d] [--surface android]
|
|
12702
|
+
conversations show <id> <conv> --why Una conversaci\xF3n: qui\xE9n la ve en su lista / por liga, y por qu\xE9
|
|
12703
|
+
turns stats Tasa real de fallos por motor y por error [--since 14d] [--all]
|
|
12704
|
+
push log --user CORREO Avisos que se le mandaron a alguien y qu\xE9 contest\xF3 Apple/Google [--since 2d]
|
|
12705
|
+
novedades ls|create|set|test|send|rm Novedades de la app Android [--title] [--body] [--image ARCHIVO] [--link] [--to] [--publish] [--at]
|
|
12706
|
+
|
|
12325
12707
|
${bold("Demos")}
|
|
12326
12708
|
demos try <slug> "msg" ["msg2"\u2026] Prueba un demo p\xFAblico como visitante (mismo hilo entre corridas) [--new] [--recycle-between --agent X]
|
|
12327
12709
|
demos history <id> Qu\xE9 hicieron los visitantes: turnos, mensajes, archivos (staff de Ghosty) [--since 2d] [--session web:\u2026] [--timeline] [--full]
|
|
@@ -12330,9 +12712,10 @@ ${bold("Hablar con un agente")}
|
|
|
12330
12712
|
try <id> "mensaje" Un turno de prueba; imprime la respuesta completa [--session] [--reset] [--media ARCHIVO: adjunto, como desde la app]
|
|
12331
12713
|
--waba --session TEL \u2026por el camino real de WhatsApp, sin enviar nada; lista lo que mandar\xEDa (tools tambi\xE9n) [--media ARCHIVO [--as-formmy]] [--history ARCHIVO]
|
|
12332
12714
|
--replay ARCHIVO --out DIR Repite conversaciones reales; el reporte se escribe local [--max 15] [--concurrency 8] [--resume]
|
|
12333
|
-
chat <id> ["mensaje"] Conversaci\xF3n en streaming (sin mensaje = interactivo) [--conversation ID] [--media ARCHIVO]
|
|
12715
|
+
chat <id> ["mensaje"] Conversaci\xF3n en streaming (sin mensaje = interactivo) [--conversation ID] [--media ARCHIVO] [--file ID: de tus archivos]
|
|
12334
12716
|
conversations ls <id> Lista conversaciones
|
|
12335
12717
|
conversations show <id> <conversaci\xF3n> Qu\xE9 se dijo [--limit 20] (0 = todo)
|
|
12718
|
+
conversations archive|rm <id> --kind test|channel Limpia su historial (rm: s\xF3lo pruebas) [--before 7d] [--dry-run]
|
|
12336
12719
|
turns ls <id> Turnos recientes y por qu\xE9 fallaron [--errors] [--skipped] [--since 1h] [--limit N]
|
|
12337
12720
|
turns ls --workspace SLUG --quota-hits Mensajes que no abrieron turno porque se acab\xF3 el saldo [--days 14]
|
|
12338
12721
|
turns show <id> [turno] Qu\xE9 lleg\xF3, qu\xE9 contest\xF3 y en qu\xE9 se fue el tiempo (default: el \xFAltimo) [--full]
|
|
@@ -12348,7 +12731,7 @@ ${bold("Partners")} ${dim("(dale un agente a cada uno de TUS clientes \u2014 ve
|
|
|
12348
12731
|
partner ls | enable | credentials | agent | tenants | audit | try
|
|
12349
12732
|
|
|
12350
12733
|
${bold("Tu cuenta")}
|
|
12351
|
-
me files ls|upload|rm
|
|
12734
|
+
me files ls|get|upload|rm Archivos de tu cuenta [--q TEXTO] [--kind image|video|audio|document|other] [--cursor C] \xB7 get <id> [--out ARCHIVO]
|
|
12352
12735
|
credentials ls|set|rm [proveedor] Llaves de proveedor en tu b\xF3veda (prompt oculto, stdin o --from-env VAR; nunca como argumento)
|
|
12353
12736
|
docs [tema] Abre la documentaci\xF3n
|
|
12354
12737
|
|
|
@@ -12365,7 +12748,7 @@ ${bold("Opciones globales")}
|
|
|
12365
12748
|
${bold("C\xF3digos de salida")} 0 bien \xB7 1 error de la API \xB7 2 uso incorrecto \xB7 3 sin sesi\xF3n
|
|
12366
12749
|
${dim("Nota: `ghosty` puede chocar con otro binario en tu PATH (p. ej. la TUI de Ghosty). Rev\xEDsalo con `which -a ghosty`; corre `npx @ghostystudio/cli` para ir a la segura.")}
|
|
12367
12750
|
${dim("Documentaci\xF3n: https://www.ghosty.studio/docs/cli")}`;
|
|
12368
|
-
var COMMANDS = ["login", "logout", "whoami", "usage", "use", "apply", "plan", "engines", "bags", "spaces", "demos", "completion", "cards", "agents", "files", "skills", "mcp", "dbs", "turns", "try", "chat", "conversations", "schedule", "partner", "me", "channels", "board", "boards", "credentials", "docs", "help"];
|
|
12751
|
+
var COMMANDS = ["login", "logout", "whoami", "usage", "use", "apply", "plan", "engines", "bags", "spaces", "workspaces", "demos", "push", "novedades", "completion", "cards", "agents", "files", "skills", "mcp", "dbs", "turns", "try", "chat", "conversations", "schedule", "partner", "me", "channels", "board", "boards", "credentials", "docs", "help"];
|
|
12369
12752
|
var AGENTS_SUBS = ["ls", "create", "get", "set", "export", "doctor", "docs", "share", "unshare", "shares", "rm", "restart", "demo", "box"];
|
|
12370
12753
|
function unknown(what, input, known, helpCmd) {
|
|
12371
12754
|
const s = suggest(input, known);
|
|
@@ -12538,6 +12921,21 @@ async function main() {
|
|
|
12538
12921
|
models: { type: "boolean" },
|
|
12539
12922
|
teams: { type: "string" },
|
|
12540
12923
|
owner: { type: "string" },
|
|
12924
|
+
cursor: { type: "string" },
|
|
12925
|
+
user: { type: "string" },
|
|
12926
|
+
why: { type: "boolean" },
|
|
12927
|
+
surface: { type: "string" },
|
|
12928
|
+
image: { type: "string" },
|
|
12929
|
+
link: { type: "string" },
|
|
12930
|
+
button: { type: "string" },
|
|
12931
|
+
publish: { type: "boolean" },
|
|
12932
|
+
draft: { type: "boolean" },
|
|
12933
|
+
"ads-only": { type: "string" },
|
|
12934
|
+
list: { type: "boolean" },
|
|
12935
|
+
"only-ads": { type: "boolean" },
|
|
12936
|
+
phones: { type: "string" },
|
|
12937
|
+
combo: { type: "string" },
|
|
12938
|
+
app: { type: "string" },
|
|
12541
12939
|
ids: { type: "boolean" },
|
|
12542
12940
|
force: { type: "boolean" },
|
|
12543
12941
|
reenv: { type: "boolean" },
|
|
@@ -12554,7 +12952,7 @@ async function main() {
|
|
|
12554
12952
|
const tokens = splitTokens(pos, v);
|
|
12555
12953
|
globals.token = tokens.session;
|
|
12556
12954
|
globals.profile = v.profile;
|
|
12557
|
-
if (v.version) return out({ version: "0.
|
|
12955
|
+
if (v.version) return out({ version: "0.31.0" }, () => "0.31.0");
|
|
12558
12956
|
for (const n of normalizeArgs(pos, v)) if (!globals.json) info(dim(n));
|
|
12559
12957
|
const context = (await readConfig()).workspace;
|
|
12560
12958
|
if (!explicitToken()?.startsWith("gat_")) {
|
|
@@ -12600,6 +12998,7 @@ async function main() {
|
|
|
12600
12998
|
case "apply":
|
|
12601
12999
|
return applyCmd(sub, v);
|
|
12602
13000
|
case "agents":
|
|
13001
|
+
if ((sub === "ls" || sub === "list" || !sub) && v.user && v.app !== void 0) return appAgentsOf(v.user, v.app);
|
|
12603
13002
|
if (sub === "ls" || sub === "list" || !sub) return agentsLs(v.all ? void 0 : v.workspace ?? context, { sponsor: v.sponsor, owner: v.owner, engine: v.engine, ids: v.ids, all: v.all });
|
|
12604
13003
|
if (sub === "create" || sub === "new") {
|
|
12605
13004
|
if (v.workspace === "none") delete v.workspace;
|
|
@@ -12632,7 +13031,14 @@ async function main() {
|
|
|
12632
13031
|
return dbsCmd(sub, a1, a2, pos.slice(4).filter((x) => x !== void 0), v);
|
|
12633
13032
|
case "turns":
|
|
12634
13033
|
if (v["quota-hits"]) return workspaceUsage(v.workspace ?? context, v);
|
|
13034
|
+
if (sub === "stats") return turnStats(v);
|
|
12635
13035
|
return turnsCmd(sub, a1, a2, v);
|
|
13036
|
+
case "push":
|
|
13037
|
+
if (sub !== "log") throw usage(t("Unknown push subcommand.", "Subcomando de push desconocido."), "ghosty push log --user <email> [--since 2d]");
|
|
13038
|
+
return pushLog(v.user, v);
|
|
13039
|
+
case "novedades":
|
|
13040
|
+
case "announcements":
|
|
13041
|
+
return novedadesCmd(sub, a1, v);
|
|
12636
13042
|
case "board":
|
|
12637
13043
|
return boardCmd(sub, a1, v, a2);
|
|
12638
13044
|
case "boards":
|
|
@@ -12648,14 +13054,18 @@ async function main() {
|
|
|
12648
13054
|
case "chat":
|
|
12649
13055
|
return chat(sub, a1, v);
|
|
12650
13056
|
case "conversations":
|
|
13057
|
+
if ((sub === "ls" || sub === "list") && v.user) return supportThreads(v.user, v);
|
|
12651
13058
|
if (sub === "ls" || sub === "list") return conversationsLs(a1);
|
|
13059
|
+
if (sub === "show" && (v.why || v.user)) return supportThread(a1, a2, v);
|
|
12652
13060
|
if (sub === "show") return conversationsShow(a1, a2, v);
|
|
13061
|
+
if (sub === "archive" || sub === "rm") return cleanupConversations(sub, a1, v);
|
|
12653
13062
|
throw usage(t(`Unknown conversations subcommand: ${sub ?? "(none)"}.`, `Subcomando de conversations desconocido: ${sub ?? "(ninguno)"}.`), t("Try: ghosty conversations ls <agent-id> \xB7 ghosty conversations show <agent-id> <conversation-id>", "Prueba: ghosty conversations ls <agent-id> \xB7 ghosty conversations show <agent-id> <conversation-id>"));
|
|
12654
13063
|
case "schedule":
|
|
12655
13064
|
return scheduleCmd(sub, a1, a2, v);
|
|
12656
13065
|
case "partner":
|
|
12657
13066
|
return partnerCmd(sub, a1, a2, { ...v, token: tokens.turn });
|
|
12658
13067
|
case "usage":
|
|
13068
|
+
if (v.boxes && v.by === "template") return fleetBoxes();
|
|
12659
13069
|
if (v.boxes) return boxesUsage(v.workspace ?? context);
|
|
12660
13070
|
if (v.workspace || v.by || v.days) return workspaceUsage(v.workspace ?? context, v);
|
|
12661
13071
|
return meUsage();
|
|
@@ -12669,6 +13079,7 @@ async function main() {
|
|
|
12669
13079
|
return bagsCmd(sub, a1, a2, v, context);
|
|
12670
13080
|
case "spaces":
|
|
12671
13081
|
case "space":
|
|
13082
|
+
case "workspaces":
|
|
12672
13083
|
return spacesCmd(sub, a1, a2, v, context);
|
|
12673
13084
|
case "plan":
|
|
12674
13085
|
return planCmd(sub, a1, v, context);
|
|
@@ -12686,7 +13097,7 @@ async function main() {
|
|
|
12686
13097
|
throw unknown(cmd, cmd, COMMANDS, "ghosty --help");
|
|
12687
13098
|
}
|
|
12688
13099
|
}
|
|
12689
|
-
var notice = updateNotice("0.
|
|
13100
|
+
var notice = updateNotice("0.31.0").catch(() => null);
|
|
12690
13101
|
main().then(
|
|
12691
13102
|
async () => {
|
|
12692
13103
|
const n = await notice;
|