ghostrail 0.9.0 → 0.11.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 (43) hide show
  1. package/dist/cli/commands.d.ts +18 -0
  2. package/dist/cli/commands.d.ts.map +1 -1
  3. package/dist/cli/commands.js +128 -3
  4. package/dist/cli/commands.js.map +1 -1
  5. package/dist/cli/prompt.d.ts +7 -4
  6. package/dist/cli/prompt.d.ts.map +1 -1
  7. package/dist/cli/prompt.js +6 -4
  8. package/dist/cli/prompt.js.map +1 -1
  9. package/dist/cli.d.ts.map +1 -1
  10. package/dist/cli.js +3 -1
  11. package/dist/cli.js.map +1 -1
  12. package/dist/commands/help.d.ts.map +1 -1
  13. package/dist/commands/help.js +14 -0
  14. package/dist/commands/help.js.map +1 -1
  15. package/dist/dashboard/index.d.ts +4 -0
  16. package/dist/dashboard/index.d.ts.map +1 -0
  17. package/dist/dashboard/index.js +4 -0
  18. package/dist/dashboard/index.js.map +1 -0
  19. package/dist/dashboard/page.d.ts +10 -0
  20. package/dist/dashboard/page.d.ts.map +1 -0
  21. package/dist/dashboard/page.js +94 -0
  22. package/dist/dashboard/page.js.map +1 -0
  23. package/dist/dashboard/routes.d.ts +48 -0
  24. package/dist/dashboard/routes.d.ts.map +1 -0
  25. package/dist/dashboard/routes.js +102 -0
  26. package/dist/dashboard/routes.js.map +1 -0
  27. package/dist/dashboard/server.d.ts +43 -0
  28. package/dist/dashboard/server.d.ts.map +1 -0
  29. package/dist/dashboard/server.js +110 -0
  30. package/dist/dashboard/server.js.map +1 -0
  31. package/dist/service/index.d.ts +1 -1
  32. package/dist/service/index.d.ts.map +1 -1
  33. package/dist/service/index.js +1 -1
  34. package/dist/service/index.js.map +1 -1
  35. package/dist/service/manager.d.ts +2 -0
  36. package/dist/service/manager.d.ts.map +1 -1
  37. package/dist/service/manager.js +18 -0
  38. package/dist/service/manager.js.map +1 -1
  39. package/dist/service/unit.d.ts +11 -0
  40. package/dist/service/unit.d.ts.map +1 -1
  41. package/dist/service/unit.js +27 -0
  42. package/dist/service/unit.js.map +1 -1
  43. package/package.json +1 -1
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"help.d.ts","sourceRoot":"","sources":["../../src/commands/help.ts"],"names":[],"mappings":"AAEA,gFAAgF;AAChF,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,qEAAqE;IACrE,OAAO,EAAE,MAAM,CAAC;IAChB,+EAA+E;IAC/E,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,8DAA8D;IAC9D,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAOD;;;;GAIG;AACH,eAAO,MAAM,QAAQ,EAAE,SAAS,WAAW,EAgM1C,CAAC;AAKF;;;;GAIG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAenC;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAclE"}
1
+ {"version":3,"file":"help.d.ts","sourceRoot":"","sources":["../../src/commands/help.ts"],"names":[],"mappings":"AAEA,gFAAgF;AAChF,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,qEAAqE;IACrE,OAAO,EAAE,MAAM,CAAC;IAChB,+EAA+E;IAC/E,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,8DAA8D;IAC9D,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAOD;;;;GAIG;AACH,eAAO,MAAM,QAAQ,EAAE,SAAS,WAAW,EA+M1C,CAAC;AAKF;;;;GAIG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAenC;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAclE"}
@@ -30,6 +30,7 @@ export const COMMANDS = [
30
30
  options: [
31
31
  " --image / --no-image Build the factory image (default: ask on a TTY)",
32
32
  " --secrets / --no-secrets Create the credentials file (default: ask on a TTY)",
33
+ " --start / --no-start Keep registered ghostrails running (default: ask on a TTY)",
33
34
  ],
34
35
  },
35
36
  {
@@ -115,6 +116,17 @@ export const COMMANDS = [
115
116
  " --no-token Serve unauthenticated off loopback (trusted networks only)",
116
117
  ],
117
118
  },
119
+ {
120
+ name: "dashboard",
121
+ summary: "Serve every registered ghostrail (started for you by `start`)",
122
+ detail: "Serves a cross-ghostrail view: every registered ghostrail with its watch flag, live service state, health, and recent runs. The checkbox writes the watch flag and nothing else, so the dashboard never starts or stops anything itself; `ghostrail start` and the service units act on the flag. This is what the dashboard's service unit invokes, the way `watch` is what a ghostrail's unit invokes, so `ghostrail start` normally launches it for you. `ghostrail board` is the separate per-repo view.",
123
+ usage: "ghostrail dashboard [options]",
124
+ options: [
125
+ " -p, --port <n> Port to bind (default: 5051, falling back upward)",
126
+ " --bind <addr> Address to bind (default: 127.0.0.1)",
127
+ " --token <token> Require this token on every request",
128
+ ],
129
+ },
118
130
  {
119
131
  name: "start",
120
132
  summary: "Start ghostrails as user services, choosing which ones",
@@ -125,6 +137,7 @@ export const COMMANDS = [
125
137
  " -n, --name <n> Start this ghostrail (repeatable; name or path)",
126
138
  " -i, --interval <dur> Poll interval for the service (default: 5m)",
127
139
  " -y, --yes Do not prompt; use each ghostrail's watch flag",
140
+ " --no-dashboard Do not start the cross-ghostrail dashboard",
128
141
  ],
129
142
  },
130
143
  {
@@ -135,6 +148,7 @@ export const COMMANDS = [
135
148
  options: [
136
149
  " -a, --all Stop every registered ghostrail",
137
150
  " -n, --name <n> Stop this ghostrail (repeatable; name or path)",
151
+ " --no-dashboard Leave the dashboard running",
138
152
  ],
139
153
  },
140
154
  {
@@ -1 +1 @@
1
- {"version":3,"file":"help.js","sourceRoot":"","sources":["../../src/commands/help.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"help.js","sourceRoot":"","sources":["../../src/commands/help.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export { DASHBOARD_PAGE_HTML } from "./page.js";
2
+ export { type DashboardDeps, type DashboardOptions, type GhostrailHealth, type GhostrailView, collectViews, createDashboardServer, } from "./server.js";
3
+ export { type DashboardRoute, isCrossOriginWrite, parseDashboardRoute, parseWatchUpdate, } from "./routes.js";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/dashboard/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,YAAY,EACZ,qBAAqB,GACtB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,KAAK,cAAc,EACnB,kBAAkB,EAClB,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,aAAa,CAAC"}
@@ -0,0 +1,4 @@
1
+ export { DASHBOARD_PAGE_HTML } from "./page.js";
2
+ export { collectViews, createDashboardServer, } from "./server.js";
3
+ export { isCrossOriginWrite, parseDashboardRoute, parseWatchUpdate, } from "./routes.js";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/dashboard/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,EAKL,YAAY,EACZ,qBAAqB,GACtB,MAAM,aAAa,CAAC;AACrB,OAAO,EAEL,kBAAkB,EAClB,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,aAAa,CAAC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * The dashboard page. Inline, like the board's, so the package ships one file
3
+ * and the server needs no static asset handling.
4
+ *
5
+ * The checkbox is desired state and the copy has to keep saying so: a ticked
6
+ * box beside an inactive unit means "will start", not "running". Showing them
7
+ * as one thing would be a lie the operator acts on.
8
+ */
9
+ export declare const DASHBOARD_PAGE_HTML = "<!doctype html>\n<meta charset=\"utf-8\">\n<title>ghostrails</title>\n<style>\n :root { color-scheme: light dark; }\n body { font: 15px/1.5 ui-sans-serif, system-ui, sans-serif; margin: 2rem auto; max-width: 60rem; padding: 0 1rem; }\n h1 { font-size: 1.3rem; margin: 0 0 .25rem; }\n .sub { opacity: .7; margin: 0 0 1.5rem; }\n .gr { border: 1px solid color-mix(in oklab, currentColor 20%, transparent); border-radius: 8px; padding: .9rem 1rem; margin-bottom: .75rem; }\n .top { display: flex; align-items: baseline; gap: .6rem; flex-wrap: wrap; }\n .name { font-weight: 600; }\n .path { opacity: .6; font-family: ui-monospace, monospace; font-size: .85em; }\n .pill { font-size: .75em; padding: .1rem .45rem; border-radius: 99px; border: 1px solid currentColor; opacity: .8; }\n .runs { margin: .6rem 0 0; padding-left: 1.1rem; opacity: .8; font-size: .9em; }\n .runs li { margin: .1rem 0; }\n .none { opacity: .55; font-size: .9em; margin: .6rem 0 0; }\n label { display: inline-flex; align-items: center; gap: .4rem; cursor: pointer; }\n .err { color: #b00; }\n</style>\n<h1>ghostrails</h1>\n<p class=\"sub\">The checkbox is what <code>ghostrail start</code> should bring up. The pill beside it is what is running right now.</p>\n<div id=\"list\">loading\u2026</div>\n<script>\nconst tok = new URLSearchParams(location.search).get(\"token\");\nconst api = (p) => p + (tok ? (p.includes(\"?\") ? \"&\" : \"?\") + \"token=\" + encodeURIComponent(tok) : \"\");\n\nfunction render(ghostrails) {\n const list = document.getElementById(\"list\");\n if (!ghostrails.length) {\n list.innerHTML = '<p class=\"none\">No ghostrails registered. <code>ghostrail init &lt;template&gt;</code> registers one.</p>';\n return;\n }\n list.innerHTML = \"\";\n for (const g of ghostrails) {\n const el = document.createElement(\"div\");\n el.className = \"gr\";\n const runs = g.runs.length\n ? '<ul class=\"runs\">' + g.runs.map((r) =>\n \"<li>\" + esc(r.itemId || r.runId) + \" \u2014 \" + esc(r.state || \"?\") +\n (r.title ? \" \u2014 \" + esc(r.title) : \"\") + \"</li>\").join(\"\") + \"</ul>\"\n : '<p class=\"none\">' + (g.health === \"ok\" ? \"no runs yet\" : esc(g.health)) + \"</p>\";\n el.innerHTML =\n '<div class=\"top\">' +\n '<label><input type=\"checkbox\" ' + (g.watch ? \"checked\" : \"\") + \" data-path=\\\"\" + esc(g.path) + '\"> watch</label>' +\n '<span class=\"name\">' + esc(g.name) + \"</span>\" +\n '<span class=\"pill\">' + esc(g.state) + \"</span>\" +\n '<span class=\"path\">' + esc(g.path) + \"</span>\" +\n \"</div>\" + runs;\n list.appendChild(el);\n }\n for (const box of list.querySelectorAll(\"input[type=checkbox]\")) {\n box.addEventListener(\"change\", async (e) => {\n const t = e.target;\n try {\n const res = await fetch(api(\"/api/watch\"), {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ path: t.dataset.path, watch: t.checked }),\n });\n if (!res.ok) throw new Error((await res.json()).error || res.statusText);\n render((await res.json()).ghostrails);\n } catch (err) {\n t.checked = !t.checked;\n alert(\"could not update: \" + err.message);\n }\n });\n }\n}\n\nconst esc = (s) => String(s).replace(/[&<>\"']/g, (c) =>\n ({ \"&\": \"&amp;\", \"<\": \"&lt;\", \">\": \"&gt;\", '\"': \"&quot;\", \"'\": \"&#39;\" })[c]);\n\nasync function load() {\n try {\n const res = await fetch(api(\"/api/ghostrails\"));\n if (!res.ok) throw new Error((await res.json()).error || res.statusText);\n render((await res.json()).ghostrails);\n } catch (err) {\n document.getElementById(\"list\").innerHTML = '<p class=\"err\">' + esc(err.message) + \"</p>\";\n }\n}\nload();\nsetInterval(load, 10000);\n</script>\n";
10
+ //# sourceMappingURL=page.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../../src/dashboard/page.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,yzHAoF/B,CAAC"}
@@ -0,0 +1,94 @@
1
+ /**
2
+ * The dashboard page. Inline, like the board's, so the package ships one file
3
+ * and the server needs no static asset handling.
4
+ *
5
+ * The checkbox is desired state and the copy has to keep saying so: a ticked
6
+ * box beside an inactive unit means "will start", not "running". Showing them
7
+ * as one thing would be a lie the operator acts on.
8
+ */
9
+ export const DASHBOARD_PAGE_HTML = `<!doctype html>
10
+ <meta charset="utf-8">
11
+ <title>ghostrails</title>
12
+ <style>
13
+ :root { color-scheme: light dark; }
14
+ body { font: 15px/1.5 ui-sans-serif, system-ui, sans-serif; margin: 2rem auto; max-width: 60rem; padding: 0 1rem; }
15
+ h1 { font-size: 1.3rem; margin: 0 0 .25rem; }
16
+ .sub { opacity: .7; margin: 0 0 1.5rem; }
17
+ .gr { border: 1px solid color-mix(in oklab, currentColor 20%, transparent); border-radius: 8px; padding: .9rem 1rem; margin-bottom: .75rem; }
18
+ .top { display: flex; align-items: baseline; gap: .6rem; flex-wrap: wrap; }
19
+ .name { font-weight: 600; }
20
+ .path { opacity: .6; font-family: ui-monospace, monospace; font-size: .85em; }
21
+ .pill { font-size: .75em; padding: .1rem .45rem; border-radius: 99px; border: 1px solid currentColor; opacity: .8; }
22
+ .runs { margin: .6rem 0 0; padding-left: 1.1rem; opacity: .8; font-size: .9em; }
23
+ .runs li { margin: .1rem 0; }
24
+ .none { opacity: .55; font-size: .9em; margin: .6rem 0 0; }
25
+ label { display: inline-flex; align-items: center; gap: .4rem; cursor: pointer; }
26
+ .err { color: #b00; }
27
+ </style>
28
+ <h1>ghostrails</h1>
29
+ <p class="sub">The checkbox is what <code>ghostrail start</code> should bring up. The pill beside it is what is running right now.</p>
30
+ <div id="list">loading…</div>
31
+ <script>
32
+ const tok = new URLSearchParams(location.search).get("token");
33
+ const api = (p) => p + (tok ? (p.includes("?") ? "&" : "?") + "token=" + encodeURIComponent(tok) : "");
34
+
35
+ function render(ghostrails) {
36
+ const list = document.getElementById("list");
37
+ if (!ghostrails.length) {
38
+ list.innerHTML = '<p class="none">No ghostrails registered. <code>ghostrail init &lt;template&gt;</code> registers one.</p>';
39
+ return;
40
+ }
41
+ list.innerHTML = "";
42
+ for (const g of ghostrails) {
43
+ const el = document.createElement("div");
44
+ el.className = "gr";
45
+ const runs = g.runs.length
46
+ ? '<ul class="runs">' + g.runs.map((r) =>
47
+ "<li>" + esc(r.itemId || r.runId) + " — " + esc(r.state || "?") +
48
+ (r.title ? " — " + esc(r.title) : "") + "</li>").join("") + "</ul>"
49
+ : '<p class="none">' + (g.health === "ok" ? "no runs yet" : esc(g.health)) + "</p>";
50
+ el.innerHTML =
51
+ '<div class="top">' +
52
+ '<label><input type="checkbox" ' + (g.watch ? "checked" : "") + " data-path=\\"" + esc(g.path) + '"> watch</label>' +
53
+ '<span class="name">' + esc(g.name) + "</span>" +
54
+ '<span class="pill">' + esc(g.state) + "</span>" +
55
+ '<span class="path">' + esc(g.path) + "</span>" +
56
+ "</div>" + runs;
57
+ list.appendChild(el);
58
+ }
59
+ for (const box of list.querySelectorAll("input[type=checkbox]")) {
60
+ box.addEventListener("change", async (e) => {
61
+ const t = e.target;
62
+ try {
63
+ const res = await fetch(api("/api/watch"), {
64
+ method: "POST",
65
+ headers: { "content-type": "application/json" },
66
+ body: JSON.stringify({ path: t.dataset.path, watch: t.checked }),
67
+ });
68
+ if (!res.ok) throw new Error((await res.json()).error || res.statusText);
69
+ render((await res.json()).ghostrails);
70
+ } catch (err) {
71
+ t.checked = !t.checked;
72
+ alert("could not update: " + err.message);
73
+ }
74
+ });
75
+ }
76
+ }
77
+
78
+ const esc = (s) => String(s).replace(/[&<>"']/g, (c) =>
79
+ ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
80
+
81
+ async function load() {
82
+ try {
83
+ const res = await fetch(api("/api/ghostrails"));
84
+ if (!res.ok) throw new Error((await res.json()).error || res.statusText);
85
+ render((await res.json()).ghostrails);
86
+ } catch (err) {
87
+ document.getElementById("list").innerHTML = '<p class="err">' + esc(err.message) + "</p>";
88
+ }
89
+ }
90
+ load();
91
+ setInterval(load, 10000);
92
+ </script>
93
+ `;
94
+ //# sourceMappingURL=page.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page.js","sourceRoot":"","sources":["../../src/dashboard/page.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoFlC,CAAC"}
@@ -0,0 +1,48 @@
1
+ import type { IncomingHttpHeaders } from "node:http";
2
+ /**
3
+ * Pure routing and request vetting for the cross-ghostrail dashboard. Split
4
+ * from the server so the parts worth being careful about, especially the write
5
+ * route's guard, are testable without a socket.
6
+ */
7
+ export type DashboardRoute = {
8
+ kind: "page";
9
+ } | {
10
+ kind: "ghostrails";
11
+ } | {
12
+ kind: "watch";
13
+ } | {
14
+ kind: "notfound";
15
+ };
16
+ /** Map a request pathname to a route. Total; never throws. */
17
+ export declare function parseDashboardRoute(pathname: string): DashboardRoute;
18
+ /**
19
+ * True when a request looks cross-origin, which the write route refuses.
20
+ *
21
+ * This is not the token decision (TJ-1554 owns that). It is hygiene for a
22
+ * mutating route: a plain HTML form on any website can POST to a loopback port
23
+ * cross-origin, and by inherited board behaviour there is no token in the way
24
+ * unless the bind is non-loopback. Without this, any page the operator visits
25
+ * could flip their watch flags.
26
+ *
27
+ * A request with neither signal, such as `curl` sending JSON, is allowed.
28
+ * Locking that out is a token's job: anything able to set arbitrary headers can
29
+ * also read a token, so refusing it would break scripting for no gain.
30
+ */
31
+ export declare function isCrossOriginWrite(headers: IncomingHttpHeaders): boolean;
32
+ /**
33
+ * Parse the write body: `{ path, watch }`. Undefined for anything else, which
34
+ * the route answers with 400.
35
+ *
36
+ * An empty `path` is rejected rather than passed through: it can never match a
37
+ * registered ghostrail, so accepting it would only produce a confusing 404.
38
+ */
39
+ /**
40
+ * Unknown keys alongside the two it needs are ignored, and the entry still
41
+ * parses. Same forward compatibility the registry uses: a later dashboard that
42
+ * sends an extra field must not make an older server reject the write outright.
43
+ */
44
+ export declare function parseWatchUpdate(body: string): {
45
+ path: string;
46
+ watch: boolean;
47
+ } | undefined;
48
+ //# sourceMappingURL=routes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/dashboard/routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAErD;;;;GAIG;AAEH,MAAM,MAAM,cAAc,GACtB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,GACtB;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,GACjB;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,CAAC;AAEzB,8DAA8D;AAC9D,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,cAAc,CAUpE;AA2BD;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAYxE;AAOD;;;;;;GAMG;AACH;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,CAY3F"}
@@ -0,0 +1,102 @@
1
+ /** Map a request pathname to a route. Total; never throws. */
2
+ export function parseDashboardRoute(pathname) {
3
+ const parts = pathname.split("/").filter((p) => p.length > 0);
4
+ // Nothing but slashes is the root. `//` reaching a server is unusual but a
5
+ // proxy or a hand-built link produces it, and serving the page beats a 404
6
+ // for something that means the root by every reading.
7
+ if (parts.length === 0)
8
+ return { kind: "page" };
9
+ if (parts[0] !== "api" || parts.length !== 2)
10
+ return { kind: "notfound" };
11
+ if (parts[1] === "ghostrails")
12
+ return { kind: "ghostrails" };
13
+ if (parts[1] === "watch")
14
+ return { kind: "watch" };
15
+ return { kind: "notfound" };
16
+ }
17
+ /**
18
+ * Every value for a header, case-insensitively.
19
+ *
20
+ * All of them, not the first. Node hands back an array for a repeated header,
21
+ * and a guard that inspects only `value[0]` can be walked past by sending the
22
+ * innocent spelling first. For a check whose whole job is refusing a request,
23
+ * "any value looks like an attack" is the only safe reading.
24
+ *
25
+ * Case-insensitive on the name because Node lowercases them but a hand-built
26
+ * object does not have to.
27
+ */
28
+ function headerValues(headers, name) {
29
+ const wanted = name.toLowerCase();
30
+ const values = [];
31
+ for (const [key, value] of Object.entries(headers)) {
32
+ if (key.toLowerCase() !== wanted || value === undefined)
33
+ continue;
34
+ if (Array.isArray(value))
35
+ values.push(...value);
36
+ else
37
+ values.push(String(value));
38
+ }
39
+ return values;
40
+ }
41
+ /** The three content types a form can send without a CORS preflight. */
42
+ const FORM_TYPES = ["application/x-www-form-urlencoded", "multipart/form-data", "text/plain"];
43
+ /**
44
+ * True when a request looks cross-origin, which the write route refuses.
45
+ *
46
+ * This is not the token decision (TJ-1554 owns that). It is hygiene for a
47
+ * mutating route: a plain HTML form on any website can POST to a loopback port
48
+ * cross-origin, and by inherited board behaviour there is no token in the way
49
+ * unless the bind is non-loopback. Without this, any page the operator visits
50
+ * could flip their watch flags.
51
+ *
52
+ * A request with neither signal, such as `curl` sending JSON, is allowed.
53
+ * Locking that out is a token's job: anything able to set arbitrary headers can
54
+ * also read a token, so refusing it would break scripting for no gain.
55
+ */
56
+ export function isCrossOriginWrite(headers) {
57
+ // Browsers send this unconditionally and script cannot forge it. Any value
58
+ // that is not same-origin rejects, so a repeated header cannot smuggle one
59
+ // past by leading with the innocent spelling.
60
+ const sites = headerValues(headers, "sec-fetch-site");
61
+ if (sites.some((site) => site.trim().toLowerCase() !== "same-origin"))
62
+ return true;
63
+ // Requiring a non-form content type forces a preflight a form cannot satisfy.
64
+ return headerValues(headers, "content-type").some((value) => {
65
+ const media = value.split(";")[0]?.trim().toLowerCase() ?? "";
66
+ return FORM_TYPES.includes(media);
67
+ });
68
+ }
69
+ /** True if `value` is a plain object (not null, not an array). */
70
+ function isRecord(value) {
71
+ return typeof value === "object" && value !== null && !Array.isArray(value);
72
+ }
73
+ /**
74
+ * Parse the write body: `{ path, watch }`. Undefined for anything else, which
75
+ * the route answers with 400.
76
+ *
77
+ * An empty `path` is rejected rather than passed through: it can never match a
78
+ * registered ghostrail, so accepting it would only produce a confusing 404.
79
+ */
80
+ /**
81
+ * Unknown keys alongside the two it needs are ignored, and the entry still
82
+ * parses. Same forward compatibility the registry uses: a later dashboard that
83
+ * sends an extra field must not make an older server reject the write outright.
84
+ */
85
+ export function parseWatchUpdate(body) {
86
+ let value;
87
+ try {
88
+ value = JSON.parse(body);
89
+ }
90
+ catch {
91
+ return undefined;
92
+ }
93
+ if (!isRecord(value))
94
+ return undefined;
95
+ const { path, watch } = value;
96
+ if (typeof path !== "string" || path.length === 0)
97
+ return undefined;
98
+ if (typeof watch !== "boolean")
99
+ return undefined;
100
+ return { path, watch };
101
+ }
102
+ //# sourceMappingURL=routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.js","sourceRoot":"","sources":["../../src/dashboard/routes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ import { type Server } from "node:http";
2
+ import type { Ghostrail } from "../global/registry.js";
3
+ /** How a ghostrail stands on disk, mirroring what `status` reports. */
4
+ export type GhostrailHealth = "ok" | "no config" | "missing";
5
+ /** One row of the dashboard: registry entry plus everything observed about it. */
6
+ export interface GhostrailView extends Ghostrail {
7
+ health: GhostrailHealth;
8
+ /** Live unit state, e.g. `active`. `unknown` when no manager could be asked. */
9
+ state: string;
10
+ /** Most recent runs from this ghostrail's own `.factory`, newest first. */
11
+ runs: {
12
+ runId: string;
13
+ itemId?: string;
14
+ state?: string;
15
+ title?: string;
16
+ }[];
17
+ }
18
+ /** Everything the server needs, injected so it can be driven without a machine. */
19
+ export interface DashboardDeps {
20
+ /** The registry, or undefined when it could not be parsed. */
21
+ readRegistry(): Promise<Ghostrail[] | undefined>;
22
+ writeRegistry(entries: readonly Ghostrail[]): Promise<void>;
23
+ health(path: string): Promise<GhostrailHealth>;
24
+ /** Live unit state per ghostrail path. */
25
+ states(entries: readonly Ghostrail[]): Promise<Map<string, string>>;
26
+ }
27
+ export interface DashboardOptions {
28
+ bind?: string;
29
+ /** When set, every request must present it. Inherited from the board's behaviour. */
30
+ token?: string;
31
+ }
32
+ /** Assemble the view for every registered ghostrail. */
33
+ export declare function collectViews(deps: DashboardDeps, entries: readonly Ghostrail[]): Promise<GhostrailView[]>;
34
+ /**
35
+ * The dashboard's HTTP server: one page, one listing, and one write route that
36
+ * sets a ghostrail's `watch` flag and does nothing else.
37
+ *
38
+ * It never starts or stops anything. `start` and the service units act on the
39
+ * flag, which keeps process control off an HTTP surface: the worst a
40
+ * compromised dashboard can do is change which ghostrails come up next time.
41
+ */
42
+ export declare function createDashboardServer(deps: DashboardDeps, options?: DashboardOptions): Server;
43
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/dashboard/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAqC,MAAM,WAAW,CAAC;AAE3E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAKvD,uEAAuE;AACvE,MAAM,MAAM,eAAe,GAAG,IAAI,GAAG,WAAW,GAAG,SAAS,CAAC;AAE7D,kFAAkF;AAClF,MAAM,WAAW,aAAc,SAAQ,SAAS;IAC9C,MAAM,EAAE,eAAe,CAAC;IACxB,gFAAgF;IAChF,KAAK,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC5E;AAED,mFAAmF;AACnF,MAAM,WAAW,aAAa;IAC5B,8DAA8D;IAC9D,YAAY,IAAI,OAAO,CAAC,SAAS,EAAE,GAAG,SAAS,CAAC,CAAC;IACjD,aAAa,CAAC,OAAO,EAAE,SAAS,SAAS,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5D,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;IAC/C,0CAA0C;IAC1C,MAAM,CAAC,OAAO,EAAE,SAAS,SAAS,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACrE;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qFAAqF;IACrF,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA0BD,wDAAwD;AACxD,wBAAsB,YAAY,CAChC,IAAI,EAAE,aAAa,EACnB,OAAO,EAAE,SAAS,SAAS,EAAE,GAC5B,OAAO,CAAC,aAAa,EAAE,CAAC,CAwB1B;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,aAAa,EAAE,OAAO,GAAE,gBAAqB,GAAG,MAAM,CAsDjG"}
@@ -0,0 +1,110 @@
1
+ import { createServer } from "node:http";
2
+ import { join } from "node:path";
3
+ import { FileStore } from "../store/file-store.js";
4
+ import { DASHBOARD_PAGE_HTML } from "./page.js";
5
+ import { isCrossOriginWrite, parseDashboardRoute, parseWatchUpdate } from "./routes.js";
6
+ /** The store dir inside a ghostrail. */
7
+ const STORE_DIR = ".factory";
8
+ /** How many runs to show per ghostrail. Enough to see recent activity, not a log. */
9
+ const RUNS_PER_GHOSTRAIL = 5;
10
+ function sendJson(res, status, data) {
11
+ res.writeHead(status, { "content-type": "application/json" });
12
+ res.end(JSON.stringify(data));
13
+ }
14
+ /** Read a request body, bounded so a stray large POST cannot exhaust memory. */
15
+ async function readBody(req, limit = 64 * 1024) {
16
+ const chunks = [];
17
+ let size = 0;
18
+ for await (const chunk of req) {
19
+ const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
20
+ size += buf.length;
21
+ if (size > limit)
22
+ break;
23
+ chunks.push(buf);
24
+ }
25
+ return Buffer.concat(chunks).toString("utf8");
26
+ }
27
+ /** Assemble the view for every registered ghostrail. */
28
+ export async function collectViews(deps, entries) {
29
+ const states = await deps.states(entries);
30
+ const views = [];
31
+ for (const entry of entries) {
32
+ const health = await deps.health(entry.path);
33
+ // A ghostrail with no .factory yet is still a ghostrail; it just has
34
+ // nothing to report. Never let a missing store turn a row into an error.
35
+ let runs = [];
36
+ if (health === "ok") {
37
+ try {
38
+ const rows = await new FileStore(join(entry.path, STORE_DIR)).listRuns({});
39
+ runs = rows.slice(0, RUNS_PER_GHOSTRAIL).map((row) => ({
40
+ runId: row.runId,
41
+ ...(row.itemId === undefined ? {} : { itemId: row.itemId }),
42
+ ...(row.state === undefined ? {} : { state: row.state }),
43
+ ...(row.title === undefined ? {} : { title: row.title }),
44
+ }));
45
+ }
46
+ catch {
47
+ runs = [];
48
+ }
49
+ }
50
+ views.push({ ...entry, health, state: states.get(entry.path) ?? "unknown", runs });
51
+ }
52
+ return views;
53
+ }
54
+ /**
55
+ * The dashboard's HTTP server: one page, one listing, and one write route that
56
+ * sets a ghostrail's `watch` flag and does nothing else.
57
+ *
58
+ * It never starts or stops anything. `start` and the service units act on the
59
+ * flag, which keeps process control off an HTTP surface: the worst a
60
+ * compromised dashboard can do is change which ghostrails come up next time.
61
+ */
62
+ export function createDashboardServer(deps, options = {}) {
63
+ return createServer((req, res) => {
64
+ void (async () => {
65
+ const url = new URL(req.url ?? "/", "http://localhost");
66
+ if (options.token !== undefined) {
67
+ const presented = req.headers["x-ghostrail-token"] ?? url.searchParams.get("token") ?? "";
68
+ if (presented !== options.token)
69
+ return sendJson(res, 401, { error: "unauthorized" });
70
+ }
71
+ const route = parseDashboardRoute(url.pathname);
72
+ if (route.kind === "page") {
73
+ res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
74
+ return res.end(DASHBOARD_PAGE_HTML);
75
+ }
76
+ const entries = await deps.readRegistry();
77
+ if (entries === undefined) {
78
+ return sendJson(res, 500, { error: "the ghostrails registry could not be parsed" });
79
+ }
80
+ if (route.kind === "ghostrails") {
81
+ return sendJson(res, 200, { ghostrails: await collectViews(deps, entries) });
82
+ }
83
+ if (route.kind === "watch") {
84
+ if (req.method !== "POST")
85
+ return sendJson(res, 405, { error: "use POST" });
86
+ // Hygiene for a mutating route, not the token decision (TJ-1554). A
87
+ // form on any site can POST here cross-origin, and by inherited board
88
+ // behaviour there is no token in the way on loopback.
89
+ if (isCrossOriginWrite(req.headers)) {
90
+ return sendJson(res, 403, { error: "cross-origin writes are refused" });
91
+ }
92
+ const update = parseWatchUpdate(await readBody(req));
93
+ if (update === undefined) {
94
+ return sendJson(res, 400, { error: "expected {path: string, watch: boolean}" });
95
+ }
96
+ // Must already be registered. Registering an arbitrary filesystem path
97
+ // over HTTP is a far larger surface than this route wants to be, and
98
+ // registration is `init`'s job.
99
+ if (!entries.some((e) => e.path === update.path)) {
100
+ return sendJson(res, 404, { error: "no registered ghostrail at that path" });
101
+ }
102
+ const next = entries.map((e) => e.path === update.path ? { ...e, watch: update.watch } : e);
103
+ await deps.writeRegistry(next);
104
+ return sendJson(res, 200, { ghostrails: await collectViews(deps, next) });
105
+ }
106
+ return sendJson(res, 404, { error: "not found" });
107
+ })().catch(() => sendJson(res, 500, { error: "internal error" }));
108
+ });
109
+ }
110
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/dashboard/server.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,3 @@
1
1
  export { type InstallOutcome, type ManagerKind, type ServiceManager, detectManager, disableAndStop, enableAndStart, installUnit, reload, removeUnit, systemdManager, systemdUnitDir, unitStates, } from "./manager.js";
2
- export { type UnitSpec, parseUnitStates, renderSystemdUnit, stopTimeoutSeconds, unitName, } from "./unit.js";
2
+ export { type UnitSpec, DASHBOARD_UNIT, renderDashboardUnit, parseUnitStates, renderSystemdUnit, stopTimeoutSeconds, unitName, } from "./unit.js";
3
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/service/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,aAAa,EACb,cAAc,EACd,cAAc,EACd,WAAW,EACX,MAAM,EACN,UAAU,EACV,cAAc,EACd,cAAc,EACd,UAAU,GACX,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,KAAK,QAAQ,EACb,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,QAAQ,GACT,MAAM,WAAW,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/service/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,aAAa,EACb,cAAc,EACd,cAAc,EACd,WAAW,EACX,MAAM,EACN,UAAU,EACV,cAAc,EACd,cAAc,EACd,UAAU,GACX,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,KAAK,QAAQ,EACb,cAAc,EACd,mBAAmB,EACnB,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,QAAQ,GACT,MAAM,WAAW,CAAC"}
@@ -1,3 +1,3 @@
1
1
  export { detectManager, disableAndStop, enableAndStart, installUnit, reload, removeUnit, systemdManager, systemdUnitDir, unitStates, } from "./manager.js";
2
- export { parseUnitStates, renderSystemdUnit, stopTimeoutSeconds, unitName, } from "./unit.js";
2
+ export { DASHBOARD_UNIT, renderDashboardUnit, parseUnitStates, renderSystemdUnit, stopTimeoutSeconds, unitName, } from "./unit.js";
3
3
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/service/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,aAAa,EACb,cAAc,EACd,cAAc,EACd,WAAW,EACX,MAAM,EACN,UAAU,EACV,cAAc,EACd,cAAc,EACd,UAAU,GACX,MAAM,cAAc,CAAC;AACtB,OAAO,EAEL,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,QAAQ,GACT,MAAM,WAAW,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/service/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,aAAa,EACb,cAAc,EACd,cAAc,EACd,WAAW,EACX,MAAM,EACN,UAAU,EACV,cAAc,EACd,cAAc,EACd,UAAU,GACX,MAAM,cAAc,CAAC;AACtB,OAAO,EAEL,cAAc,EACd,mBAAmB,EACnB,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,QAAQ,GACT,MAAM,WAAW,CAAC"}
@@ -53,6 +53,8 @@ export declare function unitStates(units: readonly string[]): Promise<Map<string
53
53
  */
54
54
  export interface ServiceManager {
55
55
  kind(): Promise<ManagerKind>;
56
+ /** Write a unit file verbatim, for units with no UnitSpec (the dashboard). */
57
+ installRaw(unit: string, contents: string): Promise<InstallOutcome>;
56
58
  install(spec: UnitSpec): Promise<{
57
59
  outcome: InstallOutcome;
58
60
  unit: string;
@@ -1 +1 @@
1
- {"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../src/service/manager.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,QAAQ,EAAgD,MAAM,WAAW,CAAC;AAExF;;;;GAIG;AACH,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,SAAS,GAAG,MAAM,CAAC;AAEzD,0EAA0E;AAC1E,wBAAsB,aAAa,IAAI,OAAO,CAAC,WAAW,CAAC,CAU1D;AAED,iDAAiD;AACjD,wBAAgB,cAAc,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAG3E;AAED,8EAA8E;AAC9E,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,WAAW,GAAG,aAAa,CAAC;AAErE;;;;;;GAMG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,QAAQ,EACd,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,OAAO,CAAC;IAAE,OAAO,EAAE,cAAc,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAkBlE;AAQD,6DAA6D;AAC7D,wBAAsB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAE5C;AAED,kFAAkF;AAClF,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAG9E;AAED;;;;;;GAMG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAQ9E;AAED,+EAA+E;AAC/E,wBAAsB,UAAU,CAC9B,IAAI,EAAE,MAAM,EACZ,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,OAAO,CAAC,IAAI,CAAC,CAEf;AAED,wDAAwD;AACxD,wBAAsB,UAAU,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAIvF;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,IAAI,OAAO,CAAC,WAAW,CAAC,CAAC;IAC7B,OAAO,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,cAAc,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC1F,2EAA2E;IAC3E,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IAClD,2EAA2E;IAC3E,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACnD,MAAM,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CAChE;AAED,wDAAwD;AACxD,wBAAgB,cAAc,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,cAAc,CAWnF"}
1
+ {"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../src/service/manager.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,QAAQ,EAAgD,MAAM,WAAW,CAAC;AAExF;;;;GAIG;AACH,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,SAAS,GAAG,MAAM,CAAC;AAEzD,0EAA0E;AAC1E,wBAAsB,aAAa,IAAI,OAAO,CAAC,WAAW,CAAC,CAU1D;AAED,iDAAiD;AACjD,wBAAgB,cAAc,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAG3E;AAED,8EAA8E;AAC9E,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,WAAW,GAAG,aAAa,CAAC;AAErE;;;;;;GAMG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,QAAQ,EACd,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,OAAO,CAAC;IAAE,OAAO,EAAE,cAAc,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAkBlE;AAQD,6DAA6D;AAC7D,wBAAsB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAE5C;AAED,kFAAkF;AAClF,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAG9E;AAED;;;;;;GAMG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAQ9E;AAED,+EAA+E;AAC/E,wBAAsB,UAAU,CAC9B,IAAI,EAAE,MAAM,EACZ,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,OAAO,CAAC,IAAI,CAAC,CAEf;AAED,wDAAwD;AACxD,wBAAsB,UAAU,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAIvF;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,IAAI,OAAO,CAAC,WAAW,CAAC,CAAC;IAC7B,8EAA8E;IAC9E,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;IACpE,OAAO,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,cAAc,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC1F,2EAA2E;IAC3E,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IAClD,2EAA2E;IAC3E,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACnD,MAAM,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CAChE;AAED,wDAAwD;AACxD,wBAAgB,cAAc,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,cAAc,CA0BnF"}