@hansenexus/hud 0.3.0 → 0.4.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 (54) hide show
  1. package/README.md +186 -4
  2. package/dist/access-CNvJOwKM.d.ts +54 -0
  3. package/dist/capabilities-CjulXFQX.js +189 -0
  4. package/dist/{define-hud-BHQR8Lbv.d.ts → define-hud-BBzsSl8b.d.ts} +6 -1
  5. package/dist/env-DfxpPfs9.js +28 -0
  6. package/dist/env-vEjyJfPW.d.ts +17 -0
  7. package/dist/force-state-BaqWsmrf.js +153 -0
  8. package/dist/force-state-CCqoTfVB.d.ts +23 -0
  9. package/dist/handler-WmE4Lu7l.d.ts +39 -0
  10. package/dist/index-BYsdS-7d.d.ts +55 -0
  11. package/dist/index.d.ts +6 -36
  12. package/dist/index.js +119 -202
  13. package/dist/marker-C66LxOhJ.d.ts +9 -0
  14. package/dist/marker-CMrvxPmS.d.ts +12 -0
  15. package/dist/marker-DjeGvL-F.js +9 -0
  16. package/dist/marker-uZYPOJ4M.js +12 -0
  17. package/dist/markers.d.ts +3 -0
  18. package/dist/markers.js +3 -0
  19. package/dist/next.d.ts +3 -18
  20. package/dist/next.js +3 -28
  21. package/dist/payload-CLKDVZWl.d.ts +68 -0
  22. package/dist/plugins/access/server.d.ts +122 -0
  23. package/dist/plugins/access/server.js +339 -0
  24. package/dist/plugins/access.d.ts +41 -0
  25. package/dist/plugins/access.js +250 -0
  26. package/dist/plugins/agent/server.d.ts +129 -0
  27. package/dist/plugins/agent/server.js +429 -0
  28. package/dist/plugins/agent.d.ts +37 -0
  29. package/dist/plugins/agent.js +411 -0
  30. package/dist/plugins/design.d.ts +125 -0
  31. package/dist/plugins/design.js +619 -0
  32. package/dist/plugins/observe.d.ts +64 -0
  33. package/dist/plugins/observe.js +617 -0
  34. package/dist/plugins/ops/server.d.ts +82 -0
  35. package/dist/plugins/ops/server.js +380 -0
  36. package/dist/plugins/ops.d.ts +26 -0
  37. package/dist/plugins/ops.js +362 -0
  38. package/dist/plugins/vitals.js +3 -86
  39. package/dist/{plugins-1QmTmZyL.d.ts → plugins-CQ_0w1ot.d.ts} +39 -1
  40. package/dist/{plugins-CzUiTn_r.js → plugins-DVy_vaYr.js} +3 -1
  41. package/dist/review.d.ts +181 -0
  42. package/dist/review.js +1017 -0
  43. package/dist/selector-NSYSe7AZ.js +28 -0
  44. package/dist/server.d.ts +3 -39
  45. package/dist/server.js +1 -1
  46. package/dist/shortcut-U6ftVY06.js +222 -0
  47. package/dist/state-boundary.d.ts +26 -0
  48. package/dist/state-boundary.js +61 -0
  49. package/dist/styles--slk86ZQ.js +332 -0
  50. package/dist/types-DADBRiGk.d.ts +50 -0
  51. package/dist/types-QJUajwWq.js +35 -0
  52. package/dist/types-f3jlNxzw.d.ts +92 -0
  53. package/dist/vitals-panel-Dvc5dnR-.js +87 -0
  54. package/package.json +23 -3
@@ -0,0 +1,617 @@
1
+ import { i as definePlugin } from "../plugins-DVy_vaYr.js";
2
+ import { r as startVitals, t as VitalsPanel } from "../vitals-panel-Dvc5dnR-.js";
3
+ import { useEffect, useLayoutEffect, useState, useSyncExternalStore, version } from "react";
4
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
+ //#region src/plugins/observe/capture-buffer.ts
6
+ /**
7
+ * Fixed-size ring buffer with a referentially-stable snapshot, shaped for
8
+ * `useSyncExternalStore`: the snapshot array is replaced on every mutation
9
+ * and reused between mutations.
10
+ */
11
+ var CaptureBuffer = class {
12
+ entries = [];
13
+ snapshot = [];
14
+ listeners = /* @__PURE__ */ new Set();
15
+ nextId = 1;
16
+ maxEntries;
17
+ constructor(maxEntries = 500) {
18
+ this.maxEntries = maxEntries;
19
+ }
20
+ push(input) {
21
+ const entry = {
22
+ id: this.nextId++,
23
+ ts: Date.now(),
24
+ ...input
25
+ };
26
+ this.entries.push(entry);
27
+ if (this.entries.length > this.maxEntries) this.entries.splice(0, this.entries.length - this.maxEntries);
28
+ this.emit();
29
+ return entry;
30
+ }
31
+ clear() {
32
+ if (this.entries.length === 0) return;
33
+ this.entries = [];
34
+ this.emit();
35
+ }
36
+ getSnapshot = () => this.snapshot;
37
+ subscribe = (listener) => {
38
+ this.listeners.add(listener);
39
+ return () => this.listeners.delete(listener);
40
+ };
41
+ emit() {
42
+ this.snapshot = [...this.entries];
43
+ for (const listener of this.listeners) listener();
44
+ }
45
+ };
46
+ /** Shared buffer the capture patches write into and the console tab reads from. */
47
+ const captureBuffer = new CaptureBuffer(500);
48
+ /**
49
+ * What the pill counts as an error: `console.error`, uncaught errors and
50
+ * rejections, and requests that failed outright or answered 5xx. A 4xx is the
51
+ * app's business (a 404 probe, a 401 before login), so it stays a plain fetch.
52
+ */
53
+ function isError(entry) {
54
+ if (entry.kind === "error") return true;
55
+ return entry.kind === "fetch" && entry.status !== void 0 && (entry.status === 0 || entry.status >= 500);
56
+ }
57
+ //#endregion
58
+ //#region src/plugins/observe/capture.ts
59
+ const MAX_TEXT = 300;
60
+ let installs = 0;
61
+ const restorers = [];
62
+ const isHudRoute = (url) => url.includes("/api/hud/");
63
+ function toText(args) {
64
+ return args.map((arg) => {
65
+ if (typeof arg === "string") return arg;
66
+ if (arg instanceof Error) return `${arg.name}: ${arg.message}`;
67
+ try {
68
+ return JSON.stringify(arg) ?? String(arg);
69
+ } catch {
70
+ return String(arg);
71
+ }
72
+ }).join(" ").slice(0, MAX_TEXT);
73
+ }
74
+ /**
75
+ * Pushes on a microtask. React logs dev warnings with `console.error` while it
76
+ * renders; a synchronous push would notify the console tab's store in the
77
+ * middle of that render, React would warn about the update, and the warning
78
+ * would land here again.
79
+ */
80
+ function record(input) {
81
+ queueMicrotask(() => captureBuffer.push(input));
82
+ }
83
+ function patchConsole() {
84
+ for (const kind of [
85
+ "log",
86
+ "warn",
87
+ "error"
88
+ ]) {
89
+ const original = console[kind];
90
+ console[kind] = (...args) => {
91
+ record({
92
+ kind,
93
+ text: toText(args)
94
+ });
95
+ original.apply(console, args);
96
+ };
97
+ restorers.push(() => {
98
+ console[kind] = original;
99
+ });
100
+ }
101
+ }
102
+ function listenErrors() {
103
+ const onError = (event) => {
104
+ record({
105
+ kind: "error",
106
+ text: toText([event.error instanceof Error ? event.error : event.message])
107
+ });
108
+ };
109
+ const onRejection = (event) => {
110
+ record({
111
+ kind: "error",
112
+ text: `Unhandled rejection: ${toText([event.reason])}`
113
+ });
114
+ };
115
+ window.addEventListener("error", onError);
116
+ window.addEventListener("unhandledrejection", onRejection);
117
+ restorers.push(() => {
118
+ window.removeEventListener("error", onError);
119
+ window.removeEventListener("unhandledrejection", onRejection);
120
+ });
121
+ }
122
+ function hasInternalHeader(input, init) {
123
+ try {
124
+ if (new Headers(init?.headers).has("x-hud-internal")) return true;
125
+ return input instanceof Request && input.headers.has("x-hud-internal");
126
+ } catch {
127
+ return false;
128
+ }
129
+ }
130
+ function patchFetch(ignore) {
131
+ const originalFetch = window.fetch;
132
+ const callOriginal = originalFetch.bind(window);
133
+ const patched = async (input, init) => {
134
+ const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
135
+ if (ignore(url) || hasInternalHeader(input, init)) return callOriginal(input, init);
136
+ const method = (init?.method ?? (input instanceof Request ? input.method : "GET")).toUpperCase();
137
+ const started = performance.now();
138
+ const elapsed = () => Math.round(performance.now() - started);
139
+ try {
140
+ const response = await callOriginal(input, init);
141
+ record({
142
+ kind: "fetch",
143
+ text: `${method} ${url}`.slice(0, MAX_TEXT),
144
+ status: response.status,
145
+ durationMs: elapsed()
146
+ });
147
+ return response;
148
+ } catch (error) {
149
+ record({
150
+ kind: "fetch",
151
+ text: `${method} ${url} — ${toText([error])}`.slice(0, MAX_TEXT),
152
+ status: 0,
153
+ durationMs: elapsed()
154
+ });
155
+ throw error;
156
+ }
157
+ };
158
+ window.fetch = Object.assign(patched, originalFetch);
159
+ restorers.push(() => {
160
+ window.fetch = originalFetch;
161
+ });
162
+ }
163
+ function observeLongTasks() {
164
+ if (typeof PerformanceObserver === "undefined") return;
165
+ try {
166
+ const observer = new PerformanceObserver((list) => {
167
+ for (const entry of list.getEntries()) {
168
+ const durationMs = Math.round(entry.duration);
169
+ record({
170
+ kind: "longtask",
171
+ text: `long task ${durationMs} ms`,
172
+ durationMs
173
+ });
174
+ }
175
+ });
176
+ observer.observe({
177
+ type: "longtask",
178
+ buffered: true
179
+ });
180
+ restorers.push(() => observer.disconnect());
181
+ } catch {}
182
+ }
183
+ /**
184
+ * Installs the patches on the first call and returns a release function; the
185
+ * last release restores the originals. Reference counted so StrictMode's
186
+ * mount-unmount-mount and two HUDs on one page neither double-patch nor
187
+ * unpatch under each other. The first caller's options win.
188
+ */
189
+ function installCapture({ ignore = isHudRoute } = {}) {
190
+ if (typeof window === "undefined") return () => {};
191
+ if (installs === 0) {
192
+ patchConsole();
193
+ listenErrors();
194
+ patchFetch(ignore);
195
+ observeLongTasks();
196
+ }
197
+ installs++;
198
+ let released = false;
199
+ return () => {
200
+ if (released) return;
201
+ released = true;
202
+ installs--;
203
+ if (installs === 0) while (restorers.length > 0) restorers.pop()?.();
204
+ };
205
+ }
206
+ //#endregion
207
+ //#region src/plugins/observe/view.ts
208
+ const FILTERS = [
209
+ "all",
210
+ "errors",
211
+ "warn",
212
+ "log",
213
+ "fetch",
214
+ "longtask"
215
+ ];
216
+ function matches(entry, filter) {
217
+ if (filter === "all") return true;
218
+ if (filter === "errors") return isError(entry);
219
+ return entry.kind === filter;
220
+ }
221
+ let view = {
222
+ tab: "console",
223
+ filter: "all"
224
+ };
225
+ const listeners = /* @__PURE__ */ new Set();
226
+ function setView(next) {
227
+ view = {
228
+ ...view,
229
+ ...next
230
+ };
231
+ for (const listener of listeners) listener();
232
+ }
233
+ function subscribe(listener) {
234
+ listeners.add(listener);
235
+ return () => listeners.delete(listener);
236
+ }
237
+ const getView = () => view;
238
+ function useView() {
239
+ return useSyncExternalStore(subscribe, getView, getView);
240
+ }
241
+ function useCapture() {
242
+ return useSyncExternalStore(captureBuffer.subscribe, captureBuffer.getSnapshot, captureBuffer.getSnapshot);
243
+ }
244
+ //#endregion
245
+ //#region src/plugins/observe/console-tab.tsx
246
+ function statusLevel(status) {
247
+ if (status === 0 || status >= 500) return "error";
248
+ return status >= 400 ? "warn" : "ok";
249
+ }
250
+ /** Console, fetch and long-task capture, newest first. */
251
+ function ConsoleTab() {
252
+ const entries = useCapture();
253
+ const { filter } = useView();
254
+ const rows = entries.filter((entry) => matches(entry, filter)).slice(-200).reverse();
255
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
256
+ className: "obs-toolbar",
257
+ role: "toolbar",
258
+ "aria-label": "Filter",
259
+ children: [FILTERS.map((f) => /* @__PURE__ */ jsxs("button", {
260
+ type: "button",
261
+ className: "obs-chip",
262
+ "aria-pressed": filter === f,
263
+ onClick: () => setView({ filter: f }),
264
+ children: [f, f !== "all" && /* @__PURE__ */ jsx("span", {
265
+ className: "obs-count",
266
+ children: entries.filter((e) => matches(e, f)).length
267
+ })]
268
+ }, f)), /* @__PURE__ */ jsx("button", {
269
+ type: "button",
270
+ className: "obs-chip obs-clear",
271
+ onClick: () => captureBuffer.clear(),
272
+ children: "clear"
273
+ })]
274
+ }), /* @__PURE__ */ jsx("div", {
275
+ className: "obs-log",
276
+ children: rows.length === 0 ? /* @__PURE__ */ jsx("p", {
277
+ className: "empty",
278
+ children: filter === "all" ? "Nothing captured yet. Console output, requests and long tasks land here." : `No ${filter} captured.`
279
+ }) : rows.map((entry) => /* @__PURE__ */ jsx(EntryRow, { entry }, entry.id))
280
+ })] });
281
+ }
282
+ function EntryRow({ entry }) {
283
+ const time = new Date(entry.ts).toLocaleTimeString(void 0, { hour12: false });
284
+ return /* @__PURE__ */ jsxs("div", {
285
+ className: "obs-row",
286
+ "data-kind": entry.kind,
287
+ children: [
288
+ /* @__PURE__ */ jsx("span", {
289
+ className: "obs-time",
290
+ children: time
291
+ }),
292
+ /* @__PURE__ */ jsx("span", {
293
+ className: "obs-kind",
294
+ "data-kind": entry.kind,
295
+ children: entry.kind
296
+ }),
297
+ /* @__PURE__ */ jsx("span", {
298
+ className: "obs-text",
299
+ title: entry.text,
300
+ children: entry.text
301
+ }),
302
+ entry.status !== void 0 && /* @__PURE__ */ jsx("span", {
303
+ className: "obs-status",
304
+ "data-level": statusLevel(entry.status),
305
+ children: entry.status || "ERR"
306
+ }),
307
+ entry.durationMs !== void 0 && /* @__PURE__ */ jsxs("span", {
308
+ className: "obs-ms",
309
+ children: [entry.durationMs, " ms"]
310
+ })
311
+ ]
312
+ });
313
+ }
314
+ //#endregion
315
+ //#region src/plugins/observe/info-tab.tsx
316
+ /** App, build, browser and user snapshot: what runs here right now. */
317
+ function InfoTab({ app, env, useUser }) {
318
+ const user = useUser();
319
+ const viewport = useViewportLabel();
320
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
321
+ /* @__PURE__ */ jsxs(Section, {
322
+ title: "App",
323
+ children: [
324
+ /* @__PURE__ */ jsx(Row, {
325
+ label: "App",
326
+ value: app
327
+ }),
328
+ /* @__PURE__ */ jsx(Row, {
329
+ label: "Origin",
330
+ value: window.location.origin,
331
+ copy: true
332
+ }),
333
+ /* @__PURE__ */ jsx(Row, {
334
+ label: "Path",
335
+ value: window.location.pathname + window.location.search,
336
+ copy: true
337
+ })
338
+ ]
339
+ }),
340
+ /* @__PURE__ */ jsxs(Section, {
341
+ title: "Build",
342
+ children: [/* @__PURE__ */ jsx(Row, {
343
+ label: "React",
344
+ value: version
345
+ }), Object.entries(env).map(([label, value]) => /* @__PURE__ */ jsx(Row, {
346
+ label,
347
+ value: value ?? "not set",
348
+ copy: value !== void 0
349
+ }, label))]
350
+ }),
351
+ user && /* @__PURE__ */ jsxs(Section, {
352
+ title: "User",
353
+ children: [
354
+ user.name !== void 0 && /* @__PURE__ */ jsx(Row, {
355
+ label: "Name",
356
+ value: user.name
357
+ }),
358
+ user.email !== void 0 && /* @__PURE__ */ jsx(Row, {
359
+ label: "Email",
360
+ value: user.email,
361
+ copy: true
362
+ }),
363
+ user.id !== void 0 && /* @__PURE__ */ jsx(Row, {
364
+ label: "ID",
365
+ value: user.id,
366
+ copy: true
367
+ }),
368
+ user.role !== void 0 && /* @__PURE__ */ jsx(Row, {
369
+ label: "Role",
370
+ value: user.role
371
+ }),
372
+ Object.entries(user.extra ?? {}).map(([label, value]) => /* @__PURE__ */ jsx(Row, {
373
+ label,
374
+ value
375
+ }, label))
376
+ ]
377
+ }),
378
+ /* @__PURE__ */ jsxs(Section, {
379
+ title: "Browser",
380
+ children: [
381
+ /* @__PURE__ */ jsx(Row, {
382
+ label: "Viewport",
383
+ value: viewport
384
+ }),
385
+ /* @__PURE__ */ jsx(Row, {
386
+ label: "Language",
387
+ value: navigator.language
388
+ }),
389
+ /* @__PURE__ */ jsx(Row, {
390
+ label: "User agent",
391
+ value: navigator.userAgent,
392
+ copy: true
393
+ })
394
+ ]
395
+ })
396
+ ] });
397
+ }
398
+ function viewportLabel() {
399
+ const dark = window.matchMedia?.("(prefers-color-scheme: dark)").matches ?? false;
400
+ return `${window.innerWidth}×${window.innerHeight} @${window.devicePixelRatio}x · ${dark ? "dark" : "light"}`;
401
+ }
402
+ function useViewportLabel() {
403
+ const [label, setLabel] = useState(viewportLabel);
404
+ useEffect(() => {
405
+ const update = () => setLabel(viewportLabel());
406
+ window.addEventListener("resize", update);
407
+ return () => window.removeEventListener("resize", update);
408
+ }, []);
409
+ return label;
410
+ }
411
+ function Section({ title, children }) {
412
+ return /* @__PURE__ */ jsxs("section", {
413
+ className: "obs-section",
414
+ "aria-label": title,
415
+ children: [/* @__PURE__ */ jsx("h4", {
416
+ className: "obs-heading",
417
+ children: title
418
+ }), /* @__PURE__ */ jsx("dl", {
419
+ className: "obs-info",
420
+ children
421
+ })]
422
+ });
423
+ }
424
+ function Row({ label, value, copy = false }) {
425
+ const [copied, setCopied] = useState(false);
426
+ useEffect(() => {
427
+ if (!copied) return;
428
+ const timer = setTimeout(() => setCopied(false), 1200);
429
+ return () => clearTimeout(timer);
430
+ }, [copied]);
431
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("dt", {
432
+ title: label,
433
+ children: label
434
+ }), /* @__PURE__ */ jsx("dd", { children: /* @__PURE__ */ jsxs("div", {
435
+ className: "obs-value",
436
+ children: [/* @__PURE__ */ jsx("span", {
437
+ title: value,
438
+ children: value
439
+ }), copy && /* @__PURE__ */ jsx("button", {
440
+ type: "button",
441
+ className: "obs-copy",
442
+ "aria-label": `Copy ${label}`,
443
+ onClick: () => {
444
+ navigator.clipboard?.writeText(value).then(() => setCopied(true), () => {});
445
+ },
446
+ children: copied ? "copied" : "copy"
447
+ })]
448
+ }) })] });
449
+ }
450
+ //#endregion
451
+ //#region src/plugins/observe/styles.ts
452
+ /**
453
+ * The observe panel's styles. The panel renders them in its own `<style>`,
454
+ * which still sits inside the HUD's shadow root, so they ship only with this
455
+ * plugin and build on the shell's `--hud-*` tokens.
456
+ */
457
+ const OBSERVE_CSS = `
458
+ /* Fixed width, so switching tabs does not resize the floating panel. */
459
+ .obs { display: flex; flex-direction: column; gap: 8px; width: 396px; max-width: 100%; }
460
+
461
+ .obs-tabs { display: flex; gap: 2px; border-bottom: 1px solid var(--hud-border); }
462
+ .hud .obs-tab {
463
+ padding: 3px 10px 5px;
464
+ margin-bottom: -1px;
465
+ border-bottom: 2px solid transparent;
466
+ color: var(--hud-fg-muted);
467
+ }
468
+ .hud .obs-tab[aria-selected="true"] { border-bottom-color: var(--hud-accent); color: var(--hud-fg); font-weight: 600; }
469
+
470
+ .obs-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 2px; }
471
+ .hud .obs-chip {
472
+ padding: 1px 7px;
473
+ border-radius: 999px;
474
+ font-family: var(--hud-mono);
475
+ font-size: 11px;
476
+ color: var(--hud-fg-muted);
477
+ }
478
+ .hud .obs-chip:hover { background: var(--hud-bg-subtle); }
479
+ .hud .obs-chip[aria-pressed="true"] { background: var(--hud-bg-subtle); color: var(--hud-fg); }
480
+ .obs-chip .obs-count { margin-left: 4px; opacity: 0.6; }
481
+ .hud .obs-clear { margin-left: auto; }
482
+
483
+ .obs-log {
484
+ max-height: 320px;
485
+ overflow: auto;
486
+ border: 1px solid var(--hud-border);
487
+ border-radius: 4px;
488
+ font-family: var(--hud-mono);
489
+ font-size: 11px;
490
+ }
491
+ .obs-row { display: flex; align-items: baseline; gap: 8px; padding: 2px 6px; }
492
+ .obs-row:hover { background: var(--hud-bg-subtle); }
493
+ .obs-row + .obs-row { border-top: 1px solid var(--hud-border); }
494
+ .obs-time, .obs-ms { flex-shrink: 0; color: var(--hud-fg-muted); }
495
+ .obs-kind { flex-shrink: 0; width: 56px; text-transform: uppercase; color: var(--hud-fg-muted); }
496
+ .obs-kind[data-kind="warn"] { color: var(--hud-warn); }
497
+ .obs-kind[data-kind="error"] { color: var(--hud-poor); }
498
+ .obs-kind[data-kind="fetch"] { color: var(--hud-accent); }
499
+ .obs-text { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
500
+ .obs-status { flex-shrink: 0; color: var(--hud-good); }
501
+ .obs-status[data-level="warn"] { color: var(--hud-warn); }
502
+ .obs-status[data-level="error"] { color: var(--hud-poor); }
503
+ .obs-log .empty { margin: 0; padding: 6px; font-family: var(--hud-font); }
504
+
505
+ .obs-section + .obs-section { margin-top: 10px; }
506
+ .obs-heading {
507
+ margin: 0 0 4px;
508
+ font-size: 10px;
509
+ font-weight: 600;
510
+ letter-spacing: 0.06em;
511
+ text-transform: uppercase;
512
+ color: var(--hud-fg-muted);
513
+ }
514
+ .obs-info {
515
+ display: grid;
516
+ grid-template-columns: 88px minmax(0, 1fr);
517
+ gap: 2px 12px;
518
+ margin: 0;
519
+ }
520
+ .obs-info dt { color: var(--hud-fg-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
521
+ .obs-info dd { margin: 0; min-width: 0; font-family: var(--hud-mono); }
522
+ .obs-value { display: flex; align-items: center; gap: 6px; }
523
+ .obs-value span { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
524
+ .hud .obs-copy { flex-shrink: 0; padding: 0 4px; border-radius: 4px; font-size: 10px; color: var(--hud-fg-muted); }
525
+ .hud .obs-copy:hover { background: var(--hud-bg-subtle); color: var(--hud-fg); }
526
+ `;
527
+ //#endregion
528
+ //#region src/plugins/observe/index.tsx
529
+ const TABS = [
530
+ {
531
+ id: "console",
532
+ label: "Console"
533
+ },
534
+ {
535
+ id: "info",
536
+ label: "Info"
537
+ },
538
+ {
539
+ id: "vitals",
540
+ label: "Vitals"
541
+ }
542
+ ];
543
+ const NO_ENV = {};
544
+ const noUser = () => void 0;
545
+ function useErrorCount() {
546
+ return useCapture().filter(isError).length;
547
+ }
548
+ /**
549
+ * Console and network capture, an app/build/browser/user snapshot and the
550
+ * page's Web Vitals. The pill counts errors; opening the panel while there are
551
+ * any lands on the console filtered to them.
552
+ */
553
+ function observe({ env = NO_ENV, useUser = noUser, ignore } = {}) {
554
+ function ObservePill() {
555
+ useEffect(() => installCapture(ignore ? { ignore } : {}), []);
556
+ useEffect(() => startVitals(), []);
557
+ const errors = useErrorCount();
558
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
559
+ className: "dot",
560
+ "data-rating": errors > 0 ? "poor" : "good"
561
+ }), errors === 1 ? "1 error" : `${errors} errors`] });
562
+ }
563
+ function ObservePanel({ ctx }) {
564
+ const { tab } = useView();
565
+ useLayoutEffect(() => {
566
+ if (captureBuffer.getSnapshot().some(isError)) setView({
567
+ tab: "console",
568
+ filter: "errors"
569
+ });
570
+ }, []);
571
+ return /* @__PURE__ */ jsxs("div", {
572
+ className: "obs",
573
+ children: [
574
+ /* @__PURE__ */ jsx("style", { children: OBSERVE_CSS }),
575
+ /* @__PURE__ */ jsx("div", {
576
+ className: "obs-tabs",
577
+ role: "tablist",
578
+ "aria-label": "Observe",
579
+ children: TABS.map(({ id, label }) => /* @__PURE__ */ jsx("button", {
580
+ type: "button",
581
+ role: "tab",
582
+ className: "obs-tab",
583
+ "aria-selected": tab === id,
584
+ onClick: () => setView({ tab: id }),
585
+ children: label
586
+ }, id))
587
+ }),
588
+ /* @__PURE__ */ jsxs("div", {
589
+ role: "tabpanel",
590
+ "aria-label": TABS.find((t) => t.id === tab)?.label,
591
+ children: [
592
+ tab === "console" && /* @__PURE__ */ jsx(ConsoleTab, {}),
593
+ tab === "info" && /* @__PURE__ */ jsx(InfoTab, {
594
+ app: ctx.app,
595
+ env,
596
+ useUser
597
+ }),
598
+ tab === "vitals" && /* @__PURE__ */ jsx(VitalsPanel, {})
599
+ ]
600
+ })
601
+ ]
602
+ });
603
+ }
604
+ return definePlugin({
605
+ id: "observe",
606
+ title: "Observe",
607
+ pill: ObservePill,
608
+ panel: ObservePanel,
609
+ commands: [{
610
+ id: "clear",
611
+ title: "Clear console capture",
612
+ run: () => captureBuffer.clear()
613
+ }]
614
+ });
615
+ }
616
+ //#endregion
617
+ export { observe };
@@ -0,0 +1,82 @@
1
+ import { d as HudRoutes } from "../../plugins-CQ_0w1ot.js";
2
+ import { a as CiSnapshot, c as TelemetryRange, i as CiRun, l as TelemetryResponse, n as CiProvider, o as CiState, r as CiResponse, s as TelemetryProvider, t as CiDeployment, u as TelemetrySnapshot } from "../../types-f3jlNxzw.js";
3
+ //#region src/plugins/ops/errors.d.ts
4
+ /**
5
+ * A provider failure whose message is safe to send to the client (it names
6
+ * the upstream and its status, never a URL with credentials or a token).
7
+ * The routes answer 502 with it; any other error becomes a generic 502.
8
+ */
9
+ export declare class OpsProviderError extends Error {
10
+ constructor(message: string);
11
+ }
12
+ //#endregion
13
+ //#region src/plugins/ops/github-actions.d.ts
14
+ interface GithubActionsOptions {
15
+ /** `owner/name`. */
16
+ repo: string;
17
+ /**
18
+ * Branch whose runs the pill shows. Default: the first of `HUD_GIT_BRANCH`,
19
+ * `VERCEL_GIT_COMMIT_REF`, `GITHUB_HEAD_REF`, `GITHUB_REF_NAME`, then the
20
+ * branch checked out where the server runs (`.git/HEAD`). With no branch
21
+ * the panel lists the repo's latest runs and the pill shows no state.
22
+ */
23
+ branch?: string | (() => string | null | Promise<string | null>);
24
+ /** Env var holding the token, read on each request. Default `GITHUB_TOKEN`. */
25
+ tokenEnv?: string;
26
+ /** Runs listed in the panel. Default 10. */
27
+ limit?: number;
28
+ /** Seconds a GitHub answer is reused, to stay inside the rate limit. Default 15. */
29
+ cacheSeconds?: number;
30
+ /** API root, for GitHub Enterprise Server. Default `https://api.github.com`. */
31
+ apiUrl?: string;
32
+ }
33
+ /**
34
+ * GitHub Actions as the `ops` plugin's CI provider. Server only: the token is
35
+ * read from env on each request and only ever sent to GitHub.
36
+ *
37
+ * ```ts
38
+ * ops({ ci: githubActions({ repo: "hansenexus/hud" }) })
39
+ * ```
40
+ */
41
+ export declare function githubActions(options: GithubActionsOptions): CiProvider;
42
+ //#endregion
43
+ //#region src/plugins/ops/signoz.d.ts
44
+ interface SignozOptions {
45
+ /** OpenTelemetry `service.name` of the app. */
46
+ service: string;
47
+ /** Env var holding the SigNoz query URL, read on each request. Default `SIGNOZ_QUERY_URL`. */
48
+ urlEnv?: string;
49
+ /** Env var holding an API key, sent as `SIGNOZ-API-KEY` when set. Default `SIGNOZ_API_KEY`. */
50
+ apiKeyEnv?: string;
51
+ }
52
+ /**
53
+ * SigNoz as the `ops` plugin's telemetry provider: error rate, p95 latency and
54
+ * request rate of one service over its PromQL `query_range` API. Server only:
55
+ * the URL and API key come from env on each request.
56
+ *
57
+ * ```ts
58
+ * ops({ telemetry: signoz({ service: "hansenexus" }) })
59
+ * ```
60
+ */
61
+ export declare function signoz(options: SignozOptions): TelemetryProvider;
62
+ //#endregion
63
+ //#region src/plugins/ops/server.d.ts
64
+ export interface OpsServerOptions {
65
+ /** CI provider behind `GET /ops/ci`, such as `githubActions({ repo })`. */
66
+ ci?: CiProvider;
67
+ /** Telemetry provider behind `GET /ops/telemetry`, such as `signoz({ service })`. */
68
+ telemetry?: TelemetryProvider;
69
+ }
70
+ /**
71
+ * Server half of the `ops` plugin. Pass it to `createHudHandler`; each
72
+ * provider adds its route, so an app without telemetry serves no
73
+ * `/telemetry`. Tokens and keys stay in the providers: responses carry
74
+ * run data, numbers and the name of a missing env var, never a secret.
75
+ */
76
+ export declare function ops({ ci, telemetry }?: OpsServerOptions): {
77
+ readonly id: "ops";
78
+ readonly capability: "cicd.read";
79
+ readonly routes: HudRoutes;
80
+ };
81
+ //#endregion
82
+ export type { CiDeployment, CiProvider, CiResponse, CiRun, CiSnapshot, CiState, GithubActionsOptions, SignozOptions, TelemetryProvider, TelemetryRange, TelemetryResponse, TelemetrySnapshot };