@andespindola/brainlink 0.1.0-beta.9 → 0.1.0-beta.91

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 (53) hide show
  1. package/AGENTS.md +8 -5
  2. package/CHANGELOG.md +26 -2
  3. package/CONTRIBUTING.md +2 -2
  4. package/COPYRIGHT.md +5 -0
  5. package/README.md +146 -17
  6. package/SECURITY.md +1 -1
  7. package/dist/application/analyze-vault.js +7 -7
  8. package/dist/application/build-context.js +56 -1
  9. package/dist/application/dedupe-notes.js +226 -0
  10. package/dist/application/frontend/client-css.js +154 -102
  11. package/dist/application/frontend/client-html.js +49 -40
  12. package/dist/application/frontend/client-js.js +3118 -167
  13. package/dist/application/frontend/client-worker-js.js +66 -0
  14. package/dist/application/get-graph-layout.js +18 -6
  15. package/dist/application/get-graph-node.js +12 -0
  16. package/dist/application/get-graph-summary.js +12 -0
  17. package/dist/application/get-graph.js +3 -3
  18. package/dist/application/import-legacy-sqlite.js +296 -0
  19. package/dist/application/index-vault.js +252 -19
  20. package/dist/application/list-agents.js +3 -3
  21. package/dist/application/list-links.js +5 -5
  22. package/dist/application/offline-pack-backup.js +44 -0
  23. package/dist/application/search-graph-node-ids.js +12 -0
  24. package/dist/application/search-knowledge.js +25 -10
  25. package/dist/application/server/routes.js +102 -1
  26. package/dist/application/start-server.js +75 -4
  27. package/dist/application/watch-vault.js +23 -2
  28. package/dist/benchmarks/large-vault.js +1 -1
  29. package/dist/cli/commands/agent-commands.js +20 -3
  30. package/dist/cli/commands/write-commands.js +818 -8
  31. package/dist/domain/context.js +53 -11
  32. package/dist/domain/embeddings.js +2 -1
  33. package/dist/domain/graph-layout.js +67 -16
  34. package/dist/domain/middle-out.js +18 -0
  35. package/dist/infrastructure/config.js +38 -0
  36. package/dist/infrastructure/file-index.js +358 -0
  37. package/dist/infrastructure/file-system-vault.js +15 -0
  38. package/dist/infrastructure/index-state.js +56 -0
  39. package/dist/infrastructure/private-pack-codec.js +134 -0
  40. package/dist/infrastructure/search-packs.js +452 -0
  41. package/dist/infrastructure/session-state.js +57 -2
  42. package/dist/mcp/server.js +11 -1
  43. package/dist/mcp/tools.js +215 -3
  44. package/docs/AGENT_USAGE.md +103 -16
  45. package/docs/ARCHITECTURE.md +25 -26
  46. package/docs/QUICKSTART.md +9 -1
  47. package/package.json +6 -4
  48. package/dist/infrastructure/sqlite/document-writer.js +0 -51
  49. package/dist/infrastructure/sqlite/graph-reader.js +0 -120
  50. package/dist/infrastructure/sqlite/schema.js +0 -111
  51. package/dist/infrastructure/sqlite/search-reader.js +0 -156
  52. package/dist/infrastructure/sqlite/types.js +0 -1
  53. package/dist/infrastructure/sqlite-index.js +0 -25
@@ -1,9 +1,78 @@
1
1
  import { createServer } from 'node:http';
2
+ import { brotliCompressSync, constants, gzipSync } from 'node:zlib';
2
3
  import { indexVault } from './index-vault.js';
3
4
  import { startVaultWatcher } from './watch-vault.js';
4
5
  import { assertLoopbackHost } from './server/host-security.js';
5
6
  import { contentTypes, createJsonResponse, isHttpError } from './server/http.js';
6
7
  import { route } from './server/routes.js';
8
+ const compressionThresholdBytes = 1024;
9
+ const normalizeEncodingToken = (value) => value.trim().toLowerCase();
10
+ const supportsEncoding = (acceptEncoding, target) => {
11
+ if (!acceptEncoding) {
12
+ return false;
13
+ }
14
+ return acceptEncoding
15
+ .split(',')
16
+ .map((entry) => entry.split(';')[0] ?? '')
17
+ .map(normalizeEncodingToken)
18
+ .includes(target);
19
+ };
20
+ const isCompressibleContentType = (contentType) => {
21
+ const normalized = contentType?.toLowerCase() ?? '';
22
+ return (normalized.includes('application/json') ||
23
+ normalized.includes('text/javascript') ||
24
+ normalized.includes('text/css') ||
25
+ normalized.includes('text/html') ||
26
+ normalized.startsWith('text/'));
27
+ };
28
+ const maybeCompressResponse = (requestHeaders, statusCode, headers, body) => {
29
+ if (statusCode === 204 || statusCode === 304) {
30
+ return { headers, body: '' };
31
+ }
32
+ if (!isCompressibleContentType(headers['content-type'])) {
33
+ return { headers, body };
34
+ }
35
+ const bodyBuffer = Buffer.from(body, 'utf8');
36
+ if (bodyBuffer.byteLength < compressionThresholdBytes) {
37
+ return { headers, body };
38
+ }
39
+ if (headers['content-encoding']) {
40
+ return { headers, body };
41
+ }
42
+ const acceptEncodingHeader = Array.isArray(requestHeaders['accept-encoding'])
43
+ ? requestHeaders['accept-encoding'].join(',')
44
+ : requestHeaders['accept-encoding'];
45
+ const vary = headers.vary ? `${headers.vary}, Accept-Encoding` : 'Accept-Encoding';
46
+ const withVary = {
47
+ ...headers,
48
+ vary
49
+ };
50
+ if (supportsEncoding(acceptEncodingHeader, 'br')) {
51
+ return {
52
+ headers: {
53
+ ...withVary,
54
+ 'content-encoding': 'br'
55
+ },
56
+ body: brotliCompressSync(bodyBuffer, {
57
+ params: {
58
+ [constants.BROTLI_PARAM_QUALITY]: 5
59
+ }
60
+ })
61
+ };
62
+ }
63
+ if (supportsEncoding(acceptEncodingHeader, 'gzip')) {
64
+ return {
65
+ headers: {
66
+ ...withVary,
67
+ 'content-encoding': 'gzip'
68
+ },
69
+ body: gzipSync(bodyBuffer, {
70
+ level: 6
71
+ })
72
+ };
73
+ }
74
+ return { headers: withVary, body };
75
+ };
7
76
  export const startServer = async (input) => {
8
77
  assertLoopbackHost(input.host);
9
78
  if (input.shouldIndex) {
@@ -19,14 +88,16 @@ export const startServer = async (input) => {
19
88
  const url = new URL(request.url ?? '/', `http://${request.headers.host ?? input.host}`);
20
89
  route(request, url, input.vaultPath)
21
90
  .then((result) => {
22
- response.writeHead(result.statusCode, result.headers);
23
- response.end(result.body);
91
+ const encoded = maybeCompressResponse(request.headers, result.statusCode, result.headers, result.body);
92
+ response.writeHead(result.statusCode, encoded.headers);
93
+ response.end(encoded.body);
24
94
  })
25
95
  .catch((error) => {
26
96
  const message = error instanceof Error ? error.message : String(error);
27
97
  const statusCode = isHttpError(error) ? error.statusCode : 500;
28
- response.writeHead(statusCode, { 'content-type': contentTypes['.json'] });
29
- response.end(createJsonResponse({ error: message }));
98
+ const fallback = maybeCompressResponse(request.headers, statusCode, { 'content-type': contentTypes['.json'] }, createJsonResponse({ error: message }));
99
+ response.writeHead(statusCode, fallback.headers);
100
+ response.end(fallback.body);
30
101
  });
31
102
  });
32
103
  await new Promise((resolve, reject) => {
@@ -1,5 +1,5 @@
1
1
  import { watch } from 'node:fs';
2
- import { indexVault } from './index-vault.js';
2
+ import { indexVaultWithOptions } from './index-vault.js';
3
3
  import { isBucketVaultPath, resolveVaultPath } from '../infrastructure/file-system-vault.js';
4
4
  const shouldIgnore = (filename) => {
5
5
  if (!filename) {
@@ -14,6 +14,27 @@ export const startVaultWatcher = (input) => {
14
14
  const absoluteVaultPath = resolveVaultPath(input.vaultPath);
15
15
  const debounceMs = input.debounceMs ?? 350;
16
16
  let timeout = null;
17
+ let running = false;
18
+ let pending = false;
19
+ const runIndex = () => {
20
+ if (running) {
21
+ pending = true;
22
+ return;
23
+ }
24
+ running = true;
25
+ indexVaultWithOptions(absoluteVaultPath, {
26
+ onProgress: input.onProgress
27
+ })
28
+ .then(input.onIndex)
29
+ .catch(input.onError)
30
+ .finally(() => {
31
+ running = false;
32
+ if (pending) {
33
+ pending = false;
34
+ runIndex();
35
+ }
36
+ });
37
+ };
17
38
  const schedule = (filename) => {
18
39
  if (shouldIgnore(filename)) {
19
40
  return;
@@ -22,7 +43,7 @@ export const startVaultWatcher = (input) => {
22
43
  clearTimeout(timeout);
23
44
  }
24
45
  timeout = setTimeout(() => {
25
- indexVault(absoluteVaultPath).then(input.onIndex).catch(input.onError);
46
+ runIndex();
26
47
  }, debounceMs);
27
48
  };
28
49
  const watcher = watch(absoluteVaultPath, { recursive: true }, (_eventType, filename) => {
@@ -21,7 +21,7 @@ const readOptions = (args) => ({
21
21
  });
22
22
  const topics = [
23
23
  'authentication jwt token refresh policy',
24
- 'sqlite graph backlinks markdown vault indexing',
24
+ 'graph backlinks markdown vault indexing',
25
25
  'frontend canvas layout graph interaction',
26
26
  'agent memory context retrieval summarization',
27
27
  'security local server vault path allowlist',
@@ -4,7 +4,7 @@ import { homedir } from 'node:os';
4
4
  import { dirname, join, resolve } from 'node:path';
5
5
  import { promisify } from 'node:util';
6
6
  import { loadBrainlinkConfig } from '../../infrastructure/config.js';
7
- import { getBootstrapPolicy, getBootstrapSessionStatus, getSessionStatePath, setBootstrapPolicy } from '../../infrastructure/session-state.js';
7
+ import { getBootstrapPolicy, getBootstrapSessionStatus, getContextSessionStatus, getSessionStatePath, setBootstrapPolicy } from '../../infrastructure/session-state.js';
8
8
  import { print } from '../runtime.js';
9
9
  const getCodexConfigPath = () => join(homedir(), '.codex', 'config.toml');
10
10
  const getMarketplacePath = () => join(homedir(), '.agents', 'plugins', 'marketplace.json');
@@ -141,6 +141,12 @@ const parseAllowedVaults = (value) => {
141
141
  export const installAgentIntegration = async (input) => {
142
142
  const codexConfigPath = getCodexConfigPath();
143
143
  const allowedVaults = parseAllowedVaults(input.allowedVaults);
144
+ const bootstrapPolicy = await setBootstrapPolicy({
145
+ enforceBootstrap: true,
146
+ enforceContextFirst: true,
147
+ autoBootstrapOnRead: true,
148
+ autoBootstrapOnStartup: true
149
+ });
144
150
  await upsertCodexMcpConfig(codexConfigPath, {
145
151
  allowedVaults,
146
152
  brainlinkHome: input.brainlinkHome
@@ -195,6 +201,7 @@ export const installAgentIntegration = async (input) => {
195
201
  codexConfigPath,
196
202
  mcpServer: 'brainlink',
197
203
  command: 'brainlink-mcp',
204
+ bootstrapPolicy,
198
205
  ...(input.mcpOnly !== true ? { pluginSourcePath, pluginSymlinkPath, marketplacePath } : {}),
199
206
  ...(selfTestResult ? { selfTest: selfTestResult } : {}),
200
207
  ...(warnings.length > 0 ? { warnings } : {})
@@ -243,6 +250,7 @@ const applyPolicyPreset = (preset) => {
243
250
  if (preset === 'fully-auto') {
244
251
  return {
245
252
  enforceBootstrap: true,
253
+ enforceContextFirst: true,
246
254
  autoBootstrapOnRead: true,
247
255
  autoBootstrapOnStartup: true
248
256
  };
@@ -250,6 +258,7 @@ const applyPolicyPreset = (preset) => {
250
258
  if (preset === 'strict') {
251
259
  return {
252
260
  enforceBootstrap: true,
261
+ enforceContextFirst: true,
253
262
  autoBootstrapOnRead: false,
254
263
  autoBootstrapOnStartup: false
255
264
  };
@@ -307,6 +316,7 @@ export const registerAgentCommands = (program) => {
307
316
  .command('policy')
308
317
  .option('--preset <preset>', 'policy preset: fully-auto or strict')
309
318
  .option('--enforce-bootstrap <true|false>', 'override enforceBootstrap')
319
+ .option('--enforce-context-first <true|false>', 'override enforceContextFirst')
310
320
  .option('--auto-bootstrap-on-read <true|false>', 'override autoBootstrapOnRead')
311
321
  .option('--auto-bootstrap-on-startup <true|false>', 'override autoBootstrapOnStartup')
312
322
  .option('--stale-after-minutes <minutes>', 'override staleAfterMinutes with positive integer')
@@ -315,11 +325,13 @@ export const registerAgentCommands = (program) => {
315
325
  .action(async (options) => {
316
326
  const presetPatch = applyPolicyPreset(options.preset);
317
327
  const enforceBootstrap = parseBooleanOption(options.enforceBootstrap, '--enforce-bootstrap');
328
+ const enforceContextFirst = parseBooleanOption(options.enforceContextFirst, '--enforce-context-first');
318
329
  const autoBootstrapOnRead = parseBooleanOption(options.autoBootstrapOnRead, '--auto-bootstrap-on-read');
319
330
  const autoBootstrapOnStartup = parseBooleanOption(options.autoBootstrapOnStartup, '--auto-bootstrap-on-startup');
320
331
  const staleAfterMinutes = parsePositiveIntegerOption(options.staleAfterMinutes, '--stale-after-minutes');
321
332
  const overridePatch = {
322
333
  ...(enforceBootstrap !== undefined ? { enforceBootstrap } : {}),
334
+ ...(enforceContextFirst !== undefined ? { enforceContextFirst } : {}),
323
335
  ...(autoBootstrapOnRead !== undefined ? { autoBootstrapOnRead } : {}),
324
336
  ...(autoBootstrapOnStartup !== undefined ? { autoBootstrapOnStartup } : {}),
325
337
  ...(staleAfterMinutes !== undefined ? { staleAfterMinutes } : {})
@@ -335,6 +347,7 @@ export const registerAgentCommands = (program) => {
335
347
  }, () => [
336
348
  ...(options.preset ? [`presetApplied=${options.preset}`] : []),
337
349
  `enforceBootstrap=${policy.enforceBootstrap}`,
350
+ `enforceContextFirst=${policy.enforceContextFirst}`,
338
351
  `autoBootstrapOnRead=${policy.autoBootstrapOnRead}`,
339
352
  `autoBootstrapOnStartup=${policy.autoBootstrapOnStartup}`,
340
353
  `staleAfterMinutes=${policy.staleAfterMinutes}`
@@ -373,6 +386,7 @@ export const registerAgentCommands = (program) => {
373
386
  const config = await loadBrainlinkConfig();
374
387
  const policy = await getBootstrapPolicy();
375
388
  const bootstrapStatus = await getBootstrapSessionStatus(config.vault, options.agent ?? config.defaultAgent);
389
+ const contextStatus = await getContextSessionStatus(config.vault, options.agent ?? config.defaultAgent);
376
390
  const sessionStatePath = getSessionStatePath();
377
391
  print(options.json, {
378
392
  configured: hasMcpSection && hasCommand,
@@ -387,7 +401,8 @@ export const registerAgentCommands = (program) => {
387
401
  vault: config.vault,
388
402
  agent: options.agent ?? config.defaultAgent ?? '*',
389
403
  bootstrapPolicy: policy,
390
- bootstrapStatus
404
+ bootstrapStatus,
405
+ contextStatus
391
406
  }, () => [
392
407
  `codexConfigPath=${codexConfigPath}`,
393
408
  `configured=${hasMcpSection && hasCommand}`,
@@ -396,7 +411,9 @@ export const registerAgentCommands = (program) => {
396
411
  `vault=${config.vault}`,
397
412
  `agent=${options.agent ?? config.defaultAgent ?? '*'}`,
398
413
  `bootstrapReady=${bootstrapStatus.ready}`,
399
- `bootstrapStale=${bootstrapStatus.stale}`
414
+ `bootstrapStale=${bootstrapStatus.stale}`,
415
+ `contextReady=${contextStatus.ready}`,
416
+ `contextStale=${contextStatus.stale}`
400
417
  ].join('\n'));
401
418
  });
402
419
  };