ghostrail 0.9.0 → 0.10.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 (39) hide show
  1. package/dist/cli/commands.d.ts +10 -0
  2. package/dist/cli/commands.d.ts.map +1 -1
  3. package/dist/cli/commands.js +112 -3
  4. package/dist/cli/commands.js.map +1 -1
  5. package/dist/cli.d.ts.map +1 -1
  6. package/dist/cli.js +3 -1
  7. package/dist/cli.js.map +1 -1
  8. package/dist/commands/help.d.ts.map +1 -1
  9. package/dist/commands/help.js +13 -0
  10. package/dist/commands/help.js.map +1 -1
  11. package/dist/dashboard/index.d.ts +4 -0
  12. package/dist/dashboard/index.d.ts.map +1 -0
  13. package/dist/dashboard/index.js +4 -0
  14. package/dist/dashboard/index.js.map +1 -0
  15. package/dist/dashboard/page.d.ts +10 -0
  16. package/dist/dashboard/page.d.ts.map +1 -0
  17. package/dist/dashboard/page.js +94 -0
  18. package/dist/dashboard/page.js.map +1 -0
  19. package/dist/dashboard/routes.d.ts +48 -0
  20. package/dist/dashboard/routes.d.ts.map +1 -0
  21. package/dist/dashboard/routes.js +102 -0
  22. package/dist/dashboard/routes.js.map +1 -0
  23. package/dist/dashboard/server.d.ts +43 -0
  24. package/dist/dashboard/server.d.ts.map +1 -0
  25. package/dist/dashboard/server.js +110 -0
  26. package/dist/dashboard/server.js.map +1 -0
  27. package/dist/service/index.d.ts +1 -1
  28. package/dist/service/index.d.ts.map +1 -1
  29. package/dist/service/index.js +1 -1
  30. package/dist/service/index.js.map +1 -1
  31. package/dist/service/manager.d.ts +2 -0
  32. package/dist/service/manager.d.ts.map +1 -1
  33. package/dist/service/manager.js +18 -0
  34. package/dist/service/manager.js.map +1 -1
  35. package/dist/service/unit.d.ts +11 -0
  36. package/dist/service/unit.d.ts.map +1 -1
  37. package/dist/service/unit.js +27 -0
  38. package/dist/service/unit.js.map +1 -1
  39. package/package.json +1 -1
@@ -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"}
@@ -96,6 +96,24 @@ export function systemdManager(env = process.env) {
96
96
  return {
97
97
  kind: detectManager,
98
98
  install: (spec) => installUnit(spec, env),
99
+ installRaw: async (unit, contents) => {
100
+ const dir = systemdUnitDir(env);
101
+ const file = join(dir, unit);
102
+ let current;
103
+ try {
104
+ current = await readFile(file, "utf8");
105
+ }
106
+ catch {
107
+ current = undefined;
108
+ }
109
+ if (current === contents)
110
+ return "unchanged";
111
+ if (current !== undefined)
112
+ return "hand-edited";
113
+ await mkdir(dir, { recursive: true });
114
+ await writeFile(file, contents, "utf8");
115
+ return "written";
116
+ },
99
117
  enable: async (unit) => {
100
118
  await reload();
101
119
  return enableAndStart(unit);
@@ -1 +1 @@
1
- {"version":3,"file":"manager.js","sourceRoot":"","sources":["../../src/service/manager.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"manager.js","sourceRoot":"","sources":["../../src/service/manager.ts"],"names":[],"mappings":"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"}
@@ -57,4 +57,15 @@ export declare function renderSystemdUnit(spec: UnitSpec): string;
57
57
  * skipped rather than fatal.
58
58
  */
59
59
  export declare function parseUnitStates(stdout: string): Map<string, string>;
60
+ /** The unit that runs the cross-ghostrail dashboard. Only ever one. */
61
+ export declare const DASHBOARD_UNIT = "ghostrail-dashboard.service";
62
+ /**
63
+ * The dashboard's unit. Unlike a ghostrail's, it has no `WorkingDirectory`
64
+ * tied to a repo: it serves all of them and reads the registry from the global
65
+ * config dir.
66
+ *
67
+ * Its stop timeout is short on purpose. A ghostrail needs minutes because an
68
+ * in-flight tick is not interrupted; an HTTP server has nothing to protect.
69
+ */
70
+ export declare function renderDashboardUnit(exec: string, port: number, bind: string): string;
60
71
  //# sourceMappingURL=unit.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"unit.d.ts","sourceRoot":"","sources":["../../src/service/unit.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AAEH,oCAAoC;AACpC,MAAM,WAAW,QAAQ;IACvB,8DAA8D;IAC9D,IAAI,EAAE,MAAM,CAAC;IACb,gEAAgE;IAChE,IAAI,EAAE,MAAM,CAAC;IACb,6CAA6C;IAC7C,IAAI,EAAE,MAAM,CAAC;IACb,qDAAqD;IACrD,QAAQ,EAAE,MAAM,CAAC;IACjB,+DAA+D;IAC/D,cAAc,EAAE,MAAM,CAAC;CACxB;AAWD;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAG3D;AAQD;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAQ5D;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAoBxD;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAoBnE"}
1
+ {"version":3,"file":"unit.d.ts","sourceRoot":"","sources":["../../src/service/unit.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AAEH,oCAAoC;AACpC,MAAM,WAAW,QAAQ;IACvB,8DAA8D;IAC9D,IAAI,EAAE,MAAM,CAAC;IACb,gEAAgE;IAChE,IAAI,EAAE,MAAM,CAAC;IACb,6CAA6C;IAC7C,IAAI,EAAE,MAAM,CAAC;IACb,qDAAqD;IACrD,QAAQ,EAAE,MAAM,CAAC;IACjB,+DAA+D;IAC/D,cAAc,EAAE,MAAM,CAAC;CACxB;AAWD;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAG3D;AAQD;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAQ5D;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAoBxD;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAoBnE;AAED,uEAAuE;AACvE,eAAO,MAAM,cAAc,gCAAgC,CAAC;AAE5D;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAgBpF"}
@@ -108,4 +108,31 @@ export function parseUnitStates(stdout) {
108
108
  }
109
109
  return states;
110
110
  }
111
+ /** The unit that runs the cross-ghostrail dashboard. Only ever one. */
112
+ export const DASHBOARD_UNIT = "ghostrail-dashboard.service";
113
+ /**
114
+ * The dashboard's unit. Unlike a ghostrail's, it has no `WorkingDirectory`
115
+ * tied to a repo: it serves all of them and reads the registry from the global
116
+ * config dir.
117
+ *
118
+ * Its stop timeout is short on purpose. A ghostrail needs minutes because an
119
+ * in-flight tick is not interrupted; an HTTP server has nothing to protect.
120
+ */
121
+ export function renderDashboardUnit(exec, port, bind) {
122
+ return `${[
123
+ "[Unit]",
124
+ "Description=ghostrail dashboard (all ghostrails)",
125
+ "Documentation=https://github.com/timothyjordan/ghostrail",
126
+ "",
127
+ "[Service]",
128
+ "Type=simple",
129
+ `ExecStart=${exec} dashboard --port ${port} --bind ${bind}`,
130
+ "Restart=on-failure",
131
+ "RestartSec=10",
132
+ "TimeoutStopSec=30",
133
+ "",
134
+ "[Install]",
135
+ "WantedBy=default.target",
136
+ ].join("\n")}\n`;
137
+ }
111
138
  //# sourceMappingURL=unit.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"unit.js","sourceRoot":"","sources":["../../src/service/unit.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"unit.js","sourceRoot":"","sources":["../../src/service/unit.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ghostrail",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "Open source software factory for coding agents. Config-driven loops, pluggable isolation backends, human merge gate.",
5
5
  "keywords": [
6
6
  "ai-agents",