@hansenexus/hud 0.1.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 (90) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +48 -0
  3. package/chunk-0cj8dkk7.js +127 -0
  4. package/chunk-1atxwdhw.js +48 -0
  5. package/chunk-24vrg16w.js +39 -0
  6. package/chunk-3j9sm249.js +26 -0
  7. package/chunk-50pq4sg6.js +41 -0
  8. package/chunk-5gtx3pza.js +9 -0
  9. package/chunk-6gw4x08j.js +264 -0
  10. package/chunk-6jt8yg4m.js +117 -0
  11. package/chunk-71se605m.js +85 -0
  12. package/chunk-82nm9wcg.js +316 -0
  13. package/chunk-8tam7r3h.js +68 -0
  14. package/chunk-babjpqkb.js +52 -0
  15. package/chunk-c5y856ad.js +98 -0
  16. package/chunk-ghxh8926.js +172 -0
  17. package/chunk-hc2g0n35.js +24 -0
  18. package/chunk-k0vr8dsy.js +179 -0
  19. package/chunk-k50yfp5d.js +166 -0
  20. package/chunk-kyeb6qeq.js +95 -0
  21. package/chunk-msqkkqw7.js +140 -0
  22. package/chunk-nm82wr8r.js +58 -0
  23. package/chunk-r06bpmy6.js +79 -0
  24. package/chunk-t9eh4t0b.js +89 -0
  25. package/chunk-tf774c4w.js +115 -0
  26. package/chunk-v086j53b.js +19 -0
  27. package/chunk-wspz08q7.js +67 -0
  28. package/chunk-xezxydm3.js +47 -0
  29. package/chunk-xjn0896g.js +574 -0
  30. package/chunk-xztw0xyt.js +113 -0
  31. package/chunk-zzrpd92r.js +172 -0
  32. package/core/access.d.mts +53 -0
  33. package/core/access.js +27 -0
  34. package/core/api-types.d.mts +146 -0
  35. package/core/api-types.js +9 -0
  36. package/core/capabilities.d.mts +27 -0
  37. package/core/capabilities.js +14 -0
  38. package/core/capture-buffer.d.mts +37 -0
  39. package/core/capture-buffer.js +9 -0
  40. package/core/force-state.d.mts +68 -0
  41. package/core/force-state.js +21 -0
  42. package/core/mock-cookie.d.mts +30 -0
  43. package/core/mock-cookie.js +15 -0
  44. package/core/types.d.mts +59 -0
  45. package/core/types.js +9 -0
  46. package/hud-provider.d.mts +38 -0
  47. package/hud-provider.js +15 -0
  48. package/hud.d.mts +15 -0
  49. package/hud.js +18 -0
  50. package/index.d.mts +94 -0
  51. package/index.js +170 -0
  52. package/lazy-panel.d.mts +28 -0
  53. package/lazy-panel.js +11 -0
  54. package/package.json +153 -0
  55. package/panels/cicd-panel.d.mts +9 -0
  56. package/panels/cicd-panel.js +13 -0
  57. package/panels/debug-info-panel.d.mts +21 -0
  58. package/panels/debug-info-panel.js +9 -0
  59. package/panels/dispatch-panel.d.mts +10 -0
  60. package/panels/dispatch-panel.js +14 -0
  61. package/panels/env-info-panel.d.mts +9 -0
  62. package/panels/env-info-panel.js +12 -0
  63. package/panels/feature-flags-panel.d.mts +26 -0
  64. package/panels/feature-flags-panel.js +9 -0
  65. package/panels/force-state-panel.d.mts +11 -0
  66. package/panels/force-state-panel.js +113 -0
  67. package/panels/info-row.d.mts +21 -0
  68. package/panels/info-row.js +14 -0
  69. package/panels/mock-seed-panel.d.mts +15 -0
  70. package/panels/mock-seed-panel.js +14 -0
  71. package/panels/network-console-panel.d.mts +6 -0
  72. package/panels/network-console-panel.js +9 -0
  73. package/panels/role-switcher-panel.d.mts +28 -0
  74. package/panels/role-switcher-panel.js +9 -0
  75. package/panels/sessions-panel.d.mts +6 -0
  76. package/panels/sessions-panel.js +13 -0
  77. package/panels/tokens-panel.d.mts +38 -0
  78. package/panels/tokens-panel.js +9 -0
  79. package/panels/unlock-panel.d.mts +33 -0
  80. package/panels/unlock-panel.js +14 -0
  81. package/panels/variants-panel.d.mts +51 -0
  82. package/panels/variants-panel.js +12 -0
  83. package/panels/vitals-panel.d.mts +11 -0
  84. package/panels/vitals-panel.js +15 -0
  85. package/server/guard.d.mts +42 -0
  86. package/server/guard.js +12 -0
  87. package/server/unlock.d.mts +28 -0
  88. package/server/unlock.js +108 -0
  89. package/state-boundary.d.mts +25 -0
  90. package/state-boundary.js +54 -0
@@ -0,0 +1,113 @@
1
+ // ../ui/lib/utils.ts
2
+ import { clsx } from "clsx";
3
+ import { twMerge } from "tailwind-merge";
4
+ function cn(...inputs) {
5
+ return twMerge(clsx(inputs));
6
+ }
7
+
8
+ // ../ui/components/badge.tsx
9
+ import { Slot } from "@radix-ui/react-slot";
10
+ import { cva } from "class-variance-authority";
11
+ import { jsx } from "react/jsx-runtime";
12
+ var badgeVariants = cva("inline-flex items-center justify-center rounded-full border border-transparent px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden", {
13
+ variants: {
14
+ variant: {
15
+ default: "bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
16
+ secondary: "bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
17
+ destructive: "bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
18
+ outline: "border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
19
+ ghost: "[a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
20
+ link: "text-primary underline-offset-4 [a&]:hover:underline",
21
+ success: "bg-emerald-500/15 text-emerald-700 border-emerald-500/20 dark:text-emerald-300 dark:bg-emerald-400/15 dark:border-emerald-400/25 [a&]:hover:bg-emerald-500/25",
22
+ warning: "bg-amber-500/15 text-amber-700 border-amber-500/20 dark:text-amber-300 dark:bg-amber-400/15 dark:border-amber-400/25 [a&]:hover:bg-amber-500/25",
23
+ info: "bg-blue-500/15 text-blue-700 border-blue-500/20 dark:text-blue-300 dark:bg-blue-400/15 dark:border-blue-400/25 [a&]:hover:bg-blue-500/25",
24
+ muted: "bg-muted text-muted-foreground border-transparent [a&]:hover:bg-muted/80"
25
+ }
26
+ },
27
+ defaultVariants: {
28
+ variant: "default"
29
+ }
30
+ });
31
+ function Badge({
32
+ className,
33
+ variant = "default",
34
+ asChild = false,
35
+ ...props
36
+ }) {
37
+ const Comp = asChild ? Slot : "span";
38
+ return /* @__PURE__ */ jsx(Comp, {
39
+ "data-slot": "badge",
40
+ "data-variant": variant,
41
+ className: cn(badgeVariants({ variant }), className),
42
+ ...props
43
+ });
44
+ }
45
+
46
+ // ../ui/components/button.tsx
47
+ import { Slot as Slot2 } from "@radix-ui/react-slot";
48
+ import { cva as cva2 } from "class-variance-authority";
49
+ import { jsx as jsx2 } from "react/jsx-runtime";
50
+ var buttonVariants = cva2("inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", {
51
+ variants: {
52
+ variant: {
53
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
54
+ destructive: "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
55
+ outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
56
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
57
+ ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
58
+ link: "text-primary underline-offset-4 hover:underline"
59
+ },
60
+ size: {
61
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
62
+ xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
63
+ sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
64
+ lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
65
+ icon: "size-9",
66
+ "icon-xs": "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3",
67
+ "icon-sm": "size-8",
68
+ "icon-lg": "size-10"
69
+ }
70
+ },
71
+ defaultVariants: {
72
+ variant: "default",
73
+ size: "default"
74
+ }
75
+ });
76
+ function Button({
77
+ className,
78
+ variant = "default",
79
+ size = "default",
80
+ asChild = false,
81
+ ...props
82
+ }) {
83
+ const Comp = asChild ? Slot2 : "button";
84
+ return /* @__PURE__ */ jsx2(Comp, {
85
+ "data-slot": "button",
86
+ "data-variant": variant,
87
+ "data-size": size,
88
+ className: cn(buttonVariants({ variant, size, className })),
89
+ ...props
90
+ });
91
+ }
92
+
93
+ // ../ui/components/switch.tsx
94
+ import * as SwitchPrimitive from "@radix-ui/react-switch";
95
+ import { jsx as jsx3 } from "react/jsx-runtime";
96
+ "use client";
97
+ function Switch({
98
+ className,
99
+ size = "default",
100
+ ...props
101
+ }) {
102
+ return /* @__PURE__ */ jsx3(SwitchPrimitive.Root, {
103
+ "data-slot": "switch",
104
+ "data-size": size,
105
+ className: cn("peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 group/switch inline-flex shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-[1.15rem] data-[size=default]:w-8 data-[size=sm]:h-3.5 data-[size=sm]:w-6", className),
106
+ ...props,
107
+ children: /* @__PURE__ */ jsx3(SwitchPrimitive.Thumb, {
108
+ "data-slot": "switch-thumb",
109
+ className: cn("bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block rounded-full ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0")
110
+ })
111
+ });
112
+ }
113
+ export { cn, Badge, Button, Switch };
@@ -0,0 +1,172 @@
1
+ import {
2
+ usePoll
3
+ } from "./chunk-xezxydm3.js";
4
+ import {
5
+ useHud
6
+ } from "./chunk-wspz08q7.js";
7
+ import {
8
+ InfoRow,
9
+ LoadingSpinner,
10
+ PanelSection
11
+ } from "./chunk-kyeb6qeq.js";
12
+ import {
13
+ Badge
14
+ } from "./chunk-xztw0xyt.js";
15
+
16
+ // panels/cicd-panel.tsx
17
+ import { GitBranch, Rocket } from "lucide-react";
18
+ import { jsx, jsxs, Fragment } from "react/jsx-runtime";
19
+ "use client";
20
+ function CicdPanel() {
21
+ const { config } = useHud();
22
+ const apiBase = config.apiBase ?? "/api/hud";
23
+ const { data, error, loading } = usePoll(`${apiBase}/cicd?app=${encodeURIComponent(config.appSlug)}`, 60000);
24
+ if (loading)
25
+ return /* @__PURE__ */ jsx(LoadingSpinner, {});
26
+ if (error) {
27
+ return /* @__PURE__ */ jsxs("p", {
28
+ className: "px-2 text-[10px] text-amber-500",
29
+ children: [
30
+ "hub unreachable — ",
31
+ error
32
+ ]
33
+ });
34
+ }
35
+ if (!data)
36
+ return null;
37
+ return /* @__PURE__ */ jsxs("div", {
38
+ className: "space-y-4",
39
+ children: [
40
+ /* @__PURE__ */ jsx(PanelSection, {
41
+ icon: Rocket,
42
+ title: `Deployed · ${data.app}`,
43
+ children: data.deployed ? /* @__PURE__ */ jsxs(Fragment, {
44
+ children: [
45
+ /* @__PURE__ */ jsx(InfoRow, {
46
+ label: "Image",
47
+ value: data.deployed.image,
48
+ copyable: true
49
+ }),
50
+ /* @__PURE__ */ jsx(InfoRow, {
51
+ label: "Sha (prod tag)",
52
+ value: data.deployed.sha,
53
+ copyable: true
54
+ }),
55
+ data.deployed.lastGoodTag && /* @__PURE__ */ jsx(InfoRow, {
56
+ label: "last-good-tag",
57
+ value: data.deployed.lastGoodTag
58
+ }),
59
+ /* @__PURE__ */ jsx(InfoRow, {
60
+ label: "Ready",
61
+ value: data.deployed.ready
62
+ })
63
+ ]
64
+ }) : /* @__PURE__ */ jsx("p", {
65
+ className: "px-2 text-[10px] text-muted-foreground/60",
66
+ children: "No deployment found for this app (deploy: false, or namespace mismatch)."
67
+ })
68
+ }),
69
+ /* @__PURE__ */ jsxs(PanelSection, {
70
+ icon: GitBranch,
71
+ title: "Recent runs",
72
+ children: [
73
+ data.githubUnconfigured && /* @__PURE__ */ jsx("p", {
74
+ className: "px-2 text-[10px] text-amber-500",
75
+ children: "GITHUB_TOKEN unset — CI runs missing."
76
+ }),
77
+ /* @__PURE__ */ jsx("div", {
78
+ className: "overflow-x-auto",
79
+ children: /* @__PURE__ */ jsxs("table", {
80
+ className: "w-full text-[10.5px]",
81
+ children: [
82
+ /* @__PURE__ */ jsx("thead", {
83
+ children: /* @__PURE__ */ jsxs("tr", {
84
+ className: "text-left text-[9px] uppercase tracking-wider text-muted-foreground",
85
+ children: [
86
+ /* @__PURE__ */ jsx("th", {
87
+ className: "px-2 py-1 font-medium",
88
+ children: "Run"
89
+ }),
90
+ /* @__PURE__ */ jsx("th", {
91
+ className: "px-2 py-1 font-medium",
92
+ children: "Source"
93
+ }),
94
+ /* @__PURE__ */ jsx("th", {
95
+ className: "px-2 py-1 font-medium",
96
+ children: "Status"
97
+ }),
98
+ /* @__PURE__ */ jsx("th", {
99
+ className: "px-2 py-1 font-medium",
100
+ children: "Branch"
101
+ })
102
+ ]
103
+ })
104
+ }),
105
+ /* @__PURE__ */ jsx("tbody", {
106
+ children: data.runs.map((run) => /* @__PURE__ */ jsx(RunRow, {
107
+ run
108
+ }, `${run.source}-${run.id}`))
109
+ })
110
+ ]
111
+ })
112
+ }),
113
+ data.runs.length === 0 && /* @__PURE__ */ jsx("p", {
114
+ className: "px-2 text-[10px] text-muted-foreground/60",
115
+ children: "No runs available."
116
+ })
117
+ ]
118
+ })
119
+ ]
120
+ });
121
+ }
122
+ function runStatusClass(run) {
123
+ const outcome = run.conclusion ?? run.status;
124
+ if (outcome === "success")
125
+ return "text-emerald-500";
126
+ if (outcome === "failure" || outcome === "cancelled")
127
+ return "text-red-500";
128
+ if (run.status === "in_progress" || run.status === "running" || run.status === "waiting") {
129
+ return "text-amber-500";
130
+ }
131
+ return "text-muted-foreground";
132
+ }
133
+ function RunRow({ run }) {
134
+ const title = /* @__PURE__ */ jsx("span", {
135
+ className: "max-w-[260px] truncate font-mono",
136
+ title: run.title,
137
+ children: run.title || run.name
138
+ });
139
+ return /* @__PURE__ */ jsxs("tr", {
140
+ className: "border-t border-border/40 hover:bg-muted/30",
141
+ children: [
142
+ /* @__PURE__ */ jsx("td", {
143
+ className: "px-2 py-1",
144
+ children: run.url ? /* @__PURE__ */ jsx("a", {
145
+ href: run.url,
146
+ target: "_blank",
147
+ rel: "noreferrer",
148
+ className: "hover:underline",
149
+ children: title
150
+ }) : title
151
+ }),
152
+ /* @__PURE__ */ jsx("td", {
153
+ className: "px-2 py-1",
154
+ children: /* @__PURE__ */ jsx(Badge, {
155
+ variant: "outline",
156
+ className: "h-4 px-1.5 text-[9px] text-muted-foreground",
157
+ children: run.source
158
+ })
159
+ }),
160
+ /* @__PURE__ */ jsx("td", {
161
+ className: `px-2 py-1 font-mono ${runStatusClass(run)}`,
162
+ children: run.conclusion ?? run.status
163
+ }),
164
+ /* @__PURE__ */ jsx("td", {
165
+ className: "px-2 py-1 font-mono text-muted-foreground",
166
+ children: run.branch ?? "—"
167
+ })
168
+ ]
169
+ });
170
+ }
171
+
172
+ export { CicdPanel };
@@ -0,0 +1,53 @@
1
+ import { HudEnv } from './types.mjs';
2
+ import 'lucide-react';
3
+ import 'react';
4
+
5
+ /**
6
+ * Access ladder for the hud: the second lock on top of the env matrix.
7
+ *
8
+ * `isHudVisible()` decides whether the hud exists at all (the flag). This
9
+ * module decides whether the operator opening it has proved who they are:
10
+ * outside development every deployed tier additionally needs an unlock
11
+ * cookie signed with `HUD_ACCESS_PASSWORD` (server-only, 1Password).
12
+ *
13
+ * Levels mirror `resolveDevRoutesAccess()` in @hn-monorepo/dev-routes so the
14
+ * vocabulary is the same across both dev surfaces:
15
+ *
16
+ * - `open`: development, no password.
17
+ * - `password`: preview/staging, flag + password, 12 h unlock.
18
+ * - `flag`: production, flag + password, 2 h unlock, observe-only caps.
19
+ * - `denied`: explicitly disabled; nothing unlocks it.
20
+ *
21
+ * The token is an HMAC over its own issue time, so it carries no secret and
22
+ * cannot be extended by the holder; re-issuing needs the password again.
23
+ */
24
+
25
+ type HudAccess = "open" | "password" | "flag" | "denied";
26
+ /** httpOnly cookie holding the signed unlock token. */
27
+ declare const HUD_UNLOCK_COOKIE = "hud-unlock";
28
+ /** Unlock lifetime per tier. Production is deliberately the shortest window. */
29
+ declare const HUD_UNLOCK_TTL_MS: Record<HudAccess, number>;
30
+ declare function resolveHudAccess(env?: HudEnv): HudAccess;
31
+ /** Whether this tier needs a valid unlock cookie before any hud data flows. */
32
+ declare function hudRequiresUnlock(access: HudAccess): boolean;
33
+ /** TTL for the tier the current env resolves to. */
34
+ declare function hudUnlockTtlMs(access: HudAccess): number;
35
+ /** Length-independent, constant-time comparison of two hex digests. */
36
+ declare function timingSafeEqual(a: string, b: string): boolean;
37
+ /**
38
+ * Mint an unlock token. Format `<issuedAt>.<hmac>`; the issue time is in the
39
+ * clear on purpose so expiry is verifiable without server state, and it is
40
+ * covered by the signature so it cannot be back-dated.
41
+ */
42
+ declare function signHudUnlockToken(password: string, issuedAt?: number): Promise<string>;
43
+ interface HudUnlockVerifyOptions {
44
+ now?: number;
45
+ ttlMs?: number;
46
+ }
47
+ declare function verifyHudUnlockToken(token: string | undefined, password: string | undefined, options?: HudUnlockVerifyOptions): Promise<boolean>;
48
+ /** Parse a single cookie out of a raw `Cookie:` header. */
49
+ declare function readCookie(header: string | null | undefined, name: string): string | undefined;
50
+ /** Serialized `Set-Cookie` value for the unlock token (or its removal). */
51
+ declare function hudUnlockCookieString(token: string | null, ttlMs: number): string;
52
+
53
+ export { HUD_UNLOCK_COOKIE, HUD_UNLOCK_TTL_MS, type HudAccess, type HudUnlockVerifyOptions, hudRequiresUnlock, hudUnlockCookieString, hudUnlockTtlMs, readCookie, resolveHudAccess, signHudUnlockToken, timingSafeEqual, verifyHudUnlockToken };
package/core/access.js ADDED
@@ -0,0 +1,27 @@
1
+ import {
2
+ HUD_UNLOCK_COOKIE,
3
+ HUD_UNLOCK_TTL_MS,
4
+ hudRequiresUnlock,
5
+ hudUnlockCookieString,
6
+ hudUnlockTtlMs,
7
+ readCookie,
8
+ resolveHudAccess,
9
+ signHudUnlockToken,
10
+ timingSafeEqual,
11
+ verifyHudUnlockToken
12
+ } from "../chunk-r06bpmy6.js";
13
+ import"../chunk-babjpqkb.js";
14
+ import"../chunk-v086j53b.js";
15
+ import"../chunk-5gtx3pza.js";
16
+ export {
17
+ verifyHudUnlockToken,
18
+ timingSafeEqual,
19
+ signHudUnlockToken,
20
+ resolveHudAccess,
21
+ readCookie,
22
+ hudUnlockTtlMs,
23
+ hudUnlockCookieString,
24
+ hudRequiresUnlock,
25
+ HUD_UNLOCK_TTL_MS,
26
+ HUD_UNLOCK_COOKIE
27
+ };
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Response shapes of the hud hub routes (served by console under /api/hud/*,
3
+ * thin proxies over console's src/lib clients — see the hud architecture
4
+ * decision). Shared so panels and routes cannot drift apart.
5
+ */
6
+ interface HudSignozResponse {
7
+ ok: boolean;
8
+ service: string;
9
+ range: string;
10
+ /** "mock" when SIGNOZ_QUERY_URL is unset — the client generates demo data. */
11
+ source: "signoz" | "mock";
12
+ errorRate: number | null;
13
+ p95Latency: number | null;
14
+ requestCount: number | null;
15
+ }
16
+ interface HudCicdRun {
17
+ id: number;
18
+ name: string;
19
+ title: string;
20
+ status: string;
21
+ conclusion?: string | null;
22
+ branch?: string;
23
+ sha?: string;
24
+ url?: string;
25
+ startedAt?: string;
26
+ source: "github";
27
+ }
28
+ interface HudCicdDeployed {
29
+ image: string;
30
+ /** Prod image tags are the deployed commit sha (deploy-production.yml). */
31
+ sha: string;
32
+ lastGoodTag?: string;
33
+ ready: string;
34
+ }
35
+ interface HudCicdResponse {
36
+ ok: boolean;
37
+ app: string;
38
+ /** null when no deployment exists for the app (e.g. deploy: false apps). */
39
+ deployed: HudCicdDeployed | null;
40
+ runs: HudCicdRun[];
41
+ githubUnconfigured?: boolean;
42
+ }
43
+ /** Grabbed-element anchor — same shape as console's designReviewComments anchor. */
44
+ interface HudAnchor {
45
+ selector: string;
46
+ label: string;
47
+ /** Rect in viewport percentages, so it survives viewport changes. */
48
+ x: number;
49
+ y: number;
50
+ width: number;
51
+ height: number;
52
+ }
53
+ interface HudComponentHint {
54
+ name: string;
55
+ path: string;
56
+ }
57
+ interface HudDispatchPayload {
58
+ app: string;
59
+ env: string;
60
+ route: string;
61
+ anchor: HudAnchor;
62
+ /** Best-effort source guesses (route manifest / component manifest). */
63
+ componentHints: HudComponentHint[];
64
+ /** Tail of captured console warns/errors — PII-scrubbed by truncation. */
65
+ recentErrors: string[];
66
+ vitals: Record<string, number>;
67
+ grabbedAt: string;
68
+ }
69
+ type HudDispatchTarget = "docket" | "queue" | "hermes";
70
+ interface HudDispatchRequest {
71
+ target: HudDispatchTarget;
72
+ note: string;
73
+ payload: HudDispatchPayload;
74
+ }
75
+ /**
76
+ * A plain work order, no HUD grab attached (#424 PR3). This is what the
77
+ * kommandant desktop cockpit sends: a board card (or a hand-typed form)
78
+ * becomes a docket dispatch without any DOM anchor to point at. Always goes
79
+ * through docket; there is no queue fallback for orders.
80
+ */
81
+ interface HudOrderDispatchRequest {
82
+ target: "docket";
83
+ order: {
84
+ title: string;
85
+ app?: string;
86
+ /** Comma-free strings — the docket CLI's `--criteria` splits on commas. */
87
+ criteria?: string[];
88
+ /** Extra context, written into the run dir as prompt.md. */
89
+ prompt?: string;
90
+ /** Forge issue to link: order links.issue + draft PR `Closes #N`. */
91
+ issue?: number;
92
+ /** Also spawn the headless agent. Default false: a human drives. */
93
+ agent?: boolean;
94
+ };
95
+ }
96
+ type HudDispatchBody = HudDispatchRequest | HudOrderDispatchRequest;
97
+ declare function isOrderDispatch(body: HudDispatchBody): body is HudOrderDispatchRequest;
98
+ interface HudDispatchResponse {
99
+ ok: boolean;
100
+ target: HudDispatchTarget;
101
+ /** docket run id, when target=docket. */
102
+ runId?: string;
103
+ /** repo-relative queue marker path, when target=queue. */
104
+ markerPath?: string;
105
+ error?: string;
106
+ }
107
+ interface HudBridgeInfo {
108
+ port: number;
109
+ token: string;
110
+ }
111
+ interface HudSessionInfo {
112
+ sessionId: string;
113
+ name?: string;
114
+ branch?: string;
115
+ toplevel?: string;
116
+ kind?: string;
117
+ /** Seconds since the registry file's mtime — the liveness signal. */
118
+ idleSeconds: number;
119
+ startedAt?: string;
120
+ }
121
+ /** One app-provided seed action, offered as a button in the mock/seed panel. */
122
+ interface HudSeedDescriptor {
123
+ id: string;
124
+ label: string;
125
+ /** Convex function ref, `module:fn` (subdirs as `dir/module:fn`). */
126
+ fn: string;
127
+ args?: Record<string, unknown>;
128
+ /** Append `--local` (app runs a local `convex dev --local` backend). */
129
+ local?: boolean;
130
+ description?: string;
131
+ }
132
+ interface HudSeedRequest {
133
+ app: string;
134
+ fn: string;
135
+ args?: Record<string, unknown>;
136
+ local?: boolean;
137
+ }
138
+ interface HudSeedResponse {
139
+ ok: boolean;
140
+ fn: string;
141
+ /** Trimmed tail of the convex CLI output. */
142
+ output?: string;
143
+ error?: string;
144
+ }
145
+
146
+ export { type HudAnchor, type HudBridgeInfo, type HudCicdDeployed, type HudCicdResponse, type HudCicdRun, type HudComponentHint, type HudDispatchBody, type HudDispatchPayload, type HudDispatchRequest, type HudDispatchResponse, type HudDispatchTarget, type HudOrderDispatchRequest, type HudSeedDescriptor, type HudSeedRequest, type HudSeedResponse, type HudSessionInfo, type HudSignozResponse, isOrderDispatch };
@@ -0,0 +1,9 @@
1
+ import"../chunk-5gtx3pza.js";
2
+
3
+ // core/api-types.ts
4
+ function isOrderDispatch(body) {
5
+ return "order" in body && typeof body.order?.title === "string";
6
+ }
7
+ export {
8
+ isOrderDispatch
9
+ };
@@ -0,0 +1,27 @@
1
+ import { HudEnv, HudCapability } from './types.mjs';
2
+ import 'lucide-react';
3
+ import 'react';
4
+
5
+ /**
6
+ * Default-deny capability matrix (see the hud plan). Client-side filtering is
7
+ * convenience only — every server route re-checks its env, and estate calls
8
+ * are additionally scoped by the console hub's token. Staging's
9
+ * `auth.impersonate` is further flag-gated server-side.
10
+ */
11
+ declare const CAPABILITY_MATRIX: Record<HudEnv, readonly HudCapability[]>;
12
+ /**
13
+ * Env ladder, following `resolveDevRoutesAccess()` in @hn-monorepo/dev-routes:
14
+ * explicit kill switch → NODE_ENV (inlined by Next, so prod bundles tree-shake
15
+ * the dev branch) → explicit NEXT_PUBLIC_HUD_ENV (k8s overlays; k3s has no
16
+ * VERCEL_ENV) → VERCEL_ENV heuristic → production.
17
+ */
18
+ declare function resolveHudEnv(): HudEnv;
19
+ /**
20
+ * Whether the hud renders at all. Outside development this requires the
21
+ * explicit opt-in flag — deployed envs get the hud only when their overlay
22
+ * turns it on (staging/preview in M6, production read-only in M7).
23
+ */
24
+ declare function isHudVisible(env?: HudEnv): boolean;
25
+ declare function hasCapability(env: HudEnv, capability: HudCapability): boolean;
26
+
27
+ export { CAPABILITY_MATRIX, hasCapability, isHudVisible, resolveHudEnv };
@@ -0,0 +1,14 @@
1
+ import {
2
+ CAPABILITY_MATRIX,
3
+ hasCapability,
4
+ isHudVisible,
5
+ resolveHudEnv
6
+ } from "../chunk-babjpqkb.js";
7
+ import"../chunk-v086j53b.js";
8
+ import"../chunk-5gtx3pza.js";
9
+ export {
10
+ resolveHudEnv,
11
+ isHudVisible,
12
+ hasCapability,
13
+ CAPABILITY_MATRIX
14
+ };
@@ -0,0 +1,37 @@
1
+ type CaptureKind = "log" | "warn" | "error" | "fetch" | "longtask";
2
+ interface CaptureEntry {
3
+ id: number;
4
+ ts: number;
5
+ kind: CaptureKind;
6
+ text: string;
7
+ status?: number;
8
+ durationMs?: number;
9
+ }
10
+ interface CaptureEntryInput {
11
+ kind: CaptureKind;
12
+ text: string;
13
+ status?: number;
14
+ durationMs?: number;
15
+ }
16
+ /**
17
+ * Fixed-size ring buffer with a referentially-stable snapshot, shaped for
18
+ * `useSyncExternalStore`: the snapshot array is replaced on every mutation
19
+ * and reused between mutations.
20
+ */
21
+ declare class CaptureBuffer {
22
+ private entries;
23
+ private snapshot;
24
+ private listeners;
25
+ private nextId;
26
+ private readonly maxEntries;
27
+ constructor(maxEntries?: number);
28
+ push(input: CaptureEntryInput): CaptureEntry;
29
+ clear(): void;
30
+ getSnapshot: () => readonly CaptureEntry[];
31
+ subscribe: (listener: () => void) => (() => void);
32
+ private emit;
33
+ }
34
+ /** Shared buffer the capture patches write into and the net panel reads from. */
35
+ declare const captureBuffer: CaptureBuffer;
36
+
37
+ export { CaptureBuffer, type CaptureEntry, type CaptureEntryInput, type CaptureKind, captureBuffer };
@@ -0,0 +1,9 @@
1
+ import {
2
+ CaptureBuffer,
3
+ captureBuffer
4
+ } from "../chunk-24vrg16w.js";
5
+ import"../chunk-5gtx3pza.js";
6
+ export {
7
+ captureBuffer,
8
+ CaptureBuffer
9
+ };
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Force-state store: lets a reviewer (HUD panel) or an agent (`?__state=`)
3
+ * render any registered state boundary as loading, empty or error without
4
+ * faking data (PRD #2110, issue #2117).
5
+ *
6
+ * Development only. The only importer outside the hud is `state-boundary.tsx`,
7
+ * which reaches this module behind a `NODE_ENV === "development"` constant, so
8
+ * production builds drop it; `scripts/ci/assert-no-hud-in-bundle.sh` greps the
9
+ * client chunks for FORCE_STATE_MARKER to prove it.
10
+ */
11
+ declare const FORCED_STATES: readonly ["loading", "empty", "error"];
12
+ type ForcedState = (typeof FORCED_STATES)[number];
13
+ /** `?__state=loading` forces top-level boundaries; `?__state=<id>:error` one boundary. */
14
+ declare const FORCE_STATE_PARAM = "__state";
15
+ /**
16
+ * globalThis key of the store singleton, and the bundle-gate marker. Holding
17
+ * the store on globalThis keeps one instance even if the module is evaluated
18
+ * twice (HMR, or a second copy resolved through another node_modules path).
19
+ */
20
+ declare const FORCE_STATE_MARKER = "__HN_FORCE_STATE__";
21
+ interface ForceStateParam {
22
+ state: ForcedState;
23
+ /** Boundary id the param targets; null = every top-level boundary. */
24
+ target: string | null;
25
+ }
26
+ interface StateBoundaryInfo {
27
+ id: string;
28
+ /** Id of the enclosing boundary; null for a top-level one. */
29
+ parentId: string | null;
30
+ }
31
+ interface ForceStateSnapshot {
32
+ boundaries: readonly StateBoundaryInfo[];
33
+ overrides: Readonly<Record<string, ForcedState>>;
34
+ param: ForceStateParam | null;
35
+ }
36
+ /** Parses the value of `?__state=`; anything unrecognised is ignored (null). */
37
+ declare function parseForceStateParam(value: string | null | undefined): ForceStateParam | null;
38
+ /** Reads the param out of a `location.search` string. */
39
+ declare function forceStateParamFromSearch(search: string): ForceStateParam | null;
40
+ /**
41
+ * The state a boundary renders. A panel override for the boundary wins; else
42
+ * the URL param applies to its target, or, untargeted, to the nearest
43
+ * boundaries: the top-level ones. Forcing an outer boundary unmounts the
44
+ * inner ones anyway, so the page-level state is what a screenshot wants.
45
+ */
46
+ declare function resolveForcedState(snapshot: ForceStateSnapshot, boundary: StateBoundaryInfo): ForcedState | null;
47
+ type Listener = () => void;
48
+ declare class ForceStateStore {
49
+ private registrations;
50
+ private overrides;
51
+ private search;
52
+ private snapshot;
53
+ private listeners;
54
+ getSnapshot: () => ForceStateSnapshot;
55
+ subscribe: (listener: Listener) => (() => void);
56
+ /** Registers a mounted boundary; the returned function unregisters it. Mounts are counted. */
57
+ register(id: string, parentId: string | null): () => void;
58
+ /** Forces one boundary (panel toggle); null returns it to auto. */
59
+ force(id: string, state: ForcedState | null): void;
60
+ clearOverrides(): void;
61
+ /** Feeds the current `location.search`; a no-op when it did not change. */
62
+ syncSearch(search: string): void;
63
+ private emit;
64
+ }
65
+ /** The page's store, created on first use. */
66
+ declare function getForceStateStore(): ForceStateStore;
67
+
68
+ export { FORCED_STATES, FORCE_STATE_MARKER, FORCE_STATE_PARAM, type ForceStateParam, type ForceStateSnapshot, ForceStateStore, type ForcedState, type StateBoundaryInfo, forceStateParamFromSearch, getForceStateStore, parseForceStateParam, resolveForcedState };