@andespindola/brainlink 1.6.15 → 1.8.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.
@@ -0,0 +1,110 @@
1
+ // Single source of truth for the unified neon identity shared by every
2
+ // self-contained page served by the graph web server (login, settings, OAuth
3
+ // authorize, and the graph explorer CSS). Keeping the tokens, font import and
4
+ // ambient effects in one place means the app -> SSO -> graph handoff reads as a
5
+ // single product, and a palette change lands everywhere at once.
6
+ //
7
+ // The identity mirrors the SaaS app: #0a0e1a base, cyan #22d3ee + violet
8
+ // #8b5cf6 accents, Space Grotesk headings / DM Sans body via a Google Fonts
9
+ // @import with a robust system fallback so it degrades gracefully offline. A
10
+ // monospace stack stays for tokens / code / graph node ids.
11
+ // Google Fonts @import for Space Grotesk (headings) + DM Sans (body). Placed at
12
+ // the very top of the served stylesheet as @import requires. If the network is
13
+ // unavailable the fallback stacks in --font-heading / --font-body take over.
14
+ export const neonFontImport = () => "@import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;700&family=Space+Grotesk:wght@400;500;600;700&display=swap');";
15
+ // Font-family stacks. Fallbacks keep the layout correct and legible offline.
16
+ export const neonFontStacks = () => ` --font-heading: "Space Grotesk", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
17
+ --font-body: "DM Sans", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
18
+ --font-mono: "Crowquill Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;`;
19
+ // The canonical color tokens. Cyan is the primary/node accent, violet the
20
+ // secondary/context accent; together they form the primary gradient.
21
+ export const neonColorTokens = () => ` --bg: #0a0e1a;
22
+ --surface: #0f1420;
23
+ --surface-2: #131a2a;
24
+ --panel: rgba(15, 20, 32, 0.82);
25
+ --panel-strong: rgba(19, 26, 42, 0.92);
26
+ --line: rgba(255, 255, 255, 0.09);
27
+ --line-strong: rgba(255, 255, 255, 0.14);
28
+ --text: #e7ecf6;
29
+ --muted: #8b94ab;
30
+ --accent: #22d3ee;
31
+ --accent-hover: #67e8f9;
32
+ --accent-weak: rgba(34, 211, 238, 0.16);
33
+ --violet: #8b5cf6;
34
+ --violet-hover: #a78bfa;
35
+ --gradient: linear-gradient(120deg, #22d3ee, #8b5cf6);
36
+ --glow-cyan: 0 0 0 1px rgba(34, 211, 238, 0.4), 0 0 22px rgba(34, 211, 238, 0.28);
37
+ --danger: #ff6b6b;
38
+ --success: #57d9a3;`;
39
+ // Full :root token block (colors + fonts) for the branded auth-style pages.
40
+ export const neonRootTokens = () => ` color-scheme: dark;
41
+ ${neonColorTokens()}
42
+ ${neonFontStacks()}`;
43
+ // The faint cyber-grid + soft radial glow background used on branded screens.
44
+ // Layered so the grid sits above the base and the radial glow blooms from the
45
+ // top. Kept as a reusable string so login / authorize / settings / graph-stage
46
+ // all render the exact same backdrop.
47
+ export const neonGridBackground = () => ` background:
48
+ linear-gradient(rgba(34, 211, 238, 0.05) 1px, transparent 1px),
49
+ linear-gradient(90deg, rgba(139, 92, 246, 0.05) 1px, transparent 1px),
50
+ radial-gradient(circle at 50% -10%, rgba(34, 211, 238, 0.16), transparent 46%),
51
+ radial-gradient(circle at 82% 8%, rgba(139, 92, 246, 0.14), transparent 42%),
52
+ var(--bg);
53
+ background-size: 30px 30px, 30px 30px, auto, auto;`;
54
+ // A gradient wordmark: cyan -> violet text clipped to the glyphs. Used for the
55
+ // "Brainlink" brand lockup so every entry point shares the same signature.
56
+ export const neonWordmark = () => ` background: var(--gradient);
57
+ -webkit-background-clip: text;
58
+ background-clip: text;
59
+ -webkit-text-fill-color: transparent;
60
+ color: transparent;`;
61
+ // Slow ambient glow "blobs" behind branded cards, plus reduced-motion + focus
62
+ // helpers. Scoped to the branded auth-style pages via the ::before/::after on
63
+ // body; the caller supplies the body selector context.
64
+ export const neonAmbientEffects = () => `@keyframes neon-blob-a {
65
+ 0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
66
+ 50% { transform: translate3d(6%, 8%, 0) scale(1.12); }
67
+ }
68
+
69
+ @keyframes neon-blob-b {
70
+ 0%, 100% { transform: translate3d(0, 0, 0) scale(1.05); }
71
+ 50% { transform: translate3d(-7%, -6%, 0) scale(0.92); }
72
+ }
73
+
74
+ @keyframes neon-fade-up {
75
+ from { opacity: 0; transform: translateY(10px); }
76
+ to { opacity: 1; transform: translateY(0); }
77
+ }
78
+
79
+ body::before,
80
+ body::after {
81
+ content: "";
82
+ position: fixed;
83
+ z-index: -1;
84
+ width: 46vmax;
85
+ height: 46vmax;
86
+ border-radius: 50%;
87
+ filter: blur(90px);
88
+ pointer-events: none;
89
+ }
90
+
91
+ body::before {
92
+ top: -18vmax;
93
+ left: -14vmax;
94
+ background: radial-gradient(circle, rgba(34, 211, 238, 0.12), transparent 70%);
95
+ animation: neon-blob-a 22s ease-in-out infinite;
96
+ }
97
+
98
+ body::after {
99
+ right: -16vmax;
100
+ bottom: -18vmax;
101
+ background: radial-gradient(circle, rgba(139, 92, 246, 0.12), transparent 70%);
102
+ animation: neon-blob-b 26s ease-in-out infinite;
103
+ }
104
+
105
+ @media (prefers-reduced-motion: reduce) {
106
+ body::before,
107
+ body::after {
108
+ animation: none;
109
+ }
110
+ }`;
@@ -1,21 +1,14 @@
1
1
  // Self-contained branded Settings page for the graph web server. Served as a
2
- // plain string (inline <style> + inline <script>, no external assets, no
3
- // framework) so it works identically for a global npm install. The visual
4
- // identity mirrors the auth pages (see login-page.ts / oauth-authorize-page.ts):
5
- // dark theme on #08131d, the same grid/radial gradient, Crowquill Mono with a
6
- // monospace fallback, and the #5aa8ff accent used for graph nodes.
7
- const settingsStyles = () => `:root {
8
- color-scheme: dark;
9
- --bg: #08131d;
10
- --panel: #0d1823;
11
- --panel-strong: #112130;
12
- --line: rgba(143, 172, 204, 0.18);
13
- --text: #edf4ff;
14
- --muted: #97a9bd;
15
- --accent: #5aa8ff;
16
- --accent-weak: rgba(90, 168, 255, 0.18);
17
- --danger: #ff6b6b;
18
- --success: #57d9a3;
2
+ // plain string (inline <style> + inline <script>, no external assets beyond the
3
+ // shared Google Fonts @import, no framework) so it works identically for a
4
+ // global npm install. The visual identity is the unified neon system shared
5
+ // with the auth pages and the graph client (see neon-theme.ts): #0a0e1a base,
6
+ // faint cyber grid, cyan #22d3ee + violet #8b5cf6, Space Grotesk headings / DM
7
+ // Sans body, glass card + gradient wordmark + cyan focus glow.
8
+ import { neonAmbientEffects, neonFontImport, neonGridBackground, neonRootTokens, neonWordmark } from './neon-theme.js';
9
+ const settingsStyles = () => `${neonFontImport()}
10
+ :root {
11
+ ${neonRootTokens()}
19
12
  }
20
13
 
21
14
  * {
@@ -37,30 +30,40 @@ body {
37
30
  min-height: 100dvh;
38
31
  padding: 24px;
39
32
  color: var(--text);
40
- font-family: "Crowquill Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
41
- background:
42
- linear-gradient(rgba(164, 197, 230, 0.05) 1px, transparent 1px),
43
- linear-gradient(90deg, rgba(164, 197, 230, 0.05) 1px, transparent 1px),
44
- radial-gradient(circle at top, rgba(43, 93, 143, 0.22), transparent 44%),
45
- #08131d;
46
- background-size: 28px 28px, 28px 28px, auto;
33
+ font-family: var(--font-body);
34
+ ${neonGridBackground()}
47
35
  }
48
36
 
37
+ ${neonAmbientEffects()}
38
+
49
39
  button,
50
40
  input,
51
- select {
41
+ select,
42
+ textarea {
52
43
  font: inherit;
53
44
  }
54
45
 
46
+ :focus-visible {
47
+ outline: 2px solid var(--accent);
48
+ outline-offset: 2px;
49
+ }
50
+
55
51
  .settings-card {
56
52
  width: 100%;
57
53
  max-width: 440px;
58
54
  padding: 32px 28px;
59
55
  border: 1px solid var(--line);
60
- border-radius: 14px;
61
- background: rgba(13, 24, 35, 0.92);
62
- box-shadow: 0 24px 80px rgba(1, 6, 13, 0.48);
63
- backdrop-filter: blur(8px);
56
+ border-radius: 16px;
57
+ background: var(--panel);
58
+ box-shadow: 0 24px 80px rgba(2, 6, 18, 0.55);
59
+ backdrop-filter: blur(16px);
60
+ animation: neon-fade-up 260ms cubic-bezier(0.16, 1, 0.3, 1) both;
61
+ }
62
+
63
+ @media (prefers-reduced-motion: reduce) {
64
+ .settings-card {
65
+ animation: none;
66
+ }
64
67
  }
65
68
 
66
69
  .settings-brand {
@@ -70,8 +73,11 @@ select {
70
73
  }
71
74
 
72
75
  .settings-brand strong {
73
- font-size: 26px;
74
- letter-spacing: 0.01em;
76
+ font-family: var(--font-heading);
77
+ font-size: 28px;
78
+ font-weight: 700;
79
+ letter-spacing: -0.01em;
80
+ ${neonWordmark()}
75
81
  }
76
82
 
77
83
  .settings-brand span {
@@ -101,15 +107,17 @@ select {
101
107
  height: 42px;
102
108
  padding: 0 14px;
103
109
  border: 1px solid var(--line);
104
- border-radius: 8px;
110
+ border-radius: 10px;
105
111
  outline: none;
106
- background: rgba(12, 24, 36, 0.94);
112
+ background: rgba(19, 26, 42, 0.7);
107
113
  color: var(--text);
114
+ transition: border-color 150ms ease, box-shadow 150ms ease;
108
115
  }
109
116
 
110
117
  .settings-field input:focus,
111
118
  .settings-field select:focus {
112
119
  border-color: var(--accent);
120
+ box-shadow: var(--glow-cyan);
113
121
  }
114
122
 
115
123
  .settings-check {
@@ -134,17 +142,22 @@ select {
134
142
  .settings-submit {
135
143
  height: 44px;
136
144
  margin-top: 4px;
137
- border: 1px solid var(--accent);
138
- border-radius: 8px;
139
- background: var(--accent-weak);
140
- color: var(--text);
145
+ border: 1px solid transparent;
146
+ border-radius: 10px;
147
+ background: var(--gradient);
148
+ color: #05070f;
149
+ font-weight: 600;
141
150
  cursor: pointer;
142
- transition: background 120ms ease, opacity 120ms ease;
151
+ transition: box-shadow 150ms ease, transform 120ms ease, opacity 120ms ease;
143
152
  }
144
153
 
145
154
  .settings-submit:hover:not(:disabled),
146
155
  .settings-submit:focus:not(:disabled) {
147
- background: rgba(90, 168, 255, 0.3);
156
+ box-shadow: 0 0 24px rgba(34, 211, 238, 0.4), 0 0 40px rgba(139, 92, 246, 0.28);
157
+ }
158
+
159
+ .settings-submit:active:not(:disabled) {
160
+ transform: scale(0.97);
148
161
  }
149
162
 
150
163
  .settings-submit:disabled {
@@ -195,6 +208,8 @@ select {
195
208
 
196
209
  .settings-section h2 {
197
210
  margin: 0;
211
+ font-family: var(--font-heading);
212
+ font-weight: 600;
198
213
  font-size: 16px;
199
214
  letter-spacing: 0.01em;
200
215
  }
@@ -210,28 +225,34 @@ select {
210
225
  margin: 0;
211
226
  padding: 10px 12px;
212
227
  border: 1px solid var(--line);
213
- border-radius: 8px;
214
- background: rgba(12, 24, 36, 0.6);
228
+ border-radius: 10px;
229
+ background: rgba(19, 26, 42, 0.55);
215
230
  color: var(--muted);
216
231
  font-size: 12px;
217
232
  line-height: 1.5;
218
233
  overflow-wrap: anywhere;
219
234
  }
220
235
 
236
+ .settings-status code {
237
+ font-family: var(--font-mono);
238
+ color: var(--accent);
239
+ }
240
+
221
241
  .settings-secondary {
222
242
  height: 42px;
223
243
  border: 1px solid var(--line);
224
- border-radius: 8px;
225
- background: rgba(12, 24, 36, 0.94);
244
+ border-radius: 10px;
245
+ background: rgba(19, 26, 42, 0.7);
226
246
  color: var(--text);
227
247
  cursor: pointer;
228
- transition: border-color 120ms ease, background 120ms ease, opacity 120ms ease;
248
+ transition: border-color 150ms ease, background 150ms ease, box-shadow 150ms ease, opacity 120ms ease;
229
249
  }
230
250
 
231
251
  .settings-secondary:hover:not(:disabled),
232
252
  .settings-secondary:focus:not(:disabled) {
233
253
  border-color: var(--accent);
234
- background: rgba(90, 168, 255, 0.14);
254
+ background: var(--accent-weak);
255
+ box-shadow: var(--glow-cyan);
235
256
  }
236
257
 
237
258
  .settings-secondary:disabled {
@@ -253,19 +274,21 @@ select {
253
274
  min-height: 96px;
254
275
  padding: 10px 12px;
255
276
  border: 1px solid var(--line);
256
- border-radius: 8px;
277
+ border-radius: 10px;
257
278
  outline: none;
258
279
  resize: vertical;
259
- background: rgba(12, 24, 36, 0.94);
280
+ background: rgba(19, 26, 42, 0.7);
260
281
  color: var(--text);
261
- font: inherit;
282
+ font-family: var(--font-mono);
262
283
  font-size: 11px;
263
284
  line-height: 1.4;
264
285
  overflow-wrap: anywhere;
286
+ transition: border-color 150ms ease, box-shadow 150ms ease;
265
287
  }
266
288
 
267
289
  .settings-key textarea:focus {
268
290
  border-color: var(--accent);
291
+ box-shadow: var(--glow-cyan);
269
292
  }
270
293
 
271
294
  .settings-key-actions {
@@ -278,18 +301,19 @@ select {
278
301
  height: 32px;
279
302
  padding: 0 14px;
280
303
  border: 1px solid var(--line);
281
- border-radius: 8px;
282
- background: rgba(12, 24, 36, 0.94);
304
+ border-radius: 10px;
305
+ background: rgba(19, 26, 42, 0.7);
283
306
  color: var(--text);
284
307
  cursor: pointer;
285
308
  font-size: 12px;
286
- transition: border-color 120ms ease, background 120ms ease;
309
+ transition: border-color 150ms ease, background 150ms ease, box-shadow 150ms ease;
287
310
  }
288
311
 
289
312
  .settings-copy:hover,
290
313
  .settings-copy:focus {
291
314
  border-color: var(--accent);
292
- background: rgba(90, 168, 255, 0.14);
315
+ background: var(--accent-weak);
316
+ box-shadow: var(--glow-cyan);
293
317
  }`;
294
318
  const settingsScript = () => `(function () {
295
319
  var form = document.getElementById('settingsForm');
@@ -29,6 +29,7 @@ import { getGraphVersion } from './graph-version.js';
29
29
  import { readAuthStatus, readEditableSettings, updateEditableSettings } from './settings-store.js';
30
30
  import { connectVaultRemote, ensureDeployKey, readVersioningStatus } from './versioning.js';
31
31
  import { createSettingsPageHtml } from '../frontend/settings-page.js';
32
+ import { createConnectPageHtml } from '../frontend/connect-page.js';
32
33
  import { contentTypes, createJsonResponse, isReadMethod, parsePositiveInteger } from './http.js';
33
34
  import { parseMultipartForm } from './multipart.js';
34
35
  const readRuntimeDefaults = async (url) => {
@@ -90,6 +91,29 @@ const requestIsSecure = (request) => {
90
91
  const proto = firstHeaderValue(request.headers['x-forwarded-proto']);
91
92
  return typeof proto === 'string' && proto.split(',')[0]?.trim() === 'https';
92
93
  };
94
+ // Resolve the "Connect your agent" page data at request time. Remote mode is
95
+ // picked only when the server is exposed (auth-gated) AND an MCP token is
96
+ // configured: only then can we hand out a working bearer + public endpoint.
97
+ // Otherwise the page falls back to the local stdio config and never shows a
98
+ // token. The host prefers the proxy-forwarded value since exposed mode sits
99
+ // behind a TLS-terminating reverse proxy.
100
+ const resolveConnectHost = (request, url) => {
101
+ const forwarded = firstHeaderValue(request.headers['x-forwarded-host']);
102
+ const forwardedHost = typeof forwarded === 'string' ? forwarded.split(',')[0]?.trim() : '';
103
+ if (forwardedHost) {
104
+ return forwardedHost;
105
+ }
106
+ const host = firstHeaderValue(request.headers.host);
107
+ return typeof host === 'string' && host.length > 0 ? host : url.host;
108
+ };
109
+ const resolveConnectPageData = (request, url, vaultPath, requireAuth) => {
110
+ const token = process.env.BRAINLINK_MCP_TOKEN?.trim() ?? '';
111
+ if (requireAuth && token.length > 0) {
112
+ const host = resolveConnectHost(request, url);
113
+ return { mode: 'remote', mcpUrl: `https://${host}/mcp`, token, vaultPath };
114
+ }
115
+ return { mode: 'local', mcpUrl: '', token: '', vaultPath };
116
+ };
93
117
  const isHtmlNavigation = (request, url) => {
94
118
  if (url.pathname.startsWith('/api/')) {
95
119
  return false;
@@ -566,6 +590,12 @@ export const route = async (request, url, vaultPath, options = {}) => {
566
590
  if (isReadMethod(request) && url.pathname === '/settings') {
567
591
  return createResponse(createSettingsPageHtml(), 200, contentTypes['.html']);
568
592
  }
593
+ // "Connect your agent": ready-to-paste MCP setup for wiring agents to this
594
+ // brainlink. Auth-gated with the rest of the exposed graph (runAuthGate ran
595
+ // above), so it may surface the owner's bearer token in remote mode.
596
+ if (isReadMethod(request) && url.pathname === '/connect') {
597
+ return createResponse(createConnectPageHtml(resolveConnectPageData(request, url, vaultPath, options.requireAuth === true)), 200, contentTypes['.html']);
598
+ }
569
599
  // Vault git versioning: generate an SSH deploy key on demand and connect a
570
600
  // GitHub repo (clone-and-aggregate, conflict-preserving — never overwrites).
571
601
  if (isReadMethod(request) && url.pathname === '/api/versioning-status') {
@@ -1,9 +1,12 @@
1
1
  // Self-contained branded OAuth authorization/consent page for the Brainlink MCP
2
- // server. Served as a plain string (inline <style>, no external assets, no
3
- // framework, no JavaScript) so it works identically for a global npm install.
4
- // The visual identity mirrors the graph client and auth pages (see
5
- // client-css.ts / login-page.ts): dark theme on #08131d, the same grid/radial
6
- // gradient, Crowquill Mono with a monospace fallback, and the #5aa8ff accent.
2
+ // server. Served as a plain string (inline <style>, no external assets beyond
3
+ // the shared Google Fonts @import, no framework, no JavaScript) so it works
4
+ // identically for a global npm install. The visual identity is the unified neon
5
+ // system shared with the SaaS app and the graph client (see neon-theme.ts /
6
+ // auth-styles.ts): #0a0e1a base, faint cyber grid, cyan #22d3ee + violet
7
+ // #8b5cf6, Space Grotesk headings / DM Sans body, glass card + gradient
8
+ // wordmark, so the SSO consent step reads as part of one product.
9
+ import { authSharedStyles } from '../application/frontend/auth-styles.js';
7
10
  // HTML-escape dynamic values. clientName arrives from an untrusted OAuth client
8
11
  // and hidden values carry OAuth params, so both must never allow HTML injection.
9
12
  const escapeHtml = (value) => value
@@ -12,85 +15,14 @@ const escapeHtml = (value) => value
12
15
  .replace(/>/g, '&gt;')
13
16
  .replace(/"/g, '&quot;')
14
17
  .replace(/'/g, '&#39;');
15
- const authorizeStyles = () => `:root {
16
- color-scheme: dark;
17
- --bg: #08131d;
18
- --panel: #0d1823;
19
- --panel-strong: #112130;
20
- --line: rgba(143, 172, 204, 0.18);
21
- --text: #edf4ff;
22
- --muted: #97a9bd;
23
- --accent: #5aa8ff;
24
- --accent-weak: rgba(90, 168, 255, 0.18);
25
- --danger: #ff6b6b;
26
- }
27
-
28
- * {
29
- box-sizing: border-box;
30
- }
31
-
32
- html,
33
- body {
34
- width: 100%;
35
- min-height: 100%;
36
- margin: 0;
37
- }
38
-
39
- body {
40
- display: flex;
41
- align-items: center;
42
- justify-content: center;
43
- min-height: 100vh;
44
- min-height: 100dvh;
45
- padding: 24px;
46
- color: var(--text);
47
- font-family: "Crowquill Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
48
- background:
49
- linear-gradient(rgba(164, 197, 230, 0.05) 1px, transparent 1px),
50
- linear-gradient(90deg, rgba(164, 197, 230, 0.05) 1px, transparent 1px),
51
- radial-gradient(circle at top, rgba(43, 93, 143, 0.22), transparent 44%),
52
- #08131d;
53
- background-size: 28px 28px, 28px 28px, auto;
54
- }
55
-
56
- button,
57
- input {
58
- font: inherit;
59
- }
60
-
61
- .auth-card {
62
- width: 100%;
63
- max-width: 400px;
64
- padding: 32px 28px;
65
- border: 1px solid var(--line);
66
- border-radius: 14px;
67
- background: rgba(13, 24, 35, 0.92);
68
- box-shadow: 0 24px 80px rgba(1, 6, 13, 0.48);
69
- backdrop-filter: blur(8px);
70
- }
71
-
72
- .auth-brand {
73
- display: grid;
74
- gap: 4px;
75
- margin-bottom: 20px;
76
- }
77
-
78
- .auth-brand strong {
79
- font-size: 26px;
80
- letter-spacing: 0.01em;
81
- }
82
-
83
- .auth-brand span {
84
- color: var(--muted);
85
- font-size: 13px;
86
- }
18
+ const authorizeStyles = () => `${authSharedStyles(400)}
87
19
 
88
20
  .auth-consent {
89
21
  margin: 0 0 20px;
90
22
  padding: 12px 14px;
91
23
  border: 1px solid var(--line);
92
- border-radius: 10px;
93
- background: rgba(12, 24, 36, 0.6);
24
+ border-radius: 12px;
25
+ background: rgba(19, 26, 42, 0.55);
94
26
  color: var(--text);
95
27
  font-size: 13px;
96
28
  line-height: 1.5;
@@ -101,58 +33,7 @@ input {
101
33
  color: var(--accent);
102
34
  }
103
35
 
104
- .auth-form {
105
- display: grid;
106
- gap: 16px;
107
- }
108
-
109
- .auth-field {
110
- display: grid;
111
- gap: 7px;
112
- }
113
-
114
- .auth-field label {
115
- color: var(--muted);
116
- font-size: 12px;
117
- letter-spacing: 0.02em;
118
- }
119
-
120
- .auth-field input {
121
- width: 100%;
122
- height: 42px;
123
- padding: 0 14px;
124
- border: 1px solid var(--line);
125
- border-radius: 8px;
126
- outline: none;
127
- background: rgba(12, 24, 36, 0.94);
128
- color: var(--text);
129
- }
130
-
131
- .auth-field input:focus {
132
- border-color: var(--accent);
133
- }
134
-
135
- .auth-submit {
136
- height: 44px;
137
- margin-top: 4px;
138
- border: 1px solid var(--accent);
139
- border-radius: 8px;
140
- background: var(--accent-weak);
141
- color: var(--text);
142
- cursor: pointer;
143
- transition: background 120ms ease, opacity 120ms ease;
144
- }
145
-
146
- .auth-submit:hover,
147
- .auth-submit:focus {
148
- background: rgba(90, 168, 255, 0.3);
149
- }
150
-
151
36
  .auth-message {
152
- margin: 0;
153
- font-size: 12px;
154
- line-height: 1.4;
155
- overflow-wrap: anywhere;
156
37
  color: var(--danger);
157
38
  }`;
158
39
  const consentLine = (clientName) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andespindola/brainlink",
3
- "version": "1.6.15",
3
+ "version": "1.8.0",
4
4
  "description": "Local-first knowledge memory for agents with Markdown, backlinks, indexing and context retrieval.",
5
5
  "type": "module",
6
6
  "license": "MIT",