brainllm 13.0.0 → 13.1.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 +6 -2
- package/SECURITY.md +3 -1
- package/dist/index.js +4402 -2220
- package/manifest.json +1 -1
- package/package.json +2 -2
- package/public/BrainLLM.png +0 -0
- package/public/favicon.ico +0 -0
- package/public/icon-128.png +0 -0
- package/public/icon-180.png +0 -0
- package/public/icon-512.png +0 -0
- package/public/icon-64.png +0 -0
- package/server.json +2 -2
- package/skills/brainllm/SKILL.md +6 -5
- package/skills/brainllm/references/troubleshooting.md +4 -1
package/manifest.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"manifest_version": "0.3",
|
|
3
3
|
"name": "brainllm",
|
|
4
4
|
"display_name": "BrainLLM",
|
|
5
|
-
"version": "13.
|
|
5
|
+
"version": "13.1.1",
|
|
6
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": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brainllm",
|
|
3
|
-
"version": "13.
|
|
3
|
+
"version": "13.1.1",
|
|
4
4
|
"mcpName": "io.github.miisodev/brainllm",
|
|
5
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>",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
},
|
|
64
64
|
"dependencies": {
|
|
65
65
|
"@modelcontextprotocol/sdk": "^1.32.0",
|
|
66
|
-
"zod": "^
|
|
66
|
+
"zod": "^4.6.6"
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
69
69
|
"@types/bun": "latest",
|
package/public/BrainLLM.png
CHANGED
|
Binary file
|
|
Binary file
|
package/public/icon-128.png
CHANGED
|
Binary file
|
package/public/icon-180.png
CHANGED
|
Binary file
|
package/public/icon-512.png
CHANGED
|
Binary file
|
package/public/icon-64.png
CHANGED
|
Binary file
|
package/server.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
3
|
"name": "io.github.miisodev/brainllm",
|
|
4
4
|
"description": "Self-hosted memory for LLMs in TriliumNext, structured by the server and editable by you.",
|
|
5
|
-
"version": "13.
|
|
5
|
+
"version": "13.1.1",
|
|
6
6
|
"websiteUrl": "https://miisodev.github.io/BrainLLM/",
|
|
7
7
|
"repository": {
|
|
8
8
|
"url": "https://github.com/miisodev/BrainLLM",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
{
|
|
13
13
|
"registryType": "npm",
|
|
14
14
|
"identifier": "brainllm",
|
|
15
|
-
"version": "13.
|
|
15
|
+
"version": "13.1.1",
|
|
16
16
|
"transport": {
|
|
17
17
|
"type": "stdio"
|
|
18
18
|
},
|
package/skills/brainllm/SKILL.md
CHANGED
|
@@ -151,17 +151,18 @@ Read `template(kind)` before your first write of a kind, then read a sibling.
|
|
|
151
151
|
| `find="<text>"` | replace every occurrence (`nth=` for one) — exact first, then through re-serialized tags, then ignoring inline formatting (`matchMode` says which) |
|
|
152
152
|
| `edits=[{find, body}]` | several surgeries in one read and one write |
|
|
153
153
|
| `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). `within` names what is replaced: `"tr"` takes a whole `<tr>…</tr>` body, `"td"` one `<td>…</td>`; a plain `find=` inside a cell replaces only that text |
|
|
154
|
-
| `within=` + `dryRun=true` | preview the element first: its full visible extent and byte size, plus the before/after shape (for a `closure=`, what would be appended or moved) — nothing written, no revision.
|
|
155
|
-
| `within="tr"` + `mode="remove"` + `closure=` | close a register row in one call: `{thread, body}` sends the row, quoted, into that thread's entry for today (needs `identity=`); `{note, table?}` moves the
|
|
154
|
+
| `within=` + `dryRun=true` | preview the element first: its full visible extent and byte size, plus the before/after shape (for a `closure=`, what would be appended or moved) — nothing written, no revision. `find=` and `edits=` also support `dryRun=true` using the same replacement and sanitizer path; metadata changes cannot be combined with a preview. `domain=` previews by default. Unsupported preview modes are refused |
|
|
155
|
+
| `within="tr"` + `mode="remove"` + `closure=` | close a register row in one call: `{thread, body}` sends the row, quoted, into that thread's entry for today (needs `identity=`); `{note, table?, row?}` moves the row into another register. Column counts must match exactly; `row=["cell", …]` provides explicitly shaped plain-text destination cells. Name `table` when several tables exist. Destination content lands before source removal; same-note moves use one write. On a partial failure, read both notes before retrying; exact destination rows and identical dated blocks are deduplicated. Run `consistency()` after on any fact the row asserted |
|
|
156
156
|
| `append={table, row}` | append one row to the table under that heading with no anchor to locate first — pipes are cell boundaries, a lone placeholder row is replaced; composes with nothing else |
|
|
157
|
+
| `moves=[{find, nth?, row?}]` + `within="tr"`, `mode="remove"`, `closure=` | batch row closures to one target; all rows are preflighted, then applied in order. `dryRun=true` previews without writes. Stops on failure with completed and remaining rows; it is not an atomic transaction |
|
|
157
158
|
| `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 |
|
|
158
159
|
|
|
159
160
|
- **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=`. The row's visible text works too (`"Phase 2 | Claude | Open"`), and quotes match in any form. An anchor in several rows is refused with previews; `nth=` picks one. Write one `<td>` per column — a row whose cells disagree with its header comes back as `tableShape`.
|
|
160
161
|
- **"Last updated" lines are server-owned.** Every content write bumps them (the receipt says `lastUpdated`); never `find=` on one.
|
|
161
162
|
- A replacement equal to its match writes nothing and reports `unchanged`.
|
|
162
|
-
- 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.
|
|
163
|
+
- A failed revision snapshot stops the content overwrite. System errors are not success receipts. 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.
|
|
163
164
|
- **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. A record append (`remember`/`revise`/`closure`) names where it landed: `entryMarker` (the `Addendum — HH:mm` block heading written) and `runHeading` (the identity heading, when the body opened with one).
|
|
164
|
-
- **`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 in every shape: drifted text shows the stored text nearby (`storedNearby`), `nth=` past the last occurrence says so, and an all-miss `edits=` returns a hint per edit.
|
|
165
|
+
- **`find=` replaces text; it does not insert beside an anchor.** To insert, use `within=` with `mode="before"/"after"`, or `section=` with an insertion mode. **`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 in every shape: drifted text shows the stored text nearby (`storedNearby`), `nth=` past the last occurrence says so, and an all-miss `edits=` returns a hint per edit.
|
|
165
166
|
- A section replace swaps everything under the heading — use `find=` for anything smaller.
|
|
166
167
|
- **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.
|
|
167
168
|
- Concurrent writers are serialised server-side; reads stay parallel.
|
|
@@ -185,7 +186,7 @@ Read `template(kind)` before your first write of a kind, then read a sibling.
|
|
|
185
186
|
|
|
186
187
|
## Verification — `consistency` and `claim`
|
|
187
188
|
|
|
188
|
-
- **`consistency(pattern | subject)`** — does the brain agree with itself? A regex with one capture group (or a fact in prose) returns every asserting note grouped by value. Values one note asserts together — two true figures in one cell — are that note's **compound fact**, not a contradiction; only values no note co-asserts flag as a disagreement. **Run it after correcting any fact that could be recorded in more than one place.** `staleAfterDays=N` reports values held in only one note untouched N+ days. Records (sessions, diary, logs, dated thread entries) are skipped because they are never rewritten; `includeRecords=true` brings them back. Without a capture group the whole match is the value; matching ignores case unless `caseSensitive=true`; `patterns=[…]` checks several facts over one read of each note.
|
|
189
|
+
- **`consistency(pattern | subject)`** — does the brain agree with itself? A regex with one capture group (or a fact in prose) returns every asserting note grouped by value. Values one note asserts together — two true figures in one cell — are that note's **compound fact**, not a contradiction; only values no note co-asserts flag as a disagreement. **Run it after correcting any fact that could be recorded in more than one place.** `staleAfterDays=N` reports values held in only one note untouched N+ days. Records (sessions, diary, logs, dated thread entries) are skipped because they are never rewritten; `includeRecords=true` brings them back. Without a capture group the whole match is the value; matching ignores case unless `caseSensitive=true`; `patterns=[…]` checks several facts over one read of each note. The default examines up to 5,000 eligible notes from 10,000 candidates. Always read `coverage`: `complete=false`, `truncated` or `failed` means the scan cannot establish absence or unanimity. Narrow the scope, increase `limit` or retry failures. `fast=true` is explicitly lossy. Case-insensitive equality groups spelling variants while returning their original `spellings`. Backend search failures propagate instead of appearing empty.
|
|
189
190
|
- **`claim(...)`** — does the brain still agree with the world? Register `assertion` + `check`; verify with `claimId` + `holds` + `evidence` (evidence required); read with `claimId`; list with no arguments. `retire=true` composes with a verification — `claim(id, holds=false, evidence=…, retire=true)` marks it broken with the evidence and archives it in one call (`retired: true`); alone it just retires. BrainLLM never runs the check — you do. Register claims that would be expensive to discover had gone stale.
|
|
190
191
|
|
|
191
192
|
---
|
|
@@ -29,7 +29,10 @@
|
|
|
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
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=` |
|
|
32
|
+
| `revise` refuses `dryRun=` | Preview uses `find=`, `edits=`, `within=` or `moves=`; domain edits preview by default. Other modes refuse before writing. Do not combine title/icon changes with a preview |
|
|
33
|
+
| `consistency` is incomplete | Inspect `coverage.failed`, `truncated` and the candidate limit. Narrow domain/kinds or raise `limit`; retry failed reads. A bounded or lossy scan cannot establish absence or unanimity |
|
|
34
|
+
| A register closure partially failed | Read both source and destination. Destination-first ordering preserves the source until the destination is durable; exact rows and identical thread blocks deduplicate on retry. A lost response can follow an accepted write; never assume no mutation |
|
|
35
|
+
| Register destination shape or table ambiguous | Name `closure.table`, and supply exact plain-text destination cells with `closure.row`. Padding is not inferred |
|
|
33
36
|
| `read(ids=[…])` returns `deferred[]` | The bodies passed ~80k characters together. Every returned entry carries `chars`, so pick by size; read the deferred ids in a second call, `section=` for the huge ones, or pass `text=true` |
|
|
34
37
|
| `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 |
|
|
35
38
|
| Deep maintenance flags the same items every session | Act on them, or `maintain(ack=[…])` the ones you reviewed and found correct |
|