@ghostystudio/cli 0.49.0 → 0.50.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 +9 -0
- package/dist/index.js +459 -38
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -103,6 +103,15 @@ Every command has its own help with examples: `ghosty <command> --help` or `ghos
|
|
|
103
103
|
|
|
104
104
|
Exit codes: `0` ok · `1` API error · `2` usage error · `3` not signed in.
|
|
105
105
|
|
|
106
|
+
### Changes in 0.50.0
|
|
107
|
+
|
|
108
|
+
- `cards pause|resume <id> <card> [--mode 30min|2h|24h|until_tomorrow|permanent]`: the thread's pause menu; resume also lifts "operator only" and doesn't answer by itself. Logged.
|
|
109
|
+
- `cards reply <id> <card> [--directive TEXT]` ("Ask for a reply") and `cards pending <id> [--since 2d|ISO] [--reply] [--dry-run]`: conversations whose last message is the customer's, flagged when they must not be answered (paused, escalated, outside the 24 h window, only a reaction, not WhatsApp Business, number without agent).
|
|
110
|
+
- `channels whatsapp integrations` shows who answered each number last; `--orphans` and `channels messenger ls [--orphans]` list numbers/pages nobody answers today that had turns.
|
|
111
|
+
- `agents set --prompt-patch overlay.json` / `--prompt-append FILE [--dry-run]`: change one piece of the prompt on the server's current copy (dry run prints the diff).
|
|
112
|
+
- `audit ls [--workspace] [--agent] [--actor] [--action] [--since]`: the audit log. The agent page's Save and channel changes are logged now.
|
|
113
|
+
- `factory runs ls|show|rename` (a workspace's Software Factory runs) and `agents ls --handle build`.
|
|
114
|
+
|
|
106
115
|
### Changes in 0.49.0
|
|
107
116
|
|
|
108
117
|
- `dbs export <id> <db> [--table T] [--out FILE]`: download a database — the whole thing as `.sqlite` (sqlite3, DB Browser, DBeaver) or one table as CSV (Excel). Same buttons on each database's page in /app/datos.
|
package/dist/index.js
CHANGED
|
@@ -8135,7 +8135,36 @@ var SERVER_MESSAGES = [
|
|
|
8135
8135
|
// — Infra interna (casi nunca llega al CLI, pero por si acaso)
|
|
8136
8136
|
["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)."],
|
|
8137
8137
|
["sqld: fetch sin namespace", "sqld: fetch without namespace"],
|
|
8138
|
-
["sqld: token sin namespace ser\xEDa un token maestro", "sqld: a token without namespace would be a master token"]
|
|
8138
|
+
["sqld: token sin namespace ser\xEDa un token maestro", "sqld: a token without namespace would be a master token"],
|
|
8139
|
+
// — CLI 0.50: cards pause/resume/reply/pending, canales huérfanos, audit, factory, prompt-patch
|
|
8140
|
+
["el agente est\xE1 trabajando ahora; repite cuando termine", "the agent is working right now; try again when it's done"],
|
|
8141
|
+
["no pude saber si el agente est\xE1 trabajando", "couldn't tell whether the agent is working"],
|
|
8142
|
+
["s\xF3lo el due\xF1o cambia a qui\xE9n puede escribir el agente desde canales de clientes", "only the owner changes who the agent may email from customer channels"],
|
|
8143
|
+
["el pedido es un n\xFAmero (#15 o 15)", "the run is a number (#15 or 15)"],
|
|
8144
|
+
["el prompt cambi\xF3 mientras tanto y {}", "the prompt changed in the meantime and {}"],
|
|
8145
|
+
["el prompt no tiene \xAB{}\xBB", "the prompt has no \xAB{}\xBB"],
|
|
8146
|
+
["el prompt quedar\xEDa de {} caracteres (m\xE1x. 120000)", "the prompt would be {} characters (max 120000)"],
|
|
8147
|
+
["el token del agente no busca handles: usa tu sesi\xF3n (ghosty login)", "the agent token can't look up handles: use your session (ghosty login)"],
|
|
8148
|
+
["el token del agente no lee la bit\xE1cora: usa tu sesi\xF3n (ghosty login)", "the agent token can't read the audit log: use your session (ghosty login)"],
|
|
8149
|
+
["el token del agente no lista los canales del espacio: usa tu sesi\xF3n (ghosty login)", "the agent token can't list the workspace's channels: use your session (ghosty login)"],
|
|
8150
|
+
["el token del agente no opera la f\xE1brica: usa tu sesi\xF3n (ghosty login)", "the agent token can't operate the factory: use your session (ghosty login)"],
|
|
8151
|
+
["el t\xEDtulo pasa de 120 caracteres", "the title is longer than 120 characters"],
|
|
8152
|
+
["ese n\xFAmero lo contesta otro agente ({})", "that number is answered by another agent ({})"],
|
|
8153
|
+
["este n\xFAmero no tiene un agente que conteste", "this number has no agent answering it"],
|
|
8154
|
+
["este prompt se genera de otro agente + una capa: cambia la base o la capa, o su\xE9ltalo (ghosty agents set {} --prompt-base none)", "this prompt is generated from another agent + an overlay: change the base or the overlay, or unlink it (ghosty agents set {} --prompt-base none)"],
|
|
8155
|
+
["falta handle", "missing handle"],
|
|
8156
|
+
["kind: waba o messenger", "kind: waba or messenger"],
|
|
8157
|
+
["no encuentro el agente {} (o su bit\xE1cora no es tuya)", "agent {} not found (or its audit log isn't yours)"],
|
|
8158
|
+
["no eres miembro del espacio \xAB{}\xBB", "you're not a member of workspace \xAB{}\xBB"],
|
|
8159
|
+
["no hay un pedido #{} en la f\xE1brica de este espacio", "there's no run #{} in this workspace's factory"],
|
|
8160
|
+
["para cambiar los pedidos de \xAB{}\xBB hace falta ser due\xF1o o admin del espacio", "changing \xAB{}\xBB's runs needs the workspace's owner or an admin"],
|
|
8161
|
+
["no se pudo", "it couldn't be done"],
|
|
8162
|
+
["parche inv\xE1lido: {}", "invalid patch: {}"],
|
|
8163
|
+
["promptAppend vac\xEDo", "empty promptAppend"],
|
|
8164
|
+
["s\xF3lo el due\xF1o de \xAB{}\xBB o el staff de Ghosty leen su bit\xE1cora", "only \xAB{}\xBB's owner or Ghosty staff read its audit log"],
|
|
8165
|
+
["s\xF3lo aplica a conversaciones de WhatsApp Business", "only applies to WhatsApp Business conversations"],
|
|
8166
|
+
["ese n\xFAmero ya no est\xE1 conectado", "that number is no longer connected"],
|
|
8167
|
+
["ese n\xFAmero no es de este tablero", "that number isn't on this board"]
|
|
8139
8168
|
];
|
|
8140
8169
|
|
|
8141
8170
|
// src/i18n.ts
|
|
@@ -8434,7 +8463,7 @@ async function postLong(path, body, timeoutMs = 9e5) {
|
|
|
8434
8463
|
const req = httpsRequest(`${base}${path}`, {
|
|
8435
8464
|
method: "POST",
|
|
8436
8465
|
timeout: timeoutMs,
|
|
8437
|
-
headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.
|
|
8466
|
+
headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.50.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
|
|
8438
8467
|
}, (res) => {
|
|
8439
8468
|
let buf = "";
|
|
8440
8469
|
res.on("data", (c) => buf += c);
|
|
@@ -8476,7 +8505,7 @@ async function request(path, opts = {}, retried = false) {
|
|
|
8476
8505
|
method: opts.method ?? (opts.body !== void 0 || opts.raw !== void 0 ? "POST" : "GET"),
|
|
8477
8506
|
headers: {
|
|
8478
8507
|
authorization: `Bearer ${token}`,
|
|
8479
|
-
"user-agent": `ghosty-cli/${"0.
|
|
8508
|
+
"user-agent": `ghosty-cli/${"0.50.0"}`,
|
|
8480
8509
|
"accept-language": lang(),
|
|
8481
8510
|
...opts.body !== void 0 ? { "content-type": "application/json" } : {},
|
|
8482
8511
|
...opts.headers
|
|
@@ -8684,6 +8713,52 @@ ${dim(t(`Report: ${file} Failed turns: ghosty turns ls ${id} --errors --since
|
|
|
8684
8713
|
if (failed) process.exitCode = 1;
|
|
8685
8714
|
}
|
|
8686
8715
|
|
|
8716
|
+
// src/diff.ts
|
|
8717
|
+
function lineDiff(before, after, context = 2) {
|
|
8718
|
+
const a = before.split("\n");
|
|
8719
|
+
const b = after.split("\n");
|
|
8720
|
+
let start = 0;
|
|
8721
|
+
while (start < a.length && start < b.length && a[start] === b[start]) start++;
|
|
8722
|
+
let endA = a.length;
|
|
8723
|
+
let endB = b.length;
|
|
8724
|
+
while (endA > start && endB > start && a[endA - 1] === b[endB - 1]) {
|
|
8725
|
+
endA--;
|
|
8726
|
+
endB--;
|
|
8727
|
+
}
|
|
8728
|
+
const midA = a.slice(start, endA);
|
|
8729
|
+
const midB = b.slice(start, endB);
|
|
8730
|
+
const n = midA.length;
|
|
8731
|
+
const m = midB.length;
|
|
8732
|
+
const dp = Array.from({ length: n + 1 }, () => new Uint32Array(m + 1));
|
|
8733
|
+
for (let i2 = n - 1; i2 >= 0; i2--) for (let j2 = m - 1; j2 >= 0; j2--) dp[i2][j2] = midA[i2] === midB[j2] ? dp[i2 + 1][j2 + 1] + 1 : Math.max(dp[i2 + 1][j2], dp[i2][j2 + 1]);
|
|
8734
|
+
const ops = a.slice(0, start).map((text, i2) => ({ kind: " ", text, at: i2 + 1 }));
|
|
8735
|
+
let i = 0;
|
|
8736
|
+
let j = 0;
|
|
8737
|
+
while (i < n || j < m) {
|
|
8738
|
+
if (i < n && j < m && midA[i] === midB[j]) {
|
|
8739
|
+
ops.push({ kind: " ", text: midA[i], at: start + i + 1 });
|
|
8740
|
+
i++;
|
|
8741
|
+
j++;
|
|
8742
|
+
} else if (i < n && (j >= m || dp[i + 1][j] >= dp[i][j + 1])) {
|
|
8743
|
+
ops.push({ kind: "-", text: midA[i], at: start + i + 1 });
|
|
8744
|
+
i++;
|
|
8745
|
+
} else ops.push({ kind: "+", text: midB[j++], at: start + i + 1 });
|
|
8746
|
+
}
|
|
8747
|
+
for (let k = endA; k < a.length; k++) ops.push({ kind: " ", text: a[k], at: k + 1 });
|
|
8748
|
+
const keep = /* @__PURE__ */ new Set();
|
|
8749
|
+
ops.forEach((o, k) => {
|
|
8750
|
+
if (o.kind !== " ") for (let x = Math.max(0, k - context); x <= Math.min(ops.length - 1, k + context); x++) keep.add(x);
|
|
8751
|
+
});
|
|
8752
|
+
const lines = [];
|
|
8753
|
+
let last = -2;
|
|
8754
|
+
for (const k of [...keep].sort((x, y) => x - y)) {
|
|
8755
|
+
if (k !== last + 1) lines.push(`@@ ${ops[k].at}`);
|
|
8756
|
+
lines.push(`${ops[k].kind} ${ops[k].text}`);
|
|
8757
|
+
last = k;
|
|
8758
|
+
}
|
|
8759
|
+
return lines.join("\n");
|
|
8760
|
+
}
|
|
8761
|
+
|
|
8687
8762
|
// src/commands/agents.ts
|
|
8688
8763
|
var enc2 = encodeURIComponent;
|
|
8689
8764
|
var agentPath = (id, rest = "") => `/api/v2/agents/${enc2(id)}${rest}`;
|
|
@@ -8830,6 +8905,25 @@ ${dim(t("Dry run: nothing changed.", "Simulacro: no se cambi\xF3 nada."))}`);
|
|
|
8830
8905
|
}
|
|
8831
8906
|
return out(r2, () => r2.unlinked ? `${lavender("\u2713")} ${t("Unlinked: the prompt stays as it is and is edited by hand again.", "Desenlazado: el prompt se queda como est\xE1 y se vuelve a editar a mano.")}` : `${lavender("\u2713")} Prompt = ${v["prompt-base"]} + ${v["prompt-overlay"]} ${t(`(${r2.chars} chars). It regenerates when the base changes; ghosty agents doctor ${id} tells if the overlay stops applying.`, `(${r2.chars} caracteres). Se regenera cuando cambia la base; ghosty agents doctor ${id} avisa si la capa deja de aplicar.`)}`);
|
|
8832
8907
|
}
|
|
8908
|
+
if (v["prompt-patch"] || v["prompt-append"]) {
|
|
8909
|
+
const body2 = {};
|
|
8910
|
+
if (v["prompt-patch"]) {
|
|
8911
|
+
try {
|
|
8912
|
+
body2.promptPatch = JSON.parse(await readFile3(v["prompt-patch"], "utf8"));
|
|
8913
|
+
} catch (e) {
|
|
8914
|
+
throw usage(t(`Can't read ${v["prompt-patch"]}: ${e.message}`, `No pude leer ${v["prompt-patch"]}: ${e.message}`), t("Its format", "Su formato") + ': {"edits":[{"what":"horario","find":"9 a 18","replace":"9 a 19","all":true}]} (regex: true ' + t("for patterns", "para patrones") + ")");
|
|
8915
|
+
}
|
|
8916
|
+
}
|
|
8917
|
+
if (v["prompt-append"]) body2.promptAppend = await readFile3(v["prompt-append"], "utf8");
|
|
8918
|
+
if (v["dry-run"]) {
|
|
8919
|
+
const r3 = await api(agentPath(id), { method: "PATCH", body: { ...body2, dryRun: true } });
|
|
8920
|
+
const diff = lineDiff(r3.before, r3.after);
|
|
8921
|
+
return out({ dryRun: true, beforeChars: r3.beforeChars, chars: r3.chars, diff }, () => `${diff.split("\n").map((l) => l.startsWith("+ ") ? lavender(l) : l.startsWith("- ") ? red(l) : l.startsWith("@@") ? dim(l) : l).join("\n")}
|
|
8922
|
+
${dim(t(`${r3.beforeChars} \u2192 ${r3.chars} chars. Dry run: nothing changed.`, `${r3.beforeChars} \u2192 ${r3.chars} caracteres. Simulacro: no se cambi\xF3 nada.`))}`);
|
|
8923
|
+
}
|
|
8924
|
+
const r2 = await api(agentPath(id), { method: "PATCH", body: body2 });
|
|
8925
|
+
return out(r2, () => `${lavender("\u2713")} ${t(`Prompt patched (${r2.edits} edit(s)${r2.appended ? " + appended text" : ""}): ${r2.beforeChars} \u2192 ${r2.chars} chars. It applies from the next conversation.`, `Prompt parchado (${r2.edits} edici\xF3n(es)${r2.appended ? " + texto anexado" : ""}): ${r2.beforeChars} \u2192 ${r2.chars} caracteres. Entra desde la siguiente conversaci\xF3n.`)}`);
|
|
8926
|
+
}
|
|
8833
8927
|
if (v.engine && v["dry-run"]) {
|
|
8834
8928
|
const p = await api(agentPath(id), { method: "PATCH", body: { engine: v.engine, ...v.model ? { model: v.model } : {}, dryRun: true } });
|
|
8835
8929
|
return out(p, () => [
|
|
@@ -9111,10 +9205,10 @@ async function skillsCmd(sub, id, slug, v) {
|
|
|
9111
9205
|
case "ls":
|
|
9112
9206
|
case "list": {
|
|
9113
9207
|
const r = await api(agentPath(id, "/skills"));
|
|
9114
|
-
const
|
|
9208
|
+
const short3 = (d) => (d ?? "").replace(/\s+/g, " ").slice(0, 70);
|
|
9115
9209
|
const rows = [
|
|
9116
|
-
...r.skills.map((s) => ({ slug: s.slug, source: "own", on: "on", description:
|
|
9117
|
-
...(r.house ?? []).map((s) => ({ slug: s.slug, source: "ghosty", on: s.enabled ? "on" : "off", description:
|
|
9210
|
+
...r.skills.map((s) => ({ slug: s.slug, source: "own", on: "on", description: short3(s.description) })),
|
|
9211
|
+
...(r.house ?? []).map((s) => ({ slug: s.slug, source: "ghosty", on: s.enabled ? "on" : "off", description: short3(s.description) }))
|
|
9118
9212
|
];
|
|
9119
9213
|
const more = r.community.filter((c) => !c.installed).length;
|
|
9120
9214
|
out(r, () => `${table(rows, ["slug", "source", "on", "description"])}
|
|
@@ -9856,7 +9950,7 @@ async function call(path, v, init = {}) {
|
|
|
9856
9950
|
"x-ghosty-ts": ts,
|
|
9857
9951
|
"x-ghosty-sig": sig,
|
|
9858
9952
|
"content-type": "application/json",
|
|
9859
|
-
"user-agent": `ghosty-cli/${"0.
|
|
9953
|
+
"user-agent": `ghosty-cli/${"0.50.0"}`
|
|
9860
9954
|
},
|
|
9861
9955
|
body: method === "GET" ? void 0 : raw
|
|
9862
9956
|
});
|
|
@@ -10916,10 +11010,16 @@ function find2(nums, ref) {
|
|
|
10916
11010
|
return hit;
|
|
10917
11011
|
}
|
|
10918
11012
|
async function channelsCmd(kind, sub, id, ref, v) {
|
|
11013
|
+
if (kind === "messenger" || kind === "fb") {
|
|
11014
|
+
if (!id) throw usage(t("Missing agent id.", "Falta el id del agente."), t("Example", "Ejemplo") + ": ghosty channels messenger ls <agent-id> [--orphans]");
|
|
11015
|
+
if (sub && sub !== "ls" && sub !== "list") throw usage(t(`Unknown subcommand: ${sub}.`, `Subcomando desconocido: ${sub}.`), t("Try", "Prueba") + ": ghosty channels messenger ls <agent-id> [--orphans]");
|
|
11016
|
+
return linksCmd(id, "messenger", v.orphans);
|
|
11017
|
+
}
|
|
10919
11018
|
if (kind !== "whatsapp" && kind !== "wa") throw usage(t(`Unknown channel: ${kind ?? "(none)"}.`, `Canal desconocido: ${kind ?? "(ninguno)"}.`), t("Try", "Prueba") + ": ghosty channels whatsapp ls <agent-id>");
|
|
10920
11019
|
if (!id) throw usage(t("Missing agent id.", "Falta el id del agente."), t("Example", "Ejemplo") + ": ghosty channels whatsapp ls <agent-id>");
|
|
10921
11020
|
if (sub === "link" || sub === "unlink") return linkCmd(sub, id, v);
|
|
10922
11021
|
if (sub === "history") return historyCmd(id, v);
|
|
11022
|
+
if (sub === "integrations" && v.orphans) return linksCmd(id, "waba", true);
|
|
10923
11023
|
if (sub === "integrations") return integrationsCmd(id, v.email);
|
|
10924
11024
|
if (sub === "import" || sub === "status" || sub === "pause" || sub === "pair") return waCmd2(sub, id, v);
|
|
10925
11025
|
if (sub === "enable" && !v["dry-run"]) requireYesIfHeadless(v.yes, `ghosty channels whatsapp enable ${id} ${ref ?? "<number>"}`);
|
|
@@ -11150,6 +11250,19 @@ async function pageWithRetry(fetchPage, where, waitMs = (n) => 5e3 * n, tries =
|
|
|
11150
11250
|
}
|
|
11151
11251
|
}
|
|
11152
11252
|
}
|
|
11253
|
+
var lastCell = (l) => l ? `${l.agentName ?? l.agentId} ${dim(l.at.slice(0, 16).replace("T", " "))}` : dim("\u2014");
|
|
11254
|
+
async function linksCmd(id, kind, orphans) {
|
|
11255
|
+
const r = await api(`/api/v2/agents/${enc7(id)}/channels/links?kind=${kind}${orphans ? "&orphans=1" : ""}`);
|
|
11256
|
+
const now = (l) => l.answersNow ? l.answersNow.agentName ?? l.answersNow.agentId : l.recordsOnly ? t("nobody (records only)", "nadie (s\xF3lo registra)") : l.via === "easybits" ? "EasyBits" : l.via === "partner" ? "partner" : lavender(t("nobody", "nadie"));
|
|
11257
|
+
const rows = r.links.map((l) => ({ [kind === "waba" ? "number" : "page"]: l.label, "answers now": now(l), "last answered by": lastCell(l.lastAnswer), turns: l.lastAnswer?.turns ?? 0, id: l.id }));
|
|
11258
|
+
const first = r.links.find((l) => l.nobody && l.lastAnswer);
|
|
11259
|
+
const hint = first ? `
|
|
11260
|
+
${dim(`${t("Give it back", "Regresarlo")}: ${kind === "waba" ? `ghosty channels whatsapp enable ${first.lastAnswer.agentId} ${first.label.split(" ")[0]} --dry-run` : t(`the agent's Channels page (Messenger \u2192 \xABPass to this agent\xBB)`, `la p\xE1gina Canales del agente (Messenger \u2192 \xABPasar a este agente\xBB)`)}`)}` : "";
|
|
11261
|
+
return out(
|
|
11262
|
+
r,
|
|
11263
|
+
() => rows.length ? `${table(rows, [kind === "waba" ? "number" : "page", "answers now", "last answered by", "turns", "id"])}${hint}` : dim(orphans ? t("No orphans: every number/page with turns has an agent answering.", "Sin hu\xE9rfanos: todo n\xFAmero o p\xE1gina con turnos tiene un agente que contesta.") : t("No numbers/pages in this agent's workspace.", "No hay n\xFAmeros ni p\xE1ginas en el espacio de este agente."))
|
|
11264
|
+
);
|
|
11265
|
+
}
|
|
11153
11266
|
async function integrationsCmd(id, email) {
|
|
11154
11267
|
const r = await api(`/api/v2/agents/${enc7(id)}/whatsapp/integrations${email ? `?email=${enc7(email)}` : ""}`);
|
|
11155
11268
|
const who2 = { gs: "gs", easybits: "EasyBits", droplet: "droplet", formmy: "Formmy", other: t("other", "otro") };
|
|
@@ -11159,11 +11272,12 @@ async function integrationsCmd(id, email) {
|
|
|
11159
11272
|
"answered by": `${who2[i.answeredBy] ?? i.answeredBy}${i.gsAgent ? ` (${i.thisAgent ? lavender(t("this agent", "este agente")) : i.gsAgent.name})` : ""}${i.isActive ? "" : dim(t(" \xB7 inactive", " \xB7 inactiva"))}`,
|
|
11160
11273
|
"formmy forwards": i.forwards ? t("yes", "s\xED") : dim("no"),
|
|
11161
11274
|
"formmy agent": i.agent ?? "",
|
|
11275
|
+
"last answered by": lastCell(i.lastAnswer),
|
|
11162
11276
|
integration: i.integrationId
|
|
11163
11277
|
}));
|
|
11164
11278
|
return out(
|
|
11165
11279
|
r,
|
|
11166
|
-
() => rows.length ? `${table(rows, ["number", "name", "answered by", "formmy forwards", "formmy agent", "integration"])}
|
|
11280
|
+
() => rows.length ? `${table(rows, ["number", "name", "answered by", "last answered by", "formmy forwards", "formmy agent", "integration"])}
|
|
11167
11281
|
${dim(`${t("Next", "Siguiente")}: ghosty channels whatsapp link ${id} --integration <integration> --answer on|off --dry-run`)}` : dim(t(`${r.ownerEmail} has no WhatsApp Business numbers in Formmy (staff: --email <other Formmy account>).`, `${r.ownerEmail} no tiene n\xFAmeros de WhatsApp Business en Formmy (staff: --email <otra cuenta de Formmy>).`))
|
|
11168
11282
|
);
|
|
11169
11283
|
}
|
|
@@ -11231,6 +11345,31 @@ ${t(`Cards appear on the team's board (no "new lead" alerts).`, "Las tarjetas ap
|
|
|
11231
11345
|
out({ ...sum, conversations: done }, () => `${lavender("\u2713")} ${t(`${sum.newCards} new card(s), ${sum.updatedCards} updated, ${sum.messages} message(s), ${sum.orders} order(s)`, `${sum.newCards} tarjeta(s) nueva(s), ${sum.updatedCards} actualizada(s), ${sum.messages} mensaje(s), ${sum.orders} pedido(s)`)}${files(sum)}${sum.suppressed ? dim(t(` \xB7 ${sum.suppressed} bot draft(s) the customer never saw, skipped`, ` \xB7 se omitieron ${sum.suppressed} borrador(es) del bot que el cliente nunca vio`)) : ""}${pausedLine(sum, id)}`);
|
|
11232
11346
|
}
|
|
11233
11347
|
|
|
11348
|
+
// src/cards.ts
|
|
11349
|
+
var PAUSE_MODES = ["30min", "2h", "24h", "until_tomorrow", "permanent"];
|
|
11350
|
+
function pauseMode(raw) {
|
|
11351
|
+
const m = (raw ?? "24h").trim();
|
|
11352
|
+
if (!PAUSE_MODES.includes(m)) throw new Error(t(`--mode takes ${PAUSE_MODES.join(" | ")}.`, `--mode acepta ${PAUSE_MODES.join(" | ")}.`));
|
|
11353
|
+
return m;
|
|
11354
|
+
}
|
|
11355
|
+
function flagLabel(flag) {
|
|
11356
|
+
const labels = {
|
|
11357
|
+
paused: ["paused", "en pausa"],
|
|
11358
|
+
escalated: ["escalated (operator only)", "escalada (s\xF3lo operador)"],
|
|
11359
|
+
window_closed: ["outside WhatsApp's 24 h window", "fuera de la ventana de 24 h"],
|
|
11360
|
+
reaction: ["only a reaction", "s\xF3lo una reacci\xF3n"],
|
|
11361
|
+
not_whatsapp: ["not WhatsApp Business", "no es WhatsApp Business"],
|
|
11362
|
+
no_agent: ["number answered by nobody", "n\xFAmero sin agente"]
|
|
11363
|
+
};
|
|
11364
|
+
const l = labels[flag];
|
|
11365
|
+
return l ? t(l[0], l[1]) : flag;
|
|
11366
|
+
}
|
|
11367
|
+
function pauseLabel(p) {
|
|
11368
|
+
if (p.state === "escalated") return t("escalated (operator only)", "escalada (s\xF3lo operador)");
|
|
11369
|
+
if (p.state === "paused") return t(`paused until ${p.until?.slice(0, 16).replace("T", " ")}`, `en pausa hasta ${p.until?.slice(0, 16).replace("T", " ")}`);
|
|
11370
|
+
return t("active (the agent answers)", "activa (el agente contesta)");
|
|
11371
|
+
}
|
|
11372
|
+
|
|
11234
11373
|
// src/commands/board.ts
|
|
11235
11374
|
var who = (role) => ({ user: t("customer", "cliente"), assistant: t("agent", "agente"), operator: t("operator", "operador") })[role] ?? role;
|
|
11236
11375
|
async function boardLs(id, v) {
|
|
@@ -11254,7 +11393,79 @@ async function boardShow(id, ref, v) {
|
|
|
11254
11393
|
};
|
|
11255
11394
|
out(r, () => [head, ...r.messages.map(line2)].join("\n"));
|
|
11256
11395
|
}
|
|
11396
|
+
var cardName = (c) => `${c.folio ? `#${c.folio} ` : ""}${c.name ?? `+${c.phone}`}`;
|
|
11397
|
+
var cardPath = (id, ref, tail = "") => `/api/v2/agents/${encodeURIComponent(id)}/board/conversations/${encodeURIComponent(ref)}${tail}`;
|
|
11398
|
+
async function cardPause(sub, id, ref, v) {
|
|
11399
|
+
if (!ref) throw usage(t("Missing card.", "Falta la tarjeta."), t("Example", "Ejemplo") + `: ghosty cards ${sub} ${id} "#12"${sub === "pause" ? " --mode 2h" : ""}`);
|
|
11400
|
+
let mode = "resume";
|
|
11401
|
+
if (sub === "pause") {
|
|
11402
|
+
try {
|
|
11403
|
+
mode = pauseMode(v.mode);
|
|
11404
|
+
} catch (e) {
|
|
11405
|
+
throw usage(e.message);
|
|
11406
|
+
}
|
|
11407
|
+
}
|
|
11408
|
+
const r = await api(cardPath(id, ref, "/pause"), { body: { mode } });
|
|
11409
|
+
out(r, () => `${lavender("\u2713")} ${bold(cardName(r.card))}: ${pauseLabel(r.before)} \u2192 ${pauseLabel(r.after)}${sub === "resume" ? `
|
|
11410
|
+
${dim(t(`Resuming doesn't answer by itself; to answer what's pending: ghosty cards reply ${id} ${JSON.stringify(ref)}`, `Reanudar no contesta solo; para contestar lo pendiente: ghosty cards reply ${id} ${JSON.stringify(ref)}`))}` : ""}`);
|
|
11411
|
+
}
|
|
11412
|
+
async function cardReply(id, ref, v) {
|
|
11413
|
+
if (!ref) throw usage(t("Missing card.", "Falta la tarjeta."), t("Example", "Ejemplo") + `: ghosty cards reply ${id} "#12" [--directive "ofr\xE9cele el env\xEDo"]`);
|
|
11414
|
+
const retry = `ghosty cards reply ${id} ${JSON.stringify(ref)}${v.directive ? ` --directive ${JSON.stringify(v.directive)}` : ""}`;
|
|
11415
|
+
requireYesIfHeadless(v.yes, retry);
|
|
11416
|
+
await confirm(t(`The agent will answer card ${ref} \u2014 a real customer gets the message. Go?`, `El agente contestar\xE1 la tarjeta ${ref}: un cliente real recibe el mensaje. \xBFSeguimos?`), { yes: v.yes, retry });
|
|
11417
|
+
const r = await api(cardPath(id, ref, "/reply"), { body: v.directive ? { directive: v.directive } : {} });
|
|
11418
|
+
out(r, () => `${lavender("\u2713")} ${t("Answered", "Contest\xF3")} ${bold(cardName(r.card))}${r.resumed ? dim(t(" (it was paused: resumed)", " (estaba en pausa: se reanud\xF3)")) : ""}`);
|
|
11419
|
+
}
|
|
11420
|
+
async function cardPending(id, v) {
|
|
11421
|
+
const retry = `ghosty cards pending ${id}${v.since ? ` --since ${v.since}` : ""} --reply`;
|
|
11422
|
+
if (v.reply && !v["dry-run"]) requireYesIfHeadless(v.yes, retry);
|
|
11423
|
+
const qs = new URLSearchParams({ ...v.since ? { since: v.since } : {}, ...v.limit ? { limit: v.limit } : {} });
|
|
11424
|
+
const r = await api(`/api/v2/agents/${encodeURIComponent(id)}/board/pending?${qs}`);
|
|
11425
|
+
const go = r.cards.filter((c) => c.flags.length === 0);
|
|
11426
|
+
const rows = r.cards.map((c) => ({
|
|
11427
|
+
card: c.folio ? `#${c.folio}` : c.id.slice(0, 8),
|
|
11428
|
+
name: c.name ?? "",
|
|
11429
|
+
phone: c.phone,
|
|
11430
|
+
msgs: c.pendingMessages,
|
|
11431
|
+
since: c.lastUserAt.slice(5, 16).replace("T", " "),
|
|
11432
|
+
last: c.lastText.replace(/\s+/g, " ").slice(0, 40),
|
|
11433
|
+
status: c.flags.length ? dim(c.flags.map(flagLabel).join(", ")) : lavender(t("answer", "contestar"))
|
|
11434
|
+
}));
|
|
11435
|
+
const summary = dim(t(
|
|
11436
|
+
`\xAB${r.board}\xBB since ${r.since.slice(0, 16).replace("T", " ")}: ${r.cards.length} waiting, ${go.length} can be answered${r.otherAgents ? ` (${r.otherAgents} more belong to another agent's numbers)` : ""}.`,
|
|
11437
|
+
`\xAB${r.board}\xBB desde ${r.since.slice(0, 16).replace("T", " ")}: ${r.cards.length} esperando, ${go.length} se pueden contestar${r.otherAgents ? ` (otras ${r.otherAgents} son de n\xFAmeros de otro agente)` : ""}.`
|
|
11438
|
+
));
|
|
11439
|
+
if (!v.reply || v["dry-run"] || !go.length) {
|
|
11440
|
+
return out({ ...r, ...v.reply ? { dryRun: true, wouldReply: go.map((c) => c.id) } : {} }, () => `${rows.length ? `${table(rows, ["card", "name", "phone", "msgs", "since", "last", "status"])}
|
|
11441
|
+
` : ""}${summary}${v.reply && go.length ? `
|
|
11442
|
+
${dim(t("Dry run: nothing was sent.", "Simulacro: no se mand\xF3 nada."))}` : go.length ? `
|
|
11443
|
+
${dim(t(`Answer them: ${retry}`, `Contestarlas: ${retry}`))}` : ""}`);
|
|
11444
|
+
}
|
|
11445
|
+
if (!globals.json) info(table(rows.filter((_, k) => r.cards[k].flags.length === 0), ["card", "name", "phone", "msgs", "since", "last"]));
|
|
11446
|
+
await confirm(t(`The agent will answer ${go.length} real customer(s), one by one. Go?`, `El agente les contestar\xE1 a ${go.length} cliente(s) reales, uno por uno. \xBFSeguimos?`), { yes: v.yes, retry });
|
|
11447
|
+
const done = [];
|
|
11448
|
+
for (const c of go) {
|
|
11449
|
+
try {
|
|
11450
|
+
await api(cardPath(id, c.id, "/reply"), { body: v.directive ? { directive: v.directive } : {} });
|
|
11451
|
+
done.push({ id: c.id, ok: true });
|
|
11452
|
+
if (!globals.json) info(`${lavender("\u2713")} ${cardName(c)}`);
|
|
11453
|
+
} catch (e) {
|
|
11454
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
11455
|
+
done.push({ id: c.id, ok: false, error: msg });
|
|
11456
|
+
if (!globals.json) info(`\u2717 ${cardName(c)}: ${serverText(msg)}`);
|
|
11457
|
+
}
|
|
11458
|
+
}
|
|
11459
|
+
const ok = done.filter((d) => d.ok).length;
|
|
11460
|
+
out({ board: r.board, replied: done }, () => `${lavender("\u2713")} ${t(`Answered ${ok} of ${go.length}.`, `Se contestaron ${ok} de ${go.length}.`)}`);
|
|
11461
|
+
}
|
|
11257
11462
|
async function boardCmd(sub, id, v, ref) {
|
|
11463
|
+
if (sub === "pause" || sub === "resume" || sub === "reply" || sub === "pending") {
|
|
11464
|
+
if (!id) throw usage(t("Missing agent id.", "Falta el id del agente."), t("Example", "Ejemplo") + `: ghosty cards ${sub} <agent-id>${sub === "pending" ? "" : ' "#12"'}`);
|
|
11465
|
+
if (sub === "reply") return cardReply(id, ref, v);
|
|
11466
|
+
if (sub === "pending") return cardPending(id, v);
|
|
11467
|
+
return cardPause(sub, id, ref, v);
|
|
11468
|
+
}
|
|
11258
11469
|
if (sub === "ls" || sub === "list") {
|
|
11259
11470
|
if (!id) throw usage(t("Missing agent id.", "Falta el id del agente."), t("Example", "Ejemplo") + ": ghosty board ls <agent-id> [--q text] [--limit 20]");
|
|
11260
11471
|
return boardLs(id, v);
|
|
@@ -11263,7 +11474,7 @@ async function boardCmd(sub, id, v, ref) {
|
|
|
11263
11474
|
if (!id) throw usage(t("Missing agent id.", "Falta el id del agente."), t("Example", "Ejemplo") + ': ghosty board show <agent-id> "#12"');
|
|
11264
11475
|
return boardShow(id, ref, v);
|
|
11265
11476
|
}
|
|
11266
|
-
if (sub !== "archive") throw usage(t(`Unknown board subcommand: ${sub ?? "(none)"}.`, `Subcomando de board desconocido: ${sub ?? "(ninguno)"}.`), t("Try", "Prueba") + ": ghosty
|
|
11477
|
+
if (sub !== "archive") throw usage(t(`Unknown board subcommand: ${sub ?? "(none)"}.`, `Subcomando de board desconocido: ${sub ?? "(ninguno)"}.`), t("Try", "Prueba") + ": ghosty cards ls <agent-id> \xB7 show <agent-id> <folio|phone> \xB7 archive <agent-id> --integration prueba --dry-run \xB7 pause|resume|reply <agent-id> <card> \xB7 pending <agent-id>");
|
|
11267
11478
|
if (!id) throw usage(t("Missing agent id.", "Falta el id del agente."), t("Example", "Ejemplo") + ": ghosty board archive <agent-id> --integration prueba --dry-run");
|
|
11268
11479
|
const filter = { ...v.integration ? { integration: v.integration } : {}, ...v.column ? { column: v.column } : {}, ...v.before ? { before: v.before } : {} };
|
|
11269
11480
|
if (!Object.keys(filter).length) throw usage(t("Add at least one filter: --integration, --column or --before.", "Agrega al menos un filtro: --integration, --column o --before."), t("A filter-less archive would empty the board.", "Archivar sin filtro vaciar\xEDa el tablero."));
|
|
@@ -11344,6 +11555,8 @@ var HELPS_ES = {
|
|
|
11344
11555
|
summary: "Crea, revisa, cambia, diagnostica y borra agentes.",
|
|
11345
11556
|
replace: [
|
|
11346
11557
|
["# the agent as a file; see ghosty apply --help", "# el agente como archivo; ver ghosty apply --help"],
|
|
11558
|
+
["# who is @build in Teams (that workspace, or all of yours)", "# qui\xE9n es @build en Teams (en ese espacio, o en todos los tuyos)"],
|
|
11559
|
+
["# change one piece of the prompt, on the server's current one", "# cambia un pedazo del prompt, sobre el actual del servidor"],
|
|
11347
11560
|
["# your account: merge copies with the same content", "# tu cuenta: une las copias con el mismo contenido"],
|
|
11348
11561
|
["# staff: their agents, latest turns, plan and boxes", "# staff: sus agentes, \xFAltimos turnos, plan y cajas"],
|
|
11349
11562
|
["# what it really has: effective toolsets, connectors connected + granted", "# lo que de verdad tiene: toolsets efectivos, conectores conectados + concedidos"],
|
|
@@ -11365,6 +11578,8 @@ var HELPS_ES = {
|
|
|
11365
11578
|
["--toolsets LIST", "set: grupos de tools de gs que recibe (crm, sales\u2026): una lista reemplaza; +x / -x agrega o quita"],
|
|
11366
11579
|
["--prompt-base ID", "set: el prompt de este agente = el prompt de ese agente + --prompt-overlay; se regenera cuando cambia la base. none = desliga (conserva el prompt de hoy)"],
|
|
11367
11580
|
["--prompt-overlay FILE", 'set: JSON {"edits":[{"what","find","replace","regex"?,"all"?}]}, aplicados en orden; cada uno debe encontrar su lugar o no cambia nada'],
|
|
11581
|
+
["--prompt-patch FILE", 'set: el mismo formato {"edits":[\u2026]}, aplicado UNA vez sobre el prompt actual del agente en el servidor (no sobre una copia local: nunca pisa el cambio de alguien m\xE1s). Si una edici\xF3n no encuentra su lugar, no cambia nada. --dry-run imprime el diff'],
|
|
11582
|
+
["--prompt-append FILE", "set: agrega ese texto al final del prompt (con --prompt-patch tambi\xE9n: primero las ediciones, luego el texto)"],
|
|
11368
11583
|
["--voice E:ID", "set: voz de las notas de voz: elevenlabs:<voiceId> o kokoro:em_santa|em_alex|ef_dora"],
|
|
11369
11584
|
["--voice-replies M", "set: auto = contesta con nota de voz cuando el cliente manda o pide audio; off = siempre texto"],
|
|
11370
11585
|
["--auto-stage on|off", "set: mueve solo las tarjetas del tablero tras cada turno del cliente (una llamada extra al modelo por turno)"],
|
|
@@ -11379,7 +11594,7 @@ var HELPS_ES = {
|
|
|
11379
11594
|
["--grant read|write|no", "docs add: acceso de este agente (default read; no = s\xF3lo subirlo a la cuenta)"],
|
|
11380
11595
|
["--note TEXT", "docs replace/restore: por qu\xE9 cambi\xF3 (sale en docs history)"],
|
|
11381
11596
|
["--role R", "share: editor (lo configura y lo usa) o admin (adem\xE1s lo comparte); repetirlo sobre un correo ya compartido cambia el rol"],
|
|
11382
|
-
["--handle NAME", "teams on: su @handle en ese equipo (default: sacado de su nombre, con sufijo si est\xE1 ocupado; @ghosty est\xE1 reservado). Sobre un equipo donde ya est\xE1, le cambia el handle"],
|
|
11597
|
+
["--handle NAME", "teams on: su @handle en ese equipo (default: sacado de su nombre, con sufijo si est\xE1 ocupado; @ghosty est\xE1 reservado). Sobre un equipo donde ya est\xE1, le cambia el handle. ls: qui\xE9n contesta a ese @handle"],
|
|
11383
11598
|
["--factory ROLES", "teams on: le pasa esos roles de la Software Factory del equipo (plan, build, check, eval; separados por comas) \u2014 lo mismo que \xABEquipo\xBB en /factory. Cada rol conserva su nombre y avatar; su siguiente turno ya corre en \xE9l. La f\xE1brica tiene que estar instalada ah\xED"],
|
|
11384
11599
|
["--yes", "rm: se salta la confirmaci\xF3n (obligatorio sin terminal)"],
|
|
11385
11600
|
["--slug NAME", "demo: nombre de la liga; se le agrega un c\xF3digo de 6 caracteres (/p/NAME-CODE)"],
|
|
@@ -11575,6 +11790,7 @@ var HELPS_ES = {
|
|
|
11575
11790
|
["--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"],
|
|
11576
11791
|
["--integration ID", "link/unlink/history: el id de la Integration del n\xFAmero de WhatsApp Business en Formmy"],
|
|
11577
11792
|
["--email E", "integrations (staff): lista los n\xFAmeros de otra cuenta de Formmy"],
|
|
11793
|
+
["--orphans", "integrations / messenger ls: s\xF3lo los n\xFAmeros o p\xE1ginas que hoy no contesta nadie y S\xCD tuvieron turnos, con qui\xE9n los contest\xF3 al \xFAltimo (a qui\xE9n regresarlos)"],
|
|
11578
11794
|
["--days N", "history: conversaciones activas en los \xFAltimos N d\xEDas (1-30, default 7)"],
|
|
11579
11795
|
["--media", "history: s\xF3lo re-copia los archivos que faltan (fotos, PDF, audios) de las conversaciones ya importadas"],
|
|
11580
11796
|
["--answer on|off", "link (obligatorio, sin default): on = este agente contesta a clientes reales; off = s\xF3lo registra"],
|
|
@@ -11592,7 +11808,7 @@ var HELPS_ES = {
|
|
|
11592
11808
|
"Apagarlo sigue registrando los mensajes; nadie los contesta.",
|
|
11593
11809
|
"Los grupos son del WhatsApp PERSONAL emparejado al agente (no de WhatsApp Business). <group> es su nombre o su jid.",
|
|
11594
11810
|
"link mueve un n\xFAmero que ya vive en Formmy (EasyBits, un droplet) sin rehacer el alta de Meta; unlink lo regresa. S\xF3lo el due\xF1o, un admin del agente o el staff de Ghosty.",
|
|
11595
|
-
"integrations lista los n\xFAmeros de WhatsApp Business del due\xF1o en Formmy (n\xFAmero, nombre, qui\xE9n contesta hoy: gs, EasyBits, un droplet o Formmy) con el id de la Integration que piden link e history. Tambi\xE9n salen los n\xFAmeros que el espacio ya enlaz\xF3 en gs, aunque en Formmy sean de otra cuenta.",
|
|
11811
|
+
"integrations lista los n\xFAmeros de WhatsApp Business del due\xF1o en Formmy (n\xFAmero, nombre, qui\xE9n contesta hoy: gs, EasyBits, un droplet o Formmy) con el id de la Integration que piden link e history. Tambi\xE9n salen los n\xFAmeros que el espacio ya enlaz\xF3 en gs, aunque en Formmy sean de otra cuenta. \xABlast answered by\xBB es el agente del \xFAltimo turno en ese n\xFAmero (de los turnos de gs): as\xED un n\xFAmero que qued\xF3 en nadie se puede regresar.",
|
|
11596
11812
|
"history trae al tablero las tarjetas, mensajes y pedidos recientes del n\xFAmero desde Formmy (enl\xE1zalo antes; --dry-run funciona sin enlazar, para decidir --days). Repetirlo nunca duplica: c\xF3rrelo antes del corte y otra vez justo en el corte. Los archivos (fotos, PDF, audios) tambi\xE9n se copian a gs: Formmy los borra a los 7 d\xEDas, as\xED que trae el n\xFAmero dentro de esa semana; una copia que fall\xF3 se reintenta con --media.",
|
|
11597
11813
|
"import: apaga primero el socket de EasyBits SIN logout (dos sockets se tumban entre s\xED; un logout invalida las llaves). Borra el archivo despu\xE9s."
|
|
11598
11814
|
]
|
|
@@ -11674,12 +11890,19 @@ var HELPS_ES = {
|
|
|
11674
11890
|
["--integration ID", "tarjetas de este canal/integraci\xF3n (p. ej. prueba)"],
|
|
11675
11891
|
["--column NAME", "tarjetas en esta columna"],
|
|
11676
11892
|
["--before T", "tarjetas sin actividad desde T (30d, 12h o una fecha ISO)"],
|
|
11677
|
-
["-n, --dry-run", "cuenta y muestra una muestra, no cambia nada"],
|
|
11678
|
-
["--yes", "se salta la confirmaci\xF3n (obligatorio sin terminal)"]
|
|
11893
|
+
["-n, --dry-run", "cuenta y muestra una muestra, no cambia nada \xB7 pending --reply: lista lo que contestar\xEDa, no manda nada"],
|
|
11894
|
+
["--yes", "se salta la confirmaci\xF3n (obligatorio sin terminal)"],
|
|
11895
|
+
["--mode M", "pause: 30min | 2h | 24h (default) | until_tomorrow (9:00) | permanent (escalada: s\xF3lo operador)"],
|
|
11896
|
+
["--directive TEXT", "reply / pending --reply: una instrucci\xF3n para el agente (redacta su respuesta sigui\xE9ndola, no la repite)"],
|
|
11897
|
+
["--since T", "pending: clientes que escribieron desde T (30m, 2d o una fecha ISO; default 3d)"],
|
|
11898
|
+
["--reply", "pending: contesta, una por una, las que no traen bandera (pregunta antes)"]
|
|
11679
11899
|
],
|
|
11680
11900
|
notes: [
|
|
11681
11901
|
"show imprime cada mensaje con qui\xE9n lo escribi\xF3 (cliente, agente, operador), su adjunto y si gs guarda una copia.",
|
|
11682
|
-
"Archivar es reversible: cuando el cliente vuelve a escribir, la tarjeta regresa. Se necesita al menos un filtro."
|
|
11902
|
+
"Archivar es reversible: cuando el cliente vuelve a escribir, la tarjeta regresa. Se necesita al menos un filtro.",
|
|
11903
|
+
"resume quita la pausa Y el \xABs\xF3lo operador\xBB (escalar = pausa permanente), en gs y en Formmy; NO contesta solo \u2014 para eso est\xE1 reply. reply s\xF3lo sirve en conversaciones de WhatsApp Business de un n\xFAmero que contesta este agente.",
|
|
11904
|
+
"pending marca y nunca contesta: en pausa, escalada, fuera de la ventana de 24 h de WhatsApp (Meta s\xF3lo acepta plantillas), s\xF3lo una reacci\xF3n, no es WhatsApp Business, n\xFAmero sin agente. Las de n\xFAmeros de otro agente no salen (s\xF3lo se cuentan).",
|
|
11905
|
+
"pause, resume y reply quedan en bit\xE1cora (ghosty audit ls --action card)."
|
|
11683
11906
|
]
|
|
11684
11907
|
},
|
|
11685
11908
|
usage: {
|
|
@@ -11770,6 +11993,39 @@ var HELPS_ES = {
|
|
|
11770
11993
|
"En el panel web el staff elige \xABAtendiendo: <cliente>\xBB (Documentos y bases, Agentes); es una cookie que se revalida en cada request, nunca otra sesi\xF3n.",
|
|
11771
11994
|
"Revocar corta en el siguiente request. Asignar dos veces no hace nada (una sola activa por par)."
|
|
11772
11995
|
]
|
|
11996
|
+
},
|
|
11997
|
+
audit: {
|
|
11998
|
+
summary: "La bit\xE1cora (AuditEvent): qui\xE9n entr\xF3 \xABcomo\xBB qui\xE9n, qu\xE9 cambi\xF3 en la ficha y en los canales de un agente, pausas y respuestas desde el CLI, traspasos, staff que entra o sale. El staff de Ghosty ve todo; cualquier otra cuenta, s\xF3lo lo de sus espacios y agentes.",
|
|
11999
|
+
flags: [
|
|
12000
|
+
["--workspace SLUG", "los eventos de ese espacio y de sus agentes (default: el de ghosty use; none = todos los tuyos)"],
|
|
12001
|
+
["--agent ID", "s\xF3lo los de ese agente (nombre o id)"],
|
|
12002
|
+
["--actor EMAIL", "s\xF3lo lo que hizo esa persona"],
|
|
12003
|
+
["--action A", "agent, channel, card, factory, workspace\u2026 (un prefijo) o una acci\xF3n exacta (agent.save)"],
|
|
12004
|
+
["--since T", "desde T (30m, 24h, 7d o una fecha ISO; default 7d)"],
|
|
12005
|
+
["--limit N", "cu\xE1ntos (default 50, m\xE1x. 1000)"],
|
|
12006
|
+
["--full", "el detalle completo de cada evento (JSON)"]
|
|
12007
|
+
],
|
|
12008
|
+
notes: [
|
|
12009
|
+
"Hoy se anotan: agent.save (el Guardar de la ficha), channel.* (p\xE1gina Canales), agent.prompt_patch, card.pause/resume/reply, factory.run_rename, workspace.*, staff y \xABentrar como\xBB. Cambios m\xE1s viejos pueden no estar.",
|
|
12010
|
+
"El token gat_ de un agente no lee la bit\xE1cora."
|
|
12011
|
+
]
|
|
12012
|
+
},
|
|
12013
|
+
factory: {
|
|
12014
|
+
summary: "Los pedidos de la Software Factory de un espacio (viven en su Teams): listarlos, ver uno (plan vigente, PR, preview, \xFAltimos eventos, qu\xE9 rol trabaja) y corregir el t\xEDtulo de un pedido.",
|
|
12015
|
+
replace: [['"Title"', '"T\xEDtulo"']],
|
|
12016
|
+
flags: [
|
|
12017
|
+
["--workspace SLUG", "el espacio (default: el de ghosty use)"],
|
|
12018
|
+
["--room R", "ls: s\xF3lo los pedidos de ese room (#slug o nombre)"],
|
|
12019
|
+
["--status S", "ls: s\xF3lo esa etapa (planning, building, checking, pr_review, merged\u2026)"],
|
|
12020
|
+
["--limit N", "ls: cu\xE1ntos (default 30)"],
|
|
12021
|
+
["--full", "show: imprime el plan vigente"],
|
|
12022
|
+
["-n, --dry-run", "rename: ense\xF1a el cambio, no cambia nada"],
|
|
12023
|
+
["--yes", "rename: se salta la confirmaci\xF3n (obligatorio sin terminal)"]
|
|
12024
|
+
],
|
|
12025
|
+
notes: [
|
|
12026
|
+
"Leer pide ser miembro del espacio; renombrar, ser due\xF1o, admin o staff de Ghosty, y queda en bit\xE1cora (factory.run_rename). La tarjeta del room toma el t\xEDtulo nuevo en su siguiente refresco.",
|
|
12027
|
+
"Qui\xE9n es @plan o @build: ghosty agents ls --handle plan --workspace SLUG."
|
|
12028
|
+
]
|
|
11773
12029
|
}
|
|
11774
12030
|
};
|
|
11775
12031
|
var HELP_ALIASES = { creds: "credentials", conectores: "connectors", board: "cards", conversations: "chat", workspaces: "spaces" };
|
|
@@ -11782,12 +12038,14 @@ var HELPS = {
|
|
|
11782
12038
|
summary: "Create, inspect, change, check and delete agents.",
|
|
11783
12039
|
usage: [
|
|
11784
12040
|
"ghosty agents ls [--engine E] [--ids] [--sponsor EMAIL | --owner EMAIL]",
|
|
12041
|
+
"ghosty agents ls --handle build [--workspace WS] # who is @build in Teams (that workspace, or all of yours)",
|
|
11785
12042
|
"ghosty agents create --name NAME [--engine E] [--model M] [--prompt TEXT | --prompt-file FILE] [--env K=V,\u2026] [--workspace SLUG] [--channels teams=off,\u2026] [--like AGENT [--copy-dbs] [--board same|main|new|NAME] [--no-notify] [--dry-run]]",
|
|
11786
12043
|
"ghosty agents get <agent-id> [--fields name,model,prompt] [--prompt-out FILE]",
|
|
11787
12044
|
"ghosty agents export <agent-id> [--out DIR] # the agent as a file; see ghosty apply --help",
|
|
11788
12045
|
"ghosty agents set <agent-id> [--name] [--engine] [--model] [--prompt | --prompt-file] [--env K=V,\u2026] [--unset-env K1,K2] [--connect ID,\u2026] [--disconnect ID,\u2026] [--voice E:ID] [--voice-replies auto|off] [--auto-stage on|off] [--toolsets crm,sales | +crm,-sales] [--tool [channel:]tool=on|off,\u2026] [--bag NAME|none] [--avatar URL|FILE]",
|
|
11789
12046
|
"ghosty agents set <agent-id> --engine E [--model M] --dry-run # what changing the engine does (sessions, machines, memory), without changing it",
|
|
11790
12047
|
"ghosty agents set <agent-id> --prompt-base <base-agent-id> --prompt-overlay overlay.json [--dry-run [--out FILE]] | --prompt-base none",
|
|
12048
|
+
"ghosty agents set <agent-id> --prompt-patch overlay.json [--prompt-append FILE] [--dry-run] # change one piece of the prompt, on the server's current one",
|
|
11791
12049
|
"ghosty agents doctor <agent-id> [--verbose]",
|
|
11792
12050
|
"ghosty agents doctor --user <email> [agent-id] [--turns 8] # staff: their agents, latest turns, plan and boxes",
|
|
11793
12051
|
"ghosty agents docs ls|grant|revoke <agent-id> [doc\u2026] [--write] [--no-deliver] [--like AGENT]",
|
|
@@ -11822,6 +12080,8 @@ var HELPS = {
|
|
|
11822
12080
|
["--toolsets LIST", "set: gs tool groups it gets (crm, sales\u2026): a list replaces; +x / -x add or remove"],
|
|
11823
12081
|
["--prompt-base ID", "set: this agent's prompt = that agent's prompt + --prompt-overlay; regenerates when the base changes. none = unlink (keeps today's prompt)"],
|
|
11824
12082
|
["--prompt-overlay FILE", 'set: JSON {"edits":[{"what","find","replace","regex"?,"all"?}]}, applied in order; each must find its place or nothing changes'],
|
|
12083
|
+
["--prompt-patch FILE", `set: the same {"edits":[\u2026]} format, applied ONCE on the agent's current prompt on the server (not a local copy: it never overwrites someone else's change). If one edit doesn't find its place, nothing changes. --dry-run prints the diff`],
|
|
12084
|
+
["--prompt-append FILE", "set: add that text at the end of the prompt (with --prompt-patch too: edits first, then the text)"],
|
|
11825
12085
|
["--voice E:ID", "set: voice for voice notes: elevenlabs:<voiceId> or kokoro:em_santa|em_alex|ef_dora"],
|
|
11826
12086
|
["--voice-replies M", "set: auto = answer with a voice note when the customer sends or asks for audio; off = always text"],
|
|
11827
12087
|
["--auto-stage on|off", "set: move board cards by itself after each customer turn (one extra model call per turn)"],
|
|
@@ -11836,7 +12096,7 @@ var HELPS = {
|
|
|
11836
12096
|
["--grant read|write|no", "docs add: access for this agent (default read; no = only upload to the account)"],
|
|
11837
12097
|
["--note TEXT", "docs replace/restore: why it changed (shown in docs history)"],
|
|
11838
12098
|
["--role R", "share: editor (configures and uses it) or admin (also shares it); again on a shared email changes the role"],
|
|
11839
|
-
["--handle NAME", "teams on: its @handle in that workspace (default: from its name, with a suffix if taken; @ghosty is reserved). On an active workspace it changes the handle"],
|
|
12099
|
+
["--handle NAME", "teams on: its @handle in that workspace (default: from its name, with a suffix if taken; @ghosty is reserved). On an active workspace it changes the handle. ls: who answers to that @handle"],
|
|
11840
12100
|
["--factory ROLES", "teams on: give it those Software Factory roles in that workspace (plan, build, check, eval; comma-separated) \u2014 the same as \xABTeam\xBB in /factory. Keeps each role's name and avatar; the next turn of each role runs on it. The factory must be installed there"],
|
|
11841
12101
|
["--yes", "rm: skip the confirmation (required without a terminal)"],
|
|
11842
12102
|
["--slug NAME", "demo: link name; a 6-char code is appended (/p/NAME-CODE)"],
|
|
@@ -12136,7 +12396,8 @@ HELPS.channels = {
|
|
|
12136
12396
|
"ghosty channels whatsapp ls <agent-id>",
|
|
12137
12397
|
"ghosty channels whatsapp enable <agent-id> <number> [--dry-run] [--yes]",
|
|
12138
12398
|
"ghosty channels whatsapp disable <agent-id> <number> [--dry-run]",
|
|
12139
|
-
"ghosty channels whatsapp integrations <agent-id> [--email OTHER]",
|
|
12399
|
+
"ghosty channels whatsapp integrations <agent-id> [--email OTHER] [--orphans]",
|
|
12400
|
+
"ghosty channels messenger ls <agent-id> [--orphans]",
|
|
12140
12401
|
"ghosty channels whatsapp link <agent-id> --integration <formmy-id> --answer on|off [--forward] [--dry-run] [--yes]",
|
|
12141
12402
|
"ghosty channels whatsapp unlink <agent-id> --integration <formmy-id> [--dry-run] [--yes]",
|
|
12142
12403
|
"ghosty channels whatsapp history <agent-id> --integration <formmy-id> [--days 7] [--media] [--dry-run] [--yes]",
|
|
@@ -12163,6 +12424,7 @@ HELPS.channels = {
|
|
|
12163
12424
|
["--name N", "groups set: the name gs shows (e.g. a group saved with its jid as name); WhatsApp's subject doesn't change"],
|
|
12164
12425
|
["--integration ID", "link/unlink/history: the WhatsApp Business number's Integration id in Formmy"],
|
|
12165
12426
|
["--email E", "integrations (staff): list the numbers of another Formmy account"],
|
|
12427
|
+
["--orphans", "integrations / messenger ls: only the numbers or pages nobody answers today that DID have turns, with who answered them last (who to give them back to)"],
|
|
12166
12428
|
["--days N", "history: conversations active in the last N days (1-30, default 7)"],
|
|
12167
12429
|
["--media", "history: only re-copy the missing files (photos, PDFs, audio) of conversations already imported"],
|
|
12168
12430
|
["--answer on|off", "link (required, no default): on = this agent answers real customers; off = it only records"],
|
|
@@ -12182,6 +12444,8 @@ HELPS.channels = {
|
|
|
12182
12444
|
"ghosty channels wa groups ls <id>",
|
|
12183
12445
|
'ghosty channels wa groups enable <id> "Pia-cotizadora" --role equipo --dry-run',
|
|
12184
12446
|
"ghosty channels whatsapp integrations <id>",
|
|
12447
|
+
"ghosty channels whatsapp integrations <id> --orphans",
|
|
12448
|
+
"ghosty channels messenger ls <id>",
|
|
12185
12449
|
"ghosty channels whatsapp link <id> --integration 6a3af395\u2026 --answer off --dry-run",
|
|
12186
12450
|
"ghosty channels whatsapp history <id> --integration 6a3af395\u2026 --days 7 --dry-run",
|
|
12187
12451
|
"ghosty channels whatsapp history <id> --integration 6a3af395\u2026 --days 7 --media",
|
|
@@ -12193,7 +12457,7 @@ HELPS.channels = {
|
|
|
12193
12457
|
"Disabling keeps logging messages; nobody answers them.",
|
|
12194
12458
|
"Groups belong to the PERSONAL WhatsApp paired to the agent (not WhatsApp Business). <group> is its name or its jid.",
|
|
12195
12459
|
"link moves a number that already lives in Formmy (EasyBits, a droplet) without redoing Meta's signup; unlink sends it back. Only the owner, an admin of the agent or Ghosty staff.",
|
|
12196
|
-
"integrations lists the owner's WhatsApp Business numbers in Formmy (number, name, who answers today: gs, EasyBits, a droplet or Formmy) with the Integration id that link and history take. Numbers the workspace already linked in gs show up too, even if another Formmy account owns them.",
|
|
12460
|
+
"integrations lists the owner's WhatsApp Business numbers in Formmy (number, name, who answers today: gs, EasyBits, a droplet or Formmy) with the Integration id that link and history take. Numbers the workspace already linked in gs show up too, even if another Formmy account owns them. \xABlast answered by\xBB is the agent of the latest turn on that number (from gs's turns), so a number left answered by nobody can be given back.",
|
|
12197
12461
|
"history brings the number's recent cards, messages and orders from Formmy to the board (link it first; --dry-run works before linking, to choose --days). Re-running never duplicates: run it before the cutover and again right at it. Files (photos, PDFs, audio) are copied to gs too: Formmy deletes them after 7 days, so bring the number within that week; a copy that failed is retried with --media.",
|
|
12198
12462
|
"import: turn off the EasyBits socket first WITHOUT logout (two sockets knock each other off; a logout invalidates the keys). Delete the file afterwards."
|
|
12199
12463
|
],
|
|
@@ -12283,7 +12547,11 @@ HELPS.cards = {
|
|
|
12283
12547
|
usage: [
|
|
12284
12548
|
"ghosty cards ls <agent-id> [--q TEXT] [--paused] [--limit 20]",
|
|
12285
12549
|
"ghosty cards show <agent-id> <folio|phone|session|id> [--limit 20]",
|
|
12286
|
-
"ghosty cards archive <agent-id> [--integration ID] [--column NAME] [--before 30d|ISO] [--dry-run] [--yes]"
|
|
12550
|
+
"ghosty cards archive <agent-id> [--integration ID] [--column NAME] [--before 30d|ISO] [--dry-run] [--yes]",
|
|
12551
|
+
"ghosty cards pause <agent-id> <card> [--mode 30min|2h|24h|until_tomorrow|permanent]",
|
|
12552
|
+
"ghosty cards resume <agent-id> <card>",
|
|
12553
|
+
"ghosty cards reply <agent-id> <card> [--directive TEXT] [--yes]",
|
|
12554
|
+
"ghosty cards pending <agent-id> [--since 2d|ISO] [--limit N] [--reply [--directive TEXT] [--yes]] [--dry-run]"
|
|
12287
12555
|
],
|
|
12288
12556
|
flags: [
|
|
12289
12557
|
["--q TEXT", "ls: cards whose name, phone or last message contains TEXT"],
|
|
@@ -12292,11 +12560,15 @@ HELPS.cards = {
|
|
|
12292
12560
|
["--integration ID", "cards from this channel/integration (e.g. prueba)"],
|
|
12293
12561
|
["--column NAME", "cards in this column"],
|
|
12294
12562
|
["--before T", "cards with no activity since T (30d, 12h or an ISO date)"],
|
|
12295
|
-
["-n, --dry-run", "count and show a sample, change nothing"],
|
|
12296
|
-
["--yes", "skip the confirmation (required without a terminal)"]
|
|
12563
|
+
["-n, --dry-run", "count and show a sample, change nothing \xB7 pending --reply: list what it would answer, send nothing"],
|
|
12564
|
+
["--yes", "skip the confirmation (required without a terminal)"],
|
|
12565
|
+
["--mode M", "pause: 30min | 2h | 24h (default) | until_tomorrow (9:00) | permanent (escalated: operator only)"],
|
|
12566
|
+
["--directive TEXT", "reply / pending --reply: an instruction for the agent (it writes its own answer following it, it doesn't repeat it)"],
|
|
12567
|
+
["--since T", "pending: customers who wrote since T (30m, 2d or an ISO date; default 3d)"],
|
|
12568
|
+
["--reply", "pending: answer, one by one, the ones with no flag (asks first)"]
|
|
12297
12569
|
],
|
|
12298
|
-
examples: ["ghosty cards ls <id> --q 7712", "ghosty cards ls <id> --paused --limit 200", 'ghosty cards show <id> "#12"', "ghosty cards show <id> 5210000009922 --limit 50", "ghosty cards archive <id> --integration prueba --dry-run", "ghosty cards archive <id> --column Perdido --before 30d --yes"],
|
|
12299
|
-
notes: ["show prints each message with who wrote it (customer, agent, operator), its attachment and whether gs keeps a copy of it.", "Archiving is reversible: when the customer writes again the card comes back. At least one filter is required."],
|
|
12570
|
+
examples: ["ghosty cards ls <id> --q 7712", "ghosty cards ls <id> --paused --limit 200", 'ghosty cards show <id> "#12"', "ghosty cards show <id> 5210000009922 --limit 50", "ghosty cards archive <id> --integration prueba --dry-run", "ghosty cards archive <id> --column Perdido --before 30d --yes", 'ghosty cards pause <id> "#12" --mode 2h', 'ghosty cards resume <id> "#12"', 'ghosty cards reply <id> "#12" --directive "ofr\xE9cele el env\xEDo a domicilio"', "ghosty cards pending <id> --since 2026-10-06T02:13Z", "ghosty cards pending <id> --since 12h --reply --dry-run"],
|
|
12571
|
+
notes: ["show prints each message with who wrote it (customer, agent, operator), its attachment and whether gs keeps a copy of it.", "Archiving is reversible: when the customer writes again the card comes back. At least one filter is required.", "resume lifts the pause AND \xABoperator only\xBB (escalating = a permanent pause), on gs and on Formmy; it does NOT answer by itself \u2014 reply does. reply only works on WhatsApp Business conversations of a number this agent answers.", "pending flags and never answers: paused, escalated, outside WhatsApp's 24 h window (Meta only takes templates), only a reaction, not WhatsApp Business, number answered by nobody. Conversations of another agent's numbers are left out (only counted).", "pause, resume and reply are logged (ghosty audit ls --action card)."],
|
|
12300
12572
|
learn: `${REF}#tablero`
|
|
12301
12573
|
};
|
|
12302
12574
|
HELPS.board = HELPS.cards;
|
|
@@ -12438,6 +12710,42 @@ HELPS.staff = {
|
|
|
12438
12710
|
],
|
|
12439
12711
|
learn: `${REF}#staff-asignado`
|
|
12440
12712
|
};
|
|
12713
|
+
HELPS.audit = {
|
|
12714
|
+
summary: "The audit log (AuditEvent): who logged in \xABas\xBB whom, what changed on an agent's page and channels, pauses and replies from the CLI, transfers, staff in and out. Ghosty staff see everything; anyone else, only the events of their own workspaces and agents.",
|
|
12715
|
+
usage: ["ghosty audit ls [--workspace SLUG] [--agent ID] [--actor EMAIL] [--action A] [--since 7d] [--limit 50] [--full]"],
|
|
12716
|
+
flags: [
|
|
12717
|
+
["--workspace SLUG", "that workspace's events and its agents' (default: the one from ghosty use; none = all of yours)"],
|
|
12718
|
+
["--agent ID", "only that agent's events (name or id)"],
|
|
12719
|
+
["--actor EMAIL", "only what that person did"],
|
|
12720
|
+
["--action A", "agent, channel, card, factory, workspace\u2026 (a prefix) or an exact action (agent.save)"],
|
|
12721
|
+
["--since T", "since T (30m, 24h, 7d or an ISO date; default 7d)"],
|
|
12722
|
+
["--limit N", "how many (default 50, max 1000)"],
|
|
12723
|
+
["--full", "the whole detail of each event (JSON)"]
|
|
12724
|
+
],
|
|
12725
|
+
examples: ["ghosty audit ls --workspace siiqtec --since 24h", "ghosty audit ls --agent tania-0 --action agent", "ghosty audit ls --actor brenda@siiqtec.com --since 2026-10-05T00:00Z", "ghosty audit ls --action card --json"],
|
|
12726
|
+
notes: ["Logged today: agent.save (the agent page's Save), channel.* (Channels page), agent.prompt_patch, card.pause/resume/reply, factory.run_rename, workspace.*, staff and \xABlog in as\xBB. Older changes may not be there.", "A gat_ agent token can't read the log."],
|
|
12727
|
+
learn: `${REF}#bitacora`
|
|
12728
|
+
};
|
|
12729
|
+
HELPS.factory = {
|
|
12730
|
+
summary: "A workspace's Software Factory runs (they live in its Teams): list them, see one (current plan, PR, preview, latest events, which role is working) and fix a run's title.",
|
|
12731
|
+
usage: [
|
|
12732
|
+
"ghosty factory runs ls [--workspace SLUG] [--room R] [--status S] [--limit 30]",
|
|
12733
|
+
"ghosty factory runs show <run> [--workspace SLUG] [--full]",
|
|
12734
|
+
'ghosty factory runs rename <run> "Title" [--workspace SLUG] [--dry-run] [--yes]'
|
|
12735
|
+
],
|
|
12736
|
+
flags: [
|
|
12737
|
+
["--workspace SLUG", "the workspace (default: the one from ghosty use)"],
|
|
12738
|
+
["--room R", "ls: only that room's runs (#slug or name)"],
|
|
12739
|
+
["--status S", "ls: only that stage (planning, building, checking, pr_review, merged\u2026)"],
|
|
12740
|
+
["--limit N", "ls: how many (default 30)"],
|
|
12741
|
+
["--full", "show: print the current plan"],
|
|
12742
|
+
["-n, --dry-run", "rename: show the change, change nothing"],
|
|
12743
|
+
["--yes", "rename: skip the confirmation (required without a terminal)"]
|
|
12744
|
+
],
|
|
12745
|
+
examples: ["ghosty factory runs ls --workspace business --room mailmask", "ghosty factory runs show 15 --workspace business", 'ghosty factory runs rename 15 "Docs del CLI al d\xEDa" --workspace business --dry-run'],
|
|
12746
|
+
notes: ["Reading needs membership in the workspace; rename needs owner, admin or Ghosty staff, and is logged (factory.run_rename). The room's card picks the new title up on its next refresh.", "Who is @plan or @build: ghosty agents ls --handle plan --workspace SLUG."],
|
|
12747
|
+
learn: `${REF}#fabrica`
|
|
12748
|
+
};
|
|
12441
12749
|
function helpText(cmd) {
|
|
12442
12750
|
const base = HELPS[cmd];
|
|
12443
12751
|
if (!base) return void 0;
|
|
@@ -13253,6 +13561,91 @@ ${dim(t("Links with the old slug may stop working.", "Las ligas con el slug viej
|
|
|
13253
13561
|
}
|
|
13254
13562
|
}
|
|
13255
13563
|
|
|
13564
|
+
// src/commands/audit.ts
|
|
13565
|
+
function detailLine(d, max = 90) {
|
|
13566
|
+
if (d === null || d === void 0) return "";
|
|
13567
|
+
if (typeof d !== "object") return String(d).slice(0, max);
|
|
13568
|
+
const parts = Object.entries(d).filter(([k]) => k !== "via").map(([k, v]) => `${k}=${v !== null && typeof v === "object" ? JSON.stringify(v) : String(v)}`);
|
|
13569
|
+
const s = parts.join(" ");
|
|
13570
|
+
return s.length > max ? `${s.slice(0, max - 1)}\u2026` : s;
|
|
13571
|
+
}
|
|
13572
|
+
async function auditCmd(sub, v, context) {
|
|
13573
|
+
if (sub && sub !== "ls" && sub !== "list") throw usage(t(`Unknown audit subcommand: ${sub}.`, `Subcomando de audit desconocido: ${sub}.`), "ghosty audit ls [--workspace SLUG] [--agent ID] [--actor EMAIL] [--action A] [--since 24h] [--limit N]");
|
|
13574
|
+
const ws = v.workspace === "none" ? void 0 : v.workspace ?? (v.agent ? void 0 : context);
|
|
13575
|
+
const qs = new URLSearchParams({
|
|
13576
|
+
...ws ? { workspace: ws } : {},
|
|
13577
|
+
...v.agent ? { agent: v.agent } : {},
|
|
13578
|
+
...v.actor ? { actor: v.actor } : {},
|
|
13579
|
+
...v.action ? { action: v.action } : {},
|
|
13580
|
+
since: v.since ?? "7d",
|
|
13581
|
+
...v.limit ? { limit: v.limit } : {}
|
|
13582
|
+
});
|
|
13583
|
+
const r = await api(`/api/v2/audit?${qs}`);
|
|
13584
|
+
const rows = r.events.map((e) => ({
|
|
13585
|
+
at: e.at.slice(0, 16).replace("T", " "),
|
|
13586
|
+
actor: e.actor,
|
|
13587
|
+
action: e.action,
|
|
13588
|
+
where: [e.workspace, e.agentName ?? e.agentId].filter(Boolean).join(" \xB7 "),
|
|
13589
|
+
detail: v.full ? JSON.stringify(e.detail ?? null) : detailLine(e.detail)
|
|
13590
|
+
}));
|
|
13591
|
+
return out(r, () => rows.length ? `${table(rows, ["at", "actor", "action", "where", "detail"])}
|
|
13592
|
+
${dim(t("Times in UTC. --full for the whole detail; --json for everything.", "Horas en UTC. --full para el detalle completo; --json para todo."))}` : dim(t(`No events since ${v.since ?? "7d"}${ws ? ` in ${ws}` : ""}.`, `Sin eventos desde ${v.since ?? "7d"}${ws ? ` en ${ws}` : ""}.`)));
|
|
13593
|
+
}
|
|
13594
|
+
|
|
13595
|
+
// src/commands/factory.ts
|
|
13596
|
+
var enc12 = encodeURIComponent;
|
|
13597
|
+
var short2 = (s) => (s ?? "").slice(0, 16).replace("T", " ");
|
|
13598
|
+
async function factoryCmd(sub, verb, ref, title, v, context) {
|
|
13599
|
+
if (sub !== "runs") throw usage(t(`Unknown factory subcommand: ${sub ?? "(none)"}.`, `Subcomando de factory desconocido: ${sub ?? "(ninguno)"}.`), "ghosty factory runs ls|show|rename \u2026");
|
|
13600
|
+
const ws = v.workspace ?? context;
|
|
13601
|
+
if (!ws) throw usage(t("Which workspace?", "\xBFQu\xE9 espacio?"), t("Add --workspace SLUG (or set one with ghosty use SLUG).", "Agrega --workspace SLUG (o fija uno con ghosty use SLUG)."));
|
|
13602
|
+
const base = `/api/v2/workspaces/${enc12(ws)}/factory/runs`;
|
|
13603
|
+
if (!verb || verb === "ls" || verb === "list") {
|
|
13604
|
+
const qs = new URLSearchParams({ ...v.room ? { room: v.room } : {}, ...v.status ? { status: v.status } : {}, ...v.limit ? { limit: v.limit } : {} });
|
|
13605
|
+
const r = await api(`${base}?${qs}`);
|
|
13606
|
+
const rows = r.runs.map((x) => ({ run: `#${x.id}`, title: x.title.slice(0, 60), status: x.status, room: x.room ? `#${x.room}` : "", pr: x.pr ? x.pr.replace(/^https:\/\/github\.com\//, "") : "", updated: short2(x.updatedAt) }));
|
|
13607
|
+
return out(r, () => rows.length ? `${table(rows, ["run", "title", "status", "room", "pr", "updated"])}
|
|
13608
|
+
${dim(`${t("Detail", "Detalle")}: ghosty factory runs show <run> --workspace ${r.workspace}`)}` : dim(t(`No factory runs in ${r.workspace}.`, `No hay pedidos de la f\xE1brica en ${r.workspace}.`)));
|
|
13609
|
+
}
|
|
13610
|
+
if (!ref) throw usage(t("Missing run.", "Falta el pedido."), `ghosty factory runs ${verb} <run> --workspace ${ws}`);
|
|
13611
|
+
const path = `${base}/${enc12(ref.replace(/^#/, ""))}`;
|
|
13612
|
+
if (verb === "show") {
|
|
13613
|
+
const { run: r } = await api(path);
|
|
13614
|
+
const lines = [
|
|
13615
|
+
`${bold(`#${r.id} ${r.title}`)} ${dim(`\xB7 ${r.status}${r.kind ? ` (${r.kind})` : ""} \xB7 ${r.room ? `#${r.room}` : ""}`)}`,
|
|
13616
|
+
`${t("repo", "repo")}: ${r.repo ?? "\u2014"}${r.branch ? ` (${r.branch})` : ""} PR: ${r.pr ?? "\u2014"}`,
|
|
13617
|
+
`preview: ${r.preview ?? "\u2014"}${r.previewState ? dim(` (${r.previewState})`) : ""}`,
|
|
13618
|
+
`${t("plan", "plan")}: v${r.planVersion}${r.plan ? ` ${r.plan.decision ?? t("pending", "pendiente")}${r.plan.decidedBy ? ` ${t("by", "por")} ${r.plan.decidedBy}` : ""}` : ""} \xB7 ${t("loops", "vueltas")}: ${r.loops} \xB7 ${t("requested by", "pidi\xF3")} ${r.requestedBy}`,
|
|
13619
|
+
...r.plans.map((p) => dim(` v${p.version} ${p.decision ?? "\u2014"}: ${p.title ?? ""}`)),
|
|
13620
|
+
`${t("running", "trabajando")}: ${r.running.length ? r.running.map((x) => `@${x.role} ${dim(`(${x.turn}, ${short2(x.since)})`)}`).join(", ") : dim(t("nobody", "nadie"))}`,
|
|
13621
|
+
bold(t("Latest events", "\xDAltimos eventos")),
|
|
13622
|
+
...r.events.map((e) => ` ${dim(short2(e.at))} ${e.type}${e.actor ? ` ${dim(e.actor)}` : ""}`),
|
|
13623
|
+
...v.full && r.plan ? ["", bold(t("Current plan", "Plan vigente")), r.plan.markdown] : [dim(t("--full prints the current plan.", "--full imprime el plan vigente."))]
|
|
13624
|
+
];
|
|
13625
|
+
return out(r, () => lines.join("\n"));
|
|
13626
|
+
}
|
|
13627
|
+
if (verb === "rename") {
|
|
13628
|
+
if (!title?.trim()) throw usage(t("Missing the new title.", "Falta el t\xEDtulo nuevo."), `ghosty factory runs rename ${ref} "T\xEDtulo" --workspace ${ws}`);
|
|
13629
|
+
const retry = `ghosty factory runs rename ${ref} ${JSON.stringify(title)} --workspace ${ws}`;
|
|
13630
|
+
if (!v["dry-run"]) requireYesIfHeadless(v.yes, retry);
|
|
13631
|
+
const plan = await api(path, { body: { title, dryRun: true } });
|
|
13632
|
+
const line2 = `#${ref.replace(/^#/, "")}: \xAB${plan.from}\xBB \u2192 \xAB${plan.to}\xBB`;
|
|
13633
|
+
if (v["dry-run"]) return out({ ...plan, dryRun: true }, () => `${line2}
|
|
13634
|
+
${dim(t("Dry run: nothing changed.", "Simulacro: no se cambi\xF3 nada."))}`);
|
|
13635
|
+
await confirm(t(`Rename ${line2}?`, `\xBFRenombrar ${line2}?`), { yes: v.yes, retry });
|
|
13636
|
+
const r = await api(path, { body: { title } });
|
|
13637
|
+
return out(r, () => `${lavender("\u2713")} ${line2} ${dim(t("(logged; the room card picks it up on its next refresh)", "(queda en bit\xE1cora; la tarjeta del room lo toma en su siguiente refresco)"))}`);
|
|
13638
|
+
}
|
|
13639
|
+
throw usage(t(`Unknown runs subcommand: ${verb}.`, `Subcomando de runs desconocido: ${verb}.`), 'ghosty factory runs ls | show <run> | rename <run> "title"');
|
|
13640
|
+
}
|
|
13641
|
+
async function agentsByHandle(handle, workspace) {
|
|
13642
|
+
const qs = new URLSearchParams({ handle, ...workspace ? { workspace } : {} });
|
|
13643
|
+
const r = await api(`/api/v2/teams/handles?${qs}`);
|
|
13644
|
+
const rows = r.agents.map((a) => ({ workspace: a.workspace, handle: `@${a.handle}`, name: a.name ?? "", engine: [a.engine, a.model].filter(Boolean).join("/"), on: a.enabled ? t("yes", "s\xED") : dim(t("paused", "en pausa")), id: a.agentId ?? dim(t("(not a gs agent)", "(no es agente de gs)")) }));
|
|
13645
|
+
return out(r, () => `${rows.length ? table(rows, ["workspace", "handle", "name", "engine", "on", "id"]) : dim(t(`Nobody is @${r.handle}${workspace ? ` in ${workspace}` : " in your workspaces"}.`, `Nadie es @${r.handle}${workspace ? ` en ${workspace}` : " en tus espacios"}.`))}${r.unreadable?.length ? `
|
|
13646
|
+
${dim(t(`Couldn't read: ${r.unreadable.join(", ")}`, `No se pudieron leer: ${r.unreadable.join(", ")}`))}` : ""}`);
|
|
13647
|
+
}
|
|
13648
|
+
|
|
13256
13649
|
// src/commands/staff.ts
|
|
13257
13650
|
var day2 = (iso) => iso ? iso.slice(0, 10) : "";
|
|
13258
13651
|
async function staffCmd(sub, staff, client, v) {
|
|
@@ -13292,7 +13685,7 @@ async function staffCmd(sub, staff, client, v) {
|
|
|
13292
13685
|
|
|
13293
13686
|
// src/commands/support.ts
|
|
13294
13687
|
import { readFile as readFile14 } from "node:fs/promises";
|
|
13295
|
-
var
|
|
13688
|
+
var enc13 = encodeURIComponent;
|
|
13296
13689
|
var cdmx = (iso) => new Date(Date.parse(iso) - 6 * 36e5).toISOString().slice(5, 16).replace("T", " ");
|
|
13297
13690
|
async function supportThreads(email, v) {
|
|
13298
13691
|
const q = new URLSearchParams({ user: email, since: v.since ?? "30d", ...v.surface ? { surface: v.surface } : {} });
|
|
@@ -13312,7 +13705,7 @@ ${table(rows, ["last", "agent", "conversation", "title", "kind", "listed", "turn
|
|
|
13312
13705
|
${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"))}`);
|
|
13313
13706
|
}
|
|
13314
13707
|
async function supportThread(agent, sid, v) {
|
|
13315
|
-
const r = await api(`/api/v2/support/threads/${
|
|
13708
|
+
const r = await api(`/api/v2/support/threads/${enc13(agent)}/${enc13(sid)}?tail=${enc13(v.limit ?? "40")}${v.tools ? "&tools=1" : ""}`);
|
|
13316
13709
|
out(r, () => {
|
|
13317
13710
|
if (v.tools && r.tools) {
|
|
13318
13711
|
const x = r.tools;
|
|
@@ -13344,7 +13737,7 @@ async function cleanupConversations(action, agent, v) {
|
|
|
13344
13737
|
const ex = `ghosty conversations ${action} <agent> --kind ${action === "rm" ? "test" : "test|channel"} [--before 7d] [--dry-run]`;
|
|
13345
13738
|
if (!agent) throw usage(t("Missing agent.", "Falta el agente."), ex);
|
|
13346
13739
|
if (v.kind !== "test" && v.kind !== "channel") throw usage(t("--kind takes test or channel.", "--kind acepta test o channel."), ex);
|
|
13347
|
-
const path = `/api/v2/agents/${
|
|
13740
|
+
const path = `/api/v2/agents/${enc13(agent)}/conversations/cleanup`;
|
|
13348
13741
|
const body = { kind: v.kind, action, ...v.before ? { before: v.before } : {} };
|
|
13349
13742
|
const plan = await api(path, { method: "POST", body: { ...body, dryRun: true } });
|
|
13350
13743
|
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)");
|
|
@@ -13359,7 +13752,7 @@ ${t("Go ahead?", "\xBFSigo?")}`, { yes: v.yes, retry: `ghosty conversations ${ac
|
|
|
13359
13752
|
out(r, () => `${lavender("\u2713")} ${t(`${r.threads} conversation(s) ${action === "rm" ? "deleted" : "archived"}.`, `${r.threads} conversaci\xF3n(es) ${action === "rm" ? "borradas" : "archivadas"}.`)}`);
|
|
13360
13753
|
}
|
|
13361
13754
|
async function turnStats(v) {
|
|
13362
|
-
const r = await api(`/api/v2/support/turn-stats?since=${
|
|
13755
|
+
const r = await api(`/api/v2/support/turn-stats?since=${enc13(v.since ?? "14d")}${v.all ? "&all=1" : ""}`);
|
|
13363
13756
|
out(
|
|
13364
13757
|
r,
|
|
13365
13758
|
() => [
|
|
@@ -13379,7 +13772,7 @@ async function fleetBoxes() {
|
|
|
13379
13772
|
${table(rows, ["template", "boxes", "on", "asleep", "ram", "vcpu", "ram on"])}`);
|
|
13380
13773
|
}
|
|
13381
13774
|
async function appAgentsOf(email, client) {
|
|
13382
|
-
const r = await api(`/api/v2/support/app-agents?user=${
|
|
13775
|
+
const r = await api(`/api/v2/support/app-agents?user=${enc13(email)}${client && client !== "all" ? `&client=${enc13(client)}` : ""}`);
|
|
13383
13776
|
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) : "" }));
|
|
13384
13777
|
const phones = (r.devices ?? []).map((d) => `${d.platform} ${d.appVersion ?? ""}${d.appBuild ? ` (build ${d.appBuild})` : " (build ?)"} \xB7 ${t("seen", "visto")} ${cdmx(d.lastSeenAt)}`.replace(/\s+/g, " "));
|
|
13385
13778
|
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"))}
|
|
@@ -13390,7 +13783,7 @@ ${dim(t("No phones registered for push.", "Sin tel\xE9fonos registrados para avi
|
|
|
13390
13783
|
}
|
|
13391
13784
|
async function pushLog(email, v) {
|
|
13392
13785
|
if (!email) throw usage(t("Missing --user.", "Falta --user."), "ghosty push log --user ana@empresa.com [--since 2d]");
|
|
13393
|
-
const r = await api(`/api/v2/support/push-log?user=${
|
|
13786
|
+
const r = await api(`/api/v2/support/push-log?user=${enc13(email)}&since=${enc13(v.since ?? "2d")}${v.limit ? `&limit=${enc13(v.limit)}` : ""}`);
|
|
13394
13787
|
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 ?? "" }));
|
|
13395
13788
|
out(r, () => rows.length ? `${bold(r.user)}
|
|
13396
13789
|
${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).`)));
|
|
@@ -13428,24 +13821,24 @@ ${dim(t(`Try it on your phone: ghosty novedades test ${r.id}`, `Pru\xE9bala en t
|
|
|
13428
13821
|
if (!id) throw usage(t("Missing id.", "Falta el id."), ex);
|
|
13429
13822
|
const b = await fields();
|
|
13430
13823
|
if (!Object.keys(b).length) throw usage(t("Nothing to change.", "Nada que cambiar."), ex);
|
|
13431
|
-
const r = await api(`${base}/${
|
|
13824
|
+
const r = await api(`${base}/${enc13(id)}`, { method: "PATCH", body: b });
|
|
13432
13825
|
return out(r, () => `${lavender("\u2713")} ${line2(r)}`);
|
|
13433
13826
|
}
|
|
13434
13827
|
case "test": {
|
|
13435
13828
|
if (!id) throw usage(t("Missing id.", "Falta el id."), ex);
|
|
13436
|
-
const r = await api(`${base}/${
|
|
13829
|
+
const r = await api(`${base}/${enc13(id)}`, { method: "POST", body: { push: "test" } });
|
|
13437
13830
|
return out(r, () => `${lavender("\u2713")} ${t(`Sent to your phone (${r.enviados} device(s)).`, `Enviada a tu tel\xE9fono (${r.enviados} dispositivo(s)).`)}`);
|
|
13438
13831
|
}
|
|
13439
13832
|
case "send":
|
|
13440
13833
|
case "schedule": {
|
|
13441
13834
|
if (!id) throw usage(t("Missing id.", "Falta el id."), ex);
|
|
13442
13835
|
if (v.at !== void 0) {
|
|
13443
|
-
const r2 = await api(`${base}/${
|
|
13836
|
+
const r2 = await api(`${base}/${enc13(id)}`, { method: "PATCH", body: { schedule: v.at || null } });
|
|
13444
13837
|
return out(r2, () => `${lavender("\u2713")} ${line2(r2)}`);
|
|
13445
13838
|
}
|
|
13446
13839
|
requireYesIfHeadless(v.yes, `ghosty novedades send ${id}`);
|
|
13447
13840
|
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}` });
|
|
13448
|
-
const r = await api(`${base}/${
|
|
13841
|
+
const r = await api(`${base}/${enc13(id)}`, { method: "POST", body: { push: "all" } });
|
|
13449
13842
|
return out(r, () => `${lavender("\u2713")} ${t(`Pushed to ${r.enviados} device(s).`, `Enviada a ${r.enviados} dispositivo(s).`)}`);
|
|
13450
13843
|
}
|
|
13451
13844
|
case "rm":
|
|
@@ -13453,7 +13846,7 @@ ${dim(t(`Try it on your phone: ghosty novedades test ${r.id}`, `Pru\xE9bala en t
|
|
|
13453
13846
|
if (!id) throw usage(t("Missing id.", "Falta el id."), ex);
|
|
13454
13847
|
requireYesIfHeadless(v.yes, `ghosty novedades rm ${id}`);
|
|
13455
13848
|
await confirm(t(`Delete announcement ${id}?`, `\xBFBorrar la novedad ${id}?`), { yes: v.yes, retry: `ghosty novedades rm ${id}` });
|
|
13456
|
-
return out(await api(`${base}/${
|
|
13849
|
+
return out(await api(`${base}/${enc13(id)}`, { method: "DELETE" }), () => `${lavender("\u2713")} ${t("Deleted.", "Borrada.")}`);
|
|
13457
13850
|
}
|
|
13458
13851
|
default:
|
|
13459
13852
|
throw usage(t(`Unknown novedades subcommand: ${sub}.`, `Subcomando de novedades desconocido: ${sub}.`), ex);
|
|
@@ -13534,9 +13927,12 @@ ${bold("Workspaces")} ${dim("(changing them: Ghosty staff)")}
|
|
|
13534
13927
|
spaces log <workspace> Its log: transfers, staff in/out, renewals (owner or staff) [--since 30d] [--action workspace]
|
|
13535
13928
|
staff ls [staff-email] [client-email] Ghosty staff assigned to client accounts (staff) [--all: also revoked]
|
|
13536
13929
|
staff assign|unassign <staff> <client> Staff sees and runs that account's agents, documents, databases and boards as themselves (no keys, billing or transfers; logged) [--reason]
|
|
13930
|
+
audit ls The audit log: who changed what (agent page, channels, pauses, replies, transfers, "log in as") [--workspace SLUG] [--agent ID] [--actor EMAIL] [--action A] [--since 24h] [--limit N]
|
|
13931
|
+
factory runs ls|show|rename A workspace's Software Factory runs: list, detail (plan, PR, preview, who is working), fix a title [--workspace SLUG] [--room R] [--status S]
|
|
13537
13932
|
|
|
13538
13933
|
${bold("Agents")}
|
|
13539
13934
|
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)
|
|
13935
|
+
[--handle build [--workspace WS]] who answers to that @handle in Teams
|
|
13540
13936
|
[--sponsor EMAIL | --owner EMAIL] any account's agents (Ghosty staff)
|
|
13541
13937
|
agents create Create an agent --name [--engine] [--model] [--prompt | --prompt-file FILE] [--env K=V,\u2026]
|
|
13542
13938
|
[--workspace SLUG] [--channels teams=off,\u2026] born in a workspace, active in its Teams
|
|
@@ -13552,6 +13948,7 @@ ${bold("Agents")}
|
|
|
13552
13948
|
[--catalog productos.csv|json] its catalog: sku,nombre,precio[,unidad,categoria]
|
|
13553
13949
|
[--toolsets crm,sales | +crm,-sales] gs tool groups it gets
|
|
13554
13950
|
[--prompt-base AGENT|none --prompt-overlay FILE [--dry-run]] prompt = another agent's + edits, kept in sync
|
|
13951
|
+
[--prompt-patch overlay.json | --prompt-append FILE [--dry-run]] change one piece of the prompt, on the server's current one
|
|
13555
13952
|
[--tool wa:tool=off,\u2026] [--bag NAME|none] [--avatar URL|FILE] a tool per channel \xB7 its token bag (staff) \xB7 its face in Teams
|
|
13556
13953
|
[--prompt-mode append|replace] replace = its prompt WITHOUT Ghosty's house persona
|
|
13557
13954
|
agents doctor <id> Check the config: overlapping skills, tools the prompt names, broken grants, failing turns
|
|
@@ -13581,7 +13978,8 @@ ${bold("Configure an agent")}
|
|
|
13581
13978
|
dbs photos put <id> <db> --table T --key-column K --dir DIR Loose photos to a catalog (SKU-123.jpg \u2192 its row), permanent links [--column C] [--dry-run]
|
|
13582
13979
|
mcp get|set <id> MCP servers [--file servers.json] (http headers accept \${turn.token})
|
|
13583
13980
|
channels whatsapp ls|enable|disable <id> [number] Which WhatsApp Business numbers it answers [--dry-run]
|
|
13584
|
-
channels whatsapp integrations <id> Its owner's WhatsApp Business numbers in Formmy (number, who answers today, Integration id)
|
|
13981
|
+
channels whatsapp integrations <id> Its owner's WhatsApp Business numbers in Formmy (number, who answers today, who answered last, Integration id) [--orphans: answered by nobody but with turns]
|
|
13982
|
+
channels messenger ls <id> The workspace's Messenger pages: who answers now and who answered last [--orphans]
|
|
13585
13983
|
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]
|
|
13586
13984
|
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]
|
|
13587
13985
|
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]
|
|
@@ -13592,6 +13990,9 @@ ${bold("Configure an agent")}
|
|
|
13592
13990
|
cards ls <id> Recent cards on its sales board [--q TEXT] [--paused] [--limit 20]
|
|
13593
13991
|
cards show <id> <card> A card's thread: who wrote, text, attachments and whether gs kept a copy (folio, phone or id)
|
|
13594
13992
|
cards archive <id> Archive cards in bulk (reversible) --integration X | --column X | --before 30d [--dry-run]
|
|
13993
|
+
cards pause|resume <id> <card> Pause the agent in a conversation, or resume it (also lifts "operator only") [--mode 30min|2h|24h|until_tomorrow|permanent]
|
|
13994
|
+
cards reply <id> <card> The agent answers what the customer left pending ("Ask for a reply") [--directive TEXT] [--yes]
|
|
13995
|
+
cards pending <id> Conversations whose last message is the customer's, and why some can't be answered [--since 2d|ISO] [--reply [--directive] [--yes]] [--dry-run]
|
|
13595
13996
|
|
|
13596
13997
|
${bold("Support")} ${dim("(Ghosty staff)")}
|
|
13597
13998
|
conversations ls --user EMAIL Someone's conversations in any agent [--since 30d] [--surface android]
|
|
@@ -13682,9 +14083,12 @@ ${bold("Espacios")} ${dim("(cambiarlos: staff de Ghosty)")}
|
|
|
13682
14083
|
spaces log <espacio> Su bit\xE1cora: traspasos, staff que entra o sale, renovaciones (due\xF1o o staff) [--since 30d] [--action workspace]
|
|
13683
14084
|
staff ls [correo-staff] [correo-cliente] Staff de Ghosty asignado a cuentas cliente (staff) [--all: tambi\xE9n revocadas]
|
|
13684
14085
|
staff assign|unassign <staff> <cliente> El staff ve y opera agentes, documentos, bases y tableros de esa cuenta como s\xED mismo (sin llaves, cobro ni traspasos; queda en bit\xE1cora) [--reason]
|
|
14086
|
+
audit ls La bit\xE1cora: qui\xE9n cambi\xF3 qu\xE9 (ficha, canales, pausas, respuestas, traspasos, \xABentrar como\xBB) [--workspace SLUG] [--agent ID] [--actor CORREO] [--action A] [--since 24h] [--limit N]
|
|
14087
|
+
factory runs ls|show|rename Los pedidos de la Software Factory de un espacio: lista, detalle (plan, PR, preview, qui\xE9n trabaja), corregir un t\xEDtulo [--workspace SLUG] [--room R] [--status S]
|
|
13685
14088
|
|
|
13686
14089
|
${bold("Agentes")}
|
|
13687
14090
|
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)
|
|
14091
|
+
[--handle build [--workspace WS]] qui\xE9n contesta a ese @handle en Teams
|
|
13688
14092
|
[--sponsor CORREO | --owner CORREO] agentes de cualquier cuenta (staff de Ghosty)
|
|
13689
14093
|
agents create Crea un agente --name [--engine] [--model] [--prompt | --prompt-file ARCHIVO] [--env K=V,\u2026]
|
|
13690
14094
|
[--workspace SLUG] [--channels teams=off,\u2026] nace en un espacio, activo en su Teams
|
|
@@ -13700,6 +14104,7 @@ ${bold("Agentes")}
|
|
|
13700
14104
|
[--catalog productos.csv|json] su cat\xE1logo: sku,nombre,precio[,unidad,categoria]
|
|
13701
14105
|
[--toolsets crm,sales | +crm,-sales] grupos de tools de gs que recibe
|
|
13702
14106
|
[--prompt-base AGENTE|none --prompt-overlay ARCHIVO [--dry-run]] prompt = el de otro agente + ediciones, sincronizado
|
|
14107
|
+
[--prompt-patch overlay.json | --prompt-append ARCHIVO [--dry-run]] cambia un pedazo del prompt, sobre el actual del servidor
|
|
13703
14108
|
[--tool wa:tool=off,\u2026] [--bag NOMBRE|none] [--avatar URL|ARCHIVO] una tool por canal \xB7 su bolsa de tokens (staff) \xB7 su cara en Teams
|
|
13704
14109
|
[--prompt-mode append|replace] replace = su prompt SIN la persona de casa de Ghosty
|
|
13705
14110
|
agents doctor <id> Revisa la configuraci\xF3n: skills encimadas, tools que nombra el prompt, permisos rotos, turnos que fallan
|
|
@@ -13729,7 +14134,8 @@ ${bold("Configurar un agente")}
|
|
|
13729
14134
|
dbs photos put <id> <db> --table T --key-column K --dir DIR Fotos sueltas a un cat\xE1logo (SKU-123.jpg \u2192 su fila), ligas permanentes [--column C] [--dry-run]
|
|
13730
14135
|
mcp get|set <id> Servidores MCP [--file servers.json] (headers http admiten \${turn.token})
|
|
13731
14136
|
channels whatsapp ls|enable|disable <id> [n\xFAmero] Qu\xE9 n\xFAmeros de WhatsApp Business contesta [--dry-run]
|
|
13732
|
-
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)
|
|
14137
|
+
channels whatsapp integrations <id> Los n\xFAmeros de WhatsApp Business de su due\xF1o en Formmy (n\xFAmero, qui\xE9n contesta hoy, qui\xE9n contest\xF3 al \xFAltimo, id de la Integration) [--orphans: sin qui\xE9n conteste pero con turnos]
|
|
14138
|
+
channels messenger ls <id> Las p\xE1ginas de Messenger del espacio: qui\xE9n contesta hoy y qui\xE9n contest\xF3 al \xFAltimo [--orphans]
|
|
13733
14139
|
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]
|
|
13734
14140
|
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]
|
|
13735
14141
|
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]
|
|
@@ -13740,6 +14146,9 @@ ${bold("Configurar un agente")}
|
|
|
13740
14146
|
cards ls <id> Tarjetas recientes de su tablero de ventas [--q TEXTO] [--paused] [--limit 20]
|
|
13741
14147
|
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)
|
|
13742
14148
|
cards archive <id> Archiva tarjetas en bloque (reversible) --integration X | --column X | --before 30d [--dry-run]
|
|
14149
|
+
cards pause|resume <id> <tarjeta> Pausa al agente en una conversaci\xF3n, o lo reanuda (tambi\xE9n quita \xABs\xF3lo operador\xBB) [--mode 30min|2h|24h|until_tomorrow|permanent]
|
|
14150
|
+
cards reply <id> <tarjeta> El agente contesta lo que el cliente dej\xF3 pendiente (\xABPedir respuesta\xBB) [--directive TEXTO] [--yes]
|
|
14151
|
+
cards pending <id> Conversaciones cuyo \xFAltimo mensaje es del cliente, y por qu\xE9 algunas no se contestan [--since 2d|ISO] [--reply [--directive] [--yes]] [--dry-run]
|
|
13743
14152
|
|
|
13744
14153
|
${bold("Soporte")} ${dim("(staff de Ghosty)")}
|
|
13745
14154
|
conversations ls --user CORREO Las conversaciones de alguien en cualquier agente [--since 30d] [--surface android]
|
|
@@ -13804,7 +14213,7 @@ ${bold("Opciones globales")}
|
|
|
13804
14213
|
${bold("C\xF3digos de salida")} 0 bien \xB7 1 error de la API \xB7 2 uso incorrecto \xB7 3 sin sesi\xF3n
|
|
13805
14214
|
${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.")}
|
|
13806
14215
|
${dim("Documentaci\xF3n: https://www.ghosty.studio/docs/cli")}`;
|
|
13807
|
-
var COMMANDS = ["login", "logout", "whoami", "usage", "use", "apply", "plan", "engines", "bags", "spaces", "workspaces", "staff", "demos", "sites", "push", "novedades", "completion", "cards", "agents", "files", "skills", "mcp", "dbs", "turns", "try", "chat", "conversations", "schedule", "partner", "me", "channels", "board", "boards", "credentials", "connectors", "browser", "docs", "help"];
|
|
14216
|
+
var COMMANDS = ["login", "logout", "whoami", "usage", "use", "apply", "plan", "engines", "bags", "spaces", "workspaces", "staff", "audit", "factory", "demos", "sites", "push", "novedades", "completion", "cards", "agents", "files", "skills", "mcp", "dbs", "turns", "try", "chat", "conversations", "schedule", "partner", "me", "channels", "board", "boards", "credentials", "connectors", "browser", "docs", "help"];
|
|
13808
14217
|
var AGENTS_SUBS = ["ls", "create", "get", "set", "export", "doctor", "docs", "share", "unshare", "shares", "teams", "rm", "restart", "demo", "box"];
|
|
13809
14218
|
function unknown(what, input, known, helpCmd) {
|
|
13810
14219
|
const s = suggest(input, known);
|
|
@@ -13940,6 +14349,13 @@ async function main() {
|
|
|
13940
14349
|
toolsets: { type: "string" },
|
|
13941
14350
|
"prompt-base": { type: "string" },
|
|
13942
14351
|
"prompt-overlay": { type: "string" },
|
|
14352
|
+
"prompt-patch": { type: "string" },
|
|
14353
|
+
"prompt-append": { type: "string" },
|
|
14354
|
+
mode: { type: "string" },
|
|
14355
|
+
directive: { type: "string" },
|
|
14356
|
+
reply: { type: "boolean" },
|
|
14357
|
+
actor: { type: "string" },
|
|
14358
|
+
room: { type: "string" },
|
|
13943
14359
|
url: { type: "string" },
|
|
13944
14360
|
description: { type: "string" },
|
|
13945
14361
|
grant: { type: "string" },
|
|
@@ -14036,7 +14452,7 @@ async function main() {
|
|
|
14036
14452
|
const tokens = splitTokens(pos, v);
|
|
14037
14453
|
globals.token = tokens.session;
|
|
14038
14454
|
globals.profile = v.profile;
|
|
14039
|
-
if (v.version) return out({ version: "0.
|
|
14455
|
+
if (v.version) return out({ version: "0.50.0" }, () => "0.50.0");
|
|
14040
14456
|
for (const n of normalizeArgs(pos, v)) if (!globals.json) info(dim(n));
|
|
14041
14457
|
const context = (await readConfig()).workspace;
|
|
14042
14458
|
if (!explicitToken()?.startsWith("gat_")) {
|
|
@@ -14084,6 +14500,7 @@ async function main() {
|
|
|
14084
14500
|
return applyCmd(sub, v);
|
|
14085
14501
|
case "agents":
|
|
14086
14502
|
if ((sub === "ls" || sub === "list" || !sub) && v.user && v.app !== void 0) return appAgentsOf(v.user, v.app);
|
|
14503
|
+
if ((sub === "ls" || sub === "list" || !sub) && v.handle) return agentsByHandle(v.handle, v.workspace ?? context);
|
|
14087
14504
|
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 });
|
|
14088
14505
|
if (sub === "create" || sub === "new") {
|
|
14089
14506
|
if (v.workspace === "none") delete v.workspace;
|
|
@@ -14179,6 +14596,10 @@ async function main() {
|
|
|
14179
14596
|
return spacesCmd(sub, a1, a2, v, context);
|
|
14180
14597
|
case "staff":
|
|
14181
14598
|
return staffCmd(sub, a1, a2, v);
|
|
14599
|
+
case "audit":
|
|
14600
|
+
return auditCmd(sub, v, context);
|
|
14601
|
+
case "factory":
|
|
14602
|
+
return factoryCmd(sub, a1, a2, pos[4], v, context);
|
|
14182
14603
|
case "plan":
|
|
14183
14604
|
return planCmd(sub, a1, v, context);
|
|
14184
14605
|
case "me":
|
|
@@ -14196,7 +14617,7 @@ async function main() {
|
|
|
14196
14617
|
throw unknown(cmd, cmd, COMMANDS, "ghosty --help");
|
|
14197
14618
|
}
|
|
14198
14619
|
}
|
|
14199
|
-
var notice = updateNotice("0.
|
|
14620
|
+
var notice = updateNotice("0.50.0").catch(() => null);
|
|
14200
14621
|
main().then(
|
|
14201
14622
|
async () => {
|
|
14202
14623
|
const n = await notice;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ghostystudio/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.50.0",
|
|
4
4
|
"description": "Official Ghosty Studio CLI — configure, test and chat with your agents from the terminal. Built for humans and coding agents.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"scripts": {
|
|
17
17
|
"build": "esbuild src/index.ts --bundle --platform=node --format=esm --target=node22 --outfile=dist/index.js --banner:js='#!/usr/bin/env node\nimport { createRequire as __cr } from \"node:module\"; const require = __cr(import.meta.url);' --define:__VERSION__=\\\"$npm_package_version\\\"",
|
|
18
18
|
"prepack": "npm run build",
|
|
19
|
-
"test": "for t in flags prompt api rows wa-auth resolve aliases i18n help completion photos channels-retry spaces sites; do esbuild src/$t.test.ts --bundle --platform=node --format=esm --outfile=/tmp/ghosty-cli-$t.test.mjs --log-level=error || exit 1; done && node --test /tmp/ghosty-cli-*.test.mjs"
|
|
19
|
+
"test": "for t in flags prompt api rows wa-auth resolve aliases i18n help completion photos channels-retry spaces sites diff cards; do esbuild src/$t.test.ts --bundle --platform=node --format=esm --outfile=/tmp/ghosty-cli-$t.test.mjs --log-level=error || exit 1; done && node --test /tmp/ghosty-cli-*.test.mjs"
|
|
20
20
|
},
|
|
21
21
|
"keywords": [
|
|
22
22
|
"ghosty",
|