brainllm 12.7.1 → 12.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -12
- package/SECURITY.md +4 -2
- package/dist/index.js +670 -111
- package/manifest.json +4 -4
- package/package.json +5 -4
- package/server.json +5 -5
- package/skills/brainllm/SKILL.md +10 -8
- package/skills/brainllm/references/troubleshooting.md +4 -0
package/manifest.json
CHANGED
|
@@ -2,22 +2,22 @@
|
|
|
2
2
|
"manifest_version": "0.3",
|
|
3
3
|
"name": "brainllm",
|
|
4
4
|
"display_name": "BrainLLM",
|
|
5
|
-
"version": "12.
|
|
6
|
-
"description": "
|
|
5
|
+
"version": "12.8.0",
|
|
6
|
+
"description": "Self-hosted memory for LLMs in TriliumNext, structured by the server and editable by you.",
|
|
7
7
|
"long_description": "BrainLLM is an MCP server that organises memory into five purpose-built areas (Master, LLM, Memory, Knowledge, Insights). The model supplies content while the server owns placement, format, lifecycle, dates and backups. Includes a full raw ETAPI surface for advanced use. Requires a running TriliumNext instance and the Bun runtime.",
|
|
8
8
|
"author": {
|
|
9
9
|
"name": "Kevin Miiso",
|
|
10
10
|
"email": "miisodev@gmail.com",
|
|
11
11
|
"url": "https://github.com/miisodev"
|
|
12
12
|
},
|
|
13
|
-
"homepage": "https://github.
|
|
13
|
+
"homepage": "https://miisodev.github.io/BrainLLM/",
|
|
14
14
|
"repository": {
|
|
15
15
|
"type": "git",
|
|
16
16
|
"url": "https://github.com/miisodev/BrainLLM.git"
|
|
17
17
|
},
|
|
18
18
|
"support": "https://github.com/miisodev/BrainLLM/issues",
|
|
19
19
|
"license": "MIT",
|
|
20
|
-
"keywords": ["mcp", "trilium", "trilium-next", "
|
|
20
|
+
"keywords": ["mcp", "trilium", "trilium-next", "knowledge-graph", "memory", "claude"],
|
|
21
21
|
"icon": "public/BrainLLM.png",
|
|
22
22
|
"server": {
|
|
23
23
|
"type": "binary",
|
package/package.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brainllm",
|
|
3
|
-
"version": "12.
|
|
3
|
+
"version": "12.8.0",
|
|
4
4
|
"mcpName": "io.github.miisodev/brainllm",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "Self-hosted memory for LLMs in TriliumNext, structured by the server and editable by you.",
|
|
6
6
|
"author": "Kevin Miiso Novo <miisodev@gmail.com>",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
10
|
"url": "https://github.com/miisodev/BrainLLM.git"
|
|
11
11
|
},
|
|
12
|
-
"homepage": "https://github.
|
|
12
|
+
"homepage": "https://miisodev.github.io/BrainLLM/",
|
|
13
13
|
"bugs": {
|
|
14
14
|
"url": "https://github.com/miisodev/BrainLLM/issues"
|
|
15
15
|
},
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"model-context-protocol",
|
|
23
23
|
"trilium",
|
|
24
24
|
"trilium-next",
|
|
25
|
-
"
|
|
25
|
+
"memory",
|
|
26
26
|
"knowledge-graph",
|
|
27
27
|
"llm",
|
|
28
28
|
"claude",
|
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
"test": "bun run src/test.ts",
|
|
55
55
|
"test:unit": "bun test",
|
|
56
56
|
"test:http": "bun run scripts/http-smoke.ts",
|
|
57
|
+
"test:live": "bun run scripts/live-smoke.ts",
|
|
57
58
|
"init": "bun run src/init.ts",
|
|
58
59
|
"icons": "bun run scripts/icons.ts",
|
|
59
60
|
"validate:release": "bun run scripts/validate-release.ts",
|
package/server.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
3
|
"name": "io.github.miisodev/brainllm",
|
|
4
|
-
"description": "
|
|
5
|
-
"version": "12.
|
|
6
|
-
"websiteUrl": "https://github.
|
|
4
|
+
"description": "Self-hosted memory for LLMs in TriliumNext, structured by the server and editable by you.",
|
|
5
|
+
"version": "12.8.0",
|
|
6
|
+
"websiteUrl": "https://miisodev.github.io/BrainLLM/",
|
|
7
7
|
"repository": {
|
|
8
8
|
"url": "https://github.com/miisodev/BrainLLM",
|
|
9
9
|
"source": "github"
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
{
|
|
13
13
|
"registryType": "npm",
|
|
14
14
|
"identifier": "brainllm",
|
|
15
|
-
"version": "12.
|
|
15
|
+
"version": "12.8.0",
|
|
16
16
|
"transport": {
|
|
17
17
|
"type": "stdio"
|
|
18
18
|
},
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
},
|
|
40
40
|
{
|
|
41
41
|
"name": "BRAINLLM_MODE",
|
|
42
|
-
"description": "core (default) exposes the
|
|
42
|
+
"description": "core (default) exposes the 45 brain-aware tools; full additionally exposes the 33 raw ETAPI tools.",
|
|
43
43
|
"isRequired": false,
|
|
44
44
|
"format": "string",
|
|
45
45
|
"default": "core"
|
package/skills/brainllm/SKILL.md
CHANGED
|
@@ -30,13 +30,13 @@ DURING remember(...) the moment something is worth keeping
|
|
|
30
30
|
resolve / withdraw / recover thread and note lifecycle
|
|
31
31
|
connect(...) wire a real relation when you see one
|
|
32
32
|
read: <surface>, domain, recall, read(ids), outline, inspect, assembly, brain
|
|
33
|
-
verify: consistency(...), claim(...), diff(since="today")
|
|
33
|
+
verify: consistency(...), claim(...), diff(since="today"), health()
|
|
34
34
|
END session() → [update singletons] → addendum() → maintain() → remarks() → diary() → close()
|
|
35
35
|
```
|
|
36
36
|
|
|
37
37
|
`start()` returns the date, **preferences and protocols in full**, the other singletons as section headings (pull one with `master(which)`/`llm(which)`, `section=` for one section; `depth="full"` inlines all), today's diary and session ids, active and dormant threads, the previous session and notes changed since. `newDay: true` on the first session of a day → call `day()` (previous session, its log, notes touched since). `day(recap=true)` returns everything written today, in order, across sessions, diary and thread entries.
|
|
38
38
|
|
|
39
|
-
**Close protocol.** `session()` returns singleton stubs, `pending=` (what each remaining step has to do), `audit=` (do the singletons agree, and do the LLM ones still serve the master ones) and `next[]`. Update master singletons with what you learned about the user and LLM singletons with what you learned about yourself, fold any addenda (`addendum()`), `maintain()`, `remarks()` for diary cues, `diary()` your closing record, then `close(summary, identity)`. `close()` refuses until those steps ran and `session → remarks → diary` held; the gate is durable across restarts. `force=true` bypasses a step with nothing to do (reported). `continuing=true` is a second close on the same day. Scoped or autonomous runs pass `session(scope="agent")`.
|
|
39
|
+
**Close protocol.** `session()` returns singleton stubs, `changedToday` (the maintained notes this day's writes touched: check every figure you changed agrees everywhere it is recorded, with `diff(since="today")` and `consistency()`), `pending=` (what each remaining step has to do), `audit=` (do the singletons agree, and do the LLM ones still serve the master ones) and `next[]`. Update master singletons with what you learned about the user and LLM singletons with what you learned about yourself, fold any addenda (`addendum()`), `maintain()`, `remarks()` for diary cues, `diary()` your closing record, then `close(summary, identity)`. `close()` refuses until those steps ran and `session → remarks → diary` held; the gate is durable across restarts. `force=true` bypasses a step with nothing to do (reported). `continuing=true` is a second close on the same day. Scoped or autonomous runs pass `session(scope="agent")`.
|
|
40
40
|
|
|
41
41
|
**Write during the session, not at the end** — a fact written mid-conversation survives a crash.
|
|
42
42
|
|
|
@@ -89,13 +89,14 @@ worth keeping?
|
|
|
89
89
|
| Need | Tool |
|
|
90
90
|
|---|---|
|
|
91
91
|
| a singleton | `master(which)` / `llm(which)` — `section=` for one section |
|
|
92
|
-
| a thread or session | `memory(id)` — dated index (newest days, up to 30 and ~24k characters; `limit=` for more) or collection entries; `date=` for one day; `index=true` for ids, dates and block markers only |
|
|
92
|
+
| a thread or session | `memory(id)` — dated index (newest days, up to 30 and ~24k characters; `limit=` for more) or collection entries; `date=` for one day; `index=true` for ids, dates and block markers only; `block="14:05"` (or `"-1"`, the newest) for one addendum of a session or day entry — `llm("diary", id, block=)` likewise |
|
|
93
93
|
| a knowledge note | `knowledge(id)` — `section=` for one section |
|
|
94
94
|
| a day's change log | `insights(date?)` |
|
|
95
95
|
| skim a surface | `<surface>_recall` |
|
|
96
96
|
| everything about an area | `domain(name)` — the reliable path; reach here first |
|
|
97
97
|
| search | `recall(query, domain=…)` — scope it; `regex=` for structure; fuzzy hits are leads |
|
|
98
|
-
| several bodies at once | `read(ids=[…])` (≤ 10) |
|
|
98
|
+
| several bodies at once | `read(ids=[…])` (≤ 10). Bodies stop at ~80k characters; the rest come back in `deferred[]` for a second call. `text=true` returns readable plain text for orienting (not for building a `find=` anchor) |
|
|
99
|
+
| storage and growth | `health()` — Trilium version, estimated database size, heaviest notes and their revisions, named backups |
|
|
99
100
|
| what the brain holds | `assembly(area?)` — titles by surface with purposes |
|
|
100
101
|
| inventory / locate by id | `brain()` — read `parent`, not position |
|
|
101
102
|
| a note's heading tree | `outline(id)` — before any `section=` edit you're unsure of |
|
|
@@ -146,15 +147,16 @@ Read `template(kind)` before your first write of a kind, then read a sibling.
|
|
|
146
147
|
| `section=` + `mode="before"/"after"` | insert a sibling block around the whole section |
|
|
147
148
|
| `section=` + `mode="prepend"` | insert at the top of the section's body |
|
|
148
149
|
| `section=` + `mode="remove"` | delete the section |
|
|
149
|
-
| `find="<
|
|
150
|
+
| `find="<text>"` | replace every occurrence (`nth=` for one) — exact first, then through re-serialized tags, then ignoring inline formatting (`matchMode` says which) |
|
|
150
151
|
| `edits=[{find, body}]` | several surgeries in one read and one write |
|
|
151
152
|
| `find="<short anchor>"` + `within="tr"` | act on the element containing the anchor: replace it (default), `mode="before"/"after"` to insert a row beside it, `mode="remove"` to delete it (`li`, `p`, `td` … too) |
|
|
153
|
+
| `domain="<name>"` + `find=`/`edits=` (no noteId) | the same find/replace across every maintained note in a domain; a dry run listing each match unless `dryRun=false`. Records are never touched. `dryRun` anywhere else is refused |
|
|
152
154
|
|
|
153
155
|
- **Tables are edited by row.** Anchor on a few words unique to the row and pass `within="tr"`; never copy a whole stored row into `find=`. An anchor in several rows is refused with previews; `nth=` picks one.
|
|
154
156
|
- A replacement equal to its match writes nothing and reports `unchanged`.
|
|
155
157
|
- A revision is taken before every content write; `diff(noteId)` shows what the last write changed, and `diff(since="today")` reviews every note you changed today in one call — run it before closing.
|
|
156
158
|
- **Check the receipt.** `matched: false` means a new section was written — `available[]` lists real headings and `didYouMean` catches typos; `strict=true` refuses instead. `headingCount > 1` means only the first match was touched. `replacedSubsections[]` names nested headings a section replace took with it.
|
|
157
|
-
- **`find=`
|
|
159
|
+
- **`find=` prefers stored HTML.** Plain text also matches through inline formatting (`<strong>`, `<code>`, `<em>`, entities), so a short anchor rarely needs its markup; a span that opens or closes formatting is widened to keep it balanced. Block tags still match literally; `outline()` gives the `raw` form of headings. On a miss the hint names the cause and shows the stored text nearby.
|
|
158
160
|
- A section replace swaps everything under the heading — use `find=` for anything smaller.
|
|
159
161
|
- **Oversized note (past the read ceiling)?** It usually holds two subjects: `split(noteId, sections=[…], into="<title>")` moves whole sections to a new sibling and leaves a pointer.
|
|
160
162
|
- Concurrent writers are serialised server-side; reads stay parallel.
|
|
@@ -191,7 +193,7 @@ Lite runs inside `start`/`close` (thread aging, label checks). `maintain(deep=tr
|
|
|
191
193
|
|---|---|
|
|
192
194
|
| stale | revise, resolve, or `ack=[id]` if correct as it stands |
|
|
193
195
|
| orphan / sink | `connect()` |
|
|
194
|
-
| dated prose | a timeless note carrying dates — move state to Current State, history to a thread entry |
|
|
196
|
+
| dated prose | a timeless note carrying dates — move state to Current State, history to a thread entry. A register (a backlog, a defects list) whose rows carry evidence dates takes `label(id, "register", value="")`, which exempts its tables but not its prose |
|
|
195
197
|
| duplicate heading / unbalanced tags / incomplete | fix with `revise` |
|
|
196
198
|
| oversized / section-edit-risk / size trajectory | `section=` reads, `split()` |
|
|
197
199
|
| dated / long title | retitle, fold into the note it should have updated, or split |
|
|
@@ -208,7 +210,7 @@ Lite runs inside `start`/`close` (thread aging, label checks). `maintain(deep=tr
|
|
|
208
210
|
|
|
209
211
|
## Other tools
|
|
210
212
|
|
|
211
|
-
`attach`/`detach` — raw artifacts on a note. `backup(name)` — milestone snapshot (close already backs up into a rolling weekday slot; every named backup is a full database copy kept on Trilium's volume, so name them sparingly). `bootstrap()` — create or refresh the structure. `template(kind)` — the canonical skeleton. **Full mode** (`BRAINLLM_MODE=full`) adds raw ETAPI tools with none of the server's guarantees. Read `references/fullmode.md` before any raw work.
|
|
213
|
+
`attach`/`detach` — raw artifacts on a note. `backup(name)` — milestone snapshot (close already backs up into a rolling weekday slot; every named backup is a full database copy kept on Trilium's volume, so name them sparingly; `health()` lists the named ones). `health()` — storage health: estimated database size, revision-heavy notes, named backups, with flags on growth. `bootstrap()` — create or refresh the structure. `template(kind)` — the canonical skeleton. **Full mode** (`BRAINLLM_MODE=full`) adds raw ETAPI tools with none of the server's guarantees. Read `references/fullmode.md` before any raw work.
|
|
212
214
|
|
|
213
215
|
## Quick-fix
|
|
214
216
|
|
|
@@ -28,6 +28,10 @@
|
|
|
28
28
|
|---|---|
|
|
29
29
|
| BrainLLM tools time out or return connection errors | The backend is unreachable, not the tool — every ETAPI call is bounded at 30 s with one retry on idempotent reads, so a hang surfaces as a failed tool call rather than a stuck session. Check the instance `TRILIUM_BASE_URL` names: a hosted deploy should answer at its own URL, a local desktop install needs the Trilium app running. Then retry. |
|
|
30
30
|
| `start` → `uninitialized` | `bootstrap` |
|
|
31
|
+
| A result ends with `[BrainLLM: … cut at 140,000 to fit the client's limit]` | The result passed the server's size cap. Narrow it: `section=`, `block=`, `limit=`, or fewer `ids=` |
|
|
32
|
+
| `revise` refuses `dryRun=` | `dryRun` exists only for `revise(domain=)`. To preview one note's edit, read the note (or `outline()`) first; `diff(noteId)` shows what a write changed |
|
|
33
|
+
| `read(ids=[…])` returns `deferred[]` | The bodies passed ~80k characters together; read the deferred ids in a second call, or pass `text=true` |
|
|
34
|
+
| `health()` flags a note's revisions | Revision history is most of the database's weight. Splitting the note, or tightening Trilium's revision-snapshot interval, bounds it |
|
|
31
35
|
| Deep maintenance flags the same items every session | Act on them, or `maintain(ack=[…])` the ones you reviewed and found correct |
|
|
32
36
|
| `revise(find=)` returns `replaced: 0` | Read the hint — it names the cause. Escaped search string (`<h3>` against stored real tags): pass the tag literally. Spans a block boundary (`</h3>` then `<ol>`): anchor inside one element, or use `section=` with `mode="before"/"after"`. Otherwise it was already replaced, or the text genuinely differs — check with `inspect(noteId, find="<shorter substring>")` |
|
|
33
37
|
| `revise(section=)` returns `matched: false` | No heading matched, so a NEW section was written — at the note's own level, with the note's real headings in `available[]`. Re-target from that list; `outline(noteId)` gives the same tree plus levels and occurrence indices before you write |
|