@andespindola/brainlink 0.1.0-beta.9 → 0.1.0-beta.90
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.
- package/AGENTS.md +8 -5
- package/CHANGELOG.md +26 -2
- package/CONTRIBUTING.md +2 -2
- package/COPYRIGHT.md +5 -0
- package/README.md +146 -17
- package/SECURITY.md +1 -1
- package/dist/application/analyze-vault.js +7 -7
- package/dist/application/build-context.js +56 -1
- package/dist/application/dedupe-notes.js +226 -0
- package/dist/application/frontend/client-css.js +154 -102
- package/dist/application/frontend/client-html.js +49 -40
- package/dist/application/frontend/client-js.js +3130 -166
- package/dist/application/frontend/client-worker-js.js +66 -0
- package/dist/application/get-graph-layout.js +18 -6
- package/dist/application/get-graph-node.js +12 -0
- package/dist/application/get-graph-summary.js +12 -0
- package/dist/application/get-graph.js +3 -3
- package/dist/application/import-legacy-sqlite.js +296 -0
- package/dist/application/index-vault.js +252 -19
- package/dist/application/list-agents.js +3 -3
- package/dist/application/list-links.js +5 -5
- package/dist/application/offline-pack-backup.js +44 -0
- package/dist/application/search-graph-node-ids.js +12 -0
- package/dist/application/search-knowledge.js +25 -10
- package/dist/application/server/routes.js +102 -1
- package/dist/application/start-server.js +75 -4
- package/dist/application/watch-vault.js +23 -2
- package/dist/benchmarks/large-vault.js +1 -1
- package/dist/cli/commands/agent-commands.js +20 -3
- package/dist/cli/commands/write-commands.js +818 -8
- package/dist/domain/context.js +53 -11
- package/dist/domain/embeddings.js +2 -1
- package/dist/domain/graph-layout.js +67 -16
- package/dist/domain/middle-out.js +18 -0
- package/dist/infrastructure/config.js +38 -0
- package/dist/infrastructure/file-index.js +358 -0
- package/dist/infrastructure/file-system-vault.js +15 -0
- package/dist/infrastructure/index-state.js +56 -0
- package/dist/infrastructure/private-pack-codec.js +134 -0
- package/dist/infrastructure/search-packs.js +452 -0
- package/dist/infrastructure/session-state.js +57 -2
- package/dist/mcp/server.js +11 -1
- package/dist/mcp/tools.js +215 -3
- package/docs/AGENT_USAGE.md +103 -16
- package/docs/ARCHITECTURE.md +25 -26
- package/docs/QUICKSTART.md +9 -1
- package/package.json +6 -4
- package/dist/infrastructure/sqlite/document-writer.js +0 -51
- package/dist/infrastructure/sqlite/graph-reader.js +0 -120
- package/dist/infrastructure/sqlite/schema.js +0 -111
- package/dist/infrastructure/sqlite/search-reader.js +0 -156
- package/dist/infrastructure/sqlite/types.js +0 -1
- 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
|
-
|
|
23
|
-
response.
|
|
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
|
-
|
|
29
|
-
response.
|
|
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 {
|
|
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
|
-
|
|
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
|
-
'
|
|
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
|
};
|