@andespindola/brainlink 0.1.0-beta.0 → 0.1.0-beta.1
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/README.md +15 -11
- package/dist/cli/commands/write-commands.js +18 -3
- package/dist/infrastructure/config.js +2 -0
- package/dist/mcp/server.js +6 -1
- package/dist/mcp/tools.js +48 -4
- package/docs/AGENT_USAGE.md +9 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -191,7 +191,9 @@ blink add "Testing Policy" \
|
|
|
191
191
|
--content "Run npm run check before final delivery. Related: [[Release Checklist]]. #testing #process"
|
|
192
192
|
```
|
|
193
193
|
|
|
194
|
-
Brainlink does not infer durable graph relationships from generated context. A context result is only a read package for the model. To create a real link in the knowledge graph, the agent must write Markdown that contains an explicit `[[Note Title]]` wiki link
|
|
194
|
+
Brainlink does not infer durable graph relationships from generated context. A context result is only a read package for the model. To create a real link in the knowledge graph, the agent must write Markdown that contains an explicit `[[Note Title]]` wiki link.
|
|
195
|
+
|
|
196
|
+
Writes with `blink add` reindex the vault automatically by default. This can be disabled with `--no-auto-index` and controlled globally with `autoIndexOnWrite` in `brainlink.config.json`.
|
|
195
197
|
|
|
196
198
|
When adding memory, follow this contract:
|
|
197
199
|
|
|
@@ -200,11 +202,7 @@ When adding memory, follow this contract:
|
|
|
200
202
|
- Add retrieval tags such as `#architecture`, `#decision`, `#runbook` or `#preference`.
|
|
201
203
|
- Do not leave isolated notes unless they are intentionally root concepts.
|
|
202
204
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
```bash
|
|
206
|
-
blink index
|
|
207
|
-
```
|
|
205
|
+
If you disable auto-index, run `blink index` after batched writes.
|
|
208
206
|
|
|
209
207
|
### 6. Validate Memory Health
|
|
210
208
|
|
|
@@ -223,7 +221,7 @@ Use this loop during real work:
|
|
|
223
221
|
3. Use returned sources as project memory.
|
|
224
222
|
4. Perform the task.
|
|
225
223
|
5. Save only durable learnings with `blink add`, including `[[wiki links]]` to related notes.
|
|
226
|
-
6. Run `blink index
|
|
224
|
+
6. Run `blink index` only when auto-index was disabled during a batch.
|
|
227
225
|
7. Validate with `blink validate`, `blink broken-links` and `blink orphans` when graph links matter.
|
|
228
226
|
|
|
229
227
|
Do not store secrets, credentials, private keys, access tokens or transient chat noise.
|
|
@@ -241,8 +239,6 @@ blink add "Auth Decision" \
|
|
|
241
239
|
--vault ./vault \
|
|
242
240
|
--content "We chose JWT for API clients. [[Architecture]] #auth #jwt"
|
|
243
241
|
|
|
244
|
-
blink index --vault ./vault
|
|
245
|
-
|
|
246
242
|
blink search "jwt auth" --vault ./vault
|
|
247
243
|
|
|
248
244
|
blink context "how does auth work?" --vault ./vault
|
|
@@ -389,13 +385,14 @@ Available tools:
|
|
|
389
385
|
- `brainlink_context`: read indexed context for a task or question.
|
|
390
386
|
- `brainlink_search`: search indexed notes.
|
|
391
387
|
- `brainlink_add_note`: write durable Markdown memory and reindex.
|
|
388
|
+
- `brainlink_add_file`: ingest a local file as a note and reindex.
|
|
392
389
|
- `brainlink_index`: rebuild the vault index.
|
|
393
390
|
- `brainlink_validate`: validate broken links and orphan notes.
|
|
394
391
|
- `brainlink_graph`: read indexed graph nodes and weighted links.
|
|
395
392
|
- `brainlink_broken_links`: list unresolved wiki links.
|
|
396
393
|
- `brainlink_orphans`: list disconnected notes.
|
|
397
394
|
|
|
398
|
-
The same linking rule applies through MCP: `brainlink_context` is read-only, and real graph links require Markdown notes with explicit `[[wiki links]]`
|
|
395
|
+
The same linking rule applies through MCP: `brainlink_context` is read-only, and real graph links require Markdown notes with explicit `[[wiki links]]`. `brainlink_add_note` and `brainlink_add_file` reindex by default and include the index result when enabled.
|
|
399
396
|
|
|
400
397
|
Agents can raise the importance of a relationship by putting priority markers on the same line as a wiki link:
|
|
401
398
|
|
|
@@ -478,8 +475,12 @@ Initializes vault metadata. Without an argument, Brainlink initializes the defau
|
|
|
478
475
|
```bash
|
|
479
476
|
blink add "Note Title" --agent coding-agent --content "Markdown content"
|
|
480
477
|
blink add "Note Title" --vault ./vault --agent coding-agent --content "Markdown content"
|
|
478
|
+
blink add "Note Title" --vault ./vault --content-file ./notes.md
|
|
479
|
+
blink add "Note Title" --vault ./vault --content-file ./notes.md --no-auto-index
|
|
481
480
|
```
|
|
482
481
|
|
|
482
|
+
`--content` and `--content-file` are mutually exclusive. Add `--no-auto-index` when you want to defer reindexing.
|
|
483
|
+
|
|
483
484
|
Creates a Markdown note under `agents/<agent-id>/`. Common secret patterns are blocked by default; use `--allow-sensitive` only for an intentionally protected vault.
|
|
484
485
|
|
|
485
486
|
### `index`
|
|
@@ -638,6 +639,7 @@ Brainlink reads `brainlink.config.json` or `.brainlink.json` from the current wo
|
|
|
638
639
|
"port": 4321,
|
|
639
640
|
"allowedVaults": [".brainlink-vault"],
|
|
640
641
|
"defaultAgent": "shared",
|
|
642
|
+
"autoIndexOnWrite": true,
|
|
641
643
|
"defaultSearchLimit": 10,
|
|
642
644
|
"defaultContextTokens": 2000,
|
|
643
645
|
"embeddingProvider": "local",
|
|
@@ -646,7 +648,9 @@ Brainlink reads `brainlink.config.json` or `.brainlink.json` from the current wo
|
|
|
646
648
|
}
|
|
647
649
|
|
|
648
650
|
`defaultAgent` is optional. When set, CLI and MCP calls that omit `--agent`/`agent` use this value automatically. If not set, behavior remains as before.
|
|
649
|
-
|
|
651
|
+
|
|
652
|
+
`autoIndexOnWrite` is optional and defaults to `true`. Set it to `false` to defer indexing after writes.
|
|
653
|
+
```
|
|
650
654
|
|
|
651
655
|
Use `"embeddingProvider": "none"` when you want FTS-only indexing.
|
|
652
656
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
1
2
|
import { addNote } from '../../application/add-note.js';
|
|
2
3
|
import { indexVault } from '../../application/index-vault.js';
|
|
3
4
|
import { startServer } from '../../application/start-server.js';
|
|
@@ -6,6 +7,15 @@ import { doctorVault } from '../../application/analyze-vault.js';
|
|
|
6
7
|
import { loadBrainlinkConfig } from '../../infrastructure/config.js';
|
|
7
8
|
import { assertVaultAllowed, ensureVault } from '../../infrastructure/file-system-vault.js';
|
|
8
9
|
import { parsePositiveInteger, print, resolveOptions } from '../runtime.js';
|
|
10
|
+
const resolveAddContent = (options) => {
|
|
11
|
+
if (options.content != null && options.content.trim().length > 0) {
|
|
12
|
+
return options.content;
|
|
13
|
+
}
|
|
14
|
+
if (options.contentFile == null || options.contentFile.trim().length === 0) {
|
|
15
|
+
throw new Error('Use --content or --content-file to provide note content.');
|
|
16
|
+
}
|
|
17
|
+
return readFileSync(options.contentFile, 'utf8');
|
|
18
|
+
};
|
|
9
19
|
export const registerWriteCommands = (program) => {
|
|
10
20
|
program
|
|
11
21
|
.command('init')
|
|
@@ -20,18 +30,23 @@ export const registerWriteCommands = (program) => {
|
|
|
20
30
|
program
|
|
21
31
|
.command('add')
|
|
22
32
|
.argument('<title>', 'note title')
|
|
23
|
-
.
|
|
33
|
+
.option('-c, --content <content>', 'markdown content')
|
|
34
|
+
.option('-f, --content-file <contentFile>', 'read markdown content from a file')
|
|
24
35
|
.option('-v, --vault <vault>', 'vault directory')
|
|
25
36
|
.option('-a, --agent <agent>', 'agent memory namespace')
|
|
26
37
|
.option('--allow-sensitive', 'allow writing content that looks like a secret')
|
|
38
|
+
.option('--no-auto-index', 'skip reindexing after add')
|
|
27
39
|
.option('--json', 'print machine-readable JSON')
|
|
28
40
|
.description('add a markdown note to the vault')
|
|
29
41
|
.action(async (title, options) => {
|
|
30
42
|
const resolved = await resolveOptions(options);
|
|
31
|
-
const
|
|
43
|
+
const content = resolveAddContent(options);
|
|
44
|
+
const notePath = await addNote(resolved.vault, title, content, resolved.agent, {
|
|
32
45
|
allowSensitive: Boolean(options.allowSensitive)
|
|
33
46
|
});
|
|
34
|
-
|
|
47
|
+
const shouldAutoIndex = options.autoIndex !== false && resolved.config.autoIndexOnWrite;
|
|
48
|
+
const index = shouldAutoIndex ? await indexVault(resolved.vault) : undefined;
|
|
49
|
+
print(options.json, { title, agent: resolved.agent ?? 'shared', path: notePath, ...(index ? { index } : {}) }, () => `Created note at ${notePath}`);
|
|
35
50
|
});
|
|
36
51
|
program
|
|
37
52
|
.command('index')
|
|
@@ -8,6 +8,7 @@ export const defaultBrainlinkConfig = {
|
|
|
8
8
|
port: 4321,
|
|
9
9
|
allowedVaults: [],
|
|
10
10
|
defaultAgent: undefined,
|
|
11
|
+
autoIndexOnWrite: true,
|
|
11
12
|
defaultSearchLimit: 10,
|
|
12
13
|
defaultContextTokens: 2000,
|
|
13
14
|
embeddingProvider: 'local',
|
|
@@ -45,6 +46,7 @@ const sanitizeConfig = (value) => ({
|
|
|
45
46
|
defaultAgent: typeof value.defaultAgent === 'string' && value.defaultAgent.trim().length > 0
|
|
46
47
|
? sanitizeAgentId(value.defaultAgent)
|
|
47
48
|
: defaultBrainlinkConfig.defaultAgent,
|
|
49
|
+
autoIndexOnWrite: typeof value.autoIndexOnWrite === 'boolean' ? value.autoIndexOnWrite : defaultBrainlinkConfig.autoIndexOnWrite,
|
|
48
50
|
defaultSearchLimit: typeof value.defaultSearchLimit === 'number' && value.defaultSearchLimit > 0
|
|
49
51
|
? value.defaultSearchLimit
|
|
50
52
|
: defaultBrainlinkConfig.defaultSearchLimit,
|
package/dist/mcp/server.js
CHANGED
|
@@ -2,7 +2,7 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
|
2
2
|
import { readFileSync } from 'node:fs';
|
|
3
3
|
import { dirname, join } from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
|
-
import { addNoteInputSchema, addNoteTool, brokenLinksInputSchema, brokenLinksTool, contextInputSchema, contextTool, graphInputSchema, graphTool, indexInputSchema, indexTool, orphansInputSchema, orphansTool, searchInputSchema, searchTool, statsInputSchema, statsTool, syncInputSchema, syncTool, validateInputSchema, validateTool } from './tools.js';
|
|
5
|
+
import { addNoteInputSchema, addFileInputSchema, addFileTool, addNoteTool, brokenLinksInputSchema, brokenLinksTool, contextInputSchema, contextTool, graphInputSchema, graphTool, indexInputSchema, indexTool, orphansInputSchema, orphansTool, searchInputSchema, searchTool, statsInputSchema, statsTool, syncInputSchema, syncTool, validateInputSchema, validateTool } from './tools.js';
|
|
6
6
|
const readPackageVersion = () => {
|
|
7
7
|
const packagePath = join(dirname(fileURLToPath(import.meta.url)), '../../package.json');
|
|
8
8
|
const metadata = JSON.parse(readFileSync(packagePath, 'utf8'));
|
|
@@ -30,6 +30,11 @@ export const createBrainlinkMcpServer = () => {
|
|
|
30
30
|
description: 'Write durable Markdown memory, then reindex the vault. Include explicit [[wiki links]] for connected graph memory. Add priority markers near links, such as priority: high, #important or #critical, when a relationship should be weighted higher.',
|
|
31
31
|
inputSchema: addNoteInputSchema
|
|
32
32
|
}, addNoteTool);
|
|
33
|
+
server.registerTool('brainlink_add_file', {
|
|
34
|
+
title: 'Ingest Markdown File',
|
|
35
|
+
description: 'Read a local markdown/text file and ingest it as a Brainlink note. Reindex defaults to true.',
|
|
36
|
+
inputSchema: addFileInputSchema
|
|
37
|
+
}, addFileTool);
|
|
33
38
|
server.registerTool('brainlink_index', {
|
|
34
39
|
title: 'Index Brainlink Vault',
|
|
35
40
|
description: 'Rebuild the local Brainlink index from Markdown notes.',
|
package/dist/mcp/tools.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { basename, extname } from 'node:path';
|
|
1
3
|
import { z } from 'zod';
|
|
2
4
|
import { getBrokenLinksReport, getOrphansReport, getStats, validateVault } from '../application/analyze-vault.js';
|
|
3
5
|
import { addNote } from '../application/add-note.js';
|
|
@@ -32,11 +34,21 @@ const resolveExecutionContext = async (input) => {
|
|
|
32
34
|
const vault = await assertVaultAllowed(input.vault ?? config.vault, config.allowedVaults);
|
|
33
35
|
const agent = input.agent ?? config.defaultAgent;
|
|
34
36
|
return {
|
|
35
|
-
vault,
|
|
36
37
|
config,
|
|
38
|
+
vault,
|
|
37
39
|
agent
|
|
38
40
|
};
|
|
39
41
|
};
|
|
42
|
+
const inferTitleFromPath = (filePath) => {
|
|
43
|
+
const extension = extname(filePath);
|
|
44
|
+
const fromFileName = basename(filePath, extension);
|
|
45
|
+
return fromFileName
|
|
46
|
+
.trim()
|
|
47
|
+
.replace(/[-_]+/g, ' ')
|
|
48
|
+
.replace(/\s+/g, ' ')
|
|
49
|
+
.trim();
|
|
50
|
+
};
|
|
51
|
+
const isTruthy = (value) => value !== false;
|
|
40
52
|
const jsonResult = (value) => ({
|
|
41
53
|
content: [
|
|
42
54
|
{
|
|
@@ -68,7 +80,16 @@ export const addNoteInputSchema = {
|
|
|
68
80
|
.string()
|
|
69
81
|
.min(1)
|
|
70
82
|
.describe('Durable Markdown memory. Include explicit [[wiki links]] and #tags when the memory should be connected. Put priority markers near important links, for example priority: high, #important or #critical.'),
|
|
71
|
-
|
|
83
|
+
...agentInput,
|
|
84
|
+
allowSensitive: z.boolean().optional().default(false).describe('Allow content that looks like a secret.'),
|
|
85
|
+
autoIndex: z.boolean().optional().default(true).describe('Reindex vault after writing note.')
|
|
86
|
+
};
|
|
87
|
+
export const addFileInputSchema = {
|
|
88
|
+
...vaultInput,
|
|
89
|
+
...agentInput,
|
|
90
|
+
title: z.string().min(1).optional().describe('Optional note title override. If omitted, uses file name.'),
|
|
91
|
+
filePath: z.string().min(1).describe('Filesystem path to markdown or text file to ingest.'),
|
|
92
|
+
autoIndex: z.boolean().optional().default(true).describe('Reindex vault after ingesting file.'),
|
|
72
93
|
allowSensitive: z.boolean().optional().default(false).describe('Allow content that looks like a secret.')
|
|
73
94
|
};
|
|
74
95
|
export const indexInputSchema = {
|
|
@@ -128,16 +149,39 @@ export const searchTool = async (input) => {
|
|
|
128
149
|
};
|
|
129
150
|
export const addNoteTool = async (input) => {
|
|
130
151
|
const context = await resolveExecutionContext(input);
|
|
152
|
+
const shouldIndex = isTruthy(input.autoIndex);
|
|
131
153
|
const path = await addNote(context.vault, input.title, input.content, context.agent, {
|
|
132
154
|
allowSensitive: input.allowSensitive
|
|
133
155
|
});
|
|
134
|
-
const index = await indexVault(context.vault);
|
|
156
|
+
const index = shouldIndex ? await indexVault(context.vault) : undefined;
|
|
135
157
|
return jsonResult({
|
|
136
158
|
vault: context.vault,
|
|
137
159
|
title: input.title,
|
|
138
160
|
agent: context.agent,
|
|
139
161
|
path,
|
|
140
|
-
index
|
|
162
|
+
...(index ? { index } : {})
|
|
163
|
+
});
|
|
164
|
+
};
|
|
165
|
+
export const addFileTool = async (input) => {
|
|
166
|
+
const context = await resolveExecutionContext(input);
|
|
167
|
+
const content = await readFile(input.filePath, 'utf8');
|
|
168
|
+
const inferredTitle = inferTitleFromPath(input.filePath);
|
|
169
|
+
const title = input.title ?? inferredTitle;
|
|
170
|
+
if (title == null || title.length === 0) {
|
|
171
|
+
throw new Error('Cannot infer note title from file path. Provide a title explicitly.');
|
|
172
|
+
}
|
|
173
|
+
const shouldIndex = isTruthy(input.autoIndex);
|
|
174
|
+
const path = await addNote(context.vault, title, content, context.agent, {
|
|
175
|
+
allowSensitive: input.allowSensitive
|
|
176
|
+
});
|
|
177
|
+
const index = shouldIndex ? await indexVault(context.vault) : undefined;
|
|
178
|
+
return jsonResult({
|
|
179
|
+
vault: context.vault,
|
|
180
|
+
title,
|
|
181
|
+
agent: context.agent,
|
|
182
|
+
filePath: input.filePath,
|
|
183
|
+
path,
|
|
184
|
+
...(index ? { index } : {})
|
|
141
185
|
});
|
|
142
186
|
};
|
|
143
187
|
export const indexTool = async (input) => {
|
package/docs/AGENT_USAGE.md
CHANGED
|
@@ -43,6 +43,8 @@ Use `--vault <path>` for a one-off custom vault, or set `vault` in `brainlink.co
|
|
|
43
43
|
|
|
44
44
|
You can also set `defaultAgent` in `brainlink.config.json` / `.brainlink.json` (for example `"defaultAgent": "coding-agent"`). When set, CLI commands and MCP calls reuse it when `--agent`/`agent` is not passed.
|
|
45
45
|
|
|
46
|
+
`autoIndexOnWrite` (default: `true`) controls whether `add` and MCP write tools index right after writing.
|
|
47
|
+
|
|
46
48
|
## Agent Namespaces
|
|
47
49
|
|
|
48
50
|
Each agent writes into a dedicated namespace under `agents/<agent-id>/`:
|
|
@@ -162,7 +164,7 @@ Required write behavior:
|
|
|
162
164
|
2. Look for an existing related concept with `search`, `links` or `backlinks`.
|
|
163
165
|
3. Add at least one `[[Existing Note Title]]` link unless the note is intentionally a root concept.
|
|
164
166
|
4. Add useful `#tags` for retrieval.
|
|
165
|
-
5.
|
|
167
|
+
5. `add` writes are indexed by default. Only batch with explicit `--no-auto-index`, then run `index` once.
|
|
166
168
|
6. Run `validate`, `broken-links` or `orphans` when the graph should be connected.
|
|
167
169
|
|
|
168
170
|
Good linked note:
|
|
@@ -171,7 +173,6 @@ Good linked note:
|
|
|
171
173
|
blink add "SQLite Index Rebuild" \
|
|
172
174
|
--agent coding-agent \
|
|
173
175
|
--content "Legacy derived indexes without agent columns are rebuilt because SQLite is disposable. Related: [[Architecture]], [[Agent Namespaces]]. #sqlite #architecture #decision"
|
|
174
|
-
blink index
|
|
175
176
|
blink validate --agent coding-agent
|
|
176
177
|
```
|
|
177
178
|
|
|
@@ -247,7 +248,7 @@ When using MCP, use this compact sequence for the same memory discipline:
|
|
|
247
248
|
1. Bootstrap context:
|
|
248
249
|
- `brainlink_context` with `agent`, `query`, `mode: hybrid`, `limit`.
|
|
249
250
|
2. Capture durable decisions:
|
|
250
|
-
- `brainlink_add_note` with explicit `[[wiki links]]` and `#tags`.
|
|
251
|
+
- `brainlink_add_note` or `brainlink_add_file` with explicit `[[wiki links]]` and `#tags`.
|
|
251
252
|
3. Run maintenance before handoff or before the next step:
|
|
252
253
|
- `brainlink_sync` with `agent`, `contextQuery`, `mode: hybrid`.
|
|
253
254
|
4. Diagnose graph issues only when needed:
|
|
@@ -346,8 +347,12 @@ $HOME/.brainlink/vault/
|
|
|
346
347
|
|
|
347
348
|
```bash
|
|
348
349
|
blink add "Note Title" --vault ./vault --content "Markdown content"
|
|
350
|
+
blink add "Note Title" --vault ./vault --content-file ./notes.md
|
|
351
|
+
blink add "Note Title" --vault ./vault --content-file ./notes.md --no-auto-index
|
|
349
352
|
```
|
|
350
353
|
|
|
354
|
+
`--content` and `--content-file` are mutually exclusive. Use `--no-auto-index` if you want to defer indexing in batch operations.
|
|
355
|
+
|
|
351
356
|
This creates a slugged Markdown file with frontmatter and a heading.
|
|
352
357
|
|
|
353
358
|
The CLI blocks common secret patterns by default. Do not use `--allow-sensitive` unless the vault is intentionally protected.
|
|
@@ -516,6 +521,7 @@ Available MCP tools:
|
|
|
516
521
|
- `brainlink_context`
|
|
517
522
|
- `brainlink_search`
|
|
518
523
|
- `brainlink_add_note`
|
|
524
|
+
- `brainlink_add_file`
|
|
519
525
|
- `brainlink_index`
|
|
520
526
|
- `brainlink_validate`
|
|
521
527
|
- `brainlink_graph`
|
package/package.json
CHANGED