@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,250 @@
1
+ import { i as definePlugin, n as HudApiError } from "../plugins-DVy_vaYr.js";
2
+ import { d as hudUnlockTtlMs, i as hasCapability, l as hudRequiresUnlock, n as CAPABILITY_MATRIX, o as resolveMatrix, p as resolveHudAccess, r as HUD_CAPABILITIES, s as toHudEnv, t as ACCESS_UNLOCK_CAPABILITY } from "../capabilities-CjulXFQX.js";
3
+ import { useEffect, useState, useSyncExternalStore } from "react";
4
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
+ //#region src/plugins/access/unlock-store.ts
6
+ /**
7
+ * Unlock state of the page's HUD. The browser never sees the password or the
8
+ * token: the cookie is httpOnly and only the server can judge it, so this
9
+ * store asks the unlock route rather than reading anything locally.
10
+ */
11
+ let state = {
12
+ env: "development",
13
+ status: "checking",
14
+ error: null
15
+ };
16
+ const listeners = /* @__PURE__ */ new Set();
17
+ function set(patch) {
18
+ state = {
19
+ ...state,
20
+ ...patch
21
+ };
22
+ for (const listener of listeners) listener();
23
+ }
24
+ function subscribe(listener) {
25
+ listeners.add(listener);
26
+ return () => listeners.delete(listener);
27
+ }
28
+ const getState = () => state;
29
+ function useUnlockState() {
30
+ return useSyncExternalStore(subscribe, getState, getState);
31
+ }
32
+ function errorOf(body) {
33
+ if (body && typeof body === "object" && "error" in body && typeof body.error === "string") return body.error;
34
+ }
35
+ /** Asks `<apiBase>/access/unlock` whether this browser is unlocked. Any failure keeps it shut. */
36
+ async function refreshUnlockStatus(apiBase) {
37
+ try {
38
+ const res = await fetch(`${apiBase.replace(/\/+$/, "")}/access/unlock`, {
39
+ cache: "no-store",
40
+ headers: { accept: "application/json" }
41
+ });
42
+ const body = await res.json().catch(() => ({}));
43
+ if (!res.ok || !body || typeof body !== "object") {
44
+ set({ status: "locked" });
45
+ return;
46
+ }
47
+ const { configured, unlocked } = body;
48
+ set({ status: configured === false ? "unconfigured" : unlocked === true ? "unlocked" : "locked" });
49
+ } catch {
50
+ set({ status: "locked" });
51
+ }
52
+ }
53
+ /** Trades the password for the unlock cookie through the plugin's own route. */
54
+ async function submitPassword(api, password) {
55
+ set({ error: null });
56
+ try {
57
+ if ((await api("/unlock", {
58
+ method: "POST",
59
+ headers: { "content-type": "application/json" },
60
+ body: JSON.stringify({ password })
61
+ }))?.unlocked === true) {
62
+ set({ status: "unlocked" });
63
+ return true;
64
+ }
65
+ set({ error: "unlock refused" });
66
+ } catch (cause) {
67
+ set({ error: cause instanceof HudApiError ? errorOf(cause.body) ?? `unlock refused (${cause.status})` : "unlock failed" });
68
+ }
69
+ return false;
70
+ }
71
+ /**
72
+ * `HudAccessControl.useUnlocked`: development is open, a disabled or unknown
73
+ * tier never unlocks, every deployed tier asks the server once per mount.
74
+ */
75
+ function useUnlocked(envName, apiBase) {
76
+ const env = toHudEnv(envName);
77
+ const access = resolveHudAccess(env);
78
+ const { status } = useUnlockState();
79
+ useEffect(() => {
80
+ const required = hudRequiresUnlock(access);
81
+ set({
82
+ env,
83
+ status: access === "open" ? "unlocked" : required ? "checking" : "locked"
84
+ });
85
+ if (required) refreshUnlockStatus(apiBase);
86
+ }, [
87
+ env,
88
+ access,
89
+ apiBase
90
+ ]);
91
+ if (access === "open") return true;
92
+ if (access === "denied") return false;
93
+ return status === "unlocked";
94
+ }
95
+ //#endregion
96
+ //#region src/plugins/access/access-panel.tsx
97
+ const ACCESS_TEXT = {
98
+ open: "open, no password",
99
+ password: "password",
100
+ flag: "password, observe-only",
101
+ denied: "disabled"
102
+ };
103
+ const field = {
104
+ flex: 1,
105
+ minWidth: 0,
106
+ padding: "3px 6px",
107
+ font: "inherit",
108
+ fontFamily: "var(--hud-mono)",
109
+ color: "var(--hud-fg)",
110
+ background: "var(--hud-bg-subtle)",
111
+ border: "1px solid var(--hud-border)",
112
+ borderRadius: 4
113
+ };
114
+ const button = {
115
+ padding: "3px 10px",
116
+ border: "1px solid var(--hud-border)",
117
+ borderRadius: 4
118
+ };
119
+ function AccessPill() {
120
+ const { env, status } = useUnlockState();
121
+ const access = resolveHudAccess(env);
122
+ const unlocked = access === "open" || status === "unlocked";
123
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
124
+ className: "dot",
125
+ "data-rating": unlocked ? "good" : "poor"
126
+ }), access === "open" ? "Open" : unlocked ? "Unlocked" : "Locked"] });
127
+ }
128
+ /** The panel a locked deployed HUD shows instead of every other plugin. */
129
+ function createAccessPanel(matrix) {
130
+ return function AccessPanel({ ctx }) {
131
+ const { env, status, error } = useUnlockState();
132
+ const [password, setPassword] = useState("");
133
+ const [submitting, setSubmitting] = useState(false);
134
+ const access = resolveHudAccess(env);
135
+ const ttlHours = hudUnlockTtlMs(access) / 36e5;
136
+ async function onSubmit(event) {
137
+ event.preventDefault();
138
+ if (!password || submitting) return;
139
+ setSubmitting(true);
140
+ const ok = await submitPassword(ctx.api, password);
141
+ setSubmitting(false);
142
+ if (ok) setPassword("");
143
+ }
144
+ return /* @__PURE__ */ jsxs("div", {
145
+ style: {
146
+ display: "grid",
147
+ gap: 8
148
+ },
149
+ children: [
150
+ /* @__PURE__ */ jsxs("div", { children: [
151
+ /* @__PURE__ */ jsx("strong", { children: env }),
152
+ " ",
153
+ /* @__PURE__ */ jsxs("span", {
154
+ className: "empty",
155
+ children: ["· ", ACCESS_TEXT[access]]
156
+ })
157
+ ] }),
158
+ status === "unconfigured" && /* @__PURE__ */ jsx("p", {
159
+ className: "empty",
160
+ style: { margin: 0 },
161
+ children: "No unlock password is configured on this deployment's server, so the HUD cannot be unlocked here."
162
+ }),
163
+ status !== "unconfigured" && access !== "open" && status !== "unlocked" && /* @__PURE__ */ jsxs("form", {
164
+ style: {
165
+ display: "flex",
166
+ gap: 6
167
+ },
168
+ onSubmit,
169
+ children: [/* @__PURE__ */ jsx("input", {
170
+ type: "password",
171
+ autoComplete: "off",
172
+ value: password,
173
+ onChange: (event) => setPassword(event.target.value),
174
+ placeholder: "hud password",
175
+ "aria-label": "hud password",
176
+ style: field
177
+ }), /* @__PURE__ */ jsx("button", {
178
+ type: "submit",
179
+ style: button,
180
+ disabled: !password || submitting,
181
+ children: submitting ? "Checking…" : "Unlock"
182
+ })]
183
+ }),
184
+ error && /* @__PURE__ */ jsx("p", {
185
+ role: "alert",
186
+ style: {
187
+ margin: 0,
188
+ color: "var(--hud-poor)"
189
+ },
190
+ children: error
191
+ }),
192
+ access !== "open" && status === "unlocked" && /* @__PURE__ */ jsxs("p", {
193
+ className: "empty",
194
+ style: { margin: 0 },
195
+ children: [
196
+ "Unlocked for up to ",
197
+ ttlHours,
198
+ " h."
199
+ ]
200
+ }),
201
+ access !== "open" && status !== "unlocked" && ttlHours > 0 && /* @__PURE__ */ jsxs("p", {
202
+ className: "empty",
203
+ style: { margin: 0 },
204
+ children: [
205
+ "An unlock lasts ",
206
+ ttlHours,
207
+ " h."
208
+ ]
209
+ }),
210
+ /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", {
211
+ className: "empty",
212
+ children: "Capabilities"
213
+ }), /* @__PURE__ */ jsx("code", {
214
+ style: {
215
+ fontFamily: "var(--hud-mono)",
216
+ whiteSpace: "pre-wrap"
217
+ },
218
+ children: env === "development" ? "all" : matrix[env].join("\n") || "none"
219
+ })] })
220
+ ]
221
+ });
222
+ };
223
+ }
224
+ //#endregion
225
+ //#region src/plugins/access/index.tsx
226
+ /**
227
+ * Opt-in for deployed tiers. Without it `defineHud` renders nothing outside
228
+ * development. With it the HUD follows the env ladder: a tier's capability
229
+ * matrix narrows the plugins, and until the operator unlocks with the
230
+ * server's password only this plugin's lock shows.
231
+ *
232
+ * Client half; the password check lives in `@hansenexus/hud/plugins/access/server`
233
+ * (`accessServer()` + `accessGuard()`), and the password never reaches the browser.
234
+ */
235
+ function access(options = {}) {
236
+ const matrix = resolveMatrix(options.matrix);
237
+ return definePlugin({
238
+ id: "access",
239
+ title: "Access",
240
+ capability: ACCESS_UNLOCK_CAPABILITY,
241
+ pill: AccessPill,
242
+ panel: createAccessPanel(matrix),
243
+ access: {
244
+ can: (env, capability) => capability === "access.unlock" || hasCapability(toHudEnv(env), capability, matrix),
245
+ useUnlocked
246
+ }
247
+ });
248
+ }
249
+ //#endregion
250
+ export { ACCESS_UNLOCK_CAPABILITY, CAPABILITY_MATRIX, HUD_CAPABILITIES, access, hasCapability, hudRequiresUnlock, resolveHudAccess };
@@ -0,0 +1,129 @@
1
+ import { r as GrabPayload, t as ComponentHint } from "../../payload-CLKDVZWl.js";
2
+ import { a as DispatchTargetInfo, i as DispatchReceipt, n as AgentSessionStatus, o as SessionsResponse, r as DispatchBody, t as AgentSession } from "../../types-DADBRiGk.js";
3
+ import { ChildProcess, SpawnOptions } from "node:child_process";
4
+ //#region src/plugins/agent/providers/types.d.ts
5
+ /** What a provider receives: the raw request plus the prompt built from it. */
6
+ interface DispatchOrder {
7
+ note: string;
8
+ payload: GrabPayload | null;
9
+ hints: ComponentHint[];
10
+ /** `dispatchPrompt(...)` of the above; what an agent should be told. */
11
+ prompt: string;
12
+ }
13
+ type ProviderStatus = {
14
+ available: true;
15
+ } | {
16
+ available: false;
17
+ reason: string;
18
+ };
19
+ /**
20
+ * The `dispatch` provider seam. One implementation per backend: `hermes()`
21
+ * and `claudeCode()` ship with the plugin; anything with the same shape works.
22
+ * Runs server side only.
23
+ */
24
+ interface DispatchProvider {
25
+ /** Unique among the providers passed in; the client selects a target by it. */
26
+ id: string;
27
+ label: string;
28
+ description?: string;
29
+ /** Default: available. */
30
+ status?: () => ProviderStatus | Promise<ProviderStatus>;
31
+ /** Starts work and returns as soon as it is accepted, not when it finishes. */
32
+ dispatch: (order: DispatchOrder) => Promise<{
33
+ runId: string;
34
+ detail?: string;
35
+ }>;
36
+ /** Runs and sessions this backend knows about, for the Sessions panel. */
37
+ sessions?: () => Promise<AgentSession[]>;
38
+ }
39
+ //#endregion
40
+ //#region src/plugins/agent/prompt.d.ts
41
+ interface PromptInput {
42
+ note: string;
43
+ payload: GrabPayload | null;
44
+ hints?: readonly ComponentHint[] | undefined;
45
+ }
46
+ /** The task text every provider receives: grab context first, then the instruction. */
47
+ export declare function dispatchPrompt({ note, payload, hints }: PromptInput): string;
48
+ //#endregion
49
+ //#region src/plugins/agent/providers/claude-code.d.ts
50
+ interface ClaudeCodeOptions {
51
+ /** Repository the agent works in. Default `process.cwd()`, the dev server's root. */
52
+ cwd?: string;
53
+ /** Default `claude`. */
54
+ command?: string;
55
+ /**
56
+ * Arguments for one run. Default `["-p", prompt, "--permission-mode", "acceptEdits"]`:
57
+ * headless, may edit files, asks (and therefore stops) before running commands.
58
+ */
59
+ args?: (prompt: string) => string[];
60
+ label?: string;
61
+ /** Where each run's stdout and stderr go. Default `<tmpdir>/hud-agent`. */
62
+ logDir?: string;
63
+ /** How many recent transcripts the Sessions panel lists. Default 8. */
64
+ transcripts?: number;
65
+ /** Home directory holding `.claude/projects`. Default `os.homedir()`. */
66
+ home?: string;
67
+ /** Test seam. */
68
+ spawn?: (command: string, args: string[], options: SpawnOptions) => ChildProcess;
69
+ }
70
+ /**
71
+ * Dispatches to a local Claude Code: each run is `claude -p <prompt>` spawned
72
+ * (argv, no shell) in `cwd`, detached so it outlives a dev-server reload, with
73
+ * its output in a log file. Sessions are the runs started here plus the most
74
+ * recent Claude Code transcripts of `cwd`.
75
+ */
76
+ export declare function claudeCode({ cwd: cwdOption, command, args, label, logDir: logDirOption, transcripts, home: homeOption, spawn: spawnOption }?: ClaudeCodeOptions): DispatchProvider;
77
+ //#endregion
78
+ //#region src/plugins/agent/providers/hermes.d.ts
79
+ interface HermesOptions {
80
+ /** Hermes Agent API server, e.g. `http://hn-hub:8642`. */
81
+ baseUrl: string;
82
+ /** Bearer token, or a getter read per request. Missing: the target shows as unavailable. */
83
+ token: string | undefined | (() => string | undefined);
84
+ /** Sent as the run's `instructions` (an ephemeral system prompt), e.g. a playbook. */
85
+ instructions?: string;
86
+ label?: string;
87
+ /** Per-request timeout. Default 10 s. */
88
+ timeoutMs?: number;
89
+ /** Test seam. */
90
+ fetch?: typeof fetch;
91
+ }
92
+ /**
93
+ * Dispatches to a Hermes Agent API server: `POST /v1/runs` starts a
94
+ * background run (202 `{ run_id }`), `GET /v1/jobs` lists runs for the
95
+ * Sessions panel. The token never leaves the server.
96
+ */
97
+ export declare function hermes({ baseUrl, token, instructions, label, timeoutMs, fetch: fetchImpl }: HermesOptions): DispatchProvider;
98
+ //#endregion
99
+ //#region src/plugins/agent/server.d.ts
100
+ export interface AgentServerOptions {
101
+ /** Dispatch backends, first available is the panel's default target. */
102
+ providers: readonly DispatchProvider[];
103
+ }
104
+ /**
105
+ * Server half of `agentDispatch`: `GET /targets` lists the providers,
106
+ * `POST /dispatch` hands a grab plus instruction to one of them.
107
+ */
108
+ export declare function agentDispatch({ providers }: AgentServerOptions): {
109
+ readonly id: "agent-dispatch";
110
+ readonly routes: {
111
+ readonly "/targets": {
112
+ GET: () => Promise<Response>;
113
+ };
114
+ readonly "/dispatch": {
115
+ readonly POST: (request: Request) => Promise<Response>;
116
+ };
117
+ };
118
+ };
119
+ /** Server half of `agentSessions`: `GET /sessions` merges every provider's sessions. */
120
+ export declare function agentSessions({ providers }: AgentServerOptions): {
121
+ readonly id: "agent-sessions";
122
+ readonly routes: {
123
+ "/sessions": {
124
+ GET: () => Promise<Response>;
125
+ };
126
+ };
127
+ };
128
+ //#endregion
129
+ export type { AgentSession, AgentSessionStatus, ClaudeCodeOptions, DispatchBody, DispatchOrder, DispatchProvider, DispatchReceipt, DispatchTargetInfo, HermesOptions, ProviderStatus, SessionsResponse };