@xerktech/claude-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 (60) hide show
  1. package/README.md +50 -0
  2. package/dist/_broker/attached.js +78 -0
  3. package/dist/_broker/attached.js.map +1 -0
  4. package/dist/_broker/audio-codec.js +82 -0
  5. package/dist/_broker/audio-codec.js.map +1 -0
  6. package/dist/_broker/audio-session.js +81 -0
  7. package/dist/_broker/audio-session.js.map +1 -0
  8. package/dist/_broker/auth.js +32 -0
  9. package/dist/_broker/auth.js.map +1 -0
  10. package/dist/_broker/bus.js +76 -0
  11. package/dist/_broker/bus.js.map +1 -0
  12. package/dist/_broker/cert.js +72 -0
  13. package/dist/_broker/cert.js.map +1 -0
  14. package/dist/_broker/claude.js +160 -0
  15. package/dist/_broker/claude.js.map +1 -0
  16. package/dist/_broker/cli.js +134 -0
  17. package/dist/_broker/cli.js.map +1 -0
  18. package/dist/_broker/cors.js +48 -0
  19. package/dist/_broker/cors.js.map +1 -0
  20. package/dist/_broker/hooks.js +196 -0
  21. package/dist/_broker/hooks.js.map +1 -0
  22. package/dist/_broker/index.js +48 -0
  23. package/dist/_broker/index.js.map +1 -0
  24. package/dist/_broker/intent-dispatcher.js +86 -0
  25. package/dist/_broker/intent-dispatcher.js.map +1 -0
  26. package/dist/_broker/intent.js +127 -0
  27. package/dist/_broker/intent.js.map +1 -0
  28. package/dist/_broker/jsonl-tail.js +185 -0
  29. package/dist/_broker/jsonl-tail.js.map +1 -0
  30. package/dist/_broker/mdns.js +41 -0
  31. package/dist/_broker/mdns.js.map +1 -0
  32. package/dist/_broker/projects.js +161 -0
  33. package/dist/_broker/projects.js.map +1 -0
  34. package/dist/_broker/qr.js +11 -0
  35. package/dist/_broker/qr.js.map +1 -0
  36. package/dist/_broker/routes.js +379 -0
  37. package/dist/_broker/routes.js.map +1 -0
  38. package/dist/_broker/server.js +325 -0
  39. package/dist/_broker/server.js.map +1 -0
  40. package/dist/_broker/sessions-store.js +50 -0
  41. package/dist/_broker/sessions-store.js.map +1 -0
  42. package/dist/_broker/sessions.js +792 -0
  43. package/dist/_broker/sessions.js.map +1 -0
  44. package/dist/_broker/store.js +79 -0
  45. package/dist/_broker/store.js.map +1 -0
  46. package/dist/_broker/stt.js +93 -0
  47. package/dist/_broker/stt.js.map +1 -0
  48. package/dist/broker-bin.js +37 -0
  49. package/dist/broker-bin.js.map +1 -0
  50. package/dist/broker-lifecycle.js +186 -0
  51. package/dist/broker-lifecycle.js.map +1 -0
  52. package/dist/index.js +189 -0
  53. package/dist/index.js.map +1 -0
  54. package/dist/paths.js +17 -0
  55. package/dist/paths.js.map +1 -0
  56. package/dist/wrapper/index.js +263 -0
  57. package/dist/wrapper/index.js.map +1 -0
  58. package/dist/wrapper/slug.js +5 -0
  59. package/dist/wrapper/slug.js.map +1 -0
  60. package/package.json +70 -0
@@ -0,0 +1,325 @@
1
+ import http from 'node:http';
2
+ import https from 'node:https';
3
+ import express from 'express';
4
+ import { WebSocketServer } from 'ws';
5
+ import { Store } from "./store.js";
6
+ import { createBearerAuth, constantTimeEqual } from "./auth.js";
7
+ import { corsMiddleware } from "./cors.js";
8
+ import { ensureCert } from "./cert.js";
9
+ import { EventBus } from "./bus.js";
10
+ import { registerRoutes } from "./routes.js";
11
+ import { SessionManager } from "./sessions.js";
12
+ import { ProjectsStore } from "./projects.js";
13
+ import { SessionsSnapshotStore } from "./sessions-store.js";
14
+ import { AudioSession } from "./audio-session.js";
15
+ import { IntentDispatcher } from "./intent-dispatcher.js";
16
+ import { HttpWhisperClient, NullWhisperClient } from "./stt.js";
17
+ import { HookManager } from "./hooks.js";
18
+ import { AttachedSessionHub } from "./attached.js";
19
+ export async function startServer(opts = {}) {
20
+ const store = opts.store ?? new Store();
21
+ const state = store.load();
22
+ const projects = opts.projects ?? new ProjectsStore(store.dir);
23
+ const sessionsStore = opts.sessionsStore ?? new SessionsSnapshotStore(store.dir);
24
+ const bus = new EventBus();
25
+ // Forward-declared so the two managers can call into each other without a
26
+ // construction-time circular reference. Hook env is also resolved lazily —
27
+ // the URL isn't known until `server.listen()` binds a port (port 0 in tests).
28
+ let hooks;
29
+ let hookEnv;
30
+ const attached = new AttachedSessionHub();
31
+ const sessions = new SessionManager({
32
+ bus,
33
+ spawn: opts.spawnClaude,
34
+ defaultCwd: opts.defaultCwd,
35
+ idleArchiveMs: state.settings.idleArchiveMinutes * 60_000,
36
+ onTerminate: (sessionId, reason) => {
37
+ hooks?.cancelSession(sessionId, `session_${reason}`);
38
+ },
39
+ getHookEnv: () => hookEnv,
40
+ attachedHub: attached,
41
+ });
42
+ hooks = new HookManager({
43
+ bus,
44
+ onPendingChange: (sessionId, kind) => {
45
+ sessions.setHookStatus(sessionId, kind);
46
+ },
47
+ });
48
+ // Load — but do NOT yet hydrate. Hydration spawns `claude --resume` for every
49
+ // persisted active session, and each spawn needs the `BROKER_HOOKS_*` env vars
50
+ // baked in. Those vars depend on the bound URL, which isn't known until
51
+ // `server.listen()` returns. We defer the hydrate to after listen below.
52
+ const previousSnapshot = sessionsStore.load();
53
+ const persist = () => sessionsStore.save(sessions.snapshot());
54
+ const app = express();
55
+ app.use(express.json({ limit: '1mb' }));
56
+ app.use(corsMiddleware({ allowedOrigins: opts.allowedOrigins }));
57
+ // Public endpoint, registered before auth so it bypasses the bearer check.
58
+ app.get('/api/health', (_req, res) => {
59
+ res.json({ ok: true, version: '0.1.0' });
60
+ });
61
+ app.use(createBearerAuth(state.token));
62
+ registerRoutes(app, { bus, sessions, projects, hooks, persist });
63
+ const useHttp = opts.http ?? process.env.BROKER_HTTP === '1';
64
+ let server;
65
+ let protocol;
66
+ if (useHttp) {
67
+ server = http.createServer(app);
68
+ protocol = 'http';
69
+ }
70
+ else {
71
+ const cert = ensureCert(store.certDir(), state.settings.hostname);
72
+ server = https.createServer({ cert: cert.cert, key: cert.key }, app);
73
+ protocol = 'https';
74
+ if (cert.source !== 'reused') {
75
+ console.log(`[cert] generated ${cert.source} cert for ${state.settings.hostname}`);
76
+ console.log(`[cert] cert: ${cert.certPath}`);
77
+ console.log(`[cert] key : ${cert.keyPath}`);
78
+ }
79
+ }
80
+ const whisper = opts.whisper ??
81
+ (state.settings.whisper.url
82
+ ? new HttpWhisperClient({
83
+ url: state.settings.whisper.url,
84
+ model: state.settings.whisper.model,
85
+ language: state.settings.whisper.language,
86
+ })
87
+ : new NullWhisperClient());
88
+ const dispatcher = new IntentDispatcher({
89
+ sessions: {
90
+ create: o => {
91
+ const s = sessions.create(o);
92
+ return { id: s.id };
93
+ },
94
+ focus: id => sessions.focus(id),
95
+ writeInput: (id, text) => sessions.writeInput(id, text),
96
+ getFocusedId: () => sessions.getFocusedId(),
97
+ findActiveByProject: id => {
98
+ const s = sessions.findActiveByProject(id);
99
+ return s ? { id: s.id } : undefined;
100
+ },
101
+ },
102
+ onChange: persist,
103
+ });
104
+ const wss = new WebSocketServer({ noServer: true });
105
+ attachWsRouter(server, wss, state.token, bus, whisper, projects, dispatcher, sessions, attached);
106
+ const port = opts.port ?? Number(process.env.BROKER_PORT ?? state.settings.port);
107
+ const host = opts.host ?? process.env.BROKER_HOST ?? '0.0.0.0';
108
+ await new Promise((resolve, reject) => {
109
+ const onError = (err) => reject(err);
110
+ server.once('error', onError);
111
+ server.listen(port, host, () => {
112
+ server.off('error', onError);
113
+ resolve();
114
+ });
115
+ });
116
+ const addr = server.address();
117
+ const boundPort = typeof addr === 'object' && addr ? addr.port : port;
118
+ const url = `${protocol}://${state.settings.hostname}:${boundPort}`;
119
+ // Now that the listener is bound and the URL is known, plug the hook env
120
+ // into the SessionManager so every subsequent `claude` spawn has the
121
+ // BROKER_HOOKS_* vars the bundled helper script needs. For HTTPS we tell
122
+ // the helper to skip TLS verification — the broker's cert is mkcert- or
123
+ // selfsigned-issued and won't validate against Node's default CA bundle
124
+ // inside the helper script. This is scoped to the helper only.
125
+ hookEnv = {
126
+ url,
127
+ token: state.token,
128
+ insecure: protocol === 'https',
129
+ };
130
+ // Hydrate AFTER hookEnv is set so every resumed `claude` process inherits the
131
+ // BROKER_HOOKS_* env vars. Hydrating earlier (before listen) would respawn
132
+ // with no env vars, and the bundled `claude-hook.mjs` would then trip its
133
+ // safe-default deny path on every PreToolUse. Pinned by
134
+ // `broker/test/lifecycle.test.ts > hydrate runs after the URL is known`.
135
+ if (previousSnapshot.sessions.length > 0) {
136
+ sessions.hydrate(previousSnapshot);
137
+ }
138
+ if (!opts.noIdleArchiver) {
139
+ sessions.startIdleArchiver();
140
+ }
141
+ return {
142
+ app,
143
+ server,
144
+ bus,
145
+ sessions,
146
+ projects,
147
+ hooks,
148
+ attached,
149
+ url,
150
+ port: boundPort,
151
+ token: state.token,
152
+ protocol,
153
+ close: () => new Promise(resolve => {
154
+ wss.clients.forEach(c => {
155
+ try {
156
+ c.close();
157
+ }
158
+ catch {
159
+ // ignore
160
+ }
161
+ });
162
+ attached.closeAll('broker_shutdown');
163
+ hooks.cancelAll('broker_shutdown');
164
+ sessions.stopIdleArchiver();
165
+ void sessions.closeAll();
166
+ try {
167
+ persist();
168
+ }
169
+ catch {
170
+ // ignore — best-effort on shutdown
171
+ }
172
+ bus.closeAll();
173
+ server.close(() => resolve());
174
+ }),
175
+ };
176
+ }
177
+ function attachWsRouter(server, wss, token, bus, whisper, projects, dispatcher, sessions, attached) {
178
+ server.on('upgrade', (req, socket, head) => {
179
+ const url = new URL(req.url ?? '/', 'http://localhost');
180
+ const audioMatch = /^\/api\/sessions\/([^/]+)\/audio$/.exec(url.pathname);
181
+ const injectMatch = /^\/api\/sessions\/([^/]+)\/inject$/.exec(url.pathname);
182
+ if (!audioMatch && !injectMatch) {
183
+ socket.write('HTTP/1.1 404 Not Found\r\n\r\n');
184
+ socket.destroy();
185
+ return;
186
+ }
187
+ const presented = url.searchParams.get('token') ?? '';
188
+ if (!presented || !constantTimeEqual(presented, token)) {
189
+ socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
190
+ socket.destroy();
191
+ return;
192
+ }
193
+ if (injectMatch) {
194
+ const sessionId = injectMatch[1];
195
+ // We don't require the session to exist *yet* — the wrapper may open the
196
+ // WS before the POST /api/sessions/attached round-trip completes in some
197
+ // races. We still gate on having a project though, to avoid an open WS
198
+ // from a stale token spamming the hub.
199
+ void sessions;
200
+ wss.handleUpgrade(req, socket, head, (ws) => {
201
+ attached.register(sessionId, ws);
202
+ ws.on('message', () => {
203
+ // The wrapper currently only listens; if it ever sends an ack,
204
+ // drop it silently rather than crash.
205
+ });
206
+ ws.send(JSON.stringify({ type: 'hello', sessionId, role: 'inject' }));
207
+ });
208
+ return;
209
+ }
210
+ const sessionId = audioMatch[1];
211
+ wss.handleUpgrade(req, socket, head, (ws) => {
212
+ let finalised = false;
213
+ const audio = new AudioSession(sessionId, {
214
+ stt: whisper,
215
+ listProjects: () => projects.list(),
216
+ onResult: result => {
217
+ const outcome = handleAudioResult(result, dispatcher);
218
+ // Best-effort send: the client may have already closed the WS.
219
+ if (ws.readyState === ws.OPEN) {
220
+ try {
221
+ ws.send(JSON.stringify(outcome));
222
+ }
223
+ catch (err) {
224
+ console.warn(`[audio] send result failed: ${err.message}`);
225
+ }
226
+ }
227
+ publishVoiceEvents(bus, sessionId, result, outcome);
228
+ // Close after the result has been queued so the client sees it before
229
+ // the close event lands.
230
+ if (ws.readyState === ws.OPEN) {
231
+ try {
232
+ ws.close(1000, 'audio_result sent');
233
+ }
234
+ catch {
235
+ // ignore
236
+ }
237
+ }
238
+ },
239
+ onError: err => console.warn(`[audio] session=${sessionId} error: ${err.message}`),
240
+ });
241
+ const finalise = async () => {
242
+ if (finalised)
243
+ return;
244
+ finalised = true;
245
+ try {
246
+ await audio.close();
247
+ }
248
+ catch (err) {
249
+ console.warn(`[audio] session=${sessionId} close failed: ${err.message}`);
250
+ }
251
+ };
252
+ ws.on('message', (data, isBinary) => {
253
+ // Text messages are control frames — currently only `{"type":"finalize"}`.
254
+ if (!isBinary) {
255
+ const text = data.toString();
256
+ try {
257
+ const parsed = JSON.parse(text);
258
+ if (parsed.type === 'finalize') {
259
+ void finalise();
260
+ return;
261
+ }
262
+ }
263
+ catch {
264
+ // ignore — unknown text frames are dropped.
265
+ }
266
+ return;
267
+ }
268
+ if (Array.isArray(data)) {
269
+ for (const chunk of data)
270
+ audio.pushFrame(chunk);
271
+ }
272
+ else if (Buffer.isBuffer(data)) {
273
+ audio.pushFrame(data);
274
+ }
275
+ else {
276
+ audio.pushFrame(Buffer.from(data));
277
+ }
278
+ });
279
+ ws.on('close', () => {
280
+ void finalise();
281
+ });
282
+ ws.on('error', () => {
283
+ void finalise();
284
+ });
285
+ ws.send(JSON.stringify({ type: 'hello', sessionId, milestone: 'M6' }));
286
+ });
287
+ });
288
+ }
289
+ function handleAudioResult(result, dispatcher) {
290
+ const outcome = dispatcher.dispatch(result.intent);
291
+ const msg = {
292
+ type: 'audio_result',
293
+ sessionId: result.sessionId,
294
+ transcript: result.transcript.text,
295
+ durationMs: result.durationMs,
296
+ bytes: result.bytes,
297
+ outcome,
298
+ };
299
+ if (result.transcript.language)
300
+ msg.language = result.transcript.language;
301
+ if (result.transcript.unavailable)
302
+ msg.unavailable = true;
303
+ if (result.transcript.reason)
304
+ msg.reason = result.transcript.reason;
305
+ return msg;
306
+ }
307
+ /**
308
+ * Mirror the audio result onto the session's SSE channel so a plugin that's
309
+ * subscribed to /events sees the voice command even when the WS isn't the
310
+ * primary control channel. Lets multiple clients (e.g. a future web admin UI)
311
+ * stay in sync without polling the WS.
312
+ */
313
+ function publishVoiceEvents(bus, sessionId, result, outcome) {
314
+ bus.publish(`session:${sessionId}`, 'voice_transcript', {
315
+ text: result.transcript.text,
316
+ language: result.transcript.language,
317
+ unavailable: result.transcript.unavailable ?? false,
318
+ durationMs: result.durationMs,
319
+ });
320
+ bus.publish(`session:${sessionId}`, 'voice_intent', {
321
+ intent: result.intent,
322
+ outcome: outcome.outcome,
323
+ });
324
+ }
325
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import os from 'node:os';
4
+ const DEFAULT_DIR = process.env.CLAUDE_HUD_DIR ?? path.join(os.homedir(), '.claude-hud');
5
+ /**
6
+ * Atomic on-disk snapshot of the session registry.
7
+ *
8
+ * Kept separate from `Store` (which owns the broker bearer token + settings) so
9
+ * that high-frequency session-state writes don't churn the file that also holds
10
+ * the bearer token.
11
+ */
12
+ export class SessionsSnapshotStore {
13
+ dir;
14
+ file;
15
+ constructor(dir = DEFAULT_DIR) {
16
+ this.dir = dir;
17
+ this.file = path.join(dir, 'sessions.json');
18
+ }
19
+ load() {
20
+ if (!fs.existsSync(this.file)) {
21
+ return { version: 1, sessions: [] };
22
+ }
23
+ try {
24
+ const parsed = JSON.parse(fs.readFileSync(this.file, 'utf8'));
25
+ return {
26
+ version: 1,
27
+ sessions: Array.isArray(parsed.sessions) ? parsed.sessions : [],
28
+ focusedId: typeof parsed.focusedId === 'string' ? parsed.focusedId : undefined,
29
+ };
30
+ }
31
+ catch (err) {
32
+ const backup = `${this.file}.corrupt-${Date.now()}`;
33
+ try {
34
+ fs.renameSync(this.file, backup);
35
+ }
36
+ catch {
37
+ // ignore — disk may be read-only in tests
38
+ }
39
+ console.warn(`[sessions-store] sessions.json was unreadable (${err.message}); moved to ${backup}`);
40
+ return { version: 1, sessions: [] };
41
+ }
42
+ }
43
+ save(snapshot) {
44
+ fs.mkdirSync(this.dir, { recursive: true });
45
+ const tmp = `${this.file}.tmp`;
46
+ fs.writeFileSync(tmp, `${JSON.stringify(snapshot, null, 2)}\n`, { mode: 0o600 });
47
+ fs.renameSync(tmp, this.file);
48
+ }
49
+ }
50
+ //# sourceMappingURL=sessions-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessions-store.js","sourceRoot":"","sources":["../src/sessions-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAA;AACxB,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,OAAO,EAAE,MAAM,SAAS,CAAA;AAGxB,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,aAAa,CAAC,CAAA;AAExF;;;;;;GAMG;AACH,MAAM,OAAO,qBAAqB;IACvB,GAAG,CAAQ;IACX,IAAI,CAAQ;IAErB,YAAY,MAAc,WAAW;QACnC,IAAI,CAAC,GAAG,GAAG,GAAG,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,eAAe,CAAC,CAAA;IAC7C,CAAC;IAED,IAAI;QACF,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAC9B,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAA;QACrC,CAAC;QACD,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAA0B,CAAA;YACtF,OAAO;gBACL,OAAO,EAAE,CAAC;gBACV,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE;gBAC/D,SAAS,EAAE,OAAO,MAAM,CAAC,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS;aAC/E,CAAA;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,YAAY,IAAI,CAAC,GAAG,EAAE,EAAE,CAAA;YACnD,IAAI,CAAC;gBACH,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;YAClC,CAAC;YAAC,MAAM,CAAC;gBACP,0CAA0C;YAC5C,CAAC;YACD,OAAO,CAAC,IAAI,CACV,kDAAmD,GAAa,CAAC,OAAO,eAAe,MAAM,EAAE,CAChG,CAAA;YACD,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAA;QACrC,CAAC;IACH,CAAC;IAED,IAAI,CAAC,QAAsB;QACzB,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;QAC3C,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,IAAI,MAAM,CAAA;QAC9B,EAAE,CAAC,aAAa,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;QAChF,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,CAAC,CAAA;IAC/B,CAAC;CACF"}