@ghostystudio/cli 0.25.0 → 0.26.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 +17 -1
- package/dist/index.js +446 -35
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -15,6 +15,11 @@ npm i -g @ghostystudio/cli && ghosty login
|
|
|
15
15
|
```bash
|
|
16
16
|
ghosty whoami
|
|
17
17
|
ghosty usage # plan and usage this week / month (also: ghosty me usage)
|
|
18
|
+
ghosty usage --workspace SLUG [--by agent,day] [--days 14] # a workspace's turns and tokens per agent/day (owner or staff)
|
|
19
|
+
ghosty engines ls [--models] # valid --engine / --model values and what each model costs
|
|
20
|
+
ghosty bags ls|create|set|rm <workspace> ["bag"] [--tokens 20M] [--recurring on|off] [--hard-cap on|off] [--note] # token bags (changing: staff)
|
|
21
|
+
ghosty spaces renew <workspace> --until YYYY-MM-DD [--note] [--dry-run] # staff
|
|
22
|
+
ghosty spaces rename <old> <new> [--dry-run] # staff, slug only
|
|
18
23
|
ghosty agents ls
|
|
19
24
|
ghosty agents create --name NAME [--engine E] [--model M] [--prompt TEXT | --prompt-file FILE] [--env K=V,…] [--workspace SLUG] [--channels teams=off]
|
|
20
25
|
ghosty agents get <id> [--fields name,model,prompt] [--prompt-out FILE]
|
|
@@ -23,7 +28,8 @@ ghosty apply <file|dir> [--agent X] [--create] [--prune] [--dry-run] # make an
|
|
|
23
28
|
ghosty agents set <id> [--name] [--engine] [--model] [--prompt | --prompt-file FILE] [--env K=V,…] [--connect ID,…] [--disconnect ID,…] [--toolsets +crm]
|
|
24
29
|
ghosty agents set <id> --prompt-base <base-id> --prompt-overlay overlay.json [--dry-run] # prompt = another agent's + edits, kept in sync
|
|
25
30
|
ghosty agents share|unshare <id> <email> [--role editor|admin] # share without handing it over · agents shares <id> lists them
|
|
26
|
-
ghosty agents rm <id> [--yes]
|
|
31
|
+
ghosty agents rm <id> [--from-workspace] [--yes] # --from-workspace: turn it off in Teams first
|
|
32
|
+
ghosty agents set <id> [--tool wa:tool=off,…] [--bag NAME|none] [--avatar URL|FILE]
|
|
27
33
|
ghosty agents restart <id>
|
|
28
34
|
ghosty agents box <id> [--check PATH]… [--contains PATH=TEXT]… [--recycle] # is its machine on the new image?
|
|
29
35
|
ghosty agents demo enable|disable <id>
|
|
@@ -40,6 +46,7 @@ ghosty skills enable|disable <id> <slug> # turn one of its skills (or one of Gho
|
|
|
40
46
|
ghosty skills templates <id> # pdf-doc templates: its own and Ghosty's, and which folder wins
|
|
41
47
|
ghosty dbs ls|tables|query|import|grant|revoke <id> [db] [--write] [--arg V]… [--external-write t1,t2] [--external-rows t:col+col] [--from easybits:ID --tables a,b --append --dry-run]
|
|
42
48
|
ghosty turns ls <id> [--errors | --status running|done|stopped|error] [--since 1h] [--limit N]
|
|
49
|
+
ghosty turns ls --workspace SLUG --quota-hits [--days 14] # messages that got no turn because the balance ran out
|
|
43
50
|
ghosty turns show <id> [turn-id|last] # what came in, what it answered, where the time went [--full]
|
|
44
51
|
ghosty channels whatsapp ls|enable|disable <id> [number] [--dry-run]
|
|
45
52
|
ghosty channels whatsapp link|unlink <id> --integration ID --answer on|off [--forward] [--dry-run] # bring a WABA number from Formmy
|
|
@@ -79,6 +86,15 @@ Every command has its own help with examples: `ghosty <command> --help` or `ghos
|
|
|
79
86
|
|
|
80
87
|
Exit codes: `0` ok · `1` API error · `2` usage error · `3` not signed in.
|
|
81
88
|
|
|
89
|
+
### Changes in 0.26.0
|
|
90
|
+
|
|
91
|
+
- `ghosty engines ls [--models]`: engines and models valid for `--engine`/`--model`; an unknown model is rejected with the valid list (also without `--engine`).
|
|
92
|
+
- `ghosty bags ls|create|set|rm <workspace>`: token bags, monthly or one-time (`--recurring off`); `agents set --bag NAME|none` hangs an agent on one. Changing them is Ghosty staff only.
|
|
93
|
+
- `ghosty spaces renew <workspace> --until DATE` and `spaces rename <old> <new>` (staff, logged in the workspace's audit).
|
|
94
|
+
- `ghosty usage --workspace SLUG --by agent,day`: turns, failed, billable tokens and «no balance» per agent and day; `turns ls --workspace SLUG --quota-hits` lists those messages (WhatsApp Business and Teams).
|
|
95
|
+
- `agents set --tool [channel:]tool=on|off`, `--avatar URL|FILE` (its face in Teams); `agents rm --from-workspace` turns it off in Teams before deleting it.
|
|
96
|
+
- `dbs import` asks you to type the database name when a replace leaves a table empty or loses more than half its rows.
|
|
97
|
+
|
|
82
98
|
### Changes in 0.9.1
|
|
83
99
|
|
|
84
100
|
- `ghosty agents --help` describes every `agents demo` flag; `ghosty usage --help` has its own page.
|
package/dist/index.js
CHANGED
|
@@ -7476,6 +7476,9 @@ var SERVER_MESSAGES = [
|
|
|
7476
7476
|
["falta email", "missing email"],
|
|
7477
7477
|
["no est\xE1 compartido con {}", "it isn't shared with {}"],
|
|
7478
7478
|
// — Motor, modelo y máquina del agente (updateFleetAgentConfig, createFleetAgentRecord)
|
|
7479
|
+
["motor desconocido: {}; v\xE1lidos: {} (ghosty engines ls)", "unknown engine: {}; valid: {} (ghosty engines ls)"],
|
|
7480
|
+
["el modelo {} no es del motor {}; v\xE1lidos: {} (ghosty engines ls)", "model {} doesn't belong to engine {}; valid: {} (ghosty engines ls)"],
|
|
7481
|
+
["modelo {} inv\xE1lido para {}; v\xE1lidos: {} (ghosty engines ls)", "invalid model {} for {}; valid: {} (ghosty engines ls)"],
|
|
7479
7482
|
["motor desconocido: {}", "unknown engine: {}"],
|
|
7480
7483
|
["el modelo {} no es del motor {}", "model {} doesn't belong to engine {}"],
|
|
7481
7484
|
["modelo {} inv\xE1lido para {}", "invalid model {} for {}"],
|
|
@@ -7967,6 +7970,38 @@ var SERVER_MESSAGES = [
|
|
|
7967
7970
|
["no tienes tablero de ventas asignado: p\xEDdele al due\xF1o que te asigne uno (ghosty.studio \u2192 Ventas)", "you have no sales board assigned: ask the owner to assign you one (ghosty.studio \u2192 Sales)"],
|
|
7968
7971
|
["no tienes tablero de ventas asignado", "you have no sales board assigned"],
|
|
7969
7972
|
["tu identidad se cambia desde el equipo, no en una conversaci\xF3n con clientes", "your identity is changed from the team, not in a conversation with customers"],
|
|
7973
|
+
// — Espacios, bolsas, avatar y bajas (CLI 0.26)
|
|
7974
|
+
["las bolsas son topes pactados: s\xF3lo el staff de Ghosty", "bags are agreed caps: Ghosty staff only"],
|
|
7975
|
+
["renovar un espacio: s\xF3lo el staff de Ghosty", "renewing a workspace: Ghosty staff only"],
|
|
7976
|
+
["renombrar un espacio: s\xF3lo el staff de Ghosty", "renaming a workspace: Ghosty staff only"],
|
|
7977
|
+
["s\xF3lo el due\xF1o de \xAB{}\xBB o el staff de Ghosty", "only the owner of \xAB{}\xBB or Ghosty staff"],
|
|
7978
|
+
["la bolsa de un agente s\xF3lo la cambia el staff de Ghosty", "only Ghosty staff changes an agent's bag"],
|
|
7979
|
+
["este agente no tiene espacio: no puede tener bolsa", "this agent has no workspace: it can't have a bag"],
|
|
7980
|
+
["no hay bolsa \xAB{}\xBB en su espacio; hay: {}", "no bag \xAB{}\xBB in its workspace; there are: {}"],
|
|
7981
|
+
["no hay bolsa \xAB{}\xBB en su espacio (no tiene bolsas: ghosty bags create)", "no bag \xAB{}\xBB in its workspace (it has none: ghosty bags create)"],
|
|
7982
|
+
["no hay bolsa \xAB{}\xBB en {}", "no bag \xAB{}\xBB in {}"],
|
|
7983
|
+
["ya hay una bolsa \xAB{}\xBB en {}", "there's already a bag \xAB{}\xBB in {}"],
|
|
7984
|
+
["falta name: el cliente lo ve en Ajustes \u2192 Uso", "missing name: the customer sees it in Settings \u2192 Usage"],
|
|
7985
|
+
["falta tokens", "missing tokens"],
|
|
7986
|
+
["tokens: un n\xFAmero \u2265 0 (facturables)", "tokens: a number \u2265 0 (billable)"],
|
|
7987
|
+
["{} agente{} usa{} \xAB{}\xBB: mu\xE9velos a otra bolsa primero", "{} agent{} use{} \xAB{}\xBB: move them to another bag first"],
|
|
7988
|
+
["esa bolsa no es de este espacio", "that bag isn't from this workspace"],
|
|
7989
|
+
["esa bolsa no es del espacio de este agente", "that bag isn't from this agent's workspace"],
|
|
7990
|
+
["la bolsa necesita un nombre: el cliente lo ve en Ajustes \u2192 Uso", "the bag needs a name: the customer sees it in Settings \u2192 Usage"],
|
|
7991
|
+
["fecha inv\xE1lida: \xAB{}\xBB (AAAA-MM-DD)", "invalid date: \xAB{}\xBB (YYYY-MM-DD)"],
|
|
7992
|
+
["{} paga con Stripe: \xABpagado hasta\xBB lo mueve la factura pagada, no se toca a mano", "{} pays with Stripe: the paid invoice moves \xABpaid until\xBB, it isn't changed by hand"],
|
|
7993
|
+
["slug inv\xE1lido: \xAB{}\xBB (2 a 40 letras min\xFAsculas, n\xFAmeros o guiones; no reservado)", "invalid slug: \xAB{}\xBB (2 to 40 lowercase letters, digits or dashes; not reserved)"],
|
|
7994
|
+
["el slug {} ya es de otro espacio", "slug {} already belongs to another workspace"],
|
|
7995
|
+
["el slug {} lo tiene un tablero de prueba (vence {})", "slug {} is taken by a trial board (expires {})"],
|
|
7996
|
+
["quedan con el nombre viejo: https://{}.sales\u2026 (404) y https://{}.teams\u2026 (vivo hasta reiniciar Teams)", "these keep the old name: https://{}.sales\u2026 (404) and https://{}.teams\u2026 (alive until Teams restarts)"],
|
|
7997
|
+
["tiene cajas de CI: los runners usan la etiqueta ws-{}; hay que re-registrarlos y cambiar runs-on", "it has CI boxes: the runners use the label ws-{}; re-register them and change runs-on"],
|
|
7998
|
+
["\xA1cambiaron campos que no deb\xEDan! {}", "fields that shouldn't change did! {}"],
|
|
7999
|
+
["el token del agente no cambia su avatar: usa tu sesi\xF3n", "the agent's token doesn't change its avatar: use your session"],
|
|
8000
|
+
["manda la imagen (image/png|jpeg|webp) o {url: https://\u2026}", "send the image (image/png|jpeg|webp) or {url: https://\u2026}"],
|
|
8001
|
+
["no est\xE1 activado en ning\xFAn Teams todav\xEDa: se le pone al activarlo", "not active in any Teams yet: it gets it when activated"],
|
|
8002
|
+
["s\xF3lo el due\xF1o (o staff) puede borrar el agente", "only the owner (or staff) can delete the agent"],
|
|
8003
|
+
["ghosty agents rm <agente> --from-workspace lo quita de Teams y lo borra", "ghosty agents rm <agent> --from-workspace takes it out of Teams and deletes it"],
|
|
8004
|
+
["Se acab\xF3 el saldo de este agente ({}). Era una carga \xFAnica y no se recarga. Los dem\xE1s agentes del espacio siguen con el suyo.", "This agent ran out of balance ({}). It was a one-time load and doesn't refill. The other agents in the workspace keep theirs."],
|
|
7970
8005
|
// — Infra interna (casi nunca llega al CLI, pero por si acaso)
|
|
7971
8006
|
["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)."],
|
|
7972
8007
|
["sqld: fetch sin namespace", "sqld: fetch without namespace"],
|
|
@@ -8268,7 +8303,7 @@ async function postLong(path, body, timeoutMs = 9e5) {
|
|
|
8268
8303
|
const req = httpsRequest(`${base}${path}`, {
|
|
8269
8304
|
method: "POST",
|
|
8270
8305
|
timeout: timeoutMs,
|
|
8271
|
-
headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.
|
|
8306
|
+
headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.26.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
|
|
8272
8307
|
}, (res) => {
|
|
8273
8308
|
let buf = "";
|
|
8274
8309
|
res.on("data", (c) => buf += c);
|
|
@@ -8310,7 +8345,7 @@ async function request(path, opts = {}, retried = false) {
|
|
|
8310
8345
|
method: opts.method ?? (opts.body !== void 0 || opts.raw !== void 0 ? "POST" : "GET"),
|
|
8311
8346
|
headers: {
|
|
8312
8347
|
authorization: `Bearer ${token}`,
|
|
8313
|
-
"user-agent": `ghosty-cli/${"0.
|
|
8348
|
+
"user-agent": `ghosty-cli/${"0.26.0"}`,
|
|
8314
8349
|
"accept-language": lang(),
|
|
8315
8350
|
...opts.body !== void 0 ? { "content-type": "application/json" } : {},
|
|
8316
8351
|
...opts.headers
|
|
@@ -8591,6 +8626,15 @@ async function agentsGet(id, fields, promptOut) {
|
|
|
8591
8626
|
}
|
|
8592
8627
|
async function agentsSet(id, v) {
|
|
8593
8628
|
id = need(id, "agent id", "ghosty agents set <agent-id> --prompt-file PROMPT.md");
|
|
8629
|
+
const done = [];
|
|
8630
|
+
if (v.bag) {
|
|
8631
|
+
const r2 = await api(agentPath(id), { method: "PATCH", body: { bag: v.bag === "none" ? null : v.bag } });
|
|
8632
|
+
done.push(r2.bag ? t(`bag \xAB${r2.bag}\xBB`, `bolsa \xAB${r2.bag}\xBB`) : t("workspace balance (no bag)", "saldo del espacio (sin bolsa)"));
|
|
8633
|
+
}
|
|
8634
|
+
if (v.avatar) {
|
|
8635
|
+
const r2 = await setAvatar(id, v.avatar);
|
|
8636
|
+
done.push(`avatar ${r2.avatar}${r2.workspaces.length ? dim(t(` (Teams: ${r2.workspaces.join(", ")})`, ` (Teams: ${r2.workspaces.join(", ")})`)) : r2.note ? dim(` (${serverText(r2.note)})`) : ""}`);
|
|
8637
|
+
}
|
|
8594
8638
|
if (v["prompt-base"]) {
|
|
8595
8639
|
const unlink = v["prompt-base"] === "none";
|
|
8596
8640
|
if (!unlink && !v["prompt-overlay"]) throw usage(t("Missing --prompt-overlay FILE.", "Falta --prompt-overlay FILE."), t("Its format", "Su formato") + ': {"edits":[{"what":"name","find":"Tania","replace":"Pia","all":true}]} (regex: true ' + t("for patterns", "para patrones") + ")");
|
|
@@ -8628,6 +8672,22 @@ ${dim(t("Dry run: nothing changed.", "Simulacro: no se cambi\xF3 nada."))}`);
|
|
|
8628
8672
|
next.delete("core");
|
|
8629
8673
|
body.tools = { toolsets: [...next] };
|
|
8630
8674
|
}
|
|
8675
|
+
if (v.tool) {
|
|
8676
|
+
const cur = await api(agentPath(id, "?fields=tools"));
|
|
8677
|
+
const canales = cur.tools?.canales ?? {};
|
|
8678
|
+
const known = new Set(Object.values(canales).flatMap((m) => Object.keys(m)));
|
|
8679
|
+
const tools = { gs: {}, canales: {} };
|
|
8680
|
+
for (const item of v.tool.split(",").map((x) => x.trim()).filter(Boolean)) {
|
|
8681
|
+
const m = /^(?:([a-z]+):)?([a-z0-9_-]+)=(on|off)$/i.exec(item);
|
|
8682
|
+
if (!m) throw usage(t(`Bad --tool entry: ${item}.`, `Entrada de --tool inv\xE1lida: ${item}.`), t("Use [channel:]tool=on|off, e.g. wa:actualizar_identidad=off", "Usa [canal:]tool=on|off, p. ej. wa:actualizar_identidad=off"));
|
|
8683
|
+
const [, ch, tool, onOff] = m;
|
|
8684
|
+
if (!known.has(tool)) throw usage(t(`Unknown tool: ${tool}.`, `Tool desconocida: ${tool}.`), t(`List them: ghosty agents get ${id} --fields tools`, `L\xEDstalas: ghosty agents get ${id} --fields tools`));
|
|
8685
|
+
if (ch && !(ch in canales)) throw usage(t(`Unknown channel: ${ch}.`, `Canal desconocido: ${ch}.`), `${t("Channels", "Canales")}: ${Object.keys(canales).join(", ")}`);
|
|
8686
|
+
if (ch) (tools.canales[ch] ??= {})[tool] = onOff === "on";
|
|
8687
|
+
else tools.gs[tool] = onOff === "on";
|
|
8688
|
+
}
|
|
8689
|
+
body.tools = { ...body.tools ?? {}, ...tools };
|
|
8690
|
+
}
|
|
8631
8691
|
const env = parseEnv(v.env);
|
|
8632
8692
|
const unset = (v["unset-env"] ?? "").split(",").map((k) => k.trim()).filter(Boolean);
|
|
8633
8693
|
if (env || unset.length) body.env = { ...env ?? {}, ...Object.fromEntries(unset.map((k) => [k, ""])) };
|
|
@@ -8651,18 +8711,32 @@ ${dim(t("Dry run: nothing changed.", "Simulacro: no se cambi\xF3 nada."))}`);
|
|
|
8651
8711
|
if (v["auto-stage"] !== "on" && v["auto-stage"] !== "off") throw usage(t("--auto-stage takes on or off.", "--auto-stage acepta on u off."));
|
|
8652
8712
|
body.autoStage = v["auto-stage"] === "on";
|
|
8653
8713
|
}
|
|
8654
|
-
if (!Object.keys(body).length)
|
|
8714
|
+
if (!Object.keys(body).length && done.length) return out({ id, updated: done }, () => `${lavender("\u2713")} ${t("Updated", "Actualizado")}: ${done.join(" \xB7 ")}`);
|
|
8715
|
+
if (!Object.keys(body).length) throw usage(t("Nothing to change.", "Nada que cambiar."), t("Use", "Usa") + " --name, --engine, --model, --prompt, --prompt-file, --env, --unset-env, --channels, --toolsets, --tool, --connect, --disconnect, --voice, --voice-replies, --auto-stage, --bag " + t("or", "o") + " --avatar.");
|
|
8655
8716
|
const r = await api(agentPath(id), { method: "PATCH", body });
|
|
8656
|
-
out(r, () => `${lavender("\u2713")} ${t("Updated", "Actualizado")}: ${Object.keys(body).join(", ")}.`);
|
|
8717
|
+
out(r, () => `${lavender("\u2713")} ${t("Updated", "Actualizado")}: ${[...Object.keys(body), ...done].join(", ")}.`);
|
|
8657
8718
|
}
|
|
8658
8719
|
async function agentsRm(id, v = {}) {
|
|
8659
|
-
id = need(id, "agent id", "ghosty agents rm <agent-id>");
|
|
8660
|
-
|
|
8720
|
+
id = need(id, "agent id", "ghosty agents rm <agent-id> [--from-workspace]");
|
|
8721
|
+
const from = v["from-workspace"];
|
|
8722
|
+
const retry = `ghosty agents rm ${id}${from ? " --from-workspace" : ""}`;
|
|
8723
|
+
requireYesIfHeadless(v.yes, retry);
|
|
8661
8724
|
if (!v.yes) {
|
|
8662
8725
|
const a = await api(agentPath(id, "?fields=name"));
|
|
8663
|
-
|
|
8726
|
+
const teams = from ? t(" It's turned off in every Teams where it's active first.", " Antes se desactiva en cada Teams donde est\xE9 activo.") : "";
|
|
8727
|
+
await confirm(t(`Delete agent ${a.name ?? id}? Its machine, memory and files go too. This can't be undone.${teams}`, `\xBFBorrar el agente ${a.name ?? id}? Se van tambi\xE9n su m\xE1quina, memoria y archivos. No se puede deshacer.${teams}`), { expect: a.name || id, retry });
|
|
8664
8728
|
}
|
|
8665
|
-
|
|
8729
|
+
const r = await api(agentPath(id, from ? "?fromWorkspace=1" : ""), { method: "DELETE" });
|
|
8730
|
+
out(r, () => `${lavender("\u2713")} ${t("Deleted", "Borrado")} ${id}.${r?.unbound?.length ? dim(t(` Turned off in Teams: ${r.unbound.join(", ")}.`, ` Desactivado en Teams: ${r.unbound.join(", ")}.`)) : ""}`);
|
|
8731
|
+
}
|
|
8732
|
+
async function setAvatar(id, src) {
|
|
8733
|
+
if (/^https:\/\//.test(src)) return api(agentPath(id, "/avatar"), { method: "PUT", body: { url: src } });
|
|
8734
|
+
const bytes = await readFile3(src).catch(() => {
|
|
8735
|
+
throw usage(t(`Can't read ${src}.`, `No puedo leer ${src}.`), t("--avatar takes an https:// link or a local JPEG/PNG/WebP file.", "--avatar acepta una liga https:// o un archivo JPEG/PNG/WebP local."));
|
|
8736
|
+
});
|
|
8737
|
+
const ext = src.toLowerCase().split(".").pop();
|
|
8738
|
+
const type = ext === "png" ? "image/png" : ext === "webp" ? "image/webp" : "image/jpeg";
|
|
8739
|
+
return api(agentPath(id, "/avatar"), { method: "PUT", raw: new Uint8Array(bytes), headers: { "content-type": type } });
|
|
8666
8740
|
}
|
|
8667
8741
|
async function agentsShare(sub, id, email, v) {
|
|
8668
8742
|
id = need(id, "agent id", "ghosty agents share <agent-id> <email> [--role admin|editor]");
|
|
@@ -9305,7 +9379,7 @@ async function call(path, v, init = {}) {
|
|
|
9305
9379
|
"x-ghosty-ts": ts,
|
|
9306
9380
|
"x-ghosty-sig": sig,
|
|
9307
9381
|
"content-type": "application/json",
|
|
9308
|
-
"user-agent": `ghosty-cli/${"0.
|
|
9382
|
+
"user-agent": `ghosty-cli/${"0.26.0"}`
|
|
9309
9383
|
},
|
|
9310
9384
|
body: method === "GET" ? void 0 : raw
|
|
9311
9385
|
});
|
|
@@ -9526,6 +9600,19 @@ function gsOnlyWarnings(gs) {
|
|
|
9526
9600
|
${lines.join("\n")}
|
|
9527
9601
|
${dim(t("Other agents may be writing there already: leave those tables out (--tables) or use --append.", "Puede que otros agentes ya escriban ah\xED: deja fuera esas tablas (--tables) o usa --append."))}`;
|
|
9528
9602
|
}
|
|
9603
|
+
function shrinking(plan) {
|
|
9604
|
+
return plan.filter((p) => p.action === "replace" && p.targetRows > 0 && (p.resultRows === 0 || p.resultRows < p.targetRows / 2));
|
|
9605
|
+
}
|
|
9606
|
+
function shrinkWarnings(plan) {
|
|
9607
|
+
const s = shrinking(plan);
|
|
9608
|
+
if (!s.length) return "";
|
|
9609
|
+
const lines = s.map(
|
|
9610
|
+
(p) => red(p.resultRows === 0 ? t(` ! ${p.table}: would be left EMPTY (${p.targetRows} \u2192 0)`, ` ! ${p.table}: quedar\xEDa VAC\xCDA (${p.targetRows} \u2192 0)`) : t(` ! ${p.table}: loses ${p.targetRows - p.resultRows} of ${p.targetRows} rows`, ` ! ${p.table}: pierde ${p.targetRows - p.resultRows} de ${p.targetRows} filas`))
|
|
9611
|
+
);
|
|
9612
|
+
return `
|
|
9613
|
+
${lines.join("\n")}
|
|
9614
|
+
${dim(t("Is the source the right database? Leave those tables out with --tables.", "\xBFLa base de origen es la correcta? D\xE9jalas fuera con --tables."))}`;
|
|
9615
|
+
}
|
|
9529
9616
|
function planText(plan) {
|
|
9530
9617
|
return plan.map((p) => p.action === "create" ? t(` + ${p.table} create, ${p.sourceRows} row(s)`, ` + ${p.table} crear, ${p.sourceRows} fila(s)`) : t(` ~ ${p.table} ${p.action} ${p.targetRows} \u2192 ${p.resultRows} row(s)`, ` ~ ${p.table} ${p.action === "replace" ? "reemplazar" : "agregar"} ${p.targetRows} \u2192 ${p.resultRows} fila(s)`)).join("\n");
|
|
9531
9618
|
}
|
|
@@ -9538,12 +9625,12 @@ async function dbsImport(id, db, v) {
|
|
|
9538
9625
|
const body = { from: "easybits", sourceDb: m[1], easybitsKey, mode: v.append ? "append" : "replace", ...v.tables ? { tables: csv(v.tables) } : {}, ...v["rehost-images"] ? { rehost: csv(v["rehost-images"]) } : {} };
|
|
9539
9626
|
const path = agentPath2(id, `/dbs/${enc6(db)}/import`);
|
|
9540
9627
|
const dry = await api(path, { body: { ...body, dryRun: true } });
|
|
9541
|
-
if (v["dry-run"]) return out(dry, () => `${dry.db} \u2190 easybits:${m[1]}
|
|
9542
|
-
${planText(dry.plan)}${importWarnings(dry.warnings)}${gsOnlyWarnings(dry.gsOnly)}
|
|
9628
|
+
if (v["dry-run"]) return out({ ...dry, shrinking: shrinking(dry.plan).map((p) => p.table) }, () => `${dry.db} \u2190 easybits:${m[1]}
|
|
9629
|
+
${planText(dry.plan)}${shrinkWarnings(dry.plan)}${importWarnings(dry.warnings)}${gsOnlyWarnings(dry.gsOnly)}
|
|
9543
9630
|
${dim(t("Dry run: nothing changed.", "Simulaci\xF3n: no cambi\xF3 nada."))}`);
|
|
9544
9631
|
const destructive = dry.plan.some((p) => p.action === "replace" && p.targetRows > 0);
|
|
9545
|
-
if (destructive) await confirm(`${planText(dry.plan)}${importWarnings(dry.warnings)}${gsOnlyWarnings(dry.gsOnly)}
|
|
9546
|
-
${t(`Replace the rows above in ${dry.db}?`, `\xBFReemplazar las filas de arriba en ${dry.db}?`)}`, { yes: v.yes, retry: `ghosty dbs import ${id} ${db} --from ${v.from}${v.tables ? ` --tables ${v.tables}` : ""}` });
|
|
9632
|
+
if (destructive) await confirm(`${planText(dry.plan)}${shrinkWarnings(dry.plan)}${importWarnings(dry.warnings)}${gsOnlyWarnings(dry.gsOnly)}
|
|
9633
|
+
${t(`Replace the rows above in ${dry.db}?`, `\xBFReemplazar las filas de arriba en ${dry.db}?`)}`, { yes: v.yes, ...shrinking(dry.plan).length ? { expect: dry.db } : {}, retry: `ghosty dbs import ${id} ${db} --from ${v.from}${v.tables ? ` --tables ${v.tables}` : ""}` });
|
|
9547
9634
|
const r = await api(path, { body });
|
|
9548
9635
|
const img = r.rehosted ? `
|
|
9549
9636
|
${t(`${r.rehosted.copied} image link(s) now permanent`, `${r.rehosted.copied} liga(s) de imagen ya permanentes`)}${r.rehosted.failed.length ? `, ${red(String(r.rehosted.failed.length))} ${t("kept as they were", "se quedaron como estaban")}:
|
|
@@ -9751,8 +9838,37 @@ async function agentDocsCmd(sub, id, docs, v) {
|
|
|
9751
9838
|
const r = await api(agentPath2(id, `/documents/${enc6(doc)}`), { method: "DELETE" });
|
|
9752
9839
|
return out(r, () => `${lavender("\u2713")} ${r.name} ${t("revoked.", "revocado.")} ${dim(`${t("Undo", "Deshacer")}: ghosty agents docs grant ${id} "${r.name}"`)}`);
|
|
9753
9840
|
}
|
|
9841
|
+
case "replace": {
|
|
9842
|
+
const ex = 'ghosty agents docs replace <agent-id> "Cat\xE1logo.pdf" --file nuevo.pdf (o --url URL)';
|
|
9843
|
+
doc = need2(doc, t("document (id or exact name)", "el documento (id o nombre exacto)"), ex);
|
|
9844
|
+
if (!v.url === !v.file) throw usage(t("Use --url URL or --file PATH (one of them).", "Usa --url URL o --file RUTA (uno de los dos)."), t("Example", "Ejemplo") + ": " + ex);
|
|
9845
|
+
const body = {
|
|
9846
|
+
...v.url ? { url: v.url } : { contentBase64: (await readFile6(v.file)).toString("base64") },
|
|
9847
|
+
...v.note ? { note: v.note } : {}
|
|
9848
|
+
};
|
|
9849
|
+
const r = await api(agentPath2(id, `/documents/${enc6(doc)}/versions`), { method: "POST", body });
|
|
9850
|
+
return out(r, () => `${lavender("\u2713")} ${r.name}: ${t("version", "versi\xF3n")} ${r.version?.n ?? "?"} ${dim(`(${r.size} bytes)`)}. ${dim(`${t("Undo", "Deshacer")}: ghosty agents docs restore ${id} "${r.name}" ${(r.version?.n ?? 2) - 1}`)}`);
|
|
9851
|
+
}
|
|
9852
|
+
case "history":
|
|
9853
|
+
case "versions": {
|
|
9854
|
+
doc = need2(doc, t("document (id or exact name)", "el documento (id o nombre exacto)"), 'ghosty agents docs history <agent-id> "Cat\xE1logo.pdf"');
|
|
9855
|
+
const r = await api(agentPath2(id, `/documents/${enc6(doc)}/versions`));
|
|
9856
|
+
const rows = r.versions.map((x) => ({ v: `${x.n}${x.current ? "*" : ""}`, when: x.createdAt.slice(0, 16).replace("T", " "), by: x.agent ?? x.via ?? x.source, size: String(x.size), note: x.note ?? "" }));
|
|
9857
|
+
const used = r.usedBy.map((u) => `${u.agent} (${u.mode === "write" ? t("updates it", "lo actualiza") + (u.writableFrom.length ? `: ${u.writableFrom.join(", ")}` : "") : t("reads it", "lo lee")})`).join(" \xB7 ");
|
|
9858
|
+
return out(r, () => `${r.document.name}
|
|
9859
|
+
${table(rows, ["v", "when", "by", "size", "note"])}${used ? `
|
|
9860
|
+
${dim(t("Used by", "Lo usan"))}: ${used}` : ""}`);
|
|
9861
|
+
}
|
|
9862
|
+
case "restore": {
|
|
9863
|
+
const ex = 'ghosty agents docs restore <agent-id> "Cat\xE1logo.pdf" <n>';
|
|
9864
|
+
doc = need2(doc, t("document (id or exact name)", "el documento (id o nombre exacto)"), ex);
|
|
9865
|
+
const n = Number(need2(docs[1], t("version number", "el n\xFAmero de versi\xF3n"), ex));
|
|
9866
|
+
if (!Number.isInteger(n) || n < 1) throw usage(t("The version is a whole number (see docs history).", "La versi\xF3n es un n\xFAmero entero (mira docs history)."), ex);
|
|
9867
|
+
const r = await api(agentPath2(id, `/documents/${enc6(doc)}/versions/${n}/restore`), { method: "POST", body: v.note ? { note: v.note } : {} });
|
|
9868
|
+
return out(r, () => `${lavender("\u2713")} ${r.name}: ${t(`restored from v${n} as version`, `restaurado de la v${n} como versi\xF3n`)} ${r.version?.n ?? "?"}.`);
|
|
9869
|
+
}
|
|
9754
9870
|
default:
|
|
9755
|
-
throw usage(t(`Unknown agents docs subcommand: ${sub}.`, `Subcomando de agents docs desconocido: ${sub}.`), t("Try", "Prueba") + ": ghosty agents docs ls | add | grant | revoke");
|
|
9871
|
+
throw usage(t(`Unknown agents docs subcommand: ${sub}.`, `Subcomando de agents docs desconocido: ${sub}.`), t("Try", "Prueba") + ": ghosty agents docs ls | add | grant | replace | history | restore | revoke");
|
|
9756
9872
|
}
|
|
9757
9873
|
}
|
|
9758
9874
|
function skipReason(r) {
|
|
@@ -10431,7 +10547,11 @@ var HELPS_ES = {
|
|
|
10431
10547
|
["--check PATH", "box: este archivo debe existir en la m\xE1quina (repetible), p. ej. un archivo que trae la imagen nueva"],
|
|
10432
10548
|
["--contains PATH=TEXT", "box: ese archivo debe contener TEXT (un cambio que no agrega archivos)"],
|
|
10433
10549
|
["--recycle", "box: reemplaza sus m\xE1quinas (no a media respuesta); la nueva arranca en el siguiente turno. ACP: usa restart"],
|
|
10434
|
-
["--unset-env K1,K2", "set: borra esas variables del env (--env K= tambi\xE9n borra K)"]
|
|
10550
|
+
["--unset-env K1,K2", "set: borra esas variables del env (--env K= tambi\xE9n borra K)"],
|
|
10551
|
+
["--tool [ch:]tool=on|off", "set: prende/apaga una tool en un canal (wa:actualizar_identidad=off) o en todos (crm_buscar=off); separadas por comas"],
|
|
10552
|
+
["--bag NAME|none", "set: de qu\xE9 bolsa de tokens de su espacio gasta; none = el saldo del espacio (s\xF3lo staff)"],
|
|
10553
|
+
["--avatar URL|FILE", "set: su cara en Teams: una liga https o un JPEG/PNG/WebP local (se guarda en img.ghosty.studio)"],
|
|
10554
|
+
["--from-workspace", "rm: lo desactiva en cada Teams donde est\xE9 y luego lo borra (due\xF1o o staff)"]
|
|
10435
10555
|
],
|
|
10436
10556
|
notes: [
|
|
10437
10557
|
"doctor sale con 1 cuando encuentra un problema, as\xED puede frenar un script o un CI.",
|
|
@@ -10492,7 +10612,8 @@ var HELPS_ES = {
|
|
|
10492
10612
|
["--status S", "running | done | stopped | error"],
|
|
10493
10613
|
["--since T", "30m, 1h, 2d o una fecha ISO"],
|
|
10494
10614
|
["--limit N", "m\xE1ximo de filas (default 20, m\xE1x. 500)"],
|
|
10495
|
-
["--full", "show: el mensaje que entr\xF3 y la respuesta completos (default: primeros 600 caracteres)"]
|
|
10615
|
+
["--full", "show: el mensaje que entr\xF3 y la respuesta completos (default: primeros 600 caracteres)"],
|
|
10616
|
+
["--quota-hits", "ls --workspace: mensajes (WhatsApp Business y Teams) que no abrieron turno porque se acab\xF3 el saldo"]
|
|
10496
10617
|
],
|
|
10497
10618
|
notes: ["--skipped guarda 30 d\xEDas y s\xF3lo ve lo que Formmy reenvi\xF3: si Formmy no reenv\xEDa (ghosty channels whatsapp ls, \xABformmy forwards\xBB), a gs nunca le llega el mensaje.", "show imprime lo que entr\xF3 y lo que sali\xF3 en cualquier motor o canal (grupos de WhatsApp tambi\xE9n); la l\xEDnea de tiempo lee el transcript del worker, as\xED que s\xF3lo motores del pool (no ACP/goose)."]
|
|
10498
10619
|
},
|
|
@@ -10656,8 +10777,40 @@ var HELPS_ES = {
|
|
|
10656
10777
|
]
|
|
10657
10778
|
},
|
|
10658
10779
|
usage: {
|
|
10659
|
-
summary: "Tu plan y cu\xE1nto llevas usado esta semana y este mes. Con --
|
|
10660
|
-
flags: [
|
|
10780
|
+
summary: "Tu plan y cu\xE1nto llevas usado esta semana y este mes. Con --workspace: turnos y tokens facturables de un espacio por agente y/o por d\xEDa. Con --boxes: sus cajas contra el tope.",
|
|
10781
|
+
flags: [
|
|
10782
|
+
["--boxes", "las cajas del espacio contra su tope (spares, dormidas) y, por agente: conversaciones por caja, cajas, despiertas, spares, turnos corriendo ahora"],
|
|
10783
|
+
["--workspace SLUG", "qu\xE9 espacio (due\xF1o o staff); con --boxes el default es el de ghosty use"],
|
|
10784
|
+
["--by agent|day|agent,day", "--workspace: filas por agente (default), por d\xEDa (CDMX) o las dos"],
|
|
10785
|
+
["--days N", "--workspace: cu\xE1ntos d\xEDas hacia atr\xE1s (default 14, m\xE1x. 92)"]
|
|
10786
|
+
],
|
|
10787
|
+
notes: ["failed = turnos que terminaron en error o se detuvieron. \xABno balance\xBB = mensajes que no abrieron turno porque se acab\xF3 la bolsa (ghosty turns ls --workspace X --quota-hits los lista)."]
|
|
10788
|
+
},
|
|
10789
|
+
engines: {
|
|
10790
|
+
summary: "Motores y modelos que puede usar un agente: los valores v\xE1lidos de --engine y --model, el default y cu\xE1nto cuesta cada modelo contra el saldo.",
|
|
10791
|
+
flags: [["--models", "cada modelo de cada motor con su nombre, multiplicador de costo y si necesita tu propia llave"]]
|
|
10792
|
+
},
|
|
10793
|
+
bags: {
|
|
10794
|
+
summary: "Las bolsas de tokens de un espacio: un tope con nombre del que gastan uno o varios agentes. Mensual (se recarga cada periodo) o de carga \xFAnica (cuenta desde que se cre\xF3 y no se recarga). Leer: due\xF1o o staff; cambiar: staff de Ghosty.",
|
|
10795
|
+
flags: [
|
|
10796
|
+
["--tokens N", "tokens facturables: 20M, 500k o un n\xFAmero"],
|
|
10797
|
+
["--recurring on|off", "on = se recarga cada periodo (default); off = carga \xFAnica, cuenta desde hoy"],
|
|
10798
|
+
["--hard-cap on|off", "on = al 100% los turnos se frenan con \xABse acab\xF3 el saldo\xBB (default); off = s\xF3lo cuenta"],
|
|
10799
|
+
["--note TEXT", "qu\xE9 se pact\xF3 y qui\xE9n lo autoriz\xF3"],
|
|
10800
|
+
["--name NEW", "set: renombra la bolsa (el cliente la ve en Ajustes \u2192 Uso)"],
|
|
10801
|
+
["--yes", "rm: s\xE1ltate la confirmaci\xF3n (obligatorio sin terminal)"]
|
|
10802
|
+
],
|
|
10803
|
+
notes: ["Un agente sin bolsa gasta del saldo del espacio. rm s\xF3lo borra una bolsa que ning\xFAn agente usa: mu\xE9velos primero (agents set --bag)."]
|
|
10804
|
+
},
|
|
10805
|
+
spaces: {
|
|
10806
|
+
summary: "Operaciones del staff de Ghosty sobre un espacio: mover \xABpagado hasta\xBB y renombrar su slug. Cada una queda en la bit\xE1cora del espacio.",
|
|
10807
|
+
flags: [
|
|
10808
|
+
["--until DATE", "renew: el nuevo \xABpagado hasta\xBB (AAAA-MM-DD)"],
|
|
10809
|
+
["--note TEXT", "renew: por qu\xE9 (va a la bit\xE1cora)"],
|
|
10810
|
+
["-n, --dry-run", "ense\xF1a qu\xE9 cambia, no cambia nada"],
|
|
10811
|
+
["--yes", "s\xE1ltate la confirmaci\xF3n (obligatorio sin terminal)"]
|
|
10812
|
+
],
|
|
10813
|
+
notes: ["Con suscripci\xF3n de Stripe renew se niega: la fecha la mueve la factura pagada.", "rename cambia s\xF3lo el slug (id y namespace se quedan, as\xED que tablero, Stripe y WhatsApp siguen funcionando); las ligas viejas <slug>.sales dejan de servir."]
|
|
10661
10814
|
}
|
|
10662
10815
|
};
|
|
10663
10816
|
var HELP_ALIASES = { creds: "credentials", board: "cards", conversations: "chat" };
|
|
@@ -10673,7 +10826,7 @@ var HELPS = {
|
|
|
10673
10826
|
"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]]",
|
|
10674
10827
|
"ghosty agents get <agent-id> [--fields name,model,prompt] [--prompt-out FILE]",
|
|
10675
10828
|
"ghosty agents export <agent-id> [--out DIR] # the agent as a file; see ghosty apply --help",
|
|
10676
|
-
"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]",
|
|
10829
|
+
"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]",
|
|
10677
10830
|
"ghosty agents set <agent-id> --prompt-base <base-agent-id> --prompt-overlay overlay.json [--dry-run [--out FILE]] | --prompt-base none",
|
|
10678
10831
|
"ghosty agents doctor <agent-id> [--verbose]",
|
|
10679
10832
|
"ghosty agents docs ls|grant|revoke <agent-id> [doc\u2026] [--write] [--no-deliver] [--like AGENT]",
|
|
@@ -10685,7 +10838,7 @@ var HELPS = {
|
|
|
10685
10838
|
"ghosty agents box <agent-id> [--check PATH]\u2026 [--contains PATH=TEXT]\u2026 [--recycle]",
|
|
10686
10839
|
"ghosty agents demo enable|disable <agent-id>",
|
|
10687
10840
|
'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]',
|
|
10688
|
-
"ghosty agents rm <agent-id> [--yes]"
|
|
10841
|
+
"ghosty agents rm <agent-id> [--from-workspace] [--yes]"
|
|
10689
10842
|
],
|
|
10690
10843
|
flags: [
|
|
10691
10844
|
["--fields a,b", "get: return only these fields"],
|
|
@@ -10721,7 +10874,11 @@ var HELPS = {
|
|
|
10721
10874
|
["--check PATH", "box: this file must exist in the machine (repeatable), e.g. a file the new image brings"],
|
|
10722
10875
|
["--contains PATH=TEXT", "box: that file must contain TEXT (a change that adds no files)"],
|
|
10723
10876
|
["--recycle", "box: replace its machines (not mid-turn); the new one starts on the next turn. ACP: use restart"],
|
|
10724
|
-
["--unset-env K1,K2", "set: delete these env variables (--env K= also deletes K)"]
|
|
10877
|
+
["--unset-env K1,K2", "set: delete these env variables (--env K= also deletes K)"],
|
|
10878
|
+
["--tool [ch:]tool=on|off", "set: one tool on/off in one channel (wa:actualizar_identidad=off) or everywhere (crm_buscar=off); comma-separated"],
|
|
10879
|
+
["--bag NAME|none", "set: the token bag of its workspace it spends from; none = the workspace balance (staff only)"],
|
|
10880
|
+
["--avatar URL|FILE", "set: its face in Teams: an https link or a local JPEG/PNG/WebP (stored at img.ghosty.studio)"],
|
|
10881
|
+
["--from-workspace", "rm: turn it off in every Teams where it's active, then delete it (owner or staff)"]
|
|
10725
10882
|
],
|
|
10726
10883
|
examples: [
|
|
10727
10884
|
"ghosty agents doctor cmu7pgbyr0001gee3ruk172mm",
|
|
@@ -10740,7 +10897,10 @@ var HELPS = {
|
|
|
10740
10897
|
"ghosty agents set <pia-id> --prompt-base <tania-id> --prompt-overlay capa.json --dry-run --out /tmp/pia.md",
|
|
10741
10898
|
"ghosty agents demo enable <id>",
|
|
10742
10899
|
'ghosty agents demo <id> --slug guardias --expires "2026-10-15T20:00" --starter "Ens\xE9\xF1ame c\xF3mo empezar"',
|
|
10743
|
-
"ghosty agents box <id> --contains /opt/claude-worker/skills/pdf-doc/render-doc.mjs=pdf-templates"
|
|
10900
|
+
"ghosty agents box <id> --contains /opt/claude-worker/skills/pdf-doc/render-doc.mjs=pdf-templates",
|
|
10901
|
+
"ghosty agents set <id> --tool wa:actualizar_identidad=off,teams:actualizar_identidad=on",
|
|
10902
|
+
'ghosty agents set minighosty --bag "MiniGhosty" --avatar https://www.ghosty.studio/avatars/minighosty.png',
|
|
10903
|
+
"ghosty agents rm fable --from-workspace"
|
|
10744
10904
|
],
|
|
10745
10905
|
notes: ["doctor exits 1 when it finds a problem, so it can gate a script or CI.", "A derived prompt can't be edited directly (409): change the base or the overlay, or unlink it with --prompt-base none.", "demo with no flags only prints its status: link, expiry, caps and numbers."],
|
|
10746
10906
|
learn: `${REF}#agentes`
|
|
@@ -10826,16 +10986,17 @@ var HELPS = {
|
|
|
10826
10986
|
},
|
|
10827
10987
|
turns: {
|
|
10828
10988
|
summary: "An agent's recent turns: what answered, what failed and why, and where one turn's time went.",
|
|
10829
|
-
usage: ["ghosty turns ls <agent-id> [--errors | --status STATE | --skipped] [--since 1h] [--limit N]", "ghosty turns show <agent-id> [turn-id | last] [--full]"],
|
|
10989
|
+
usage: ["ghosty turns ls <agent-id> [--errors | --status STATE | --skipped] [--since 1h] [--limit N]", "ghosty turns ls --workspace <slug> --quota-hits [--days 14]", "ghosty turns show <agent-id> [turn-id | last] [--full]"],
|
|
10830
10990
|
flags: [
|
|
10831
10991
|
["--errors", "only failed turns (same as --status error)"],
|
|
10832
10992
|
["--skipped", "ls: WhatsApp Business customer messages that did NOT open a turn, and why (operator active, records only, paused, reaction, channel off, no balance)"],
|
|
10833
10993
|
["--status S", "running | done | stopped | error"],
|
|
10834
10994
|
["--since T", "30m, 1h, 2d or an ISO date"],
|
|
10835
10995
|
["--limit N", "max rows (default 20, max 500)"],
|
|
10836
|
-
["--full", "show: the whole message that came in and the whole answer (default: first 600 chars)"]
|
|
10996
|
+
["--full", "show: the whole message that came in and the whole answer (default: first 600 chars)"],
|
|
10997
|
+
["--quota-hits", "ls --workspace: messages (WhatsApp Business and Teams) that got no turn because the balance ran out"]
|
|
10837
10998
|
],
|
|
10838
|
-
examples: ["ghosty turns ls <id> --errors --since 1h", "ghosty turns ls <id> --skipped --since 1d", "ghosty turns ls <id> --json --limit 100", "ghosty turns show <id> # last turn: each tool, its seconds, output tokens"],
|
|
10999
|
+
examples: ["ghosty turns ls <id> --errors --since 1h", "ghosty turns ls --workspace descti --quota-hits --days 30", "ghosty turns ls <id> --skipped --since 1d", "ghosty turns ls <id> --json --limit 100", "ghosty turns show <id> # last turn: each tool, its seconds, output tokens"],
|
|
10839
11000
|
notes: ["--skipped keeps 30 days and only sees what Formmy forwarded: if Formmy doesn't forward (ghosty channels whatsapp ls, \xABformmy forwards\xBB), gs never gets the message.", "show prints what came in and what went out on any engine or channel (WhatsApp groups too); the timeline reads the worker's transcript, so pool engines only (not ACP/goose)."],
|
|
10840
11001
|
learn: `${REF}#turnos`
|
|
10841
11002
|
},
|
|
@@ -11084,12 +11245,58 @@ HELPS.board = HELPS.cards;
|
|
|
11084
11245
|
HELPS.conversations = HELPS.chat;
|
|
11085
11246
|
HELPS.usage = {
|
|
11086
11247
|
...HELPS.me,
|
|
11087
|
-
summary: "Your plan and how much you've used this week and month. With --
|
|
11088
|
-
usage: ["ghosty usage", "ghosty me usage", "ghosty usage --boxes [--workspace SLUG]"],
|
|
11089
|
-
flags: [
|
|
11090
|
-
|
|
11248
|
+
summary: "Your plan and how much you've used this week and month. With --workspace: a workspace's turns and billable tokens per agent and/or day. With --boxes: its boxes against its limit.",
|
|
11249
|
+
usage: ["ghosty usage", "ghosty me usage", "ghosty usage --workspace SLUG [--by agent|day|agent,day] [--days 14]", "ghosty usage --boxes [--workspace SLUG]"],
|
|
11250
|
+
flags: [
|
|
11251
|
+
["--boxes", "the workspace's boxes against its limit (spares, asleep) and, per agent: conversations per box, boxes, awake, spares, turns running now"],
|
|
11252
|
+
["--workspace SLUG", "which workspace (owner or staff); with --boxes the default is the one from ghosty use"],
|
|
11253
|
+
["--by agent|day|agent,day", "--workspace: rows per agent (default), per day (CDMX) or both"],
|
|
11254
|
+
["--days N", "--workspace: how far back (default 14, max 92)"]
|
|
11255
|
+
],
|
|
11256
|
+
examples: ["ghosty usage", "ghosty usage --json", "ghosty usage --workspace descti --by agent,day --days 14", "ghosty usage --boxes --workspace siiqtec"],
|
|
11257
|
+
notes: ["failed = turns that ended in error or were stopped. \xABno balance\xBB = messages that got no turn because the bag ran out (ghosty turns ls --workspace X --quota-hits lists them)."],
|
|
11091
11258
|
learn: `${REF}#uso`
|
|
11092
11259
|
};
|
|
11260
|
+
HELPS.engines = {
|
|
11261
|
+
summary: "Engines and models an agent can use: the valid values of --engine and --model, the default, and what each model costs against the balance.",
|
|
11262
|
+
usage: ["ghosty engines ls [--models]"],
|
|
11263
|
+
flags: [["--models", "every model of each engine with its label, cost multiplier and whether it needs your own key"]],
|
|
11264
|
+
examples: ["ghosty engines ls", "ghosty engines ls --models", "ghosty engines ls --json"],
|
|
11265
|
+
learn: `${REF}#agentes`
|
|
11266
|
+
};
|
|
11267
|
+
HELPS.bags = {
|
|
11268
|
+
summary: "A workspace's token bags: a named top that one or more agents spend from. Monthly (refills every period) or one-time (counts from when it was created and never refills). Reading: owner or staff; changing: Ghosty staff.",
|
|
11269
|
+
usage: [
|
|
11270
|
+
"ghosty bags ls <workspace>",
|
|
11271
|
+
'ghosty bags create <workspace> "<bag>" --tokens 20M [--recurring on|off] [--hard-cap on|off] [--note TEXT]',
|
|
11272
|
+
'ghosty bags set <workspace> "<bag>" [--tokens 30M] [--recurring on|off] [--hard-cap on|off] [--note TEXT] [--name NEW]',
|
|
11273
|
+
'ghosty bags rm <workspace> "<bag>" [--yes]'
|
|
11274
|
+
],
|
|
11275
|
+
flags: [
|
|
11276
|
+
["--tokens N", "billable tokens: 20M, 500k or a plain number"],
|
|
11277
|
+
["--recurring on|off", "on = refills every period (default); off = one-time, counts from today"],
|
|
11278
|
+
["--hard-cap on|off", "on = at 100% turns stop with \xABout of balance\xBB (default); off = only counts"],
|
|
11279
|
+
["--note TEXT", "what was agreed and who approved it"],
|
|
11280
|
+
["--name NEW", "set: rename the bag (customers see it in Settings \u2192 Usage)"],
|
|
11281
|
+
["--yes", "rm: skip the confirmation (required without a terminal)"]
|
|
11282
|
+
],
|
|
11283
|
+
examples: ["ghosty bags ls descti", 'ghosty bags create descti "MiniGhosty" --tokens 20M --note "pactado 30-sep"', 'ghosty bags set descti "Astra" --recurring off', 'ghosty agents set minighosty --bag "MiniGhosty"'],
|
|
11284
|
+
notes: ["An agent with no bag spends from the workspace balance. rm only deletes a bag no agent uses: move them first (agents set --bag)."],
|
|
11285
|
+
learn: `${REF}#espacios`
|
|
11286
|
+
};
|
|
11287
|
+
HELPS.spaces = {
|
|
11288
|
+
summary: "Ghosty staff operations on a workspace: move \xABpaid until\xBB and rename its slug. Each one is logged in the workspace's audit.",
|
|
11289
|
+
usage: ["ghosty spaces renew <workspace> --until YYYY-MM-DD [--note TEXT] [--dry-run] [--yes]", "ghosty spaces rename <old> <new> [--dry-run] [--yes]"],
|
|
11290
|
+
flags: [
|
|
11291
|
+
["--until DATE", "renew: the new \xABpaid until\xBB (YYYY-MM-DD)"],
|
|
11292
|
+
["--note TEXT", "renew: why (goes into the audit)"],
|
|
11293
|
+
["-n, --dry-run", "show what changes, change nothing"],
|
|
11294
|
+
["--yes", "skip the confirmation (required without a terminal)"]
|
|
11295
|
+
],
|
|
11296
|
+
examples: ['ghosty spaces renew descti --until 2026-11-01 --note "pag\xF3 octubre"', "ghosty spaces rename totequim siiqtec --dry-run"],
|
|
11297
|
+
notes: ["With a Stripe subscription renew refuses: the paid invoice moves the date.", "rename changes only the slug (id and namespace stay, so board, Stripe and WhatsApp keep working); old <slug>.sales links stop working."],
|
|
11298
|
+
learn: `${REF}#espacios`
|
|
11299
|
+
};
|
|
11093
11300
|
function helpText(cmd) {
|
|
11094
11301
|
const base = HELPS[cmd];
|
|
11095
11302
|
if (!base) return void 0;
|
|
@@ -11467,6 +11674,171 @@ ${table(rows, ["agent", "engine", "per box", "boxes", "on", "spares", "running n
|
|
|
11467
11674
|
${dim(t(`${r.totals.activeTurns} turn(s) running`, `${r.totals.activeTurns} turno(s) corriendo`))}`);
|
|
11468
11675
|
}
|
|
11469
11676
|
|
|
11677
|
+
// src/commands/spaces.ts
|
|
11678
|
+
var enc10 = encodeURIComponent;
|
|
11679
|
+
var wsPath = (slug, rest = "") => `/api/v2/workspaces/${enc10(slug)}${rest}`;
|
|
11680
|
+
var needSpace = (slug, example) => {
|
|
11681
|
+
if (!slug) throw usage(t("Which workspace?", "\xBFQu\xE9 espacio?"), `${t("Example", "Ejemplo")}: ${example} ${t("(or ghosty use <workspace>)", "(o ghosty use <espacio>)")}`);
|
|
11682
|
+
return slug;
|
|
11683
|
+
};
|
|
11684
|
+
function parseTokens(raw) {
|
|
11685
|
+
const m = /^\s*(\d+(?:\.\d+)?)\s*([kKmM]?)\s*$/.exec(raw);
|
|
11686
|
+
if (!m) throw usage(t(`Bad token amount: ${raw}.`, `Cantidad de tokens inv\xE1lida: ${raw}.`), t("Use 20M, 500k or a plain number.", "Usa 20M, 500k o un n\xFAmero."));
|
|
11687
|
+
const n = Number(m[1]) * (m[2].toLowerCase() === "m" ? 1e6 : m[2].toLowerCase() === "k" ? 1e3 : 1);
|
|
11688
|
+
return Math.round(n);
|
|
11689
|
+
}
|
|
11690
|
+
function fmtTokens(n) {
|
|
11691
|
+
if (n >= 1e6) return `${+(n / 1e6).toFixed(n % 1e6 === 0 ? 0 : 1)}M`;
|
|
11692
|
+
if (n >= 1e3) return `${Math.round(n / 1e3)}k`;
|
|
11693
|
+
return String(n);
|
|
11694
|
+
}
|
|
11695
|
+
function parseSwitch(flag, raw) {
|
|
11696
|
+
if (raw === void 0) return void 0;
|
|
11697
|
+
if (/^(on|true|yes|s[ií])$/i.test(raw)) return true;
|
|
11698
|
+
if (/^(off|false|no)$/i.test(raw)) return false;
|
|
11699
|
+
throw usage(t(`${flag} takes on or off.`, `${flag} acepta on u off.`));
|
|
11700
|
+
}
|
|
11701
|
+
async function enginesCmd(sub, v) {
|
|
11702
|
+
if (sub && sub !== "ls" && sub !== "list") throw usage(t(`Unknown engines subcommand: ${sub}.`, `Subcomando de engines desconocido: ${sub}.`), "ghosty engines ls [--models]");
|
|
11703
|
+
const r = await api("/api/v2/engines");
|
|
11704
|
+
out(r, () => {
|
|
11705
|
+
if (!v.models) {
|
|
11706
|
+
const rows = r.engines.map((e) => ({ engine: e.id, provider: e.provider, default: e.defaultModel ?? "", models: e.models.map((m) => m.id).join(", ") }));
|
|
11707
|
+
return `${table(rows, ["engine", "provider", "default"])}
|
|
11708
|
+
${dim(t("Models per engine: ghosty engines ls --models", "Modelos de cada motor: ghosty engines ls --models"))}`;
|
|
11709
|
+
}
|
|
11710
|
+
return r.engines.map((e) => {
|
|
11711
|
+
const rows = e.models.map((m) => ({ model: m.id + (m.id === e.defaultModel ? " *" : ""), label: m.label, cost: `${m.multiplier}\xD7`, note: m.ownKeyOnly ? t("your own key only", "s\xF3lo con llave propia") : "" }));
|
|
11712
|
+
return `${bold(e.id)} ${dim(`${e.provider} \xB7 ${e.protocol}${e.vision ? t(" \xB7 sees images", " \xB7 ve im\xE1genes") : ""}`)}
|
|
11713
|
+
${table(rows, ["model", "label", "cost", "note"])}`;
|
|
11714
|
+
}).join("\n\n") + `
|
|
11715
|
+
${dim(t("* default \xB7 cost = balance multiplier per turn", "* por default \xB7 costo = multiplicador de saldo por turno"))}`;
|
|
11716
|
+
});
|
|
11717
|
+
}
|
|
11718
|
+
function bagsText(r) {
|
|
11719
|
+
const rows = r.bags.map((b) => ({
|
|
11720
|
+
bag: b.name,
|
|
11721
|
+
tokens: fmtTokens(b.tokens),
|
|
11722
|
+
used: `${fmtTokens(b.used)} (${Math.round(b.ratio * 100)}%)`,
|
|
11723
|
+
kind: b.recurring ? t("monthly", "mensual") : t(`one-time since ${b.since?.slice(0, 10)}`, `carga \xFAnica desde ${b.since?.slice(0, 10)}`),
|
|
11724
|
+
cap: b.hardCap ? t("cuts", "corta") : t("warns", "avisa"),
|
|
11725
|
+
agents: b.agents.map((a) => a.name ?? a.id).join(", ") || "\u2014"
|
|
11726
|
+
}));
|
|
11727
|
+
const free = r.unbagged.length ? `
|
|
11728
|
+
${dim(t(`Workspace balance (no bag): ${r.unbagged.map((a) => a.name ?? a.id).join(", ")}`, `Saldo del espacio (sin bolsa): ${r.unbagged.map((a) => a.name ?? a.id).join(", ")}`))}` : "";
|
|
11729
|
+
return `${bold(r.workspace)}
|
|
11730
|
+
${r.bags.length ? table(rows, ["bag", "tokens", "used", "kind", "cap", "agents"]) : dim(t("No bags: every agent uses the workspace balance.", "Sin bolsas: todos los agentes usan el saldo del espacio."))}${free}`;
|
|
11731
|
+
}
|
|
11732
|
+
async function bagsCmd(sub, slug, name, v, context) {
|
|
11733
|
+
const ex = 'ghosty bags ls <workspace> \xB7 create <workspace> "<bag>" --tokens 20M [--recurring off] \xB7 set <workspace> "<bag>" --tokens 30M \xB7 rm <workspace> "<bag>"';
|
|
11734
|
+
if (sub && !["ls", "list", "create", "new", "set", "rm", "delete"].includes(sub)) {
|
|
11735
|
+
name = slug;
|
|
11736
|
+
slug = sub;
|
|
11737
|
+
sub = "ls";
|
|
11738
|
+
}
|
|
11739
|
+
slug = needSpace(slug ?? context, ex);
|
|
11740
|
+
const recurring = parseSwitch("--recurring", v.recurring);
|
|
11741
|
+
const hardCap = parseSwitch("--hard-cap", v["hard-cap"]);
|
|
11742
|
+
switch (sub ?? "ls") {
|
|
11743
|
+
case "ls":
|
|
11744
|
+
case "list": {
|
|
11745
|
+
const r = await api(wsPath(slug, "/bags"));
|
|
11746
|
+
return out(r, () => bagsText(r));
|
|
11747
|
+
}
|
|
11748
|
+
case "create":
|
|
11749
|
+
case "new": {
|
|
11750
|
+
if (!name) throw usage(t("Missing the bag's name.", "Falta el nombre de la bolsa."), ex);
|
|
11751
|
+
if (!v.tokens) throw usage(t("Missing --tokens.", "Falta --tokens."), `ghosty bags create ${slug} "${name}" --tokens 20M`);
|
|
11752
|
+
const r = await api(wsPath(slug, "/bags"), { method: "POST", body: { name, tokens: parseTokens(v.tokens), ...recurring !== void 0 ? { recurring } : {}, ...hardCap !== void 0 ? { hardCap } : {}, ...v.note ? { note: v.note } : {} } });
|
|
11753
|
+
return out(r, () => `${lavender("\u2713")} ${t(`Bag \xAB${name}\xBB created.`, `Bolsa \xAB${name}\xBB creada.`)} ${dim(t(`Give it agents: ghosty agents set <agent> --bag "${name}"`, `Cu\xE9lgale agentes: ghosty agents set <agente> --bag "${name}"`))}
|
|
11754
|
+
${bagsText(r)}`);
|
|
11755
|
+
}
|
|
11756
|
+
case "set": {
|
|
11757
|
+
if (!name) throw usage(t("Missing the bag's name.", "Falta el nombre de la bolsa."), ex);
|
|
11758
|
+
const body = { ...v.tokens ? { tokens: parseTokens(v.tokens) } : {}, ...recurring !== void 0 ? { recurring } : {}, ...hardCap !== void 0 ? { hardCap } : {}, ...v.note !== void 0 ? { note: v.note } : {}, ...v.name ? { name: v.name } : {} };
|
|
11759
|
+
if (!Object.keys(body).length) throw usage(t("Nothing to change.", "Nada que cambiar."), "--tokens 30M | --recurring on|off | --hard-cap on|off | --note TEXT | --name NEW");
|
|
11760
|
+
const r = await api(wsPath(slug, `/bags/${enc10(name)}`), { method: "PATCH", body });
|
|
11761
|
+
return out(r, () => `${lavender("\u2713")} ${t(`Bag \xAB${r.updated}\xBB updated.`, `Bolsa \xAB${r.updated}\xBB actualizada.`)}
|
|
11762
|
+
${bagsText(r)}`);
|
|
11763
|
+
}
|
|
11764
|
+
case "rm":
|
|
11765
|
+
case "delete": {
|
|
11766
|
+
if (!name) throw usage(t("Missing the bag's name.", "Falta el nombre de la bolsa."), ex);
|
|
11767
|
+
requireYesIfHeadless(v.yes, `ghosty bags rm ${slug} "${name}"`);
|
|
11768
|
+
await confirm(t(`Delete bag \xAB${name}\xBB from ${slug}? (only if no agent uses it)`, `\xBFBorrar la bolsa \xAB${name}\xBB de ${slug}? (s\xF3lo si ning\xFAn agente la usa)`), { yes: v.yes, retry: `ghosty bags rm ${slug} "${name}"` });
|
|
11769
|
+
const r = await api(wsPath(slug, `/bags/${enc10(name)}`), { method: "DELETE" });
|
|
11770
|
+
return out(r, () => `${lavender("\u2713")} ${t(`Bag \xAB${r.deleted}\xBB deleted.`, `Bolsa \xAB${r.deleted}\xBB borrada.`)}
|
|
11771
|
+
${bagsText(r)}`);
|
|
11772
|
+
}
|
|
11773
|
+
}
|
|
11774
|
+
}
|
|
11775
|
+
async function spacesCmd(sub, slug, arg, v, context) {
|
|
11776
|
+
const ex = "ghosty spaces renew <workspace> --until 2026-11-01 [--note TEXT] \xB7 rename <old> <new> [--dry-run]";
|
|
11777
|
+
if (sub === "renew") {
|
|
11778
|
+
slug = needSpace(slug ?? context, ex);
|
|
11779
|
+
if (!v.until) throw usage(t("Missing --until YYYY-MM-DD.", "Falta --until AAAA-MM-DD."), `ghosty spaces renew ${slug} --until 2026-11-01`);
|
|
11780
|
+
const path = wsPath(slug, "/renew");
|
|
11781
|
+
const plan = await api(path, { method: "POST", body: { until: v.until, dryRun: true } });
|
|
11782
|
+
const line2 = t(`${plan.workspace} (${plan.plan}): paid until ${plan.from ?? "\u2014"} \u2192 ${plan.to}`, `${plan.workspace} (${plan.plan}): pagado hasta ${plan.from ?? "\u2014"} \u2192 ${plan.to}`);
|
|
11783
|
+
if (v["dry-run"]) return out(plan, () => `${line2}
|
|
11784
|
+
${dim(t("Dry run: nothing changed.", "Simulaci\xF3n: no cambi\xF3 nada."))}`);
|
|
11785
|
+
requireYesIfHeadless(v.yes, `ghosty spaces renew ${slug} --until ${v.until}`);
|
|
11786
|
+
await confirm(`${line2}
|
|
11787
|
+
${t("Renew?", "\xBFRenovar?")}`, { yes: v.yes, retry: `ghosty spaces renew ${slug} --until ${v.until}` });
|
|
11788
|
+
const r = await api(path, { method: "POST", body: { until: v.until, ...v.note ? { note: v.note } : {} } });
|
|
11789
|
+
return out(r, () => `${lavender("\u2713")} ${line2} ${dim(t("(logged in the workspace's audit)", "(queda en la bit\xE1cora del espacio)"))}`);
|
|
11790
|
+
}
|
|
11791
|
+
if (sub === "rename") {
|
|
11792
|
+
slug = needSpace(slug, ex);
|
|
11793
|
+
if (!arg) throw usage(t("Missing the new name.", "Falta el nombre nuevo."), `ghosty spaces rename ${slug} <new> --dry-run`);
|
|
11794
|
+
const path = wsPath(slug, "/rename");
|
|
11795
|
+
const plan = await api(path, { method: "POST", body: { to: arg, dryRun: true } });
|
|
11796
|
+
const text = `${plan.from} \u2192 ${bold(plan.to)}
|
|
11797
|
+
${plan.warnings.map((w) => dim(` \xB7 ${serverText(w)}`)).join("\n")}`;
|
|
11798
|
+
if (v["dry-run"]) return out(plan, () => `${text}
|
|
11799
|
+
${dim(t("Dry run: nothing changed.", "Simulaci\xF3n: no cambi\xF3 nada."))}`);
|
|
11800
|
+
requireYesIfHeadless(v.yes, `ghosty spaces rename ${slug} ${arg}`);
|
|
11801
|
+
await confirm(`${text}
|
|
11802
|
+
${t("Rename?", "\xBFRenombrar?")}`, { yes: v.yes, expect: plan.from, retry: `ghosty spaces rename ${slug} ${arg}` });
|
|
11803
|
+
const r = await api(path, { method: "POST", body: { to: arg } });
|
|
11804
|
+
return out(r, () => `${lavender("\u2713")} ${t(`Renamed: ${r.from} \u2192 ${r.to}. Undo: ghosty spaces rename ${r.to} ${r.from}`, `Renombrado: ${r.from} \u2192 ${r.to}. Deshacer: ghosty spaces rename ${r.to} ${r.from}`)}`);
|
|
11805
|
+
}
|
|
11806
|
+
throw usage(t(`Unknown spaces subcommand: ${sub ?? "(none)"}.`, `Subcomando de spaces desconocido: ${sub ?? "(ninguno)"}.`), ex);
|
|
11807
|
+
}
|
|
11808
|
+
async function workspaceUsage(slug, v) {
|
|
11809
|
+
slug = needSpace(slug, "ghosty usage --workspace <slug> --by agent,day --days 14");
|
|
11810
|
+
const r = await api(wsPath(slug, `/usage?days=${enc10(v.days ?? "14")}`));
|
|
11811
|
+
const cdmx = (iso) => new Date(Date.parse(iso) - 6 * 36e5).toISOString().slice(5, 16).replace("T", " ");
|
|
11812
|
+
if (v["quota-hits"]) {
|
|
11813
|
+
const rows2 = r.quotaHits.map((q) => ({ at: cdmx(q.at), agent: q.agent, channel: q.channel, from: q.phone ?? q.conversationId.slice(0, 24), message: (q.preview ?? "").slice(0, 60) }));
|
|
11814
|
+
const total2 = r.agents.reduce((n, a) => n + a.noQuota, 0);
|
|
11815
|
+
return out(r.quotaHits, () => rows2.length ? `${table(rows2, ["at", "agent", "channel", "from", "message"])}
|
|
11816
|
+
${dim(t(`${total2} message(s) unanswered for lack of balance in ${r.days} day(s) (CDMX time)`, `${total2} mensaje(s) sin atender por saldo en ${r.days} d\xEDa(s) (hora CDMX)`))}` : dim(t(`No message ran out of balance in ${r.days} day(s).`, `Ning\xFAn mensaje se qued\xF3 sin saldo en ${r.days} d\xEDa(s).`)));
|
|
11817
|
+
}
|
|
11818
|
+
const by = new Set((v.by ?? "agent").split(",").map((x) => x.trim()));
|
|
11819
|
+
if ([...by].some((b) => b !== "agent" && b !== "day")) throw usage(t("--by takes agent, day or agent,day.", "--by acepta agent, day o agent,day."));
|
|
11820
|
+
let rows;
|
|
11821
|
+
const fmt2 = (x) => ({ turns: x.turns, failed: x.failed ? red(String(x.failed)) : 0, tokens: fmtTokens(x.billable), "no balance": x.noQuota || "" });
|
|
11822
|
+
if (by.has("day") && by.has("agent")) rows = r.rows.map((x) => ({ day: x.day, agent: x.agent, ...fmt2(x) }));
|
|
11823
|
+
else if (by.has("day")) {
|
|
11824
|
+
const days = /* @__PURE__ */ new Map();
|
|
11825
|
+
for (const x of r.rows) {
|
|
11826
|
+
const d = days.get(x.day) ?? { agentId: "", agent: "", turns: 0, failed: 0, billable: 0, outputTokens: 0, cacheRead: 0, noQuota: 0 };
|
|
11827
|
+
d.turns += x.turns;
|
|
11828
|
+
d.failed += x.failed;
|
|
11829
|
+
d.billable += x.billable;
|
|
11830
|
+
d.noQuota += x.noQuota;
|
|
11831
|
+
days.set(x.day, d);
|
|
11832
|
+
}
|
|
11833
|
+
rows = [...days].map(([day, x]) => ({ day, ...fmt2(x) }));
|
|
11834
|
+
} else rows = r.agents.map((x) => ({ agent: x.agent, ...fmt2(x) }));
|
|
11835
|
+
const cols = [...by.has("day") ? ["day"] : [], ...by.has("agent") ? ["agent"] : [], "turns", "failed", "tokens", "no balance"];
|
|
11836
|
+
const total = r.agents.reduce((n, a) => n + a.billable, 0);
|
|
11837
|
+
out(r, () => `${bold(r.workspace)} ${dim(t(`last ${r.days} day(s), CDMX days`, `\xFAltimos ${r.days} d\xEDa(s), d\xEDas en hora CDMX`))}
|
|
11838
|
+
${table(rows, cols)}
|
|
11839
|
+
${dim(t(`${fmtTokens(total)} billable tokens \xB7 failed = error or stopped \xB7 no balance: ghosty turns ls --workspace ${r.workspace} --quota-hits`, `${fmtTokens(total)} tokens facturables \xB7 failed = error o detenido \xB7 sin saldo: ghosty turns ls --workspace ${r.workspace} --quota-hits`))}`);
|
|
11840
|
+
}
|
|
11841
|
+
|
|
11470
11842
|
// src/index.ts
|
|
11471
11843
|
var HELP_EN = `${bold("Usage:")} ghosty <command> [options]
|
|
11472
11844
|
|
|
@@ -11478,7 +11850,14 @@ ${bold("Session")}
|
|
|
11478
11850
|
use [workspace] Default workspace for boards, agents create and agents ls [--clear]
|
|
11479
11851
|
completion zsh|bash|fish Tab completion for commands, flags, agents and workspaces
|
|
11480
11852
|
usage Your plan and how much you've used this week and month [--boxes: boxes vs the workspace limit]
|
|
11853
|
+
usage --workspace SLUG A workspace's turns and tokens per agent/day [--by agent,day] [--days 14]
|
|
11481
11854
|
plan apply [workspace] Apply a scheduled plan now, showing what changes [--dry-run]
|
|
11855
|
+
engines ls Engines and models valid for --engine/--model [--models]
|
|
11856
|
+
|
|
11857
|
+
${bold("Workspaces")} ${dim("(changing them: Ghosty staff)")}
|
|
11858
|
+
bags ls|create|set|rm <workspace> ["bag"] Token bags its agents spend from [--tokens 20M] [--recurring on|off] [--hard-cap on|off] [--note]
|
|
11859
|
+
spaces renew <workspace> --until DATE Move \xABpaid until\xBB (logged) [--note] [--dry-run]
|
|
11860
|
+
spaces rename <old> <new> Change its slug only [--dry-run]
|
|
11482
11861
|
|
|
11483
11862
|
${bold("Agents")}
|
|
11484
11863
|
agents ls List your agents
|
|
@@ -11494,12 +11873,13 @@ ${bold("Agents")}
|
|
|
11494
11873
|
[--voice elevenlabs:ID | kokoro:em_santa] [--voice-replies auto|off] [--auto-stage on|off]
|
|
11495
11874
|
[--toolsets crm,sales | +crm,-sales] gs tool groups it gets
|
|
11496
11875
|
[--prompt-base AGENT|none --prompt-overlay FILE [--dry-run]] prompt = another agent's + edits, kept in sync
|
|
11876
|
+
[--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
|
|
11497
11877
|
agents doctor <id> Check the config: overlapping skills, tools the prompt names, broken grants, failing turns
|
|
11498
11878
|
agents docs ls|grant|revoke <id> [doc] Documents it reads and may send [--write] [--no-deliver]
|
|
11499
11879
|
agents docs grant <id> A B\u2026 Grant several documents at once [--like AGENT] copies another agent's
|
|
11500
11880
|
agents docs add <id> Upload a document and grant it --url URL | --file PATH [--description] [--grant read|write|no]
|
|
11501
11881
|
agents share|unshare <id> <email> Share it without handing it over [--role editor|admin] \xB7 agents shares <id> lists them
|
|
11502
|
-
agents rm <id> Delete an agent (owner only; asks you to type its name, or --yes)
|
|
11882
|
+
agents rm <id> Delete an agent (owner only; asks you to type its name, or --yes) [--from-workspace: turn it off in Teams first]
|
|
11503
11883
|
agents restart <id> Restart an ACP agent (needsRestart) so it loads new skills
|
|
11504
11884
|
agents box <id> The machine serving it: is it on the new image? [--check PATH]\u2026 [--contains PATH=TEXT]\u2026 [--recycle]
|
|
11505
11885
|
agents demo enable|disable <id> Turn its public demo link on or off
|
|
@@ -11536,6 +11916,7 @@ ${bold("Talk to an agent")}
|
|
|
11536
11916
|
conversations ls <id> List conversations
|
|
11537
11917
|
conversations show <id> <conversation> What was said [--limit 20] (0 = all)
|
|
11538
11918
|
turns ls <id> Recent turns and why they failed [--errors] [--skipped] [--since 1h] [--limit N]
|
|
11919
|
+
turns ls --workspace SLUG --quota-hits Messages that got no turn because the balance ran out [--days 14]
|
|
11539
11920
|
turns show <id> [turn] What came in, what it answered, and where the time went (default: last) [--full]
|
|
11540
11921
|
|
|
11541
11922
|
${bold("Schedule turns")} ${dim("(each run lands in a conversation and notifies your phone; an answer of exactly OK stays silent)")}
|
|
@@ -11574,7 +11955,14 @@ ${bold("Sesi\xF3n")}
|
|
|
11574
11955
|
use [espacio] Espacio por default para boards, agents create y agents ls [--clear]
|
|
11575
11956
|
completion zsh|bash|fish Autocompletado con TAB: comandos, opciones, agentes y espacios
|
|
11576
11957
|
usage Tu plan y cu\xE1nto llevas usado esta semana y este mes [--boxes: cajas contra el tope del espacio]
|
|
11958
|
+
usage --workspace SLUG Turnos y tokens de un espacio por agente/d\xEDa [--by agent,day] [--days 14]
|
|
11577
11959
|
plan apply [espacio] Aplica ya un plan programado, ense\xF1ando qu\xE9 cambia [--dry-run]
|
|
11960
|
+
engines ls Motores y modelos v\xE1lidos para --engine/--model [--models]
|
|
11961
|
+
|
|
11962
|
+
${bold("Espacios")} ${dim("(cambiarlos: staff de Ghosty)")}
|
|
11963
|
+
bags ls|create|set|rm <espacio> ["bolsa"] Bolsas de tokens de las que gastan sus agentes [--tokens 20M] [--recurring on|off] [--hard-cap on|off] [--note]
|
|
11964
|
+
spaces renew <espacio> --until FECHA Mueve \xABpagado hasta\xBB (queda en bit\xE1cora) [--note] [--dry-run]
|
|
11965
|
+
spaces rename <viejo> <nuevo> Cambia s\xF3lo su slug [--dry-run]
|
|
11578
11966
|
|
|
11579
11967
|
${bold("Agentes")}
|
|
11580
11968
|
agents ls Lista tus agentes
|
|
@@ -11590,12 +11978,13 @@ ${bold("Agentes")}
|
|
|
11590
11978
|
[--voice elevenlabs:ID | kokoro:em_santa] [--voice-replies auto|off] [--auto-stage on|off]
|
|
11591
11979
|
[--toolsets crm,sales | +crm,-sales] grupos de tools de gs que recibe
|
|
11592
11980
|
[--prompt-base AGENTE|none --prompt-overlay ARCHIVO [--dry-run]] prompt = el de otro agente + ediciones, sincronizado
|
|
11981
|
+
[--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
|
|
11593
11982
|
agents doctor <id> Revisa la configuraci\xF3n: skills encimadas, tools que nombra el prompt, permisos rotos, turnos que fallan
|
|
11594
11983
|
agents docs ls|grant|revoke <id> [doc] Documentos que lee y puede enviar [--write] [--no-deliver]
|
|
11595
11984
|
agents docs grant <id> A B\u2026 Da varios documentos a la vez [--like AGENTE] copia los de otro agente
|
|
11596
11985
|
agents docs add <id> Sube un documento y d\xE1selo --url URL | --file RUTA [--description] [--grant read|write|no]
|
|
11597
11986
|
agents share|unshare <id> <correo> Comp\xE1rtelo sin cederlo [--role editor|admin] \xB7 agents shares <id> los lista
|
|
11598
|
-
agents rm <id> Borra un agente (s\xF3lo el due\xF1o; te pide escribir su nombre, o --yes)
|
|
11987
|
+
agents rm <id> Borra un agente (s\xF3lo el due\xF1o; te pide escribir su nombre, o --yes) [--from-workspace: antes lo desactiva en Teams]
|
|
11599
11988
|
agents restart <id> Reinicia un agente ACP (needsRestart) para que cargue skills nuevas
|
|
11600
11989
|
agents box <id> La m\xE1quina que lo atiende: \xBFya trae la imagen nueva? [--check RUTA]\u2026 [--contains RUTA=TEXTO]\u2026 [--recycle]
|
|
11601
11990
|
agents demo enable|disable <id> Prende o apaga su liga p\xFAblica de demo
|
|
@@ -11632,6 +12021,7 @@ ${bold("Hablar con un agente")}
|
|
|
11632
12021
|
conversations ls <id> Lista conversaciones
|
|
11633
12022
|
conversations show <id> <conversaci\xF3n> Qu\xE9 se dijo [--limit 20] (0 = todo)
|
|
11634
12023
|
turns ls <id> Turnos recientes y por qu\xE9 fallaron [--errors] [--skipped] [--since 1h] [--limit N]
|
|
12024
|
+
turns ls --workspace SLUG --quota-hits Mensajes que no abrieron turno porque se acab\xF3 el saldo [--days 14]
|
|
11635
12025
|
turns show <id> [turno] Qu\xE9 lleg\xF3, qu\xE9 contest\xF3 y en qu\xE9 se fue el tiempo (default: el \xFAltimo) [--full]
|
|
11636
12026
|
|
|
11637
12027
|
${bold("Turnos programados")} ${dim("(cada corrida cae en una conversaci\xF3n y avisa a tu tel\xE9fono; una respuesta de exactamente OK no avisa)")}
|
|
@@ -11660,7 +12050,7 @@ ${bold("Opciones globales")}
|
|
|
11660
12050
|
${bold("C\xF3digos de salida")} 0 bien \xB7 1 error de la API \xB7 2 uso incorrecto \xB7 3 sin sesi\xF3n
|
|
11661
12051
|
${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.")}
|
|
11662
12052
|
${dim("Documentaci\xF3n: https://www.ghosty.studio/docs/cli")}`;
|
|
11663
|
-
var COMMANDS = ["login", "logout", "whoami", "usage", "use", "apply", "plan", "completion", "cards", "agents", "files", "skills", "mcp", "dbs", "turns", "try", "chat", "conversations", "schedule", "partner", "me", "channels", "board", "boards", "credentials", "docs", "help"];
|
|
12053
|
+
var COMMANDS = ["login", "logout", "whoami", "usage", "use", "apply", "plan", "engines", "bags", "spaces", "completion", "cards", "agents", "files", "skills", "mcp", "dbs", "turns", "try", "chat", "conversations", "schedule", "partner", "me", "channels", "board", "boards", "credentials", "docs", "help"];
|
|
11664
12054
|
var AGENTS_SUBS = ["ls", "create", "get", "set", "export", "doctor", "docs", "share", "unshare", "shares", "rm", "restart", "demo", "box"];
|
|
11665
12055
|
function unknown(what, input, known, helpCmd) {
|
|
11666
12056
|
const s = suggest(input, known);
|
|
@@ -11819,7 +12209,18 @@ async function main() {
|
|
|
11819
12209
|
"no-connect": { type: "boolean" },
|
|
11820
12210
|
cached: { type: "boolean" },
|
|
11821
12211
|
check: { type: "string", multiple: true },
|
|
11822
|
-
contains: { type: "string", multiple: true }
|
|
12212
|
+
contains: { type: "string", multiple: true },
|
|
12213
|
+
tokens: { type: "string" },
|
|
12214
|
+
recurring: { type: "string" },
|
|
12215
|
+
"hard-cap": { type: "string" },
|
|
12216
|
+
note: { type: "string" },
|
|
12217
|
+
bag: { type: "string" },
|
|
12218
|
+
avatar: { type: "string" },
|
|
12219
|
+
tool: { type: "string" },
|
|
12220
|
+
"from-workspace": { type: "boolean" },
|
|
12221
|
+
"quota-hits": { type: "boolean" },
|
|
12222
|
+
by: { type: "string" },
|
|
12223
|
+
models: { type: "boolean" }
|
|
11823
12224
|
}
|
|
11824
12225
|
});
|
|
11825
12226
|
globals.json = Boolean(v.json);
|
|
@@ -11827,7 +12228,7 @@ async function main() {
|
|
|
11827
12228
|
const tokens = splitTokens(pos, v);
|
|
11828
12229
|
globals.token = tokens.session;
|
|
11829
12230
|
globals.profile = v.profile;
|
|
11830
|
-
if (v.version) return out({ version: "0.
|
|
12231
|
+
if (v.version) return out({ version: "0.26.0" }, () => "0.26.0");
|
|
11831
12232
|
for (const n of normalizeArgs(pos, v)) if (!globals.json) info(dim(n));
|
|
11832
12233
|
const context = (await readConfig()).workspace;
|
|
11833
12234
|
if (!explicitToken()?.startsWith("gat_")) {
|
|
@@ -11904,6 +12305,7 @@ async function main() {
|
|
|
11904
12305
|
if (sub === "photos") return dbsPhotosCmd(a1, a2, pos[4], v);
|
|
11905
12306
|
return dbsCmd(sub, a1, a2, pos.slice(4).filter((x) => x !== void 0), v);
|
|
11906
12307
|
case "turns":
|
|
12308
|
+
if (v["quota-hits"]) return workspaceUsage(v.workspace ?? context, v);
|
|
11907
12309
|
return turnsCmd(sub, a1, a2, v);
|
|
11908
12310
|
case "board":
|
|
11909
12311
|
return boardCmd(sub, a1, v, a2);
|
|
@@ -11929,7 +12331,16 @@ async function main() {
|
|
|
11929
12331
|
return partnerCmd(sub, a1, a2, { ...v, token: tokens.turn });
|
|
11930
12332
|
case "usage":
|
|
11931
12333
|
if (v.boxes) return boxesUsage(v.workspace ?? context);
|
|
12334
|
+
if (v.workspace || v.by || v.days) return workspaceUsage(v.workspace ?? context, v);
|
|
11932
12335
|
return meUsage();
|
|
12336
|
+
case "engines":
|
|
12337
|
+
return enginesCmd(sub, v);
|
|
12338
|
+
case "bags":
|
|
12339
|
+
case "bag":
|
|
12340
|
+
return bagsCmd(sub, a1, a2, v, context);
|
|
12341
|
+
case "spaces":
|
|
12342
|
+
case "space":
|
|
12343
|
+
return spacesCmd(sub, a1, a2, v, context);
|
|
11933
12344
|
case "plan":
|
|
11934
12345
|
return planCmd(sub, a1, v, context);
|
|
11935
12346
|
case "me":
|
|
@@ -11946,7 +12357,7 @@ async function main() {
|
|
|
11946
12357
|
throw unknown(cmd, cmd, COMMANDS, "ghosty --help");
|
|
11947
12358
|
}
|
|
11948
12359
|
}
|
|
11949
|
-
var notice = updateNotice("0.
|
|
12360
|
+
var notice = updateNotice("0.26.0").catch(() => null);
|
|
11950
12361
|
main().then(
|
|
11951
12362
|
async () => {
|
|
11952
12363
|
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.26.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; 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; 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",
|