@andespindola/brainlink 0.1.0-alpha.0 → 0.1.0-alpha.10
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 -2
- package/README.md +79 -60
- package/SECURITY.md +2 -2
- package/assets/brainlink-logo.svg +25 -0
- package/dist/application/server/host-security.js +3 -3
- package/dist/application/start-server.js +2 -2
- package/dist/cli/commands/read-commands.js +10 -10
- package/dist/cli/commands/write-commands.js +8 -10
- package/dist/cli/main.js +9 -2
- package/dist/infrastructure/config.js +2 -1
- package/dist/infrastructure/file-system-vault.js +2 -1
- package/dist/infrastructure/paths.js +14 -0
- package/dist/infrastructure/sqlite/schema.js +14 -1
- package/dist/mcp/main.js +10 -0
- package/dist/mcp/server.js +59 -0
- package/dist/mcp/tools.js +166 -0
- package/docs/AGENT_USAGE.md +96 -18
- package/docs/ARCHITECTURE.md +5 -7
- package/docs/RELEASE.md +19 -4
- package/package.json +8 -3
package/AGENTS.md
CHANGED
|
@@ -20,17 +20,23 @@ npm run dev -- index --vault ./vault
|
|
|
20
20
|
|
|
21
21
|
Do not store permanent knowledge only in SQLite.
|
|
22
22
|
|
|
23
|
+
By default, the installed Brainlink CLI uses `$HOME/.brainlink/vault` as its vault. Passing `--vault` or setting `vault` in `brainlink.config.json` intentionally selects a custom vault such as `./vault`.
|
|
24
|
+
|
|
23
25
|
## Agent Workflow
|
|
24
26
|
|
|
25
27
|
Use this loop when using Brainlink as memory:
|
|
26
28
|
|
|
27
29
|
1. Write durable knowledge into Markdown notes.
|
|
28
|
-
2. Link related notes with `[[Note Title]]
|
|
30
|
+
2. Link related notes with explicit `[[Note Title]]` wiki links inside the note body.
|
|
29
31
|
3. Add explicit `#tags` for retrieval.
|
|
30
32
|
4. Run `index` after writes.
|
|
31
33
|
5. Run `context "<task or question>"` before answering.
|
|
32
34
|
6. Use the returned sources as grounded context.
|
|
33
35
|
|
|
36
|
+
`context` is read-only. It does not create notes, backlinks, graph edges or durable memory by itself. A relationship exists only when a Markdown note contains a `[[wiki link]]` to another note and the vault has been indexed after that write.
|
|
37
|
+
|
|
38
|
+
When an agent adds durable memory, it should connect the new note to at least one existing concept unless the note is intentionally a root concept. Prefer exact note titles in links, for example `[[Architecture]]`, and run `broken-links`, `orphans` or `validate` when the graph looks disconnected.
|
|
39
|
+
|
|
34
40
|
## Commands
|
|
35
41
|
|
|
36
42
|
```bash
|
|
@@ -80,7 +86,7 @@ npm run dev -- watch --vault ./vault
|
|
|
80
86
|
Start MCP over stdio:
|
|
81
87
|
|
|
82
88
|
```bash
|
|
83
|
-
npm run dev
|
|
89
|
+
npm run dev:mcp
|
|
84
90
|
```
|
|
85
91
|
|
|
86
92
|
Automation-facing CLI commands support `--json`. When invoking through `npm`, use `npm run --silent dev -- ...` so stdout remains valid JSON.
|
package/README.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="./assets/brainlink-logo.svg" alt="Brainlink" width="720">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
1
5
|
# Brainlink
|
|
2
6
|
|
|
3
7
|
Local-first memory and knowledge graph for AI agents.
|
|
@@ -64,7 +68,7 @@ Markdown is the source of truth. `.brainlink/brainlink.db` is only a rebuildable
|
|
|
64
68
|
- Agent namespaces under `agents/<agent-id>/`.
|
|
65
69
|
- CLI with machine-readable `--json` output.
|
|
66
70
|
- Short CLI alias: `blink`.
|
|
67
|
-
-
|
|
71
|
+
- Built-in MCP stdio server for agent tool integration.
|
|
68
72
|
- Local HTTP API.
|
|
69
73
|
- Realtime graph UI with agent selector and colored knowledge groups.
|
|
70
74
|
|
|
@@ -76,7 +80,7 @@ Requirements:
|
|
|
76
80
|
- npm
|
|
77
81
|
|
|
78
82
|
```bash
|
|
79
|
-
npm install -g @andespindola/brainlink
|
|
83
|
+
npm install -g @andespindola/brainlink@latest
|
|
80
84
|
```
|
|
81
85
|
|
|
82
86
|
The package installs two equivalent commands:
|
|
@@ -88,6 +92,14 @@ blink --help
|
|
|
88
92
|
|
|
89
93
|
Use `brainlink` when clarity matters. Use `blink` for faster daily terminal usage.
|
|
90
94
|
|
|
95
|
+
The npm package page may show `npm i @andespindola/brainlink`. That installs
|
|
96
|
+
Brainlink as a project dependency. Use `-g` when you want the terminal commands
|
|
97
|
+
available globally, or run it without a global install:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
npx @andespindola/brainlink@latest --help
|
|
101
|
+
```
|
|
102
|
+
|
|
91
103
|
## Agent Autonomous Setup
|
|
92
104
|
|
|
93
105
|
This section is written for agents. If you are an AI agent operating in a user workspace, use this workflow to install and use Brainlink without requiring the user to run commands manually.
|
|
@@ -101,7 +113,7 @@ command -v blink || command -v brainlink
|
|
|
101
113
|
If neither command exists, install it:
|
|
102
114
|
|
|
103
115
|
```bash
|
|
104
|
-
npm install -g @andespindola/brainlink
|
|
116
|
+
npm install -g @andespindola/brainlink@latest
|
|
105
117
|
```
|
|
106
118
|
|
|
107
119
|
Then verify:
|
|
@@ -110,26 +122,33 @@ Then verify:
|
|
|
110
122
|
blink --help
|
|
111
123
|
```
|
|
112
124
|
|
|
113
|
-
### 2. Choose A Vault
|
|
125
|
+
### 2. Use The Default Vault Or Choose A Custom Vault
|
|
114
126
|
|
|
115
|
-
|
|
127
|
+
By default, Brainlink stores memory in:
|
|
116
128
|
|
|
117
129
|
```bash
|
|
118
|
-
|
|
130
|
+
$HOME/.brainlink/vault
|
|
119
131
|
```
|
|
120
132
|
|
|
121
|
-
Use
|
|
133
|
+
Use the default vault when memory should span many projects:
|
|
122
134
|
|
|
123
135
|
```bash
|
|
124
|
-
|
|
136
|
+
blink init
|
|
125
137
|
```
|
|
126
138
|
|
|
127
|
-
|
|
139
|
+
Choose a project-local vault only when memory belongs to one repository:
|
|
128
140
|
|
|
129
141
|
```bash
|
|
142
|
+
BLINK_VAULT=".brainlink-vault"
|
|
130
143
|
blink init "$BLINK_VAULT"
|
|
131
144
|
```
|
|
132
145
|
|
|
146
|
+
You can also set `BRAINLINK_HOME` to move Brainlink's default home directory:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
export BRAINLINK_HOME="/path/to/brainlink-home"
|
|
150
|
+
```
|
|
151
|
+
|
|
133
152
|
### 3. Choose An Agent Namespace
|
|
134
153
|
|
|
135
154
|
Use a stable namespace for yourself:
|
|
@@ -146,7 +165,6 @@ Before answering or changing code, retrieve context:
|
|
|
146
165
|
|
|
147
166
|
```bash
|
|
148
167
|
blink context "What should I know before working on this task?" \
|
|
149
|
-
--vault "$BLINK_VAULT" \
|
|
150
168
|
--agent "$BLINK_AGENT" \
|
|
151
169
|
--json
|
|
152
170
|
```
|
|
@@ -155,7 +173,6 @@ If the context is weak, inspect raw search results:
|
|
|
155
173
|
|
|
156
174
|
```bash
|
|
157
175
|
blink search "architecture conventions tests release" \
|
|
158
|
-
--vault "$BLINK_VAULT" \
|
|
159
176
|
--agent "$BLINK_AGENT" \
|
|
160
177
|
--mode hybrid \
|
|
161
178
|
--limit 10 \
|
|
@@ -168,23 +185,31 @@ Only store knowledge that is likely to matter later:
|
|
|
168
185
|
|
|
169
186
|
```bash
|
|
170
187
|
blink add "Testing Policy" \
|
|
171
|
-
--vault "$BLINK_VAULT" \
|
|
172
188
|
--agent "$BLINK_AGENT" \
|
|
173
189
|
--content "Run npm run check before final delivery. Related: [[Release Checklist]]. #testing #process"
|
|
174
190
|
```
|
|
175
191
|
|
|
192
|
+
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 and then rebuild the index.
|
|
193
|
+
|
|
194
|
+
When adding memory, follow this contract:
|
|
195
|
+
|
|
196
|
+
- Link the new note to at least one existing note when there is a related concept.
|
|
197
|
+
- Use the exact target note title inside `[[...]]`.
|
|
198
|
+
- Add retrieval tags such as `#architecture`, `#decision`, `#runbook` or `#preference`.
|
|
199
|
+
- Do not leave isolated notes unless they are intentionally root concepts.
|
|
200
|
+
|
|
176
201
|
Rebuild the index:
|
|
177
202
|
|
|
178
203
|
```bash
|
|
179
|
-
blink index
|
|
204
|
+
blink index
|
|
180
205
|
```
|
|
181
206
|
|
|
182
207
|
### 6. Validate Memory Health
|
|
183
208
|
|
|
184
209
|
```bash
|
|
185
|
-
blink validate --
|
|
186
|
-
blink broken-links --
|
|
187
|
-
blink orphans --
|
|
210
|
+
blink validate --agent "$BLINK_AGENT" --json
|
|
211
|
+
blink broken-links --agent "$BLINK_AGENT" --json
|
|
212
|
+
blink orphans --agent "$BLINK_AGENT" --json
|
|
188
213
|
```
|
|
189
214
|
|
|
190
215
|
### Agent Operating Loop
|
|
@@ -192,12 +217,12 @@ blink orphans --vault "$BLINK_VAULT" --agent "$BLINK_AGENT" --json
|
|
|
192
217
|
Use this loop during real work:
|
|
193
218
|
|
|
194
219
|
1. Identify the task and choose `BLINK_AGENT`.
|
|
195
|
-
2. Run `blink context "<task>" --
|
|
220
|
+
2. Run `blink context "<task>" --agent "$BLINK_AGENT" --json`.
|
|
196
221
|
3. Use returned sources as project memory.
|
|
197
222
|
4. Perform the task.
|
|
198
|
-
5. Save only durable learnings with `blink add
|
|
223
|
+
5. Save only durable learnings with `blink add`, including `[[wiki links]]` to related notes.
|
|
199
224
|
6. Run `blink index`.
|
|
200
|
-
7. Validate with `blink validate
|
|
225
|
+
7. Validate with `blink validate`, `blink broken-links` and `blink orphans` when graph links matter.
|
|
201
226
|
|
|
202
227
|
Do not store secrets, credentials, private keys, access tokens or transient chat noise.
|
|
203
228
|
|
|
@@ -229,6 +254,8 @@ Open the graph UI:
|
|
|
229
254
|
http://127.0.0.1:4321
|
|
230
255
|
```
|
|
231
256
|
|
|
257
|
+
When `--vault` is omitted, commands use the default vault at `$HOME/.brainlink/vault`. Pass `--vault` or configure `vault` in `brainlink.config.json` when you want a custom project-local vault.
|
|
258
|
+
|
|
232
259
|
## Core Model
|
|
233
260
|
|
|
234
261
|
```txt
|
|
@@ -307,59 +334,47 @@ This allows `coding-agent` and `research-agent` to both have a note named `Archi
|
|
|
307
334
|
|
|
308
335
|
## MCP Server Integration
|
|
309
336
|
|
|
310
|
-
Brainlink
|
|
311
|
-
|
|
312
|
-
An MCP server can use Brainlink by spawning `blink` or `brainlink` as a subprocess and reading `--json` output. This keeps Brainlink decoupled from any specific MCP SDK while still making it usable by MCP-compatible agents.
|
|
313
|
-
|
|
314
|
-
Minimum integration contract:
|
|
337
|
+
Brainlink ships a stdio MCP server with the npm package:
|
|
315
338
|
|
|
316
339
|
```bash
|
|
317
|
-
|
|
318
|
-
blink add "Decision Title" --vault "$BLINK_VAULT" --agent "$BLINK_AGENT" --content "Durable memory. #decision"
|
|
319
|
-
blink index --vault "$BLINK_VAULT"
|
|
340
|
+
brainlink-mcp
|
|
320
341
|
```
|
|
321
342
|
|
|
322
|
-
Example
|
|
323
|
-
|
|
324
|
-
```js
|
|
325
|
-
import { execFile } from 'node:child_process'
|
|
326
|
-
import { promisify } from 'node:util'
|
|
343
|
+
Example MCP client configuration:
|
|
327
344
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
vault,
|
|
336
|
-
'--agent',
|
|
337
|
-
agent,
|
|
338
|
-
'--mode',
|
|
339
|
-
'hybrid',
|
|
340
|
-
'--json'
|
|
341
|
-
])
|
|
342
|
-
|
|
343
|
-
return JSON.parse(stdout)
|
|
345
|
+
```json
|
|
346
|
+
{
|
|
347
|
+
"mcpServers": {
|
|
348
|
+
"brainlink": {
|
|
349
|
+
"command": "brainlink-mcp"
|
|
350
|
+
}
|
|
351
|
+
}
|
|
344
352
|
}
|
|
345
353
|
```
|
|
346
354
|
|
|
347
|
-
|
|
355
|
+
Available tools:
|
|
356
|
+
|
|
357
|
+
- `brainlink_context`: read indexed context for a task or question.
|
|
358
|
+
- `brainlink_search`: search indexed notes.
|
|
359
|
+
- `brainlink_add_note`: write durable Markdown memory and reindex.
|
|
360
|
+
- `brainlink_index`: rebuild the vault index.
|
|
361
|
+
- `brainlink_validate`: validate broken links and orphan notes.
|
|
362
|
+
- `brainlink_graph`: read indexed graph nodes and links.
|
|
363
|
+
- `brainlink_broken_links`: list unresolved wiki links.
|
|
364
|
+
- `brainlink_orphans`: list disconnected notes.
|
|
348
365
|
|
|
349
|
-
|
|
350
|
-
- `brainlink_search`: calls `blink search ... --json`.
|
|
351
|
-
- `brainlink_add_note`: calls `blink add ... --json`, then `blink index`.
|
|
352
|
-
- `brainlink_graph`: calls `blink graph ... --json`.
|
|
353
|
-
- `brainlink_validate`: calls `blink validate ... --json`.
|
|
366
|
+
The same linking rule applies through MCP: `brainlink_context` is read-only, and real graph links require Markdown notes with explicit `[[wiki links]]` followed by indexing.
|
|
354
367
|
|
|
355
368
|
## Graph UI
|
|
356
369
|
|
|
357
370
|
Start the local frontend:
|
|
358
371
|
|
|
359
372
|
```bash
|
|
360
|
-
blink server --
|
|
373
|
+
blink server --host 127.0.0.1 --port 4321 --watch
|
|
361
374
|
```
|
|
362
375
|
|
|
376
|
+
By default, the server uses `$HOME/.brainlink/vault`. Pass `--vault ./vault` only when you want to inspect a custom vault.
|
|
377
|
+
|
|
363
378
|
The graph UI shows:
|
|
364
379
|
|
|
365
380
|
- notes as nodes
|
|
@@ -380,7 +395,7 @@ blink server --vault ./vault --no-index
|
|
|
380
395
|
|
|
381
396
|
The HTTP API is read-only and exists only to power the graph UI and local inspection workflows.
|
|
382
397
|
|
|
383
|
-
The server refuses non-loopback hosts
|
|
398
|
+
The server always refuses non-loopback hosts. Brainlink HTTP only runs on localhost.
|
|
384
399
|
|
|
385
400
|
Routes:
|
|
386
401
|
|
|
@@ -411,14 +426,16 @@ Every command works with either `brainlink` or `blink`.
|
|
|
411
426
|
### `init`
|
|
412
427
|
|
|
413
428
|
```bash
|
|
429
|
+
blink init
|
|
414
430
|
blink init ./vault
|
|
415
431
|
```
|
|
416
432
|
|
|
417
|
-
Initializes vault metadata.
|
|
433
|
+
Initializes vault metadata. Without an argument, Brainlink initializes the default vault at `$HOME/.brainlink/vault`.
|
|
418
434
|
|
|
419
435
|
### `add`
|
|
420
436
|
|
|
421
437
|
```bash
|
|
438
|
+
blink add "Note Title" --agent coding-agent --content "Markdown content"
|
|
422
439
|
blink add "Note Title" --vault ./vault --agent coding-agent --content "Markdown content"
|
|
423
440
|
```
|
|
424
441
|
|
|
@@ -427,6 +444,7 @@ Creates a Markdown note under `agents/<agent-id>/`. Common secret patterns are b
|
|
|
427
444
|
### `index`
|
|
428
445
|
|
|
429
446
|
```bash
|
|
447
|
+
blink index
|
|
430
448
|
blink index --vault ./vault
|
|
431
449
|
```
|
|
432
450
|
|
|
@@ -546,12 +564,13 @@ Watches Markdown files and rebuilds the index when notes change.
|
|
|
546
564
|
### `server`
|
|
547
565
|
|
|
548
566
|
```bash
|
|
567
|
+
blink server --watch
|
|
549
568
|
blink server --vault ./vault --watch
|
|
550
569
|
```
|
|
551
570
|
|
|
552
571
|
Starts the local read-only graph UI and HTTP API.
|
|
553
572
|
|
|
554
|
-
|
|
573
|
+
The HTTP server only binds to loopback hosts such as `127.0.0.1`, `localhost` or `::1`.
|
|
555
574
|
|
|
556
575
|
## Machine-Readable Output
|
|
557
576
|
|
|
@@ -569,7 +588,7 @@ npm run --silent dev -- context "question" --vault ./vault --json
|
|
|
569
588
|
|
|
570
589
|
## Configuration
|
|
571
590
|
|
|
572
|
-
Brainlink reads `brainlink.config.json` or `.brainlink.json` from the current working directory.
|
|
591
|
+
Brainlink reads `brainlink.config.json` or `.brainlink.json` from the current working directory. If no `vault` is configured and no `--vault` flag is passed, Brainlink uses `$HOME/.brainlink/vault`.
|
|
573
592
|
|
|
574
593
|
```json
|
|
575
594
|
{
|
|
@@ -679,7 +698,6 @@ Detailed notes:
|
|
|
679
698
|
- Semantic search uses SQLite embedding buckets to narrow candidates before cosine scoring.
|
|
680
699
|
- `embeddingProvider` currently supports `local` and `none`.
|
|
681
700
|
- Link resolution is title-based inside each agent namespace, with `shared` as fallback.
|
|
682
|
-
- No embedded MCP server is shipped; MCP integration is done by external servers wrapping the CLI.
|
|
683
701
|
- HTTP API is local and unauthenticated.
|
|
684
702
|
- Watch mode depends on the platform filesystem watcher.
|
|
685
703
|
|
|
@@ -700,6 +718,7 @@ The alpha includes local semantic retrieval. Remote embedding providers, remote
|
|
|
700
718
|
Brainlink is local-first by default.
|
|
701
719
|
|
|
702
720
|
- Do not expose the HTTP server publicly without authentication.
|
|
721
|
+
- Brainlink HTTP is localhost-only and refuses non-loopback hosts.
|
|
703
722
|
- Brainlink blocks common secret patterns by default when adding notes. Use `--allow-sensitive` only for intentional, protected vaults.
|
|
704
723
|
- Do not store secrets, credentials, API keys or regulated personal data unless the vault is protected by your own storage controls.
|
|
705
724
|
- Treat `.brainlink/brainlink.db` as disposable derived data.
|
package/SECURITY.md
CHANGED
|
@@ -5,7 +5,7 @@ Brainlink is local-first.
|
|
|
5
5
|
## Defaults
|
|
6
6
|
|
|
7
7
|
- The HTTP server binds to `127.0.0.1` by default.
|
|
8
|
-
- The HTTP server refuses non-loopback hosts
|
|
8
|
+
- The HTTP server always refuses non-loopback hosts.
|
|
9
9
|
- The HTTP server is read-only and does not expose note creation, indexing or update routes.
|
|
10
10
|
- The SQLite database is a derived local index.
|
|
11
11
|
- Markdown files are user-owned source data.
|
|
@@ -14,7 +14,7 @@ Brainlink is local-first.
|
|
|
14
14
|
|
|
15
15
|
## Remote Exposure
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
Brainlink HTTP is intentionally localhost-only. It does not support binding to a public interface.
|
|
18
18
|
|
|
19
19
|
## Sensitive Memory
|
|
20
20
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<svg width="900" height="220" viewBox="0 0 900 220" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="title desc">
|
|
2
|
+
<title id="title">Brainlink</title>
|
|
3
|
+
<desc id="desc">Brainlink logo with a linked memory graph mark and wordmark.</desc>
|
|
4
|
+
<rect width="900" height="220" fill="transparent"/>
|
|
5
|
+
<g transform="translate(42 24)">
|
|
6
|
+
<rect x="6" y="6" width="160" height="160" rx="36" fill="#F8FAFC"/>
|
|
7
|
+
<rect x="6" y="6" width="160" height="160" rx="36" stroke="#111827" stroke-width="8"/>
|
|
8
|
+
<path d="M47 84C47 65.225 62.225 50 81 50H92C110.775 50 126 65.225 126 84C126 102.775 110.775 118 92 118H81C62.225 118 47 102.775 47 84Z" stroke="#111827" stroke-width="12" stroke-linecap="round"/>
|
|
9
|
+
<path d="M67 84C67 76.268 73.268 70 81 70H92C99.732 70 106 76.268 106 84C106 91.732 99.732 98 92 98H81C73.268 98 67 91.732 67 84Z" fill="#FFFFFF"/>
|
|
10
|
+
<path d="M54 132L43 121L54 110" stroke="#2563EB" stroke-width="10" stroke-linecap="round" stroke-linejoin="round"/>
|
|
11
|
+
<path d="M118 110L129 121L118 132" stroke="#2563EB" stroke-width="10" stroke-linecap="round" stroke-linejoin="round"/>
|
|
12
|
+
<circle cx="55" cy="43" r="12" fill="#14B8A6"/>
|
|
13
|
+
<circle cx="121" cy="43" r="12" fill="#2563EB"/>
|
|
14
|
+
<circle cx="86" cy="136" r="12" fill="#111827"/>
|
|
15
|
+
<path d="M66.5 47.5L109.5 47.5" stroke="#94A3B8" stroke-width="7" stroke-linecap="round"/>
|
|
16
|
+
<path d="M62 54L79 125" stroke="#94A3B8" stroke-width="7" stroke-linecap="round"/>
|
|
17
|
+
<path d="M115 54L92 125" stroke="#94A3B8" stroke-width="7" stroke-linecap="round"/>
|
|
18
|
+
</g>
|
|
19
|
+
<g transform="translate(244 68)">
|
|
20
|
+
<text x="0" y="62" fill="#111827" font-family="Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="72" font-weight="760">Brainlink</text>
|
|
21
|
+
<text x="4" y="102" fill="#475569" font-family="Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="24" font-weight="500">Local-first memory for AI agents</text>
|
|
22
|
+
<path d="M0 124H268" stroke="#14B8A6" stroke-width="8" stroke-linecap="round"/>
|
|
23
|
+
<path d="M292 124H392" stroke="#2563EB" stroke-width="8" stroke-linecap="round"/>
|
|
24
|
+
</g>
|
|
25
|
+
</svg>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export const isLoopbackHost = (host) => host === 'localhost' || host === '::1' || host === '[::1]' || host.startsWith('127.');
|
|
2
|
-
export const
|
|
3
|
-
if (!
|
|
4
|
-
throw new Error(`Refusing to bind Brainlink server to non-loopback host ${host}.
|
|
2
|
+
export const assertLoopbackHost = (host) => {
|
|
3
|
+
if (!isLoopbackHost(host)) {
|
|
4
|
+
throw new Error(`Refusing to bind Brainlink server to non-loopback host ${host}. Brainlink HTTP only runs on localhost.`);
|
|
5
5
|
}
|
|
6
6
|
};
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { createServer } from 'node:http';
|
|
2
2
|
import { indexVault } from './index-vault.js';
|
|
3
3
|
import { startVaultWatcher } from './watch-vault.js';
|
|
4
|
-
import {
|
|
4
|
+
import { assertLoopbackHost } from './server/host-security.js';
|
|
5
5
|
import { contentTypes, createJsonResponse, isHttpError } from './server/http.js';
|
|
6
6
|
import { route } from './server/routes.js';
|
|
7
7
|
export const startServer = async (input) => {
|
|
8
|
-
|
|
8
|
+
assertLoopbackHost(input.host);
|
|
9
9
|
if (input.shouldIndex) {
|
|
10
10
|
await indexVault(input.vaultPath);
|
|
11
11
|
}
|
|
@@ -10,7 +10,7 @@ export const registerReadCommands = (program) => {
|
|
|
10
10
|
program
|
|
11
11
|
.command('search')
|
|
12
12
|
.argument('<query>', 'search query')
|
|
13
|
-
.option('-v, --vault <vault>', 'vault directory'
|
|
13
|
+
.option('-v, --vault <vault>', 'vault directory')
|
|
14
14
|
.option('-a, --agent <agent>', 'filter by agent memory namespace')
|
|
15
15
|
.option('-l, --limit <limit>', 'maximum results', '10')
|
|
16
16
|
.option('-m, --mode <mode>', 'search mode: fts, semantic or hybrid')
|
|
@@ -27,7 +27,7 @@ export const registerReadCommands = (program) => {
|
|
|
27
27
|
});
|
|
28
28
|
program
|
|
29
29
|
.command('links')
|
|
30
|
-
.option('-v, --vault <vault>', 'vault directory'
|
|
30
|
+
.option('-v, --vault <vault>', 'vault directory')
|
|
31
31
|
.option('-a, --agent <agent>', 'filter by agent memory namespace')
|
|
32
32
|
.option('--json', 'print machine-readable JSON')
|
|
33
33
|
.description('list indexed wiki links')
|
|
@@ -44,7 +44,7 @@ export const registerReadCommands = (program) => {
|
|
|
44
44
|
program
|
|
45
45
|
.command('backlinks')
|
|
46
46
|
.argument('<title>', 'target note title')
|
|
47
|
-
.option('-v, --vault <vault>', 'vault directory'
|
|
47
|
+
.option('-v, --vault <vault>', 'vault directory')
|
|
48
48
|
.option('-a, --agent <agent>', 'filter by agent memory namespace')
|
|
49
49
|
.option('--json', 'print machine-readable JSON')
|
|
50
50
|
.description('list notes linking to a target note')
|
|
@@ -56,7 +56,7 @@ export const registerReadCommands = (program) => {
|
|
|
56
56
|
program
|
|
57
57
|
.command('context')
|
|
58
58
|
.argument('<query>', 'context query')
|
|
59
|
-
.option('-v, --vault <vault>', 'vault directory'
|
|
59
|
+
.option('-v, --vault <vault>', 'vault directory')
|
|
60
60
|
.option('-a, --agent <agent>', 'filter by agent memory namespace')
|
|
61
61
|
.option('-l, --limit <limit>', 'maximum search results before context selection', '12')
|
|
62
62
|
.option('-t, --tokens <tokens>', 'maximum estimated context tokens', '2000')
|
|
@@ -71,7 +71,7 @@ export const registerReadCommands = (program) => {
|
|
|
71
71
|
});
|
|
72
72
|
program
|
|
73
73
|
.command('graph')
|
|
74
|
-
.option('-v, --vault <vault>', 'vault directory'
|
|
74
|
+
.option('-v, --vault <vault>', 'vault directory')
|
|
75
75
|
.option('-a, --agent <agent>', 'filter by agent memory namespace')
|
|
76
76
|
.option('--json', 'print machine-readable JSON')
|
|
77
77
|
.description('print indexed graph data')
|
|
@@ -82,7 +82,7 @@ export const registerReadCommands = (program) => {
|
|
|
82
82
|
});
|
|
83
83
|
program
|
|
84
84
|
.command('agents')
|
|
85
|
-
.option('-v, --vault <vault>', 'vault directory'
|
|
85
|
+
.option('-v, --vault <vault>', 'vault directory')
|
|
86
86
|
.option('--json', 'print machine-readable JSON')
|
|
87
87
|
.description('list indexed agent memory namespaces')
|
|
88
88
|
.action(async (options) => {
|
|
@@ -92,7 +92,7 @@ export const registerReadCommands = (program) => {
|
|
|
92
92
|
});
|
|
93
93
|
program
|
|
94
94
|
.command('stats')
|
|
95
|
-
.option('-v, --vault <vault>', 'vault directory'
|
|
95
|
+
.option('-v, --vault <vault>', 'vault directory')
|
|
96
96
|
.option('-a, --agent <agent>', 'filter by agent memory namespace')
|
|
97
97
|
.option('--json', 'print machine-readable JSON')
|
|
98
98
|
.description('print indexed vault statistics')
|
|
@@ -110,7 +110,7 @@ export const registerReadCommands = (program) => {
|
|
|
110
110
|
});
|
|
111
111
|
program
|
|
112
112
|
.command('broken-links')
|
|
113
|
-
.option('-v, --vault <vault>', 'vault directory'
|
|
113
|
+
.option('-v, --vault <vault>', 'vault directory')
|
|
114
114
|
.option('-a, --agent <agent>', 'filter by agent memory namespace')
|
|
115
115
|
.option('--json', 'print machine-readable JSON')
|
|
116
116
|
.description('list unresolved wiki links')
|
|
@@ -123,7 +123,7 @@ export const registerReadCommands = (program) => {
|
|
|
123
123
|
});
|
|
124
124
|
program
|
|
125
125
|
.command('orphans')
|
|
126
|
-
.option('-v, --vault <vault>', 'vault directory'
|
|
126
|
+
.option('-v, --vault <vault>', 'vault directory')
|
|
127
127
|
.option('-a, --agent <agent>', 'filter by agent memory namespace')
|
|
128
128
|
.option('--json', 'print machine-readable JSON')
|
|
129
129
|
.description('list indexed notes without incoming or outgoing links')
|
|
@@ -134,7 +134,7 @@ export const registerReadCommands = (program) => {
|
|
|
134
134
|
});
|
|
135
135
|
program
|
|
136
136
|
.command('validate')
|
|
137
|
-
.option('-v, --vault <vault>', 'vault directory'
|
|
137
|
+
.option('-v, --vault <vault>', 'vault directory')
|
|
138
138
|
.option('-a, --agent <agent>', 'filter by agent memory namespace')
|
|
139
139
|
.option('--json', 'print machine-readable JSON')
|
|
140
140
|
.description('validate indexed vault graph health')
|
|
@@ -9,19 +9,19 @@ import { parsePositiveInteger, print, resolveOptions } from '../runtime.js';
|
|
|
9
9
|
export const registerWriteCommands = (program) => {
|
|
10
10
|
program
|
|
11
11
|
.command('init')
|
|
12
|
-
.argument('[vault]', 'vault directory'
|
|
12
|
+
.argument('[vault]', 'vault directory')
|
|
13
13
|
.option('--json', 'print machine-readable JSON')
|
|
14
14
|
.description('initialize a Brainlink vault')
|
|
15
15
|
.action(async (vault, options) => {
|
|
16
16
|
const config = await loadBrainlinkConfig();
|
|
17
|
-
const path = await ensureVault(assertVaultAllowed(vault, config.allowedVaults));
|
|
17
|
+
const path = await ensureVault(assertVaultAllowed(vault ?? config.vault, config.allowedVaults));
|
|
18
18
|
print(options.json, { path }, () => `Initialized Brainlink vault at ${path}`);
|
|
19
19
|
});
|
|
20
20
|
program
|
|
21
21
|
.command('add')
|
|
22
22
|
.argument('<title>', 'note title')
|
|
23
23
|
.requiredOption('-c, --content <content>', 'markdown content')
|
|
24
|
-
.option('-v, --vault <vault>', 'vault directory'
|
|
24
|
+
.option('-v, --vault <vault>', 'vault directory')
|
|
25
25
|
.option('-a, --agent <agent>', 'agent memory namespace', 'shared')
|
|
26
26
|
.option('--allow-sensitive', 'allow writing content that looks like a secret')
|
|
27
27
|
.option('--json', 'print machine-readable JSON')
|
|
@@ -35,7 +35,7 @@ export const registerWriteCommands = (program) => {
|
|
|
35
35
|
});
|
|
36
36
|
program
|
|
37
37
|
.command('index')
|
|
38
|
-
.option('-v, --vault <vault>', 'vault directory'
|
|
38
|
+
.option('-v, --vault <vault>', 'vault directory')
|
|
39
39
|
.option('--json', 'print machine-readable JSON')
|
|
40
40
|
.description('index markdown notes, links, tags and chunks')
|
|
41
41
|
.action(async (options) => {
|
|
@@ -45,7 +45,7 @@ export const registerWriteCommands = (program) => {
|
|
|
45
45
|
});
|
|
46
46
|
program
|
|
47
47
|
.command('doctor')
|
|
48
|
-
.option('-v, --vault <vault>', 'vault directory'
|
|
48
|
+
.option('-v, --vault <vault>', 'vault directory')
|
|
49
49
|
.option('--json', 'print machine-readable JSON')
|
|
50
50
|
.description('run Brainlink environment and vault checks')
|
|
51
51
|
.action(async (options) => {
|
|
@@ -56,7 +56,7 @@ export const registerWriteCommands = (program) => {
|
|
|
56
56
|
});
|
|
57
57
|
program
|
|
58
58
|
.command('watch')
|
|
59
|
-
.option('-v, --vault <vault>', 'vault directory'
|
|
59
|
+
.option('-v, --vault <vault>', 'vault directory')
|
|
60
60
|
.option('--json', 'print machine-readable JSON events')
|
|
61
61
|
.description('watch markdown files and reindex on changes')
|
|
62
62
|
.action(async (options) => {
|
|
@@ -84,12 +84,11 @@ export const registerWriteCommands = (program) => {
|
|
|
84
84
|
});
|
|
85
85
|
program
|
|
86
86
|
.command('server')
|
|
87
|
-
.option('-v, --vault <vault>', 'vault directory'
|
|
87
|
+
.option('-v, --vault <vault>', 'vault directory')
|
|
88
88
|
.option('-h, --host <host>', 'server host', '127.0.0.1')
|
|
89
89
|
.option('-p, --port <port>', 'server port', '4321')
|
|
90
90
|
.option('--no-index', 'skip indexing before starting the server')
|
|
91
91
|
.option('-w, --watch', 'watch markdown files and reindex on changes')
|
|
92
|
-
.option('--allow-public', 'allow binding the server to a non-loopback host')
|
|
93
92
|
.option('--json', 'print machine-readable JSON')
|
|
94
93
|
.description('start a local web UI for the knowledge graph')
|
|
95
94
|
.action(async (options) => {
|
|
@@ -99,8 +98,7 @@ export const registerWriteCommands = (program) => {
|
|
|
99
98
|
host: options.host ?? resolved.config.host,
|
|
100
99
|
port: parsePositiveInteger(options.port ?? String(resolved.config.port), resolved.config.port),
|
|
101
100
|
shouldIndex: options.index,
|
|
102
|
-
shouldWatch: Boolean(options.watch)
|
|
103
|
-
allowPublic: Boolean(options.allowPublic)
|
|
101
|
+
shouldWatch: Boolean(options.watch)
|
|
104
102
|
});
|
|
105
103
|
print(options.json, { url: server.url, watch: Boolean(options.watch), readonly: true }, () => `Brainlink graph server running at ${server.url}`);
|
|
106
104
|
});
|
package/dist/cli/main.js
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from 'commander';
|
|
3
|
-
import {
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
|
+
import { basename, dirname, join } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
4
6
|
import { registerReadCommands } from './commands/read-commands.js';
|
|
5
7
|
import { registerWriteCommands } from './commands/write-commands.js';
|
|
8
|
+
const readPackageVersion = () => {
|
|
9
|
+
const packagePath = join(dirname(fileURLToPath(import.meta.url)), '../../package.json');
|
|
10
|
+
const metadata = JSON.parse(readFileSync(packagePath, 'utf8'));
|
|
11
|
+
return metadata.version ?? '0.0.0';
|
|
12
|
+
};
|
|
6
13
|
const program = new Command();
|
|
7
14
|
const cliName = basename(process.argv[1] ?? 'brainlink');
|
|
8
15
|
const displayName = cliName === 'blink' ? 'blink' : 'brainlink';
|
|
@@ -11,7 +18,7 @@ program
|
|
|
11
18
|
.name(displayName)
|
|
12
19
|
.alias(aliasName)
|
|
13
20
|
.description('Local-first knowledge memory for agents')
|
|
14
|
-
.version(
|
|
21
|
+
.version(readPackageVersion());
|
|
15
22
|
registerWriteCommands(program);
|
|
16
23
|
registerReadCommands(program);
|
|
17
24
|
program.parseAsync().catch((error) => {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
2
|
import { resolve } from 'node:path';
|
|
3
|
+
import { getDefaultVaultPath } from './paths.js';
|
|
3
4
|
export const defaultBrainlinkConfig = {
|
|
4
|
-
vault:
|
|
5
|
+
vault: getDefaultVaultPath(),
|
|
5
6
|
host: '127.0.0.1',
|
|
6
7
|
port: 4321,
|
|
7
8
|
allowedVaults: [],
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { chmod, mkdir, readdir, readFile, stat, writeFile } from 'node:fs/promises';
|
|
2
2
|
import { dirname, extname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
3
|
+
import { resolvePath } from './paths.js';
|
|
3
4
|
const excludedDirectories = new Set(['.brainlink', '.git', 'node_modules', 'dist']);
|
|
4
5
|
const directoryMode = 0o700;
|
|
5
6
|
const fileMode = 0o600;
|
|
@@ -14,7 +15,7 @@ const walkMarkdownFiles = async (directory) => {
|
|
|
14
15
|
}));
|
|
15
16
|
return nested.flat();
|
|
16
17
|
};
|
|
17
|
-
export const resolveVaultPath = (vaultPath) =>
|
|
18
|
+
export const resolveVaultPath = (vaultPath) => resolvePath(vaultPath);
|
|
18
19
|
const isPathInside = (parent, child) => {
|
|
19
20
|
const path = relative(parent, child);
|
|
20
21
|
return path === '' || (!path.startsWith('..') && !isAbsolute(path));
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { homedir } from 'node:os';
|
|
2
|
+
import { isAbsolute, join, resolve } from 'node:path';
|
|
3
|
+
const defaultHomeDirectoryName = '.brainlink';
|
|
4
|
+
const defaultVaultDirectoryName = 'vault';
|
|
5
|
+
export const expandHomePath = (path) => path === '~' || path.startsWith('~/') ? join(homedir(), path.slice(2)) : path;
|
|
6
|
+
export const resolvePath = (path, cwd = process.cwd()) => {
|
|
7
|
+
const expandedPath = expandHomePath(path);
|
|
8
|
+
return isAbsolute(expandedPath) ? expandedPath : resolve(cwd, expandedPath);
|
|
9
|
+
};
|
|
10
|
+
export const getBrainlinkHomePath = () => {
|
|
11
|
+
const configuredHome = process.env.BRAINLINK_HOME?.trim();
|
|
12
|
+
return configuredHome ? resolvePath(configuredHome) : join(homedir(), defaultHomeDirectoryName);
|
|
13
|
+
};
|
|
14
|
+
export const getDefaultVaultPath = () => join(getBrainlinkHomePath(), defaultVaultDirectoryName);
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
const schemaVersion = 4;
|
|
2
|
+
const requiredTableColumns = {
|
|
3
|
+
documents: ['id', 'agent_id', 'title', 'path', 'content', 'tags_json', 'frontmatter_json', 'created_at', 'updated_at'],
|
|
4
|
+
chunks: ['id', 'document_id', 'ordinal', 'content', 'token_count', 'embedding_provider', 'embedding_json'],
|
|
5
|
+
chunks_fts: ['chunk_id', 'document_id', 'agent_id', 'title', 'content']
|
|
6
|
+
};
|
|
2
7
|
const getStoredSchemaVersion = (database) => {
|
|
3
8
|
const hasMetadata = database
|
|
4
9
|
.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'metadata'")
|
|
@@ -18,9 +23,17 @@ const dropDerivedSchema = (database) => {
|
|
|
18
23
|
DROP TABLE IF EXISTS documents;
|
|
19
24
|
`);
|
|
20
25
|
};
|
|
26
|
+
const getTableColumns = (database, tableName) => {
|
|
27
|
+
const rows = database.prepare(`SELECT name FROM pragma_table_info(?)`).all(tableName);
|
|
28
|
+
return rows.map((row) => row.name);
|
|
29
|
+
};
|
|
30
|
+
const hasCompatibleSchemaShape = (database) => Object.entries(requiredTableColumns).every(([tableName, requiredColumns]) => {
|
|
31
|
+
const columns = getTableColumns(database, tableName);
|
|
32
|
+
return columns.length === 0 || requiredColumns.every((column) => columns.includes(column));
|
|
33
|
+
});
|
|
21
34
|
export const createSchema = (database) => {
|
|
22
35
|
const storedSchemaVersion = getStoredSchemaVersion(database);
|
|
23
|
-
if (storedSchemaVersion > 0 && storedSchemaVersion < schemaVersion) {
|
|
36
|
+
if ((storedSchemaVersion > 0 && storedSchemaVersion < schemaVersion) || !hasCompatibleSchemaShape(database)) {
|
|
24
37
|
dropDerivedSchema(database);
|
|
25
38
|
}
|
|
26
39
|
database.exec(`
|
package/dist/mcp/main.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
import { createBrainlinkMcpServer } from './server.js';
|
|
4
|
+
const server = createBrainlinkMcpServer();
|
|
5
|
+
const transport = new StdioServerTransport();
|
|
6
|
+
server.connect(transport).catch((error) => {
|
|
7
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
8
|
+
console.error(message);
|
|
9
|
+
process.exitCode = 1;
|
|
10
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { addNoteInputSchema, addNoteTool, brokenLinksInputSchema, brokenLinksTool, contextInputSchema, contextTool, graphInputSchema, graphTool, indexInputSchema, indexTool, orphansInputSchema, orphansTool, searchInputSchema, searchTool, validateInputSchema, validateTool } from './tools.js';
|
|
6
|
+
const readPackageVersion = () => {
|
|
7
|
+
const packagePath = join(dirname(fileURLToPath(import.meta.url)), '../../package.json');
|
|
8
|
+
const metadata = JSON.parse(readFileSync(packagePath, 'utf8'));
|
|
9
|
+
return metadata.version ?? '0.0.0';
|
|
10
|
+
};
|
|
11
|
+
export const createBrainlinkMcpServer = () => {
|
|
12
|
+
const server = new McpServer({
|
|
13
|
+
name: 'brainlink',
|
|
14
|
+
title: 'Brainlink',
|
|
15
|
+
version: readPackageVersion(),
|
|
16
|
+
description: 'Local-first Markdown memory tools for AI agents.'
|
|
17
|
+
});
|
|
18
|
+
server.registerTool('brainlink_context', {
|
|
19
|
+
title: 'Build Brainlink Context',
|
|
20
|
+
description: 'Read indexed Brainlink memory for a task or question. This is read-only and does not create graph links.',
|
|
21
|
+
inputSchema: contextInputSchema
|
|
22
|
+
}, contextTool);
|
|
23
|
+
server.registerTool('brainlink_search', {
|
|
24
|
+
title: 'Search Brainlink Memory',
|
|
25
|
+
description: 'Search indexed Brainlink notes with FTS, semantic or hybrid retrieval.',
|
|
26
|
+
inputSchema: searchInputSchema
|
|
27
|
+
}, searchTool);
|
|
28
|
+
server.registerTool('brainlink_add_note', {
|
|
29
|
+
title: 'Add Brainlink Note',
|
|
30
|
+
description: 'Write durable Markdown memory, then reindex the vault. Include explicit [[wiki links]] for connected graph memory.',
|
|
31
|
+
inputSchema: addNoteInputSchema
|
|
32
|
+
}, addNoteTool);
|
|
33
|
+
server.registerTool('brainlink_index', {
|
|
34
|
+
title: 'Index Brainlink Vault',
|
|
35
|
+
description: 'Rebuild the local Brainlink index from Markdown notes.',
|
|
36
|
+
inputSchema: indexInputSchema
|
|
37
|
+
}, indexTool);
|
|
38
|
+
server.registerTool('brainlink_validate', {
|
|
39
|
+
title: 'Validate Brainlink Vault',
|
|
40
|
+
description: 'Validate indexed graph health, including broken links and orphan notes.',
|
|
41
|
+
inputSchema: validateInputSchema
|
|
42
|
+
}, validateTool);
|
|
43
|
+
server.registerTool('brainlink_graph', {
|
|
44
|
+
title: 'Read Brainlink Graph',
|
|
45
|
+
description: 'Read indexed graph nodes and wiki-link edges.',
|
|
46
|
+
inputSchema: graphInputSchema
|
|
47
|
+
}, graphTool);
|
|
48
|
+
server.registerTool('brainlink_broken_links', {
|
|
49
|
+
title: 'List Brainlink Broken Links',
|
|
50
|
+
description: 'List unresolved indexed wiki links.',
|
|
51
|
+
inputSchema: brokenLinksInputSchema
|
|
52
|
+
}, brokenLinksTool);
|
|
53
|
+
server.registerTool('brainlink_orphans', {
|
|
54
|
+
title: 'List Brainlink Orphans',
|
|
55
|
+
description: 'List indexed notes without incoming or outgoing graph links.',
|
|
56
|
+
inputSchema: orphansInputSchema
|
|
57
|
+
}, orphansTool);
|
|
58
|
+
return server;
|
|
59
|
+
};
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { getBrokenLinksReport, getOrphansReport, validateVault } from '../application/analyze-vault.js';
|
|
3
|
+
import { addNote } from '../application/add-note.js';
|
|
4
|
+
import { buildContextPackage } from '../application/build-context.js';
|
|
5
|
+
import { getGraph } from '../application/get-graph.js';
|
|
6
|
+
import { indexVault } from '../application/index-vault.js';
|
|
7
|
+
import { searchKnowledge } from '../application/search-knowledge.js';
|
|
8
|
+
import { sanitizeSearchMode } from '../infrastructure/config.js';
|
|
9
|
+
import { loadBrainlinkConfig } from '../infrastructure/config.js';
|
|
10
|
+
import { assertVaultAllowed } from '../infrastructure/file-system-vault.js';
|
|
11
|
+
const positiveInteger = (fallback) => z
|
|
12
|
+
.number()
|
|
13
|
+
.int()
|
|
14
|
+
.positive()
|
|
15
|
+
.optional()
|
|
16
|
+
.transform((value) => value ?? fallback);
|
|
17
|
+
const vaultInput = {
|
|
18
|
+
vault: z.string().min(1).optional().describe('Vault directory. Omit to use the configured Brainlink default vault.')
|
|
19
|
+
};
|
|
20
|
+
const agentInput = {
|
|
21
|
+
agent: z.string().min(1).optional().describe('Agent memory namespace. Omit to read shared/default indexed memory.')
|
|
22
|
+
};
|
|
23
|
+
const searchModeInput = {
|
|
24
|
+
mode: z.enum(['fts', 'semantic', 'hybrid']).optional().describe('Search mode. Defaults to the Brainlink config value.')
|
|
25
|
+
};
|
|
26
|
+
const resolveVault = async (vault) => {
|
|
27
|
+
const config = await loadBrainlinkConfig();
|
|
28
|
+
return assertVaultAllowed(vault ?? config.vault, config.allowedVaults);
|
|
29
|
+
};
|
|
30
|
+
const jsonResult = (value) => ({
|
|
31
|
+
content: [
|
|
32
|
+
{
|
|
33
|
+
type: 'text',
|
|
34
|
+
text: JSON.stringify(value, null, 2)
|
|
35
|
+
}
|
|
36
|
+
],
|
|
37
|
+
structuredContent: value
|
|
38
|
+
});
|
|
39
|
+
export const contextInputSchema = {
|
|
40
|
+
...vaultInput,
|
|
41
|
+
...agentInput,
|
|
42
|
+
...searchModeInput,
|
|
43
|
+
query: z.string().min(1).describe('Task or question to retrieve Brainlink context for.'),
|
|
44
|
+
limit: positiveInteger(12).describe('Maximum search results before context selection.'),
|
|
45
|
+
tokens: positiveInteger(2000).describe('Maximum estimated context tokens.')
|
|
46
|
+
};
|
|
47
|
+
export const searchInputSchema = {
|
|
48
|
+
...vaultInput,
|
|
49
|
+
...agentInput,
|
|
50
|
+
...searchModeInput,
|
|
51
|
+
query: z.string().min(1).describe('Search query.'),
|
|
52
|
+
limit: positiveInteger(10).describe('Maximum result count.')
|
|
53
|
+
};
|
|
54
|
+
export const addNoteInputSchema = {
|
|
55
|
+
...vaultInput,
|
|
56
|
+
title: z.string().min(1).describe('Markdown note title.'),
|
|
57
|
+
content: z
|
|
58
|
+
.string()
|
|
59
|
+
.min(1)
|
|
60
|
+
.describe('Durable Markdown memory. Include explicit [[wiki links]] and #tags when the memory should be connected.'),
|
|
61
|
+
agent: z.string().min(1).optional().default('shared').describe('Agent memory namespace. Defaults to shared.'),
|
|
62
|
+
allowSensitive: z.boolean().optional().default(false).describe('Allow content that looks like a secret.')
|
|
63
|
+
};
|
|
64
|
+
export const indexInputSchema = {
|
|
65
|
+
...vaultInput
|
|
66
|
+
};
|
|
67
|
+
export const validateInputSchema = {
|
|
68
|
+
...vaultInput,
|
|
69
|
+
...agentInput
|
|
70
|
+
};
|
|
71
|
+
export const graphInputSchema = {
|
|
72
|
+
...vaultInput,
|
|
73
|
+
...agentInput
|
|
74
|
+
};
|
|
75
|
+
export const brokenLinksInputSchema = {
|
|
76
|
+
...vaultInput,
|
|
77
|
+
...agentInput
|
|
78
|
+
};
|
|
79
|
+
export const orphansInputSchema = {
|
|
80
|
+
...vaultInput,
|
|
81
|
+
...agentInput
|
|
82
|
+
};
|
|
83
|
+
export const contextTool = async (input) => {
|
|
84
|
+
const vault = await resolveVault(input.vault);
|
|
85
|
+
const config = await loadBrainlinkConfig();
|
|
86
|
+
const mode = sanitizeSearchMode(input.mode, config.defaultSearchMode);
|
|
87
|
+
const contextPackage = await buildContextPackage(vault, input.query, input.limit, input.tokens, input.agent, mode);
|
|
88
|
+
return jsonResult({
|
|
89
|
+
vault,
|
|
90
|
+
agent: input.agent,
|
|
91
|
+
mode,
|
|
92
|
+
...contextPackage
|
|
93
|
+
});
|
|
94
|
+
};
|
|
95
|
+
export const searchTool = async (input) => {
|
|
96
|
+
const vault = await resolveVault(input.vault);
|
|
97
|
+
const config = await loadBrainlinkConfig();
|
|
98
|
+
const mode = sanitizeSearchMode(input.mode, config.defaultSearchMode);
|
|
99
|
+
const results = await searchKnowledge(vault, input.query, input.limit, input.agent, mode);
|
|
100
|
+
return jsonResult({
|
|
101
|
+
vault,
|
|
102
|
+
agent: input.agent,
|
|
103
|
+
query: input.query,
|
|
104
|
+
limit: input.limit,
|
|
105
|
+
mode,
|
|
106
|
+
results
|
|
107
|
+
});
|
|
108
|
+
};
|
|
109
|
+
export const addNoteTool = async (input) => {
|
|
110
|
+
const vault = await resolveVault(input.vault);
|
|
111
|
+
const path = await addNote(vault, input.title, input.content, input.agent, {
|
|
112
|
+
allowSensitive: input.allowSensitive
|
|
113
|
+
});
|
|
114
|
+
const index = await indexVault(vault);
|
|
115
|
+
return jsonResult({
|
|
116
|
+
vault,
|
|
117
|
+
title: input.title,
|
|
118
|
+
agent: input.agent,
|
|
119
|
+
path,
|
|
120
|
+
index
|
|
121
|
+
});
|
|
122
|
+
};
|
|
123
|
+
export const indexTool = async (input) => {
|
|
124
|
+
const vault = await resolveVault(input.vault);
|
|
125
|
+
const result = await indexVault(vault);
|
|
126
|
+
return jsonResult({
|
|
127
|
+
vault,
|
|
128
|
+
...result
|
|
129
|
+
});
|
|
130
|
+
};
|
|
131
|
+
export const validateTool = async (input) => {
|
|
132
|
+
const vault = await resolveVault(input.vault);
|
|
133
|
+
const validation = await validateVault(vault, input.agent);
|
|
134
|
+
return jsonResult({
|
|
135
|
+
vault,
|
|
136
|
+
agent: input.agent,
|
|
137
|
+
...validation
|
|
138
|
+
});
|
|
139
|
+
};
|
|
140
|
+
export const graphTool = async (input) => {
|
|
141
|
+
const vault = await resolveVault(input.vault);
|
|
142
|
+
const graph = await getGraph(vault, input.agent);
|
|
143
|
+
return jsonResult({
|
|
144
|
+
vault,
|
|
145
|
+
agent: input.agent,
|
|
146
|
+
...graph
|
|
147
|
+
});
|
|
148
|
+
};
|
|
149
|
+
export const brokenLinksTool = async (input) => {
|
|
150
|
+
const vault = await resolveVault(input.vault);
|
|
151
|
+
const brokenLinks = await getBrokenLinksReport(vault, input.agent);
|
|
152
|
+
return jsonResult({
|
|
153
|
+
vault,
|
|
154
|
+
agent: input.agent,
|
|
155
|
+
brokenLinks
|
|
156
|
+
});
|
|
157
|
+
};
|
|
158
|
+
export const orphansTool = async (input) => {
|
|
159
|
+
const vault = await resolveVault(input.vault);
|
|
160
|
+
const orphans = await getOrphansReport(vault, input.agent);
|
|
161
|
+
return jsonResult({
|
|
162
|
+
vault,
|
|
163
|
+
agent: input.agent,
|
|
164
|
+
orphans
|
|
165
|
+
});
|
|
166
|
+
};
|
package/docs/AGENT_USAGE.md
CHANGED
|
@@ -29,6 +29,18 @@ blink --help
|
|
|
29
29
|
|
|
30
30
|
Use `blink` as the short terminal alias and `brainlink` in documentation when explicit naming is more important.
|
|
31
31
|
|
|
32
|
+
## Default Vault
|
|
33
|
+
|
|
34
|
+
When `--vault` is omitted, Brainlink uses a user-level vault:
|
|
35
|
+
|
|
36
|
+
```txt
|
|
37
|
+
$HOME/.brainlink/vault
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`blink server` follows the same rule, so it serves the default Brainlink vault instead of the current working directory.
|
|
41
|
+
|
|
42
|
+
Use `--vault <path>` for a one-off custom vault, or set `vault` in `brainlink.config.json` / `.brainlink.json` for a workspace-level custom default. Set `BRAINLINK_HOME` when the whole Brainlink home directory should live somewhere else.
|
|
43
|
+
|
|
32
44
|
## Agent Namespaces
|
|
33
45
|
|
|
34
46
|
Each agent writes into a dedicated namespace under `agents/<agent-id>/`:
|
|
@@ -126,12 +138,47 @@ Rules:
|
|
|
126
138
|
- Prefer summaries over raw transcripts.
|
|
127
139
|
- Preserve dates when the timing matters.
|
|
128
140
|
|
|
141
|
+
## Linking Contract
|
|
142
|
+
|
|
143
|
+
Brainlink only builds graph edges from Markdown `[[wiki links]]`.
|
|
144
|
+
|
|
145
|
+
The `context` command is read-only. It retrieves indexed notes and returns a compact package for the model, but it does not write memory, create backlinks, infer relationships or modify the graph. If an agent reads context and then learns something durable, the agent must write a note with explicit links before that knowledge becomes connected memory.
|
|
146
|
+
|
|
147
|
+
Required write behavior:
|
|
148
|
+
|
|
149
|
+
1. Choose a clear title for the new note.
|
|
150
|
+
2. Look for an existing related concept with `search`, `links` or `backlinks`.
|
|
151
|
+
3. Add at least one `[[Existing Note Title]]` link unless the note is intentionally a root concept.
|
|
152
|
+
4. Add useful `#tags` for retrieval.
|
|
153
|
+
5. Run `index` after the write.
|
|
154
|
+
6. Run `validate`, `broken-links` or `orphans` when the graph should be connected.
|
|
155
|
+
|
|
156
|
+
Good linked note:
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
blink add "SQLite Index Rebuild" \
|
|
160
|
+
--agent coding-agent \
|
|
161
|
+
--content "Legacy derived indexes without agent columns are rebuilt because SQLite is disposable. Related: [[Architecture]], [[Agent Namespaces]]. #sqlite #architecture #decision"
|
|
162
|
+
blink index
|
|
163
|
+
blink validate --agent coding-agent
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Poor disconnected note:
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
blink add "SQLite Index Rebuild" \
|
|
170
|
+
--agent coding-agent \
|
|
171
|
+
--content "We rebuild old indexes now."
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
The poor note may be searchable, but it will not create graph links, backlinks or useful traversal paths.
|
|
175
|
+
|
|
129
176
|
## Read Policy
|
|
130
177
|
|
|
131
178
|
Before answering a memory-dependent question, run:
|
|
132
179
|
|
|
133
180
|
```bash
|
|
134
|
-
blink context "<question>" --
|
|
181
|
+
blink context "<question>" --agent coding-agent
|
|
135
182
|
```
|
|
136
183
|
|
|
137
184
|
Use the returned context as source-grounded memory.
|
|
@@ -139,7 +186,7 @@ Use the returned context as source-grounded memory.
|
|
|
139
186
|
For machine-readable output, use:
|
|
140
187
|
|
|
141
188
|
```bash
|
|
142
|
-
blink context "<question>" --
|
|
189
|
+
blink context "<question>" --agent coding-agent --json
|
|
143
190
|
```
|
|
144
191
|
|
|
145
192
|
If the context is empty or weak:
|
|
@@ -158,20 +205,18 @@ These examples assume the agent can run shell commands in the user workspace.
|
|
|
158
205
|
Run this at the start of a task:
|
|
159
206
|
|
|
160
207
|
```bash
|
|
161
|
-
export BLINK_VAULT=".brainlink-vault"
|
|
162
208
|
export BLINK_AGENT="codex"
|
|
163
|
-
blink init
|
|
164
|
-
blink context "$USER_TASK" --
|
|
209
|
+
blink init
|
|
210
|
+
blink context "$USER_TASK" --agent "$BLINK_AGENT" --mode hybrid --json
|
|
165
211
|
```
|
|
166
212
|
|
|
167
213
|
After discovering durable project knowledge:
|
|
168
214
|
|
|
169
215
|
```bash
|
|
170
216
|
blink add "Implementation Boundary" \
|
|
171
|
-
--vault "$BLINK_VAULT" \
|
|
172
217
|
--agent "$BLINK_AGENT" \
|
|
173
218
|
--content "Keep use cases in application and pure transformations in domain. [[Architecture]] #architecture #typescript"
|
|
174
|
-
blink index
|
|
219
|
+
blink index
|
|
175
220
|
```
|
|
176
221
|
|
|
177
222
|
### Claude Code-Style Agent
|
|
@@ -224,16 +269,19 @@ blink index --vault .brainlink-vault
|
|
|
224
269
|
### Initialize A Vault
|
|
225
270
|
|
|
226
271
|
```bash
|
|
272
|
+
blink init
|
|
227
273
|
blink init ./vault
|
|
228
274
|
```
|
|
229
275
|
|
|
230
276
|
Creates:
|
|
231
277
|
|
|
232
278
|
```txt
|
|
233
|
-
vault/
|
|
279
|
+
$HOME/.brainlink/vault/
|
|
234
280
|
.brainlink/
|
|
235
281
|
```
|
|
236
282
|
|
|
283
|
+
`blink init ./vault` creates a custom vault instead.
|
|
284
|
+
|
|
237
285
|
### Add A Note
|
|
238
286
|
|
|
239
287
|
```bash
|
|
@@ -344,11 +392,14 @@ shared: 30 documents
|
|
|
344
392
|
### Start Graph UI
|
|
345
393
|
|
|
346
394
|
```bash
|
|
395
|
+
blink server --host 127.0.0.1 --port 4321
|
|
347
396
|
blink server --vault ./vault --host 127.0.0.1 --port 4321
|
|
348
397
|
```
|
|
349
398
|
|
|
350
399
|
This starts a local frontend for inspecting the knowledge graph.
|
|
351
400
|
|
|
401
|
+
Without `--vault`, the graph UI serves `$HOME/.brainlink/vault`.
|
|
402
|
+
|
|
352
403
|
The frontend includes an agent selector. Selecting an agent calls the same read APIs with `agent=<agent-id>` and renders that namespace instead of merging every agent into one graph.
|
|
353
404
|
|
|
354
405
|
The command reindexes by default, then serves:
|
|
@@ -380,19 +431,38 @@ blink watch --vault ./vault
|
|
|
380
431
|
|
|
381
432
|
This process watches Markdown files and rebuilds the index after changes.
|
|
382
433
|
|
|
383
|
-
### Use From
|
|
434
|
+
### Use From MCP
|
|
384
435
|
|
|
385
|
-
Brainlink
|
|
436
|
+
Brainlink ships a stdio MCP server:
|
|
386
437
|
|
|
387
|
-
|
|
438
|
+
```bash
|
|
439
|
+
brainlink-mcp
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
Example MCP client configuration:
|
|
443
|
+
|
|
444
|
+
```json
|
|
445
|
+
{
|
|
446
|
+
"mcpServers": {
|
|
447
|
+
"brainlink": {
|
|
448
|
+
"command": "brainlink-mcp"
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
Available MCP tools:
|
|
388
455
|
|
|
389
|
-
- `brainlink_context
|
|
390
|
-
- `brainlink_search
|
|
391
|
-
- `brainlink_add_note
|
|
392
|
-
- `
|
|
393
|
-
- `brainlink_validate
|
|
456
|
+
- `brainlink_context`
|
|
457
|
+
- `brainlink_search`
|
|
458
|
+
- `brainlink_add_note`
|
|
459
|
+
- `brainlink_index`
|
|
460
|
+
- `brainlink_validate`
|
|
461
|
+
- `brainlink_graph`
|
|
462
|
+
- `brainlink_broken_links`
|
|
463
|
+
- `brainlink_orphans`
|
|
394
464
|
|
|
395
|
-
|
|
465
|
+
MCP clients can pass `vault` and `agent` arguments per tool call. Set `BRAINLINK_ALLOWED_VAULTS` when exposing Brainlink to an external agent process so a tool cannot pass arbitrary vault paths:
|
|
396
466
|
|
|
397
467
|
```bash
|
|
398
468
|
export BRAINLINK_ALLOWED_VAULTS="/absolute/path/to/project-vault"
|
|
@@ -437,6 +507,12 @@ Output:
|
|
|
437
507
|
|
|
438
508
|
Agents should include source paths in their reasoning or final answer when the user needs traceability.
|
|
439
509
|
|
|
510
|
+
Non-goals:
|
|
511
|
+
|
|
512
|
+
- `context` must not be treated as a write operation.
|
|
513
|
+
- Retrieved context must not be assumed to create graph edges.
|
|
514
|
+
- Backlinks are derived only from indexed `[[wiki links]]`.
|
|
515
|
+
|
|
440
516
|
## Operational Rules
|
|
441
517
|
|
|
442
518
|
- Re-run `index` after modifying notes.
|
|
@@ -445,6 +521,8 @@ Agents should include source paths in their reasoning or final answer when the u
|
|
|
445
521
|
- Do not manually edit the database.
|
|
446
522
|
- Keep generated context short enough for the target model.
|
|
447
523
|
- Prefer specific queries over broad queries.
|
|
524
|
+
- Write explicit `[[wiki links]]` when durable memory should be connected.
|
|
525
|
+
- Check `orphans` before assuming the graph is healthy.
|
|
448
526
|
|
|
449
527
|
## Failure Modes
|
|
450
528
|
|
|
@@ -472,6 +550,6 @@ Weak retrieval usually means:
|
|
|
472
550
|
|
|
473
551
|
- Search supports FTS, local semantic embeddings, SQLite semantic buckets and hybrid ranking.
|
|
474
552
|
- Local embeddings are deterministic and provider-free; remote embedding providers are not implemented yet.
|
|
475
|
-
- MCP integration is
|
|
553
|
+
- MCP integration is available through the `brainlink-mcp` stdio server.
|
|
476
554
|
- HTTP API is local and unauthenticated.
|
|
477
555
|
- Watch mode depends on platform filesystem watcher behavior.
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -167,20 +167,18 @@ HTTP request
|
|
|
167
167
|
|
|
168
168
|
The HTTP API is local-first and unauthenticated. It is meant for local agents, browser UI, and development workflows.
|
|
169
169
|
|
|
170
|
-
##
|
|
170
|
+
## MCP Flow
|
|
171
171
|
|
|
172
|
-
Brainlink
|
|
172
|
+
Brainlink includes a stdio MCP server for agent integrations.
|
|
173
173
|
|
|
174
174
|
```txt
|
|
175
175
|
MCP client
|
|
176
|
-
->
|
|
177
|
-
-> child_process execFile("blink", ["context", ..., "--json"])
|
|
178
|
-
-> Brainlink CLI
|
|
176
|
+
-> brainlink-mcp
|
|
179
177
|
-> application use case
|
|
180
|
-
->
|
|
178
|
+
-> MCP tool result
|
|
181
179
|
```
|
|
182
180
|
|
|
183
|
-
|
|
181
|
+
The MCP adapter stays thin. It validates tool inputs, resolves the configured vault and calls the same application use cases used by the CLI.
|
|
184
182
|
|
|
185
183
|
## Link Resolution
|
|
186
184
|
|
package/docs/RELEASE.md
CHANGED
|
@@ -32,19 +32,34 @@ blink context "release smoke" --vault ./tmp-vault --mode hybrid --json
|
|
|
32
32
|
blink server --vault ./tmp-vault --host 127.0.0.1 --port 4321
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
9. Verify the server refuses
|
|
35
|
+
9. Verify the server refuses public binds:
|
|
36
36
|
|
|
37
37
|
```bash
|
|
38
38
|
blink server --vault ./tmp-vault --host 0.0.0.0
|
|
39
39
|
```
|
|
40
40
|
|
|
41
41
|
10. Confirm no test/demo vault files are included in the package tarball.
|
|
42
|
-
11.
|
|
43
|
-
12.
|
|
42
|
+
11. Confirm the repository has an `NPM_TOKEN` secret with publish permission for `@andespindola/brainlink`.
|
|
43
|
+
12. Create the git tag only after the package name is final.
|
|
44
|
+
13. Publish from GitHub Actions by publishing a GitHub Release for the tag.
|
|
44
45
|
|
|
45
46
|
## Publish Commands
|
|
46
47
|
|
|
47
|
-
|
|
48
|
+
The preferred path is the `Publish npm` GitHub Actions workflow:
|
|
49
|
+
|
|
50
|
+
- Push to `main`: runs checks, pack smoke, then publishes the package to npm with `latest` when `package.json` contains a version that is not already published.
|
|
51
|
+
- GitHub Release `published`: runs checks, pack smoke, then publishes to npm with provenance.
|
|
52
|
+
- Manual `workflow_dispatch`: runs a dry run by default. Disable `dry_run` only for an intentional manual publish.
|
|
53
|
+
- Manual `workflow_dispatch` accepts an optional `dist_tag` override. Use `latest` only when the default npm install command should resolve to that version.
|
|
54
|
+
- Prerelease versions publish under their prerelease dist-tag, for example `0.1.0-alpha.1` publishes with `--tag alpha`.
|
|
55
|
+
|
|
56
|
+
On `main`, the publish job checks npm before publishing. If the version already exists, it automatically bumps the package inside the runner to the next available version before checks, packing and publishing. For example, `0.1.0-alpha.4` becomes `0.1.0-alpha.5`.
|
|
57
|
+
|
|
58
|
+
The automatic bump is intentionally not pushed back to `main`. The branch stays protected, and npm remains the source of truth for the latest published package version.
|
|
59
|
+
|
|
60
|
+
Manual and GitHub Release publishes do not auto-bump. If their version already exists, they skip `npm publish` because npm versions are immutable.
|
|
61
|
+
|
|
62
|
+
For emergency local publishing of scoped public packages:
|
|
48
63
|
|
|
49
64
|
```bash
|
|
50
65
|
npm publish --access public
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@andespindola/brainlink",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.10",
|
|
4
4
|
"description": "Local-first knowledge memory for agents with Markdown, backlinks, indexing and context retrieval.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -25,10 +25,12 @@
|
|
|
25
25
|
],
|
|
26
26
|
"bin": {
|
|
27
27
|
"brainlink": "dist/cli/main.js",
|
|
28
|
-
"blink": "dist/cli/main.js"
|
|
28
|
+
"blink": "dist/cli/main.js",
|
|
29
|
+
"brainlink-mcp": "dist/mcp/main.js"
|
|
29
30
|
},
|
|
30
31
|
"files": [
|
|
31
32
|
"dist",
|
|
33
|
+
"assets",
|
|
32
34
|
"README.md",
|
|
33
35
|
"LICENSE",
|
|
34
36
|
"CHANGELOG.md",
|
|
@@ -44,6 +46,7 @@
|
|
|
44
46
|
"clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
|
|
45
47
|
"build": "npm run clean && tsc -p tsconfig.json",
|
|
46
48
|
"dev": "tsx src/cli/main.ts",
|
|
49
|
+
"dev:mcp": "tsx src/mcp/main.ts",
|
|
47
50
|
"test": "vitest run --config vitest.config.ts",
|
|
48
51
|
"check": "npm run build && npm run test",
|
|
49
52
|
"benchmark:large": "tsx src/benchmarks/large-vault.ts",
|
|
@@ -51,8 +54,10 @@
|
|
|
51
54
|
"pack:smoke": "npm pack --dry-run"
|
|
52
55
|
},
|
|
53
56
|
"dependencies": {
|
|
57
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
54
58
|
"better-sqlite3": "^12.9.0",
|
|
55
|
-
"commander": "^14.0.2"
|
|
59
|
+
"commander": "^14.0.2",
|
|
60
|
+
"zod": "^4.3.6"
|
|
56
61
|
},
|
|
57
62
|
"devDependencies": {
|
|
58
63
|
"@types/better-sqlite3": "^7.6.13",
|