@schlessera/brain-ui-server 0.13.1 → 0.14.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 (182) hide show
  1. package/README.md +51 -12
  2. package/dist/agent/backend.d.ts +163 -37
  3. package/dist/agent/backend.d.ts.map +1 -1
  4. package/dist/agent/backend.js +281 -286
  5. package/dist/agent/backend.js.map +1 -1
  6. package/dist/app.d.ts +46 -5
  7. package/dist/app.d.ts.map +1 -1
  8. package/dist/app.js +94 -35
  9. package/dist/app.js.map +1 -1
  10. package/dist/brain/client.d.ts +41 -30
  11. package/dist/brain/client.d.ts.map +1 -1
  12. package/dist/brain/client.js +138 -132
  13. package/dist/brain/client.js.map +1 -1
  14. package/dist/config/env.d.ts +104 -0
  15. package/dist/config/env.d.ts.map +1 -0
  16. package/dist/config/env.js +296 -0
  17. package/dist/config/env.js.map +1 -0
  18. package/dist/cron/scheduler.d.ts +16 -8
  19. package/dist/cron/scheduler.d.ts.map +1 -1
  20. package/dist/cron/scheduler.js +54 -53
  21. package/dist/cron/scheduler.js.map +1 -1
  22. package/dist/db/brain-db.d.ts +50 -0
  23. package/dist/db/brain-db.d.ts.map +1 -0
  24. package/dist/db/brain-db.js +75 -0
  25. package/dist/db/brain-db.js.map +1 -0
  26. package/dist/db/client.d.ts +8 -6
  27. package/dist/db/client.d.ts.map +1 -1
  28. package/dist/db/client.js +12 -25
  29. package/dist/db/client.js.map +1 -1
  30. package/dist/db/settings.d.ts +5 -4
  31. package/dist/db/settings.d.ts.map +1 -1
  32. package/dist/db/settings.js +9 -12
  33. package/dist/db/settings.js.map +1 -1
  34. package/dist/files/walker.d.ts +6 -7
  35. package/dist/files/walker.d.ts.map +1 -1
  36. package/dist/files/walker.js +6 -9
  37. package/dist/files/walker.js.map +1 -1
  38. package/dist/graph/reader.d.ts +6 -6
  39. package/dist/graph/reader.d.ts.map +1 -1
  40. package/dist/graph/reader.js +22 -12
  41. package/dist/graph/reader.js.map +1 -1
  42. package/dist/index.d.ts +15 -9
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +20 -10
  45. package/dist/index.js.map +1 -1
  46. package/dist/middleware/auth.d.ts +17 -6
  47. package/dist/middleware/auth.d.ts.map +1 -1
  48. package/dist/middleware/auth.js +34 -43
  49. package/dist/middleware/auth.js.map +1 -1
  50. package/dist/middleware/origin.d.ts +1 -2
  51. package/dist/middleware/origin.d.ts.map +1 -1
  52. package/dist/middleware/origin.js +1 -7
  53. package/dist/middleware/origin.js.map +1 -1
  54. package/dist/middleware/passkeys.d.ts +29 -5
  55. package/dist/middleware/passkeys.d.ts.map +1 -1
  56. package/dist/middleware/passkeys.js +47 -76
  57. package/dist/middleware/passkeys.js.map +1 -1
  58. package/dist/middleware/tailscale.d.ts +14 -11
  59. package/dist/middleware/tailscale.d.ts.map +1 -1
  60. package/dist/middleware/tailscale.js +11 -10
  61. package/dist/middleware/tailscale.js.map +1 -1
  62. package/dist/routes/brain.d.ts +9 -154
  63. package/dist/routes/brain.d.ts.map +1 -1
  64. package/dist/routes/brain.js +187 -188
  65. package/dist/routes/brain.js.map +1 -1
  66. package/dist/routes/files.d.ts +4 -88
  67. package/dist/routes/files.d.ts.map +1 -1
  68. package/dist/routes/files.js +74 -71
  69. package/dist/routes/files.js.map +1 -1
  70. package/dist/routes/graph.d.ts +4 -255
  71. package/dist/routes/graph.d.ts.map +1 -1
  72. package/dist/routes/graph.js +67 -64
  73. package/dist/routes/graph.js.map +1 -1
  74. package/dist/routes/health.d.ts +8 -22
  75. package/dist/routes/health.d.ts.map +1 -1
  76. package/dist/routes/health.js +10 -10
  77. package/dist/routes/health.js.map +1 -1
  78. package/dist/routes/models.d.ts +7 -90
  79. package/dist/routes/models.d.ts.map +1 -1
  80. package/dist/routes/models.js +53 -51
  81. package/dist/routes/models.js.map +1 -1
  82. package/dist/routes/providers.d.ts +5 -34
  83. package/dist/routes/providers.d.ts.map +1 -1
  84. package/dist/routes/providers.js +13 -11
  85. package/dist/routes/providers.js.map +1 -1
  86. package/dist/routes/sessions.d.ts +7 -78
  87. package/dist/routes/sessions.d.ts.map +1 -1
  88. package/dist/routes/sessions.js +35 -34
  89. package/dist/routes/sessions.js.map +1 -1
  90. package/dist/routes/share.d.ts +7 -60
  91. package/dist/routes/share.d.ts.map +1 -1
  92. package/dist/routes/share.js +80 -77
  93. package/dist/routes/share.js.map +1 -1
  94. package/dist/routes/voice.d.ts +8 -91
  95. package/dist/routes/voice.d.ts.map +1 -1
  96. package/dist/routes/voice.js +62 -59
  97. package/dist/routes/voice.js.map +1 -1
  98. package/dist/share/staging.d.ts +4 -4
  99. package/dist/share/staging.d.ts.map +1 -1
  100. package/dist/share/staging.js +7 -8
  101. package/dist/share/staging.js.map +1 -1
  102. package/dist/voice/deepgram-token.d.ts +1 -1
  103. package/dist/voice/deepgram-token.d.ts.map +1 -1
  104. package/dist/voice/deepgram-token.js +1 -2
  105. package/dist/voice/deepgram-token.js.map +1 -1
  106. package/dist/voice/keyterm-builder.d.ts +23 -8
  107. package/dist/voice/keyterm-builder.d.ts.map +1 -1
  108. package/dist/voice/keyterm-builder.js +67 -43
  109. package/dist/voice/keyterm-builder.js.map +1 -1
  110. package/dist/voice/speech-providers.d.ts +6 -4
  111. package/dist/voice/speech-providers.d.ts.map +1 -1
  112. package/dist/voice/speech-providers.js +28 -25
  113. package/dist/voice/speech-providers.js.map +1 -1
  114. package/dist/ws/bridge.d.ts.map +1 -1
  115. package/dist/ws/bridge.js +2 -3
  116. package/dist/ws/bridge.js.map +1 -1
  117. package/dist/ws/clients.d.ts +18 -10
  118. package/dist/ws/clients.d.ts.map +1 -1
  119. package/dist/ws/clients.js +35 -37
  120. package/dist/ws/clients.js.map +1 -1
  121. package/dist/ws/connection.d.ts.map +1 -1
  122. package/dist/ws/connection.js +5 -7
  123. package/dist/ws/connection.js.map +1 -1
  124. package/dist/ws/dispatch.d.ts.map +1 -1
  125. package/dist/ws/dispatch.js +1 -2
  126. package/dist/ws/dispatch.js.map +1 -1
  127. package/dist/ws/host.d.ts +16 -12
  128. package/dist/ws/host.d.ts.map +1 -1
  129. package/dist/ws/host.js +14 -23
  130. package/dist/ws/host.js.map +1 -1
  131. package/dist/ws/routing.d.ts +2 -1
  132. package/dist/ws/routing.d.ts.map +1 -1
  133. package/dist/ws/routing.js +5 -6
  134. package/dist/ws/routing.js.map +1 -1
  135. package/dist/ws/run-session.js +1 -1
  136. package/dist/ws/run-session.js.map +1 -1
  137. package/dist/ws/session-catalog.d.ts +2 -2
  138. package/dist/ws/session-catalog.d.ts.map +1 -1
  139. package/dist/ws/session-catalog.js +2 -3
  140. package/dist/ws/session-catalog.js.map +1 -1
  141. package/package.json +8 -5
  142. package/src/agent/backend.ts +509 -340
  143. package/src/app.ts +157 -36
  144. package/src/brain/client.ts +166 -143
  145. package/src/config/env.ts +401 -0
  146. package/src/cron/scheduler.ts +77 -59
  147. package/src/db/brain-db.ts +104 -0
  148. package/src/db/client.ts +12 -31
  149. package/src/db/settings.ts +12 -14
  150. package/src/files/walker.ts +6 -10
  151. package/src/graph/reader.ts +26 -15
  152. package/src/index.ts +56 -17
  153. package/src/middleware/auth.ts +51 -45
  154. package/src/middleware/origin.ts +1 -8
  155. package/src/middleware/passkeys.ts +72 -68
  156. package/src/middleware/tailscale.ts +22 -17
  157. package/src/routes/brain.ts +31 -30
  158. package/src/routes/files.ts +10 -7
  159. package/src/routes/graph.ts +9 -6
  160. package/src/routes/health.ts +17 -10
  161. package/src/routes/models.ts +19 -15
  162. package/src/routes/providers.ts +14 -15
  163. package/src/routes/sessions.ts +18 -11
  164. package/src/routes/share.ts +35 -24
  165. package/src/routes/voice.ts +78 -68
  166. package/src/share/staging.ts +10 -8
  167. package/src/voice/deepgram-token.ts +4 -2
  168. package/src/voice/keyterm-builder.ts +90 -56
  169. package/src/voice/speech-providers.ts +28 -24
  170. package/src/ws/bridge.ts +2 -3
  171. package/src/ws/clients.ts +38 -41
  172. package/src/ws/connection.ts +5 -7
  173. package/src/ws/dispatch.ts +1 -2
  174. package/src/ws/host.ts +24 -27
  175. package/src/ws/routing.ts +6 -10
  176. package/src/ws/run-session.ts +1 -1
  177. package/src/ws/session-catalog.ts +2 -3
  178. package/dist/ws/handler.d.ts +0 -34
  179. package/dist/ws/handler.d.ts.map +0 -1
  180. package/dist/ws/handler.js +0 -33
  181. package/dist/ws/handler.js.map +0 -1
  182. package/src/ws/handler.ts +0 -50
@@ -0,0 +1,296 @@
1
+ /**
2
+ * The package's ONLY `process.env` reader.
3
+ *
4
+ * Everything `@schlessera/brain-ui-server` can be configured with is declared
5
+ * here twice: once as a runtime descriptor ({@link ENV_VARS}, the artifact the
6
+ * env-parity gate diffs against the documentation) and once as the resolver
7
+ * ({@link resolveServerConfig}) that turns an environment into a plain,
8
+ * fully-resolved {@link ServerConfig}.
9
+ *
10
+ * `createApp()` resolves the environment ONCE, at the edge; every consumer
11
+ * inside the package receives the resolved object (or a slice of it) and never
12
+ * touches `process.env` itself. Tests vary configuration by passing their own
13
+ * env record to the resolver — no global mutation required.
14
+ */
15
+ import { join } from "path";
16
+ /**
17
+ * Every environment variable this package reads. Order groups by concern.
18
+ * The env-parity gate (G4) asserts this list equals the package's env
19
+ * documentation in both directions — add here and to the docs together.
20
+ */
21
+ export const ENV_VARS = [
22
+ // core paths / identity
23
+ {
24
+ name: "BRAIN_PATH",
25
+ description: "Path to the brain repo the server operates on.",
26
+ default: "$HOME/brain",
27
+ required: false,
28
+ },
29
+ {
30
+ name: "HOME",
31
+ description: "Fallback anchor for the BRAIN_PATH default only.",
32
+ default: "/root",
33
+ required: false,
34
+ },
35
+ {
36
+ name: "DB_PATH",
37
+ description: "SQLite file for the UI's own database (sessions, passkeys, settings).",
38
+ default: "./brain-ui.db",
39
+ required: false,
40
+ },
41
+ {
42
+ name: "HOST",
43
+ description: "Bind host; consulted by the auth validation to decide whether AUTH_MODE=none is loopback-safe.",
44
+ default: "(empty)",
45
+ required: false,
46
+ },
47
+ {
48
+ name: "SOURCE_COMMIT",
49
+ description: "Git SHA reported by /api/status (baked at image build time).",
50
+ default: "dev",
51
+ required: false,
52
+ },
53
+ {
54
+ name: "ALLOWED_ORIGINS",
55
+ description: "Comma-separated cross-origin allowlist for a split client/API topology; empty means same-origin only.",
56
+ default: "(empty)",
57
+ required: false,
58
+ },
59
+ {
60
+ name: "MAX_CONCURRENT_SESSIONS",
61
+ description: "Cap on concurrently RUNNING agent sessions.",
62
+ default: "3",
63
+ required: false,
64
+ },
65
+ // auth
66
+ {
67
+ name: "AUTH_MODE",
68
+ description: "Authentication mode: password | tailscale | proxy | none. Unset auto-detects (password when a hash is set, else tailscale).",
69
+ default: "(auto-detect)",
70
+ required: false,
71
+ },
72
+ {
73
+ name: "BRAIN_UI_PASSWORD_HASH",
74
+ description: "Bun.password argon2id hash of the shared password.",
75
+ default: null,
76
+ required: "AUTH_MODE=password",
77
+ },
78
+ {
79
+ name: "COOKIE_SECRET",
80
+ description: "Secret signing the session cookie.",
81
+ default: null,
82
+ required: "AUTH_MODE=password",
83
+ },
84
+ {
85
+ name: "TRUST_PROXY",
86
+ description: 'Set "1" to trust x-forwarded-for/x-real-ip and the proxy auth header; only safe behind a trusted reverse proxy.',
87
+ default: "0",
88
+ required: "AUTH_MODE=proxy",
89
+ },
90
+ {
91
+ name: "TRUST_PROXY_HOPS",
92
+ description: "How many trusted proxies front the app (x-forwarded-for parse depth).",
93
+ default: "1",
94
+ required: false,
95
+ },
96
+ {
97
+ name: "PROXY_AUTH_HEADER",
98
+ description: "Header a fronting auth proxy sets for AUTH_MODE=proxy.",
99
+ default: "x-forwarded-user",
100
+ required: false,
101
+ },
102
+ {
103
+ name: "BRAIN_UI_DANGEROUSLY_DISABLE_AUTH",
104
+ description: 'Set "1" to allow AUTH_MODE=none on a non-loopback host. Every network peer gets full agent access.',
105
+ default: "0",
106
+ required: false,
107
+ },
108
+ {
109
+ name: "BRAIN_UI_ALLOW_PASSWORD",
110
+ description: 'Set "1" to keep password login enabled after a passkey exists for the RP (break-glass recovery).',
111
+ default: "0",
112
+ required: false,
113
+ },
114
+ // WebAuthn / passkeys
115
+ {
116
+ name: "WEBAUTHN_RP_NAME",
117
+ description: "Relying-party display name shown by authenticators.",
118
+ default: "Brain UI",
119
+ required: false,
120
+ },
121
+ {
122
+ name: "WEBAUTHN_USER_NAME",
123
+ description: "WebAuthn user name shown by authenticators.",
124
+ default: "owner",
125
+ required: false,
126
+ },
127
+ {
128
+ name: "WEBAUTHN_USER_ID",
129
+ description: "Stable WebAuthn user handle (wire contract — burned into every resident credential; max 64 bytes; never change it after the first passkey).",
130
+ default: "brain-ui-owner",
131
+ required: false,
132
+ },
133
+ {
134
+ name: "WEBAUTHN_RP_ID",
135
+ description: "Relying-party id override for proxies that rewrite Host.",
136
+ default: "(derived from the request origin)",
137
+ required: false,
138
+ },
139
+ {
140
+ name: "WEBAUTHN_ORIGINS",
141
+ description: "Comma-separated extra origins allowed for WebAuthn ceremonies.",
142
+ default: "(empty)",
143
+ required: false,
144
+ },
145
+ {
146
+ name: "BRAIN_UI_ALLOW_LOOPBACK_ORIGIN",
147
+ description: 'Set "1" to accept loopback Origins for WebAuthn regardless of Host (dev-only, for the vite proxy).',
148
+ default: "0",
149
+ required: false,
150
+ },
151
+ // agent backend
152
+ {
153
+ name: "AGENT_BACKEND",
154
+ description: 'Primary agent backend: "claude" (default) or "pi".',
155
+ default: "claude",
156
+ required: false,
157
+ },
158
+ {
159
+ name: "CLAUDE_CODE_PATH",
160
+ description: "Path to the Claude Code native binary handed to the Agent SDK.",
161
+ default: "/usr/local/bin/claude",
162
+ required: false,
163
+ },
164
+ {
165
+ name: "BRAIN_UI_CLAUDE_DEFAULT_MODEL",
166
+ description: "Model the built-in default Claude profile is pinned to.",
167
+ default: "claude-sonnet-4-6",
168
+ required: false,
169
+ },
170
+ {
171
+ name: "BRAIN_UI_CLAUDE_PROFILES",
172
+ description: "JSON array of extra Anthropic-compatible inference profiles ({id,label,model?,baseUrl?,authTokenEnv?,apiKeyEnv?,modelAliases?}).",
173
+ default: "(none)",
174
+ required: false,
175
+ },
176
+ {
177
+ name: "BRAIN_UI_MODEL_DISCOVERY",
178
+ description: 'Model discovery against the Anthropic Models API; "0"/"off"/"false" disables. Defaults ON, except under a test runner (NODE_ENV=test) where it defaults OFF.',
179
+ default: "on (off under NODE_ENV=test)",
180
+ required: false,
181
+ },
182
+ {
183
+ name: "BRAIN_UI_MODEL_TTL_HOURS",
184
+ description: "How long a model-discovery result stays fresh, in hours.",
185
+ default: "24",
186
+ required: false,
187
+ },
188
+ {
189
+ name: "NODE_ENV",
190
+ description: "Only consulted for test-runner detection: flips the model-discovery default to off under bun test. Never gates any security behavior.",
191
+ default: "(unset)",
192
+ required: false,
193
+ },
194
+ // voice
195
+ {
196
+ name: "DEEPGRAM_API_KEY",
197
+ description: "Deepgram API key for streaming ASR (short-lived tokens are minted from it).",
198
+ default: null,
199
+ required: "VOICE_PROVIDER=deepgram (or any voice use without VOICE_PROVIDER=webspeech)",
200
+ },
201
+ {
202
+ name: "VOICE_PROVIDER",
203
+ description: 'Speech provider: "deepgram" or "webspeech" (opt-in only — Chromium streams audio to Google). Unset auto-detects deepgram when its key is present.',
204
+ default: "(auto-detect)",
205
+ required: false,
206
+ },
207
+ {
208
+ name: "VOICE_KEYTERM_LIMIT",
209
+ description: "Maximum custom-vocabulary terms built from the brain database.",
210
+ default: "500",
211
+ required: false,
212
+ },
213
+ {
214
+ name: "VOICE_CACHE_DIR",
215
+ description: "Directory holding the keyterm cache JSON.",
216
+ default: "$BRAIN_PATH/.brain-ui",
217
+ required: false,
218
+ },
219
+ ];
220
+ function list(raw) {
221
+ return (raw ?? "")
222
+ .split(",")
223
+ .map((s) => s.trim())
224
+ .filter(Boolean);
225
+ }
226
+ function flag(raw) {
227
+ return raw === "1";
228
+ }
229
+ const AUTH_MODES = ["password", "tailscale", "proxy", "none"];
230
+ /**
231
+ * Resolve an environment into a {@link ServerConfig}. Defaults to the real
232
+ * process environment; tests pass their own record instead of mutating it.
233
+ */
234
+ export function resolveServerConfig(env = process.env) {
235
+ const brainPath = env.BRAIN_PATH || join(env.HOME || "/root", "brain");
236
+ const rawAuthMode = env.AUTH_MODE?.trim().toLowerCase() || null;
237
+ const validMode = AUTH_MODES.find((mode) => mode === rawAuthMode) ?? null;
238
+ const rawDiscovery = env.BRAIN_UI_MODEL_DISCOVERY?.trim().toLowerCase();
239
+ const modelDiscovery = rawDiscovery
240
+ ? !(rawDiscovery === "0" || rawDiscovery === "off" || rawDiscovery === "false")
241
+ : env.NODE_ENV !== "test";
242
+ const rawTtl = Number(env.BRAIN_UI_MODEL_TTL_HOURS);
243
+ const ttlHours = Number.isFinite(rawTtl) && rawTtl > 0 ? rawTtl : 24;
244
+ return {
245
+ brainPath,
246
+ dbPath: env.DB_PATH || join(process.cwd(), "brain-ui.db"),
247
+ host: env.HOST ?? "",
248
+ sourceCommit: env.SOURCE_COMMIT ?? "dev",
249
+ allowedOrigins: list(env.ALLOWED_ORIGINS),
250
+ maxConcurrentSessions: Math.max(1, Number(env.MAX_CONCURRENT_SESSIONS) || 3),
251
+ auth: {
252
+ mode: validMode,
253
+ invalidMode: validMode ? null : rawAuthMode,
254
+ passwordHash: env.BRAIN_UI_PASSWORD_HASH || null,
255
+ cookieSecret: env.COOKIE_SECRET || null,
256
+ trustProxy: flag(env.TRUST_PROXY),
257
+ trustProxyHops: Math.max(1, Number(env.TRUST_PROXY_HOPS) || 1),
258
+ proxyAuthHeader: (env.PROXY_AUTH_HEADER || "x-forwarded-user").toLowerCase(),
259
+ dangerouslyDisableAuth: flag(env.BRAIN_UI_DANGEROUSLY_DISABLE_AUTH),
260
+ allowPassword: flag(env.BRAIN_UI_ALLOW_PASSWORD),
261
+ },
262
+ webauthn: {
263
+ rpName: env.WEBAUTHN_RP_NAME || "Brain UI",
264
+ userName: env.WEBAUTHN_USER_NAME || "owner",
265
+ userId: env.WEBAUTHN_USER_ID || "brain-ui-owner",
266
+ rpId: env.WEBAUTHN_RP_ID || null,
267
+ origins: list(env.WEBAUTHN_ORIGINS),
268
+ allowLoopbackOrigin: flag(env.BRAIN_UI_ALLOW_LOOPBACK_ORIGIN),
269
+ },
270
+ agent: {
271
+ backend: env.AGENT_BACKEND?.trim().toLowerCase() || null,
272
+ claudeCodePath: env.CLAUDE_CODE_PATH || "/usr/local/bin/claude",
273
+ defaultModel: env.BRAIN_UI_CLAUDE_DEFAULT_MODEL?.trim() || "claude-sonnet-4-6",
274
+ profilesJson: env.BRAIN_UI_CLAUDE_PROFILES?.trim() || null,
275
+ modelDiscovery,
276
+ modelTtlMs: ttlHours * 60 * 60 * 1000,
277
+ },
278
+ voice: {
279
+ provider: env.VOICE_PROVIDER?.trim().toLowerCase() || null,
280
+ deepgramApiKey: env.DEEPGRAM_API_KEY || null,
281
+ keytermLimit: Number(env.VOICE_KEYTERM_LIMIT || 500),
282
+ cacheDir: env.VOICE_CACHE_DIR || join(brainPath, ".brain-ui"),
283
+ },
284
+ };
285
+ }
286
+ /**
287
+ * The parent environment for spawned subprocesses (brain CLI, whatsup), plus
288
+ * overrides. Child processes legitimately inherit the whole environment
289
+ * (PATH, credentials for the tools they run) — that is process plumbing, not
290
+ * configuration, but it still reads `process.env`, so it lives behind this
291
+ * chokepoint.
292
+ */
293
+ export function subprocessEnv(extra = {}) {
294
+ return { ...process.env, ...extra };
295
+ }
296
+ //# sourceMappingURL=env.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env.js","sourceRoot":"","sources":["../../src/config/env.ts"],"names":[],"mappings":"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"}
@@ -1,9 +1,17 @@
1
- export declare function getCronStatus(): {
2
- name: string;
3
- lastRunAt: any;
4
- lastStatus: any;
5
- lastDurationMs: any;
6
- lastError: any;
7
- }[];
8
- export declare function triggerJob(name: string): Promise<boolean>;
1
+ import type { Database } from "bun:sqlite";
2
+ import type { BrainClient } from "../brain/client.js";
3
+ export interface CronScheduler {
4
+ getCronStatus(): Array<{
5
+ name: string;
6
+ lastRunAt: number | null;
7
+ lastStatus: string | null;
8
+ lastDurationMs: number | null;
9
+ lastError: string | null;
10
+ }>;
11
+ triggerJob(name: string): Promise<boolean>;
12
+ }
13
+ export declare function createCronScheduler(deps: {
14
+ db: Database;
15
+ brain: BrainClient;
16
+ }): CronScheduler;
9
17
  //# sourceMappingURL=scheduler.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../../src/cron/scheduler.ts"],"names":[],"mappings":"AAsDA,wBAAgB,aAAa;;;;;;IAiB5B;AAED,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAM/D"}
1
+ {"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../../src/cron/scheduler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAOtD,MAAM,WAAW,aAAa;IAC5B,aAAa,IAAI,KAAK,CAAC;QACrB,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;QACzB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;QAC1B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;QAC9B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;KAC1B,CAAC,CAAC;IACH,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC5C;AAOD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE;IACxC,EAAE,EAAE,QAAQ,CAAC;IACb,KAAK,EAAE,WAAW,CAAC;CACpB,GAAG,aAAa,CAsEhB"}
@@ -1,58 +1,59 @@
1
- import { getDb } from "../db/client.js";
2
- import { brainSync, brainIndex, brainValidate } from "../brain/client.js";
3
- const jobs = [
4
- {
5
- name: "sync",
6
- handler: async () => {
7
- await brainSync();
8
- await brainIndex();
1
+ export function createCronScheduler(deps) {
2
+ const { db, brain } = deps;
3
+ const jobs = [
4
+ {
5
+ name: "sync",
6
+ handler: async () => {
7
+ await brain.sync();
8
+ await brain.index();
9
+ },
9
10
  },
10
- },
11
- {
12
- name: "validate",
13
- handler: async () => {
14
- await brainValidate();
11
+ {
12
+ name: "validate",
13
+ handler: async () => {
14
+ await brain.validate();
15
+ },
15
16
  },
16
- },
17
- ];
18
- async function runJob(job) {
19
- const db = getDb();
20
- const startedAt = Date.now();
21
- db.prepare("INSERT INTO cron_runs (job_name, started_at, status) VALUES (?, ?, 'running')").run(job.name, startedAt);
22
- try {
23
- await job.handler();
24
- const durationMs = Date.now() - startedAt;
25
- db.prepare("UPDATE cron_runs SET status = 'success', finished_at = ?, duration_ms = ? WHERE job_name = ? AND started_at = ?").run(Date.now(), durationMs, job.name, startedAt);
26
- console.log(`[cron] ${job.name} completed in ${durationMs}ms`);
17
+ ];
18
+ async function runJob(job) {
19
+ const startedAt = Date.now();
20
+ db.prepare("INSERT INTO cron_runs (job_name, started_at, status) VALUES (?, ?, 'running')").run(job.name, startedAt);
21
+ try {
22
+ await job.handler();
23
+ const durationMs = Date.now() - startedAt;
24
+ db.prepare("UPDATE cron_runs SET status = 'success', finished_at = ?, duration_ms = ? WHERE job_name = ? AND started_at = ?").run(Date.now(), durationMs, job.name, startedAt);
25
+ console.log(`[cron] ${job.name} completed in ${durationMs}ms`);
26
+ }
27
+ catch (err) {
28
+ const durationMs = Date.now() - startedAt;
29
+ const message = err instanceof Error ? err.message : String(err);
30
+ db.prepare("UPDATE cron_runs SET status = 'error', finished_at = ?, duration_ms = ?, error_message = ? WHERE job_name = ? AND started_at = ?").run(Date.now(), durationMs, message, job.name, startedAt);
31
+ console.error(`[cron] ${job.name} failed: ${message}`);
32
+ }
27
33
  }
28
- catch (err) {
29
- const durationMs = Date.now() - startedAt;
30
- const message = err instanceof Error ? err.message : String(err);
31
- db.prepare("UPDATE cron_runs SET status = 'error', finished_at = ?, duration_ms = ?, error_message = ? WHERE job_name = ? AND started_at = ?").run(Date.now(), durationMs, message, job.name, startedAt);
32
- console.error(`[cron] ${job.name} failed: ${message}`);
33
- }
34
- }
35
- export function getCronStatus() {
36
- const db = getDb();
37
- return jobs.map((job) => {
38
- const lastRun = db
39
- .query("SELECT * FROM cron_runs WHERE job_name = ? ORDER BY started_at DESC LIMIT 1")
40
- .get(job.name);
41
- return {
42
- name: job.name,
43
- lastRunAt: lastRun?.started_at ?? null,
44
- lastStatus: lastRun?.status ?? null,
45
- lastDurationMs: lastRun?.duration_ms ?? null,
46
- lastError: lastRun?.error_message ?? null,
47
- };
48
- });
49
- }
50
- export async function triggerJob(name) {
51
- const job = jobs.find((j) => j.name === name);
52
- if (!job)
53
- return false;
54
- // Run async (don't await - let it run in background)
55
- runJob(job);
56
- return true;
34
+ return {
35
+ getCronStatus() {
36
+ return jobs.map((job) => {
37
+ const lastRun = db
38
+ .query("SELECT * FROM cron_runs WHERE job_name = ? ORDER BY started_at DESC LIMIT 1")
39
+ .get(job.name);
40
+ return {
41
+ name: job.name,
42
+ lastRunAt: lastRun?.started_at ?? null,
43
+ lastStatus: lastRun?.status ?? null,
44
+ lastDurationMs: lastRun?.duration_ms ?? null,
45
+ lastError: lastRun?.error_message ?? null,
46
+ };
47
+ });
48
+ },
49
+ async triggerJob(name) {
50
+ const job = jobs.find((j) => j.name === name);
51
+ if (!job)
52
+ return false;
53
+ // Run async (don't await - let it run in background)
54
+ runJob(job);
55
+ return true;
56
+ },
57
+ };
57
58
  }
58
59
  //# sourceMappingURL=scheduler.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"scheduler.js","sourceRoot":"","sources":["../../src/cron/scheduler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAY1E,MAAM,IAAI,GAAc;IACtB;QACE,IAAI,EAAE,MAAM;QACZ,OAAO,EAAE,KAAK,IAAI,EAAE;YAClB,MAAM,SAAS,EAAE,CAAC;YAClB,MAAM,UAAU,EAAE,CAAC;QACrB,CAAC;KACF;IACD;QACE,IAAI,EAAE,UAAU;QAChB,OAAO,EAAE,KAAK,IAAI,EAAE;YAClB,MAAM,aAAa,EAAE,CAAC;QACxB,CAAC;KACF;CACF,CAAC;AAEF,KAAK,UAAU,MAAM,CAAC,GAAY;IAChC,MAAM,EAAE,GAAG,KAAK,EAAE,CAAC;IACnB,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAE7B,EAAE,CAAC,OAAO,CACR,+EAA+E,CAChF,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;IAE3B,IAAI,CAAC;QACH,MAAM,GAAG,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;QAC1C,EAAE,CAAC,OAAO,CACR,iHAAiH,CAClH,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,UAAU,EAAE,GAAG,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QACnD,OAAO,CAAC,GAAG,CAAC,UAAU,GAAG,CAAC,IAAI,iBAAiB,UAAU,IAAI,CAAC,CAAC;IACjE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;QAC1C,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,EAAE,CAAC,OAAO,CACR,kIAAkI,CACnI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,UAAU,EAAE,OAAO,EAAE,GAAG,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QAC5D,OAAO,CAAC,KAAK,CAAC,UAAU,GAAG,CAAC,IAAI,YAAY,OAAO,EAAE,CAAC,CAAC;IACzD,CAAC;AACH,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,MAAM,EAAE,GAAG,KAAK,EAAE,CAAC;IACnB,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;QACtB,MAAM,OAAO,GAAG,EAAE;aACf,KAAK,CACJ,6EAA6E,CAC9E;aACA,GAAG,CAAC,GAAG,CAAC,IAAI,CAAQ,CAAC;QAExB,OAAO;YACL,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,SAAS,EAAE,OAAO,EAAE,UAAU,IAAI,IAAI;YACtC,UAAU,EAAE,OAAO,EAAE,MAAM,IAAI,IAAI;YACnC,cAAc,EAAE,OAAO,EAAE,WAAW,IAAI,IAAI;YAC5C,SAAS,EAAE,OAAO,EAAE,aAAa,IAAI,IAAI;SAC1C,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,IAAY;IAC3C,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;IAC9C,IAAI,CAAC,GAAG;QAAE,OAAO,KAAK,CAAC;IACvB,qDAAqD;IACrD,MAAM,CAAC,GAAG,CAAC,CAAC;IACZ,OAAO,IAAI,CAAC;AACd,CAAC"}
1
+ {"version":3,"file":"scheduler.js","sourceRoot":"","sources":["../../src/cron/scheduler.ts"],"names":[],"mappings":"AAwBA,MAAM,UAAU,mBAAmB,CAAC,IAGnC;IACC,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC;IAE3B,MAAM,IAAI,GAAc;QACtB;YACE,IAAI,EAAE,MAAM;YACZ,OAAO,EAAE,KAAK,IAAI,EAAE;gBAClB,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC;gBACnB,MAAM,KAAK,CAAC,KAAK,EAAE,CAAC;YACtB,CAAC;SACF;QACD;YACE,IAAI,EAAE,UAAU;YAChB,OAAO,EAAE,KAAK,IAAI,EAAE;gBAClB,MAAM,KAAK,CAAC,QAAQ,EAAE,CAAC;YACzB,CAAC;SACF;KACF,CAAC;IAEF,KAAK,UAAU,MAAM,CAAC,GAAY;QAChC,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAE7B,EAAE,CAAC,OAAO,CACR,+EAA+E,CAChF,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QAE3B,IAAI,CAAC;YACH,MAAM,GAAG,CAAC,OAAO,EAAE,CAAC;YACpB,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;YAC1C,EAAE,CAAC,OAAO,CACR,iHAAiH,CAClH,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,UAAU,EAAE,GAAG,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;YACnD,OAAO,CAAC,GAAG,CAAC,UAAU,GAAG,CAAC,IAAI,iBAAiB,UAAU,IAAI,CAAC,CAAC;QACjE,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;YAC1C,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACjE,EAAE,CAAC,OAAO,CACR,kIAAkI,CACnI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,UAAU,EAAE,OAAO,EAAE,GAAG,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;YAC5D,OAAO,CAAC,KAAK,CAAC,UAAU,GAAG,CAAC,IAAI,YAAY,OAAO,EAAE,CAAC,CAAC;QACzD,CAAC;IACH,CAAC;IAED,OAAO;QACL,aAAa;YACX,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;gBACtB,MAAM,OAAO,GAAG,EAAE;qBACf,KAAK,CACJ,6EAA6E,CAC9E;qBACA,GAAG,CAAC,GAAG,CAAC,IAAI,CAAQ,CAAC;gBAExB,OAAO;oBACL,IAAI,EAAE,GAAG,CAAC,IAAI;oBACd,SAAS,EAAE,OAAO,EAAE,UAAU,IAAI,IAAI;oBACtC,UAAU,EAAE,OAAO,EAAE,MAAM,IAAI,IAAI;oBACnC,cAAc,EAAE,OAAO,EAAE,WAAW,IAAI,IAAI;oBAC5C,SAAS,EAAE,OAAO,EAAE,aAAa,IAAI,IAAI;iBAC1C,CAAC;YACJ,CAAC,CAAC,CAAC;QACL,CAAC;QAED,KAAK,CAAC,UAAU,CAAC,IAAY;YAC3B,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;YAC9C,IAAI,CAAC,GAAG;gBAAE,OAAO,KAAK,CAAC;YACvB,qDAAqD;YACrD,MAAM,CAAC,GAAG,CAAC,CAAC;YACZ,OAAO,IAAI,CAAC;QACd,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * The one place ui-server opens the BRAIN database (`$BRAIN_PATH/brain.db`).
3
+ *
4
+ * core owns that schema and versions it independently, so every reader on this
5
+ * side of the package boundary must check `schema_version` before trusting the
6
+ * shape — a reader without the gate breaks at some later `brain index` with an
7
+ * error pointing nowhere near the cause. A repo-level lint bans `new Database(`
8
+ * in ui-server/src outside src/db/, so a new reader cannot skip this wrapper.
9
+ *
10
+ * (src/db/client.ts is unrelated: that is the UI's OWN database — sessions,
11
+ * passkeys, settings — whose schema this package owns and migrates itself.)
12
+ */
13
+ import { Database } from "bun:sqlite";
14
+ /**
15
+ * The oldest brain.db schema the SQL in this package is written against:
16
+ * v3 added `documents.asset_type`, which both the graph reader and the
17
+ * keyterm extractor select on.
18
+ */
19
+ export declare const MIN_BRAIN_SCHEMA_VERSION = 3;
20
+ export type BrainDbUnavailableReason = "missing" | "schema";
21
+ /** brain.db cannot be read: absent, or older than the caller's floor. */
22
+ export declare class BrainDbUnavailableError extends Error {
23
+ readonly reason: BrainDbUnavailableReason;
24
+ readonly schemaVersion: number;
25
+ constructor(reason: BrainDbUnavailableReason, schemaVersion: number);
26
+ }
27
+ export interface OpenBrainDbOptions {
28
+ /**
29
+ * Refuse (throw {@link BrainDbUnavailableError}) below this schema_version.
30
+ * Defaults to {@link MIN_BRAIN_SCHEMA_VERSION}. Callers with a newer floor
31
+ * (the graph tables arrived in v8) still gate per-feature on the returned
32
+ * `schemaVersion` — degrading, not throwing, where a partial read is useful.
33
+ */
34
+ minSchemaVersion?: number;
35
+ }
36
+ export interface BrainDbHandle {
37
+ db: Database;
38
+ /** schema_version from index_metadata; 0 when unreadable. */
39
+ schemaVersion: number;
40
+ }
41
+ /**
42
+ * Open brain.db read-only and assert its schema_version. Opened per call, not
43
+ * cached (the keyterm-builder precedent): WAL makes concurrent readers cheap,
44
+ * and a short-lived handle means a `brain sync` swapping the file underneath
45
+ * can never leave the server on a stale page cache.
46
+ */
47
+ export declare function openBrainDb(brainPath: string, options?: OpenBrainDbOptions): BrainDbHandle;
48
+ /** Open, run `fn`, close — the shape every per-request reader wants. */
49
+ export declare function withBrainDb<T>(brainPath: string, options: OpenBrainDbOptions, fn: (db: Database, schemaVersion: number) => T): T;
50
+ //# sourceMappingURL=brain-db.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brain-db.d.ts","sourceRoot":"","sources":["../../src/db/brain-db.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAItC;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,IAAI,CAAC;AAE1C,MAAM,MAAM,wBAAwB,GAAG,SAAS,GAAG,QAAQ,CAAC;AAE5D,yEAAyE;AACzE,qBAAa,uBAAwB,SAAQ,KAAK;IAE9C,QAAQ,CAAC,MAAM,EAAE,wBAAwB;IACzC,QAAQ,CAAC,aAAa,EAAE,MAAM;gBADrB,MAAM,EAAE,wBAAwB,EAChC,aAAa,EAAE,MAAM;CAKjC;AAED,MAAM,WAAW,kBAAkB;IACjC;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,QAAQ,CAAC;IACb,6DAA6D;IAC7D,aAAa,EAAE,MAAM,CAAC;CACvB;AAgBD;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE,kBAAuB,GAC/B,aAAa,CAaf;AAED,wEAAwE;AACxE,wBAAgB,WAAW,CAAC,CAAC,EAC3B,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,kBAAkB,EAC3B,EAAE,EAAE,CAAC,EAAE,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC,GAC7C,CAAC,CAOH"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * The one place ui-server opens the BRAIN database (`$BRAIN_PATH/brain.db`).
3
+ *
4
+ * core owns that schema and versions it independently, so every reader on this
5
+ * side of the package boundary must check `schema_version` before trusting the
6
+ * shape — a reader without the gate breaks at some later `brain index` with an
7
+ * error pointing nowhere near the cause. A repo-level lint bans `new Database(`
8
+ * in ui-server/src outside src/db/, so a new reader cannot skip this wrapper.
9
+ *
10
+ * (src/db/client.ts is unrelated: that is the UI's OWN database — sessions,
11
+ * passkeys, settings — whose schema this package owns and migrates itself.)
12
+ */
13
+ import { Database } from "bun:sqlite";
14
+ import { existsSync } from "fs";
15
+ import { join } from "path";
16
+ /**
17
+ * The oldest brain.db schema the SQL in this package is written against:
18
+ * v3 added `documents.asset_type`, which both the graph reader and the
19
+ * keyterm extractor select on.
20
+ */
21
+ export const MIN_BRAIN_SCHEMA_VERSION = 3;
22
+ /** brain.db cannot be read: absent, or older than the caller's floor. */
23
+ export class BrainDbUnavailableError extends Error {
24
+ reason;
25
+ schemaVersion;
26
+ constructor(reason, schemaVersion) {
27
+ super(`brain_db_unavailable: ${reason} (schema_version=${schemaVersion})`);
28
+ this.reason = reason;
29
+ this.schemaVersion = schemaVersion;
30
+ this.name = "BrainDbUnavailableError";
31
+ }
32
+ }
33
+ function readSchemaVersion(db) {
34
+ try {
35
+ const row = db
36
+ .query("SELECT value FROM index_metadata WHERE key = 'schema_version'")
37
+ .get();
38
+ const parsed = Number.parseInt(row?.value ?? "", 10);
39
+ return Number.isFinite(parsed) ? parsed : 0;
40
+ }
41
+ catch {
42
+ return 0;
43
+ }
44
+ }
45
+ /**
46
+ * Open brain.db read-only and assert its schema_version. Opened per call, not
47
+ * cached (the keyterm-builder precedent): WAL makes concurrent readers cheap,
48
+ * and a short-lived handle means a `brain sync` swapping the file underneath
49
+ * can never leave the server on a stale page cache.
50
+ */
51
+ export function openBrainDb(brainPath, options = {}) {
52
+ const min = options.minSchemaVersion ?? MIN_BRAIN_SCHEMA_VERSION;
53
+ const dbPath = join(brainPath, "brain.db");
54
+ if (!existsSync(dbPath)) {
55
+ throw new BrainDbUnavailableError("missing", 0);
56
+ }
57
+ const db = new Database(dbPath, { readonly: true });
58
+ const schemaVersion = readSchemaVersion(db);
59
+ if (schemaVersion < min) {
60
+ db.close();
61
+ throw new BrainDbUnavailableError("schema", schemaVersion);
62
+ }
63
+ return { db, schemaVersion };
64
+ }
65
+ /** Open, run `fn`, close — the shape every per-request reader wants. */
66
+ export function withBrainDb(brainPath, options, fn) {
67
+ const { db, schemaVersion } = openBrainDb(brainPath, options);
68
+ try {
69
+ return fn(db, schemaVersion);
70
+ }
71
+ finally {
72
+ db.close();
73
+ }
74
+ }
75
+ //# sourceMappingURL=brain-db.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brain-db.js","sourceRoot":"","sources":["../../src/db/brain-db.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAChC,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B;;;;GAIG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,CAAC;AAI1C,yEAAyE;AACzE,MAAM,OAAO,uBAAwB,SAAQ,KAAK;IAErC;IACA;IAFX,YACW,MAAgC,EAChC,aAAqB;QAE9B,KAAK,CAAC,yBAAyB,MAAM,oBAAoB,aAAa,GAAG,CAAC,CAAC;QAHlE,WAAM,GAAN,MAAM,CAA0B;QAChC,kBAAa,GAAb,aAAa,CAAQ;QAG9B,IAAI,CAAC,IAAI,GAAG,yBAAyB,CAAC;IACxC,CAAC;CACF;AAkBD,SAAS,iBAAiB,CAAC,EAAY;IACrC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,EAAE;aACX,KAAK,CACJ,+DAA+D,CAChE;aACA,GAAG,EAAE,CAAC;QACT,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,KAAK,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;QACrD,OAAO,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,CAAC;IACX,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CACzB,SAAiB,EACjB,UAA8B,EAAE;IAEhC,MAAM,GAAG,GAAG,OAAO,CAAC,gBAAgB,IAAI,wBAAwB,CAAC;IACjE,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;IAC3C,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,uBAAuB,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC;IAClD,CAAC;IACD,MAAM,EAAE,GAAG,IAAI,QAAQ,CAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IACpD,MAAM,aAAa,GAAG,iBAAiB,CAAC,EAAE,CAAC,CAAC;IAC5C,IAAI,aAAa,GAAG,GAAG,EAAE,CAAC;QACxB,EAAE,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,uBAAuB,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC;IAC7D,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,aAAa,EAAE,CAAC;AAC/B,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,WAAW,CACzB,SAAiB,EACjB,OAA2B,EAC3B,EAA8C;IAE9C,MAAM,EAAE,EAAE,EAAE,aAAa,EAAE,GAAG,WAAW,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IAC9D,IAAI,CAAC;QACH,OAAO,EAAE,CAAC,EAAE,EAAE,aAAa,CAAC,CAAC;IAC/B,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,KAAK,EAAE,CAAC;IACb,CAAC;AACH,CAAC"}
@@ -1,10 +1,12 @@
1
1
  import { Database } from "bun:sqlite";
2
2
  /**
3
- * Override the database path before the first `getDb()` call (used by
4
- * `createApp({ dbPath })`). Configuring after the handle exists would silently
5
- * split state across two files — refuse instead.
3
+ * The UI's OWN SQLite database (sessions, passkeys, settings, cron runs)
4
+ * distinct from the brain database, which is opened read-only via
5
+ * src/db/brain-db.ts.
6
+ *
7
+ * No module-level handle: `createApp()` opens one per app instance and threads
8
+ * it to every consumer, so two apps with different configuration can coexist
9
+ * in one process and a test gets an isolated database by construction.
6
10
  */
7
- export declare function configureDb(dbPath: string): void;
8
- export declare function getDb(): Database;
9
- export declare function closeDb(): void;
11
+ export declare function createUiDb(dbPath: string): Database;
10
12
  //# sourceMappingURL=client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/db/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAOtC;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAOhD;AAED,wBAAgB,KAAK,IAAI,QAAQ,CAUhC;AAED,wBAAgB,OAAO,SAKtB"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/db/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAItC;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,CAMnD"}