@ghostystudio/cli 0.75.0 → 0.77.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 +5 -1
- package/dist/index.js +177 -19
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -122,7 +122,11 @@ ghosty connectors ls|connect|disconnect [id] [--q TEXT] [--from-env VAR] [--no-
|
|
|
122
122
|
ghosty support open --title T --body TEXT · support ls | show <id> | reply <id> --body TEXT # ask Ghosty for help; answered by email
|
|
123
123
|
ghosty browser logout # revoke your terminal agents' browser tokens and disconnect Ghosty for Chrome
|
|
124
124
|
ghosty audit ls [--workspace SLUG] [--agent ID] [--actor EMAIL] [--action A] [--since 24h] [--limit N] # who changed what
|
|
125
|
+
ghosty factory rooms repos # the repos your GitHub App sees, and where they're connected
|
|
125
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
|
|
126
130
|
ghosty factory run "…" --repo owner/name [--wait] # hand a request to the Software Factory from the terminal
|
|
127
131
|
ghosty factory runs watch|approve|changes|merge|card <run> # wait for it and decide it without opening Teams
|
|
128
132
|
ghosty factory runs ls|show|why|stats|rename [--workspace SLUG] [--room R] [--status S] [show --timing] [why --refresh] # Software Factory runs
|
|
@@ -147,7 +151,7 @@ Exit codes: `0` ok · `1` API error · `2` usage error · `3` not signed in.
|
|
|
147
151
|
|
|
148
152
|
### Changes in 0.73.0
|
|
149
153
|
|
|
150
|
-
- `factory rooms ls|connect|disconnect`: set up the factory for a repo without opening Teams. `connect owner/repo --room R --create` creates the room, connects the repo (only if your GitHub App sees it) and tells you how ready it is for agents.
|
|
154
|
+
- `factory rooms ls|repos|connect|disconnect`: `repos` lists what your GitHub App sees (newest first) and the rooms each repo is in. set up the factory for a repo without opening Teams. `connect owner/repo --room R --create` creates the room, connects the repo (only if your GitHub App sees it) and tells you how ready it is for agents.
|
|
151
155
|
|
|
152
156
|
### Changes in 0.72.0
|
|
153
157
|
|
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.77.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.77.0"}`,
|
|
1221
1221
|
"accept-language": lang(),
|
|
1222
1222
|
...opts.body !== void 0 ? { "content-type": "application/json" } : {},
|
|
1223
1223
|
...opts.headers
|
|
@@ -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.77.0"}`
|
|
11199
11209
|
},
|
|
11200
11210
|
body: method === "GET" ? void 0 : raw
|
|
11201
11211
|
});
|
|
@@ -13509,6 +13519,7 @@ var HELPS_ES = {
|
|
|
13509
13519
|
["--user EMAIL", "ls (staff): los espacios de esa persona"],
|
|
13510
13520
|
["--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
13521
|
["--room ROOM", "members add --role collaborator: el room de Teams al que entra (obligatorio con collaborator)"],
|
|
13522
|
+
["--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
13523
|
["--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
13524
|
["--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
13525
|
["--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 +13589,8 @@ var HELPS_ES = {
|
|
|
13578
13589
|
["--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
13590
|
["--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
13591
|
["--skip-preview", "merge: mergea sin la preview que ped\xEDa el plan (queda en bit\xE1cora)"],
|
|
13592
|
+
["--gen LLAVE,LLAVE", "env push: genera ah\xED mismo llaves al azar (32 bytes hex) con esos nombres; nunca se imprimen"],
|
|
13593
|
+
["--replace", "env push: reemplaza el .env entero de la preview (por defecto agrega y deja las dem\xE1s)"],
|
|
13581
13594
|
["--create", "rooms connect: crea el room si no existe"],
|
|
13582
13595
|
["--private", "rooms connect --create: el room nuevo es privado (s\xF3lo t\xFA, y a quien agregues)"],
|
|
13583
13596
|
["--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 +13604,9 @@ var HELPS_ES = {
|
|
|
13591
13604
|
["--yes", "rename, approve, changes, merge: se salta la confirmaci\xF3n (obligatorio sin terminal)"]
|
|
13592
13605
|
],
|
|
13593
13606
|
notes: [
|
|
13607
|
+
"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).",
|
|
13608
|
+
"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).",
|
|
13609
|
+
"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.",
|
|
13594
13610
|
"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).",
|
|
13595
13611
|
"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).",
|
|
13596
13612
|
"card baja la tarjeta viva del pedido al final de su hilo; la anterior queda como huella quieta con liga a la viva. Los roles tambi\xE9n pueden (factory_card).",
|
|
@@ -14367,11 +14383,12 @@ HELPS.novedades = {
|
|
|
14367
14383
|
};
|
|
14368
14384
|
HELPS.spaces = {
|
|
14369
14385
|
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.",
|
|
14370
|
-
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]"],
|
|
14386
|
+
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]"],
|
|
14371
14387
|
flags: [
|
|
14372
14388
|
["--user EMAIL", "ls (staff): that person's workspaces"],
|
|
14373
14389
|
["--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)"],
|
|
14374
14390
|
["--room ROOM", "members add --role collaborator: the Teams room they enter (required with collaborator)"],
|
|
14391
|
+
["--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"],
|
|
14375
14392
|
["--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"],
|
|
14376
14393
|
["--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"],
|
|
14377
14394
|
["--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"],
|
|
@@ -14493,8 +14510,15 @@ HELPS.factory = {
|
|
|
14493
14510
|
summary: "Hand requests to a workspace's Software Factory from the terminal and decide them (approve the plan, merge the PR) without opening Teams \u2014 the factory as a sidekick for your coding agent. Also its runs (they live in its Teams): list them with time and cost, see one (current plan, PR, preview, latest events, which role is working), see where its time and money went and why it got stuck or failed, totals for the workspace, and fix a run's title.",
|
|
14494
14511
|
usage: [
|
|
14495
14512
|
"ghosty factory rooms ls [--full] [--workspace SLUG]",
|
|
14513
|
+
"ghosty factory rooms repos [--workspace SLUG]",
|
|
14496
14514
|
"ghosty factory rooms connect OWNER/REPO --room R [--create] [--private] [--workspace SLUG]",
|
|
14497
14515
|
"ghosty factory rooms disconnect OWNER/REPO --room R [--yes]",
|
|
14516
|
+
"ghosty factory sprints ls [--workspace SLUG]",
|
|
14517
|
+
"ghosty factory sprints show ID [--workspace SLUG]",
|
|
14518
|
+
"ghosty factory sprints approve ID [--yes] [--workspace SLUG]",
|
|
14519
|
+
"ghosty factory env ls [OWNER/REPO] [--workspace SLUG]",
|
|
14520
|
+
"ghosty factory env push [OWNER/REPO] [FILE] [--gen KEY,KEY] [--replace] [--yes] [--workspace SLUG]",
|
|
14521
|
+
"ghosty factory runs recheck <run> [--yes] [--workspace SLUG]",
|
|
14498
14522
|
`ghosty factory run "What, why and how we know it's done" --repo OWNER/REPO [--room R] [--wait] [--timeout 1800] [--workspace SLUG]`,
|
|
14499
14523
|
"ghosty factory runs watch <run> | --thread <id> [--timeout 1800] [--workspace SLUG]",
|
|
14500
14524
|
'ghosty factory runs approve <run> --plan-version N [--note "\u2026"] [--check-scope "\u2026"] [--yes]',
|
|
@@ -14519,6 +14543,8 @@ HELPS.factory = {
|
|
|
14519
14543
|
["--note T", "changes: what to change in the plan (or why to replan after an escalation). approve after an escalation: what @build must fix"],
|
|
14520
14544
|
["--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"],
|
|
14521
14545
|
["--skip-preview", "merge: merge without the preview the plan asked for (logged)"],
|
|
14546
|
+
["--gen KEY,KEY", "env push: generate random keys (32 bytes hex) for those names right there; never printed"],
|
|
14547
|
+
["--replace", "env push: replace the whole preview .env (default: add, keeping the rest)"],
|
|
14522
14548
|
["--create", "rooms connect: create the room if it doesn't exist"],
|
|
14523
14549
|
["--private", "rooms connect --create: make the new room private (only you, then whoever you add)"],
|
|
14524
14550
|
["--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)"],
|
|
@@ -14532,7 +14558,7 @@ HELPS.factory = {
|
|
|
14532
14558
|
["--yes", "rename, approve, changes, merge: skip the confirmation (required without a terminal)"]
|
|
14533
14559
|
],
|
|
14534
14560
|
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'],
|
|
14535
|
-
notes: ["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."],
|
|
14561
|
+
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."],
|
|
14536
14562
|
learn: `${REF}#fabrica`
|
|
14537
14563
|
};
|
|
14538
14564
|
function helpText(cmd) {
|
|
@@ -15530,6 +15556,9 @@ ${dim(t("Times in UTC. --full for the whole detail; --json for everything.", "Ho
|
|
|
15530
15556
|
|
|
15531
15557
|
// src/commands/factory.ts
|
|
15532
15558
|
init_api();
|
|
15559
|
+
import { execFileSync } from "node:child_process";
|
|
15560
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
15561
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
15533
15562
|
init_util();
|
|
15534
15563
|
init_i18n();
|
|
15535
15564
|
var enc16 = encodeURIComponent;
|
|
@@ -15551,7 +15580,19 @@ async function roomsCmd(ws, verb, repo, v) {
|
|
|
15551
15580
|
].join("\n")
|
|
15552
15581
|
);
|
|
15553
15582
|
}
|
|
15554
|
-
if (verb
|
|
15583
|
+
if (verb === "repos") {
|
|
15584
|
+
const r2 = await api(`${path}?repos=1`);
|
|
15585
|
+
return out(r2, () => {
|
|
15586
|
+
if (!r2.connected) return red(t(`GitHub isn't connected: ${r2.detail ?? "connect it with ghosty connectors connect github"}`, `GitHub no est\xE1 conectado: ${r2.detail ?? "con\xE9ctalo con ghosty connectors connect github"}`));
|
|
15587
|
+
if (!r2.installed) return red(t(`The Ghosty GitHub App isn't installed: ${r2.installUrl ?? ""}`, `La GitHub App de Ghosty no est\xE1 instalada: ${r2.installUrl ?? ""}`));
|
|
15588
|
+
const rows = r2.repos.map((x) => ({ repo: x.repo, rooms: x.rooms.map((s2) => `#${s2}`).join(", ") || dim("\u2014"), private: x.private ? t("yes", "s\xED") : "", pushed: x.pushedAt?.slice(0, 10) ?? "" }));
|
|
15589
|
+
return [
|
|
15590
|
+
rows.length ? table(rows, ["repo", "rooms", "private", "pushed"]) : dim(t("The App doesn't see any repo yet.", "La App todav\xEDa no ve ning\xFAn repo.")),
|
|
15591
|
+
dim(t(`Missing one? Give the App access: ${r2.installUrl ?? ""}`, `\xBFFalta uno? Dale acceso a la App: ${r2.installUrl ?? ""}`))
|
|
15592
|
+
].join("\n");
|
|
15593
|
+
});
|
|
15594
|
+
}
|
|
15595
|
+
if (verb !== "connect" && verb !== "disconnect") throw usage(t(`Unknown rooms subcommand: ${verb}.`, `Subcomando de rooms desconocido: ${verb}.`), "ghosty factory rooms ls | repos | connect <owner/repo> --room R [--create] [--private] | disconnect <owner/repo> --room R --yes");
|
|
15555
15596
|
if (!repo) throw usage(t("Missing the repo.", "Falta el repo."), `ghosty factory rooms ${verb} owner/repo --room R --workspace ${ws}`);
|
|
15556
15597
|
if (!v.room) throw usage(t("Which room?", "\xBFQu\xE9 room?"), t("Add --room NAME (with --create it's made if missing).", "Agrega --room NOMBRE (con --create se crea si no existe)."));
|
|
15557
15598
|
if (verb === "disconnect") {
|
|
@@ -15575,6 +15616,103 @@ async function roomsCmd(ws, verb, repo, v) {
|
|
|
15575
15616
|
].filter((l) => l !== "").join("\n")
|
|
15576
15617
|
);
|
|
15577
15618
|
}
|
|
15619
|
+
async function sprintsCmd(ws, verb, ref, v) {
|
|
15620
|
+
const path = `/api/v2/workspaces/${enc16(ws)}/factory/sprints`;
|
|
15621
|
+
if (!verb || verb === "ls" || verb === "list") {
|
|
15622
|
+
const r2 = await api(path);
|
|
15623
|
+
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}` }));
|
|
15624
|
+
return out(r2, () => rows.length ? table(rows, ["sprint", "title", "status", "room", "merged"]) : dim(t("No sprints yet.", "Todav\xEDa no hay sprints.")));
|
|
15625
|
+
}
|
|
15626
|
+
const id = Number(String(ref ?? "").replace(/^#/, ""));
|
|
15627
|
+
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");
|
|
15628
|
+
if (!id) throw usage(t("Which sprint?", "\xBFQu\xE9 sprint?"), `ghosty factory sprints ${verb} 12 --workspace ${ws}`);
|
|
15629
|
+
if (verb === "approve") {
|
|
15630
|
+
const retry = `ghosty factory sprints approve ${id} --workspace ${ws}`;
|
|
15631
|
+
requireYesIfHeadless(v.yes, retry);
|
|
15632
|
+
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 });
|
|
15633
|
+
}
|
|
15634
|
+
const r = verb === "approve" ? await api(`${path}/${id}`, { body: { action: "approve" } }) : await api(`${path}/${id}`);
|
|
15635
|
+
return out(
|
|
15636
|
+
r,
|
|
15637
|
+
() => [
|
|
15638
|
+
`${bold(`#${r.id} ${r.title}`)} ${dim(`\xB7 ${r.status} \xB7 v${r.version}${r.repo ? ` \xB7 ${r.repo}` : ""}`)}`,
|
|
15639
|
+
r.goal ? dim(r.goal) : "",
|
|
15640
|
+
table(
|
|
15641
|
+
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}` : "" })),
|
|
15642
|
+
["key", "ticket", "size", "after", "status", "run"]
|
|
15643
|
+
),
|
|
15644
|
+
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}`)) : ""
|
|
15645
|
+
].filter(Boolean).join("\n")
|
|
15646
|
+
);
|
|
15647
|
+
}
|
|
15648
|
+
function repoHere() {
|
|
15649
|
+
try {
|
|
15650
|
+
const url = execFileSync("git", ["remote", "get-url", "origin"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
15651
|
+
return url.match(/github\.com[:/]([^/]+\/[^/]+?)(?:\.git)?$/)?.[1] ?? null;
|
|
15652
|
+
} catch {
|
|
15653
|
+
return null;
|
|
15654
|
+
}
|
|
15655
|
+
}
|
|
15656
|
+
function dotenvKeys(text) {
|
|
15657
|
+
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());
|
|
15658
|
+
}
|
|
15659
|
+
async function envCmd(ws, verb, ref, file, v) {
|
|
15660
|
+
const path = `/api/v2/workspaces/${enc16(ws)}/factory/env`;
|
|
15661
|
+
const repo = ref && ref.includes("/") ? ref : v.repo ?? repoHere();
|
|
15662
|
+
if (ref && !ref.includes("/") && !file) file = ref;
|
|
15663
|
+
if (!repo) throw usage(t("Which repo?", "\xBFQu\xE9 repo?"), `ghosty factory env ${verb ?? "ls"} OWNER/REPO --workspace ${ws}`);
|
|
15664
|
+
if (!verb || verb === "ls" || verb === "list") {
|
|
15665
|
+
const r2 = await api(`${path}?repo=${enc16(repo)}`);
|
|
15666
|
+
const risk = new Map(r2.risks.map((x) => [x.key, x.risk]));
|
|
15667
|
+
return out(
|
|
15668
|
+
r2,
|
|
15669
|
+
() => [
|
|
15670
|
+
`${bold(r2.repo)} ${dim(`\xB7 ${r2.rooms.map((x) => `#${x}`).join(" ")}`)}`,
|
|
15671
|
+
`${t("saved", "guardadas")}: ${r2.saved.length ? r2.saved.map((k) => risk.has(k) ? red(`${k} (${risk.get(k)})`) : k).join(", ") : dim(t("none", "ninguna"))}`,
|
|
15672
|
+
r2.missing.length ? `${red(t("missing", "faltan"))}: ${r2.missing.join(", ")}` : `${lavender("\u2713")} ${t("nothing missing", "no falta ninguna")}`,
|
|
15673
|
+
...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(", ")})` : ""}`))),
|
|
15674
|
+
r2.missing.length ? dim(
|
|
15675
|
+
t(
|
|
15676
|
+
`Use DISPOSABLE values (a test database, freshly generated keys), never production: the preview runs the repo's code and can read them.
|
|
15677
|
+
Push: ghosty factory env push ${r2.repo} .env.preview${r2.missing.length ? ` [--gen KEY,KEY]` : ""} --workspace ${ws}`,
|
|
15678
|
+
`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.
|
|
15679
|
+
S\xFAbelas: ghosty factory env push ${r2.repo} .env.preview${r2.missing.length ? ` [--gen LLAVE,LLAVE]` : ""} --workspace ${ws}`
|
|
15680
|
+
)
|
|
15681
|
+
) : "",
|
|
15682
|
+
r2.canWrite ? "" : dim(t("Only the workspace owner can save them.", "S\xF3lo el due\xF1o del espacio puede guardarlas."))
|
|
15683
|
+
].filter(Boolean).join("\n")
|
|
15684
|
+
);
|
|
15685
|
+
}
|
|
15686
|
+
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]");
|
|
15687
|
+
const src = file ?? v.file;
|
|
15688
|
+
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."));
|
|
15689
|
+
let dotenv = src ? readFileSync(src, "utf8") : "";
|
|
15690
|
+
const gen = (v.gen ?? "").split(",").map((k) => k.trim()).filter(Boolean);
|
|
15691
|
+
for (const k of gen) {
|
|
15692
|
+
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");
|
|
15693
|
+
dotenv += `
|
|
15694
|
+
${k}=${randomBytes2(32).toString("hex")}`;
|
|
15695
|
+
}
|
|
15696
|
+
const keys = [...new Set(dotenvKeys(dotenv))];
|
|
15697
|
+
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}`);
|
|
15698
|
+
const retry = `ghosty factory env push ${repo}${src ? ` ${src}` : ""}${gen.length ? ` --gen ${gen.join(",")}` : ""}${v.replace ? " --replace" : ""} --workspace ${ws}`;
|
|
15699
|
+
requireYesIfHeadless(v.yes, retry);
|
|
15700
|
+
await confirm(
|
|
15701
|
+
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.`),
|
|
15702
|
+
{ yes: v.yes, retry }
|
|
15703
|
+
);
|
|
15704
|
+
const r = await api(path, {
|
|
15705
|
+
body: { repo, dotenv, ...v.replace ? { replace: true } : {} }
|
|
15706
|
+
});
|
|
15707
|
+
return out(
|
|
15708
|
+
r,
|
|
15709
|
+
() => [
|
|
15710
|
+
`${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.`)}`,
|
|
15711
|
+
...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.`))),
|
|
15712
|
+
...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}`)))
|
|
15713
|
+
].join("\n")
|
|
15714
|
+
);
|
|
15715
|
+
}
|
|
15578
15716
|
var sleep3 = (ms) => new Promise((ok) => setTimeout(ok, ms));
|
|
15579
15717
|
var STOPS = /* @__PURE__ */ new Set(["plan_review", "escalated", "pr_review", "done", "cancelled"]);
|
|
15580
15718
|
var STAGE = {
|
|
@@ -15599,6 +15737,17 @@ Or ask for changes: ghosty factory runs changes ${r.id} --note "\u2026" ${w}`,
|
|
|
15599
15737
|
Firma: ghosty factory runs approve ${r.id} --plan-version ${r.planVersion} ${w}
|
|
15600
15738
|
O pide cambios: ghosty factory runs changes ${r.id} --note "\u2026" ${w}`
|
|
15601
15739
|
);
|
|
15740
|
+
if (r.status === "escalated" && "escalation" in r && r.escalation?.blocked)
|
|
15741
|
+
return t(
|
|
15742
|
+
`@check is blocked by something @build can't provide (e.g. preview variables). Resolve it, then:
|
|
15743
|
+
What's missing: ghosty factory env ls ${r.repo ?? "OWNER/REPO"} ${w}
|
|
15744
|
+
Review again (no @build loop): ghosty factory runs recheck ${r.id} ${w}
|
|
15745
|
+
Replan: ghosty factory runs changes ${r.id} --note "\u2026" ${w}`,
|
|
15746
|
+
`A @check lo bloquea algo que @build no puede dar (p. ej. variables de la preview). Resu\xE9lvelo y luego:
|
|
15747
|
+
Qu\xE9 falta: ghosty factory env ls ${r.repo ?? "DUE\xD1O/REPO"} ${w}
|
|
15748
|
+
Que revise otra vez (sin vuelta de @build): ghosty factory runs recheck ${r.id} ${w}
|
|
15749
|
+
Replanear: ghosty factory runs changes ${r.id} --note "\u2026" ${w}`
|
|
15750
|
+
);
|
|
15602
15751
|
if (r.status === "escalated")
|
|
15603
15752
|
return t(
|
|
15604
15753
|
`Another loop: ghosty factory runs approve ${r.id} ${w}
|
|
@@ -15647,10 +15796,12 @@ function timeoutOf(v) {
|
|
|
15647
15796
|
return s2;
|
|
15648
15797
|
}
|
|
15649
15798
|
async function factoryCmd(sub, verb, ref, title, v, context) {
|
|
15650
|
-
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');
|
|
15799
|
+
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');
|
|
15651
15800
|
const ws = v.workspace ?? context;
|
|
15652
15801
|
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)."));
|
|
15653
15802
|
if (sub === "rooms") return roomsCmd(ws, verb, ref, v);
|
|
15803
|
+
if (sub === "sprints") return sprintsCmd(ws, verb, ref, v);
|
|
15804
|
+
if (sub === "env") return envCmd(ws, verb, ref, title, v);
|
|
15654
15805
|
const base = `/api/v2/workspaces/${enc16(ws)}/factory/runs`;
|
|
15655
15806
|
if (sub === "run") {
|
|
15656
15807
|
const text = verb?.trim();
|
|
@@ -15799,7 +15950,7 @@ ${dim(t("Dry run: nothing changed.", "Simulacro: no se cambi\xF3 nada."))}`);
|
|
|
15799
15950
|
const r = await api(path, { body: { title } });
|
|
15800
15951
|
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)"))}`);
|
|
15801
15952
|
}
|
|
15802
|
-
if (verb === "approve" || verb === "changes" || verb === "merge" || verb === "card") {
|
|
15953
|
+
if (verb === "approve" || verb === "changes" || verb === "recheck" || verb === "merge" || verb === "card") {
|
|
15803
15954
|
const id = Number(ref.replace(/^#/, ""));
|
|
15804
15955
|
const note = v.note?.trim() ?? title?.trim();
|
|
15805
15956
|
const scope = verb === "approve" || verb === "changes" ? v["check-scope"]?.trim() : void 0;
|
|
@@ -15808,7 +15959,7 @@ ${dim(t("Dry run: nothing changed.", "Simulacro: no se cambi\xF3 nada."))}`);
|
|
|
15808
15959
|
if (verb !== "card") {
|
|
15809
15960
|
requireYesIfHeadless(v.yes, retry);
|
|
15810
15961
|
const cur = (await api(`${base}/${id}`)).run;
|
|
15811
|
-
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];
|
|
15962
|
+
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];
|
|
15812
15963
|
await confirm(`${what}?`, { yes: v.yes, retry });
|
|
15813
15964
|
}
|
|
15814
15965
|
const r = await api(`${base}/${id}/act`, {
|
|
@@ -15817,13 +15968,14 @@ ${dim(t("Dry run: nothing changed.", "Simulacro: no se cambi\xF3 nada."))}`);
|
|
|
15817
15968
|
const msg = {
|
|
15818
15969
|
approve: t(`#${id} approved: ${stage(r.status)}.`, `#${id} firmado: ${stage(r.status)}.`),
|
|
15819
15970
|
changes: t(`#${id}: changes sent to @plan.`, `#${id}: cambios enviados a @plan.`),
|
|
15971
|
+
recheck: t(`#${id}: @check is reviewing the same PR again.`, `#${id}: @check revisa otra vez el mismo PR.`),
|
|
15820
15972
|
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.`),
|
|
15821
15973
|
card: t(`#${id}: the card is at the bottom of its thread again.`, `#${id}: la tarjeta volvi\xF3 al final de su hilo.`)
|
|
15822
15974
|
}[verb];
|
|
15823
|
-
return out(r, () => `${lavender("\u2713")} ${msg}${verb === "approve" || verb === "changes" ? `
|
|
15975
|
+
return out(r, () => `${lavender("\u2713")} ${msg}${verb === "approve" || verb === "changes" || verb === "recheck" ? `
|
|
15824
15976
|
${dim(t(`Follow it: ghosty factory runs watch ${id} --workspace ${ws}`, `S\xEDguelo: ghosty factory runs watch ${id} --workspace ${ws}`))}` : ""}`);
|
|
15825
15977
|
}
|
|
15826
|
-
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>');
|
|
15978
|
+
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>');
|
|
15827
15979
|
}
|
|
15828
15980
|
async function agentsByHandle(handle, workspace) {
|
|
15829
15981
|
const qs = new URLSearchParams({ handle, ...workspace ? { workspace } : {} });
|
|
@@ -16232,8 +16384,10 @@ ${bold("Workspaces")} ${dim("(changing them: Ghosty staff)")}
|
|
|
16232
16384
|
settings ls|set|unset <KEY> [value] Platform switches (staff): who answers /c without an account, native subagents\u2026 [+id/-id edits a list]
|
|
16233
16385
|
ads watch [ls|run] Campaign watch (staff): Meta campaigns that stop delivering and ads-only WhatsApp numbers with no leads for 24 h [run --dry-run: what it would alert]
|
|
16234
16386
|
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]
|
|
16235
|
-
factory rooms ls|connect|disconnect
|
|
16387
|
+
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]
|
|
16236
16388
|
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>]
|
|
16389
|
+
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]
|
|
16390
|
+
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]
|
|
16237
16391
|
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]
|
|
16238
16392
|
|
|
16239
16393
|
${bold("Agents")}
|
|
@@ -16431,8 +16585,10 @@ ${bold("Espacios")} ${dim("(cambiarlos: staff de Ghosty)")}
|
|
|
16431
16585
|
settings ls|set|unset <CLAVE> [valor] Perillas de la plataforma (staff): qui\xE9n atiende /c sin cuenta, subagentes nativos\u2026 [+id/-id edita una lista]
|
|
16432
16586
|
ads watch [ls|run] Vig\xEDa de campa\xF1as (staff): campa\xF1as de Meta que dejan de entregar y n\xFAmeros \xABs\xF3lo anuncios\xBB sin leads en 24 h [run --dry-run: lo que avisar\xEDa]
|
|
16433
16587
|
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]
|
|
16434
|
-
factory rooms ls|connect|disconnect
|
|
16588
|
+
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]
|
|
16435
16589
|
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>]
|
|
16590
|
+
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]
|
|
16591
|
+
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]
|
|
16436
16592
|
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]
|
|
16437
16593
|
|
|
16438
16594
|
${bold("Agentes")}
|
|
@@ -16773,6 +16929,7 @@ async function main() {
|
|
|
16773
16929
|
reply: { type: "boolean" },
|
|
16774
16930
|
actor: { type: "string" },
|
|
16775
16931
|
room: { type: "string" },
|
|
16932
|
+
cap: { type: "string" },
|
|
16776
16933
|
url: { type: "string" },
|
|
16777
16934
|
description: { type: "string" },
|
|
16778
16935
|
grant: { type: "string" },
|
|
@@ -16850,6 +17007,7 @@ async function main() {
|
|
|
16850
17007
|
thread: { type: "string" },
|
|
16851
17008
|
private: { type: "boolean" },
|
|
16852
17009
|
"skip-preview": { type: "boolean" },
|
|
17010
|
+
gen: { type: "string" },
|
|
16853
17011
|
owner: { type: "string" },
|
|
16854
17012
|
cursor: { type: "string" },
|
|
16855
17013
|
user: { type: "string" },
|
|
@@ -16897,7 +17055,7 @@ async function main() {
|
|
|
16897
17055
|
const tokens = splitTokens(pos, v);
|
|
16898
17056
|
globals.token = tokens.session;
|
|
16899
17057
|
globals.profile = v.profile;
|
|
16900
|
-
if (v.version) return out({ version: "0.
|
|
17058
|
+
if (v.version) return out({ version: "0.77.0" }, () => "0.77.0");
|
|
16901
17059
|
for (const n of normalizeArgs(pos, v)) if (!globals.json) info(dim(n));
|
|
16902
17060
|
const context = (await readConfig()).workspace;
|
|
16903
17061
|
if (!explicitToken()?.startsWith("gat_")) {
|
|
@@ -17092,7 +17250,7 @@ async function main() {
|
|
|
17092
17250
|
throw unknown(cmd, cmd, COMMANDS, "ghosty --help");
|
|
17093
17251
|
}
|
|
17094
17252
|
}
|
|
17095
|
-
var notice = updateNotice("0.
|
|
17253
|
+
var notice = updateNotice("0.77.0").catch(() => null);
|
|
17096
17254
|
main().then(
|
|
17097
17255
|
async () => {
|
|
17098
17256
|
const n = await notice;
|
package/package.json
CHANGED