@schlessera/brain-ui-server 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 (188) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +65 -0
  3. package/dist/agent/backend.d.ts +30 -0
  4. package/dist/agent/backend.d.ts.map +1 -0
  5. package/dist/agent/backend.js +226 -0
  6. package/dist/agent/backend.js.map +1 -0
  7. package/dist/app.d.ts +27 -0
  8. package/dist/app.d.ts.map +1 -0
  9. package/dist/app.js +116 -0
  10. package/dist/app.js.map +1 -0
  11. package/dist/brain/client.d.ts +43 -0
  12. package/dist/brain/client.d.ts.map +1 -0
  13. package/dist/brain/client.js +154 -0
  14. package/dist/brain/client.js.map +1 -0
  15. package/dist/brain/types.d.ts +41 -0
  16. package/dist/brain/types.d.ts.map +1 -0
  17. package/dist/brain/types.js +2 -0
  18. package/dist/brain/types.js.map +1 -0
  19. package/dist/cron/scheduler.d.ts +9 -0
  20. package/dist/cron/scheduler.d.ts.map +1 -0
  21. package/dist/cron/scheduler.js +58 -0
  22. package/dist/cron/scheduler.js.map +1 -0
  23. package/dist/db/client.d.ts +10 -0
  24. package/dist/db/client.d.ts.map +1 -0
  25. package/dist/db/client.js +73 -0
  26. package/dist/db/client.js.map +1 -0
  27. package/dist/files/walker.d.ts +63 -0
  28. package/dist/files/walker.d.ts.map +1 -0
  29. package/dist/files/walker.js +416 -0
  30. package/dist/files/walker.js.map +1 -0
  31. package/dist/index.d.ts +18 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +25 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/middleware/auth.d.ts +59 -0
  36. package/dist/middleware/auth.d.ts.map +1 -0
  37. package/dist/middleware/auth.js +246 -0
  38. package/dist/middleware/auth.js.map +1 -0
  39. package/dist/middleware/passkeys.d.ts +36 -0
  40. package/dist/middleware/passkeys.d.ts.map +1 -0
  41. package/dist/middleware/passkeys.js +427 -0
  42. package/dist/middleware/passkeys.js.map +1 -0
  43. package/dist/middleware/tailscale.d.ts +31 -0
  44. package/dist/middleware/tailscale.d.ts.map +1 -0
  45. package/dist/middleware/tailscale.js +64 -0
  46. package/dist/middleware/tailscale.js.map +1 -0
  47. package/dist/render/template.d.ts +7 -0
  48. package/dist/render/template.d.ts.map +1 -0
  49. package/dist/render/template.js +112 -0
  50. package/dist/render/template.js.map +1 -0
  51. package/dist/routes/brain.d.ts +155 -0
  52. package/dist/routes/brain.d.ts.map +1 -0
  53. package/dist/routes/brain.js +250 -0
  54. package/dist/routes/brain.js.map +1 -0
  55. package/dist/routes/files.d.ts +89 -0
  56. package/dist/routes/files.d.ts.map +1 -0
  57. package/dist/routes/files.js +91 -0
  58. package/dist/routes/files.js.map +1 -0
  59. package/dist/routes/health.d.ts +37 -0
  60. package/dist/routes/health.d.ts.map +1 -0
  61. package/dist/routes/health.js +27 -0
  62. package/dist/routes/health.js.map +1 -0
  63. package/dist/routes/providers.d.ts +33 -0
  64. package/dist/routes/providers.d.ts.map +1 -0
  65. package/dist/routes/providers.js +9 -0
  66. package/dist/routes/providers.js.map +1 -0
  67. package/dist/routes/render.d.ts +25 -0
  68. package/dist/routes/render.d.ts.map +1 -0
  69. package/dist/routes/render.js +47 -0
  70. package/dist/routes/render.js.map +1 -0
  71. package/dist/routes/sessions.d.ts +79 -0
  72. package/dist/routes/sessions.d.ts.map +1 -0
  73. package/dist/routes/sessions.js +37 -0
  74. package/dist/routes/sessions.js.map +1 -0
  75. package/dist/routes/voice.d.ts +92 -0
  76. package/dist/routes/voice.d.ts.map +1 -0
  77. package/dist/routes/voice.js +64 -0
  78. package/dist/routes/voice.js.map +1 -0
  79. package/dist/voice/deepgram-token.d.ts +5 -0
  80. package/dist/voice/deepgram-token.d.ts.map +1 -0
  81. package/dist/voice/deepgram-token.js +63 -0
  82. package/dist/voice/deepgram-token.js.map +1 -0
  83. package/dist/voice/keyterm-builder.d.ts +18 -0
  84. package/dist/voice/keyterm-builder.d.ts.map +1 -0
  85. package/dist/voice/keyterm-builder.js +391 -0
  86. package/dist/voice/keyterm-builder.js.map +1 -0
  87. package/dist/voice/speech-providers.d.ts +25 -0
  88. package/dist/voice/speech-providers.d.ts.map +1 -0
  89. package/dist/voice/speech-providers.js +104 -0
  90. package/dist/voice/speech-providers.js.map +1 -0
  91. package/dist/ws/attachments.d.ts +10 -0
  92. package/dist/ws/attachments.d.ts.map +1 -0
  93. package/dist/ws/attachments.js +63 -0
  94. package/dist/ws/attachments.js.map +1 -0
  95. package/dist/ws/bridge.d.ts +7 -0
  96. package/dist/ws/bridge.d.ts.map +1 -0
  97. package/dist/ws/bridge.js +86 -0
  98. package/dist/ws/bridge.js.map +1 -0
  99. package/dist/ws/clients.d.ts +20 -0
  100. package/dist/ws/clients.d.ts.map +1 -0
  101. package/dist/ws/clients.js +48 -0
  102. package/dist/ws/clients.js.map +1 -0
  103. package/dist/ws/connection.d.ts +8 -0
  104. package/dist/ws/connection.d.ts.map +1 -0
  105. package/dist/ws/connection.js +116 -0
  106. package/dist/ws/connection.js.map +1 -0
  107. package/dist/ws/dispatch.d.ts +5 -0
  108. package/dist/ws/dispatch.d.ts.map +1 -0
  109. package/dist/ws/dispatch.js +139 -0
  110. package/dist/ws/dispatch.js.map +1 -0
  111. package/dist/ws/frames.d.ts +17 -0
  112. package/dist/ws/frames.d.ts.map +1 -0
  113. package/dist/ws/frames.js +37 -0
  114. package/dist/ws/frames.js.map +1 -0
  115. package/dist/ws/handler.d.ts +34 -0
  116. package/dist/ws/handler.d.ts.map +1 -0
  117. package/dist/ws/handler.js +33 -0
  118. package/dist/ws/handler.js.map +1 -0
  119. package/dist/ws/history.d.ts +17 -0
  120. package/dist/ws/history.d.ts.map +1 -0
  121. package/dist/ws/history.js +45 -0
  122. package/dist/ws/history.js.map +1 -0
  123. package/dist/ws/host.d.ts +40 -0
  124. package/dist/ws/host.d.ts.map +1 -0
  125. package/dist/ws/host.js +49 -0
  126. package/dist/ws/host.js.map +1 -0
  127. package/dist/ws/routing.d.ts +12 -0
  128. package/dist/ws/routing.d.ts.map +1 -0
  129. package/dist/ws/routing.js +31 -0
  130. package/dist/ws/routing.js.map +1 -0
  131. package/dist/ws/run-session.d.ts +17 -0
  132. package/dist/ws/run-session.d.ts.map +1 -0
  133. package/dist/ws/run-session.js +160 -0
  134. package/dist/ws/run-session.js.map +1 -0
  135. package/dist/ws/session-catalog.d.ts +25 -0
  136. package/dist/ws/session-catalog.d.ts.map +1 -0
  137. package/dist/ws/session-catalog.js +45 -0
  138. package/dist/ws/session-catalog.js.map +1 -0
  139. package/dist/ws/shrink.d.ts +34 -0
  140. package/dist/ws/shrink.d.ts.map +1 -0
  141. package/dist/ws/shrink.js +91 -0
  142. package/dist/ws/shrink.js.map +1 -0
  143. package/dist/ws/turns.d.ts +77 -0
  144. package/dist/ws/turns.d.ts.map +1 -0
  145. package/dist/ws/turns.js +89 -0
  146. package/dist/ws/turns.js.map +1 -0
  147. package/migrations/001_initial.sql +29 -0
  148. package/migrations/002_session_provider.sql +1 -0
  149. package/migrations/003_session_backend.sql +5 -0
  150. package/migrations/004_drop_gemini_backend.sql +20 -0
  151. package/migrations/005_passkey_credentials.sql +18 -0
  152. package/package.json +72 -0
  153. package/src/agent/backend.ts +308 -0
  154. package/src/app.ts +159 -0
  155. package/src/brain/client.ts +186 -0
  156. package/src/brain/types.ts +44 -0
  157. package/src/cron/scheduler.ts +80 -0
  158. package/src/db/client.ts +85 -0
  159. package/src/files/walker.ts +418 -0
  160. package/src/index.ts +39 -0
  161. package/src/middleware/auth.ts +321 -0
  162. package/src/middleware/passkeys.ts +510 -0
  163. package/src/middleware/tailscale.ts +81 -0
  164. package/src/render/template.ts +124 -0
  165. package/src/routes/brain.ts +305 -0
  166. package/src/routes/files.ts +99 -0
  167. package/src/routes/health.ts +29 -0
  168. package/src/routes/providers.ts +9 -0
  169. package/src/routes/render.ts +68 -0
  170. package/src/routes/sessions.ts +49 -0
  171. package/src/routes/voice.ts +80 -0
  172. package/src/voice/deepgram-token.ts +80 -0
  173. package/src/voice/keyterm-builder.ts +426 -0
  174. package/src/voice/speech-providers.ts +111 -0
  175. package/src/ws/attachments.ts +86 -0
  176. package/src/ws/bridge.ts +139 -0
  177. package/src/ws/clients.ts +57 -0
  178. package/src/ws/connection.ts +132 -0
  179. package/src/ws/dispatch.ts +162 -0
  180. package/src/ws/frames.ts +42 -0
  181. package/src/ws/handler.ts +50 -0
  182. package/src/ws/history.ts +51 -0
  183. package/src/ws/host.ts +69 -0
  184. package/src/ws/routing.ts +43 -0
  185. package/src/ws/run-session.ts +192 -0
  186. package/src/ws/session-catalog.ts +97 -0
  187. package/src/ws/shrink.ts +95 -0
  188. package/src/ws/turns.ts +151 -0
@@ -0,0 +1,416 @@
1
+ import { readdir, readFile, stat, realpath } from "node:fs/promises";
2
+ import { join, resolve, sep, posix, dirname } from "node:path";
3
+ import { existsSync } from "node:fs";
4
+ import ignore from "ignore";
5
+ import { FILE_SIZE_CAP_BYTES } from "@schlessera/brain-ui-sdk/protocol";
6
+ const HARD_EXCLUDE_DIRS = new Set([".git", "node_modules"]);
7
+ const HARD_EXCLUDE_FILE_PATTERNS = [
8
+ /\.db$/i,
9
+ /\.db-wal$/i,
10
+ /\.db-shm$/i,
11
+ ];
12
+ // Build / dependency manifests that aren't browseable content. Matched by
13
+ // exact name (case-insensitive) at any depth.
14
+ const HARD_EXCLUDE_FILE_NAMES = new Set([
15
+ "package.json",
16
+ "package-lock.json",
17
+ "bun.lock",
18
+ "bun.lockb",
19
+ "yarn.lock",
20
+ "pnpm-lock.yaml",
21
+ "skills-lock.json",
22
+ ].map((n) => n.toLowerCase()));
23
+ export function getBrainRoot() {
24
+ return process.env.BRAIN_PATH || join(process.env.HOME || "/root", "brain");
25
+ }
26
+ export class PathEscapeError extends Error {
27
+ constructor(rel) {
28
+ super(`path_escape: ${rel}`);
29
+ this.name = "PathEscapeError";
30
+ }
31
+ }
32
+ export class NotFoundError extends Error {
33
+ constructor(rel) {
34
+ super(`not_found: ${rel}`);
35
+ this.name = "NotFoundError";
36
+ }
37
+ }
38
+ export class TooLargeError extends Error {
39
+ size;
40
+ constructor(size) {
41
+ super(`file_too_large: ${size}`);
42
+ this.size = size;
43
+ this.name = "TooLargeError";
44
+ }
45
+ }
46
+ /**
47
+ * Resolve a repo-relative path to an absolute path, rejecting any escape
48
+ * outside the brain root (including via symlinks).
49
+ */
50
+ export async function safeResolve(rel, root = getBrainRoot()) {
51
+ if (typeof rel !== "string")
52
+ throw new PathEscapeError(String(rel));
53
+ // Reject absolute paths and null bytes
54
+ if (rel.startsWith("/") || rel.startsWith("\\") || rel.includes("\0")) {
55
+ throw new PathEscapeError(rel);
56
+ }
57
+ // Normalize separators; reject ".." segments anywhere
58
+ const normalized = rel.replace(/\\/g, "/");
59
+ for (const seg of normalized.split("/")) {
60
+ if (seg === "..")
61
+ throw new PathEscapeError(rel);
62
+ }
63
+ const absRoot = resolve(root);
64
+ const abs = resolve(absRoot, normalized);
65
+ if (abs !== absRoot && !abs.startsWith(absRoot + sep)) {
66
+ throw new PathEscapeError(rel);
67
+ }
68
+ // Check for symlink escape via realpath when the path exists
69
+ if (existsSync(abs)) {
70
+ const real = await realpath(abs);
71
+ if (real !== absRoot && !real.startsWith(absRoot + sep)) {
72
+ throw new PathEscapeError(rel);
73
+ }
74
+ }
75
+ return abs;
76
+ }
77
+ const ignoreCache = new Map();
78
+ async function loadIgnore(root) {
79
+ const gitignore = join(root, ".gitignore");
80
+ let mtime = 0;
81
+ try {
82
+ const s = await stat(gitignore);
83
+ mtime = s.mtimeMs;
84
+ }
85
+ catch {
86
+ // no .gitignore — empty matcher
87
+ }
88
+ const cached = ignoreCache.get(root);
89
+ if (cached && cached.mtime === mtime)
90
+ return cached.matcher;
91
+ const matcher = ignore();
92
+ if (mtime > 0) {
93
+ try {
94
+ const text = await readFile(gitignore, "utf8");
95
+ matcher.add(text);
96
+ }
97
+ catch { }
98
+ }
99
+ ignoreCache.set(root, { matcher, mtime });
100
+ return matcher;
101
+ }
102
+ function isHardExcluded(name, isDir) {
103
+ // Hide all dotfiles and dot-directories — they are tooling/config noise
104
+ // (.git, .gitignore, .agents, .claude, .brain-ui, etc.) that the user
105
+ // doesn't want to navigate as content.
106
+ if (name.startsWith("."))
107
+ return true;
108
+ if (isDir)
109
+ return HARD_EXCLUDE_DIRS.has(name);
110
+ if (HARD_EXCLUDE_FILE_NAMES.has(name.toLowerCase()))
111
+ return true;
112
+ return HARD_EXCLUDE_FILE_PATTERNS.some((re) => re.test(name));
113
+ }
114
+ /**
115
+ * List entries of a directory, applying ignore rules.
116
+ * Always excludes: .git, node_modules, *.db*. Then applies .gitignore.
117
+ */
118
+ export async function listDirectory(rel, root = getBrainRoot()) {
119
+ const abs = await safeResolve(rel, root);
120
+ let s;
121
+ try {
122
+ s = await stat(abs);
123
+ }
124
+ catch {
125
+ throw new NotFoundError(rel);
126
+ }
127
+ if (!s.isDirectory())
128
+ throw new NotFoundError(rel);
129
+ const matcher = await loadIgnore(root);
130
+ const dirents = await readdir(abs, { withFileTypes: true });
131
+ const entries = [];
132
+ for (const d of dirents) {
133
+ const isDir = d.isDirectory();
134
+ const isFile = d.isFile();
135
+ const isSymlink = d.isSymbolicLink();
136
+ if (!isDir && !isFile && !isSymlink)
137
+ continue;
138
+ if (isHardExcluded(d.name, isDir))
139
+ continue;
140
+ const relChild = rel ? posix.join(rel, d.name) : d.name;
141
+ // ignore expects trailing slash on dirs to match dir-only rules
142
+ const matchPath = isDir ? `${relChild}/` : relChild;
143
+ if (matcher.ignores(matchPath))
144
+ continue;
145
+ let childStat;
146
+ try {
147
+ childStat = await stat(join(abs, d.name));
148
+ }
149
+ catch {
150
+ continue; // broken symlink
151
+ }
152
+ // Resolve symlinks: drop ones that escape the root
153
+ if (isSymlink) {
154
+ try {
155
+ const real = await realpath(join(abs, d.name));
156
+ const absRoot = resolve(root);
157
+ if (real !== absRoot && !real.startsWith(absRoot + sep))
158
+ continue;
159
+ }
160
+ catch {
161
+ continue;
162
+ }
163
+ }
164
+ entries.push({
165
+ name: d.name,
166
+ path: relChild,
167
+ type: childStat.isDirectory() ? "dir" : "file",
168
+ size: childStat.isFile() ? childStat.size : undefined,
169
+ mtime: Math.floor(childStat.mtimeMs),
170
+ });
171
+ }
172
+ entries.sort((a, b) => {
173
+ if (a.type !== b.type)
174
+ return a.type === "dir" ? -1 : 1;
175
+ return a.name.localeCompare(b.name, undefined, { sensitivity: "base" });
176
+ });
177
+ return entries;
178
+ }
179
+ const MARKDOWN_EXT = new Set([".md", ".markdown", ".mdx"]);
180
+ const HTML_EXT = new Set([".html", ".htm"]);
181
+ const KNOWN_TEXT_EXT = new Set([
182
+ ".txt", ".text", ".log",
183
+ ".json", ".jsonc", ".yaml", ".yml", ".toml", ".xml", ".csv", ".tsv",
184
+ ".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs",
185
+ ".py", ".rb", ".go", ".rs", ".java", ".kt", ".swift", ".c", ".h",
186
+ ".cpp", ".hpp", ".cc", ".cs", ".php", ".sh", ".bash", ".zsh", ".fish",
187
+ ".css", ".scss", ".sass", ".less",
188
+ ".sql", ".env", ".ini", ".conf", ".cfg",
189
+ ".gitignore", ".gitattributes", ".editorconfig", ".prettierrc",
190
+ ".dockerfile", ".lock",
191
+ ]);
192
+ function extOf(name) {
193
+ const i = name.lastIndexOf(".");
194
+ if (i < 0)
195
+ return "";
196
+ return name.slice(i).toLowerCase();
197
+ }
198
+ export function classifyKind(name, bytes) {
199
+ const ext = extOf(name);
200
+ if (MARKDOWN_EXT.has(ext))
201
+ return "markdown";
202
+ if (HTML_EXT.has(ext))
203
+ return "html";
204
+ if (KNOWN_TEXT_EXT.has(ext))
205
+ return "text";
206
+ // Bare names like "Dockerfile", "Makefile", "LICENSE", "README"
207
+ const base = name.toLowerCase();
208
+ if (["dockerfile", "makefile", "license", "licence", "readme", "changelog", "authors", "contributors", "notice"].includes(base)) {
209
+ return "text";
210
+ }
211
+ // Fall back to byte sniff: a buffer is "text" if no NUL byte in first 4KB and decodes as UTF-8.
212
+ if (bytes) {
213
+ const slice = bytes.subarray(0, Math.min(bytes.length, 4096));
214
+ if (slice.includes(0))
215
+ return "binary";
216
+ try {
217
+ new TextDecoder("utf-8", { fatal: true }).decode(slice);
218
+ return "text";
219
+ }
220
+ catch {
221
+ return "binary";
222
+ }
223
+ }
224
+ return "binary";
225
+ }
226
+ const MIME_BY_EXT = {
227
+ ".png": "image/png",
228
+ ".jpg": "image/jpeg",
229
+ ".jpeg": "image/jpeg",
230
+ ".gif": "image/gif",
231
+ ".webp": "image/webp",
232
+ ".svg": "image/svg+xml",
233
+ ".avif": "image/avif",
234
+ ".bmp": "image/bmp",
235
+ ".ico": "image/x-icon",
236
+ ".pdf": "application/pdf",
237
+ ".mp3": "audio/mpeg",
238
+ ".wav": "audio/wav",
239
+ ".mp4": "video/mp4",
240
+ ".webm": "video/webm",
241
+ };
242
+ export function mimeFor(name, kind) {
243
+ const ext = extOf(name);
244
+ if (MIME_BY_EXT[ext])
245
+ return MIME_BY_EXT[ext];
246
+ if (kind === "markdown")
247
+ return "text/markdown; charset=utf-8";
248
+ if (kind === "html")
249
+ return "text/html; charset=utf-8";
250
+ if (kind === "text")
251
+ return "text/plain; charset=utf-8";
252
+ return "application/octet-stream";
253
+ }
254
+ export async function readFileContent(rel, root = getBrainRoot()) {
255
+ const abs = await safeResolve(rel, root);
256
+ let s;
257
+ try {
258
+ s = await stat(abs);
259
+ }
260
+ catch {
261
+ throw new NotFoundError(rel);
262
+ }
263
+ if (!s.isFile())
264
+ throw new NotFoundError(rel);
265
+ if (s.size > FILE_SIZE_CAP_BYTES)
266
+ throw new TooLargeError(s.size);
267
+ const ext = extOf(abs);
268
+ const presumed = MARKDOWN_EXT.has(ext)
269
+ ? "markdown"
270
+ : HTML_EXT.has(ext)
271
+ ? "html"
272
+ : KNOWN_TEXT_EXT.has(ext)
273
+ ? "text"
274
+ : null;
275
+ if (presumed) {
276
+ const content = await readFile(abs, "utf8");
277
+ return {
278
+ kind: presumed,
279
+ size: s.size,
280
+ mtime: Math.floor(s.mtimeMs),
281
+ mime: mimeFor(rel, presumed),
282
+ content,
283
+ };
284
+ }
285
+ const buf = await readFile(abs);
286
+ const kind = classifyKind(abs, buf);
287
+ if (kind === "text" || kind === "markdown" || kind === "html") {
288
+ return {
289
+ kind,
290
+ size: s.size,
291
+ mtime: Math.floor(s.mtimeMs),
292
+ mime: mimeFor(rel, kind),
293
+ content: buf.toString("utf8"),
294
+ };
295
+ }
296
+ return {
297
+ kind: "binary",
298
+ size: s.size,
299
+ mtime: Math.floor(s.mtimeMs),
300
+ mime: mimeFor(rel, "binary"),
301
+ };
302
+ }
303
+ /**
304
+ * Stream raw bytes of a file (for image/binary preview).
305
+ * Returns the absolute path; caller uses Bun.file() to stream.
306
+ */
307
+ export async function resolveForRaw(rel, root = getBrainRoot()) {
308
+ const abs = await safeResolve(rel, root);
309
+ let s;
310
+ try {
311
+ s = await stat(abs);
312
+ }
313
+ catch {
314
+ throw new NotFoundError(rel);
315
+ }
316
+ if (!s.isFile())
317
+ throw new NotFoundError(rel);
318
+ if (s.size > FILE_SIZE_CAP_BYTES)
319
+ throw new TooLargeError(s.size);
320
+ const kind = classifyKind(abs);
321
+ return { abs, size: s.size, mime: mimeFor(rel, kind), kind };
322
+ }
323
+ /**
324
+ * Walk the brain repo and build a slug -> repo-relative-path map for all
325
+ * markdown files (.md / .markdown / .mdx), respecting the same ignore
326
+ * rules as listDirectory. Slug = basename without extension, lowercase.
327
+ *
328
+ * If two files share a slug (rare), the first one encountered wins —
329
+ * deterministic given the sorted directory traversal.
330
+ */
331
+ export async function buildWikilinkMap(root = getBrainRoot()) {
332
+ const matcher = await loadIgnore(root);
333
+ const out = {};
334
+ async function walk(rel) {
335
+ const abs = await safeResolve(rel, root);
336
+ let dirents;
337
+ try {
338
+ dirents = await readdir(abs, { withFileTypes: true });
339
+ }
340
+ catch {
341
+ return;
342
+ }
343
+ // Sort for deterministic precedence
344
+ dirents.sort((a, b) => a.name.localeCompare(b.name));
345
+ for (const d of dirents) {
346
+ const isDir = d.isDirectory();
347
+ const isFile = d.isFile();
348
+ const isSymlink = d.isSymbolicLink();
349
+ if (!isDir && !isFile && !isSymlink)
350
+ continue;
351
+ if (isHardExcluded(d.name, isDir))
352
+ continue;
353
+ const relChild = rel ? posix.join(rel, d.name) : d.name;
354
+ const matchPath = isDir ? `${relChild}/` : relChild;
355
+ if (matcher.ignores(matchPath))
356
+ continue;
357
+ let childStat;
358
+ try {
359
+ childStat = await stat(join(abs, d.name));
360
+ }
361
+ catch {
362
+ continue;
363
+ }
364
+ if (isSymlink) {
365
+ try {
366
+ const real = await realpath(join(abs, d.name));
367
+ const absRoot = resolve(root);
368
+ if (real !== absRoot && !real.startsWith(absRoot + sep))
369
+ continue;
370
+ }
371
+ catch {
372
+ continue;
373
+ }
374
+ }
375
+ if (childStat.isDirectory()) {
376
+ await walk(relChild);
377
+ continue;
378
+ }
379
+ const lower = d.name.toLowerCase();
380
+ const dot = lower.lastIndexOf(".");
381
+ const ext = dot >= 0 ? lower.slice(dot) : "";
382
+ if (ext !== ".md" && ext !== ".markdown" && ext !== ".mdx")
383
+ continue;
384
+ const slug = lower.slice(0, dot);
385
+ if (slug && !out[slug])
386
+ out[slug] = relChild;
387
+ }
388
+ }
389
+ await walk("");
390
+ return out;
391
+ }
392
+ /**
393
+ * Return ancestor directories of a path, root-first (excluding root and the path itself).
394
+ * resolve("a/b/c.md") -> { ancestors: ["a", "a/b"], exists, type }
395
+ */
396
+ export async function resolveAncestors(rel, root = getBrainRoot()) {
397
+ const normalized = rel.replace(/\\/g, "/").replace(/^\/+|\/+$/g, "");
398
+ if (!normalized)
399
+ return { ancestors: [], exists: true, type: "dir" };
400
+ const parts = normalized.split("/");
401
+ const ancestors = [];
402
+ for (let i = 1; i < parts.length; i++) {
403
+ ancestors.push(parts.slice(0, i).join("/"));
404
+ }
405
+ try {
406
+ const abs = await safeResolve(normalized, root);
407
+ const s = await stat(abs);
408
+ return { ancestors, exists: true, type: s.isDirectory() ? "dir" : "file" };
409
+ }
410
+ catch {
411
+ return { ancestors, exists: false };
412
+ }
413
+ }
414
+ // Re-export for callers
415
+ export { dirname };
416
+ //# sourceMappingURL=walker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"walker.js","sourceRoot":"","sources":["../../src/files/walker.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @schlessera/brain-ui-server — the chat-UI backend as a library.
3
+ *
4
+ * The deployment shell owns the process: it builds the Bun.serve() object,
5
+ * decides the port/idleTimeout, wires SIGTERM, and injects deployment-only
6
+ * pieces (static client build, PNG/PDF renderer). Everything else — routes,
7
+ * auth, passkeys, the WebSocket turn coordinator, the session catalog — lives
8
+ * behind createApp().
9
+ */
10
+ export { createApp, type CreateAppOptions, type AppRenderer } from "./app.js";
11
+ export { websocket, cancelActiveTurn, isTurnActive } from "./ws/handler.js";
12
+ export { getDb, closeDb, configureDb } from "./db/client.js";
13
+ export { getBackends, getBackendsInfo } from "./agent/backend.js";
14
+ export { resolveAuthMode, type AuthMode } from "./middleware/auth.js";
15
+ export { WsHost, type WsHostOptions, defaultWsHost, configureWsHost, handleClientMessage, resetForTests, resolveTurnTarget, createSessionCatalog, type SessionCatalog, } from "./ws/handler.js";
16
+ export * as brainClient from "./brain/client.js";
17
+ export { buildKeyterms, writeCache } from "./voice/keyterm-builder.js";
18
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,SAAS,EAAE,KAAK,gBAAgB,EAAE,KAAK,WAAW,EAAE,MAAM,UAAU,CAAC;AAG9E,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC5E,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAI7D,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,KAAK,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAGtE,OAAO,EACL,MAAM,EACN,KAAK,aAAa,EAClB,aAAa,EACb,eAAe,EACf,mBAAmB,EACnB,aAAa,EACb,iBAAiB,EACjB,oBAAoB,EACpB,KAAK,cAAc,GACpB,MAAM,iBAAiB,CAAC;AAIzB,OAAO,KAAK,WAAW,MAAM,mBAAmB,CAAC;AAGjD,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,25 @@
1
+ /**
2
+ * @schlessera/brain-ui-server — the chat-UI backend as a library.
3
+ *
4
+ * The deployment shell owns the process: it builds the Bun.serve() object,
5
+ * decides the port/idleTimeout, wires SIGTERM, and injects deployment-only
6
+ * pieces (static client build, PNG/PDF renderer). Everything else — routes,
7
+ * auth, passkeys, the WebSocket turn coordinator, the session catalog — lives
8
+ * behind createApp().
9
+ */
10
+ export { createApp } from "./app.js";
11
+ // Process lifecycle helpers for the bin entry.
12
+ export { websocket, cancelActiveTurn, isTurnActive } from "./ws/handler.js";
13
+ export { getDb, closeDb, configureDb } from "./db/client.js";
14
+ // Boot-time diagnostics (fail fast on a bad AGENT_BACKEND / profile config,
15
+ // log the resolved auth mode).
16
+ export { getBackends, getBackendsInfo } from "./agent/backend.js";
17
+ export { resolveAuthMode } from "./middleware/auth.js";
18
+ // WebSocket internals for embedders and tests.
19
+ export { WsHost, defaultWsHost, configureWsHost, handleClientMessage, resetForTests, resolveTurnTarget, createSessionCatalog, } from "./ws/handler.js";
20
+ // Brain repo access (spawned CLI wrapper) — useful for embedders that add
21
+ // their own routes on top.
22
+ export * as brainClient from "./brain/client.js";
23
+ // Voice keyterm cache rebuild (used by deployments after `brain sync`).
24
+ export { buildKeyterms, writeCache } from "./voice/keyterm-builder.js";
25
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,SAAS,EAA2C,MAAM,UAAU,CAAC;AAE9E,+CAA+C;AAC/C,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC5E,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7D,4EAA4E;AAC5E,+BAA+B;AAC/B,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAClE,OAAO,EAAE,eAAe,EAAiB,MAAM,sBAAsB,CAAC;AAEtE,+CAA+C;AAC/C,OAAO,EACL,MAAM,EAEN,aAAa,EACb,eAAe,EACf,mBAAmB,EACnB,aAAa,EACb,iBAAiB,EACjB,oBAAoB,GAErB,MAAM,iBAAiB,CAAC;AAEzB,0EAA0E;AAC1E,2BAA2B;AAC3B,OAAO,KAAK,WAAW,MAAM,mBAAmB,CAAC;AAEjD,wEAAwE;AACxE,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC"}
@@ -0,0 +1,59 @@
1
+ import { Hono } from "hono";
2
+ import type { Context, MiddlewareHandler } from "hono";
3
+ /**
4
+ * Authentication for a remote surface to an agent with write access to the
5
+ * user's files. Modes (AUTH_MODE):
6
+ *
7
+ * - `password` — a single shared password (Bun.password argon2id hash in
8
+ * BRAIN_UI_PASSWORD_HASH), a signed HttpOnly SameSite=Strict
9
+ * session cookie (COOKIE_SECRET), a rate-limited login route.
10
+ * The only mode where `docker compose up` on a bare VPS is safe.
11
+ * - `tailscale` — Tailscale IP allowlist (today's model). Header trust is gated
12
+ * behind TRUST_PROXY=1; otherwise the socket address is used.
13
+ * - `proxy` — trust an upstream auth header (PROXY_AUTH_HEADER) set by
14
+ * Authelia / oauth2-proxy / Caddy basic_auth, etc.
15
+ * - `none` — no auth. REFUSES to start unless HOST is loopback (fail
16
+ * closed), regardless of NODE_ENV; the only override is the
17
+ * explicit BRAIN_UI_DANGEROUSLY_DISABLE_AUTH=1 escape hatch.
18
+ *
19
+ * Default: `password` when BRAIN_UI_PASSWORD_HASH is set, else `tailscale`.
20
+ */
21
+ export type AuthMode = "password" | "tailscale" | "proxy" | "none";
22
+ export declare const LOGIN_RATE_LIMIT = 5;
23
+ export declare const GLOBAL_LOGIN_RATE_LIMIT = 20;
24
+ export declare function resolveAuthMode(): AuthMode;
25
+ /**
26
+ * Validate the auth configuration at startup. Throws (refusing to boot) on an
27
+ * unsafe or unusable configuration.
28
+ */
29
+ export declare function assertAuthConfig(mode: AuthMode): void;
30
+ /** Middleware guarding /api/* according to the resolved mode. */
31
+ export declare function authGuard(mode: AuthMode): MiddlewareHandler;
32
+ /**
33
+ * Authorize a WebSocket upgrade. The upgrade handler must call this itself —
34
+ * browsers can't set headers on the WS handshake, so the session cookie (sent
35
+ * automatically same-origin) is the credential, and no header-modifying
36
+ * middleware may sit on the WS route (immutable-header errors). Mirrors
37
+ * {@link authGuard} without emitting a response.
38
+ */
39
+ export declare function isWsAuthorized(c: Context, mode: AuthMode): Promise<boolean>;
40
+ /**
41
+ * Mint the session cookie. The single place a session comes into existence —
42
+ * both password login and passkey login (middleware/passkeys.ts) call this, so
43
+ * authGuard / isWsAuthorized / TTL semantics stay identical across methods.
44
+ */
45
+ export declare function issueSessionCookie(c: Context): Promise<void>;
46
+ /** Test-only: clear all rate-limit buckets (they are module-global state). */
47
+ export declare function resetLoginRateLimiter(): void;
48
+ export declare function consumeLoginToken(key: string, limit: number): boolean;
49
+ /** Login/logout routes. Only functional in `password` mode. */
50
+ export declare function authRoutes(mode: AuthMode, deps?: {
51
+ /**
52
+ * When provided and returning true, password login is refused (the app
53
+ * injects passkeys' passwordLoginDisabled so the shared password dies
54
+ * once a passkey exists for the RP). Injected to keep this module free
55
+ * of WebAuthn concerns.
56
+ */
57
+ passwordDisabled?: (c: Context) => boolean;
58
+ }): Hono;
59
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/middleware/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAIvD;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,MAAM,QAAQ,GAAG,UAAU,GAAG,WAAW,GAAG,OAAO,GAAG,MAAM,CAAC;AAInE,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAQlC,eAAO,MAAM,uBAAuB,KAAK,CAAC;AAG1C,wBAAgB,eAAe,IAAI,QAAQ,CAe1C;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,CAwDrD;AAED,iEAAiE;AACjE,wBAAgB,SAAS,CAAC,IAAI,EAAE,QAAQ,GAAG,iBAAiB,CAqC3D;AAED;;;;;;GAMG;AACH,wBAAsB,cAAc,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,CAWjF;AAID;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAYlE;AA4CD,8EAA8E;AAC9E,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAUrE;AAED,+DAA+D;AAC/D,wBAAgB,UAAU,CACxB,IAAI,EAAE,QAAQ,EACd,IAAI,GAAE;IACJ;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC;CACvC,GACL,IAAI,CAkDN"}