@ghostystudio/cli 0.49.0 → 0.51.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 +15 -0
- package/dist/index.js +510 -40
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -38,6 +38,8 @@ ghosty agents box <id> [--check PATH]… [--contains PATH=TEXT]… [--recycle]
|
|
|
38
38
|
ghosty agents demo enable|disable <id>
|
|
39
39
|
ghosty agents demo <id> [--slug NAME] [--expires "YYYY-MM-DDTHH:MM"|no] [--turns N] [--per-ip N]
|
|
40
40
|
[--welcome TEXT] [--starter TEXT] [--chips "a|b|c"] [--sponsor on|off] [--rotate] # public /p/ link, no account needed
|
|
41
|
+
ghosty agents embed enable|disable <id> [--domains a.com,*.b.com] # web widget: chat bubble on your site, only on those domains
|
|
42
|
+
ghosty agents embed <id> [--per-visitor N] [--per-ip N] [--welcome TEXT] [--color #hex] [--rotate] # state + the <script> to paste
|
|
41
43
|
ghosty agents doctor <id> [--verbose] # exits 1 when it finds a problem
|
|
42
44
|
ghosty agents docs ls|grant|revoke <id> [doc] [--write] [--no-deliver]
|
|
43
45
|
ghosty agents docs add <id> --url URL | --file PATH [--description TEXT] [--grant read|write|no]
|
|
@@ -103,6 +105,19 @@ Every command has its own help with examples: `ghosty <command> --help` or `ghos
|
|
|
103
105
|
|
|
104
106
|
Exit codes: `0` ok · `1` API error · `2` usage error · `3` not signed in.
|
|
105
107
|
|
|
108
|
+
### Changes in 0.51.0
|
|
109
|
+
|
|
110
|
+
- `agents embed enable|disable <id> [--domains a.com,*.b.com] [--per-visitor N] [--per-ip N] [--welcome T] [--color #hex] [--rotate]`: the **web widget**, a chat bubble with the agent on your own site (one `<script>`), loading only on the domains you list; each visitor gets an isolated conversation and talks as a customer. Claude engine only. No flags = state + the snippet.
|
|
111
|
+
|
|
112
|
+
### Changes in 0.50.0
|
|
113
|
+
|
|
114
|
+
- `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.
|
|
115
|
+
- `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).
|
|
116
|
+
- `channels whatsapp integrations` shows who answered each number last; `--orphans` and `channels messenger ls [--orphans]` list numbers/pages nobody answers today that had turns.
|
|
117
|
+
- `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).
|
|
118
|
+
- `audit ls [--workspace] [--agent] [--actor] [--action] [--since]`: the audit log. The agent page's Save and channel changes are logged now.
|
|
119
|
+
- `factory runs ls|show|rename` (a workspace's Software Factory runs) and `agents ls --handle build`.
|
|
120
|
+
|
|
106
121
|
### Changes in 0.49.0
|
|
107
122
|
|
|
108
123
|
- `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,41 @@ 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"],
|
|
8168
|
+
// — Widget web (`agents embed`)
|
|
8169
|
+
["s\xF3lo el due\xF1o del agente maneja su widget web", "only the agent's owner manages its web widget"],
|
|
8170
|
+
["dominio no v\xE1lido: {} (usa ejemplo.com o *.ejemplo.com, sin https:// ni rutas)", "invalid domain: {} (use example.com or *.example.com, no https:// or paths)"],
|
|
8171
|
+
["el widget web s\xF3lo corre con motor claude (es el que a\xEDsla a cada visitante)", "the web widget only runs on the claude engine (it's the one that isolates each visitor)"],
|
|
8172
|
+
["pon al menos un dominio donde va a vivir el widget (--domains ejemplo.com)", "add at least one domain where the widget will live (--domains example.com)"]
|
|
8139
8173
|
];
|
|
8140
8174
|
|
|
8141
8175
|
// src/i18n.ts
|
|
@@ -8434,7 +8468,7 @@ async function postLong(path, body, timeoutMs = 9e5) {
|
|
|
8434
8468
|
const req = httpsRequest(`${base}${path}`, {
|
|
8435
8469
|
method: "POST",
|
|
8436
8470
|
timeout: timeoutMs,
|
|
8437
|
-
headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.
|
|
8471
|
+
headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.51.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
|
|
8438
8472
|
}, (res) => {
|
|
8439
8473
|
let buf = "";
|
|
8440
8474
|
res.on("data", (c) => buf += c);
|
|
@@ -8476,7 +8510,7 @@ async function request(path, opts = {}, retried = false) {
|
|
|
8476
8510
|
method: opts.method ?? (opts.body !== void 0 || opts.raw !== void 0 ? "POST" : "GET"),
|
|
8477
8511
|
headers: {
|
|
8478
8512
|
authorization: `Bearer ${token}`,
|
|
8479
|
-
"user-agent": `ghosty-cli/${"0.
|
|
8513
|
+
"user-agent": `ghosty-cli/${"0.51.0"}`,
|
|
8480
8514
|
"accept-language": lang(),
|
|
8481
8515
|
...opts.body !== void 0 ? { "content-type": "application/json" } : {},
|
|
8482
8516
|
...opts.headers
|
|
@@ -8684,6 +8718,52 @@ ${dim(t(`Report: ${file} Failed turns: ghosty turns ls ${id} --errors --since
|
|
|
8684
8718
|
if (failed) process.exitCode = 1;
|
|
8685
8719
|
}
|
|
8686
8720
|
|
|
8721
|
+
// src/diff.ts
|
|
8722
|
+
function lineDiff(before, after, context = 2) {
|
|
8723
|
+
const a = before.split("\n");
|
|
8724
|
+
const b = after.split("\n");
|
|
8725
|
+
let start = 0;
|
|
8726
|
+
while (start < a.length && start < b.length && a[start] === b[start]) start++;
|
|
8727
|
+
let endA = a.length;
|
|
8728
|
+
let endB = b.length;
|
|
8729
|
+
while (endA > start && endB > start && a[endA - 1] === b[endB - 1]) {
|
|
8730
|
+
endA--;
|
|
8731
|
+
endB--;
|
|
8732
|
+
}
|
|
8733
|
+
const midA = a.slice(start, endA);
|
|
8734
|
+
const midB = b.slice(start, endB);
|
|
8735
|
+
const n = midA.length;
|
|
8736
|
+
const m = midB.length;
|
|
8737
|
+
const dp = Array.from({ length: n + 1 }, () => new Uint32Array(m + 1));
|
|
8738
|
+
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]);
|
|
8739
|
+
const ops = a.slice(0, start).map((text, i2) => ({ kind: " ", text, at: i2 + 1 }));
|
|
8740
|
+
let i = 0;
|
|
8741
|
+
let j = 0;
|
|
8742
|
+
while (i < n || j < m) {
|
|
8743
|
+
if (i < n && j < m && midA[i] === midB[j]) {
|
|
8744
|
+
ops.push({ kind: " ", text: midA[i], at: start + i + 1 });
|
|
8745
|
+
i++;
|
|
8746
|
+
j++;
|
|
8747
|
+
} else if (i < n && (j >= m || dp[i + 1][j] >= dp[i][j + 1])) {
|
|
8748
|
+
ops.push({ kind: "-", text: midA[i], at: start + i + 1 });
|
|
8749
|
+
i++;
|
|
8750
|
+
} else ops.push({ kind: "+", text: midB[j++], at: start + i + 1 });
|
|
8751
|
+
}
|
|
8752
|
+
for (let k = endA; k < a.length; k++) ops.push({ kind: " ", text: a[k], at: k + 1 });
|
|
8753
|
+
const keep = /* @__PURE__ */ new Set();
|
|
8754
|
+
ops.forEach((o, k) => {
|
|
8755
|
+
if (o.kind !== " ") for (let x = Math.max(0, k - context); x <= Math.min(ops.length - 1, k + context); x++) keep.add(x);
|
|
8756
|
+
});
|
|
8757
|
+
const lines = [];
|
|
8758
|
+
let last = -2;
|
|
8759
|
+
for (const k of [...keep].sort((x, y) => x - y)) {
|
|
8760
|
+
if (k !== last + 1) lines.push(`@@ ${ops[k].at}`);
|
|
8761
|
+
lines.push(`${ops[k].kind} ${ops[k].text}`);
|
|
8762
|
+
last = k;
|
|
8763
|
+
}
|
|
8764
|
+
return lines.join("\n");
|
|
8765
|
+
}
|
|
8766
|
+
|
|
8687
8767
|
// src/commands/agents.ts
|
|
8688
8768
|
var enc2 = encodeURIComponent;
|
|
8689
8769
|
var agentPath = (id, rest = "") => `/api/v2/agents/${enc2(id)}${rest}`;
|
|
@@ -8830,6 +8910,25 @@ ${dim(t("Dry run: nothing changed.", "Simulacro: no se cambi\xF3 nada."))}`);
|
|
|
8830
8910
|
}
|
|
8831
8911
|
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
8912
|
}
|
|
8913
|
+
if (v["prompt-patch"] || v["prompt-append"]) {
|
|
8914
|
+
const body2 = {};
|
|
8915
|
+
if (v["prompt-patch"]) {
|
|
8916
|
+
try {
|
|
8917
|
+
body2.promptPatch = JSON.parse(await readFile3(v["prompt-patch"], "utf8"));
|
|
8918
|
+
} catch (e) {
|
|
8919
|
+
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") + ")");
|
|
8920
|
+
}
|
|
8921
|
+
}
|
|
8922
|
+
if (v["prompt-append"]) body2.promptAppend = await readFile3(v["prompt-append"], "utf8");
|
|
8923
|
+
if (v["dry-run"]) {
|
|
8924
|
+
const r3 = await api(agentPath(id), { method: "PATCH", body: { ...body2, dryRun: true } });
|
|
8925
|
+
const diff = lineDiff(r3.before, r3.after);
|
|
8926
|
+
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")}
|
|
8927
|
+
${dim(t(`${r3.beforeChars} \u2192 ${r3.chars} chars. Dry run: nothing changed.`, `${r3.beforeChars} \u2192 ${r3.chars} caracteres. Simulacro: no se cambi\xF3 nada.`))}`);
|
|
8928
|
+
}
|
|
8929
|
+
const r2 = await api(agentPath(id), { method: "PATCH", body: body2 });
|
|
8930
|
+
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.`)}`);
|
|
8931
|
+
}
|
|
8833
8932
|
if (v.engine && v["dry-run"]) {
|
|
8834
8933
|
const p = await api(agentPath(id), { method: "PATCH", body: { engine: v.engine, ...v.model ? { model: v.model } : {}, dryRun: true } });
|
|
8835
8934
|
return out(p, () => [
|
|
@@ -9111,10 +9210,10 @@ async function skillsCmd(sub, id, slug, v) {
|
|
|
9111
9210
|
case "ls":
|
|
9112
9211
|
case "list": {
|
|
9113
9212
|
const r = await api(agentPath(id, "/skills"));
|
|
9114
|
-
const
|
|
9213
|
+
const short3 = (d) => (d ?? "").replace(/\s+/g, " ").slice(0, 70);
|
|
9115
9214
|
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:
|
|
9215
|
+
...r.skills.map((s) => ({ slug: s.slug, source: "own", on: "on", description: short3(s.description) })),
|
|
9216
|
+
...(r.house ?? []).map((s) => ({ slug: s.slug, source: "ghosty", on: s.enabled ? "on" : "off", description: short3(s.description) }))
|
|
9118
9217
|
];
|
|
9119
9218
|
const more = r.community.filter((c) => !c.installed).length;
|
|
9120
9219
|
out(r, () => `${table(rows, ["slug", "source", "on", "description"])}
|
|
@@ -9245,6 +9344,33 @@ async function agentsDemo(id, v) {
|
|
|
9245
9344
|
].join("\n")
|
|
9246
9345
|
);
|
|
9247
9346
|
}
|
|
9347
|
+
async function agentsEmbed(id, v) {
|
|
9348
|
+
id = need(id, "agent id", "ghosty agents embed enable <agent-id> --domains tusitio.com");
|
|
9349
|
+
if (v.on && v.off) throw usage(t("Use enable or disable, not both.", "Usa enable o disable, no los dos."));
|
|
9350
|
+
const body = {};
|
|
9351
|
+
if (v.on) body.enabled = true;
|
|
9352
|
+
if (v.off) body.enabled = false;
|
|
9353
|
+
if (v.domains !== void 0) body.domains = v.domains.split(/[\s,]+/).map((d) => d.trim()).filter(Boolean);
|
|
9354
|
+
if (v["per-visitor"]) body.perVisitorDay = Number(v["per-visitor"]);
|
|
9355
|
+
if (v["per-ip"]) body.perIpDay = Number(v["per-ip"]);
|
|
9356
|
+
if (v.welcome !== void 0) body.welcome = v.welcome || null;
|
|
9357
|
+
if (v.color !== void 0) body.color = v.color;
|
|
9358
|
+
if (v.rotate) body.rotate = true;
|
|
9359
|
+
const e = Object.keys(body).length ? await api(agentPath(id, "/embed"), { method: "PUT", body }) : await api(agentPath(id, "/embed"));
|
|
9360
|
+
out(
|
|
9361
|
+
e,
|
|
9362
|
+
() => [
|
|
9363
|
+
`${e.enabled ? lavender(t("\u25CF on", "\u25CF encendido")) : dim(t("\u25CB off", "\u25CB apagado"))} ${t("web widget", "widget web")}`,
|
|
9364
|
+
e.domains.length ? `${t("domains", "dominios")}: ${e.domains.join(", ")}` : red(t("domains: (none \u2014 it loads nowhere; add --domains yoursite.com)", "dominios: (ninguno \u2014 no carga en ning\xFAn lado; agrega --domains tusitio.com)")),
|
|
9365
|
+
...e.engineOk ? [] : [red(t("engine: only claude isolates each visitor; switch with `ghosty agents set <id> --engine claude`", "motor: s\xF3lo claude a\xEDsla a cada visitante; c\xE1mbialo con `ghosty agents set <id> --engine claude`"))],
|
|
9366
|
+
dim(t(`cap ${e.perVisitorDay} messages per visitor per day \xB7 ${e.perIpDay} per IP per day \xB7 color ${e.color}`, `tope ${e.perVisitorDay} mensajes por visitante al d\xEDa \xB7 ${e.perIpDay} por IP al d\xEDa \xB7 color ${e.color}`)),
|
|
9367
|
+
e.welcome ? `${t("welcome", "bienvenida")}: ${e.welcome}` : dim(t("welcome: (default greeting)", "bienvenida: (saludo por defecto)")),
|
|
9368
|
+
...e.snippet ? ["", t("Paste before </body> on your site:", "Pega antes de </body> en tu sitio:"), e.snippet, ""] : [],
|
|
9369
|
+
t(`visitors ${e.stats.visitantes} \xB7 messages ${e.stats.mensajes}`, `visitantes ${e.stats.visitantes} \xB7 mensajes ${e.stats.mensajes}`),
|
|
9370
|
+
...e.lastError ? [red(`${t("last failure", "\xFAltima falla")} ${e.lastError.at}: ${serverText(e.lastError.message ?? e.lastError.error ?? "")}`)] : []
|
|
9371
|
+
].join("\n")
|
|
9372
|
+
);
|
|
9373
|
+
}
|
|
9248
9374
|
async function agentsBox(id, v) {
|
|
9249
9375
|
id = need(id, "agent id", "ghosty agents box <agent-id> [--check PATH] [--contains PATH=TEXT] [--recycle]");
|
|
9250
9376
|
if (v.recycle) {
|
|
@@ -9856,7 +9982,7 @@ async function call(path, v, init = {}) {
|
|
|
9856
9982
|
"x-ghosty-ts": ts,
|
|
9857
9983
|
"x-ghosty-sig": sig,
|
|
9858
9984
|
"content-type": "application/json",
|
|
9859
|
-
"user-agent": `ghosty-cli/${"0.
|
|
9985
|
+
"user-agent": `ghosty-cli/${"0.51.0"}`
|
|
9860
9986
|
},
|
|
9861
9987
|
body: method === "GET" ? void 0 : raw
|
|
9862
9988
|
});
|
|
@@ -10916,10 +11042,16 @@ function find2(nums, ref) {
|
|
|
10916
11042
|
return hit;
|
|
10917
11043
|
}
|
|
10918
11044
|
async function channelsCmd(kind, sub, id, ref, v) {
|
|
11045
|
+
if (kind === "messenger" || kind === "fb") {
|
|
11046
|
+
if (!id) throw usage(t("Missing agent id.", "Falta el id del agente."), t("Example", "Ejemplo") + ": ghosty channels messenger ls <agent-id> [--orphans]");
|
|
11047
|
+
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]");
|
|
11048
|
+
return linksCmd(id, "messenger", v.orphans);
|
|
11049
|
+
}
|
|
10919
11050
|
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
11051
|
if (!id) throw usage(t("Missing agent id.", "Falta el id del agente."), t("Example", "Ejemplo") + ": ghosty channels whatsapp ls <agent-id>");
|
|
10921
11052
|
if (sub === "link" || sub === "unlink") return linkCmd(sub, id, v);
|
|
10922
11053
|
if (sub === "history") return historyCmd(id, v);
|
|
11054
|
+
if (sub === "integrations" && v.orphans) return linksCmd(id, "waba", true);
|
|
10923
11055
|
if (sub === "integrations") return integrationsCmd(id, v.email);
|
|
10924
11056
|
if (sub === "import" || sub === "status" || sub === "pause" || sub === "pair") return waCmd2(sub, id, v);
|
|
10925
11057
|
if (sub === "enable" && !v["dry-run"]) requireYesIfHeadless(v.yes, `ghosty channels whatsapp enable ${id} ${ref ?? "<number>"}`);
|
|
@@ -11150,6 +11282,19 @@ async function pageWithRetry(fetchPage, where, waitMs = (n) => 5e3 * n, tries =
|
|
|
11150
11282
|
}
|
|
11151
11283
|
}
|
|
11152
11284
|
}
|
|
11285
|
+
var lastCell = (l) => l ? `${l.agentName ?? l.agentId} ${dim(l.at.slice(0, 16).replace("T", " "))}` : dim("\u2014");
|
|
11286
|
+
async function linksCmd(id, kind, orphans) {
|
|
11287
|
+
const r = await api(`/api/v2/agents/${enc7(id)}/channels/links?kind=${kind}${orphans ? "&orphans=1" : ""}`);
|
|
11288
|
+
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"));
|
|
11289
|
+
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 }));
|
|
11290
|
+
const first = r.links.find((l) => l.nobody && l.lastAnswer);
|
|
11291
|
+
const hint = first ? `
|
|
11292
|
+
${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)`)}`)}` : "";
|
|
11293
|
+
return out(
|
|
11294
|
+
r,
|
|
11295
|
+
() => 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."))
|
|
11296
|
+
);
|
|
11297
|
+
}
|
|
11153
11298
|
async function integrationsCmd(id, email) {
|
|
11154
11299
|
const r = await api(`/api/v2/agents/${enc7(id)}/whatsapp/integrations${email ? `?email=${enc7(email)}` : ""}`);
|
|
11155
11300
|
const who2 = { gs: "gs", easybits: "EasyBits", droplet: "droplet", formmy: "Formmy", other: t("other", "otro") };
|
|
@@ -11159,11 +11304,12 @@ async function integrationsCmd(id, email) {
|
|
|
11159
11304
|
"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
11305
|
"formmy forwards": i.forwards ? t("yes", "s\xED") : dim("no"),
|
|
11161
11306
|
"formmy agent": i.agent ?? "",
|
|
11307
|
+
"last answered by": lastCell(i.lastAnswer),
|
|
11162
11308
|
integration: i.integrationId
|
|
11163
11309
|
}));
|
|
11164
11310
|
return out(
|
|
11165
11311
|
r,
|
|
11166
|
-
() => rows.length ? `${table(rows, ["number", "name", "answered by", "formmy forwards", "formmy agent", "integration"])}
|
|
11312
|
+
() => rows.length ? `${table(rows, ["number", "name", "answered by", "last answered by", "formmy forwards", "formmy agent", "integration"])}
|
|
11167
11313
|
${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
11314
|
);
|
|
11169
11315
|
}
|
|
@@ -11231,6 +11377,31 @@ ${t(`Cards appear on the team's board (no "new lead" alerts).`, "Las tarjetas ap
|
|
|
11231
11377
|
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
11378
|
}
|
|
11233
11379
|
|
|
11380
|
+
// src/cards.ts
|
|
11381
|
+
var PAUSE_MODES = ["30min", "2h", "24h", "until_tomorrow", "permanent"];
|
|
11382
|
+
function pauseMode(raw) {
|
|
11383
|
+
const m = (raw ?? "24h").trim();
|
|
11384
|
+
if (!PAUSE_MODES.includes(m)) throw new Error(t(`--mode takes ${PAUSE_MODES.join(" | ")}.`, `--mode acepta ${PAUSE_MODES.join(" | ")}.`));
|
|
11385
|
+
return m;
|
|
11386
|
+
}
|
|
11387
|
+
function flagLabel(flag) {
|
|
11388
|
+
const labels = {
|
|
11389
|
+
paused: ["paused", "en pausa"],
|
|
11390
|
+
escalated: ["escalated (operator only)", "escalada (s\xF3lo operador)"],
|
|
11391
|
+
window_closed: ["outside WhatsApp's 24 h window", "fuera de la ventana de 24 h"],
|
|
11392
|
+
reaction: ["only a reaction", "s\xF3lo una reacci\xF3n"],
|
|
11393
|
+
not_whatsapp: ["not WhatsApp Business", "no es WhatsApp Business"],
|
|
11394
|
+
no_agent: ["number answered by nobody", "n\xFAmero sin agente"]
|
|
11395
|
+
};
|
|
11396
|
+
const l = labels[flag];
|
|
11397
|
+
return l ? t(l[0], l[1]) : flag;
|
|
11398
|
+
}
|
|
11399
|
+
function pauseLabel(p) {
|
|
11400
|
+
if (p.state === "escalated") return t("escalated (operator only)", "escalada (s\xF3lo operador)");
|
|
11401
|
+
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", " ")}`);
|
|
11402
|
+
return t("active (the agent answers)", "activa (el agente contesta)");
|
|
11403
|
+
}
|
|
11404
|
+
|
|
11234
11405
|
// src/commands/board.ts
|
|
11235
11406
|
var who = (role) => ({ user: t("customer", "cliente"), assistant: t("agent", "agente"), operator: t("operator", "operador") })[role] ?? role;
|
|
11236
11407
|
async function boardLs(id, v) {
|
|
@@ -11254,7 +11425,79 @@ async function boardShow(id, ref, v) {
|
|
|
11254
11425
|
};
|
|
11255
11426
|
out(r, () => [head, ...r.messages.map(line2)].join("\n"));
|
|
11256
11427
|
}
|
|
11428
|
+
var cardName = (c) => `${c.folio ? `#${c.folio} ` : ""}${c.name ?? `+${c.phone}`}`;
|
|
11429
|
+
var cardPath = (id, ref, tail = "") => `/api/v2/agents/${encodeURIComponent(id)}/board/conversations/${encodeURIComponent(ref)}${tail}`;
|
|
11430
|
+
async function cardPause(sub, id, ref, v) {
|
|
11431
|
+
if (!ref) throw usage(t("Missing card.", "Falta la tarjeta."), t("Example", "Ejemplo") + `: ghosty cards ${sub} ${id} "#12"${sub === "pause" ? " --mode 2h" : ""}`);
|
|
11432
|
+
let mode = "resume";
|
|
11433
|
+
if (sub === "pause") {
|
|
11434
|
+
try {
|
|
11435
|
+
mode = pauseMode(v.mode);
|
|
11436
|
+
} catch (e) {
|
|
11437
|
+
throw usage(e.message);
|
|
11438
|
+
}
|
|
11439
|
+
}
|
|
11440
|
+
const r = await api(cardPath(id, ref, "/pause"), { body: { mode } });
|
|
11441
|
+
out(r, () => `${lavender("\u2713")} ${bold(cardName(r.card))}: ${pauseLabel(r.before)} \u2192 ${pauseLabel(r.after)}${sub === "resume" ? `
|
|
11442
|
+
${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)}`))}` : ""}`);
|
|
11443
|
+
}
|
|
11444
|
+
async function cardReply(id, ref, v) {
|
|
11445
|
+
if (!ref) throw usage(t("Missing card.", "Falta la tarjeta."), t("Example", "Ejemplo") + `: ghosty cards reply ${id} "#12" [--directive "ofr\xE9cele el env\xEDo"]`);
|
|
11446
|
+
const retry = `ghosty cards reply ${id} ${JSON.stringify(ref)}${v.directive ? ` --directive ${JSON.stringify(v.directive)}` : ""}`;
|
|
11447
|
+
requireYesIfHeadless(v.yes, retry);
|
|
11448
|
+
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 });
|
|
11449
|
+
const r = await api(cardPath(id, ref, "/reply"), { body: v.directive ? { directive: v.directive } : {} });
|
|
11450
|
+
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)")) : ""}`);
|
|
11451
|
+
}
|
|
11452
|
+
async function cardPending(id, v) {
|
|
11453
|
+
const retry = `ghosty cards pending ${id}${v.since ? ` --since ${v.since}` : ""} --reply`;
|
|
11454
|
+
if (v.reply && !v["dry-run"]) requireYesIfHeadless(v.yes, retry);
|
|
11455
|
+
const qs = new URLSearchParams({ ...v.since ? { since: v.since } : {}, ...v.limit ? { limit: v.limit } : {} });
|
|
11456
|
+
const r = await api(`/api/v2/agents/${encodeURIComponent(id)}/board/pending?${qs}`);
|
|
11457
|
+
const go = r.cards.filter((c) => c.flags.length === 0);
|
|
11458
|
+
const rows = r.cards.map((c) => ({
|
|
11459
|
+
card: c.folio ? `#${c.folio}` : c.id.slice(0, 8),
|
|
11460
|
+
name: c.name ?? "",
|
|
11461
|
+
phone: c.phone,
|
|
11462
|
+
msgs: c.pendingMessages,
|
|
11463
|
+
since: c.lastUserAt.slice(5, 16).replace("T", " "),
|
|
11464
|
+
last: c.lastText.replace(/\s+/g, " ").slice(0, 40),
|
|
11465
|
+
status: c.flags.length ? dim(c.flags.map(flagLabel).join(", ")) : lavender(t("answer", "contestar"))
|
|
11466
|
+
}));
|
|
11467
|
+
const summary = dim(t(
|
|
11468
|
+
`\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)` : ""}.`,
|
|
11469
|
+
`\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)` : ""}.`
|
|
11470
|
+
));
|
|
11471
|
+
if (!v.reply || v["dry-run"] || !go.length) {
|
|
11472
|
+
return out({ ...r, ...v.reply ? { dryRun: true, wouldReply: go.map((c) => c.id) } : {} }, () => `${rows.length ? `${table(rows, ["card", "name", "phone", "msgs", "since", "last", "status"])}
|
|
11473
|
+
` : ""}${summary}${v.reply && go.length ? `
|
|
11474
|
+
${dim(t("Dry run: nothing was sent.", "Simulacro: no se mand\xF3 nada."))}` : go.length ? `
|
|
11475
|
+
${dim(t(`Answer them: ${retry}`, `Contestarlas: ${retry}`))}` : ""}`);
|
|
11476
|
+
}
|
|
11477
|
+
if (!globals.json) info(table(rows.filter((_, k) => r.cards[k].flags.length === 0), ["card", "name", "phone", "msgs", "since", "last"]));
|
|
11478
|
+
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 });
|
|
11479
|
+
const done = [];
|
|
11480
|
+
for (const c of go) {
|
|
11481
|
+
try {
|
|
11482
|
+
await api(cardPath(id, c.id, "/reply"), { body: v.directive ? { directive: v.directive } : {} });
|
|
11483
|
+
done.push({ id: c.id, ok: true });
|
|
11484
|
+
if (!globals.json) info(`${lavender("\u2713")} ${cardName(c)}`);
|
|
11485
|
+
} catch (e) {
|
|
11486
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
11487
|
+
done.push({ id: c.id, ok: false, error: msg });
|
|
11488
|
+
if (!globals.json) info(`\u2717 ${cardName(c)}: ${serverText(msg)}`);
|
|
11489
|
+
}
|
|
11490
|
+
}
|
|
11491
|
+
const ok = done.filter((d) => d.ok).length;
|
|
11492
|
+
out({ board: r.board, replied: done }, () => `${lavender("\u2713")} ${t(`Answered ${ok} of ${go.length}.`, `Se contestaron ${ok} de ${go.length}.`)}`);
|
|
11493
|
+
}
|
|
11257
11494
|
async function boardCmd(sub, id, v, ref) {
|
|
11495
|
+
if (sub === "pause" || sub === "resume" || sub === "reply" || sub === "pending") {
|
|
11496
|
+
if (!id) throw usage(t("Missing agent id.", "Falta el id del agente."), t("Example", "Ejemplo") + `: ghosty cards ${sub} <agent-id>${sub === "pending" ? "" : ' "#12"'}`);
|
|
11497
|
+
if (sub === "reply") return cardReply(id, ref, v);
|
|
11498
|
+
if (sub === "pending") return cardPending(id, v);
|
|
11499
|
+
return cardPause(sub, id, ref, v);
|
|
11500
|
+
}
|
|
11258
11501
|
if (sub === "ls" || sub === "list") {
|
|
11259
11502
|
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
11503
|
return boardLs(id, v);
|
|
@@ -11263,7 +11506,7 @@ async function boardCmd(sub, id, v, ref) {
|
|
|
11263
11506
|
if (!id) throw usage(t("Missing agent id.", "Falta el id del agente."), t("Example", "Ejemplo") + ': ghosty board show <agent-id> "#12"');
|
|
11264
11507
|
return boardShow(id, ref, v);
|
|
11265
11508
|
}
|
|
11266
|
-
if (sub !== "archive") throw usage(t(`Unknown board subcommand: ${sub ?? "(none)"}.`, `Subcomando de board desconocido: ${sub ?? "(ninguno)"}.`), t("Try", "Prueba") + ": ghosty
|
|
11509
|
+
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
11510
|
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
11511
|
const filter = { ...v.integration ? { integration: v.integration } : {}, ...v.column ? { column: v.column } : {}, ...v.before ? { before: v.before } : {} };
|
|
11269
11512
|
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,13 +11587,17 @@ var HELPS_ES = {
|
|
|
11344
11587
|
summary: "Crea, revisa, cambia, diagnostica y borra agentes.",
|
|
11345
11588
|
replace: [
|
|
11346
11589
|
["# the agent as a file; see ghosty apply --help", "# el agente como archivo; ver ghosty apply --help"],
|
|
11590
|
+
["# 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)"],
|
|
11591
|
+
["# change one piece of the prompt, on the server's current one", "# cambia un pedazo del prompt, sobre el actual del servidor"],
|
|
11347
11592
|
["# your account: merge copies with the same content", "# tu cuenta: une las copias con el mismo contenido"],
|
|
11348
11593
|
["# staff: their agents, latest turns, plan and boxes", "# staff: sus agentes, \xFAltimos turnos, plan y cajas"],
|
|
11349
11594
|
["# what it really has: effective toolsets, connectors connected + granted", "# lo que de verdad tiene: toolsets efectivos, conectores conectados + concedidos"],
|
|
11350
11595
|
["# Teams workspaces you administer: where it answers, is paused, and with which @handle", "# los equipos de Teams que administras: d\xF3nde contesta, d\xF3nde est\xE1 en pausa y con qu\xE9 @handle"],
|
|
11351
11596
|
["# turn it on there (or change its @handle)", "# lo prende ah\xED (o le cambia el @handle)"],
|
|
11352
11597
|
["# pause it there; it keeps its @handle", "# lo pausa ah\xED; conserva su @handle"],
|
|
11353
|
-
["# remove it from there; the agent stays", "# lo quita de ah\xED; el agente sigue"]
|
|
11598
|
+
["# remove it from there; the agent stays", "# lo quita de ah\xED; el agente sigue"],
|
|
11599
|
+
["# web widget: chat bubble on your site, only on those domains", "# widget web: burbuja de chat en tu sitio, s\xF3lo en esos dominios"],
|
|
11600
|
+
["# state + the <script> to paste", "# su estado + el <script> para pegar"]
|
|
11354
11601
|
],
|
|
11355
11602
|
flags: [
|
|
11356
11603
|
["--fields a,b", "get: regresa s\xF3lo estos campos"],
|
|
@@ -11365,6 +11612,8 @@ var HELPS_ES = {
|
|
|
11365
11612
|
["--toolsets LIST", "set: grupos de tools de gs que recibe (crm, sales\u2026): una lista reemplaza; +x / -x agrega o quita"],
|
|
11366
11613
|
["--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
11614
|
["--prompt-overlay FILE", 'set: JSON {"edits":[{"what","find","replace","regex"?,"all"?}]}, aplicados en orden; cada uno debe encontrar su lugar o no cambia nada'],
|
|
11615
|
+
["--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'],
|
|
11616
|
+
["--prompt-append FILE", "set: agrega ese texto al final del prompt (con --prompt-patch tambi\xE9n: primero las ediciones, luego el texto)"],
|
|
11368
11617
|
["--voice E:ID", "set: voz de las notas de voz: elevenlabs:<voiceId> o kokoro:em_santa|em_alex|ef_dora"],
|
|
11369
11618
|
["--voice-replies M", "set: auto = contesta con nota de voz cuando el cliente manda o pide audio; off = siempre texto"],
|
|
11370
11619
|
["--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 +11628,7 @@ var HELPS_ES = {
|
|
|
11379
11628
|
["--grant read|write|no", "docs add: acceso de este agente (default read; no = s\xF3lo subirlo a la cuenta)"],
|
|
11380
11629
|
["--note TEXT", "docs replace/restore: por qu\xE9 cambi\xF3 (sale en docs history)"],
|
|
11381
11630
|
["--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"],
|
|
11631
|
+
["--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
11632
|
["--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
11633
|
["--yes", "rm: se salta la confirmaci\xF3n (obligatorio sin terminal)"],
|
|
11385
11634
|
["--slug NAME", "demo: nombre de la liga; se le agrega un c\xF3digo de 6 caracteres (/p/NAME-CODE)"],
|
|
@@ -11575,6 +11824,7 @@ var HELPS_ES = {
|
|
|
11575
11824
|
["--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
11825
|
["--integration ID", "link/unlink/history: el id de la Integration del n\xFAmero de WhatsApp Business en Formmy"],
|
|
11577
11826
|
["--email E", "integrations (staff): lista los n\xFAmeros de otra cuenta de Formmy"],
|
|
11827
|
+
["--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
11828
|
["--days N", "history: conversaciones activas en los \xFAltimos N d\xEDas (1-30, default 7)"],
|
|
11579
11829
|
["--media", "history: s\xF3lo re-copia los archivos que faltan (fotos, PDF, audios) de las conversaciones ya importadas"],
|
|
11580
11830
|
["--answer on|off", "link (obligatorio, sin default): on = este agente contesta a clientes reales; off = s\xF3lo registra"],
|
|
@@ -11592,7 +11842,7 @@ var HELPS_ES = {
|
|
|
11592
11842
|
"Apagarlo sigue registrando los mensajes; nadie los contesta.",
|
|
11593
11843
|
"Los grupos son del WhatsApp PERSONAL emparejado al agente (no de WhatsApp Business). <group> es su nombre o su jid.",
|
|
11594
11844
|
"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.",
|
|
11845
|
+
"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
11846
|
"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
11847
|
"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
11848
|
]
|
|
@@ -11674,12 +11924,19 @@ var HELPS_ES = {
|
|
|
11674
11924
|
["--integration ID", "tarjetas de este canal/integraci\xF3n (p. ej. prueba)"],
|
|
11675
11925
|
["--column NAME", "tarjetas en esta columna"],
|
|
11676
11926
|
["--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)"]
|
|
11927
|
+
["-n, --dry-run", "cuenta y muestra una muestra, no cambia nada \xB7 pending --reply: lista lo que contestar\xEDa, no manda nada"],
|
|
11928
|
+
["--yes", "se salta la confirmaci\xF3n (obligatorio sin terminal)"],
|
|
11929
|
+
["--mode M", "pause: 30min | 2h | 24h (default) | until_tomorrow (9:00) | permanent (escalada: s\xF3lo operador)"],
|
|
11930
|
+
["--directive TEXT", "reply / pending --reply: una instrucci\xF3n para el agente (redacta su respuesta sigui\xE9ndola, no la repite)"],
|
|
11931
|
+
["--since T", "pending: clientes que escribieron desde T (30m, 2d o una fecha ISO; default 3d)"],
|
|
11932
|
+
["--reply", "pending: contesta, una por una, las que no traen bandera (pregunta antes)"]
|
|
11679
11933
|
],
|
|
11680
11934
|
notes: [
|
|
11681
11935
|
"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."
|
|
11936
|
+
"Archivar es reversible: cuando el cliente vuelve a escribir, la tarjeta regresa. Se necesita al menos un filtro.",
|
|
11937
|
+
"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.",
|
|
11938
|
+
"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).",
|
|
11939
|
+
"pause, resume y reply quedan en bit\xE1cora (ghosty audit ls --action card)."
|
|
11683
11940
|
]
|
|
11684
11941
|
},
|
|
11685
11942
|
usage: {
|
|
@@ -11770,6 +12027,39 @@ var HELPS_ES = {
|
|
|
11770
12027
|
"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
12028
|
"Revocar corta en el siguiente request. Asignar dos veces no hace nada (una sola activa por par)."
|
|
11772
12029
|
]
|
|
12030
|
+
},
|
|
12031
|
+
audit: {
|
|
12032
|
+
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.",
|
|
12033
|
+
flags: [
|
|
12034
|
+
["--workspace SLUG", "los eventos de ese espacio y de sus agentes (default: el de ghosty use; none = todos los tuyos)"],
|
|
12035
|
+
["--agent ID", "s\xF3lo los de ese agente (nombre o id)"],
|
|
12036
|
+
["--actor EMAIL", "s\xF3lo lo que hizo esa persona"],
|
|
12037
|
+
["--action A", "agent, channel, card, factory, workspace\u2026 (un prefijo) o una acci\xF3n exacta (agent.save)"],
|
|
12038
|
+
["--since T", "desde T (30m, 24h, 7d o una fecha ISO; default 7d)"],
|
|
12039
|
+
["--limit N", "cu\xE1ntos (default 50, m\xE1x. 1000)"],
|
|
12040
|
+
["--full", "el detalle completo de cada evento (JSON)"]
|
|
12041
|
+
],
|
|
12042
|
+
notes: [
|
|
12043
|
+
"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.",
|
|
12044
|
+
"El token gat_ de un agente no lee la bit\xE1cora."
|
|
12045
|
+
]
|
|
12046
|
+
},
|
|
12047
|
+
factory: {
|
|
12048
|
+
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.",
|
|
12049
|
+
replace: [['"Title"', '"T\xEDtulo"']],
|
|
12050
|
+
flags: [
|
|
12051
|
+
["--workspace SLUG", "el espacio (default: el de ghosty use)"],
|
|
12052
|
+
["--room R", "ls: s\xF3lo los pedidos de ese room (#slug o nombre)"],
|
|
12053
|
+
["--status S", "ls: s\xF3lo esa etapa (planning, building, checking, pr_review, merged\u2026)"],
|
|
12054
|
+
["--limit N", "ls: cu\xE1ntos (default 30)"],
|
|
12055
|
+
["--full", "show: imprime el plan vigente"],
|
|
12056
|
+
["-n, --dry-run", "rename: ense\xF1a el cambio, no cambia nada"],
|
|
12057
|
+
["--yes", "rename: se salta la confirmaci\xF3n (obligatorio sin terminal)"]
|
|
12058
|
+
],
|
|
12059
|
+
notes: [
|
|
12060
|
+
"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.",
|
|
12061
|
+
"Qui\xE9n es @plan o @build: ghosty agents ls --handle plan --workspace SLUG."
|
|
12062
|
+
]
|
|
11773
12063
|
}
|
|
11774
12064
|
};
|
|
11775
12065
|
var HELP_ALIASES = { creds: "credentials", conectores: "connectors", board: "cards", conversations: "chat", workspaces: "spaces" };
|
|
@@ -11782,12 +12072,14 @@ var HELPS = {
|
|
|
11782
12072
|
summary: "Create, inspect, change, check and delete agents.",
|
|
11783
12073
|
usage: [
|
|
11784
12074
|
"ghosty agents ls [--engine E] [--ids] [--sponsor EMAIL | --owner EMAIL]",
|
|
12075
|
+
"ghosty agents ls --handle build [--workspace WS] # who is @build in Teams (that workspace, or all of yours)",
|
|
11785
12076
|
"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
12077
|
"ghosty agents get <agent-id> [--fields name,model,prompt] [--prompt-out FILE]",
|
|
11787
12078
|
"ghosty agents export <agent-id> [--out DIR] # the agent as a file; see ghosty apply --help",
|
|
11788
12079
|
"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
12080
|
"ghosty agents set <agent-id> --engine E [--model M] --dry-run # what changing the engine does (sessions, machines, memory), without changing it",
|
|
11790
12081
|
"ghosty agents set <agent-id> --prompt-base <base-agent-id> --prompt-overlay overlay.json [--dry-run [--out FILE]] | --prompt-base none",
|
|
12082
|
+
"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
12083
|
"ghosty agents doctor <agent-id> [--verbose]",
|
|
11792
12084
|
"ghosty agents doctor --user <email> [agent-id] [--turns 8] # staff: their agents, latest turns, plan and boxes",
|
|
11793
12085
|
"ghosty agents docs ls|grant|revoke <agent-id> [doc\u2026] [--write] [--no-deliver] [--like AGENT]",
|
|
@@ -11807,6 +12099,8 @@ var HELPS = {
|
|
|
11807
12099
|
"ghosty agents box <agent-id> [--check PATH]\u2026 [--contains PATH=TEXT]\u2026 [--recycle]",
|
|
11808
12100
|
"ghosty agents demo enable|disable <agent-id>",
|
|
11809
12101
|
'ghosty agents demo <agent-id> [--slug NAME] [--expires "YYYY-MM-DDTHH:MM"|no] [--turns N] [--per-ip N] [--welcome TEXT] [--starter TEXT] [--chips "a|b|c"] [--sponsor on|off] [--rotate]',
|
|
12102
|
+
"ghosty agents embed enable|disable <agent-id> [--domains a.com,*.b.com] # web widget: chat bubble on your site, only on those domains",
|
|
12103
|
+
"ghosty agents embed <agent-id> [--domains \u2026] [--per-visitor N] [--per-ip N] [--welcome TEXT] [--color #hex] [--rotate] # state + the <script> to paste",
|
|
11810
12104
|
"ghosty agents rm <agent-id> [--from-workspace] [--yes]"
|
|
11811
12105
|
],
|
|
11812
12106
|
flags: [
|
|
@@ -11822,6 +12116,8 @@ var HELPS = {
|
|
|
11822
12116
|
["--toolsets LIST", "set: gs tool groups it gets (crm, sales\u2026): a list replaces; +x / -x add or remove"],
|
|
11823
12117
|
["--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
12118
|
["--prompt-overlay FILE", 'set: JSON {"edits":[{"what","find","replace","regex"?,"all"?}]}, applied in order; each must find its place or nothing changes'],
|
|
12119
|
+
["--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`],
|
|
12120
|
+
["--prompt-append FILE", "set: add that text at the end of the prompt (with --prompt-patch too: edits first, then the text)"],
|
|
11825
12121
|
["--voice E:ID", "set: voice for voice notes: elevenlabs:<voiceId> or kokoro:em_santa|em_alex|ef_dora"],
|
|
11826
12122
|
["--voice-replies M", "set: auto = answer with a voice note when the customer sends or asks for audio; off = always text"],
|
|
11827
12123
|
["--auto-stage on|off", "set: move board cards by itself after each customer turn (one extra model call per turn)"],
|
|
@@ -11836,7 +12132,7 @@ var HELPS = {
|
|
|
11836
12132
|
["--grant read|write|no", "docs add: access for this agent (default read; no = only upload to the account)"],
|
|
11837
12133
|
["--note TEXT", "docs replace/restore: why it changed (shown in docs history)"],
|
|
11838
12134
|
["--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"],
|
|
12135
|
+
["--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
12136
|
["--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
12137
|
["--yes", "rm: skip the confirmation (required without a terminal)"],
|
|
11842
12138
|
["--slug NAME", "demo: link name; a 6-char code is appended (/p/NAME-CODE)"],
|
|
@@ -11883,6 +12179,7 @@ var HELPS = {
|
|
|
11883
12179
|
"ghosty agents set <pia-id> --prompt-base <tania-id> --prompt-overlay capa.json --dry-run --out /tmp/pia.md",
|
|
11884
12180
|
"ghosty agents demo enable <id>",
|
|
11885
12181
|
'ghosty agents demo <id> --slug guardias --expires "2026-10-15T20:00" --starter "Ens\xE9\xF1ame c\xF3mo empezar"',
|
|
12182
|
+
'ghosty agents embed enable <id> --domains "mitienda.com,*.mitienda.mx" --welcome "\xA1Hola! \xBFEn qu\xE9 te ayudo?"',
|
|
11886
12183
|
"ghosty agents box <id> --contains /opt/claude-worker/skills/pdf-doc/render-doc.mjs=pdf-templates",
|
|
11887
12184
|
"ghosty agents set <id> --tool wa:actualizar_identidad=off,teams:actualizar_identidad=on",
|
|
11888
12185
|
'ghosty agents set minighosty --bag "MiniGhosty" --avatar https://www.ghosty.studio/avatars/minighosty.png',
|
|
@@ -12136,7 +12433,8 @@ HELPS.channels = {
|
|
|
12136
12433
|
"ghosty channels whatsapp ls <agent-id>",
|
|
12137
12434
|
"ghosty channels whatsapp enable <agent-id> <number> [--dry-run] [--yes]",
|
|
12138
12435
|
"ghosty channels whatsapp disable <agent-id> <number> [--dry-run]",
|
|
12139
|
-
"ghosty channels whatsapp integrations <agent-id> [--email OTHER]",
|
|
12436
|
+
"ghosty channels whatsapp integrations <agent-id> [--email OTHER] [--orphans]",
|
|
12437
|
+
"ghosty channels messenger ls <agent-id> [--orphans]",
|
|
12140
12438
|
"ghosty channels whatsapp link <agent-id> --integration <formmy-id> --answer on|off [--forward] [--dry-run] [--yes]",
|
|
12141
12439
|
"ghosty channels whatsapp unlink <agent-id> --integration <formmy-id> [--dry-run] [--yes]",
|
|
12142
12440
|
"ghosty channels whatsapp history <agent-id> --integration <formmy-id> [--days 7] [--media] [--dry-run] [--yes]",
|
|
@@ -12163,6 +12461,7 @@ HELPS.channels = {
|
|
|
12163
12461
|
["--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
12462
|
["--integration ID", "link/unlink/history: the WhatsApp Business number's Integration id in Formmy"],
|
|
12165
12463
|
["--email E", "integrations (staff): list the numbers of another Formmy account"],
|
|
12464
|
+
["--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
12465
|
["--days N", "history: conversations active in the last N days (1-30, default 7)"],
|
|
12167
12466
|
["--media", "history: only re-copy the missing files (photos, PDFs, audio) of conversations already imported"],
|
|
12168
12467
|
["--answer on|off", "link (required, no default): on = this agent answers real customers; off = it only records"],
|
|
@@ -12182,6 +12481,8 @@ HELPS.channels = {
|
|
|
12182
12481
|
"ghosty channels wa groups ls <id>",
|
|
12183
12482
|
'ghosty channels wa groups enable <id> "Pia-cotizadora" --role equipo --dry-run',
|
|
12184
12483
|
"ghosty channels whatsapp integrations <id>",
|
|
12484
|
+
"ghosty channels whatsapp integrations <id> --orphans",
|
|
12485
|
+
"ghosty channels messenger ls <id>",
|
|
12185
12486
|
"ghosty channels whatsapp link <id> --integration 6a3af395\u2026 --answer off --dry-run",
|
|
12186
12487
|
"ghosty channels whatsapp history <id> --integration 6a3af395\u2026 --days 7 --dry-run",
|
|
12187
12488
|
"ghosty channels whatsapp history <id> --integration 6a3af395\u2026 --days 7 --media",
|
|
@@ -12193,7 +12494,7 @@ HELPS.channels = {
|
|
|
12193
12494
|
"Disabling keeps logging messages; nobody answers them.",
|
|
12194
12495
|
"Groups belong to the PERSONAL WhatsApp paired to the agent (not WhatsApp Business). <group> is its name or its jid.",
|
|
12195
12496
|
"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.",
|
|
12497
|
+
"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
12498
|
"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
12499
|
"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
12500
|
],
|
|
@@ -12283,7 +12584,11 @@ HELPS.cards = {
|
|
|
12283
12584
|
usage: [
|
|
12284
12585
|
"ghosty cards ls <agent-id> [--q TEXT] [--paused] [--limit 20]",
|
|
12285
12586
|
"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]"
|
|
12587
|
+
"ghosty cards archive <agent-id> [--integration ID] [--column NAME] [--before 30d|ISO] [--dry-run] [--yes]",
|
|
12588
|
+
"ghosty cards pause <agent-id> <card> [--mode 30min|2h|24h|until_tomorrow|permanent]",
|
|
12589
|
+
"ghosty cards resume <agent-id> <card>",
|
|
12590
|
+
"ghosty cards reply <agent-id> <card> [--directive TEXT] [--yes]",
|
|
12591
|
+
"ghosty cards pending <agent-id> [--since 2d|ISO] [--limit N] [--reply [--directive TEXT] [--yes]] [--dry-run]"
|
|
12287
12592
|
],
|
|
12288
12593
|
flags: [
|
|
12289
12594
|
["--q TEXT", "ls: cards whose name, phone or last message contains TEXT"],
|
|
@@ -12292,11 +12597,15 @@ HELPS.cards = {
|
|
|
12292
12597
|
["--integration ID", "cards from this channel/integration (e.g. prueba)"],
|
|
12293
12598
|
["--column NAME", "cards in this column"],
|
|
12294
12599
|
["--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)"]
|
|
12600
|
+
["-n, --dry-run", "count and show a sample, change nothing \xB7 pending --reply: list what it would answer, send nothing"],
|
|
12601
|
+
["--yes", "skip the confirmation (required without a terminal)"],
|
|
12602
|
+
["--mode M", "pause: 30min | 2h | 24h (default) | until_tomorrow (9:00) | permanent (escalated: operator only)"],
|
|
12603
|
+
["--directive TEXT", "reply / pending --reply: an instruction for the agent (it writes its own answer following it, it doesn't repeat it)"],
|
|
12604
|
+
["--since T", "pending: customers who wrote since T (30m, 2d or an ISO date; default 3d)"],
|
|
12605
|
+
["--reply", "pending: answer, one by one, the ones with no flag (asks first)"]
|
|
12297
12606
|
],
|
|
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."],
|
|
12607
|
+
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"],
|
|
12608
|
+
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
12609
|
learn: `${REF}#tablero`
|
|
12301
12610
|
};
|
|
12302
12611
|
HELPS.board = HELPS.cards;
|
|
@@ -12438,6 +12747,42 @@ HELPS.staff = {
|
|
|
12438
12747
|
],
|
|
12439
12748
|
learn: `${REF}#staff-asignado`
|
|
12440
12749
|
};
|
|
12750
|
+
HELPS.audit = {
|
|
12751
|
+
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.",
|
|
12752
|
+
usage: ["ghosty audit ls [--workspace SLUG] [--agent ID] [--actor EMAIL] [--action A] [--since 7d] [--limit 50] [--full]"],
|
|
12753
|
+
flags: [
|
|
12754
|
+
["--workspace SLUG", "that workspace's events and its agents' (default: the one from ghosty use; none = all of yours)"],
|
|
12755
|
+
["--agent ID", "only that agent's events (name or id)"],
|
|
12756
|
+
["--actor EMAIL", "only what that person did"],
|
|
12757
|
+
["--action A", "agent, channel, card, factory, workspace\u2026 (a prefix) or an exact action (agent.save)"],
|
|
12758
|
+
["--since T", "since T (30m, 24h, 7d or an ISO date; default 7d)"],
|
|
12759
|
+
["--limit N", "how many (default 50, max 1000)"],
|
|
12760
|
+
["--full", "the whole detail of each event (JSON)"]
|
|
12761
|
+
],
|
|
12762
|
+
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"],
|
|
12763
|
+
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."],
|
|
12764
|
+
learn: `${REF}#bitacora`
|
|
12765
|
+
};
|
|
12766
|
+
HELPS.factory = {
|
|
12767
|
+
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.",
|
|
12768
|
+
usage: [
|
|
12769
|
+
"ghosty factory runs ls [--workspace SLUG] [--room R] [--status S] [--limit 30]",
|
|
12770
|
+
"ghosty factory runs show <run> [--workspace SLUG] [--full]",
|
|
12771
|
+
'ghosty factory runs rename <run> "Title" [--workspace SLUG] [--dry-run] [--yes]'
|
|
12772
|
+
],
|
|
12773
|
+
flags: [
|
|
12774
|
+
["--workspace SLUG", "the workspace (default: the one from ghosty use)"],
|
|
12775
|
+
["--room R", "ls: only that room's runs (#slug or name)"],
|
|
12776
|
+
["--status S", "ls: only that stage (planning, building, checking, pr_review, merged\u2026)"],
|
|
12777
|
+
["--limit N", "ls: how many (default 30)"],
|
|
12778
|
+
["--full", "show: print the current plan"],
|
|
12779
|
+
["-n, --dry-run", "rename: show the change, change nothing"],
|
|
12780
|
+
["--yes", "rename: skip the confirmation (required without a terminal)"]
|
|
12781
|
+
],
|
|
12782
|
+
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'],
|
|
12783
|
+
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."],
|
|
12784
|
+
learn: `${REF}#fabrica`
|
|
12785
|
+
};
|
|
12441
12786
|
function helpText(cmd) {
|
|
12442
12787
|
const base = HELPS[cmd];
|
|
12443
12788
|
if (!base) return void 0;
|
|
@@ -12681,6 +13026,10 @@ function normalizeArgs(pos, v) {
|
|
|
12681
13026
|
if (v.expires !== void 0) v.vence = v.expires;
|
|
12682
13027
|
else if (v.vence !== void 0) notes.push(t("`--vence` is now `--expires`.", "`--vence` ahora es `--expires`."));
|
|
12683
13028
|
}
|
|
13029
|
+
if (cmd === "agents" && (sub === "embed" || sub === "widget") && (pos[2] === "enable" || pos[2] === "disable")) {
|
|
13030
|
+
v[pos[2] === "enable" ? "on" : "off"] = true;
|
|
13031
|
+
pos.splice(2, 1);
|
|
13032
|
+
}
|
|
12684
13033
|
if (cmd === "channels" && pos[2] === "groups") {
|
|
12685
13034
|
if (pos[3] === "enable" || pos[3] === "disable") {
|
|
12686
13035
|
v[pos[3] === "enable" ? "on" : "off"] = true;
|
|
@@ -13253,6 +13602,91 @@ ${dim(t("Links with the old slug may stop working.", "Las ligas con el slug viej
|
|
|
13253
13602
|
}
|
|
13254
13603
|
}
|
|
13255
13604
|
|
|
13605
|
+
// src/commands/audit.ts
|
|
13606
|
+
function detailLine(d, max = 90) {
|
|
13607
|
+
if (d === null || d === void 0) return "";
|
|
13608
|
+
if (typeof d !== "object") return String(d).slice(0, max);
|
|
13609
|
+
const parts = Object.entries(d).filter(([k]) => k !== "via").map(([k, v]) => `${k}=${v !== null && typeof v === "object" ? JSON.stringify(v) : String(v)}`);
|
|
13610
|
+
const s = parts.join(" ");
|
|
13611
|
+
return s.length > max ? `${s.slice(0, max - 1)}\u2026` : s;
|
|
13612
|
+
}
|
|
13613
|
+
async function auditCmd(sub, v, context) {
|
|
13614
|
+
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]");
|
|
13615
|
+
const ws = v.workspace === "none" ? void 0 : v.workspace ?? (v.agent ? void 0 : context);
|
|
13616
|
+
const qs = new URLSearchParams({
|
|
13617
|
+
...ws ? { workspace: ws } : {},
|
|
13618
|
+
...v.agent ? { agent: v.agent } : {},
|
|
13619
|
+
...v.actor ? { actor: v.actor } : {},
|
|
13620
|
+
...v.action ? { action: v.action } : {},
|
|
13621
|
+
since: v.since ?? "7d",
|
|
13622
|
+
...v.limit ? { limit: v.limit } : {}
|
|
13623
|
+
});
|
|
13624
|
+
const r = await api(`/api/v2/audit?${qs}`);
|
|
13625
|
+
const rows = r.events.map((e) => ({
|
|
13626
|
+
at: e.at.slice(0, 16).replace("T", " "),
|
|
13627
|
+
actor: e.actor,
|
|
13628
|
+
action: e.action,
|
|
13629
|
+
where: [e.workspace, e.agentName ?? e.agentId].filter(Boolean).join(" \xB7 "),
|
|
13630
|
+
detail: v.full ? JSON.stringify(e.detail ?? null) : detailLine(e.detail)
|
|
13631
|
+
}));
|
|
13632
|
+
return out(r, () => rows.length ? `${table(rows, ["at", "actor", "action", "where", "detail"])}
|
|
13633
|
+
${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}` : ""}.`)));
|
|
13634
|
+
}
|
|
13635
|
+
|
|
13636
|
+
// src/commands/factory.ts
|
|
13637
|
+
var enc12 = encodeURIComponent;
|
|
13638
|
+
var short2 = (s) => (s ?? "").slice(0, 16).replace("T", " ");
|
|
13639
|
+
async function factoryCmd(sub, verb, ref, title, v, context) {
|
|
13640
|
+
if (sub !== "runs") throw usage(t(`Unknown factory subcommand: ${sub ?? "(none)"}.`, `Subcomando de factory desconocido: ${sub ?? "(ninguno)"}.`), "ghosty factory runs ls|show|rename \u2026");
|
|
13641
|
+
const ws = v.workspace ?? context;
|
|
13642
|
+
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)."));
|
|
13643
|
+
const base = `/api/v2/workspaces/${enc12(ws)}/factory/runs`;
|
|
13644
|
+
if (!verb || verb === "ls" || verb === "list") {
|
|
13645
|
+
const qs = new URLSearchParams({ ...v.room ? { room: v.room } : {}, ...v.status ? { status: v.status } : {}, ...v.limit ? { limit: v.limit } : {} });
|
|
13646
|
+
const r = await api(`${base}?${qs}`);
|
|
13647
|
+
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) }));
|
|
13648
|
+
return out(r, () => rows.length ? `${table(rows, ["run", "title", "status", "room", "pr", "updated"])}
|
|
13649
|
+
${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}.`)));
|
|
13650
|
+
}
|
|
13651
|
+
if (!ref) throw usage(t("Missing run.", "Falta el pedido."), `ghosty factory runs ${verb} <run> --workspace ${ws}`);
|
|
13652
|
+
const path = `${base}/${enc12(ref.replace(/^#/, ""))}`;
|
|
13653
|
+
if (verb === "show") {
|
|
13654
|
+
const { run: r } = await api(path);
|
|
13655
|
+
const lines = [
|
|
13656
|
+
`${bold(`#${r.id} ${r.title}`)} ${dim(`\xB7 ${r.status}${r.kind ? ` (${r.kind})` : ""} \xB7 ${r.room ? `#${r.room}` : ""}`)}`,
|
|
13657
|
+
`${t("repo", "repo")}: ${r.repo ?? "\u2014"}${r.branch ? ` (${r.branch})` : ""} PR: ${r.pr ?? "\u2014"}`,
|
|
13658
|
+
`preview: ${r.preview ?? "\u2014"}${r.previewState ? dim(` (${r.previewState})`) : ""}`,
|
|
13659
|
+
`${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}`,
|
|
13660
|
+
...r.plans.map((p) => dim(` v${p.version} ${p.decision ?? "\u2014"}: ${p.title ?? ""}`)),
|
|
13661
|
+
`${t("running", "trabajando")}: ${r.running.length ? r.running.map((x) => `@${x.role} ${dim(`(${x.turn}, ${short2(x.since)})`)}`).join(", ") : dim(t("nobody", "nadie"))}`,
|
|
13662
|
+
bold(t("Latest events", "\xDAltimos eventos")),
|
|
13663
|
+
...r.events.map((e) => ` ${dim(short2(e.at))} ${e.type}${e.actor ? ` ${dim(e.actor)}` : ""}`),
|
|
13664
|
+
...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."))]
|
|
13665
|
+
];
|
|
13666
|
+
return out(r, () => lines.join("\n"));
|
|
13667
|
+
}
|
|
13668
|
+
if (verb === "rename") {
|
|
13669
|
+
if (!title?.trim()) throw usage(t("Missing the new title.", "Falta el t\xEDtulo nuevo."), `ghosty factory runs rename ${ref} "T\xEDtulo" --workspace ${ws}`);
|
|
13670
|
+
const retry = `ghosty factory runs rename ${ref} ${JSON.stringify(title)} --workspace ${ws}`;
|
|
13671
|
+
if (!v["dry-run"]) requireYesIfHeadless(v.yes, retry);
|
|
13672
|
+
const plan = await api(path, { body: { title, dryRun: true } });
|
|
13673
|
+
const line2 = `#${ref.replace(/^#/, "")}: \xAB${plan.from}\xBB \u2192 \xAB${plan.to}\xBB`;
|
|
13674
|
+
if (v["dry-run"]) return out({ ...plan, dryRun: true }, () => `${line2}
|
|
13675
|
+
${dim(t("Dry run: nothing changed.", "Simulacro: no se cambi\xF3 nada."))}`);
|
|
13676
|
+
await confirm(t(`Rename ${line2}?`, `\xBFRenombrar ${line2}?`), { yes: v.yes, retry });
|
|
13677
|
+
const r = await api(path, { body: { title } });
|
|
13678
|
+
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)"))}`);
|
|
13679
|
+
}
|
|
13680
|
+
throw usage(t(`Unknown runs subcommand: ${verb}.`, `Subcomando de runs desconocido: ${verb}.`), 'ghosty factory runs ls | show <run> | rename <run> "title"');
|
|
13681
|
+
}
|
|
13682
|
+
async function agentsByHandle(handle, workspace) {
|
|
13683
|
+
const qs = new URLSearchParams({ handle, ...workspace ? { workspace } : {} });
|
|
13684
|
+
const r = await api(`/api/v2/teams/handles?${qs}`);
|
|
13685
|
+
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)")) }));
|
|
13686
|
+
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 ? `
|
|
13687
|
+
${dim(t(`Couldn't read: ${r.unreadable.join(", ")}`, `No se pudieron leer: ${r.unreadable.join(", ")}`))}` : ""}`);
|
|
13688
|
+
}
|
|
13689
|
+
|
|
13256
13690
|
// src/commands/staff.ts
|
|
13257
13691
|
var day2 = (iso) => iso ? iso.slice(0, 10) : "";
|
|
13258
13692
|
async function staffCmd(sub, staff, client, v) {
|
|
@@ -13292,7 +13726,7 @@ async function staffCmd(sub, staff, client, v) {
|
|
|
13292
13726
|
|
|
13293
13727
|
// src/commands/support.ts
|
|
13294
13728
|
import { readFile as readFile14 } from "node:fs/promises";
|
|
13295
|
-
var
|
|
13729
|
+
var enc13 = encodeURIComponent;
|
|
13296
13730
|
var cdmx = (iso) => new Date(Date.parse(iso) - 6 * 36e5).toISOString().slice(5, 16).replace("T", " ");
|
|
13297
13731
|
async function supportThreads(email, v) {
|
|
13298
13732
|
const q = new URLSearchParams({ user: email, since: v.since ?? "30d", ...v.surface ? { surface: v.surface } : {} });
|
|
@@ -13312,7 +13746,7 @@ ${table(rows, ["last", "agent", "conversation", "title", "kind", "listed", "turn
|
|
|
13312
13746
|
${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
13747
|
}
|
|
13314
13748
|
async function supportThread(agent, sid, v) {
|
|
13315
|
-
const r = await api(`/api/v2/support/threads/${
|
|
13749
|
+
const r = await api(`/api/v2/support/threads/${enc13(agent)}/${enc13(sid)}?tail=${enc13(v.limit ?? "40")}${v.tools ? "&tools=1" : ""}`);
|
|
13316
13750
|
out(r, () => {
|
|
13317
13751
|
if (v.tools && r.tools) {
|
|
13318
13752
|
const x = r.tools;
|
|
@@ -13344,7 +13778,7 @@ async function cleanupConversations(action, agent, v) {
|
|
|
13344
13778
|
const ex = `ghosty conversations ${action} <agent> --kind ${action === "rm" ? "test" : "test|channel"} [--before 7d] [--dry-run]`;
|
|
13345
13779
|
if (!agent) throw usage(t("Missing agent.", "Falta el agente."), ex);
|
|
13346
13780
|
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/${
|
|
13781
|
+
const path = `/api/v2/agents/${enc13(agent)}/conversations/cleanup`;
|
|
13348
13782
|
const body = { kind: v.kind, action, ...v.before ? { before: v.before } : {} };
|
|
13349
13783
|
const plan = await api(path, { method: "POST", body: { ...body, dryRun: true } });
|
|
13350
13784
|
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 +13793,7 @@ ${t("Go ahead?", "\xBFSigo?")}`, { yes: v.yes, retry: `ghosty conversations ${ac
|
|
|
13359
13793
|
out(r, () => `${lavender("\u2713")} ${t(`${r.threads} conversation(s) ${action === "rm" ? "deleted" : "archived"}.`, `${r.threads} conversaci\xF3n(es) ${action === "rm" ? "borradas" : "archivadas"}.`)}`);
|
|
13360
13794
|
}
|
|
13361
13795
|
async function turnStats(v) {
|
|
13362
|
-
const r = await api(`/api/v2/support/turn-stats?since=${
|
|
13796
|
+
const r = await api(`/api/v2/support/turn-stats?since=${enc13(v.since ?? "14d")}${v.all ? "&all=1" : ""}`);
|
|
13363
13797
|
out(
|
|
13364
13798
|
r,
|
|
13365
13799
|
() => [
|
|
@@ -13379,7 +13813,7 @@ async function fleetBoxes() {
|
|
|
13379
13813
|
${table(rows, ["template", "boxes", "on", "asleep", "ram", "vcpu", "ram on"])}`);
|
|
13380
13814
|
}
|
|
13381
13815
|
async function appAgentsOf(email, client) {
|
|
13382
|
-
const r = await api(`/api/v2/support/app-agents?user=${
|
|
13816
|
+
const r = await api(`/api/v2/support/app-agents?user=${enc13(email)}${client && client !== "all" ? `&client=${enc13(client)}` : ""}`);
|
|
13383
13817
|
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
13818
|
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
13819
|
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 +13824,7 @@ ${dim(t("No phones registered for push.", "Sin tel\xE9fonos registrados para avi
|
|
|
13390
13824
|
}
|
|
13391
13825
|
async function pushLog(email, v) {
|
|
13392
13826
|
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=${
|
|
13827
|
+
const r = await api(`/api/v2/support/push-log?user=${enc13(email)}&since=${enc13(v.since ?? "2d")}${v.limit ? `&limit=${enc13(v.limit)}` : ""}`);
|
|
13394
13828
|
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
13829
|
out(r, () => rows.length ? `${bold(r.user)}
|
|
13396
13830
|
${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 +13862,24 @@ ${dim(t(`Try it on your phone: ghosty novedades test ${r.id}`, `Pru\xE9bala en t
|
|
|
13428
13862
|
if (!id) throw usage(t("Missing id.", "Falta el id."), ex);
|
|
13429
13863
|
const b = await fields();
|
|
13430
13864
|
if (!Object.keys(b).length) throw usage(t("Nothing to change.", "Nada que cambiar."), ex);
|
|
13431
|
-
const r = await api(`${base}/${
|
|
13865
|
+
const r = await api(`${base}/${enc13(id)}`, { method: "PATCH", body: b });
|
|
13432
13866
|
return out(r, () => `${lavender("\u2713")} ${line2(r)}`);
|
|
13433
13867
|
}
|
|
13434
13868
|
case "test": {
|
|
13435
13869
|
if (!id) throw usage(t("Missing id.", "Falta el id."), ex);
|
|
13436
|
-
const r = await api(`${base}/${
|
|
13870
|
+
const r = await api(`${base}/${enc13(id)}`, { method: "POST", body: { push: "test" } });
|
|
13437
13871
|
return out(r, () => `${lavender("\u2713")} ${t(`Sent to your phone (${r.enviados} device(s)).`, `Enviada a tu tel\xE9fono (${r.enviados} dispositivo(s)).`)}`);
|
|
13438
13872
|
}
|
|
13439
13873
|
case "send":
|
|
13440
13874
|
case "schedule": {
|
|
13441
13875
|
if (!id) throw usage(t("Missing id.", "Falta el id."), ex);
|
|
13442
13876
|
if (v.at !== void 0) {
|
|
13443
|
-
const r2 = await api(`${base}/${
|
|
13877
|
+
const r2 = await api(`${base}/${enc13(id)}`, { method: "PATCH", body: { schedule: v.at || null } });
|
|
13444
13878
|
return out(r2, () => `${lavender("\u2713")} ${line2(r2)}`);
|
|
13445
13879
|
}
|
|
13446
13880
|
requireYesIfHeadless(v.yes, `ghosty novedades send ${id}`);
|
|
13447
13881
|
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}/${
|
|
13882
|
+
const r = await api(`${base}/${enc13(id)}`, { method: "POST", body: { push: "all" } });
|
|
13449
13883
|
return out(r, () => `${lavender("\u2713")} ${t(`Pushed to ${r.enviados} device(s).`, `Enviada a ${r.enviados} dispositivo(s).`)}`);
|
|
13450
13884
|
}
|
|
13451
13885
|
case "rm":
|
|
@@ -13453,7 +13887,7 @@ ${dim(t(`Try it on your phone: ghosty novedades test ${r.id}`, `Pru\xE9bala en t
|
|
|
13453
13887
|
if (!id) throw usage(t("Missing id.", "Falta el id."), ex);
|
|
13454
13888
|
requireYesIfHeadless(v.yes, `ghosty novedades rm ${id}`);
|
|
13455
13889
|
await confirm(t(`Delete announcement ${id}?`, `\xBFBorrar la novedad ${id}?`), { yes: v.yes, retry: `ghosty novedades rm ${id}` });
|
|
13456
|
-
return out(await api(`${base}/${
|
|
13890
|
+
return out(await api(`${base}/${enc13(id)}`, { method: "DELETE" }), () => `${lavender("\u2713")} ${t("Deleted.", "Borrada.")}`);
|
|
13457
13891
|
}
|
|
13458
13892
|
default:
|
|
13459
13893
|
throw usage(t(`Unknown novedades subcommand: ${sub}.`, `Subcomando de novedades desconocido: ${sub}.`), ex);
|
|
@@ -13534,9 +13968,12 @@ ${bold("Workspaces")} ${dim("(changing them: Ghosty staff)")}
|
|
|
13534
13968
|
spaces log <workspace> Its log: transfers, staff in/out, renewals (owner or staff) [--since 30d] [--action workspace]
|
|
13535
13969
|
staff ls [staff-email] [client-email] Ghosty staff assigned to client accounts (staff) [--all: also revoked]
|
|
13536
13970
|
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]
|
|
13971
|
+
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]
|
|
13972
|
+
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
13973
|
|
|
13538
13974
|
${bold("Agents")}
|
|
13539
13975
|
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)
|
|
13976
|
+
[--handle build [--workspace WS]] who answers to that @handle in Teams
|
|
13540
13977
|
[--sponsor EMAIL | --owner EMAIL] any account's agents (Ghosty staff)
|
|
13541
13978
|
agents create Create an agent --name [--engine] [--model] [--prompt | --prompt-file FILE] [--env K=V,\u2026]
|
|
13542
13979
|
[--workspace SLUG] [--channels teams=off,\u2026] born in a workspace, active in its Teams
|
|
@@ -13552,6 +13989,7 @@ ${bold("Agents")}
|
|
|
13552
13989
|
[--catalog productos.csv|json] its catalog: sku,nombre,precio[,unidad,categoria]
|
|
13553
13990
|
[--toolsets crm,sales | +crm,-sales] gs tool groups it gets
|
|
13554
13991
|
[--prompt-base AGENT|none --prompt-overlay FILE [--dry-run]] prompt = another agent's + edits, kept in sync
|
|
13992
|
+
[--prompt-patch overlay.json | --prompt-append FILE [--dry-run]] change one piece of the prompt, on the server's current one
|
|
13555
13993
|
[--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
13994
|
[--prompt-mode append|replace] replace = its prompt WITHOUT Ghosty's house persona
|
|
13557
13995
|
agents doctor <id> Check the config: overlapping skills, tools the prompt names, broken grants, failing turns
|
|
@@ -13568,6 +14006,8 @@ ${bold("Agents")}
|
|
|
13568
14006
|
agents box <id> The machine serving it: is it on the new image? [--check PATH]\u2026 [--contains PATH=TEXT]\u2026 [--recycle [--reenv] [--force]]
|
|
13569
14007
|
agents demo enable|disable <id> Turn its public demo link on or off
|
|
13570
14008
|
agents demo <id> Public demo link /p/<slug>: anyone tries it without an account, "Keep it" clones it to theirs
|
|
14009
|
+
agents embed enable|disable <id> Web widget: a chat bubble on YOUR site (one <script>), only on the domains you list [--domains a.com,*.b.com] [--per-visitor N] [--per-ip N] [--welcome T] [--color #hex] [--rotate]
|
|
14010
|
+
agents embed <id> Its state and the snippet to paste (claude engine only: it isolates each visitor)
|
|
13571
14011
|
[--slug NAME] [--expires "YYYY-MM-DDTHH:MM"|no] [--turns N] [--per-ip N] [--welcome TEXT] [--chips "a|b|c"] [--starter TEXT] [--sponsor on|off] [--rotate]
|
|
13572
14012
|
|
|
13573
14013
|
${bold("Configure an agent")}
|
|
@@ -13581,7 +14021,8 @@ ${bold("Configure an agent")}
|
|
|
13581
14021
|
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
14022
|
mcp get|set <id> MCP servers [--file servers.json] (http headers accept \${turn.token})
|
|
13583
14023
|
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)
|
|
14024
|
+
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]
|
|
14025
|
+
channels messenger ls <id> The workspace's Messenger pages: who answers now and who answered last [--orphans]
|
|
13585
14026
|
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
14027
|
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
14028
|
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 +14033,9 @@ ${bold("Configure an agent")}
|
|
|
13592
14033
|
cards ls <id> Recent cards on its sales board [--q TEXT] [--paused] [--limit 20]
|
|
13593
14034
|
cards show <id> <card> A card's thread: who wrote, text, attachments and whether gs kept a copy (folio, phone or id)
|
|
13594
14035
|
cards archive <id> Archive cards in bulk (reversible) --integration X | --column X | --before 30d [--dry-run]
|
|
14036
|
+
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]
|
|
14037
|
+
cards reply <id> <card> The agent answers what the customer left pending ("Ask for a reply") [--directive TEXT] [--yes]
|
|
14038
|
+
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
14039
|
|
|
13596
14040
|
${bold("Support")} ${dim("(Ghosty staff)")}
|
|
13597
14041
|
conversations ls --user EMAIL Someone's conversations in any agent [--since 30d] [--surface android]
|
|
@@ -13682,9 +14126,12 @@ ${bold("Espacios")} ${dim("(cambiarlos: staff de Ghosty)")}
|
|
|
13682
14126
|
spaces log <espacio> Su bit\xE1cora: traspasos, staff que entra o sale, renovaciones (due\xF1o o staff) [--since 30d] [--action workspace]
|
|
13683
14127
|
staff ls [correo-staff] [correo-cliente] Staff de Ghosty asignado a cuentas cliente (staff) [--all: tambi\xE9n revocadas]
|
|
13684
14128
|
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]
|
|
14129
|
+
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]
|
|
14130
|
+
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
14131
|
|
|
13686
14132
|
${bold("Agentes")}
|
|
13687
14133
|
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)
|
|
14134
|
+
[--handle build [--workspace WS]] qui\xE9n contesta a ese @handle en Teams
|
|
13688
14135
|
[--sponsor CORREO | --owner CORREO] agentes de cualquier cuenta (staff de Ghosty)
|
|
13689
14136
|
agents create Crea un agente --name [--engine] [--model] [--prompt | --prompt-file ARCHIVO] [--env K=V,\u2026]
|
|
13690
14137
|
[--workspace SLUG] [--channels teams=off,\u2026] nace en un espacio, activo en su Teams
|
|
@@ -13700,6 +14147,7 @@ ${bold("Agentes")}
|
|
|
13700
14147
|
[--catalog productos.csv|json] su cat\xE1logo: sku,nombre,precio[,unidad,categoria]
|
|
13701
14148
|
[--toolsets crm,sales | +crm,-sales] grupos de tools de gs que recibe
|
|
13702
14149
|
[--prompt-base AGENTE|none --prompt-overlay ARCHIVO [--dry-run]] prompt = el de otro agente + ediciones, sincronizado
|
|
14150
|
+
[--prompt-patch overlay.json | --prompt-append ARCHIVO [--dry-run]] cambia un pedazo del prompt, sobre el actual del servidor
|
|
13703
14151
|
[--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
14152
|
[--prompt-mode append|replace] replace = su prompt SIN la persona de casa de Ghosty
|
|
13705
14153
|
agents doctor <id> Revisa la configuraci\xF3n: skills encimadas, tools que nombra el prompt, permisos rotos, turnos que fallan
|
|
@@ -13716,6 +14164,8 @@ ${bold("Agentes")}
|
|
|
13716
14164
|
agents box <id> La m\xE1quina que lo atiende: \xBFya trae la imagen nueva? [--check RUTA]\u2026 [--contains RUTA=TEXTO]\u2026 [--recycle [--reenv] [--force]]
|
|
13717
14165
|
agents demo enable|disable <id> Prende o apaga su liga p\xFAblica de demo
|
|
13718
14166
|
agents demo <id> Liga p\xFAblica de demo /p/<slug>: cualquiera lo prueba sin cuenta y \xABQued\xE1rmelo\xBB lo clona a la suya
|
|
14167
|
+
agents embed enable|disable <id> Widget web: burbuja de chat en TU sitio (un <script>), s\xF3lo en los dominios que pongas [--domains a.com,*.b.com] [--per-visitor N] [--per-ip N] [--welcome T] [--color #hex] [--rotate]
|
|
14168
|
+
agents embed <id> Su estado y el snippet para pegar (s\xF3lo motor claude: a\xEDsla a cada visitante)
|
|
13719
14169
|
[--slug NOMBRE] [--expires "AAAA-MM-DDTHH:MM"|no] [--turns N] [--per-ip N] [--welcome TEXTO] [--chips "a|b|c"] [--starter TEXTO] [--sponsor on|off] [--rotate]
|
|
13720
14170
|
|
|
13721
14171
|
${bold("Configurar un agente")}
|
|
@@ -13729,7 +14179,8 @@ ${bold("Configurar un agente")}
|
|
|
13729
14179
|
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
14180
|
mcp get|set <id> Servidores MCP [--file servers.json] (headers http admiten \${turn.token})
|
|
13731
14181
|
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)
|
|
14182
|
+
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]
|
|
14183
|
+
channels messenger ls <id> Las p\xE1ginas de Messenger del espacio: qui\xE9n contesta hoy y qui\xE9n contest\xF3 al \xFAltimo [--orphans]
|
|
13733
14184
|
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
14185
|
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
14186
|
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 +14191,9 @@ ${bold("Configurar un agente")}
|
|
|
13740
14191
|
cards ls <id> Tarjetas recientes de su tablero de ventas [--q TEXTO] [--paused] [--limit 20]
|
|
13741
14192
|
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
14193
|
cards archive <id> Archiva tarjetas en bloque (reversible) --integration X | --column X | --before 30d [--dry-run]
|
|
14194
|
+
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]
|
|
14195
|
+
cards reply <id> <tarjeta> El agente contesta lo que el cliente dej\xF3 pendiente (\xABPedir respuesta\xBB) [--directive TEXTO] [--yes]
|
|
14196
|
+
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
14197
|
|
|
13744
14198
|
${bold("Soporte")} ${dim("(staff de Ghosty)")}
|
|
13745
14199
|
conversations ls --user CORREO Las conversaciones de alguien en cualquier agente [--since 30d] [--surface android]
|
|
@@ -13804,8 +14258,8 @@ ${bold("Opciones globales")}
|
|
|
13804
14258
|
${bold("C\xF3digos de salida")} 0 bien \xB7 1 error de la API \xB7 2 uso incorrecto \xB7 3 sin sesi\xF3n
|
|
13805
14259
|
${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
14260
|
${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"];
|
|
13808
|
-
var AGENTS_SUBS = ["ls", "create", "get", "set", "export", "doctor", "docs", "share", "unshare", "shares", "teams", "rm", "restart", "demo", "box"];
|
|
14261
|
+
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"];
|
|
14262
|
+
var AGENTS_SUBS = ["ls", "create", "get", "set", "export", "doctor", "docs", "share", "unshare", "shares", "teams", "rm", "restart", "demo", "embed", "box"];
|
|
13809
14263
|
function unknown(what, input, known, helpCmd) {
|
|
13810
14264
|
const s = suggest(input, known);
|
|
13811
14265
|
return usage(t(`Unknown command: ${what}.`, `Comando desconocido: ${what}.`), s ? t(`Did you mean "${s}"? Run: ${helpCmd}`, `\xBFQuisiste decir "${s}"? Corre: ${helpCmd}`) : t(`Run: ${helpCmd}`, `Corre: ${helpCmd}`));
|
|
@@ -13887,6 +14341,9 @@ async function main() {
|
|
|
13887
14341
|
turns: { type: "string" },
|
|
13888
14342
|
vence: { type: "string" },
|
|
13889
14343
|
"per-ip": { type: "string" },
|
|
14344
|
+
"per-visitor": { type: "string" },
|
|
14345
|
+
domains: { type: "string" },
|
|
14346
|
+
color: { type: "string" },
|
|
13890
14347
|
welcome: { type: "string" },
|
|
13891
14348
|
chips: { type: "string" },
|
|
13892
14349
|
starter: { type: "string" },
|
|
@@ -13940,6 +14397,13 @@ async function main() {
|
|
|
13940
14397
|
toolsets: { type: "string" },
|
|
13941
14398
|
"prompt-base": { type: "string" },
|
|
13942
14399
|
"prompt-overlay": { type: "string" },
|
|
14400
|
+
"prompt-patch": { type: "string" },
|
|
14401
|
+
"prompt-append": { type: "string" },
|
|
14402
|
+
mode: { type: "string" },
|
|
14403
|
+
directive: { type: "string" },
|
|
14404
|
+
reply: { type: "boolean" },
|
|
14405
|
+
actor: { type: "string" },
|
|
14406
|
+
room: { type: "string" },
|
|
13943
14407
|
url: { type: "string" },
|
|
13944
14408
|
description: { type: "string" },
|
|
13945
14409
|
grant: { type: "string" },
|
|
@@ -14036,7 +14500,7 @@ async function main() {
|
|
|
14036
14500
|
const tokens = splitTokens(pos, v);
|
|
14037
14501
|
globals.token = tokens.session;
|
|
14038
14502
|
globals.profile = v.profile;
|
|
14039
|
-
if (v.version) return out({ version: "0.
|
|
14503
|
+
if (v.version) return out({ version: "0.51.0" }, () => "0.51.0");
|
|
14040
14504
|
for (const n of normalizeArgs(pos, v)) if (!globals.json) info(dim(n));
|
|
14041
14505
|
const context = (await readConfig()).workspace;
|
|
14042
14506
|
if (!explicitToken()?.startsWith("gat_")) {
|
|
@@ -14084,6 +14548,7 @@ async function main() {
|
|
|
14084
14548
|
return applyCmd(sub, v);
|
|
14085
14549
|
case "agents":
|
|
14086
14550
|
if ((sub === "ls" || sub === "list" || !sub) && v.user && v.app !== void 0) return appAgentsOf(v.user, v.app);
|
|
14551
|
+
if ((sub === "ls" || sub === "list" || !sub) && v.handle) return agentsByHandle(v.handle, v.workspace ?? context);
|
|
14087
14552
|
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
14553
|
if (sub === "create" || sub === "new") {
|
|
14089
14554
|
if (v.workspace === "none") delete v.workspace;
|
|
@@ -14103,6 +14568,7 @@ async function main() {
|
|
|
14103
14568
|
if (sub === "docs" || sub === "documents") return agentDocsCmd(a1, a2, pos.slice(4).filter((x) => x !== void 0), v);
|
|
14104
14569
|
if (sub === "restart") return agentsRestart(a1);
|
|
14105
14570
|
if (sub === "demo") return agentsDemo(a1, v);
|
|
14571
|
+
if (sub === "embed" || sub === "widget") return agentsEmbed(a1, v);
|
|
14106
14572
|
if (sub === "box") return agentsBox(a1, v);
|
|
14107
14573
|
if (sub === "export") return agentsExport(a1, v);
|
|
14108
14574
|
throw unknown(`agents ${sub}`, sub, AGENTS_SUBS, "ghosty agents --help");
|
|
@@ -14179,6 +14645,10 @@ async function main() {
|
|
|
14179
14645
|
return spacesCmd(sub, a1, a2, v, context);
|
|
14180
14646
|
case "staff":
|
|
14181
14647
|
return staffCmd(sub, a1, a2, v);
|
|
14648
|
+
case "audit":
|
|
14649
|
+
return auditCmd(sub, v, context);
|
|
14650
|
+
case "factory":
|
|
14651
|
+
return factoryCmd(sub, a1, a2, pos[4], v, context);
|
|
14182
14652
|
case "plan":
|
|
14183
14653
|
return planCmd(sub, a1, v, context);
|
|
14184
14654
|
case "me":
|
|
@@ -14196,7 +14666,7 @@ async function main() {
|
|
|
14196
14666
|
throw unknown(cmd, cmd, COMMANDS, "ghosty --help");
|
|
14197
14667
|
}
|
|
14198
14668
|
}
|
|
14199
|
-
var notice = updateNotice("0.
|
|
14669
|
+
var notice = updateNotice("0.51.0").catch(() => null);
|
|
14200
14670
|
main().then(
|
|
14201
14671
|
async () => {
|
|
14202
14672
|
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.51.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",
|