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.
Files changed (71) hide show
  1. package/dist/agent/claude-code.d.ts.map +1 -1
  2. package/dist/agent/claude-code.js +15 -5
  3. package/dist/agent/claude-code.js.map +1 -1
  4. package/dist/agent/failure.d.ts +19 -0
  5. package/dist/agent/failure.d.ts.map +1 -1
  6. package/dist/agent/failure.js +45 -0
  7. package/dist/agent/failure.js.map +1 -1
  8. package/dist/board/index.d.ts +1 -1
  9. package/dist/board/index.d.ts.map +1 -1
  10. package/dist/board/index.js.map +1 -1
  11. package/dist/board/page.d.ts +1 -1
  12. package/dist/board/page.d.ts.map +1 -1
  13. package/dist/board/page.js +196 -13
  14. package/dist/board/page.js.map +1 -1
  15. package/dist/board/server.d.ts +18 -4
  16. package/dist/board/server.d.ts.map +1 -1
  17. package/dist/board/server.js +78 -4
  18. package/dist/board/server.js.map +1 -1
  19. package/dist/cli/commands.d.ts.map +1 -1
  20. package/dist/cli/commands.js +19 -2
  21. package/dist/cli/commands.js.map +1 -1
  22. package/dist/loop/budget.d.ts +35 -0
  23. package/dist/loop/budget.d.ts.map +1 -0
  24. package/dist/loop/budget.js +42 -0
  25. package/dist/loop/budget.js.map +1 -0
  26. package/dist/loop/control.d.ts +15 -0
  27. package/dist/loop/control.d.ts.map +1 -0
  28. package/dist/loop/control.js +30 -0
  29. package/dist/loop/control.js.map +1 -0
  30. package/dist/loop/engine.d.ts.map +1 -1
  31. package/dist/loop/engine.js +22 -2
  32. package/dist/loop/engine.js.map +1 -1
  33. package/dist/loop/index.d.ts +1 -0
  34. package/dist/loop/index.d.ts.map +1 -1
  35. package/dist/loop/index.js +1 -0
  36. package/dist/loop/index.js.map +1 -1
  37. package/dist/loop/ports.d.ts +12 -2
  38. package/dist/loop/ports.d.ts.map +1 -1
  39. package/dist/loop/watch.d.ts +7 -0
  40. package/dist/loop/watch.d.ts.map +1 -1
  41. package/dist/loop/watch.js +14 -0
  42. package/dist/loop/watch.js.map +1 -1
  43. package/dist/publish/gh.d.ts +4 -1
  44. package/dist/publish/gh.d.ts.map +1 -1
  45. package/dist/publish/gh.js +58 -0
  46. package/dist/publish/gh.js.map +1 -1
  47. package/dist/publish/githost.d.ts +19 -0
  48. package/dist/publish/githost.d.ts.map +1 -1
  49. package/dist/publish/githost.js.map +1 -1
  50. package/dist/publish/index.d.ts +2 -2
  51. package/dist/publish/index.d.ts.map +1 -1
  52. package/dist/publish/index.js +1 -1
  53. package/dist/publish/index.js.map +1 -1
  54. package/dist/respond/respond.d.ts.map +1 -1
  55. package/dist/respond/respond.js +24 -4
  56. package/dist/respond/respond.js.map +1 -1
  57. package/dist/respond/store.d.ts +12 -0
  58. package/dist/respond/store.d.ts.map +1 -1
  59. package/dist/respond/store.js +22 -1
  60. package/dist/respond/store.js.map +1 -1
  61. package/dist/store/events.d.ts +3 -0
  62. package/dist/store/events.d.ts.map +1 -1
  63. package/dist/store/events.js +2 -0
  64. package/dist/store/events.js.map +1 -1
  65. package/dist/store/file-store.d.ts +2 -0
  66. package/dist/store/file-store.d.ts.map +1 -1
  67. package/dist/store/file-store.js +3 -0
  68. package/dist/store/file-store.js.map +1 -1
  69. package/package.json +1 -1
  70. package/templates/code-local/ghostrail.toml +4 -0
  71. 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,EAAE,MAAM,kBAAkB,CAAC;AAK1E,+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;CA6DxD"}
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":"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"}
1
+ {"version":3,"file":"claude-code.js","sourceRoot":"","sources":["../../src/agent/claude-code.ts"],"names":[],"mappings":"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"}
@@ -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;AAoBD;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAK7F"}
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"}
@@ -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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"}
@@ -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"}
@@ -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,GAEX,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,YAAY,EAAc,MAAM,YAAY,CAAC"}
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"}
@@ -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) => ({ \"&\": \"&amp;\", \"<\": \"&lt;\", \">\": \"&gt;\" }[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 { \"&\": \"&amp;\", \"<\": \"&lt;\", \">\": \"&gt;\", '\"': \"&quot;\" }[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
@@ -1 +1 @@
1
- {"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../../src/board/page.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,eAAO,MAAM,SAAS,ugFAoDd,CAAC"}
1
+ {"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../../src/board/page.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,eAAO,MAAM,SAAS,w2TA2Od,CAAC"}
@@ -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) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;" }[c]));
41
+ const esc = (s) => String(s ?? "").replace(/[&<>"]/g, (c) => (
42
+ { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" }[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((r) => {
41
- const gate = (r.gate || []).map((s) => (s.exitCode === 0 ? "✓" : "✗")).join("");
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);
@@ -1 +1 @@
1
- {"version":3,"file":"page.js","sourceRoot":"","sources":["../../src/board/page.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,MAAM,CAAC,MAAM,SAAS,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAoDjB,CAAC"}
1
+ {"version":3,"file":"page.js","sourceRoot":"","sources":["../../src/board/page.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,MAAM,CAAC,MAAM,SAAS,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QA2OjB,CAAC"}
@@ -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 read
40
- * API (`/api/runs`, `/api/runs/:id`, `/api/runs/:id/events`, `/api/stats`,
41
- * `/api/artifacts/:hash`). When a token is configured (required whenever the
42
- * bind is not loopback), every request must present it.
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;AACjG,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,OAAO,CAAA;CAAE,GACjB;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,CAAC;AAEzB,qDAAqD;AACrD,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,KAAK,CAiBlD;AAED,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;CACxB;AAED,kFAAkF;AAClF,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEhD;AAQD;;;;;GAKG;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"}
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"}