@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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alain Schlesser
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,65 @@
1
+ # @schlessera/brain-ui-server
2
+
3
+ The brain-kit chat-UI backend as a library: a Hono app factory that serves the
4
+ REST API, the authenticated WebSocket, and the agent turn coordinator. The
5
+ deployment shell owns the process — port, `Bun.serve()` wiring, SIGTERM, the
6
+ built client bundle, and the optional PNG/PDF renderer — and injects those
7
+ pieces through `createApp()`.
8
+
9
+ **Bun-only** (`bun:sqlite`, `hono/bun`). See `engines`.
10
+
11
+ ## Usage
12
+
13
+ ```ts
14
+ import { createApp, cancelActiveTurn, closeDb } from "@schlessera/brain-ui-server";
15
+
16
+ const app = createApp({
17
+ staticRoot: "./client/dist", // optional: serve a built SPA + fallback
18
+ renderer, // optional: { renderPng, renderPdf }
19
+ appName: "Brain UI", // branding in status copy
20
+ dbPath: process.env.DB_PATH, // session/passkey SQLite (default ./brain-ui.db)
21
+ });
22
+
23
+ export default {
24
+ port: 3000,
25
+ fetch: app.fetch,
26
+ websocket: app.websocket,
27
+ };
28
+ ```
29
+
30
+ One app per process: `createApp()` configures a process-wide host (WebSocket
31
+ coordinator, SQLite handle, backend registry) — calling it twice with
32
+ different options reconfigures the first app rather than creating a second.
33
+
34
+ ## What it owns
35
+
36
+ - **`createApp(options)`** — route mounting order, CORS (split topology via
37
+ `ALLOWED_ORIGINS`), the auth guard, and the `/ws` upgrade (CSWSH origin check
38
+ + cookie/IP auth). Refuses to boot on an unsafe auth configuration.
39
+ - **Auth** (`AUTH_MODE`): `password` (+ passkeys/WebAuthn), `tailscale`,
40
+ `proxy`, `none` (loopback-only unless explicitly overridden).
41
+ - **WebSocket turn coordinator** — parallel sessions with per-session turn
42
+ slots, follow-up queueing, host-owned per-turn timeout/cancellation, and the
43
+ approval / ask-user / location round-trips. Decomposed into explicit host
44
+ objects (`WsHost`, `TurnCoordinator`, `SessionCatalog`) under `src/ws/`.
45
+ - **Session catalog** — SQLite (WAL) with bundled migrations, applied on first
46
+ open.
47
+ - **Brain routes** — search/briefing/stats/list/add plus SSE sync/whatsup,
48
+ spawning the `brain` CLI from `BRAIN_PATH`.
49
+ - **Voice** — Deepgram token minting and keyterm-cache building from the brain
50
+ index.
51
+ - **Render seam** — `POST /api/render` answers 501 unless the deployment
52
+ injects a renderer (see `@schlessera/brain-render-puppeteer`).
53
+
54
+ ## Environment
55
+
56
+ The package reads the same env contract the brain-ui deployment documents:
57
+ `AUTH_MODE`, `BRAIN_UI_PASSWORD_HASH`, `COOKIE_SECRET`, `TRUST_PROXY`,
58
+ `WEBAUTHN_*`, `BRAIN_PATH`, `DB_PATH`, `AGENT_BACKEND`,
59
+ `BRAIN_UI_CLAUDE_PROFILES`, `MAX_CONCURRENT_SESSIONS`, `DEEPGRAM_API_KEY`,
60
+ `VOICE_*`, `NOMINATIM_*`, `ALLOWED_ORIGINS`. `createApp()` options win over
61
+ env where both exist.
62
+
63
+ ## Versioning
64
+
65
+ Versions in lockstep with all `@schlessera/brain-*` packages.
@@ -0,0 +1,30 @@
1
+ import type { ProviderInfo } from "@schlessera/brain-ui-sdk";
2
+ import type { AgentBackend, BackendCapabilities } from "@schlessera/brain-ui-sdk/server";
3
+ /** All configured backends, with the default backend first. */
4
+ export declare function getBackends(): Promise<AgentBackend[]>;
5
+ /** Find a configured backend by its stable id. */
6
+ export declare function getBackendById(id: string): Promise<AgentBackend | undefined>;
7
+ /** The default backend used for new and legacy sessions. */
8
+ export declare function getDefaultBackend(): Promise<AgentBackend>;
9
+ /** The stable id of the default backend. */
10
+ export declare function getDefaultBackendId(): Promise<string>;
11
+ /** Resolve the backend that owns a globally unique profile id. */
12
+ export declare function getBackendForProfile(profileId: string): Promise<AgentBackend | undefined>;
13
+ /** Resolve a persisted backend id, falling back for legacy/unknown sessions. */
14
+ export declare function getBackendForSession(backendId: string | null | undefined): Promise<AgentBackend>;
15
+ /** List every available profile, tagged with its owning backend id. */
16
+ export declare function listAllProviders(): Promise<ProviderInfo[]>;
17
+ /** Per-backend capability metadata exposed by the providers route. */
18
+ export declare function getBackendsInfo(): Promise<Record<string, {
19
+ id: string;
20
+ capabilities: BackendCapabilities;
21
+ }>>;
22
+ /** @deprecated Use getDefaultBackend() or a routing-specific accessor. */
23
+ export declare function getBackend(): Promise<AgentBackend>;
24
+ /** Test seam: drop the cached registry so later access rebuilds from env. */
25
+ export declare function resetBackendForTests(): void;
26
+ /** Test seam: install one fake backend as the complete registry. */
27
+ export declare function setBackendForTests(backend: AgentBackend): void;
28
+ /** Test seam: install a complete fake registry with an explicit default. */
29
+ export declare function setBackendsForTests(backends: AgentBackend[], defaultBackendId?: string): void;
30
+ //# sourceMappingURL=backend.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../../src/agent/backend.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,KAAK,EACV,YAAY,EACZ,mBAAmB,EACpB,MAAM,iCAAiC,CAAC;AAkHzC,+DAA+D;AAC/D,wBAAsB,WAAW,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC,CAE3D;AAED,kDAAkD;AAClD,wBAAsB,cAAc,CAClC,EAAE,EAAE,MAAM,GACT,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC,CAEnC;AAED,4DAA4D;AAC5D,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,YAAY,CAAC,CAG/D;AAED,4CAA4C;AAC5C,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,MAAM,CAAC,CAE3D;AAED,kEAAkE;AAClE,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC,CAInC;AAED,gFAAgF;AAChF,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GACnC,OAAO,CAAC,YAAY,CAAC,CAMvB;AAED,uEAAuE;AACvE,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC,CAQhE;AAED,sEAAsE;AACtE,wBAAsB,eAAe,IAAI,OAAO,CAC9C,MAAM,CAAC,MAAM,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,mBAAmB,CAAA;CAAE,CAAC,CAClE,CAQA;AAED,0EAA0E;AAC1E,wBAAsB,UAAU,IAAI,OAAO,CAAC,YAAY,CAAC,CAExD;AAED,6EAA6E;AAC7E,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C;AAED,oEAAoE;AACpE,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,CAE9D;AAED,4EAA4E;AAC5E,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,YAAY,EAAE,EACxB,gBAAgB,SAAwB,GACvC,IAAI,CAEN"}
@@ -0,0 +1,226 @@
1
+ import { createRequire } from "module";
2
+ import { createClaudeBackend, defineProfiles, } from "@schlessera/brain-backend-claude";
3
+ /**
4
+ * Backend registry for the deployment. Claude is always available.
5
+ * `AGENT_BACKEND=pi` retains the legacy single-backend deployment mode when the
6
+ * optional pi package is installed.
7
+ *
8
+ * The old five hardcoded personal inference profiles are gone: the Claude
9
+ * backend ships the single "claude" default, and additional Anthropic-compatible
10
+ * endpoints are declared via the BRAIN_UI_CLAUDE_PROFILES env var (documented in
11
+ * .env.example) rather than in code.
12
+ */
13
+ const BRAIN_PATH = process.env.BRAIN_PATH || `${process.env.HOME || "/root"}/brain`;
14
+ // The Claude Code native binary is not in node_modules — it is installed via
15
+ // the official curl-bash installer (~/.local/bin/claude, symlinked to
16
+ // /usr/local/bin/claude in the container). The Agent SDK can't always
17
+ // auto-discover it, so hand it the explicit path; override with CLAUDE_CODE_PATH.
18
+ const CLAUDE_CODE_PATH = process.env.CLAUDE_CODE_PATH || "/usr/local/bin/claude";
19
+ let cachedRegistry = null;
20
+ async function createRegistry(backends, defaultBackendId) {
21
+ if (backends.length === 0) {
22
+ throw new Error("Backend registry requires at least one backend.");
23
+ }
24
+ const byId = new Map(backends.map((backend) => [backend.id, backend]));
25
+ const resolvedDefaultId = byId.has(defaultBackendId)
26
+ ? defaultBackendId
27
+ : backends[0].id;
28
+ const ordered = [
29
+ byId.get(resolvedDefaultId),
30
+ ...backends.filter((backend) => backend.id !== resolvedDefaultId),
31
+ ];
32
+ const listedProfiles = await Promise.all(ordered.map(async (backend) => ({
33
+ backend,
34
+ profiles: await backend.listProfiles(),
35
+ })));
36
+ const profileOwners = new Map();
37
+ const profiles = new Map();
38
+ for (const listed of listedProfiles) {
39
+ profiles.set(listed.backend.id, listed.profiles);
40
+ for (const profile of listed.profiles) {
41
+ const owner = profileOwners.get(profile.id);
42
+ if (owner && owner !== listed.backend.id) {
43
+ throw new Error(`Profile id collision across backends: "${profile.id}" is exposed by "${owner}" and "${listed.backend.id}".`);
44
+ }
45
+ profileOwners.set(profile.id, listed.backend.id);
46
+ }
47
+ }
48
+ return {
49
+ backends: ordered,
50
+ byId,
51
+ defaultBackendId: resolvedDefaultId,
52
+ profileOwners,
53
+ profiles,
54
+ };
55
+ }
56
+ async function buildRegistry() {
57
+ const primary = (process.env.AGENT_BACKEND || "claude")
58
+ .trim()
59
+ .toLowerCase();
60
+ if (primary === "pi") {
61
+ const pi = buildPiBackend();
62
+ return createRegistry([pi], pi.id);
63
+ }
64
+ const claude = createClaudeBackend({
65
+ brainPath: BRAIN_PATH,
66
+ claudeCodePath: CLAUDE_CODE_PATH,
67
+ profiles: loadClaudeProfiles(),
68
+ });
69
+ const backends = [claude];
70
+ // AGENT_BACKEND must name a configured backend. The in-process options are
71
+ // "claude" (default) and "pi" (handled above). An unrecognized value is a
72
+ // misconfiguration — fail loudly instead of silently coercing to claude and
73
+ // running on the wrong backend with nothing in the logs.
74
+ if (process.env.AGENT_BACKEND && !backends.some((b) => b.id === primary)) {
75
+ throw new Error(`AGENT_BACKEND="${primary}" does not match any configured backend ` +
76
+ `(expected "claude" or "pi").`);
77
+ }
78
+ return createRegistry(backends, primary);
79
+ }
80
+ async function getRegistry() {
81
+ if (!cachedRegistry)
82
+ cachedRegistry = buildRegistry();
83
+ return cachedRegistry;
84
+ }
85
+ /** All configured backends, with the default backend first. */
86
+ export async function getBackends() {
87
+ return (await getRegistry()).backends;
88
+ }
89
+ /** Find a configured backend by its stable id. */
90
+ export async function getBackendById(id) {
91
+ return (await getRegistry()).byId.get(id);
92
+ }
93
+ /** The default backend used for new and legacy sessions. */
94
+ export async function getDefaultBackend() {
95
+ const registry = await getRegistry();
96
+ return registry.byId.get(registry.defaultBackendId);
97
+ }
98
+ /** The stable id of the default backend. */
99
+ export async function getDefaultBackendId() {
100
+ return (await getRegistry()).defaultBackendId;
101
+ }
102
+ /** Resolve the backend that owns a globally unique profile id. */
103
+ export async function getBackendForProfile(profileId) {
104
+ const registry = await getRegistry();
105
+ const backendId = registry.profileOwners.get(profileId);
106
+ return backendId ? registry.byId.get(backendId) : undefined;
107
+ }
108
+ /** Resolve a persisted backend id, falling back for legacy/unknown sessions. */
109
+ export async function getBackendForSession(backendId) {
110
+ if (backendId) {
111
+ const backend = await getBackendById(backendId);
112
+ if (backend)
113
+ return backend;
114
+ }
115
+ return getDefaultBackend();
116
+ }
117
+ /** List every available profile, tagged with its owning backend id. */
118
+ export async function listAllProviders() {
119
+ const registry = await getRegistry();
120
+ return registry.backends.flatMap((backend) => (registry.profiles.get(backend.id) ?? []).map((profile) => ({
121
+ ...profile,
122
+ backendId: backend.id,
123
+ })));
124
+ }
125
+ /** Per-backend capability metadata exposed by the providers route. */
126
+ export async function getBackendsInfo() {
127
+ const backends = await getBackends();
128
+ return Object.fromEntries(backends.map((backend) => [
129
+ backend.id,
130
+ { id: backend.id, capabilities: backend.capabilities },
131
+ ]));
132
+ }
133
+ /** @deprecated Use getDefaultBackend() or a routing-specific accessor. */
134
+ export async function getBackend() {
135
+ return getDefaultBackend();
136
+ }
137
+ /** Test seam: drop the cached registry so later access rebuilds from env. */
138
+ export function resetBackendForTests() {
139
+ cachedRegistry = null;
140
+ }
141
+ /** Test seam: install one fake backend as the complete registry. */
142
+ export function setBackendForTests(backend) {
143
+ cachedRegistry = createRegistry([backend], backend.id);
144
+ }
145
+ /** Test seam: install a complete fake registry with an explicit default. */
146
+ export function setBackendsForTests(backends, defaultBackendId = backends[0]?.id ?? "") {
147
+ cachedRegistry = createRegistry(backends, defaultBackendId);
148
+ }
149
+ // The built-in default profile's model. The Claude backend historically pinned
150
+ // the default to a specific model; after the SDK extraction the pin was lost and
151
+ // resumed default-profile sessions drifted to "whatever the CLI defaults to",
152
+ // silently changing model/behaviour/billing on every CLI bump. Re-pin it here
153
+ // (a committed default, overridable per deploy via BRAIN_UI_CLAUDE_DEFAULT_MODEL)
154
+ // so the default stays on a known model.
155
+ const BUILTIN_DEFAULT_MODEL = process.env.BRAIN_UI_CLAUDE_DEFAULT_MODEL?.trim() || "claude-sonnet-4-6";
156
+ function builtinDefaultProfiles() {
157
+ return defineProfiles([
158
+ {
159
+ id: "claude",
160
+ label: "Claude",
161
+ vendor: "anthropic",
162
+ model: BUILTIN_DEFAULT_MODEL,
163
+ modelAliases: true,
164
+ },
165
+ ]);
166
+ }
167
+ /**
168
+ * The Claude roster: the pinned built-in "claude" default first, plus any extra
169
+ * profiles from BRAIN_UI_CLAUDE_PROFILES (a JSON array of InferenceProfileInput
170
+ * {id,label,model?,baseUrl?,authTokenEnv?,apiKeyEnv?,modelAliases?}).
171
+ *
172
+ * A malformed or duplicate-id roster THROWS — caught at boot by the registry
173
+ * fail-fast — rather than silently degrading to default-only and rebilling
174
+ * every pinned session to the subscription with nothing in the logs.
175
+ */
176
+ function loadClaudeProfiles() {
177
+ const base = builtinDefaultProfiles();
178
+ const raw = process.env.BRAIN_UI_CLAUDE_PROFILES?.trim();
179
+ if (!raw)
180
+ return base;
181
+ let inputs;
182
+ try {
183
+ inputs = JSON.parse(raw);
184
+ }
185
+ catch (err) {
186
+ throw new Error(`BRAIN_UI_CLAUDE_PROFILES is not valid JSON: ${err instanceof Error ? err.message : String(err)}`);
187
+ }
188
+ if (!Array.isArray(inputs)) {
189
+ throw new Error("BRAIN_UI_CLAUDE_PROFILES must be a JSON array.");
190
+ }
191
+ // Reject duplicate ids (including collisions with the built-in "claude"): a
192
+ // duplicate silently shadows and can resolve to the wrong credentials.
193
+ const seen = new Set(base.map((profile) => profile.id));
194
+ for (const input of inputs) {
195
+ if (!input || typeof input.id !== "string" || input.id.length === 0) {
196
+ throw new Error("Each BRAIN_UI_CLAUDE_PROFILES entry needs a non-empty string id.");
197
+ }
198
+ if (seen.has(input.id)) {
199
+ throw new Error(`Duplicate profile id in BRAIN_UI_CLAUDE_PROFILES: "${input.id}".`);
200
+ }
201
+ seen.add(input.id);
202
+ }
203
+ return [...base, ...defineProfiles(inputs)];
204
+ }
205
+ /**
206
+ * Optional pi backend. @schlessera/brain-backend-pi is NOT a hard dependency — a
207
+ * deployment that never opts into it does not need it (or its pi SDK peer deps)
208
+ * installed. Loaded lazily so the server still boots without it; when requested
209
+ * but absent, fail with an actionable message.
210
+ */
211
+ function buildPiBackend() {
212
+ const require = createRequire(import.meta.url);
213
+ let mod;
214
+ try {
215
+ mod = require("@schlessera/brain-backend-pi");
216
+ }
217
+ catch {
218
+ throw new Error('AGENT_BACKEND=pi but "@schlessera/brain-backend-pi" is not installed. ' +
219
+ "Add it (with its pi SDK dependencies) or set AGENT_BACKEND=claude.");
220
+ }
221
+ if (typeof mod.createPiBackend !== "function") {
222
+ throw new Error('"@schlessera/brain-backend-pi" does not export createPiBackend.');
223
+ }
224
+ return mod.createPiBackend({ brainPath: BRAIN_PATH });
225
+ }
226
+ //# sourceMappingURL=backend.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backend.js","sourceRoot":"","sources":["../../src/agent/backend.ts"],"names":[],"mappings":"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"}
package/dist/app.d.ts ADDED
@@ -0,0 +1,27 @@
1
+ import { type AppRenderer } from "./routes/render.js";
2
+ export type { AppRenderer };
3
+ export interface CreateAppOptions {
4
+ /**
5
+ * Directory of a built SPA to serve at `/*` with an index.html fallback.
6
+ * The deployment shell decides whether (and what) to serve — the package
7
+ * has no client build of its own and no NODE_ENV heuristics.
8
+ */
9
+ staticRoot?: string;
10
+ /** Display name used in connection/status copy. Default "Brain UI". */
11
+ appName?: string;
12
+ /** SQLite path override; falls back to DB_PATH, then ./brain-ui.db. */
13
+ dbPath?: string;
14
+ /**
15
+ * PNG/PDF renderer for `POST /api/render`. The deployment owns the actual
16
+ * renderer instance (e.g. @schlessera/brain-render-puppeteer) — without one
17
+ * the route answers 501.
18
+ */
19
+ renderer?: AppRenderer;
20
+ /** Per-turn timeout in ms (default 10 minutes). */
21
+ turnTimeoutMs?: number;
22
+ }
23
+ export declare function createApp(options?: CreateAppOptions): {
24
+ fetch: (request: Request, env?: unknown, executionCtx?: import("hono").ExecutionContext) => Response | Promise<Response>;
25
+ websocket: import("hono/bun").BunWebSocketHandler<import("hono/bun").BunWebSocketData>;
26
+ };
27
+ //# sourceMappingURL=app.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAWA,OAAO,EAAsB,KAAK,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAkB1E,YAAY,EAAE,WAAW,EAAE,CAAC;AAE5B,MAAM,WAAW,gBAAgB;IAC/B;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uEAAuE;IACvE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,WAAW,CAAC;IACvB,mDAAmD;IACnD,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAqBD,wBAAgB,SAAS,CAAC,OAAO,GAAE,gBAAqB;;;EAuFvD"}
package/dist/app.js ADDED
@@ -0,0 +1,116 @@
1
+ import { Hono } from "hono";
2
+ import { logger } from "hono/logger";
3
+ import { cors } from "hono/cors";
4
+ import { serveStatic } from "hono/bun";
5
+ import { join } from "path";
6
+ import { healthRoutes, statusRoutes } from "./routes/health.js";
7
+ import { brainRoutes } from "./routes/brain.js";
8
+ import { sessionRoutes } from "./routes/sessions.js";
9
+ import { voiceRoutes } from "./routes/voice.js";
10
+ import { filesRoutes } from "./routes/files.js";
11
+ import { createRenderRoutes } from "./routes/render.js";
12
+ import { providerRoutes } from "./routes/providers.js";
13
+ import { resolveAuthMode, assertAuthConfig, authGuard, authRoutes, isWsAuthorized, } from "./middleware/auth.js";
14
+ import { passkeyPublicRoutes, passkeyManagementRoutes, passwordLoginDisabled, assertPasskeyConfig, } from "./middleware/passkeys.js";
15
+ import { configureDb } from "./db/client.js";
16
+ import { configureWsHost, wsUpgrade, websocket } from "./ws/handler.js";
17
+ // Cross-site WebSocket hijacking (CSWSH) defense. CORS does not apply to the WS
18
+ // handshake and browsers do not enforce same-origin on `new WebSocket()`, so
19
+ // rejecting a cross-site upgrade is entirely the server's job. A browser always
20
+ // sends `Origin` on a WS handshake; a non-browser client (no Origin) is allowed
21
+ // through here and still gated by isWsAuthorized. When ALLOWED_ORIGINS is set we
22
+ // use it; otherwise we require the Origin's host to match the request host
23
+ // (same-origin). Applied in every auth mode.
24
+ function isAllowedWsOrigin(c, allowedOrigins) {
25
+ const origin = c.req.header("origin");
26
+ if (!origin)
27
+ return true;
28
+ if (allowedOrigins.length > 0)
29
+ return allowedOrigins.includes(origin);
30
+ try {
31
+ const host = c.req.header("host");
32
+ return !!host && new URL(origin).host === host;
33
+ }
34
+ catch {
35
+ return false;
36
+ }
37
+ }
38
+ export function createApp(options = {}) {
39
+ const app = new Hono();
40
+ const authMode = resolveAuthMode();
41
+ // Validate inside the factory, not the bin entry: every consumer of the app
42
+ // (a deployment bin, tests, another embedder) gets the same refuse-to-boot
43
+ // guarantee on an unsafe auth configuration.
44
+ assertAuthConfig(authMode);
45
+ assertPasskeyConfig();
46
+ if (options.dbPath)
47
+ configureDb(options.dbPath);
48
+ configureWsHost({
49
+ ...(options.appName ? { appName: options.appName } : {}),
50
+ ...(options.turnTimeoutMs ? { turnTimeoutMs: options.turnTimeoutMs } : {}),
51
+ });
52
+ // Middleware
53
+ app.use("*", logger());
54
+ // CORS is only needed for a SPLIT topology where the client is served from a
55
+ // different origin than the API. ALLOWED_ORIGINS is a comma-separated
56
+ // allowlist; empty/unset means same-origin (the default), so the CORS
57
+ // middleware is skipped entirely.
58
+ const allowedOrigins = (process.env.ALLOWED_ORIGINS ?? "")
59
+ .split(",")
60
+ .map((origin) => origin.trim())
61
+ .filter(Boolean);
62
+ if (allowedOrigins.length > 0) {
63
+ app.use("/api/*", cors({
64
+ origin: allowedOrigins,
65
+ allowMethods: ["GET", "POST", "PUT", "DELETE"],
66
+ allowHeaders: ["Content-Type"],
67
+ credentials: true,
68
+ }));
69
+ }
70
+ // Public routes, registered BEFORE the auth guard: only the minimal liveness
71
+ // probe and the login/logout routes are reachable without a session.
72
+ // /api/status is intentionally NOT here — it leaks the git SHA, cron errors,
73
+ // and a session oracle, so it lives behind the guard below.
74
+ app.route("/api", healthRoutes);
75
+ app.route("/api", authRoutes(authMode, { passwordDisabled: passwordLoginDisabled }));
76
+ app.route("/api", passkeyPublicRoutes(authMode));
77
+ // Auth guard for every other /api/* route. The probe below is intentionally
78
+ // behind it: an unauthenticated client gets 401 (password/proxy) or 403
79
+ // (tailscale) from /api/vpn-check and shows the login / VPN screen.
80
+ app.use("/api/*", authGuard(authMode));
81
+ app.get("/api/vpn-check", (c) => c.json({ vpn: true }));
82
+ // Passkey registration/management: after the guard, so a session is required
83
+ // by mount position (the public assertion routes are registered above).
84
+ app.route("/api", passkeyManagementRoutes(authMode));
85
+ app.route("/api", statusRoutes);
86
+ app.route("/api", brainRoutes);
87
+ app.route("/api", sessionRoutes);
88
+ app.route("/api", voiceRoutes);
89
+ app.route("/api", filesRoutes);
90
+ app.route("/api", createRenderRoutes(options.renderer));
91
+ app.route("/api", providerRoutes);
92
+ // WebSocket endpoint. Browsers can't set headers on the WS handshake, so the
93
+ // upgrade authenticates via the session cookie (or IP/proxy header) INSIDE
94
+ // the handler — no header-modifying middleware may sit on this route.
95
+ app.get("/ws", async (c, next) => {
96
+ if (!isAllowedWsOrigin(c, allowedOrigins)) {
97
+ return c.json({ error: "Cross-origin WebSocket rejected" }, 403);
98
+ }
99
+ if (!(await isWsAuthorized(c, authMode))) {
100
+ return c.json({ error: "Authentication required" }, 401);
101
+ }
102
+ return wsUpgrade(c, next);
103
+ });
104
+ // Static files: the deployment shell passes its built client explicitly.
105
+ if (options.staticRoot) {
106
+ const staticRoot = options.staticRoot;
107
+ app.use("/*", serveStatic({ root: staticRoot }));
108
+ // SPA fallback
109
+ app.get("*", serveStatic({ path: join(staticRoot, "index.html") }));
110
+ }
111
+ return {
112
+ fetch: app.fetch,
113
+ websocket,
114
+ };
115
+ }
116
+ //# sourceMappingURL=app.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ import type { BrainSearchResponse, BrainDocument, BrainStats, BrainSyncResult } from "./types.js";
2
+ /**
3
+ * argv prefix for invoking the brain CLI inside BRAIN_PATH.
4
+ *
5
+ * Two brain-repo layouts exist and both must work: a repo that depends on
6
+ * `@schlessera/brain` gets a real bin at `node_modules/.bin/brain`, while the
7
+ * legacy layout vendors `scripts/brain-cli.ts`. Prefer the packaged bin.
8
+ *
9
+ * Resolved per call (a bare `existsSync` stat) rather than at import time: in
10
+ * the container the brain repo is cloned and `bun install`ed by entrypoint.sh,
11
+ * and a repo can gain the dependency without restarting this server. The same
12
+ * preference order is mirrored in `scripts/entrypoint.sh`, which links whichever
13
+ * it finds onto PATH for cron.
14
+ */
15
+ export declare function brainCliCommand(): string[];
16
+ export declare function brainSearch(query: string, opts?: {
17
+ type?: string;
18
+ tag?: string;
19
+ limit?: number;
20
+ mode?: string;
21
+ }): Promise<BrainSearchResponse>;
22
+ export declare function brainBriefing(): Promise<string>;
23
+ export declare function brainStats(): Promise<BrainStats>;
24
+ export declare function brainList(opts?: {
25
+ type?: string;
26
+ tag?: string;
27
+ status?: string;
28
+ relevance?: string;
29
+ limit?: number;
30
+ }): Promise<BrainDocument[]>;
31
+ export declare function brainRead(path: string): Promise<string>;
32
+ export declare function brainSync(): Promise<BrainSyncResult>;
33
+ export declare function brainAdd(content: string, opts?: {
34
+ type?: string;
35
+ title?: string;
36
+ tags?: string[];
37
+ }): Promise<void>;
38
+ export declare function brainIndex(opts?: {
39
+ /** Full rebuild; indexing is incremental by default since brain 22e82e1. */
40
+ force?: boolean;
41
+ }): Promise<void>;
42
+ export declare function brainValidate(): Promise<string>;
43
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/brain/client.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAEV,mBAAmB,EACnB,aAAa,EACb,UAAU,EACV,eAAe,EAChB,MAAM,YAAY,CAAC;AAIpB;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,IAAI,MAAM,EAAE,CAI1C;AA2CD,wBAAsB,WAAW,CAC/B,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GACpE,OAAO,CAAC,mBAAmB,CAAC,CAW9B;AAED,wBAAsB,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,CAMrD;AAED,wBAAsB,UAAU,IAAI,OAAO,CAAC,UAAU,CAAC,CAGtD;AAED,wBAAsB,SAAS,CAAC,IAAI,CAAC,EAAE;IACrC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAU3B;AAED,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAM7D;AAED,wBAAsB,SAAS,IAAI,OAAO,CAAC,eAAe,CAAC,CAsB1D;AAED,wBAAsB,QAAQ,CAC5B,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,GACxD,OAAO,CAAC,IAAI,CAAC,CAUf;AAED,wBAAsB,UAAU,CAAC,IAAI,CAAC,EAAE;IACtC,4EAA4E;IAC5E,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,GAAG,OAAO,CAAC,IAAI,CAAC,CAQhB;AAED,wBAAsB,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,CAMrD"}