@ghostystudio/cli 0.76.0 → 0.78.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 +3 -0
- package/dist/index.js +306 -42
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -124,6 +124,9 @@ ghosty browser logout # revoke your terminal agents' browser
|
|
|
124
124
|
ghosty audit ls [--workspace SLUG] [--agent ID] [--actor EMAIL] [--action A] [--since 24h] [--limit N] # who changed what
|
|
125
125
|
ghosty factory rooms repos # the repos your GitHub App sees, and where they're connected
|
|
126
126
|
ghosty factory rooms connect owner/name --room R --create # set up the factory for a repo from the terminal
|
|
127
|
+
ghosty factory sprints ls|show|approve ID # the sprints @plan proposes for a bigger goal; approve signs every ticket at once
|
|
128
|
+
ghosty factory env ls|push [OWNER/REPO] [.env.preview] # the preview's env vars: what's missing, push them from your .env (--gen for random keys)
|
|
129
|
+
ghosty factory runs recheck <run> # escalated because blocked: @check reviews the same PR again, no @build loop
|
|
127
130
|
ghosty factory run "…" --repo owner/name [--wait] # hand a request to the Software Factory from the terminal
|
|
128
131
|
ghosty factory runs watch|approve|changes|merge|card <run> # wait for it and decide it without opening Teams
|
|
129
132
|
ghosty factory runs ls|show|why|stats|rename [--workspace SLUG] [--room R] [--status S] [show --timing] [why --refresh] # Software Factory runs
|
package/dist/index.js
CHANGED
|
@@ -1175,7 +1175,7 @@ async function postLong(path, body, timeoutMs = 9e5) {
|
|
|
1175
1175
|
const req = httpsRequest(`${base}${path}`, {
|
|
1176
1176
|
method: "POST",
|
|
1177
1177
|
timeout: timeoutMs,
|
|
1178
|
-
headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.
|
|
1178
|
+
headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.78.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
|
|
1179
1179
|
}, (res) => {
|
|
1180
1180
|
let buf = "";
|
|
1181
1181
|
res.on("data", (c) => buf += c);
|
|
@@ -1217,7 +1217,7 @@ async function request(path, opts = {}, retried = false) {
|
|
|
1217
1217
|
method: opts.method ?? (opts.body !== void 0 || opts.raw !== void 0 ? "POST" : "GET"),
|
|
1218
1218
|
headers: {
|
|
1219
1219
|
authorization: `Bearer ${token}`,
|
|
1220
|
-
"user-agent": `ghosty-cli/${"0.
|
|
1220
|
+
"user-agent": `ghosty-cli/${"0.78.0"}`,
|
|
1221
1221
|
"accept-language": lang(),
|
|
1222
1222
|
...opts.body !== void 0 ? { "content-type": "application/json" } : {},
|
|
1223
1223
|
...opts.headers
|
|
@@ -1264,7 +1264,7 @@ var mteams_exports = {};
|
|
|
1264
1264
|
__export(mteams_exports, {
|
|
1265
1265
|
mteamsCmd: () => mteamsCmd
|
|
1266
1266
|
});
|
|
1267
|
-
import { writeFile as
|
|
1267
|
+
import { writeFile as writeFile8 } from "node:fs/promises";
|
|
1268
1268
|
function list(bots) {
|
|
1269
1269
|
return bots.length ? table(
|
|
1270
1270
|
bots.map((b) => ({
|
|
@@ -1306,7 +1306,7 @@ ${dim(`${t("Next", "Sigue")}: ghosty channels mteams zip ${id} ${b?.id ?? "<bot-
|
|
|
1306
1306
|
const res = await request(`${path}/${enc9(botId)}/zip`);
|
|
1307
1307
|
const bytes = Buffer.from(await res.arrayBuffer());
|
|
1308
1308
|
const file = v.out ?? "ghosty-teams.zip";
|
|
1309
|
-
await
|
|
1309
|
+
await writeFile8(file, bytes);
|
|
1310
1310
|
return out({ saved: file, bytes: bytes.length }, () => `${lavender("\u2713")} ${t(`Saved ${file}: upload it in Teams admin \u2192 Manage apps.`, `Se guard\xF3 ${file}: s\xFAbelo en Teams admin \u2192 Administrar apps.`)}`);
|
|
1311
1311
|
}
|
|
1312
1312
|
case "on":
|
|
@@ -9229,6 +9229,13 @@ async function anonVisitorUsage(id, v) {
|
|
|
9229
9229
|
${table(rows, ["visitor", "first", "msgs", "usd", "page", "pdf", "account"])}
|
|
9230
9230
|
${dim(foot)}` : dim(t(`No visitors in ${r.days} day(s).`, `Sin visitantes en ${r.days} d\xEDa(s).`)));
|
|
9231
9231
|
}
|
|
9232
|
+
function parseCapFlag(raw) {
|
|
9233
|
+
if (raw === "none") return { ok: true, cap: null };
|
|
9234
|
+
return /^[1-9]\d*$/.test(raw) ? { ok: true, cap: Number(raw) } : { ok: false };
|
|
9235
|
+
}
|
|
9236
|
+
function capColumn(tokenCap) {
|
|
9237
|
+
return tokenCap == null ? "" : String(tokenCap);
|
|
9238
|
+
}
|
|
9232
9239
|
function fmtUntil(iso) {
|
|
9233
9240
|
if (!iso) return "";
|
|
9234
9241
|
const d = new Date(new Date(iso).getTime() - 6 * 36e5);
|
|
@@ -9237,22 +9244,25 @@ function fmtUntil(iso) {
|
|
|
9237
9244
|
async function spacesMembers(action, slug, email, v, context) {
|
|
9238
9245
|
if (context && slug?.includes("@") && !email) [slug, email] = [context, slug];
|
|
9239
9246
|
if (action && !["ls", "list", "add", "rm", "remove"].includes(action) && !slug) [action, slug] = ["ls", action];
|
|
9240
|
-
const ex = "ghosty spaces members ls <workspace> \xB7 add <workspace> <email>[,email\u2026] [--role member|admin|collaborator --room <room>] [--until 2026-10-28|14d] \xB7 rm <workspace> <email>";
|
|
9247
|
+
const ex = "ghosty spaces members ls <workspace> \xB7 add <workspace> <email>[,email\u2026] [--role member|admin|collaborator --room <room> [--cap N|none]] [--until 2026-10-28|14d] \xB7 rm <workspace> <email>";
|
|
9241
9248
|
slug = needSpace(slug ?? context, ex);
|
|
9242
9249
|
switch (action ?? "ls") {
|
|
9243
9250
|
case "ls":
|
|
9244
9251
|
case "list": {
|
|
9245
9252
|
const r = await api(wsPath(slug, "/members"));
|
|
9246
|
-
const rows = r.members.map((m) => ({ email: m.email, role: m.role.toLowerCase(), status: m.status, until: fmtUntil(m.until) }));
|
|
9247
|
-
return out(r, () => `${table(rows, ["email", "role", "status", "until"])}${r.error ? `
|
|
9253
|
+
const rows = r.members.map((m) => ({ email: m.email, role: m.role.toLowerCase(), status: m.status, until: fmtUntil(m.until), cap: capColumn(m.tokenCap) }));
|
|
9254
|
+
return out(r, () => `${table(rows, ["email", "role", "status", "until", "cap"])}${r.error ? `
|
|
9248
9255
|
${lavender(`\u26A0\uFE0F ${serverText(r.error)}`)}` : ""}`);
|
|
9249
9256
|
}
|
|
9250
9257
|
case "add": {
|
|
9251
9258
|
if (!email) throw usage(t("Missing email.", "Falta el correo."), ex);
|
|
9252
9259
|
if (v.role === "collaborator" && !v.room) throw usage(t("A collaborator needs --room (the Teams room they enter).", "Un colaborador necesita --room (el room de Teams al que entra)."), ex);
|
|
9253
9260
|
if (v.room && !/^[A-Za-z0-9_-]{1,64}$/.test(v.room)) throw usage(t("The room takes letters, numbers, _ or - (max 64).", "El room lleva s\xF3lo letras, n\xFAmeros, _ o - (m\xE1x. 64)."), ex);
|
|
9261
|
+
if (v.cap !== void 0 && v.role !== "collaborator") throw usage(t("--cap goes only with a collaborator (--role collaborator).", "--cap s\xF3lo va con un colaborador (--role collaborator)."), ex);
|
|
9262
|
+
const cap = v.cap === void 0 ? void 0 : parseCapFlag(v.cap);
|
|
9263
|
+
if (cap && !cap.ok) throw usage(t("--cap takes a whole number of tokens above 0, or none.", "--cap lleva un entero de tokens mayor que 0, o none."), ex);
|
|
9254
9264
|
const emails = email.split(",").map((e) => e.trim()).filter(Boolean);
|
|
9255
|
-
const r = await api(wsPath(slug, "/members"), { method: "POST", body: { emails, role: v.role ?? "member", ...v.until ? { until: v.until } : {}, ...v.room ? { room: v.room } : {} } });
|
|
9265
|
+
const r = await api(wsPath(slug, "/members"), { method: "POST", body: { emails, role: v.role ?? "member", ...v.until ? { until: v.until } : {}, ...v.room ? { room: v.room } : {}, ...cap?.ok ? { cap: cap.cap } : {} } });
|
|
9256
9266
|
const lines = [
|
|
9257
9267
|
...r.invited.map((e) => `${lavender("\u2713")} ${e} ${t(`added to ${r.workspace}`, `agregada a ${r.workspace}`)}${r.until ? dim(t(` until ${fmtUntil(r.until)} (CDMX), then removed`, ` hasta ${fmtUntil(r.until)} (CDMX), luego se retira sola`)) : ""}`),
|
|
9258
9268
|
...r.extended.map((e) => `${lavender("\u2713")} ${e} ${t("was already in; expires", "ya estaba; vence")} ${fmtUntil(r.until)} (CDMX)`),
|
|
@@ -11195,7 +11205,7 @@ async function call(path, v, init = {}) {
|
|
|
11195
11205
|
"x-ghosty-ts": ts,
|
|
11196
11206
|
"x-ghosty-sig": sig,
|
|
11197
11207
|
"content-type": "application/json",
|
|
11198
|
-
"user-agent": `ghosty-cli/${"0.
|
|
11208
|
+
"user-agent": `ghosty-cli/${"0.78.0"}`
|
|
11199
11209
|
},
|
|
11200
11210
|
body: method === "GET" ? void 0 : raw
|
|
11201
11211
|
});
|
|
@@ -11472,9 +11482,41 @@ async function partnerCmd(sub, a1, a2, v, a3) {
|
|
|
11472
11482
|
}
|
|
11473
11483
|
}
|
|
11474
11484
|
|
|
11485
|
+
// src/commands/image.ts
|
|
11486
|
+
import { writeFile as writeFile6 } from "node:fs/promises";
|
|
11487
|
+
init_util();
|
|
11488
|
+
init_i18n();
|
|
11489
|
+
async function imageCmd(sub, prompt, v) {
|
|
11490
|
+
if (sub !== "generate" && sub !== "gen") {
|
|
11491
|
+
throw usage(t(`Unknown image subcommand: ${sub ?? "(none)"}.`, `Subcomando de image desconocido: ${sub ?? "(ninguno)"}.`), t("Try", "Prueba") + ': ghosty image generate "a cat astronaut" [--ref URL] [--aspect 16:9]');
|
|
11492
|
+
}
|
|
11493
|
+
if (!prompt?.trim()) throw usage(t("Missing the prompt.", "Falta el prompt."), t("Example", "Ejemplo") + ': ghosty image generate "my mascot in a Halloween scene" --ref https://example.com/mascot.png --aspect 16:9');
|
|
11494
|
+
const body = {
|
|
11495
|
+
prompt,
|
|
11496
|
+
...v.ref?.length ? { referenceUrls: v.ref } : {},
|
|
11497
|
+
...v.aspect ? { aspectRatio: v.aspect } : {},
|
|
11498
|
+
...v.tenant ? { tenant: { externalId: v.tenant } } : {},
|
|
11499
|
+
...v.agent ? { agent: v.agent } : {}
|
|
11500
|
+
};
|
|
11501
|
+
const r = await json("/api/v2/partner/images", v, { method: "POST", body });
|
|
11502
|
+
if (v.out) {
|
|
11503
|
+
const res = await fetch(r.url);
|
|
11504
|
+
if (!res.ok) throw new Error(t(`Could not download the image (${res.status}).`, `No se pudo bajar la imagen (${res.status}).`));
|
|
11505
|
+
await writeFile6(v.out, Buffer.from(await res.arrayBuffer()));
|
|
11506
|
+
}
|
|
11507
|
+
return out(
|
|
11508
|
+
r,
|
|
11509
|
+
() => [
|
|
11510
|
+
r.url,
|
|
11511
|
+
dim(`${r.mode === "edit" ? t("edited from references", "editada desde referencias") : t("generated", "generada")}${r.width && r.height ? ` \xB7 ${r.width}\xD7${r.height}` : ""} \xB7 ${t("charged", "cobrado")} USD ${r.priceUsd.toFixed(2)}`),
|
|
11512
|
+
...v.out ? [`${lavender("\u2713")} ${t(`saved to ${v.out}`, `guardada en ${v.out}`)}`] : []
|
|
11513
|
+
].join("\n")
|
|
11514
|
+
);
|
|
11515
|
+
}
|
|
11516
|
+
|
|
11475
11517
|
// src/commands/data.ts
|
|
11476
11518
|
init_api();
|
|
11477
|
-
import { readdir as readdir2, readFile as readFile6, writeFile as
|
|
11519
|
+
import { readdir as readdir2, readFile as readFile6, writeFile as writeFile7 } from "node:fs/promises";
|
|
11478
11520
|
import { basename as basename3, join as join5 } from "node:path";
|
|
11479
11521
|
|
|
11480
11522
|
// src/rows.ts
|
|
@@ -11643,7 +11685,7 @@ async function dbsExport(id, db, v) {
|
|
|
11643
11685
|
const suggested = /filename="([^"]+)"/.exec(res.headers.get("content-disposition") ?? "")?.[1] ?? (format === "csv" ? `${db}-${v.table}.csv` : `${db}.sqlite`);
|
|
11644
11686
|
const file = v.out ?? suggested;
|
|
11645
11687
|
const bytes = Buffer.from(await res.arrayBuffer());
|
|
11646
|
-
await
|
|
11688
|
+
await writeFile7(file, bytes);
|
|
11647
11689
|
const countsHeader = res.headers.get("x-gs-row-counts");
|
|
11648
11690
|
const counts = countsHeader ? JSON.parse(Buffer.from(countsHeader, "base64").toString("utf8")) : {};
|
|
11649
11691
|
const rows = Object.entries(counts).map(([tb, n]) => ({ table: tb, rows: n }));
|
|
@@ -11709,6 +11751,19 @@ ${dim(`${t(`${rows.length} row(s)`, `${rows.length} fila(s)`)} \xB7 ${Math.round
|
|
|
11709
11751
|
const rows = r.writes.map((w) => ({ when: w.created_at, agent: w.agent_id.slice(0, 10), channel: w.canal ?? "", what: `${w.op} ${w.table_name}${w.row_id != null ? ` #${w.row_id}` : ""}`, customer: w.phone10 ?? "", why: w.reason ?? "" }));
|
|
11710
11752
|
return out(r, () => rows.length ? table(rows, ["when", "agent", "channel", "what", "customer", "why"]) : dim(t("No agent writes recorded yet in this database.", "Todav\xEDa no hay escrituras de agentes registradas en esta base.")));
|
|
11711
11753
|
}
|
|
11754
|
+
case "checkpoints": {
|
|
11755
|
+
db = need2(db, t("database (id or name)", "la base (id o nombre)"), "ghosty dbs checkpoints <agent-id> <db>");
|
|
11756
|
+
const r = await api(agentPath2(id, `/dbs/${enc8(db)}/checkpoints`));
|
|
11757
|
+
const rows = r.checkpoints.map((c) => ({ id: c.id, when: c.at, rows: Object.values(c.counts).reduce((a, b) => a + b, 0), why: c.reason }));
|
|
11758
|
+
return out(r, () => rows.length ? table(rows, ["id", "when", "rows", "why"]) : dim(t("No checkpoints yet: one is taken before the first write of each agent turn.", "Todav\xEDa no hay checkpoints: se toma uno antes de la primera escritura de cada turno de un agente.")));
|
|
11759
|
+
}
|
|
11760
|
+
case "restore": {
|
|
11761
|
+
db = need2(db, t("database (id or name)", "la base (id o nombre)"), "ghosty dbs restore <agent-id> <db> <checkpoint-id> [--yes]");
|
|
11762
|
+
const ckpt = need2(rest[0], t("checkpoint id (from dbs checkpoints)", "el id del checkpoint (de dbs checkpoints)"), `ghosty dbs restore ${id} ${db} <checkpoint-id>`);
|
|
11763
|
+
await confirm(t(`Return every table of ${db} to checkpoint ${ckpt}? A new checkpoint is taken first, so you can undo it.`, `\xBFRegresar todas las tablas de ${db} al checkpoint ${ckpt}? Antes se toma otro, as\xED que se puede deshacer.`), { yes: v.yes, retry: `ghosty dbs restore ${id} ${db} ${ckpt}` });
|
|
11764
|
+
const r = await api(agentPath2(id, `/dbs/${enc8(db)}/checkpoints`), { method: "POST", body: { checkpointId: ckpt } });
|
|
11765
|
+
return out(r, () => `${lavender("\u2713")} ${t(`${r.db} returned to ${r.restoredTo.at}.`, `${r.db} regresada a ${r.restoredTo.at}.`)} ${dim(r.undo ? t(`Undo: ghosty dbs restore ${id} ${db} ${r.undo.id}`, `Para deshacer: ghosty dbs restore ${id} ${db} ${r.undo.id}`) : r.warning ?? "")}`);
|
|
11766
|
+
}
|
|
11712
11767
|
case "grant": {
|
|
11713
11768
|
db = need2(db, t("database (id or name)", "la base (id o nombre)"), "ghosty dbs grant <agent-id> <db> [--external-write t1,t2] [--external-rows t:col+col]");
|
|
11714
11769
|
let rowScope;
|
|
@@ -11871,6 +11926,7 @@ function skipReason(r) {
|
|
|
11871
11926
|
reaction: ["only a reaction", "s\xF3lo una reacci\xF3n"],
|
|
11872
11927
|
channel_off: ["WhatsApp channel off for this agent", "canal de WhatsApp apagado en este agente"],
|
|
11873
11928
|
no_quota: ["out of balance", "sin saldo"],
|
|
11929
|
+
own_number: ["sent by another of our agent numbers (bot loop guard)", "lo mand\xF3 otro n\xFAmero nuestro con agente (guarda de bucle)"],
|
|
11874
11930
|
partner_token: ["partner did not issue a turn token", "el partner no dio token de turno"],
|
|
11875
11931
|
partner_busy: ["partner at its concurrent-turn limit", "el partner lleg\xF3 a su tope de turnos a la vez"]
|
|
11876
11932
|
};
|
|
@@ -13228,6 +13284,24 @@ var HELPS_ES = {
|
|
|
13228
13284
|
summary: "Ghosty para Chrome: cerrar la sesi\xF3n del navegador.",
|
|
13229
13285
|
notes: ["Revoca todos los tokens de Ghosty para Chrome que tengan tus agentes de terminal (Claude Code, CLI, @ghostystudio/browser-mcp) y desconecta la extensi\xF3n. Para volver, copia un comando nuevo del panel de la extensi\xF3n."]
|
|
13230
13286
|
},
|
|
13287
|
+
image: {
|
|
13288
|
+
summary: "Una imagen con IA (Gemini \xABNano Banana Pro\xBB) para los negocios de tu espacio de partner, cobrada del saldo del espacio.",
|
|
13289
|
+
replace: [
|
|
13290
|
+
["our mascot dressed as a pumpkin in a Halloween scene, flat illustration, no text", "nuestra mascota disfrazada de calabaza en una escena de Halloween, ilustraci\xF3n plana, sin texto"],
|
|
13291
|
+
["a cozy spa with candles, warm light", "un spa acogedor con velas, luz c\xE1lida"]
|
|
13292
|
+
],
|
|
13293
|
+
flags: [
|
|
13294
|
+
["--ref URL", "una imagen de referencia (https; JPEG, PNG, WebP, GIF o SVG); se repite, hasta 4. La imagen CONSERVA su personaje u objeto (mascota, logo, producto)"],
|
|
13295
|
+
["--aspect RATIO", "1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9 o 21:9"],
|
|
13296
|
+
["--tenant ID", "el negocio (externalId) para el que es: va al desglose de consumo y a su tope diario opcional"],
|
|
13297
|
+
["--agent SLUG", "a qu\xE9 agente del partner se carga en el desglose (default: el de siempre)"],
|
|
13298
|
+
["--out FILE", "adem\xE1s baja el PNG aqu\xED"]
|
|
13299
|
+
],
|
|
13300
|
+
notes: [
|
|
13301
|
+
`Cada imagen se cobra aparte: USD 0.25 del saldo del espacio de partner (las fallidas no se cobran). Sin saldo: 402. gs no pone topes propios; un negocio puede tener el suyo: tenants set <id> --config '{"imageDailyCap":20}'.`,
|
|
13302
|
+
"Auth: tu login (due\xF1a del espacio; --workspace si tienes varios) o la credencial del servidor del partner en el env (GHOSTY_PARTNER_KEY + GHOSTY_PARTNER_KEY_SECRET), que entonces exige --tenant."
|
|
13303
|
+
]
|
|
13304
|
+
},
|
|
13231
13305
|
me: {
|
|
13232
13306
|
summary: "Tu cuenta: archivos, memorias, documentos editables y uso.",
|
|
13233
13307
|
replace: [
|
|
@@ -13484,6 +13558,7 @@ var HELPS_ES = {
|
|
|
13484
13558
|
["--currency C", "pin: usd (default) o mxn \u2014 la moneda del cobro si hay que pagar"],
|
|
13485
13559
|
["--add-pack", "pin: ya usas tus lugares \u2014 suma 3 m\xE1s (mismo periodo y moneda de tu suscripci\xF3n) y fija \xE9ste"],
|
|
13486
13560
|
["--cancel / --resume", "pins: cancela al final del periodo (los sitios vuelven a Gratis con aviso), o lo deshace"],
|
|
13561
|
+
["--days N", "search: la ventana de Search Console en d\xEDas (default 28; Google reporta con 2\u20133 d\xEDas de retraso)"],
|
|
13487
13562
|
["--title T", "publish: el nombre del sitio (default: el <title> del HTML o el nombre de la carpeta)"],
|
|
13488
13563
|
["--slug S", "publish: el slug de un sitio NUEVO (a-z, 0-9 y guiones, 3\u201360, no reservado)"],
|
|
13489
13564
|
["--site X", "publish: actualiza ese sitio de la casa (slug, id o URL) en vez de crear uno"]
|
|
@@ -13509,6 +13584,7 @@ var HELPS_ES = {
|
|
|
13509
13584
|
["--user EMAIL", "ls (staff): los espacios de esa persona"],
|
|
13510
13585
|
["--role R", "members add: member (default: tableros + editor de los agentes del espacio, sin llaves, cobro ni traspasos) | admin (adem\xE1s invita y quita gente) | collaborator (invitado a UN room: sin asiento ni agente personal)"],
|
|
13511
13586
|
["--room ROOM", "members add --role collaborator: el room de Teams al que entra (obligatorio con collaborator)"],
|
|
13587
|
+
["--cap N|none", "members add --role collaborator: tokens facturables m\xE1ximos que puede gastar por periodo (entero > 0); none lo quita. Sin tope s\xF3lo aplica el saldo del espacio"],
|
|
13512
13588
|
["--until D", "members add: asiento temporal que se retira solo en esa fecha (fin del d\xEDa, CDMX) o tras 14d / 12h \u2014 cuentas de revisor (Meta, Google, Apple). A quien ya est\xE1, le pone o mueve la fecha"],
|
|
13513
13589
|
["--combo C", "set (staff): teams | sales | both | none (ni Teams ni Ventas) \u2014 sin Ventas sus tableros existen pero nadie los ve; create: distinto de teams, s\xF3lo staff"],
|
|
13514
13590
|
["--model-cap X", "set (staff): el modelo m\xE1s caro que puede elegir el espacio \u2014 0.25 econ\xF3micos, 1 est\xE1ndar (Sonnet), 4 todos; none = el de su plan"],
|
|
@@ -13578,6 +13654,8 @@ var HELPS_ES = {
|
|
|
13578
13654
|
["--note T", "changes: qu\xE9 cambiar del plan (o por qu\xE9 replanear tras un escalado). approve tras un escalado: qu\xE9 debe arreglar @build"],
|
|
13579
13655
|
["--check-scope T", "approve/changes: lo \xFAnico que @check revisa en la siguiente vuelta (lo propone runs advise); lo nuevo va como nota, no como hallazgo"],
|
|
13580
13656
|
["--skip-preview", "merge: mergea sin la preview que ped\xEDa el plan (queda en bit\xE1cora)"],
|
|
13657
|
+
["--gen KEY,KEY", "env push: genera ah\xED mismo llaves al azar (32 bytes hex) con esos nombres; nunca se imprimen"],
|
|
13658
|
+
["--replace", "env push: reemplaza el .env entero de la preview (por defecto agrega y deja las dem\xE1s)"],
|
|
13581
13659
|
["--create", "rooms connect: crea el room si no existe"],
|
|
13582
13660
|
["--private", "rooms connect --create: el room nuevo es privado (s\xF3lo t\xFA, y a quien agregues)"],
|
|
13583
13661
|
["--room R", "rooms connect/disconnect: el room (slug o nombre). run: ese room en vez de buscarlo por --repo. ls: s\xF3lo los pedidos de ese room (#slug o nombre)"],
|
|
@@ -13591,6 +13669,8 @@ var HELPS_ES = {
|
|
|
13591
13669
|
["--yes", "rename, approve, changes, merge: se salta la confirmaci\xF3n (obligatorio sin terminal)"]
|
|
13592
13670
|
],
|
|
13593
13671
|
notes: [
|
|
13672
|
+
"env: las variables de entorno de la preview viven en tu compu, as\xED que las sube tu agente de terminal. ls lista las guardadas y las que faltan (de .env.example y de los pedidos escalados porque la preview no arranca); push las agrega (s\xF3lo el due\xF1o del espacio). Usa valores desechables, nunca los de producci\xF3n: la preview corre el c\xF3digo del repo. Los valores nunca regresan. Luego runs recheck <pedido>: @check revisa otra vez el mismo PR sin otra vuelta de @build. Queda en bit\xE1cora (factory.env_set y factory.run_recheck).",
|
|
13673
|
+
"sprints: si el objetivo no cabe en un pedido, @plan propone un sprint (3\u20138 tickets en orden, con dependencias) en borrador. approve firma todos los tickets incluidos de una vez; arrancan uno a la vez conforme sus dependencias tienen merge. S\xF3lo el due\xF1o o quien lo pidi\xF3 lo aprueba. Queda en bit\xE1cora (factory.sprint_approve).",
|
|
13594
13674
|
"rooms repos lista los repos que ve tu GitHub App (los m\xE1s recientes primero) y en qu\xE9 rooms ya est\xE1 cada uno.",
|
|
13595
13675
|
"rooms connect revisa que TU instalaci\xF3n de la GitHub App vea el repo (si no, te da la liga para darle acceso) y dice qu\xE9 tan listo est\xE1 para agentes (niveles 1\u20133 y lo que falta). Cualquier miembro puede conectar; desconectar, quien lo conect\xF3 o el due\xF1o. Queda en bit\xE1cora (factory.room_connect / factory.room_disconnect).",
|
|
13596
13676
|
"run publica \xAB@plan <tu texto>\xBB en el room A TU NOMBRE, como si lo escribieras en Teams: el mismo plan, firmas y tarjeta. Escr\xEDbelo como ticket: el objetivo, por qu\xE9 y c\xF3mo se sabe que qued\xF3. Cualquier miembro puede pedir, firmar, pedir cambios y mergear; todo queda en bit\xE1cora (factory.run, factory.run_approve\u2026).",
|
|
@@ -13821,6 +13901,8 @@ var HELPS = {
|
|
|
13821
13901
|
"ghosty dbs tables <agent-id> <db>",
|
|
13822
13902
|
'ghosty dbs query <agent-id> <db> "SQL" [--arg V]\u2026 [--write]',
|
|
13823
13903
|
"ghosty dbs origin <agent-id> <db> [--table T] [--since 2026-10-10] [--limit N]",
|
|
13904
|
+
"ghosty dbs checkpoints <agent-id> <db>",
|
|
13905
|
+
"ghosty dbs restore <agent-id> <db> <checkpoint-id> [--yes]",
|
|
13824
13906
|
"ghosty dbs export <agent-id> <db> [--table T] [--out FILE]",
|
|
13825
13907
|
"ghosty dbs import <agent-id> <db> --from easybits:<db-id> [--tables a,b] [--append] [--rehost-images t.col,\u2026] [--dry-run] [--yes]",
|
|
13826
13908
|
"ghosty dbs rehost <agent-id> <db> --table T --column C [--dry-run]",
|
|
@@ -14005,6 +14087,26 @@ var HELPS = {
|
|
|
14005
14087
|
notes: ["Revokes every Ghosty for Chrome token your terminal agents hold (Claude Code, CLI, @ghostystudio/browser-mcp) and disconnects the extension. Copy a new command from the extension panel to reconnect."],
|
|
14006
14088
|
learn: `${REF}#navegador`
|
|
14007
14089
|
},
|
|
14090
|
+
image: {
|
|
14091
|
+
summary: "An AI image (Gemini \xABNano Banana Pro\xBB) for your partner businesses, charged to the space's balance.",
|
|
14092
|
+
usage: ['ghosty image generate "prompt" [--ref URL]... [--aspect 16:9] [--tenant ID] [--agent SLUG] [--out FILE]'],
|
|
14093
|
+
flags: [
|
|
14094
|
+
["--ref URL", "a reference image (https; JPEG, PNG, WebP, GIF or SVG); repeat it, up to 4. The image KEEPS its character or object (mascot, logo, product)"],
|
|
14095
|
+
["--aspect RATIO", "1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9 or 21:9"],
|
|
14096
|
+
["--tenant ID", "the business (externalId) it is for: it goes to the usage breakdown and to its optional daily cap"],
|
|
14097
|
+
["--agent SLUG", "which partner agent it is charged to in the breakdown (default: the main one)"],
|
|
14098
|
+
["--out FILE", "also download the PNG here"]
|
|
14099
|
+
],
|
|
14100
|
+
examples: [
|
|
14101
|
+
'ghosty image generate "our mascot dressed as a pumpkin in a Halloween scene, flat illustration, no text" --ref https://example.com/mascot.svg --aspect 16:9 --tenant org_123',
|
|
14102
|
+
'ghosty image generate "a cozy spa with candles, warm light" --aspect 1:1 --out spa.png'
|
|
14103
|
+
],
|
|
14104
|
+
notes: [
|
|
14105
|
+
`Each image is charged apart: USD 0.25 from the partner space's balance (failed ones are not charged). Without balance: 402. gs sets no cap of its own; a business can have one: tenants set <id> --config '{"imageDailyCap":20}'.`,
|
|
14106
|
+
"Auth: your login (space owner; --workspace if you have several) or the partner server credential in the env (GHOSTY_PARTNER_KEY + GHOSTY_PARTNER_KEY_SECRET), which then requires --tenant."
|
|
14107
|
+
],
|
|
14108
|
+
learn: `${DOCS}/partners#imagenes-con-ia`
|
|
14109
|
+
},
|
|
14008
14110
|
me: {
|
|
14009
14111
|
summary: "Your account: files, memories, editable docs and usage.",
|
|
14010
14112
|
usage: [
|
|
@@ -14368,11 +14470,12 @@ HELPS.novedades = {
|
|
|
14368
14470
|
};
|
|
14369
14471
|
HELPS.spaces = {
|
|
14370
14472
|
summary: "Workspaces: create one of your own (trial), list yours (or, staff, anyone's), and Ghosty staff operations \u2014 join/leave a client's workspace, change its combo, move \xABpaid until\xBB, rename its slug, change its owner \u2014 and, owner or staff, archive or restore it. Each change is logged in the workspace's audit.",
|
|
14371
|
-
usage: ["ghosty spaces ls [--user EMAIL]", "ghosty spaces create <slug> [--combo teams|sales|both|none] [--dry-run]", "ghosty spaces join|leave <workspace>", "ghosty spaces set <workspace> --combo teams|sales|both|none | --model-cap 0.25|1|4|none | --house-claude on|off [--dry-run]", "ghosty spaces renew <workspace> --until YYYY-MM-DD [--note TEXT] [--dry-run] [--yes]", "ghosty spaces rename <old> <new> [--dry-run] [--yes]", "ghosty spaces archive|restore <workspace> [--dry-run] [--yes]", "ghosty spaces transfer <workspace> --to EMAIL [--keep-as admin|member|none] [--copy-keys] [--dry-run] [--yes]", "ghosty spaces log <workspace> [--since 30d] [--action workspace] [--limit N]", "ghosty spaces members ls|add|rm <workspace> [email,\u2026] [--role member|admin|collaborator] [--room ROOM] [--until 2026-10-28|14d] [--yes]"],
|
|
14473
|
+
usage: ["ghosty spaces ls [--user EMAIL]", "ghosty spaces create <slug> [--combo teams|sales|both|none] [--dry-run]", "ghosty spaces join|leave <workspace>", "ghosty spaces set <workspace> --combo teams|sales|both|none | --model-cap 0.25|1|4|none | --house-claude on|off [--dry-run]", "ghosty spaces renew <workspace> --until YYYY-MM-DD [--note TEXT] [--dry-run] [--yes]", "ghosty spaces rename <old> <new> [--dry-run] [--yes]", "ghosty spaces archive|restore <workspace> [--dry-run] [--yes]", "ghosty spaces transfer <workspace> --to EMAIL [--keep-as admin|member|none] [--copy-keys] [--dry-run] [--yes]", "ghosty spaces log <workspace> [--since 30d] [--action workspace] [--limit N]", "ghosty spaces members ls|add|rm <workspace> [email,\u2026] [--role member|admin|collaborator] [--room ROOM] [--cap N|none] [--until 2026-10-28|14d] [--yes]"],
|
|
14372
14474
|
flags: [
|
|
14373
14475
|
["--user EMAIL", "ls (staff): that person's workspaces"],
|
|
14374
14476
|
["--role R", "members add: member (default: boards + editor of the workspace's agents, no keys, billing or transfers) | admin (also invites and removes people) | collaborator (invited to ONE Teams room: no seat, no personal agent)"],
|
|
14375
14477
|
["--room ROOM", "members add --role collaborator: the Teams room they enter (required with collaborator)"],
|
|
14478
|
+
["--cap N|none", "members add --role collaborator: max billable tokens they can spend per period (whole number > 0); none removes it. Without a cap only the space balance applies"],
|
|
14376
14479
|
["--until D", "members add: temporary seat, removed automatically on that date (end of day, CDMX) or after 14d / 12h \u2014 reviewer accounts (Meta, Google, Apple). On someone already in, it sets or moves the date"],
|
|
14377
14480
|
["--combo C", "set (staff): teams | sales | both | none (neither Teams nor Sales) \u2014 without Sales its boards exist but nobody sees them; create: other than teams, staff only"],
|
|
14378
14481
|
["--model-cap X", "set (staff): the most expensive model the workspace can pick \u2014 0.25 economy, 1 standard (Sonnet), 4 everything; none = its plan's"],
|
|
@@ -14409,6 +14512,7 @@ HELPS.sites = {
|
|
|
14409
14512
|
"ghosty sites pin <slug|id> [--interval year|month] [--currency usd|mxn] [--add-pack]",
|
|
14410
14513
|
"ghosty sites unpin <slug|id>",
|
|
14411
14514
|
"ghosty sites pins [--cancel | --resume]",
|
|
14515
|
+
"ghosty sites search <slug|id> [--days 28]",
|
|
14412
14516
|
"ghosty sites publish <file.html|folder> [--title T] [--slug S] [--site <slug|id>]",
|
|
14413
14517
|
"ghosty sites slug <slug|id> <new-slug>"
|
|
14414
14518
|
],
|
|
@@ -14418,6 +14522,7 @@ HELPS.sites = {
|
|
|
14418
14522
|
["--currency C", "pin: usd (default) or mxn \u2014 the currency charged if you need to pay"],
|
|
14419
14523
|
["--add-pack", "pin: you already use your slots \u2014 add 3 more (same period and currency as your subscription) and pin this one"],
|
|
14420
14524
|
["--cancel / --resume", "pins: cancel at period end (sites go back to Free with notice), or undo it"],
|
|
14525
|
+
["--days N", "search: Search Console window in days (default 28; Google reports 2\u20133 days late)"],
|
|
14421
14526
|
["--title T", "publish: the site's name (default: the HTML <title> or the folder name)"],
|
|
14422
14527
|
["--slug S", "publish: the slug for a NEW site (a-z, 0-9 and hyphens, 3\u201360, not reserved)"],
|
|
14423
14528
|
["--site X", "publish: update that house site (slug, id or URL) instead of creating one"]
|
|
@@ -14497,6 +14602,12 @@ HELPS.factory = {
|
|
|
14497
14602
|
"ghosty factory rooms repos [--workspace SLUG]",
|
|
14498
14603
|
"ghosty factory rooms connect OWNER/REPO --room R [--create] [--private] [--workspace SLUG]",
|
|
14499
14604
|
"ghosty factory rooms disconnect OWNER/REPO --room R [--yes]",
|
|
14605
|
+
"ghosty factory sprints ls [--workspace SLUG]",
|
|
14606
|
+
"ghosty factory sprints show ID [--workspace SLUG]",
|
|
14607
|
+
"ghosty factory sprints approve ID [--yes] [--workspace SLUG]",
|
|
14608
|
+
"ghosty factory env ls [OWNER/REPO] [--workspace SLUG]",
|
|
14609
|
+
"ghosty factory env push [OWNER/REPO] [FILE] [--gen KEY,KEY] [--replace] [--yes] [--workspace SLUG]",
|
|
14610
|
+
"ghosty factory runs recheck <run> [--yes] [--workspace SLUG]",
|
|
14500
14611
|
`ghosty factory run "What, why and how we know it's done" --repo OWNER/REPO [--room R] [--wait] [--timeout 1800] [--workspace SLUG]`,
|
|
14501
14612
|
"ghosty factory runs watch <run> | --thread <id> [--timeout 1800] [--workspace SLUG]",
|
|
14502
14613
|
'ghosty factory runs approve <run> --plan-version N [--note "\u2026"] [--check-scope "\u2026"] [--yes]',
|
|
@@ -14521,6 +14632,8 @@ HELPS.factory = {
|
|
|
14521
14632
|
["--note T", "changes: what to change in the plan (or why to replan after an escalation). approve after an escalation: what @build must fix"],
|
|
14522
14633
|
["--check-scope T", "approve/changes: the only thing @check reviews on the next loop (runs advise proposes it); anything new goes as a note, not a finding"],
|
|
14523
14634
|
["--skip-preview", "merge: merge without the preview the plan asked for (logged)"],
|
|
14635
|
+
["--gen KEY,KEY", "env push: generate random keys (32 bytes hex) for those names right there; never printed"],
|
|
14636
|
+
["--replace", "env push: replace the whole preview .env (default: add, keeping the rest)"],
|
|
14524
14637
|
["--create", "rooms connect: create the room if it doesn't exist"],
|
|
14525
14638
|
["--private", "rooms connect --create: make the new room private (only you, then whoever you add)"],
|
|
14526
14639
|
["--room R", "rooms connect/disconnect: the room (slug or name). run: that room instead of finding it by --repo. ls: only that room's runs (#slug or name)"],
|
|
@@ -14534,7 +14647,7 @@ HELPS.factory = {
|
|
|
14534
14647
|
["--yes", "rename, approve, changes, merge: skip the confirmation (required without a terminal)"]
|
|
14535
14648
|
],
|
|
14536
14649
|
examples: ["ghosty factory rooms connect blissito/ghosty-studio --room studio --create --workspace business", 'ghosty factory run "Add a COLLABORATOR role: invited by room, lands in the room, no personal agent. Done when an invite to #factory logs in straight there" --repo blissito/ghosty-studio --wait --workspace business', "ghosty factory runs approve 31 --plan-version 2 --workspace business", "ghosty factory runs watch 31 --workspace business", "ghosty factory runs merge 31 --workspace business", "ghosty factory runs ls --workspace business --room mailmask", "ghosty factory runs show 15 --workspace business", "ghosty factory runs show 13 --timing --workspace mercadito-verde", "ghosty factory runs why 13 --workspace mercadito-verde", "ghosty factory runs advise 115 --workspace business", "ghosty factory runs stats --since 7d --workspace mercadito-verde", 'ghosty factory runs rename 15 "Docs del CLI al d\xEDa" --workspace business --dry-run'],
|
|
14537
|
-
notes: ["rooms repos lists the repos your GitHub App sees (most recent first) and which rooms each one is already in.", "rooms connect checks that YOUR GitHub App installation sees the repo (if not, it gives you the link to grant access) and tells you how ready the repo is for agents (levels 1\u20133 and what's missing). Any member can connect; only whoever connected it, or the owner, can disconnect. Logged as factory.room_connect / factory.room_disconnect.", "run posts \xAB@plan <your text>\xBB in the room under YOUR name, like typing it in Teams: the same plan, approvals and card. Write it like a ticket: the goal, why, and how you'll know it's done. Any member can run, approve, ask for changes and merge; each one is logged (factory.run, factory.run_approve\u2026).", "card brings the run's live card down to the bottom of its thread; the old one stays as a still footprint linking to the live one. The roles can do it too (factory_card).", "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.", "stats: runs, % merged, median minutes to PR and to merge, @check\u2192@build loops and red CI per run, and cost per run and per merged PR. The cost counts @plan's first turn in the thread, before the run existed.", "--timing reads each role's transcript: engines without one (ACP) show minutes and cost but no commands. It also lists stuck patterns: a command open 8+ min, the same command 4 times, the same error 3 times, going back and forth, or apt install inside a box.", "why: if the run wasn't healthy (stuck patterns, @build over 30 min, loops, red CI, escalated), a category (missing_service, unresolved_build_or_tests, fixed_strategy\u2026), the critical step with its evidence and what to change next time. First it checks the approved plan's criteria against the evidence: approved without the preview a criterion asked for, merged without preview, smoke failed on the merged commit, or a CI criterion with CI none \u2192 approved_unmet_criterion, above any stuck pattern. A healthy run doesn't call the model. When a run closes unhealthy, the room gets this summary in its thread.", "advise: for an ESCALATED run, what to do \u2014 let @build fix it or redo the plan \u2014 with the note for @build point by point (each one cites the finding or plan step behind it) and what @check should review. It compares @check's findings against the signed plan. Nothing is applied: it prints the exact approve/changes command. show tells you why it was escalated.", "While a role works, if it gets stuck the room gets a \u23F3 message in the thread and the role is told what to check; from the second time in the same run, whoever asked gets a push."],
|
|
14650
|
+
notes: ["env: the preview's environment variables live on your computer, so your terminal agent pushes them. ls lists saved and missing names (from .env.example and from runs escalated because the preview won't start); push adds them (only the workspace owner). Use disposable values, never production: the preview runs the repo's code. Values never come back. Then runs recheck <run>: @check reviews the same PR again without another @build loop. Logged as factory.env_set and factory.run_recheck.", "sprints: when the goal is too big for one run, @plan proposes a sprint (3\u20138 tickets in order, with dependencies) as a draft. approve signs every included ticket at once; they start one at a time as their dependencies merge. Only the owner or whoever asked can approve. Logged as factory.sprint_approve.", "rooms repos lists the repos your GitHub App sees (most recent first) and which rooms each one is already in.", "rooms connect checks that YOUR GitHub App installation sees the repo (if not, it gives you the link to grant access) and tells you how ready the repo is for agents (levels 1\u20133 and what's missing). Any member can connect; only whoever connected it, or the owner, can disconnect. Logged as factory.room_connect / factory.room_disconnect.", "run posts \xAB@plan <your text>\xBB in the room under YOUR name, like typing it in Teams: the same plan, approvals and card. Write it like a ticket: the goal, why, and how you'll know it's done. Any member can run, approve, ask for changes and merge; each one is logged (factory.run, factory.run_approve\u2026).", "card brings the run's live card down to the bottom of its thread; the old one stays as a still footprint linking to the live one. The roles can do it too (factory_card).", "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.", "stats: runs, % merged, median minutes to PR and to merge, @check\u2192@build loops and red CI per run, and cost per run and per merged PR. The cost counts @plan's first turn in the thread, before the run existed.", "--timing reads each role's transcript: engines without one (ACP) show minutes and cost but no commands. It also lists stuck patterns: a command open 8+ min, the same command 4 times, the same error 3 times, going back and forth, or apt install inside a box.", "why: if the run wasn't healthy (stuck patterns, @build over 30 min, loops, red CI, escalated), a category (missing_service, unresolved_build_or_tests, fixed_strategy\u2026), the critical step with its evidence and what to change next time. First it checks the approved plan's criteria against the evidence: approved without the preview a criterion asked for, merged without preview, smoke failed on the merged commit, or a CI criterion with CI none \u2192 approved_unmet_criterion, above any stuck pattern. A healthy run doesn't call the model. When a run closes unhealthy, the room gets this summary in its thread.", "advise: for an ESCALATED run, what to do \u2014 let @build fix it or redo the plan \u2014 with the note for @build point by point (each one cites the finding or plan step behind it) and what @check should review. It compares @check's findings against the signed plan. Nothing is applied: it prints the exact approve/changes command. show tells you why it was escalated.", "While a role works, if it gets stuck the room gets a \u23F3 message in the thread and the role is told what to check; from the second time in the same run, whoever asked gets a push."],
|
|
14538
14651
|
learn: `${REF}#fabrica`
|
|
14539
14652
|
};
|
|
14540
14653
|
function helpText(cmd) {
|
|
@@ -14561,7 +14674,7 @@ function helpText(cmd) {
|
|
|
14561
14674
|
|
|
14562
14675
|
// src/update.ts
|
|
14563
14676
|
init_i18n();
|
|
14564
|
-
import { mkdir as mkdir5, readFile as readFile9, writeFile as
|
|
14677
|
+
import { mkdir as mkdir5, readFile as readFile9, writeFile as writeFile9 } from "node:fs/promises";
|
|
14565
14678
|
import { homedir as homedir3 } from "node:os";
|
|
14566
14679
|
import { dirname as dirname3, join as join7 } from "node:path";
|
|
14567
14680
|
var FILE = join7(process.env.XDG_CONFIG_HOME || join7(homedir3(), ".config"), "ghosty", "update.json");
|
|
@@ -14587,7 +14700,7 @@ async function latestVersion(current2) {
|
|
|
14587
14700
|
const latest = (await r.json()).version;
|
|
14588
14701
|
if (latest) cache2 = { latest, checkedAt: Date.now() };
|
|
14589
14702
|
await mkdir5(dirname3(FILE), { recursive: true });
|
|
14590
|
-
await
|
|
14703
|
+
await writeFile9(FILE, JSON.stringify(cache2));
|
|
14591
14704
|
} catch {
|
|
14592
14705
|
}
|
|
14593
14706
|
}
|
|
@@ -14667,7 +14780,7 @@ function agentArgIndex(pos) {
|
|
|
14667
14780
|
}
|
|
14668
14781
|
|
|
14669
14782
|
// src/completion.ts
|
|
14670
|
-
import { mkdir as mkdir6, readFile as readFile10, writeFile as
|
|
14783
|
+
import { mkdir as mkdir6, readFile as readFile10, writeFile as writeFile10 } from "node:fs/promises";
|
|
14671
14784
|
import { homedir as homedir4 } from "node:os";
|
|
14672
14785
|
import { join as join8 } from "node:path";
|
|
14673
14786
|
var GLOBAL_FLAGS = ["--json", "--token", "--profile", "--yes", "--help", "--version", "--lang", "--dry-run"];
|
|
@@ -14719,7 +14832,7 @@ async function names() {
|
|
|
14719
14832
|
const workspaces = [...new Set(list2.map((a) => a.espacio?.tipo === "workspace" ? a.espacio.nombre : void 0).filter((x) => !!x))];
|
|
14720
14833
|
await mkdir6(join8(cacheFile(), ".."), { recursive: true }).catch(() => {
|
|
14721
14834
|
});
|
|
14722
|
-
await
|
|
14835
|
+
await writeFile10(cacheFile(), JSON.stringify({ at: Date.now(), agents, workspaces })).catch(() => {
|
|
14723
14836
|
});
|
|
14724
14837
|
return { agents, workspaces };
|
|
14725
14838
|
}
|
|
@@ -14806,7 +14919,7 @@ init_i18n();
|
|
|
14806
14919
|
// src/commands/spec.ts
|
|
14807
14920
|
var import_yaml = __toESM(require_dist(), 1);
|
|
14808
14921
|
init_api();
|
|
14809
|
-
import { mkdir as mkdir7, readdir as readdir4, readFile as readFile11, stat as stat2, writeFile as
|
|
14922
|
+
import { mkdir as mkdir7, readdir as readdir4, readFile as readFile11, stat as stat2, writeFile as writeFile11 } from "node:fs/promises";
|
|
14810
14923
|
import { join as join9, resolve as resolve2 } from "node:path";
|
|
14811
14924
|
init_util();
|
|
14812
14925
|
init_i18n();
|
|
@@ -14820,7 +14933,7 @@ async function agentsExport(id, v) {
|
|
|
14820
14933
|
}
|
|
14821
14934
|
const dir = resolve2(v.out);
|
|
14822
14935
|
await mkdir7(dir, { recursive: true });
|
|
14823
|
-
await
|
|
14936
|
+
await writeFile11(join9(dir, "agent.yaml"), yaml);
|
|
14824
14937
|
const own = (0, import_yaml.parse)(yaml)["x-ghosty"]?.skills?.own ?? [];
|
|
14825
14938
|
for (const slug of own) {
|
|
14826
14939
|
const sk = await api(`/api/v2/agents/${enc12(id)}/skills/${enc12(slug)}`);
|
|
@@ -14943,7 +15056,7 @@ ${dim(t(`${r.totals.activeTurns} turn(s) running`, `${r.totals.activeTurns} turn
|
|
|
14943
15056
|
init_api();
|
|
14944
15057
|
init_auth();
|
|
14945
15058
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
14946
|
-
import { mkdir as mkdir8, readFile as readFile12, writeFile as
|
|
15059
|
+
import { mkdir as mkdir8, readFile as readFile12, writeFile as writeFile12 } from "node:fs/promises";
|
|
14947
15060
|
import { homedir as homedir5 } from "node:os";
|
|
14948
15061
|
import { dirname as dirname4, join as join10 } from "node:path";
|
|
14949
15062
|
init_i18n();
|
|
@@ -14988,7 +15101,7 @@ async function visitorCookie(base, slug, fresh) {
|
|
|
14988
15101
|
if (!cookie) throw new CliError(t("The demo didn't hand out a visitor cookie.", "El demo no entreg\xF3 cookie de visitante."), EXIT.API);
|
|
14989
15102
|
jar[slug] = cookie;
|
|
14990
15103
|
await mkdir8(dirname4(jarPath()), { recursive: true });
|
|
14991
|
-
await
|
|
15104
|
+
await writeFile12(jarPath(), JSON.stringify(jar, null, 2), { mode: 384 });
|
|
14992
15105
|
return cookie;
|
|
14993
15106
|
}
|
|
14994
15107
|
async function visitorTurn(base, slug, cookie, text) {
|
|
@@ -15104,7 +15217,7 @@ var packPrice = (c, i, lang2) => `$${(PACK[i]?.[c] ?? 0).toLocaleString("en-US")
|
|
|
15104
15217
|
var kb = (n) => n ? `${Math.max(1, Math.round(n / 1024))} KB` : "";
|
|
15105
15218
|
var day = (iso) => iso ? iso.slice(0, 10) : "";
|
|
15106
15219
|
async function sitesCmd(sub, a1, a2, v) {
|
|
15107
|
-
const ex = "ghosty sites ls [--house] \xB7 pin|unpin <slug|id> \xB7 pins [--cancel|--resume] \xB7 publish <file.html|folder> [--title T] [--slug S] [--site <slug|id>] \xB7 slug <slug|id> <new-slug>";
|
|
15220
|
+
const ex = "ghosty sites ls [--house] \xB7 pin|unpin <slug|id> \xB7 pins [--cancel|--resume] \xB7 search <slug|id> [--days 28] \xB7 publish <file.html|folder> [--title T] [--slug S] [--site <slug|id>] \xB7 slug <slug|id> <new-slug>";
|
|
15108
15221
|
switch (sub ?? "ls") {
|
|
15109
15222
|
case "ls":
|
|
15110
15223
|
case "list": {
|
|
@@ -15145,6 +15258,22 @@ ${dim(r.expiresAt ? t(`Free rule: pauses ${day(r.expiresAt)} unless it changes (
|
|
|
15145
15258
|
${table(r.pinned.map((p) => ({ slug: p.slug, title: (p.title ?? "").slice(0, 40), url: p.url, since: day(p.pinnedAt) })), ["slug", "title", "url", "since"])}` : t("No pinned sites. Pin one ($49 USD/yr or $5 USD/mo, includes up to 3 sites): ghosty sites pin <slug>", "Sin sitios fijos. Fija uno ($49 USD al a\xF1o o $5 USD al mes, incluye hasta 3 sitios): ghosty sites pin <slug>")
|
|
15146
15259
|
);
|
|
15147
15260
|
}
|
|
15261
|
+
case "search": {
|
|
15262
|
+
if (!a1) throw usage(t("Missing the site (slug, id or URL).", "Falta el sitio (slug, id o URL)."), "ghosty sites search <slug|id> [--days 28]");
|
|
15263
|
+
const r = await api(`/api/v2/sites/${encodeURIComponent(a1)}/search${v.days ? `?days=${encodeURIComponent(v.days)}` : ""}`);
|
|
15264
|
+
const p = r.portada;
|
|
15265
|
+
const index = p.error ? dim(p.error) : p.indexada ? `${lavender("\u2713")} ${t("indexed", "indexada")} ${dim(day(p.ultimoRastreo ?? null))}` : `${t("not indexed", "sin indexar")} ${dim(p.estado ?? "")}`;
|
|
15266
|
+
const tot = r.total;
|
|
15267
|
+
return out(
|
|
15268
|
+
r,
|
|
15269
|
+
() => [
|
|
15270
|
+
`${bold(r.sitio)} ${dim(`${r.desde} \u2192 ${r.hasta}`)}`,
|
|
15271
|
+
`${t("Home", "Portada")}: ${index}`,
|
|
15272
|
+
`${t("clicks", "clics")} ${tot.clics} \xB7 ${t("impressions", "impresiones")} ${tot.impresiones} \xB7 CTR ${tot.ctr} \xB7 ${t("position", "posici\xF3n")} ${tot.posicion ?? "\u2014"}`,
|
|
15273
|
+
r.busquedas.length ? table(r.busquedas.map((q) => ({ query: q.busqueda, clicks: q.clics, impressions: q.impresiones, position: q.posicion })), ["query", "clicks", "impressions", "position"]) : dim(t("No searches yet (Google reports 2\u20133 days late).", "A\xFAn sin b\xFAsquedas (Google reporta con 2\u20133 d\xEDas de atraso)."))
|
|
15274
|
+
].join("\n")
|
|
15275
|
+
);
|
|
15276
|
+
}
|
|
15148
15277
|
case "publish":
|
|
15149
15278
|
case "deploy": {
|
|
15150
15279
|
if (!a1) throw usage(t("Missing the file or folder.", "Falta el archivo o la carpeta."), ex);
|
|
@@ -15174,7 +15303,7 @@ ${dim(t("Links with the old slug may stop working.", "Las ligas con el slug viej
|
|
|
15174
15303
|
|
|
15175
15304
|
// src/commands/docs.ts
|
|
15176
15305
|
init_api();
|
|
15177
|
-
import { writeFile as
|
|
15306
|
+
import { writeFile as writeFile13 } from "node:fs/promises";
|
|
15178
15307
|
|
|
15179
15308
|
// ../docs/src/core/doc-blocks.ts
|
|
15180
15309
|
var DOC_ENVELOPE_VERSION = 1;
|
|
@@ -15337,7 +15466,7 @@ ${t("Ids: ghosty me docs ls", "Ids: ghosty me docs ls")}`);
|
|
|
15337
15466
|
}
|
|
15338
15467
|
const named = /filename\*=UTF-8''([^;]+)/i.exec(res.headers.get("content-disposition") ?? "")?.[1];
|
|
15339
15468
|
const file = v.out ?? (named ? decodeURIComponent(named) : fileName(id, format));
|
|
15340
|
-
await
|
|
15469
|
+
await writeFile13(file, bytes);
|
|
15341
15470
|
return out({ id, format, saved: file, bytes: bytes.length }, () => `${lavender("\u2713")} ${file} (${bytes.length} bytes)`);
|
|
15342
15471
|
}
|
|
15343
15472
|
case "check": {
|
|
@@ -15363,7 +15492,7 @@ ${t("Ids: ghosty me docs ls", "Ids: ghosty me docs ls")}`);
|
|
|
15363
15492
|
if (res.status === 204) return out({ id, block, segment, segments, saved: null }, () => dim(t("That block has no text to read.", "Ese bloque no tiene texto que leer.")));
|
|
15364
15493
|
const bytes = Buffer.from(await res.arrayBuffer());
|
|
15365
15494
|
const file = v.out ?? `${id}-${block}${segment !== null ? `-${segment}` : ""}.${audioExt(res.headers.get("content-type"))}`;
|
|
15366
|
-
await
|
|
15495
|
+
await writeFile13(file, bytes);
|
|
15367
15496
|
return out({ id, block, segment, segments, saved: file, bytes: bytes.length }, () => `${lavender("\u2713")} ${file} (${bytes.length} bytes) ${dim(t(`\xB7 the block has ${segments} sentence(s): --segment 0\u2026${Math.max(segments - 1, 0)}`, `\xB7 el bloque tiene ${segments} frase(s): --segment 0\u2026${Math.max(segments - 1, 0)}`))}`);
|
|
15368
15497
|
}
|
|
15369
15498
|
case "restore": {
|
|
@@ -15532,6 +15661,9 @@ ${dim(t("Times in UTC. --full for the whole detail; --json for everything.", "Ho
|
|
|
15532
15661
|
|
|
15533
15662
|
// src/commands/factory.ts
|
|
15534
15663
|
init_api();
|
|
15664
|
+
import { execFileSync } from "node:child_process";
|
|
15665
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
15666
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
15535
15667
|
init_util();
|
|
15536
15668
|
init_i18n();
|
|
15537
15669
|
var enc16 = encodeURIComponent;
|
|
@@ -15589,6 +15721,103 @@ async function roomsCmd(ws, verb, repo, v) {
|
|
|
15589
15721
|
].filter((l) => l !== "").join("\n")
|
|
15590
15722
|
);
|
|
15591
15723
|
}
|
|
15724
|
+
async function sprintsCmd(ws, verb, ref, v) {
|
|
15725
|
+
const path = `/api/v2/workspaces/${enc16(ws)}/factory/sprints`;
|
|
15726
|
+
if (!verb || verb === "ls" || verb === "list") {
|
|
15727
|
+
const r2 = await api(path);
|
|
15728
|
+
const rows = r2.sprints.map((x) => ({ sprint: `#${x.id}`, title: x.title.slice(0, 60), status: x.status, room: x.room ? `#${x.room}` : "", merged: `${x.merged}/${x.total}` }));
|
|
15729
|
+
return out(r2, () => rows.length ? table(rows, ["sprint", "title", "status", "room", "merged"]) : dim(t("No sprints yet.", "Todav\xEDa no hay sprints.")));
|
|
15730
|
+
}
|
|
15731
|
+
const id = Number(String(ref ?? "").replace(/^#/, ""));
|
|
15732
|
+
if (verb !== "show" && verb !== "approve") throw usage(t(`Unknown sprints subcommand: ${verb}.`, `Subcomando de sprints desconocido: ${verb}.`), "ghosty factory sprints ls | show <id> | approve <id> --yes");
|
|
15733
|
+
if (!id) throw usage(t("Which sprint?", "\xBFQu\xE9 sprint?"), `ghosty factory sprints ${verb} 12 --workspace ${ws}`);
|
|
15734
|
+
if (verb === "approve") {
|
|
15735
|
+
const retry = `ghosty factory sprints approve ${id} --workspace ${ws}`;
|
|
15736
|
+
requireYesIfHeadless(v.yes, retry);
|
|
15737
|
+
await confirm(t(`Approve sprint #${id}? Every included ticket starts, one at a time.`, `\xBFAprobar el sprint #${id}? Arrancan todos los tickets incluidos, uno a la vez.`), { yes: v.yes, retry });
|
|
15738
|
+
}
|
|
15739
|
+
const r = verb === "approve" ? await api(`${path}/${id}`, { body: { action: "approve" } }) : await api(`${path}/${id}`);
|
|
15740
|
+
return out(
|
|
15741
|
+
r,
|
|
15742
|
+
() => [
|
|
15743
|
+
`${bold(`#${r.id} ${r.title}`)} ${dim(`\xB7 ${r.status} \xB7 v${r.version}${r.repo ? ` \xB7 ${r.repo}` : ""}`)}`,
|
|
15744
|
+
r.goal ? dim(r.goal) : "",
|
|
15745
|
+
table(
|
|
15746
|
+
r.items.map((i) => ({ key: i.key, ticket: i.title.slice(0, 56), size: i.size, after: i.dependsOn.join(",") || "", status: i.included ? i.status : dim(t("out", "fuera")), run: i.runId ? `#${i.runId}` : "" })),
|
|
15747
|
+
["key", "ticket", "size", "after", "status", "run"]
|
|
15748
|
+
),
|
|
15749
|
+
r.status === "draft" && r.canEdit ? dim(t(`Approve: ghosty factory sprints approve ${r.id} --workspace ${ws}`, `Aprueba: ghosty factory sprints approve ${r.id} --workspace ${ws}`)) : ""
|
|
15750
|
+
].filter(Boolean).join("\n")
|
|
15751
|
+
);
|
|
15752
|
+
}
|
|
15753
|
+
function repoHere() {
|
|
15754
|
+
try {
|
|
15755
|
+
const url = execFileSync("git", ["remote", "get-url", "origin"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
15756
|
+
return url.match(/github\.com[:/]([^/]+\/[^/]+?)(?:\.git)?$/)?.[1] ?? null;
|
|
15757
|
+
} catch {
|
|
15758
|
+
return null;
|
|
15759
|
+
}
|
|
15760
|
+
}
|
|
15761
|
+
function dotenvKeys(text) {
|
|
15762
|
+
return text.split("\n").map((l) => l.trim().replace(/^export\s+/, "")).filter((l) => l && !l.startsWith("#") && l.indexOf("=") > 0).map((l) => l.slice(0, l.indexOf("=")).trim());
|
|
15763
|
+
}
|
|
15764
|
+
async function envCmd(ws, verb, ref, file, v) {
|
|
15765
|
+
const path = `/api/v2/workspaces/${enc16(ws)}/factory/env`;
|
|
15766
|
+
const repo = ref && ref.includes("/") ? ref : v.repo ?? repoHere();
|
|
15767
|
+
if (ref && !ref.includes("/") && !file) file = ref;
|
|
15768
|
+
if (!repo) throw usage(t("Which repo?", "\xBFQu\xE9 repo?"), `ghosty factory env ${verb ?? "ls"} OWNER/REPO --workspace ${ws}`);
|
|
15769
|
+
if (!verb || verb === "ls" || verb === "list") {
|
|
15770
|
+
const r2 = await api(`${path}?repo=${enc16(repo)}`);
|
|
15771
|
+
const risk = new Map(r2.risks.map((x) => [x.key, x.risk]));
|
|
15772
|
+
return out(
|
|
15773
|
+
r2,
|
|
15774
|
+
() => [
|
|
15775
|
+
`${bold(r2.repo)} ${dim(`\xB7 ${r2.rooms.map((x) => `#${x}`).join(" ")}`)}`,
|
|
15776
|
+
`${t("saved", "guardadas")}: ${r2.saved.length ? r2.saved.map((k) => risk.has(k) ? red(`${k} (${risk.get(k)})`) : k).join(", ") : dim(t("none", "ninguna"))}`,
|
|
15777
|
+
r2.missing.length ? `${red(t("missing", "faltan"))}: ${r2.missing.join(", ")}` : `${lavender("\u2713")} ${t("nothing missing", "no falta ninguna")}`,
|
|
15778
|
+
...r2.blocked.map((b) => dim(t(`#${b.id} is escalated because its preview doesn't start${b.keys.length ? ` (${b.keys.join(", ")})` : ""}`, `#${b.id} est\xE1 escalado porque su preview no arranca${b.keys.length ? ` (${b.keys.join(", ")})` : ""}`))),
|
|
15779
|
+
r2.missing.length ? dim(
|
|
15780
|
+
t(
|
|
15781
|
+
`Use DISPOSABLE values (a test database, freshly generated keys), never production: the preview runs the repo's code and can read them.
|
|
15782
|
+
Push: ghosty factory env push ${r2.repo} .env.preview${r2.missing.length ? ` [--gen KEY,KEY]` : ""} --workspace ${ws}`,
|
|
15783
|
+
`Usa valores DESECHABLES (una base de prueba, llaves reci\xE9n generadas), nunca los de producci\xF3n: la preview corre el c\xF3digo del repo y puede leerlos.
|
|
15784
|
+
S\xFAbelas: ghosty factory env push ${r2.repo} .env.preview${r2.missing.length ? ` [--gen LLAVE,LLAVE]` : ""} --workspace ${ws}`
|
|
15785
|
+
)
|
|
15786
|
+
) : "",
|
|
15787
|
+
r2.canWrite ? "" : dim(t("Only the workspace owner can save them.", "S\xF3lo el due\xF1o del espacio puede guardarlas."))
|
|
15788
|
+
].filter(Boolean).join("\n")
|
|
15789
|
+
);
|
|
15790
|
+
}
|
|
15791
|
+
if (verb !== "push") throw usage(t(`Unknown env subcommand: ${verb}.`, `Subcomando de env desconocido: ${verb}.`), "ghosty factory env ls [OWNER/REPO] | push [OWNER/REPO] [FILE] [--gen KEY,KEY] [--replace] [--yes]");
|
|
15792
|
+
const src = file ?? v.file;
|
|
15793
|
+
if (src && !existsSync(src)) throw usage(t(`No such file: ${src}.`, `No existe el archivo: ${src}.`), t("A .env file: KEY=value per line.", "Un archivo .env: KEY=valor por rengl\xF3n."));
|
|
15794
|
+
let dotenv = src ? readFileSync(src, "utf8") : "";
|
|
15795
|
+
const gen = (v.gen ?? "").split(",").map((k) => k.trim()).filter(Boolean);
|
|
15796
|
+
for (const k of gen) {
|
|
15797
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(k)) throw usage(t(`Invalid name: ${k}.`, `Nombre inv\xE1lido: ${k}.`), "--gen SESSION_SECRET,ENCRYPTION_KEY");
|
|
15798
|
+
dotenv += `
|
|
15799
|
+
${k}=${randomBytes2(32).toString("hex")}`;
|
|
15800
|
+
}
|
|
15801
|
+
const keys = [...new Set(dotenvKeys(dotenv))];
|
|
15802
|
+
if (!keys.length) throw usage(t("Nothing to push.", "No hay nada que subir."), `ghosty factory env push ${repo} .env.preview [--gen KEY] --workspace ${ws}`);
|
|
15803
|
+
const retry = `ghosty factory env push ${repo}${src ? ` ${src}` : ""}${gen.length ? ` --gen ${gen.join(",")}` : ""}${v.replace ? " --replace" : ""} --workspace ${ws}`;
|
|
15804
|
+
requireYesIfHeadless(v.yes, retry);
|
|
15805
|
+
await confirm(
|
|
15806
|
+
v.replace ? t(`REPLACE the whole preview .env of ${repo} with ${keys.length} variables (${keys.join(", ")})?`, `\xBFREEMPLAZAR todo el .env de la preview de ${repo} con ${keys.length} variables (${keys.join(", ")})?`) : t(`Save ${keys.length} variables for the preview of ${repo} (${keys.join(", ")})? The others stay.`, `\xBFGuardar ${keys.length} variables para la preview de ${repo} (${keys.join(", ")})? Las dem\xE1s se quedan.`),
|
|
15807
|
+
{ yes: v.yes, retry }
|
|
15808
|
+
);
|
|
15809
|
+
const r = await api(path, {
|
|
15810
|
+
body: { repo, dotenv, ...v.replace ? { replace: true } : {} }
|
|
15811
|
+
});
|
|
15812
|
+
return out(
|
|
15813
|
+
r,
|
|
15814
|
+
() => [
|
|
15815
|
+
`${lavender("\u2713")} ${t(`${r.repo}: ${r.keys.length} variables saved; its previews are rebuilding.`, `${r.repo}: ${r.keys.length} variables guardadas; sus previews se est\xE1n reconstruyendo.`)}`,
|
|
15816
|
+
...r.risks.map((x) => red(t(`${x.key} touches production (${x.risk}): use a disposable value.`, `${x.key} toca producci\xF3n (${x.risk}): usa un valor desechable.`))),
|
|
15817
|
+
...r.blocked.map((b) => dim(t(`#${b.id} was waiting for this: ghosty factory runs recheck ${b.id} --workspace ${ws}`, `#${b.id} esperaba esto: ghosty factory runs recheck ${b.id} --workspace ${ws}`)))
|
|
15818
|
+
].join("\n")
|
|
15819
|
+
);
|
|
15820
|
+
}
|
|
15592
15821
|
var sleep3 = (ms) => new Promise((ok) => setTimeout(ok, ms));
|
|
15593
15822
|
var STOPS = /* @__PURE__ */ new Set(["plan_review", "escalated", "pr_review", "done", "cancelled"]);
|
|
15594
15823
|
var STAGE = {
|
|
@@ -15613,6 +15842,17 @@ Or ask for changes: ghosty factory runs changes ${r.id} --note "\u2026" ${w}`,
|
|
|
15613
15842
|
Firma: ghosty factory runs approve ${r.id} --plan-version ${r.planVersion} ${w}
|
|
15614
15843
|
O pide cambios: ghosty factory runs changes ${r.id} --note "\u2026" ${w}`
|
|
15615
15844
|
);
|
|
15845
|
+
if (r.status === "escalated" && "escalation" in r && r.escalation?.blocked)
|
|
15846
|
+
return t(
|
|
15847
|
+
`@check is blocked by something @build can't provide (e.g. preview variables). Resolve it, then:
|
|
15848
|
+
What's missing: ghosty factory env ls ${r.repo ?? "OWNER/REPO"} ${w}
|
|
15849
|
+
Review again (no @build loop): ghosty factory runs recheck ${r.id} ${w}
|
|
15850
|
+
Replan: ghosty factory runs changes ${r.id} --note "\u2026" ${w}`,
|
|
15851
|
+
`A @check lo bloquea algo que @build no puede dar (p. ej. variables de la preview). Resu\xE9lvelo y luego:
|
|
15852
|
+
Qu\xE9 falta: ghosty factory env ls ${r.repo ?? "DUE\xD1O/REPO"} ${w}
|
|
15853
|
+
Que revise otra vez (sin vuelta de @build): ghosty factory runs recheck ${r.id} ${w}
|
|
15854
|
+
Replanear: ghosty factory runs changes ${r.id} --note "\u2026" ${w}`
|
|
15855
|
+
);
|
|
15616
15856
|
if (r.status === "escalated")
|
|
15617
15857
|
return t(
|
|
15618
15858
|
`Another loop: ghosty factory runs approve ${r.id} ${w}
|
|
@@ -15661,10 +15901,12 @@ function timeoutOf(v) {
|
|
|
15661
15901
|
return s2;
|
|
15662
15902
|
}
|
|
15663
15903
|
async function factoryCmd(sub, verb, ref, title, v, context) {
|
|
15664
|
-
if (sub !== "runs" && sub !== "run" && sub !== "rooms") throw usage(t(`Unknown factory subcommand: ${sub ?? "(none)"}.`, `Subcomando de factory desconocido: ${sub ?? "(ninguno)"}.`), 'ghosty factory run "\u2026" | rooms ls|connect|disconnect | runs ls|show|why|advise|stats|rename|watch|approve|changes|merge|card \u2026');
|
|
15904
|
+
if (sub !== "runs" && sub !== "run" && sub !== "rooms" && sub !== "sprints" && sub !== "env") throw usage(t(`Unknown factory subcommand: ${sub ?? "(none)"}.`, `Subcomando de factory desconocido: ${sub ?? "(ninguno)"}.`), 'ghosty factory run "\u2026" | rooms ls|repos|connect|disconnect | sprints ls|show|approve | env ls|push | runs ls|show|why|advise|stats|rename|watch|approve|changes|merge|card \u2026');
|
|
15665
15905
|
const ws = v.workspace ?? context;
|
|
15666
15906
|
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)."));
|
|
15667
15907
|
if (sub === "rooms") return roomsCmd(ws, verb, ref, v);
|
|
15908
|
+
if (sub === "sprints") return sprintsCmd(ws, verb, ref, v);
|
|
15909
|
+
if (sub === "env") return envCmd(ws, verb, ref, title, v);
|
|
15668
15910
|
const base = `/api/v2/workspaces/${enc16(ws)}/factory/runs`;
|
|
15669
15911
|
if (sub === "run") {
|
|
15670
15912
|
const text = verb?.trim();
|
|
@@ -15764,20 +16006,25 @@ ${dim(`${t("Detail", "Detalle")}: ghosty factory runs show <run> --workspace ${r
|
|
|
15764
16006
|
const { advice: a, workspace } = await api(`${path}/advise`, { body: {} });
|
|
15765
16007
|
const note = a.buildNote.map((p, i) => `${i + 1}. ${p.point}`).join(" ");
|
|
15766
16008
|
const sh = (x) => `'${x.replace(/'/g, `'\\''`)}'`;
|
|
15767
|
-
const apply = a.decision === "fix" ? `ghosty factory runs approve ${id} --note ${sh(note)}${a.checkScope ? ` --check-scope ${sh(a.checkScope)}` : ""} --workspace ${workspace}` : `ghosty factory runs changes ${id} --note ${sh(note)} --workspace ${workspace}
|
|
16009
|
+
const apply = a.decision === "fix" ? `ghosty factory runs approve ${id} --note ${sh(note)}${a.checkScope ? ` --check-scope ${sh(a.checkScope)}` : ""} --workspace ${workspace}` : a.decision === "replan" ? `ghosty factory runs changes ${id} --note ${sh(note)} --workspace ${workspace}` : a.decision === "recheck" ? `ghosty factory runs recheck ${id} --workspace ${workspace}` : null;
|
|
16010
|
+
const label = {
|
|
16011
|
+
fix: t("Let @build fix it", "Que @build lo arregle"),
|
|
16012
|
+
replan: t("Redo the plan", "Rehacer el plan"),
|
|
16013
|
+
close: t("Close the PR", "Cerrar el PR"),
|
|
16014
|
+
close_ignore: t("Close the PR and ignore this version", "Cerrar el PR e ignorar esta versi\xF3n"),
|
|
16015
|
+
recheck: t("Let @check review again", "Que @check revise otra vez"),
|
|
16016
|
+
wait: t("Wait: it's a platform fault", "Esperar: es una falla de la plataforma")
|
|
16017
|
+
}[a.decision] ?? a.decision;
|
|
15768
16018
|
return out({ workspace, advice: a, apply }, () => {
|
|
15769
16019
|
const conf = { high: t("high", "alta"), medium: t("medium", "media"), low: t("low", "baja") }[a.confidence] ?? a.confidence;
|
|
15770
16020
|
return [
|
|
15771
|
-
`${bold(`#${id}`)} ${bold(
|
|
16021
|
+
`${bold(`#${id}`)} ${bold(label)} ${dim(`\xB7 ${t("confidence", "confianza")} ${conf} \xB7 ${a.model}`)}`,
|
|
15772
16022
|
a.why,
|
|
15773
|
-
"",
|
|
15774
|
-
|
|
15775
|
-
...a.buildNote.map((p, i) => ` ${i + 1}. ${p.point}
|
|
15776
|
-
${dim(p.cites)}`),
|
|
16023
|
+
...a.buildNote.length ? ["", bold(a.decision === "fix" ? t("Note for @build", "Nota para @build") : t("What to change in the plan", "Qu\xE9 cambiar en el plan")), ...a.buildNote.map((p, i) => ` ${i + 1}. ${p.point}
|
|
16024
|
+
${dim(p.cites)}`)] : [],
|
|
15777
16025
|
...a.checkScope && a.decision === "fix" ? ["", bold(t("@check reviews only", "@check revisa s\xF3lo")), ` ${a.checkScope}`] : [],
|
|
15778
16026
|
"",
|
|
15779
|
-
dim(t("Nothing was applied. To apply it (edit the note first if you want):", "No se aplic\xF3 nada. Para aplicarla (edita la nota antes si quieres):")),
|
|
15780
|
-
` ${apply}`
|
|
16027
|
+
...apply ? [dim(t("Nothing was applied. To apply it (edit the note first if you want):", "No se aplic\xF3 nada. Para aplicarla (edita la nota antes si quieres):")), ` ${apply}`] : [dim(t("Nothing was applied. Decide it on the run's card or in the app.", "No se aplic\xF3 nada. Dec\xEDdelo en la tarjeta del pedido o en la app."))]
|
|
15781
16028
|
].join("\n");
|
|
15782
16029
|
});
|
|
15783
16030
|
}
|
|
@@ -15813,7 +16060,7 @@ ${dim(t("Dry run: nothing changed.", "Simulacro: no se cambi\xF3 nada."))}`);
|
|
|
15813
16060
|
const r = await api(path, { body: { title } });
|
|
15814
16061
|
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)"))}`);
|
|
15815
16062
|
}
|
|
15816
|
-
if (verb === "approve" || verb === "changes" || verb === "merge" || verb === "card") {
|
|
16063
|
+
if (verb === "approve" || verb === "changes" || verb === "recheck" || verb === "merge" || verb === "card") {
|
|
15817
16064
|
const id = Number(ref.replace(/^#/, ""));
|
|
15818
16065
|
const note = v.note?.trim() ?? title?.trim();
|
|
15819
16066
|
const scope = verb === "approve" || verb === "changes" ? v["check-scope"]?.trim() : void 0;
|
|
@@ -15822,7 +16069,7 @@ ${dim(t("Dry run: nothing changed.", "Simulacro: no se cambi\xF3 nada."))}`);
|
|
|
15822
16069
|
if (verb !== "card") {
|
|
15823
16070
|
requireYesIfHeadless(v.yes, retry);
|
|
15824
16071
|
const cur = (await api(`${base}/${id}`)).run;
|
|
15825
|
-
const what = { approve: t(`Approve #${id} (plan v${cur.planVersion})`, `\xBFFirmar el #${id} (plan v${cur.planVersion})`), changes: t(`Ask for changes on #${id}`, `\xBFPedir cambios en el #${id}`), merge: t(`Merge the PR of #${id} (${cur.pr ?? "\u2014"})`, `\xBFMergear el PR del #${id} (${cur.pr ?? "\u2014"})`) }[verb];
|
|
16072
|
+
const what = { approve: t(`Approve #${id} (plan v${cur.planVersion})`, `\xBFFirmar el #${id} (plan v${cur.planVersion})`), changes: t(`Ask for changes on #${id}`, `\xBFPedir cambios en el #${id}`), recheck: t(`Have @check review #${id} again (no @build loop)`, `\xBFQue @check revise otra vez el #${id} (sin vuelta de @build)`), merge: t(`Merge the PR of #${id} (${cur.pr ?? "\u2014"})`, `\xBFMergear el PR del #${id} (${cur.pr ?? "\u2014"})`) }[verb];
|
|
15826
16073
|
await confirm(`${what}?`, { yes: v.yes, retry });
|
|
15827
16074
|
}
|
|
15828
16075
|
const r = await api(`${base}/${id}/act`, {
|
|
@@ -15831,13 +16078,14 @@ ${dim(t("Dry run: nothing changed.", "Simulacro: no se cambi\xF3 nada."))}`);
|
|
|
15831
16078
|
const msg = {
|
|
15832
16079
|
approve: t(`#${id} approved: ${stage(r.status)}.`, `#${id} firmado: ${stage(r.status)}.`),
|
|
15833
16080
|
changes: t(`#${id}: changes sent to @plan.`, `#${id}: cambios enviados a @plan.`),
|
|
16081
|
+
recheck: t(`#${id}: @check is reviewing the same PR again.`, `#${id}: @check revisa otra vez el mismo PR.`),
|
|
15834
16082
|
merge: r.queued ? t(`#${id}: merge queued, it goes in when CI passes.`, `#${id}: merge en cola, entra cuando pase el CI.`) : t(`#${id} merged.`, `#${id} mergeado.`),
|
|
15835
16083
|
card: t(`#${id}: the card is at the bottom of its thread again.`, `#${id}: la tarjeta volvi\xF3 al final de su hilo.`)
|
|
15836
16084
|
}[verb];
|
|
15837
|
-
return out(r, () => `${lavender("\u2713")} ${msg}${verb === "approve" || verb === "changes" ? `
|
|
16085
|
+
return out(r, () => `${lavender("\u2713")} ${msg}${verb === "approve" || verb === "changes" || verb === "recheck" ? `
|
|
15838
16086
|
${dim(t(`Follow it: ghosty factory runs watch ${id} --workspace ${ws}`, `S\xEDguelo: ghosty factory runs watch ${id} --workspace ${ws}`))}` : ""}`);
|
|
15839
16087
|
}
|
|
15840
|
-
throw usage(t(`Unknown runs subcommand: ${verb}.`, `Subcomando de runs desconocido: ${verb}.`), 'ghosty factory runs ls | show <run> [--timing] | why <run> [--refresh] | advise <run> | stats [--since 30d] | rename <run> "title" | watch <run> | approve <run> --plan-version N [--note "\u2026"] [--check-scope "\u2026"] | changes <run> --note "\u2026" | merge <run> | card <run>');
|
|
16088
|
+
throw usage(t(`Unknown runs subcommand: ${verb}.`, `Subcomando de runs desconocido: ${verb}.`), 'ghosty factory runs ls | show <run> [--timing] | why <run> [--refresh] | advise <run> | stats [--since 30d] | rename <run> "title" | watch <run> | approve <run> --plan-version N [--note "\u2026"] [--check-scope "\u2026"] | changes <run> --note "\u2026" | recheck <run> | merge <run> | card <run>');
|
|
15841
16089
|
}
|
|
15842
16090
|
async function agentsByHandle(handle, workspace) {
|
|
15843
16091
|
const qs = new URLSearchParams({ handle, ...workspace ? { workspace } : {} });
|
|
@@ -15920,6 +16168,9 @@ async function adsCmd(sub, action, v) {
|
|
|
15920
16168
|
lavender(t("Meta ad accounts", "Cuentas de Meta")),
|
|
15921
16169
|
r.accounts.length ? table(r.accounts.map((a) => ({ workspace: a.workspace, account: a.account ?? "", active: `${a.active}/${a.campaigns}`, status: a.accountStatus ?? "", token: day3(a.tokenExpiresAt), checked: day3(a.checkedAt), alerted: day3(a.alertedAt), error: a.error ?? "" })), ["workspace", "account", "active", "status", "token", "checked", "alerted", "error"]) : dim(t("(none connected)", "(ninguna conectada)")),
|
|
15922
16170
|
"",
|
|
16171
|
+
lavender(t("Google Ads accounts", "Cuentas de Google Ads")),
|
|
16172
|
+
r.google?.length ? table(r.google.map((g) => ({ workspace: g.workspace, account: g.account, link: g.link, active: `${g.active}/${g.campaigns}`, status: g.accountStatus ?? "", checked: day3(g.checkedAt), alerted: day3(g.alertedAt), error: g.error ?? "" })), ["workspace", "account", "link", "active", "status", "checked", "alerted", "error"]) : dim(t("(none linked)", "(ninguna vinculada)")),
|
|
16173
|
+
"",
|
|
15923
16174
|
lavender(t("Ads-only WhatsApp numbers", "N\xFAmeros de WhatsApp \xABs\xF3lo anuncios\xBB")),
|
|
15924
16175
|
r.numbers.length ? table(r.numbers.map((n) => ({ number: n.number, "last ad lead": day3(n.lastAdLeadAt), alerted: day3(n.alertedAt) })), ["number", "last ad lead", "alerted"]) : dim(t("(none)", "(ninguno)"))
|
|
15925
16176
|
].join("\n")
|
|
@@ -16248,6 +16499,8 @@ ${bold("Workspaces")} ${dim("(changing them: Ghosty staff)")}
|
|
|
16248
16499
|
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]
|
|
16249
16500
|
factory rooms ls|repos|connect|disconnect Set up the factory for a repo without opening Teams: create a room and connect the repo to it [connect owner/repo --room R --create]
|
|
16250
16501
|
factory run "\u2026" --repo o/n [--wait] Hand a request to the Software Factory from the terminal (@plan plans, @build builds, @check reviews); --wait returns when you have to decide [runs watch|approve|changes|merge|card <run>]
|
|
16502
|
+
factory sprints ls|show|approve The sprints @plan proposes for a bigger goal (3\u20138 tickets in order): list, read, approve with one signature [approve <id> --yes]
|
|
16503
|
+
factory env ls|push [o/n] [.env] The preview's environment variables: what's saved and missing, push them from your .env (disposable values) [--gen KEY,KEY] [--replace]
|
|
16251
16504
|
factory runs ls|show|why|advise|stats|rename A workspace's Software Factory runs: list (time and cost), detail (plan, PR, preview, who is working), where the time and money went, why it got stuck or failed, what to do when it's escalated, totals, fix a title [--workspace SLUG] [--room R] [--status S] [show --timing] [why --refresh] [stats --since 30d --repo R]
|
|
16252
16505
|
|
|
16253
16506
|
${bold("Agents")}
|
|
@@ -16344,6 +16597,7 @@ ${bold("Sites")} ${dim("(sites your agents publish at <slug>.ghosty.page)")}
|
|
|
16344
16597
|
sites ls Sites your agents published (live and paused) [--house: Ghosty's own demos and landings (staff)]
|
|
16345
16598
|
sites pin <slug|id> Pin it: never pauses, no banner ($49 USD/yr or $5/mo, includes up to 3 sites) [--interval year|month] [--currency usd|mxn] [--add-pack: 3 more]
|
|
16346
16599
|
sites unpin <slug|id> Release its slot (back to the Free rule: 30 days + notice)
|
|
16600
|
+
sites search <slug|id> How Google finds it: clicks, impressions, searches, indexed or not [--days 28]
|
|
16347
16601
|
sites pins Your pinned-sites subscription and slots [--cancel | --resume]
|
|
16348
16602
|
sites publish <file.html|folder> Publish a house site (staff): no owner, no expiry, no banner [--title] [--slug S] [--site <slug|id>: update that one]
|
|
16349
16603
|
sites slug <slug|id> <new-slug> Change a site's slug (staff)
|
|
@@ -16386,6 +16640,7 @@ ${bold("Browser")} ${dim("(Ghosty for Chrome)")}
|
|
|
16386
16640
|
|
|
16387
16641
|
${bold("Partners")} ${dim("(give an agent to each of YOUR customers \u2014 see: ghosty partner --help)")}
|
|
16388
16642
|
partner ls | enable | credentials | agent | tenants | audit | try
|
|
16643
|
+
image generate "prompt" An AI image for your businesses, charged to the space's balance [--ref URL (repeat, keeps the character)] [--aspect 16:9] [--tenant ID] [--out FILE]
|
|
16389
16644
|
|
|
16390
16645
|
${bold("Your account")}
|
|
16391
16646
|
me files ls|get|export|upload|rm Files in your account [--q TEXT] [--kind image|video|audio|document|other] [--cursor C] \xB7 get <id> [--out FILE]
|
|
@@ -16447,6 +16702,8 @@ ${bold("Espacios")} ${dim("(cambiarlos: staff de Ghosty)")}
|
|
|
16447
16702
|
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]
|
|
16448
16703
|
factory rooms ls|repos|connect|disconnect Deja lista la f\xE1brica de un repo sin abrir Teams: crea un room y con\xE9ctale el repo [connect due\xF1o/repo --room R --create]
|
|
16449
16704
|
factory run "\u2026" --repo o/n [--wait] P\xEDdele algo a la Software Factory desde la terminal (@plan planea, @build construye, @check revisa); --wait regresa cuando te toca decidir [runs watch|approve|changes|merge|card <pedido>]
|
|
16705
|
+
factory sprints ls|show|approve Los sprints que propone @plan para un objetivo grande (3\u20138 tickets en orden): lista, l\xE9elo, apru\xE9balo con una sola firma [approve <id> --yes]
|
|
16706
|
+
factory env ls|push [d/r] [.env] Las variables de entorno de la preview: cu\xE1les hay y cu\xE1les faltan, s\xFAbelas desde tu .env (valores desechables) [--gen LLAVE,LLAVE] [--replace]
|
|
16450
16707
|
factory runs ls|show|why|advise|stats|rename Los pedidos de la Software Factory de un espacio: lista (tiempo y costo), detalle (plan, PR, preview, qui\xE9n trabaja), en qu\xE9 se fue el tiempo y el dinero, por qu\xE9 se ator\xF3 o fall\xF3, qu\xE9 hacer si se escal\xF3, totales, corregir un t\xEDtulo [--workspace SLUG] [--room R] [--status S] [show --timing] [why --refresh] [stats --since 30d --repo R]
|
|
16451
16708
|
|
|
16452
16709
|
${bold("Agentes")}
|
|
@@ -16585,6 +16842,7 @@ ${bold("Navegador")} ${dim("(Ghosty para Chrome)")}
|
|
|
16585
16842
|
|
|
16586
16843
|
${bold("Partners")} ${dim("(dale un agente a cada uno de TUS clientes \u2014 ver: ghosty partner --help)")}
|
|
16587
16844
|
partner ls | enable | credentials | agent | tenants | audit | try
|
|
16845
|
+
image generate "prompt" Una imagen con IA para tus negocios, cobrada del saldo del espacio [--ref URL (se repite, conserva al personaje)] [--aspect 16:9] [--tenant ID] [--out ARCHIVO]
|
|
16588
16846
|
|
|
16589
16847
|
${bold("Tu cuenta")}
|
|
16590
16848
|
me files ls|get|export|upload|rm Archivos de tu cuenta [--q TEXTO] [--kind image|video|audio|document|other] [--cursor C] \xB7 get <id> [--out ARCHIVO]
|
|
@@ -16610,7 +16868,7 @@ ${bold("Opciones globales")}
|
|
|
16610
16868
|
${bold("C\xF3digos de salida")} 0 bien \xB7 1 error de la API \xB7 2 uso incorrecto \xB7 3 sin sesi\xF3n
|
|
16611
16869
|
${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.")}
|
|
16612
16870
|
${dim("Documentaci\xF3n: https://www.ghosty.studio/docs/cli")}`;
|
|
16613
|
-
var COMMANDS = ["login", "logout", "whoami", "usage", "use", "apply", "plan", "engines", "bags", "spaces", "workspaces", "staff", "settings", "ads", "audit", "factory", "demos", "sites", "push", "novedades", "completion", "cards", "agents", "files", "skills", "mcp", "dbs", "turns", "try", "chat", "conversations", "schedule", "teams", "partner", "me", "channels", "board", "boards", "access", "credentials", "connectors", "browser", "support", "docs", "help"];
|
|
16871
|
+
var COMMANDS = ["login", "logout", "whoami", "usage", "use", "apply", "plan", "engines", "bags", "spaces", "workspaces", "staff", "settings", "ads", "audit", "factory", "demos", "sites", "push", "novedades", "completion", "cards", "agents", "files", "skills", "mcp", "dbs", "turns", "try", "chat", "conversations", "schedule", "teams", "partner", "image", "me", "channels", "board", "boards", "access", "credentials", "connectors", "browser", "support", "docs", "help"];
|
|
16614
16872
|
var AGENTS_SUBS = ["ls", "create", "get", "show", "set", "export", "doctor", "docs", "share", "unshare", "shares", "teams", "rm", "restart", "demo", "embed", "box", "token", "memory", "transfer"];
|
|
16615
16873
|
function unknown(what, input, known, helpCmd) {
|
|
16616
16874
|
const s2 = suggest(input, known);
|
|
@@ -16787,6 +17045,7 @@ async function main() {
|
|
|
16787
17045
|
reply: { type: "boolean" },
|
|
16788
17046
|
actor: { type: "string" },
|
|
16789
17047
|
room: { type: "string" },
|
|
17048
|
+
cap: { type: "string" },
|
|
16790
17049
|
url: { type: "string" },
|
|
16791
17050
|
description: { type: "string" },
|
|
16792
17051
|
grant: { type: "string" },
|
|
@@ -16864,6 +17123,7 @@ async function main() {
|
|
|
16864
17123
|
thread: { type: "string" },
|
|
16865
17124
|
private: { type: "boolean" },
|
|
16866
17125
|
"skip-preview": { type: "boolean" },
|
|
17126
|
+
gen: { type: "string" },
|
|
16867
17127
|
owner: { type: "string" },
|
|
16868
17128
|
cursor: { type: "string" },
|
|
16869
17129
|
user: { type: "string" },
|
|
@@ -16877,6 +17137,8 @@ async function main() {
|
|
|
16877
17137
|
draft: { type: "boolean" },
|
|
16878
17138
|
"ads-only": { type: "string" },
|
|
16879
17139
|
"partner-tenant": { type: "string" },
|
|
17140
|
+
ref: { type: "string", multiple: true },
|
|
17141
|
+
aspect: { type: "string" },
|
|
16880
17142
|
tenant: { type: "string" },
|
|
16881
17143
|
"app-id": { type: "string" },
|
|
16882
17144
|
secret: { type: "string" },
|
|
@@ -16911,14 +17173,14 @@ async function main() {
|
|
|
16911
17173
|
const tokens = splitTokens(pos, v);
|
|
16912
17174
|
globals.token = tokens.session;
|
|
16913
17175
|
globals.profile = v.profile;
|
|
16914
|
-
if (v.version) return out({ version: "0.
|
|
17176
|
+
if (v.version) return out({ version: "0.78.0" }, () => "0.78.0");
|
|
16915
17177
|
for (const n of normalizeArgs(pos, v)) if (!globals.json) info(dim(n));
|
|
16916
17178
|
const context = (await readConfig()).workspace;
|
|
16917
17179
|
if (!explicitToken()?.startsWith("gat_")) {
|
|
16918
17180
|
const at = agentArgIndex(pos);
|
|
16919
17181
|
if (at !== null && pos[at]) pos[at] = await resolveAgent(pos[at], context);
|
|
16920
17182
|
for (const f of ["like", "prompt-base", "agent"]) {
|
|
16921
|
-
if (f === "agent" && pos[0] === "partner") continue;
|
|
17183
|
+
if (f === "agent" && (pos[0] === "partner" || pos[0] === "image")) continue;
|
|
16922
17184
|
const val = v[f];
|
|
16923
17185
|
if (typeof val === "string" && val !== "none") v[f] = await resolveAgent(val, context);
|
|
16924
17186
|
}
|
|
@@ -17052,6 +17314,8 @@ async function main() {
|
|
|
17052
17314
|
return browserCmd(sub, v);
|
|
17053
17315
|
case "partner":
|
|
17054
17316
|
return partnerCmd(sub, a1, a2, { ...v, token: tokens.turn }, pos[4]);
|
|
17317
|
+
case "image":
|
|
17318
|
+
return imageCmd(sub, a1, v);
|
|
17055
17319
|
case "usage":
|
|
17056
17320
|
if (v.images) return imageUsage(v);
|
|
17057
17321
|
if (v.boxes && v.by === "template") return fleetBoxes();
|
|
@@ -17106,7 +17370,7 @@ async function main() {
|
|
|
17106
17370
|
throw unknown(cmd, cmd, COMMANDS, "ghosty --help");
|
|
17107
17371
|
}
|
|
17108
17372
|
}
|
|
17109
|
-
var notice = updateNotice("0.
|
|
17373
|
+
var notice = updateNotice("0.78.0").catch(() => null);
|
|
17110
17374
|
main().then(
|
|
17111
17375
|
async () => {
|
|
17112
17376
|
const n = await notice;
|
package/package.json
CHANGED