@ghostystudio/cli 0.54.0 → 0.56.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 +12 -1
- package/dist/index.js +99 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -109,6 +109,17 @@ Every command has its own help with examples: `ghosty <command> --help` or `ghos
|
|
|
109
109
|
|
|
110
110
|
Exit codes: `0` ok · `1` API error · `2` usage error · `3` not signed in.
|
|
111
111
|
|
|
112
|
+
### Changes in 0.56.0
|
|
113
|
+
|
|
114
|
+
- `factory runs why <run> [--refresh]`: why a run got stuck or failed — category, impact, the critical step with its evidence and what to change next time (a healthy run doesn't call the model).
|
|
115
|
+
- `factory runs show --timing` lists stuck patterns (a command open 8+ min, the same command repeated, errors in a row, going back and forth, apt install in a box); `runs stats` counts runs with stuck alerts.
|
|
116
|
+
|
|
117
|
+
### Changes in 0.55.0
|
|
118
|
+
|
|
119
|
+
- `factory runs show <run> --timing`: where a run's time and money went — each turn of each role (critic, @plan, @build, @check) with minutes, minutes in tools, model and USD; the clock split into working, waiting for a person and the rest (CI, queues); and the commands that took 60 s or more.
|
|
120
|
+
- `factory runs stats [--since 30d] [--repo owner/repo]`: runs, % merged, median minutes to PR and to merge, @check→@build loops, red CI per run, and cost per run and per merged PR.
|
|
121
|
+
- `factory runs ls` shows each run's time and cost.
|
|
122
|
+
|
|
112
123
|
### Changes in 0.54.0
|
|
113
124
|
|
|
114
125
|
- `agents embed <id> --copy '{json}'|none` and `--copy-file texts.json`: the phrases the web widget shows while the agent works (`waking`, `connecting`, `thinking` — 1–8 that rotate —, `stillWorking`, `cantStart`, `interrupted`, `retryLabel`, plain text ≤ 80 chars) and whether it shows the duration and the Copy button (`showDuration`, `showCopyButton`). Patch per key; `null` = Ghosty's phrase. `agents embed <id> theme` exports `copy` too, and `--out` bakes it into the component.
|
|
@@ -134,7 +145,7 @@ Exit codes: `0` ok · `1` API error · `2` usage error · `3` not signed in.
|
|
|
134
145
|
- `channels whatsapp integrations` shows who answered each number last; `--orphans` and `channels messenger ls [--orphans]` list numbers/pages nobody answers today that had turns.
|
|
135
146
|
- `agents set --prompt-patch overlay.json` / `--prompt-append FILE [--dry-run]`: change one piece of the prompt on the server's current copy (dry run prints the diff).
|
|
136
147
|
- `audit ls [--workspace] [--agent] [--actor] [--action] [--since]`: the audit log. The agent page's Save and channel changes are logged now.
|
|
137
|
-
- `factory runs ls|show|rename` (a workspace's Software Factory runs) and `agents ls --handle build`.
|
|
148
|
+
- `factory runs ls|show|rename` (a workspace's Software Factory runs) and `agents ls --handle build`. Since 0.55: `show --timing` and `stats`.
|
|
138
149
|
|
|
139
150
|
### Changes in 0.49.0
|
|
140
151
|
|
package/dist/index.js
CHANGED
|
@@ -8468,7 +8468,7 @@ async function postLong(path, body, timeoutMs = 9e5) {
|
|
|
8468
8468
|
const req = httpsRequest(`${base}${path}`, {
|
|
8469
8469
|
method: "POST",
|
|
8470
8470
|
timeout: timeoutMs,
|
|
8471
|
-
headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.
|
|
8471
|
+
headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.56.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
|
|
8472
8472
|
}, (res) => {
|
|
8473
8473
|
let buf = "";
|
|
8474
8474
|
res.on("data", (c) => buf += c);
|
|
@@ -8510,7 +8510,7 @@ async function request(path, opts = {}, retried = false) {
|
|
|
8510
8510
|
method: opts.method ?? (opts.body !== void 0 || opts.raw !== void 0 ? "POST" : "GET"),
|
|
8511
8511
|
headers: {
|
|
8512
8512
|
authorization: `Bearer ${token}`,
|
|
8513
|
-
"user-agent": `ghosty-cli/${"0.
|
|
8513
|
+
"user-agent": `ghosty-cli/${"0.56.0"}`,
|
|
8514
8514
|
"accept-language": lang(),
|
|
8515
8515
|
...opts.body !== void 0 ? { "content-type": "application/json" } : {},
|
|
8516
8516
|
...opts.headers
|
|
@@ -10404,7 +10404,7 @@ async function call(path, v, init = {}) {
|
|
|
10404
10404
|
"x-ghosty-ts": ts,
|
|
10405
10405
|
"x-ghosty-sig": sig,
|
|
10406
10406
|
"content-type": "application/json",
|
|
10407
|
-
"user-agent": `ghosty-cli/${"0.
|
|
10407
|
+
"user-agent": `ghosty-cli/${"0.56.0"}`
|
|
10408
10408
|
},
|
|
10409
10409
|
body: method === "GET" ? void 0 : raw
|
|
10410
10410
|
});
|
|
@@ -12492,7 +12492,7 @@ var HELPS_ES = {
|
|
|
12492
12492
|
]
|
|
12493
12493
|
},
|
|
12494
12494
|
factory: {
|
|
12495
|
-
summary: "Los pedidos de la Software Factory de un espacio (viven en su Teams): listarlos, ver uno (plan vigente, PR, preview, \xFAltimos eventos, qu\xE9 rol trabaja) y corregir el t\xEDtulo de un pedido.",
|
|
12495
|
+
summary: "Los pedidos de la Software Factory de un espacio (viven en su Teams): listarlos con tiempo y costo, ver uno (plan vigente, PR, preview, \xFAltimos eventos, qu\xE9 rol trabaja), ver en qu\xE9 se fue su tiempo y su dinero y por qu\xE9 se ator\xF3 o fall\xF3, los totales del espacio y corregir el t\xEDtulo de un pedido.",
|
|
12496
12496
|
replace: [['"Title"', '"T\xEDtulo"']],
|
|
12497
12497
|
flags: [
|
|
12498
12498
|
["--workspace SLUG", "el espacio (default: el de ghosty use)"],
|
|
@@ -12500,12 +12500,20 @@ var HELPS_ES = {
|
|
|
12500
12500
|
["--status S", "ls: s\xF3lo esa etapa (planning, building, checking, pr_review, merged\u2026)"],
|
|
12501
12501
|
["--limit N", "ls: cu\xE1ntos (default 30)"],
|
|
12502
12502
|
["--full", "show: imprime el plan vigente"],
|
|
12503
|
+
["--timing", "show: cada turno de cada rol (cr\xEDtico, @plan, @build, @check) con minutos, minutos en tools, modelo y USD; el reloj del pedido partido en trabajando, esperando a una persona (plan o PR por aprobar) y lo dem\xE1s (CI, colas); y los comandos de 60 s o m\xE1s"],
|
|
12504
|
+
["--refresh", "why: rehace el an\xE1lisis (se guarda la primera vez)"],
|
|
12505
|
+
["--since 30d", "stats: cu\xE1nto hacia atr\xE1s (d\xEDas, default 30)"],
|
|
12506
|
+
["--repo OWNER/REPO", "stats: s\xF3lo ese repo (due\xF1o/repo)"],
|
|
12503
12507
|
["-n, --dry-run", "rename: ense\xF1a el cambio, no cambia nada"],
|
|
12504
12508
|
["--yes", "rename: se salta la confirmaci\xF3n (obligatorio sin terminal)"]
|
|
12505
12509
|
],
|
|
12506
12510
|
notes: [
|
|
12507
12511
|
"Leer pide ser miembro del espacio; renombrar, ser due\xF1o, admin o staff de Ghosty, y queda en bit\xE1cora (factory.run_rename). La tarjeta del room toma el t\xEDtulo nuevo en su siguiente refresco.",
|
|
12508
|
-
"Qui\xE9n es @plan o @build: ghosty agents ls --handle plan --workspace SLUG."
|
|
12512
|
+
"Qui\xE9n es @plan o @build: ghosty agents ls --handle plan --workspace SLUG.",
|
|
12513
|
+
"stats: pedidos, % mezclados, mediana de minutos hasta el PR y hasta el merge, vueltas @check\u2192@build y CI rojo por pedido, y costo por pedido y por PR mezclado. El costo incluye el primer turno de @plan en el hilo, antes de que naciera el pedido.",
|
|
12514
|
+
"--timing lee la transcripci\xF3n de cada rol: los motores sin una (ACP) salen con minutos y costo, pero sin comandos. Tambi\xE9n lista los atascos: un comando abierto 8+ min, el mismo comando 4 veces, el mismo error 3 veces, ir y venir entre dos acciones, o apt install dentro de una caja.",
|
|
12515
|
+
"why: si el pedido no fue sano (atascos, @build de m\xE1s de 30 min, vueltas, CI rojo, escalado), una categor\xEDa (missing_service, unresolved_build_or_tests, fixed_strategy\u2026), el paso cr\xEDtico con su evidencia y qu\xE9 cambiar la pr\xF3xima vez. Un pedido sano no llama al modelo. Cuando un pedido cierra sin ser sano, el room recibe este resumen en su hilo.",
|
|
12516
|
+
"Mientras un rol trabaja, si se atora el room recibe un \u23F3 en el hilo y al rol se le dice qu\xE9 revisar; desde la segunda vez en el mismo pedido, quien lo pidi\xF3 recibe un push."
|
|
12509
12517
|
]
|
|
12510
12518
|
}
|
|
12511
12519
|
};
|
|
@@ -13239,10 +13247,13 @@ HELPS.audit = {
|
|
|
13239
13247
|
learn: `${REF}#bitacora`
|
|
13240
13248
|
};
|
|
13241
13249
|
HELPS.factory = {
|
|
13242
|
-
summary: "A workspace's Software Factory runs (they live in its Teams): list them, see one (current plan, PR, preview, latest events, which role is working) and fix a run's title.",
|
|
13250
|
+
summary: "A workspace's Software Factory 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.",
|
|
13243
13251
|
usage: [
|
|
13244
13252
|
"ghosty factory runs ls [--workspace SLUG] [--room R] [--status S] [--limit 30]",
|
|
13245
13253
|
"ghosty factory runs show <run> [--workspace SLUG] [--full]",
|
|
13254
|
+
"ghosty factory runs show <run> --timing [--workspace SLUG]",
|
|
13255
|
+
"ghosty factory runs why <run> [--refresh] [--workspace SLUG]",
|
|
13256
|
+
"ghosty factory runs stats [--since 30d] [--repo OWNER/REPO] [--workspace SLUG]",
|
|
13246
13257
|
'ghosty factory runs rename <run> "Title" [--workspace SLUG] [--dry-run] [--yes]'
|
|
13247
13258
|
],
|
|
13248
13259
|
flags: [
|
|
@@ -13251,11 +13262,15 @@ HELPS.factory = {
|
|
|
13251
13262
|
["--status S", "ls: only that stage (planning, building, checking, pr_review, merged\u2026)"],
|
|
13252
13263
|
["--limit N", "ls: how many (default 30)"],
|
|
13253
13264
|
["--full", "show: print the current plan"],
|
|
13265
|
+
["--timing", "show: each turn of each role (critic, @plan, @build, @check) with minutes, minutes in tools, model and USD; the run's clock split into working, waiting for a person (plan or PR to approve) and the rest (CI, queues); and the commands that took 60 s or more"],
|
|
13266
|
+
["--refresh", "why: redo the analysis (it's saved the first time)"],
|
|
13267
|
+
["--since 30d", "stats: how far back (days, default 30)"],
|
|
13268
|
+
["--repo OWNER/REPO", "stats: only that repo"],
|
|
13254
13269
|
["-n, --dry-run", "rename: show the change, change nothing"],
|
|
13255
13270
|
["--yes", "rename: skip the confirmation (required without a terminal)"]
|
|
13256
13271
|
],
|
|
13257
|
-
examples: ["ghosty factory runs ls --workspace business --room mailmask", "ghosty factory runs show 15 --workspace business", 'ghosty factory runs rename 15 "Docs del CLI al d\xEDa" --workspace business --dry-run'],
|
|
13258
|
-
notes: ["Reading needs membership in the workspace; rename needs owner, admin or Ghosty staff, and is logged (factory.run_rename). The room's card picks the new title up on its next refresh.", "Who is @plan or @build: ghosty agents ls --handle plan --workspace SLUG."],
|
|
13272
|
+
examples: ["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 stats --since 7d --workspace mercadito-verde", 'ghosty factory runs rename 15 "Docs del CLI al d\xEDa" --workspace business --dry-run'],
|
|
13273
|
+
notes: ["Reading needs membership in the workspace; rename needs owner, admin or Ghosty staff, and is logged (factory.run_rename). The room's card picks the new title up on its next refresh.", "Who is @plan or @build: ghosty agents ls --handle plan --workspace SLUG.", "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. A healthy run doesn't call the model. When a run closes unhealthy, the room gets this summary in its thread.", "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."],
|
|
13259
13274
|
learn: `${REF}#fabrica`
|
|
13260
13275
|
};
|
|
13261
13276
|
function helpText(cmd) {
|
|
@@ -13613,11 +13628,11 @@ async function planCmd(sub, slug, v, context) {
|
|
|
13613
13628
|
if (!slug) throw usage(t("Missing workspace.", "Falta el espacio."), "ghosty plan apply <workspace> [--dry-run] (or ghosty use <workspace>)");
|
|
13614
13629
|
const path = `/api/v2/workspaces/${encodeURIComponent(slug)}/plan/apply`;
|
|
13615
13630
|
const plan = await api(path, { method: "POST", body: { dryRun: true } });
|
|
13616
|
-
const
|
|
13631
|
+
const money2 = (n) => `$${n.toLocaleString("es-MX")} MXN`;
|
|
13617
13632
|
const text = [
|
|
13618
13633
|
`${bold(plan.workspace)} ${dim(plan.scheduledFor ? t(`scheduled for ${plan.scheduledFor.slice(0, 10)}`, `programado para el ${plan.scheduledFor.slice(0, 10)}`) : "")}`,
|
|
13619
13634
|
...plan.changes.map((c) => ` ~ ${c.label}: ${String(c.from)} \u2192 ${String(c.to)}`),
|
|
13620
|
-
` ${t("monthly", "al mes")}: ${
|
|
13635
|
+
` ${t("monthly", "al mes")}: ${money2(plan.monthlyTotal.from)} \u2192 ${money2(plan.monthlyTotal.to)}`
|
|
13621
13636
|
].join("\n");
|
|
13622
13637
|
if (v["dry-run"]) return out(plan, () => `${text}
|
|
13623
13638
|
${dim(t("Dry run: nothing applied.", "Simulaci\xF3n: no se aplic\xF3 nada."))}`);
|
|
@@ -14110,21 +14125,84 @@ ${dim(t("Times in UTC. --full for the whole detail; --json for everything.", "Ho
|
|
|
14110
14125
|
|
|
14111
14126
|
// src/commands/factory.ts
|
|
14112
14127
|
var enc12 = encodeURIComponent;
|
|
14128
|
+
var money = (n) => n === null || n === void 0 ? "\u2014" : `$${n.toFixed(2)}`;
|
|
14129
|
+
var mins = (n) => n === null || n === void 0 ? "\u2014" : n >= 120 ? `${(n / 60).toFixed(1)} h` : `${n} min`;
|
|
14130
|
+
var hhmm = (iso) => new Date(iso).toLocaleTimeString("es-MX", { hour: "2-digit", minute: "2-digit", hour12: false });
|
|
14113
14131
|
var short2 = (s2) => (s2 ?? "").slice(0, 16).replace("T", " ");
|
|
14114
14132
|
async function factoryCmd(sub, verb, ref, title, v, context) {
|
|
14115
|
-
if (sub !== "runs") throw usage(t(`Unknown factory subcommand: ${sub ?? "(none)"}.`, `Subcomando de factory desconocido: ${sub ?? "(ninguno)"}.`), "ghosty factory runs ls|show|rename \u2026");
|
|
14133
|
+
if (sub !== "runs") throw usage(t(`Unknown factory subcommand: ${sub ?? "(none)"}.`, `Subcomando de factory desconocido: ${sub ?? "(ninguno)"}.`), "ghosty factory runs ls|show|why|stats|rename \u2026");
|
|
14116
14134
|
const ws = v.workspace ?? context;
|
|
14117
14135
|
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)."));
|
|
14118
14136
|
const base = `/api/v2/workspaces/${enc12(ws)}/factory/runs`;
|
|
14119
14137
|
if (!verb || verb === "ls" || verb === "list") {
|
|
14120
14138
|
const qs = new URLSearchParams({ ...v.room ? { room: v.room } : {}, ...v.status ? { status: v.status } : {}, ...v.limit ? { limit: v.limit } : {} });
|
|
14121
14139
|
const r = await api(`${base}?${qs}`);
|
|
14122
|
-
const rows = r.runs.map((x) => ({ run: `#${x.id}`, title: x.title.slice(0, 60), status: x.status, room: x.room ? `#${x.room}` : "", pr: x.pr ? x.pr.replace(/^https:\/\/github\.com\//, "") : "", updated: short2(x.updatedAt) }));
|
|
14123
|
-
return out(r, () => rows.length ? `${table(rows, ["run", "title", "status", "room", "pr", "updated"])}
|
|
14140
|
+
const rows = r.runs.map((x) => ({ run: `#${x.id}`, title: x.title.slice(0, 60), status: x.status, room: x.room ? `#${x.room}` : "", pr: x.pr ? x.pr.replace(/^https:\/\/github\.com\//, "") : "", time: mins(x.durationMin), usd: money(x.usd), updated: short2(x.updatedAt) }));
|
|
14141
|
+
return out(r, () => rows.length ? `${table(rows, ["run", "title", "status", "room", "pr", "time", "usd", "updated"])}
|
|
14124
14142
|
${dim(`${t("Detail", "Detalle")}: ghosty factory runs show <run> --workspace ${r.workspace}`)}` : dim(t(`No factory runs in ${r.workspace}.`, `No hay pedidos de la f\xE1brica en ${r.workspace}.`)));
|
|
14125
14143
|
}
|
|
14144
|
+
if (verb === "stats") {
|
|
14145
|
+
const qs = new URLSearchParams({ since: v.since ?? "30d", ...v.repo ? { repo: v.repo } : {} });
|
|
14146
|
+
const { stats: s2, workspace } = await api(`/api/v2/workspaces/${enc12(ws)}/factory/stats?${qs}`);
|
|
14147
|
+
return out({ workspace, stats: s2 }, () => {
|
|
14148
|
+
if (!s2.runs) return dim(t(`No factory runs in ${workspace} in the last ${s2.sinceDays} days.`, `No hay pedidos de la f\xE1brica en ${workspace} en los \xFAltimos ${s2.sinceDays} d\xEDas.`));
|
|
14149
|
+
return [
|
|
14150
|
+
bold(t(`${workspace} \xB7 last ${s2.sinceDays} days${s2.repo ? ` \xB7 ${s2.repo}` : ""}`, `${workspace} \xB7 \xFAltimos ${s2.sinceDays} d\xEDas${s2.repo ? ` \xB7 ${s2.repo}` : ""}`)),
|
|
14151
|
+
`${t("runs", "pedidos")}: ${s2.runs} \xB7 ${t("merged", "mezclados")}: ${s2.merged} (${s2.mergedPct ?? "\u2014"} %) ${dim(s2.byStatus.map((b) => `${b.status} ${b.runs}`).join(" \xB7 "))}`,
|
|
14152
|
+
`${t("median to PR", "mediana hasta el PR")}: ${mins(s2.medianMinutesToPr)} \xB7 ${t("to merge", "hasta el merge")}: ${mins(s2.medianMinutesToMerge)}`,
|
|
14153
|
+
`${t("@check\u2192@build loops per run", "vueltas @check\u2192@build por pedido")}: ${s2.avgLoops ?? "\u2014"} \xB7 ${t("red CI per run", "CI rojo por pedido")}: ${s2.avgCiFails ?? "\u2014"}${s2.runsWithStuck !== void 0 ? ` \xB7 ${t("runs with stuck alerts", "pedidos con atascos")}: ${s2.runsWithStuck}` : ""}`,
|
|
14154
|
+
`${t("cost", "costo")}: ${money(s2.usd)} USD \xB7 ${t("per run", "por pedido")} ${money(s2.usdPerRun)} \xB7 ${bold(t(`per merged PR ${money(s2.usdPerMergedPr)}`, `por PR mezclado ${money(s2.usdPerMergedPr)}`))}`,
|
|
14155
|
+
...s2.byRepo.length > 1 ? ["", table(s2.byRepo.map((b) => ({ repo: b.repo, runs: b.runs, merged: b.merged, usd: money(b.usd) })), ["repo", "runs", "merged", "usd"])] : []
|
|
14156
|
+
].join("\n");
|
|
14157
|
+
});
|
|
14158
|
+
}
|
|
14126
14159
|
if (!ref) throw usage(t("Missing run.", "Falta el pedido."), `ghosty factory runs ${verb} <run> --workspace ${ws}`);
|
|
14127
14160
|
const path = `${base}/${enc12(ref.replace(/^#/, ""))}`;
|
|
14161
|
+
if (verb === "show" && v.timing) {
|
|
14162
|
+
const { timing: tm, workspace } = await api(`${path}/timing`);
|
|
14163
|
+
return out({ workspace, timing: tm }, () => {
|
|
14164
|
+
const c = tm.clock;
|
|
14165
|
+
const head = [
|
|
14166
|
+
`${bold(`#${tm.run}`)} ${dim(`\xB7 ${tm.status} \xB7 ${t("loops", "vueltas")} ${tm.loops} \xB7 ${t("red CI", "CI rojo")} ${tm.ciFails}`)} \xB7 ${bold(money(tm.usd))} USD`,
|
|
14167
|
+
`${t("clock", "reloj")}: ${mins(c.totalMinutes)} = ${t("working", "trabajando")} ${mins(c.workingMinutes)} \xB7 ${t("waiting for a person", "esperando a una persona")} ${mins(c.waitingHumanMinutes)} \xB7 ${t("other (CI, queues)", "lo dem\xE1s (CI, colas)")} ${mins(c.otherMinutes)}`,
|
|
14168
|
+
dim(c.byState.map((b) => `${b.state} ${mins(b.minutes)}`).join(" \u2192 ")),
|
|
14169
|
+
""
|
|
14170
|
+
];
|
|
14171
|
+
const rows = tm.steps.map((s2) => ({
|
|
14172
|
+
[t("at", "hora")]: hhmm(s2.startedAt),
|
|
14173
|
+
[t("step", "paso")]: `@${s2.role ?? "?"}${s2.critic ? t(" (critic)", " (cr\xEDtico)") : ""}${s2.prePlan ? t(" (thread)", " (hilo)") : ""}`,
|
|
14174
|
+
[t("time", "tiempo")]: s2.state === "running" ? `${mins(s2.minutes)} \u2026` : mins(s2.minutes),
|
|
14175
|
+
[t("in tools", "en tools")]: mins(s2.toolMinutes),
|
|
14176
|
+
model: (s2.model ?? "\u2014").replace(/^claude-/, ""),
|
|
14177
|
+
usd: money(s2.usd),
|
|
14178
|
+
[t("state", "estado")]: s2.state === "error" ? red(s2.state) : s2.state
|
|
14179
|
+
}));
|
|
14180
|
+
const cols = Object.keys(rows[0] ?? {});
|
|
14181
|
+
const slow = tm.steps.flatMap((s2) => s2.slow.map((x) => ({ ...x, role: s2.role })));
|
|
14182
|
+
return [
|
|
14183
|
+
...head,
|
|
14184
|
+
rows.length ? table(rows, cols) : dim(t("No turns found for this run.", "No hay turnos de este pedido.")),
|
|
14185
|
+
...slow.length ? ["", bold(t("Slowest commands (\u2265 60 s)", "Comandos m\xE1s lentos (\u2265 60 s)")), ...slow.map((x) => ` ${hhmm(x.at)} @${x.role} ${x.secs === null ? red(t("no result", "sin resultado")) : mins(Math.round(x.secs / 6) / 10)} ${dim(`${x.tool}: ${x.input}`)}`)] : [],
|
|
14186
|
+
...tm.steps.some((s2) => s2.flags?.length) ? ["", bold(t("Stuck patterns", "Atascos")), ...tm.steps.flatMap((s2) => (s2.flags ?? []).map((f) => ` ${hhmm(f.at)} @${s2.role} ${red(f.rule)}${f.count > 1 ? ` \xD7${f.count}` : ""}${f.secs ? ` ${mins(Math.round(f.secs / 6) / 10)}` : ""} ${dim(f.detail.slice(0, 120))}`)), dim(t(`Why: ghosty factory runs why ${tm.run} --workspace ${workspace}`, `Por qu\xE9: ghosty factory runs why ${tm.run} --workspace ${workspace}`))] : [],
|
|
14187
|
+
...tm.steps.filter((s2) => s2.why).map((s2) => dim(` @${s2.role}: ${t("no timeline", "sin l\xEDnea de tiempo")} (${s2.why})`))
|
|
14188
|
+
].join("\n");
|
|
14189
|
+
});
|
|
14190
|
+
}
|
|
14191
|
+
if (verb === "why") {
|
|
14192
|
+
const { why: w, workspace } = await api(`${path}/why`, v.refresh ? { body: { refresh: true } } : void 0);
|
|
14193
|
+
return out({ workspace, why: w }, () => {
|
|
14194
|
+
if (w.healthy) return `${lavender("\u2713")} #${w.runId} ${t("was healthy: no stuck patterns, no long @build, no loops, no red CI.", "fue sano: sin atascos, sin @build largo, sin vueltas ni CI rojo.")}`;
|
|
14195
|
+
const impact = { high: t("high", "alto"), medium: t("medium", "medio"), low: t("low", "bajo") }[w.impact] ?? w.impact;
|
|
14196
|
+
return [
|
|
14197
|
+
`${bold(`#${w.runId}`)} ${red(w.category)} ${dim(`\xB7 ${t("impact", "impacto")} ${impact} \xB7 ${w.source === "model" ? w.model ?? "model" : t("rules", "reglas")} \xB7 ${short2(w.createdAt)}`)}`,
|
|
14198
|
+
w.summary,
|
|
14199
|
+
dim(`${t("signals", "se\xF1ales")}: ${w.reasons.join("; ")}`),
|
|
14200
|
+
...w.criticalStep ? ["", bold(t("Critical step", "Paso cr\xEDtico")), ` ${w.criticalStep.at ? hhmm(w.criticalStep.at) : "\u2014"} @${w.criticalStep.role ?? "?"} ${dim(w.criticalStep.command ?? "")}`, ` ${w.criticalStep.evidence}`] : [],
|
|
14201
|
+
...w.actions.length ? ["", bold(t("Next time", "Para la pr\xF3xima")), ...w.actions.map((a) => ` ${lavender(a.type)} ${a.text}`)] : [],
|
|
14202
|
+
...v.refresh ? [] : ["", dim(t("Saved analysis; --refresh redoes it.", "An\xE1lisis guardado; --refresh lo rehace."))]
|
|
14203
|
+
].join("\n");
|
|
14204
|
+
});
|
|
14205
|
+
}
|
|
14128
14206
|
if (verb === "show") {
|
|
14129
14207
|
const { run: r } = await api(path);
|
|
14130
14208
|
const lines = [
|
|
@@ -14152,7 +14230,7 @@ ${dim(t("Dry run: nothing changed.", "Simulacro: no se cambi\xF3 nada."))}`);
|
|
|
14152
14230
|
const r = await api(path, { body: { title } });
|
|
14153
14231
|
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)"))}`);
|
|
14154
14232
|
}
|
|
14155
|
-
throw usage(t(`Unknown runs subcommand: ${verb}.`, `Subcomando de runs desconocido: ${verb}.`), 'ghosty factory runs ls | show <run> | rename <run> "title"');
|
|
14233
|
+
throw usage(t(`Unknown runs subcommand: ${verb}.`, `Subcomando de runs desconocido: ${verb}.`), 'ghosty factory runs ls | show <run> [--timing] | why <run> [--refresh] | stats [--since 30d] | rename <run> "title"');
|
|
14156
14234
|
}
|
|
14157
14235
|
async function agentsByHandle(handle, workspace) {
|
|
14158
14236
|
const qs = new URLSearchParams({ handle, ...workspace ? { workspace } : {} });
|
|
@@ -14444,7 +14522,7 @@ ${bold("Workspaces")} ${dim("(changing them: Ghosty staff)")}
|
|
|
14444
14522
|
staff ls [staff-email] [client-email] Ghosty staff assigned to client accounts (staff) [--all: also revoked]
|
|
14445
14523
|
staff assign|unassign <staff> <client> Staff sees and runs that account's agents, documents, databases and boards as themselves (no keys, billing or transfers; logged) [--reason]
|
|
14446
14524
|
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]
|
|
14447
|
-
factory runs ls|show|rename
|
|
14525
|
+
factory runs ls|show|why|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, totals, fix a title [--workspace SLUG] [--room R] [--status S] [show --timing] [why --refresh] [stats --since 30d --repo R]
|
|
14448
14526
|
|
|
14449
14527
|
${bold("Agents")}
|
|
14450
14528
|
agents ls List your agents [--engine E] [--ids: only ids, for xargs] \xB7 --user EMAIL --app all|android|ios|desktop: as their app sees it (staff)
|
|
@@ -14608,7 +14686,7 @@ ${bold("Espacios")} ${dim("(cambiarlos: staff de Ghosty)")}
|
|
|
14608
14686
|
staff ls [correo-staff] [correo-cliente] Staff de Ghosty asignado a cuentas cliente (staff) [--all: tambi\xE9n revocadas]
|
|
14609
14687
|
staff assign|unassign <staff> <cliente> El staff ve y opera agentes, documentos, bases y tableros de esa cuenta como s\xED mismo (sin llaves, cobro ni traspasos; queda en bit\xE1cora) [--reason]
|
|
14610
14688
|
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]
|
|
14611
|
-
factory runs ls|show|rename
|
|
14689
|
+
factory runs ls|show|why|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, totales, corregir un t\xEDtulo [--workspace SLUG] [--room R] [--status S] [show --timing] [why --refresh] [stats --since 30d --repo R]
|
|
14612
14690
|
|
|
14613
14691
|
${bold("Agentes")}
|
|
14614
14692
|
agents ls Lista tus agentes [--engine E] [--ids: s\xF3lo ids, para xargs] \xB7 --user CORREO --app all|android|ios|desktop: como lo ve su app (staff)
|
|
@@ -14868,6 +14946,9 @@ async function main() {
|
|
|
14868
14946
|
action: { type: "string" },
|
|
14869
14947
|
verbose: { type: "boolean" },
|
|
14870
14948
|
full: { type: "boolean" },
|
|
14949
|
+
timing: { type: "boolean" },
|
|
14950
|
+
refresh: { type: "boolean" },
|
|
14951
|
+
repo: { type: "string" },
|
|
14871
14952
|
"as-formmy": { type: "boolean" },
|
|
14872
14953
|
"key-column": { type: "string" },
|
|
14873
14954
|
"unset-env": { type: "string" },
|
|
@@ -15004,7 +15085,7 @@ async function main() {
|
|
|
15004
15085
|
const tokens = splitTokens(pos, v);
|
|
15005
15086
|
globals.token = tokens.session;
|
|
15006
15087
|
globals.profile = v.profile;
|
|
15007
|
-
if (v.version) return out({ version: "0.
|
|
15088
|
+
if (v.version) return out({ version: "0.56.0" }, () => "0.56.0");
|
|
15008
15089
|
for (const n of normalizeArgs(pos, v)) if (!globals.json) info(dim(n));
|
|
15009
15090
|
const context = (await readConfig()).workspace;
|
|
15010
15091
|
if (!explicitToken()?.startsWith("gat_")) {
|
|
@@ -15170,7 +15251,7 @@ async function main() {
|
|
|
15170
15251
|
throw unknown(cmd, cmd, COMMANDS, "ghosty --help");
|
|
15171
15252
|
}
|
|
15172
15253
|
}
|
|
15173
|
-
var notice = updateNotice("0.
|
|
15254
|
+
var notice = updateNotice("0.56.0").catch(() => null);
|
|
15174
15255
|
main().then(
|
|
15175
15256
|
async () => {
|
|
15176
15257
|
const n = await notice;
|
package/package.json
CHANGED