ghostrail 0.12.2 → 0.14.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/dist/agent/claude-code.d.ts.map +1 -1
- package/dist/agent/claude-code.js +15 -5
- package/dist/agent/claude-code.js.map +1 -1
- package/dist/agent/failure.d.ts +19 -0
- package/dist/agent/failure.d.ts.map +1 -1
- package/dist/agent/failure.js +45 -0
- package/dist/agent/failure.js.map +1 -1
- package/dist/board/index.d.ts +1 -1
- package/dist/board/index.d.ts.map +1 -1
- package/dist/board/index.js.map +1 -1
- package/dist/board/page.d.ts +1 -1
- package/dist/board/page.d.ts.map +1 -1
- package/dist/board/page.js +196 -13
- package/dist/board/page.js.map +1 -1
- package/dist/board/server.d.ts +18 -4
- package/dist/board/server.d.ts.map +1 -1
- package/dist/board/server.js +78 -4
- package/dist/board/server.js.map +1 -1
- package/dist/cli/commands.d.ts.map +1 -1
- package/dist/cli/commands.js +19 -2
- package/dist/cli/commands.js.map +1 -1
- package/dist/loop/budget.d.ts +35 -0
- package/dist/loop/budget.d.ts.map +1 -0
- package/dist/loop/budget.js +42 -0
- package/dist/loop/budget.js.map +1 -0
- package/dist/loop/control.d.ts +15 -0
- package/dist/loop/control.d.ts.map +1 -0
- package/dist/loop/control.js +30 -0
- package/dist/loop/control.js.map +1 -0
- package/dist/loop/engine.d.ts.map +1 -1
- package/dist/loop/engine.js +22 -2
- package/dist/loop/engine.js.map +1 -1
- package/dist/loop/index.d.ts +1 -0
- package/dist/loop/index.d.ts.map +1 -1
- package/dist/loop/index.js +1 -0
- package/dist/loop/index.js.map +1 -1
- package/dist/loop/ports.d.ts +12 -2
- package/dist/loop/ports.d.ts.map +1 -1
- package/dist/loop/watch.d.ts +7 -0
- package/dist/loop/watch.d.ts.map +1 -1
- package/dist/loop/watch.js +14 -0
- package/dist/loop/watch.js.map +1 -1
- package/dist/publish/gh.d.ts +4 -1
- package/dist/publish/gh.d.ts.map +1 -1
- package/dist/publish/gh.js +58 -0
- package/dist/publish/gh.js.map +1 -1
- package/dist/publish/githost.d.ts +19 -0
- package/dist/publish/githost.d.ts.map +1 -1
- package/dist/publish/githost.js.map +1 -1
- package/dist/publish/index.d.ts +2 -2
- package/dist/publish/index.d.ts.map +1 -1
- package/dist/publish/index.js +1 -1
- package/dist/publish/index.js.map +1 -1
- package/dist/respond/respond.d.ts.map +1 -1
- package/dist/respond/respond.js +24 -4
- package/dist/respond/respond.js.map +1 -1
- package/dist/respond/store.d.ts +12 -0
- package/dist/respond/store.d.ts.map +1 -1
- package/dist/respond/store.js +22 -1
- package/dist/respond/store.js.map +1 -1
- package/dist/store/events.d.ts +3 -0
- package/dist/store/events.d.ts.map +1 -1
- package/dist/store/events.js +2 -0
- package/dist/store/events.js.map +1 -1
- package/dist/store/file-store.d.ts +2 -0
- package/dist/store/file-store.d.ts.map +1 -1
- package/dist/store/file-store.js +3 -0
- package/dist/store/file-store.js.map +1 -1
- package/package.json +1 -1
- package/templates/code-local/ghostrail.toml +4 -0
- package/templates/content-loop/ghostrail.toml +4 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"claude-code.d.ts","sourceRoot":"","sources":["../../src/agent/claude-code.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,YAAY,
|
|
1
|
+
{"version":3,"file":"claude-code.d.ts","sourceRoot":"","sources":["../../src/agent/claude-code.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,YAAY,EAAc,MAAM,kBAAkB,CAAC;AAKtF,+EAA+E;AAC/E,eAAO,MAAM,WAAW,2BAA2B,CAAC;AAEpD,MAAM,WAAW,iBAAiB;IAChC,4EAA4E;IAC5E,MAAM,EAAE,MAAM,CAAC;IACf,4EAA4E;IAC5E,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,8EAA8E;IAC9E,MAAM,EAAE,MAAM,CAAC;IACf,6CAA6C;IAC7C,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,aAAa,CAAC;IAC/C,sDAAsD;IACtD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oCAAoC;IACpC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,2BAA2B;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kEAAkE;IAClE,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,iBAAiB,GAAG,MAAM,EAAE,CAwBpE;AAYD;;;;;;GAMG;AACH,qBAAa,eAAgB,YAAW,KAAK;IAO/B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC;IANxC;;;;;OAKG;gBAC0B,UAAU,CAAC,EAAE,MAAM,YAAA;IAE1C,GAAG,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC;CAoExD"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
|
-
import { describeAgentFailure, describeSilentRun, spokenReason } from "./failure.js";
|
|
2
|
+
import { costFromJson, describeAgentFailure, describeSilentRun, spokenReason } from "./failure.js";
|
|
3
3
|
import { renderPrompt } from "./prompt.js";
|
|
4
4
|
import { parseAgentResult } from "./result.js";
|
|
5
5
|
/** Path, relative to the workspace root, where the agent writes its result. */
|
|
@@ -86,8 +86,13 @@ export class ClaudeCodeAgent {
|
|
|
86
86
|
? {}
|
|
87
87
|
: { env: { HOME: profileDir, CLAUDE_CONFIG_DIR: join(profileDir, ".claude") } }),
|
|
88
88
|
});
|
|
89
|
+
// Read once, before any return, and attached to every one of them. A run
|
|
90
|
+
// that timed out or failed still spent money, and the tick's ceiling is
|
|
91
|
+
// only honest if those count too. A partial envelope simply yields no cost,
|
|
92
|
+
// which the caller charges as an assumption rather than as free.
|
|
93
|
+
const spend = withSpend(costFromJson(run.stdout));
|
|
89
94
|
if (run.timedOut) {
|
|
90
|
-
return { status: "failed", error: "agent timed out" };
|
|
95
|
+
return { ...spend, status: "failed", error: "agent timed out" };
|
|
91
96
|
}
|
|
92
97
|
const raw = await readFile(backend, workspace, RESULT_PATH);
|
|
93
98
|
if (raw !== undefined && raw.trim().length > 0) {
|
|
@@ -100,12 +105,13 @@ export class ClaudeCodeAgent {
|
|
|
100
105
|
if (outcome.status === "noop" && outcome.reason === undefined) {
|
|
101
106
|
const spoken = spokenReason(run.stdout);
|
|
102
107
|
if (spoken !== undefined)
|
|
103
|
-
return { status: "noop", reason: spoken };
|
|
108
|
+
return { ...spend, status: "noop", reason: spoken };
|
|
104
109
|
}
|
|
105
|
-
return outcome;
|
|
110
|
+
return { ...spend, ...outcome };
|
|
106
111
|
}
|
|
107
112
|
if (run.exitCode !== 0) {
|
|
108
113
|
return {
|
|
114
|
+
...spend,
|
|
109
115
|
status: "failed",
|
|
110
116
|
error: describeAgentFailure(run.exitCode, run.stdout, run.stderr),
|
|
111
117
|
};
|
|
@@ -114,7 +120,11 @@ export class ClaudeCodeAgent {
|
|
|
114
120
|
// nothing was reported and nothing should be published, but it is not the
|
|
115
121
|
// same event as an agent that deliberately wrote {"status":"noop"}. Carry
|
|
116
122
|
// what the agent said so the two can be told apart after the fact.
|
|
117
|
-
return { status: "noop", reason: describeSilentRun(run.stdout, run.stderr) };
|
|
123
|
+
return { ...spend, status: "noop", reason: describeSilentRun(run.stdout, run.stderr) };
|
|
118
124
|
}
|
|
119
125
|
}
|
|
126
|
+
/** An optional cost as a spreadable object, so absent stays absent. */
|
|
127
|
+
function withSpend(costUsd) {
|
|
128
|
+
return costUsd === undefined ? {} : { costUsd };
|
|
129
|
+
}
|
|
120
130
|
//# sourceMappingURL=claude-code.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"claude-code.js","sourceRoot":"","sources":["../../src/agent/claude-code.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAIjC,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"claude-code.js","sourceRoot":"","sources":["../../src/agent/claude-code.ts"],"names":[],"mappings":"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"}
|
package/dist/agent/failure.d.ts
CHANGED
|
@@ -26,6 +26,25 @@ export declare function describeSilentRun(stdout: string, stderr: string): strin
|
|
|
26
26
|
* only account of a silent run anyone gets.
|
|
27
27
|
*/
|
|
28
28
|
export declare function resultFromJson(stdout: string): string | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* The `total_cost_usd` of the agent's output envelope, or undefined.
|
|
31
|
+
*
|
|
32
|
+
* Lives next to {@link resultFromJson} and tries the same two shapes, because
|
|
33
|
+
* both read one field out of the same payload: `--output-format json` puts the
|
|
34
|
+
* envelope in the whole text, `stream-json` puts it on the last non-empty line.
|
|
35
|
+
* Two parsers for one envelope would drift.
|
|
36
|
+
*
|
|
37
|
+
* Undefined covers every way of not knowing: absent, not a number, NaN,
|
|
38
|
+
* infinite, or negative. A non-finite cost poisons every sum it enters, and a
|
|
39
|
+
* negative one would let a single run refund the tick's budget. Zero is a
|
|
40
|
+
* value, not a miss: a run that genuinely cost nothing reported a cost, and the
|
|
41
|
+
* caller charges an assumption for a run that reported none.
|
|
42
|
+
*
|
|
43
|
+
* Never throws. Unparseable output means a missing cost, not a failed run: the
|
|
44
|
+
* work happened, and failing it over its accounting would be worse than not
|
|
45
|
+
* knowing what it spent.
|
|
46
|
+
*/
|
|
47
|
+
export declare function costFromJson(stdout: string): number | undefined;
|
|
29
48
|
/**
|
|
30
49
|
* The one-line error recorded for a failed agent run.
|
|
31
50
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"failure.d.ts","sourceRoot":"","sources":["../../src/agent/failure.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,eAAO,MAAM,gBAAgB,iCAAiC,CAAC;AAK/D;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAG/D;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAKxE;AAYD;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CASjE;
|
|
1
|
+
{"version":3,"file":"failure.d.ts","sourceRoot":"","sources":["../../src/agent/failure.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,eAAO,MAAM,gBAAgB,iCAAiC,CAAC;AAK/D;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAG/D;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAKxE;AAYD;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CASjE;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAS/D;AAiCD;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAK7F"}
|
package/dist/agent/failure.js
CHANGED
|
@@ -55,6 +55,51 @@ export function resultFromJson(stdout) {
|
|
|
55
55
|
}
|
|
56
56
|
return undefined;
|
|
57
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* The `total_cost_usd` of the agent's output envelope, or undefined.
|
|
60
|
+
*
|
|
61
|
+
* Lives next to {@link resultFromJson} and tries the same two shapes, because
|
|
62
|
+
* both read one field out of the same payload: `--output-format json` puts the
|
|
63
|
+
* envelope in the whole text, `stream-json` puts it on the last non-empty line.
|
|
64
|
+
* Two parsers for one envelope would drift.
|
|
65
|
+
*
|
|
66
|
+
* Undefined covers every way of not knowing: absent, not a number, NaN,
|
|
67
|
+
* infinite, or negative. A non-finite cost poisons every sum it enters, and a
|
|
68
|
+
* negative one would let a single run refund the tick's budget. Zero is a
|
|
69
|
+
* value, not a miss: a run that genuinely cost nothing reported a cost, and the
|
|
70
|
+
* caller charges an assumption for a run that reported none.
|
|
71
|
+
*
|
|
72
|
+
* Never throws. Unparseable output means a missing cost, not a failed run: the
|
|
73
|
+
* work happened, and failing it over its accounting would be worse than not
|
|
74
|
+
* knowing what it spent.
|
|
75
|
+
*/
|
|
76
|
+
export function costFromJson(stdout) {
|
|
77
|
+
const lines = stdout.split("\n").filter((line) => line.trim().length > 0);
|
|
78
|
+
const lastLine = lines[lines.length - 1];
|
|
79
|
+
for (const candidate of [stdout, lastLine]) {
|
|
80
|
+
if (candidate === undefined)
|
|
81
|
+
continue;
|
|
82
|
+
const cost = costField(candidate);
|
|
83
|
+
if (cost !== undefined)
|
|
84
|
+
return cost;
|
|
85
|
+
}
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
/** The usable `total_cost_usd` of one JSON object, or undefined. */
|
|
89
|
+
function costField(text) {
|
|
90
|
+
try {
|
|
91
|
+
const parsed = JSON.parse(text);
|
|
92
|
+
if (typeof parsed !== "object" || parsed === null)
|
|
93
|
+
return undefined;
|
|
94
|
+
const cost = parsed.total_cost_usd;
|
|
95
|
+
if (typeof cost !== "number" || !Number.isFinite(cost) || cost < 0)
|
|
96
|
+
return undefined;
|
|
97
|
+
return cost;
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
58
103
|
/** The non-empty string `result` of one JSON object, or undefined. */
|
|
59
104
|
function resultField(text) {
|
|
60
105
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"failure.js","sourceRoot":"","sources":["../../src/agent/failure.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"failure.js","sourceRoot":"","sources":["../../src/agent/failure.ts"],"names":[],"mappings":"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"}
|
package/dist/board/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { type BoardOptions, createBoardServer, isLoopback, normalizeToken, parseRoute, type Route, } from "./server.js";
|
|
1
|
+
export { type BoardOptions, createBoardServer, isLoopback, normalizeToken, parseRoute, type PrStatusReader, type Route, } from "./server.js";
|
|
2
2
|
export { PAGE_HTML } from "./page.js";
|
|
3
3
|
export { computeStats, type Stats } from "./stats.js";
|
|
4
4
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/board/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,YAAY,EACjB,iBAAiB,EACjB,UAAU,EACV,cAAc,EACd,UAAU,EACV,KAAK,KAAK,GACX,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,YAAY,EAAE,KAAK,KAAK,EAAE,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/board/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,YAAY,EACjB,iBAAiB,EACjB,UAAU,EACV,cAAc,EACd,UAAU,EACV,KAAK,cAAc,EACnB,KAAK,KAAK,GACX,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,YAAY,EAAE,KAAK,KAAK,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/board/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/board/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,iBAAiB,EACjB,UAAU,EACV,cAAc,EACd,UAAU,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/board/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,iBAAiB,EACjB,UAAU,EACV,cAAc,EACd,UAAU,GAGX,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,YAAY,EAAc,MAAM,YAAY,CAAC"}
|
package/dist/board/page.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
/** The self-contained board page: polls the read API and renders runs + stats. */
|
|
2
|
-
export declare const PAGE_HTML = "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\" />\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n<title>ghostrail board</title>\n<style>\n :root { color-scheme: light dark; }\n body { font: 14px/1.5 system-ui, sans-serif; margin: 0; padding: 1.5rem; }\n h1 { font-size: 1.1rem; margin: 0 0 1rem; }\n .stats { display: flex; gap: 1rem; flex-wrap: wrap; margin-bottom: 1rem; }\n .stat { border: 1px solid #8884; border-radius: 8px; padding: .5rem .75rem; }\n .stat b { display: block; font-size: 1.3rem; }\n table { border-collapse: collapse; width: 100%; }\n th, td { text-align: left; padding: .4rem .6rem; border-bottom: 1px solid #8883; }\n .state { font-weight: 600; }\n .done { color: #2a9d3a; } .failed { color: #d33; } .blocked { color: #d90; }\n .noop, .skipped { opacity: .6; } .muted { opacity: .6; }\n a { color: inherit; }\n</style>\n</head>\n<body>\n<h1>ghostrail board</h1>\n<div class=\"stats\" id=\"stats\"></div>\n<table><thead><tr><th>Run</th><th>Item</th><th>State</th><th>Backend</th><th>Gate</th><th>PR</th><th>When</th></tr></thead>\n<tbody id=\"rows\"></tbody></table>\n<script>\nconst esc = (s) => String(s ?? \"\").replace(/[&<>]/g, (c) => ({ \"&\": \"&\", \"<\": \"<\", \">\": \">\" }[c]));\nasync function refresh() {\n const [runs, stats] = await Promise.all([\n fetch(\"api/runs\").then((r) => r.json()),\n fetch(\"api/stats\").then((r) => r.json()),\n ]);\n document.getElementById(\"stats\").innerHTML = [\n [\"runs\", stats.total],\n [\"success\", (stats.successRate * 100).toFixed(0) + \"%\"],\n [\"PRs\", stats.prCount],\n ].map(([k, v]) => '<div class=\"stat\"><b>' + esc(v) + \"</b>\" + esc(k) + \"</div>\").join(\"\");\n document.getElementById(\"rows\").innerHTML = runs.map((r) => {\n const gate = (r.gate || []).map((s) => (s.exitCode === 0 ? \"\u2713\" : \"\u2717\")).join(\"\");\n const pr = r.prUrl ? '<a href=\"' + esc(r.prUrl) + '\">#' + esc(r.prNumber) + \"</a>\" : \"\";\n const when = r.startedAt ? new Date(r.startedAt).toLocaleString() : \"\";\n const state = esc(r.state || \"in-progress\");\n return \"<tr><td class=muted>\" + esc(r.runId.slice(0, 8)) + \"</td><td>\" + esc(r.title || r.itemId || \"\") +\n '</td><td class=\"state ' + state + '\">' + state + \"</td><td>\" + esc(r.backend || \"\") +\n \"</td><td>\" + gate + \"</td><td>\" + pr + \"</td><td class=muted>\" + esc(when) + \"</td></tr>\";\n }).join(\"\");\n}\nrefresh();\nsetInterval(refresh, 5000);\n</script>\n</body>\n</html>";
|
|
2
|
+
export declare const PAGE_HTML = "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\" />\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n<title>ghostrail board</title>\n<style>\n :root { color-scheme: light dark; }\n body { font: 14px/1.5 system-ui, sans-serif; margin: 0; padding: 1.5rem; }\n h1 { font-size: 1.1rem; margin: 0 0 1rem; }\n .stats { display: flex; gap: 1rem; flex-wrap: wrap; margin-bottom: 1rem; }\n .stat { border: 1px solid #8884; border-radius: 8px; padding: .5rem .75rem; }\n .stat b { display: block; font-size: 1.3rem; }\n table { border-collapse: collapse; width: 100%; }\n th, td { text-align: left; padding: .4rem .6rem; border-bottom: 1px solid #8883; }\n .state { font-weight: 600; }\n .done { color: #2a9d3a; } .failed { color: #d33; } .blocked { color: #d90; }\n .noop, .skipped { opacity: .6; } .muted { opacity: .6; }\n a { color: inherit; }\n .twirl {\n background: none; border: none; cursor: pointer; font: inherit; padding: 0 .3rem;\n display: inline-block; transition: transform .15s ease;\n }\n .twirl.open { transform: rotate(90deg); }\n .detail-cell { padding: .75rem 1.5rem; background: #8881; }\n .detail-cell > div { margin-bottom: .5rem; }\n .detail-cell pre { white-space: pre-wrap; word-break: break-word; margin: .25rem 0 0; font: inherit; }\n .subtable { border-collapse: collapse; margin-top: .25rem; }\n .subtable td { padding: .15rem .5rem .15rem 0; border-bottom: none; }\n .detail-cell ul { margin: .25rem 0 0; padding-left: 1.2rem; }\n .toggle { font-size: .8rem; font-weight: normal; margin-left: .75rem; }\n</style>\n</head>\n<body>\n<h1>ghostrail board <label class=\"toggle\"><input type=\"checkbox\" id=\"running\" /> running</label></h1>\n<div class=\"stats\" id=\"stats\"></div>\n<table><thead><tr><th></th><th>Run</th><th>Item</th><th>State</th><th>Backend</th><th>Gate</th><th>PR</th><th>When</th></tr></thead>\n<tbody id=\"rows\"></tbody></table>\n<script>\nconst esc = (s) => String(s ?? \"\").replace(/[&<>\"]/g, (c) => (\n { \"&\": \"&\", \"<\": \"<\", \">\": \">\", '\"': \""\" }[c]\n));\nconst expanded = new Set();\nconst detailHtml = new Map();\nconst prStatusCache = new Map();\n\nfunction fmtWhen(ts) {\n return ts ? new Date(ts).toLocaleString() : \"\";\n}\n\nfunction gateStepsHtml(gate) {\n if (!gate || gate.length === 0) return \"<div class=muted>no gate steps</div>\";\n const rows = gate.map((s) =>\n \"<tr><td>\" + esc(s.name) + \"</td><td class='\" + (s.exitCode === 0 ? \"done\" : \"failed\") + \"'>\" +\n (s.exitCode === 0 ? \"passed\" : \"failed (exit \" + esc(s.exitCode) + \")\") + \"</td><td class=muted>\" +\n esc(s.durationMs) + \"ms</td></tr>\"\n ).join(\"\");\n return \"<table class=subtable>\" + rows + \"</table>\";\n}\n\nfunction artifactsHtml(artifacts) {\n if (!artifacts || artifacts.length === 0) return \"\";\n const items = artifacts.map((a) =>\n '<li><a href=\"api/artifacts/' + esc(a.hash) + '\">' + esc(a.name) + \"</a></li>\"\n ).join(\"\");\n return \"<div><b>Artifacts</b><ul>\" + items + \"</ul></div>\";\n}\n\nfunction checkLineHtml(c) {\n const label = c.conclusion || c.status;\n const cls = c.conclusion === \"success\" ? \"done\" : c.conclusion ? \"failed\" : \"muted\";\n const name = c.url ? '<a href=\"' + esc(c.url) + '\">' + esc(c.name) + \"</a>\" : esc(c.name);\n return \"<li>\" + name + \": <span class='\" + cls + \"'>\" + esc(label) + \"</span></li>\";\n}\n\nfunction prStatusHtml(status) {\n if (!status) return \"<div class=muted>loading CI status\u2026</div>\";\n if (!status.available) {\n return \"<div class=muted>PR status unknown\" + (status.error ? \": \" + esc(status.error) : \"\") + \"</div>\";\n }\n const merged = status.state === \"merged\";\n const parts = [\n \"<div><b>PR state:</b> \" + esc(status.state) +\n (merged && status.mergedAt ? \" at \" + esc(new Date(status.mergedAt).toLocaleString()) : \"\") + \"</div>\",\n ];\n if (status.checks && status.checks.length > 0) {\n parts.push(\"<div><b>CI checks</b><ul>\" + status.checks.map(checkLineHtml).join(\"\") + \"</ul></div>\");\n } else {\n parts.push(\"<div class=muted>no CI checks reported</div>\");\n }\n return parts.join(\"\");\n}\n\nfunction detailBodyHtml(row) {\n const parts = [];\n parts.push(\"<div><b>Item</b> \" + esc(row.itemId || \"\") + (row.kind ? \" (\" + esc(row.kind) + \")\" : \"\") + \"</div>\");\n if (row.branch) parts.push(\"<div><b>Branch</b> \" + esc(row.branch) + \"</div>\");\n if (row.costUsd !== undefined) {\n parts.push(\"<div><b>Cost</b> $\" + esc(Number(row.costUsd).toFixed(4)) + \"</div>\");\n }\n if (row.tokensIn !== undefined || row.tokensOut !== undefined) {\n parts.push(\"<div><b>Tokens</b> in \" + esc(row.tokensIn ?? 0) + \" / out \" + esc(row.tokensOut ?? 0) + \"</div>\");\n }\n parts.push(\"<div><b>Gate</b>\" + gateStepsHtml(row.gate) + \"</div>\");\n if (row.error) parts.push(\"<div><b>Error</b><pre class=failed>\" + esc(row.error) + \"</pre></div>\");\n if (row.summary) parts.push(\"<div><b>Comment</b><pre>\" + esc(row.summary) + \"</pre></div>\");\n if (row.prUrl) {\n parts.push('<div><b>PR</b> <a href=\"' + esc(row.prUrl) + '\">' + esc(row.prUrl) + \"</a></div>\");\n }\n parts.push(artifactsHtml(row.artifacts));\n parts.push(\n \"<div class=muted>started \" + esc(fmtWhen(row.startedAt)) +\n \", finished \" + esc(fmtWhen(row.finishedAt)) + \"</div>\",\n );\n return parts.join(\"\");\n}\n\nasync function loadDetail(runId) {\n let row;\n try {\n const res = await fetch(\"api/runs/\" + encodeURIComponent(runId));\n if (!res.ok) return;\n row = (await res.json()).row;\n } catch {\n return;\n }\n let html = detailBodyHtml(row);\n if (row.prNumber !== undefined) {\n html += '<div class=\"pr-status\" data-run-id=\"' + esc(runId) + '\">' +\n prStatusHtml(prStatusCache.get(runId)) + \"</div>\";\n }\n detailHtml.set(runId, html);\n paintDetail(runId);\n if (row.prNumber !== undefined) await loadPrStatus(runId);\n}\n\nasync function loadPrStatus(runId) {\n let status;\n try {\n status = await (await fetch(\"api/runs/\" + encodeURIComponent(runId) + \"/pr-status\")).json();\n } catch {\n return;\n }\n prStatusCache.set(runId, status);\n const el = document.querySelector('.pr-status[data-run-id=\"' + runId + '\"]');\n if (el) el.innerHTML = prStatusHtml(status);\n}\n\nfunction paintDetail(runId) {\n const cell = document.getElementById(\"detail-\" + runId);\n if (cell) cell.innerHTML = detailHtml.get(runId) || \"<div class=muted>loading\u2026</div>\";\n}\n\nfunction toggle(runId) {\n if (expanded.has(runId)) {\n expanded.delete(runId);\n } else {\n expanded.add(runId);\n if (!detailHtml.has(runId)) loadDetail(runId);\n }\n const btn = document.querySelector('.twirl[data-run-id=\"' + runId + '\"]');\n if (btn) btn.classList.toggle(\"open\", expanded.has(runId));\n const detailRow = document.getElementById(\"detail-row-\" + runId);\n if (detailRow) detailRow.style.display = expanded.has(runId) ? \"table-row\" : \"none\";\n if (expanded.has(runId)) paintDetail(runId);\n}\n\nfunction rowHtml(r) {\n const isOpen = expanded.has(r.runId);\n const gate = (r.gate || []).map((s) => (s.exitCode === 0 ? \"\u2713\" : \"\u2717\")).join(\"\");\n const pr = r.prUrl ? '<a href=\"' + esc(r.prUrl) + '\">#' + esc(r.prNumber) + \"</a>\" : \"\";\n const when = fmtWhen(r.startedAt);\n const state = esc(r.state || \"in-progress\");\n const main = \"<tr class=run-row>\" +\n \"<td><button class='twirl\" + (isOpen ? \" open\" : \"\") + \"' data-run-id='\" + esc(r.runId) + \"'>\u25B6</button></td>\" +\n \"<td class=muted>\" + esc(r.runId.slice(0, 8)) + \"</td>\" +\n \"<td>\" + esc(r.title || r.itemId || \"\") + \"</td>\" +\n \"<td class='state \" + state + \"'>\" + state + \"</td>\" +\n \"<td>\" + esc(r.backend || \"\") + \"</td>\" +\n \"<td>\" + gate + \"</td>\" +\n \"<td>\" + pr + \"</td>\" +\n \"<td class=muted>\" + esc(when) + \"</td></tr>\";\n const cellContent = isOpen ? (detailHtml.get(r.runId) || \"<div class=muted>loading\u2026</div>\") : \"\";\n const detail = \"<tr class=detail-row id='detail-row-\" + esc(r.runId) + \"'\" +\n \" style='display:\" + (isOpen ? \"table-row\" : \"none\") + \"'>\" +\n \"<td colspan=8 class=detail-cell id='detail-\" + esc(r.runId) + \"'>\" + cellContent + \"</td></tr>\";\n return main + detail;\n}\n\ndocument.getElementById(\"rows\").addEventListener(\"click\", (e) => {\n const btn = e.target.closest(\".twirl\");\n if (btn) toggle(btn.dataset.runId);\n});\n\nconst runningBox = document.getElementById(\"running\");\nlet toggling = false;\nrunningBox.addEventListener(\"change\", async () => {\n const wanted = runningBox.checked;\n toggling = true;\n runningBox.disabled = true;\n try {\n const res = await fetch(\"api/control\", {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ enabled: wanted }),\n });\n if (!res.ok) throw new Error(\"toggle failed\");\n const state = await res.json();\n runningBox.checked = state.enabled;\n } catch {\n runningBox.checked = !wanted;\n } finally {\n runningBox.disabled = false;\n toggling = false;\n }\n});\nasync function refresh() {\n const [runs, stats, control] = await Promise.all([\n fetch(\"api/runs\").then((r) => r.json()),\n fetch(\"api/stats\").then((r) => r.json()),\n fetch(\"api/control\").then((r) => r.json()),\n ]);\n if (!toggling) runningBox.checked = control.enabled;\n document.getElementById(\"stats\").innerHTML = [\n [\"runs\", stats.total],\n [\"success\", (stats.successRate * 100).toFixed(0) + \"%\"],\n [\"PRs\", stats.prCount],\n ].map(([k, v]) => '<div class=\"stat\"><b>' + esc(v) + \"</b>\" + esc(k) + \"</div>\").join(\"\");\n document.getElementById(\"rows\").innerHTML = runs.map(rowHtml).join(\"\");\n for (const runId of expanded) loadDetail(runId);\n}\nrefresh();\nsetInterval(refresh, 5000);\n</script>\n</body>\n</html>";
|
|
3
3
|
//# sourceMappingURL=page.d.ts.map
|
package/dist/board/page.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../../src/board/page.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,eAAO,MAAM,SAAS,
|
|
1
|
+
{"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../../src/board/page.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,eAAO,MAAM,SAAS,w2TA2Od,CAAC"}
|
package/dist/board/page.js
CHANGED
|
@@ -18,34 +18,217 @@ export const PAGE_HTML = `<!doctype html>
|
|
|
18
18
|
.done { color: #2a9d3a; } .failed { color: #d33; } .blocked { color: #d90; }
|
|
19
19
|
.noop, .skipped { opacity: .6; } .muted { opacity: .6; }
|
|
20
20
|
a { color: inherit; }
|
|
21
|
+
.twirl {
|
|
22
|
+
background: none; border: none; cursor: pointer; font: inherit; padding: 0 .3rem;
|
|
23
|
+
display: inline-block; transition: transform .15s ease;
|
|
24
|
+
}
|
|
25
|
+
.twirl.open { transform: rotate(90deg); }
|
|
26
|
+
.detail-cell { padding: .75rem 1.5rem; background: #8881; }
|
|
27
|
+
.detail-cell > div { margin-bottom: .5rem; }
|
|
28
|
+
.detail-cell pre { white-space: pre-wrap; word-break: break-word; margin: .25rem 0 0; font: inherit; }
|
|
29
|
+
.subtable { border-collapse: collapse; margin-top: .25rem; }
|
|
30
|
+
.subtable td { padding: .15rem .5rem .15rem 0; border-bottom: none; }
|
|
31
|
+
.detail-cell ul { margin: .25rem 0 0; padding-left: 1.2rem; }
|
|
32
|
+
.toggle { font-size: .8rem; font-weight: normal; margin-left: .75rem; }
|
|
21
33
|
</style>
|
|
22
34
|
</head>
|
|
23
35
|
<body>
|
|
24
|
-
<h1>ghostrail board</h1>
|
|
36
|
+
<h1>ghostrail board <label class="toggle"><input type="checkbox" id="running" /> running</label></h1>
|
|
25
37
|
<div class="stats" id="stats"></div>
|
|
26
|
-
<table><thead><tr><th>Run</th><th>Item</th><th>State</th><th>Backend</th><th>Gate</th><th>PR</th><th>When</th></tr></thead>
|
|
38
|
+
<table><thead><tr><th></th><th>Run</th><th>Item</th><th>State</th><th>Backend</th><th>Gate</th><th>PR</th><th>When</th></tr></thead>
|
|
27
39
|
<tbody id="rows"></tbody></table>
|
|
28
40
|
<script>
|
|
29
|
-
const esc = (s) => String(s ?? "").replace(/[&<>]/g, (c) => (
|
|
41
|
+
const esc = (s) => String(s ?? "").replace(/[&<>"]/g, (c) => (
|
|
42
|
+
{ "&": "&", "<": "<", ">": ">", '"': """ }[c]
|
|
43
|
+
));
|
|
44
|
+
const expanded = new Set();
|
|
45
|
+
const detailHtml = new Map();
|
|
46
|
+
const prStatusCache = new Map();
|
|
47
|
+
|
|
48
|
+
function fmtWhen(ts) {
|
|
49
|
+
return ts ? new Date(ts).toLocaleString() : "";
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function gateStepsHtml(gate) {
|
|
53
|
+
if (!gate || gate.length === 0) return "<div class=muted>no gate steps</div>";
|
|
54
|
+
const rows = gate.map((s) =>
|
|
55
|
+
"<tr><td>" + esc(s.name) + "</td><td class='" + (s.exitCode === 0 ? "done" : "failed") + "'>" +
|
|
56
|
+
(s.exitCode === 0 ? "passed" : "failed (exit " + esc(s.exitCode) + ")") + "</td><td class=muted>" +
|
|
57
|
+
esc(s.durationMs) + "ms</td></tr>"
|
|
58
|
+
).join("");
|
|
59
|
+
return "<table class=subtable>" + rows + "</table>";
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function artifactsHtml(artifacts) {
|
|
63
|
+
if (!artifacts || artifacts.length === 0) return "";
|
|
64
|
+
const items = artifacts.map((a) =>
|
|
65
|
+
'<li><a href="api/artifacts/' + esc(a.hash) + '">' + esc(a.name) + "</a></li>"
|
|
66
|
+
).join("");
|
|
67
|
+
return "<div><b>Artifacts</b><ul>" + items + "</ul></div>";
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function checkLineHtml(c) {
|
|
71
|
+
const label = c.conclusion || c.status;
|
|
72
|
+
const cls = c.conclusion === "success" ? "done" : c.conclusion ? "failed" : "muted";
|
|
73
|
+
const name = c.url ? '<a href="' + esc(c.url) + '">' + esc(c.name) + "</a>" : esc(c.name);
|
|
74
|
+
return "<li>" + name + ": <span class='" + cls + "'>" + esc(label) + "</span></li>";
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function prStatusHtml(status) {
|
|
78
|
+
if (!status) return "<div class=muted>loading CI status…</div>";
|
|
79
|
+
if (!status.available) {
|
|
80
|
+
return "<div class=muted>PR status unknown" + (status.error ? ": " + esc(status.error) : "") + "</div>";
|
|
81
|
+
}
|
|
82
|
+
const merged = status.state === "merged";
|
|
83
|
+
const parts = [
|
|
84
|
+
"<div><b>PR state:</b> " + esc(status.state) +
|
|
85
|
+
(merged && status.mergedAt ? " at " + esc(new Date(status.mergedAt).toLocaleString()) : "") + "</div>",
|
|
86
|
+
];
|
|
87
|
+
if (status.checks && status.checks.length > 0) {
|
|
88
|
+
parts.push("<div><b>CI checks</b><ul>" + status.checks.map(checkLineHtml).join("") + "</ul></div>");
|
|
89
|
+
} else {
|
|
90
|
+
parts.push("<div class=muted>no CI checks reported</div>");
|
|
91
|
+
}
|
|
92
|
+
return parts.join("");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function detailBodyHtml(row) {
|
|
96
|
+
const parts = [];
|
|
97
|
+
parts.push("<div><b>Item</b> " + esc(row.itemId || "") + (row.kind ? " (" + esc(row.kind) + ")" : "") + "</div>");
|
|
98
|
+
if (row.branch) parts.push("<div><b>Branch</b> " + esc(row.branch) + "</div>");
|
|
99
|
+
if (row.costUsd !== undefined) {
|
|
100
|
+
parts.push("<div><b>Cost</b> $" + esc(Number(row.costUsd).toFixed(4)) + "</div>");
|
|
101
|
+
}
|
|
102
|
+
if (row.tokensIn !== undefined || row.tokensOut !== undefined) {
|
|
103
|
+
parts.push("<div><b>Tokens</b> in " + esc(row.tokensIn ?? 0) + " / out " + esc(row.tokensOut ?? 0) + "</div>");
|
|
104
|
+
}
|
|
105
|
+
parts.push("<div><b>Gate</b>" + gateStepsHtml(row.gate) + "</div>");
|
|
106
|
+
if (row.error) parts.push("<div><b>Error</b><pre class=failed>" + esc(row.error) + "</pre></div>");
|
|
107
|
+
if (row.summary) parts.push("<div><b>Comment</b><pre>" + esc(row.summary) + "</pre></div>");
|
|
108
|
+
if (row.prUrl) {
|
|
109
|
+
parts.push('<div><b>PR</b> <a href="' + esc(row.prUrl) + '">' + esc(row.prUrl) + "</a></div>");
|
|
110
|
+
}
|
|
111
|
+
parts.push(artifactsHtml(row.artifacts));
|
|
112
|
+
parts.push(
|
|
113
|
+
"<div class=muted>started " + esc(fmtWhen(row.startedAt)) +
|
|
114
|
+
", finished " + esc(fmtWhen(row.finishedAt)) + "</div>",
|
|
115
|
+
);
|
|
116
|
+
return parts.join("");
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function loadDetail(runId) {
|
|
120
|
+
let row;
|
|
121
|
+
try {
|
|
122
|
+
const res = await fetch("api/runs/" + encodeURIComponent(runId));
|
|
123
|
+
if (!res.ok) return;
|
|
124
|
+
row = (await res.json()).row;
|
|
125
|
+
} catch {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
let html = detailBodyHtml(row);
|
|
129
|
+
if (row.prNumber !== undefined) {
|
|
130
|
+
html += '<div class="pr-status" data-run-id="' + esc(runId) + '">' +
|
|
131
|
+
prStatusHtml(prStatusCache.get(runId)) + "</div>";
|
|
132
|
+
}
|
|
133
|
+
detailHtml.set(runId, html);
|
|
134
|
+
paintDetail(runId);
|
|
135
|
+
if (row.prNumber !== undefined) await loadPrStatus(runId);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function loadPrStatus(runId) {
|
|
139
|
+
let status;
|
|
140
|
+
try {
|
|
141
|
+
status = await (await fetch("api/runs/" + encodeURIComponent(runId) + "/pr-status")).json();
|
|
142
|
+
} catch {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
prStatusCache.set(runId, status);
|
|
146
|
+
const el = document.querySelector('.pr-status[data-run-id="' + runId + '"]');
|
|
147
|
+
if (el) el.innerHTML = prStatusHtml(status);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function paintDetail(runId) {
|
|
151
|
+
const cell = document.getElementById("detail-" + runId);
|
|
152
|
+
if (cell) cell.innerHTML = detailHtml.get(runId) || "<div class=muted>loading…</div>";
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function toggle(runId) {
|
|
156
|
+
if (expanded.has(runId)) {
|
|
157
|
+
expanded.delete(runId);
|
|
158
|
+
} else {
|
|
159
|
+
expanded.add(runId);
|
|
160
|
+
if (!detailHtml.has(runId)) loadDetail(runId);
|
|
161
|
+
}
|
|
162
|
+
const btn = document.querySelector('.twirl[data-run-id="' + runId + '"]');
|
|
163
|
+
if (btn) btn.classList.toggle("open", expanded.has(runId));
|
|
164
|
+
const detailRow = document.getElementById("detail-row-" + runId);
|
|
165
|
+
if (detailRow) detailRow.style.display = expanded.has(runId) ? "table-row" : "none";
|
|
166
|
+
if (expanded.has(runId)) paintDetail(runId);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function rowHtml(r) {
|
|
170
|
+
const isOpen = expanded.has(r.runId);
|
|
171
|
+
const gate = (r.gate || []).map((s) => (s.exitCode === 0 ? "✓" : "✗")).join("");
|
|
172
|
+
const pr = r.prUrl ? '<a href="' + esc(r.prUrl) + '">#' + esc(r.prNumber) + "</a>" : "";
|
|
173
|
+
const when = fmtWhen(r.startedAt);
|
|
174
|
+
const state = esc(r.state || "in-progress");
|
|
175
|
+
const main = "<tr class=run-row>" +
|
|
176
|
+
"<td><button class='twirl" + (isOpen ? " open" : "") + "' data-run-id='" + esc(r.runId) + "'>▶</button></td>" +
|
|
177
|
+
"<td class=muted>" + esc(r.runId.slice(0, 8)) + "</td>" +
|
|
178
|
+
"<td>" + esc(r.title || r.itemId || "") + "</td>" +
|
|
179
|
+
"<td class='state " + state + "'>" + state + "</td>" +
|
|
180
|
+
"<td>" + esc(r.backend || "") + "</td>" +
|
|
181
|
+
"<td>" + gate + "</td>" +
|
|
182
|
+
"<td>" + pr + "</td>" +
|
|
183
|
+
"<td class=muted>" + esc(when) + "</td></tr>";
|
|
184
|
+
const cellContent = isOpen ? (detailHtml.get(r.runId) || "<div class=muted>loading…</div>") : "";
|
|
185
|
+
const detail = "<tr class=detail-row id='detail-row-" + esc(r.runId) + "'" +
|
|
186
|
+
" style='display:" + (isOpen ? "table-row" : "none") + "'>" +
|
|
187
|
+
"<td colspan=8 class=detail-cell id='detail-" + esc(r.runId) + "'>" + cellContent + "</td></tr>";
|
|
188
|
+
return main + detail;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
document.getElementById("rows").addEventListener("click", (e) => {
|
|
192
|
+
const btn = e.target.closest(".twirl");
|
|
193
|
+
if (btn) toggle(btn.dataset.runId);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
const runningBox = document.getElementById("running");
|
|
197
|
+
let toggling = false;
|
|
198
|
+
runningBox.addEventListener("change", async () => {
|
|
199
|
+
const wanted = runningBox.checked;
|
|
200
|
+
toggling = true;
|
|
201
|
+
runningBox.disabled = true;
|
|
202
|
+
try {
|
|
203
|
+
const res = await fetch("api/control", {
|
|
204
|
+
method: "POST",
|
|
205
|
+
headers: { "content-type": "application/json" },
|
|
206
|
+
body: JSON.stringify({ enabled: wanted }),
|
|
207
|
+
});
|
|
208
|
+
if (!res.ok) throw new Error("toggle failed");
|
|
209
|
+
const state = await res.json();
|
|
210
|
+
runningBox.checked = state.enabled;
|
|
211
|
+
} catch {
|
|
212
|
+
runningBox.checked = !wanted;
|
|
213
|
+
} finally {
|
|
214
|
+
runningBox.disabled = false;
|
|
215
|
+
toggling = false;
|
|
216
|
+
}
|
|
217
|
+
});
|
|
30
218
|
async function refresh() {
|
|
31
|
-
const [runs, stats] = await Promise.all([
|
|
219
|
+
const [runs, stats, control] = await Promise.all([
|
|
32
220
|
fetch("api/runs").then((r) => r.json()),
|
|
33
221
|
fetch("api/stats").then((r) => r.json()),
|
|
222
|
+
fetch("api/control").then((r) => r.json()),
|
|
34
223
|
]);
|
|
224
|
+
if (!toggling) runningBox.checked = control.enabled;
|
|
35
225
|
document.getElementById("stats").innerHTML = [
|
|
36
226
|
["runs", stats.total],
|
|
37
227
|
["success", (stats.successRate * 100).toFixed(0) + "%"],
|
|
38
228
|
["PRs", stats.prCount],
|
|
39
229
|
].map(([k, v]) => '<div class="stat"><b>' + esc(v) + "</b>" + esc(k) + "</div>").join("");
|
|
40
|
-
document.getElementById("rows").innerHTML = runs.map((
|
|
41
|
-
|
|
42
|
-
const pr = r.prUrl ? '<a href="' + esc(r.prUrl) + '">#' + esc(r.prNumber) + "</a>" : "";
|
|
43
|
-
const when = r.startedAt ? new Date(r.startedAt).toLocaleString() : "";
|
|
44
|
-
const state = esc(r.state || "in-progress");
|
|
45
|
-
return "<tr><td class=muted>" + esc(r.runId.slice(0, 8)) + "</td><td>" + esc(r.title || r.itemId || "") +
|
|
46
|
-
'</td><td class="state ' + state + '">' + state + "</td><td>" + esc(r.backend || "") +
|
|
47
|
-
"</td><td>" + gate + "</td><td>" + pr + "</td><td class=muted>" + esc(when) + "</td></tr>";
|
|
48
|
-
}).join("");
|
|
230
|
+
document.getElementById("rows").innerHTML = runs.map(rowHtml).join("");
|
|
231
|
+
for (const runId of expanded) loadDetail(runId);
|
|
49
232
|
}
|
|
50
233
|
refresh();
|
|
51
234
|
setInterval(refresh, 5000);
|
package/dist/board/page.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"page.js","sourceRoot":"","sources":["../../src/board/page.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,MAAM,CAAC,MAAM,SAAS,GAAG
|
|
1
|
+
{"version":3,"file":"page.js","sourceRoot":"","sources":["../../src/board/page.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,MAAM,CAAC,MAAM,SAAS,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QA2OjB,CAAC"}
|
package/dist/board/server.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type Server } from "node:http";
|
|
2
|
+
import type { PrStatus } from "../publish/githost.js";
|
|
2
3
|
import type { FileStore } from "../store/file-store.js";
|
|
3
4
|
export type Route = {
|
|
4
5
|
kind: "page";
|
|
@@ -10,16 +11,25 @@ export type Route = {
|
|
|
10
11
|
} | {
|
|
11
12
|
kind: "events";
|
|
12
13
|
id: string;
|
|
14
|
+
} | {
|
|
15
|
+
kind: "prStatus";
|
|
16
|
+
id: string;
|
|
13
17
|
} | {
|
|
14
18
|
kind: "stats";
|
|
15
19
|
} | {
|
|
16
20
|
kind: "artifact";
|
|
17
21
|
hash: string;
|
|
22
|
+
} | {
|
|
23
|
+
kind: "control";
|
|
18
24
|
} | {
|
|
19
25
|
kind: "notfound";
|
|
20
26
|
};
|
|
21
27
|
/** Map a request pathname to a board route. Pure. */
|
|
22
28
|
export declare function parseRoute(pathname: string): Route;
|
|
29
|
+
/** The narrow read the board needs to show live PR/CI status; a `GitHost` satisfies it. */
|
|
30
|
+
export interface PrStatusReader {
|
|
31
|
+
getPrStatus(prNumber: number): Promise<PrStatus>;
|
|
32
|
+
}
|
|
23
33
|
export interface BoardOptions {
|
|
24
34
|
/** Address to bind. Defaults to loopback. */
|
|
25
35
|
bind?: string;
|
|
@@ -32,14 +42,18 @@ export interface BoardOptions {
|
|
|
32
42
|
* who can reach the host.
|
|
33
43
|
*/
|
|
34
44
|
allowNoToken?: boolean;
|
|
45
|
+
/** Optional live PR/CI status reader. Without it, `/api/runs/:id/pr-status` reports unavailable. */
|
|
46
|
+
gitHost?: PrStatusReader;
|
|
35
47
|
}
|
|
36
48
|
/** True for an address only this machine can reach, where a token is optional. */
|
|
37
49
|
export declare function isLoopback(bind: string): boolean;
|
|
38
50
|
/**
|
|
39
|
-
* Build the board's HTTP server over the store: a static page plus a small
|
|
40
|
-
* API (`/api/runs`, `/api/runs/:id`, `/api/runs/:id/events`,
|
|
41
|
-
* `/api/artifacts/:hash`)
|
|
42
|
-
*
|
|
51
|
+
* Build the board's HTTP server over the store: a static page plus a small
|
|
52
|
+
* read API (`/api/runs`, `/api/runs/:id`, `/api/runs/:id/events`,
|
|
53
|
+
* `/api/stats`, `/api/artifacts/:hash`) and one read/write route,
|
|
54
|
+
* `/api/control`, that starts/stops the ghostrail this board serves. When a
|
|
55
|
+
* token is configured (required whenever the bind is not loopback), every
|
|
56
|
+
* request must present it.
|
|
43
57
|
*/
|
|
44
58
|
export declare function createBoardServer(store: FileStore, options?: BoardOptions): Server;
|
|
45
59
|
/** A token that is absent, empty, or only whitespace is no token at all. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/board/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwB,KAAK,MAAM,EAAqC,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/board/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwB,KAAK,MAAM,EAAqC,MAAM,WAAW,CAAC;AAEjG,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,KAAK,EAAE,SAAS,EAAa,MAAM,wBAAwB,CAAC;AAInE,MAAM,MAAM,KAAK,GACb;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAC3B;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAChC;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,GACjB;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,CAAC;AAEzB,qDAAqD;AACrD,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,KAAK,CAmBlD;AAED,2FAA2F;AAC3F,MAAM,WAAW,cAAc;IAC7B,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CAClD;AAuBD,MAAM,WAAW,YAAY;IAC3B,6CAA6C;IAC7C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,4FAA4F;IAC5F,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,oGAAoG;IACpG,OAAO,CAAC,EAAE,cAAc,CAAC;CAC1B;AAED,kFAAkF;AAClF,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEhD;AAQD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,GAAE,YAAiB,GAAG,MAAM,CAqBtF;AAED,4EAA4E;AAC5E;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAG5E"}
|