brainllm 12.3.0 → 12.4.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/.env.example CHANGED
@@ -8,7 +8,7 @@ TRILIUM_BASE_URL=http://localhost:8080
8
8
  # TRILIUM_BASE_URL=https://trilium.yourdomain.com
9
9
 
10
10
  # ETAPI token — create in Trilium (Options → ETAPI → Create token), or set
11
- # TRILIUM_PASSWORD instead and `bun run init` will mint one for you and print it.
11
+ # TRILIUM_PASSWORD instead and `bun run init` will mint and cache one for you.
12
12
  TRILIUM_ETAPI_TOKEN=
13
13
 
14
14
  # Alternative to the token: when TRILIUM_ETAPI_TOKEN is empty, BrainLLM mints one on
@@ -33,6 +33,11 @@ TRILIUM_PASSWORD=
33
33
  # Bearer token that all MCP clients must send as Authorization: Bearer <token>.
34
34
  # Generate with: openssl rand -hex 32
35
35
  # MCP_AUTH_TOKEN=
36
+ # HTTP mode fails closed when neither MCP_AUTH_TOKEN nor BRAINLLM_OWNER_PASSWORD is set.
37
+ # Only use this explicit escape hatch on an isolated, trusted network:
38
+ # BRAINLLM_ALLOW_UNAUTHENTICATED_HTTP=true
39
+ # Set true only when a trusted reverse proxy supplies x-forwarded-host/proto.
40
+ # BRAINLLM_TRUST_PROXY=true
36
41
 
37
42
  # ── Config persistence (Railway / persistent-volume deploys) ──────────────────
38
43
  # Absolute path where brainllm.json should be read/written.
@@ -47,6 +52,9 @@ TRILIUM_PASSWORD=
47
52
  # you type on the consent screen when authorizing a client. Leave unset and the
48
53
  # OAuth surface is disabled entirely; MCP_AUTH_TOKEN still works for Claude Code.
49
54
  # BRAINLLM_OWNER_PASSWORD=
55
+ # Optional deployment secret used to sign OAuth access/refresh tokens. Set a
56
+ # new random value (32+ chars) when rotating OAuth; it overrides the local store.
57
+ # BRAINLLM_OAUTH_SECRET=
50
58
 
51
59
  # Only needed if a proxy rewrites Host so the server cannot derive its own
52
60
  # public URL. Must match the URL you type into the client, exactly.
package/README.md CHANGED
@@ -6,11 +6,11 @@
6
6
 
7
7
  **Give Claude a memory that survives the session.**
8
8
 
9
- A persistent, graph-structured second brain for Claude and any MCP client — built on [TriliumNext Notes](https://github.com/TriliumNext/Notes), served over the [Model Context Protocol](https://modelcontextprotocol.io).
9
+ A persistent, graph-structured second brain for Claude and any MCP client — built on [TriliumNext Notes](https://github.com/TriliumNext/Trilium), served over the [Model Context Protocol](https://modelcontextprotocol.io).
10
10
 
11
11
  [**brainllm site**](https://miisodev.github.io/BrainLLM/) · [How it works](https://miisodev.github.io/BrainLLM/how-it-works.html) · [Use cases](https://miisodev.github.io/BrainLLM/use-cases.html) · [Docs](https://miisodev.github.io/BrainLLM/docs.html)
12
12
 
13
- [![Version](https://img.shields.io/badge/version-12.3.0-f59e0b?style=flat-square)](https://github.com/miisodev/BrainLLM/releases)
13
+ [![Version](https://img.shields.io/badge/version-12.4.1-f59e0b?style=flat-square)](https://github.com/miisodev/BrainLLM/releases)
14
14
  [![CI](https://img.shields.io/github/actions/workflow/status/miisodev/BrainLLM/ci.yml?branch=main&style=flat-square&label=CI&color=f59e0b)](https://github.com/miisodev/BrainLLM/actions/workflows/ci.yml)
15
15
  [![License: MIT](https://img.shields.io/badge/license-MIT-a1a1aa?style=flat-square)](./LICENSE)
16
16
  [![Runtime: Bun](https://img.shields.io/badge/runtime-Bun%20%E2%89%A5%201.0-a1a1aa?style=flat-square&logo=bun&logoColor=white)](https://bun.sh)
@@ -25,7 +25,7 @@ A persistent, graph-structured second brain for Claude and any MCP client — bu
25
25
 
26
26
  LLMs forget. Every session starts from zero: who you are, what you're working on, what was decided yesterday, what went wrong last week. BrainLLM fixes that.
27
27
 
28
- **BrainLLM is an MCP server that gives an LLM a real, persistent memory** — stored in [TriliumNext Notes](https://github.com/TriliumNext/Notes), a mature open-source knowledge base you self-host and own. The model opens each session by loading who you are and what's live, writes durable facts the moment they surface, wires knowledge together as a typed graph, and closes each session with a log, a diary entry, and a database backup. The next session picks up exactly where the last one ended.
28
+ **BrainLLM is an MCP server that gives an LLM a real, persistent memory** — stored in [TriliumNext Notes](https://github.com/TriliumNext/Trilium), a mature open-source knowledge base you self-host and own. The model opens each session by loading who you are and what's live, writes durable facts the moment they surface, wires knowledge together as a typed graph, and closes each session with a log, a diary entry, and a database backup. The next session picks up exactly where the last one ended.
29
29
 
30
30
  It's a single Bun/TypeScript service with two dependencies (the MCP SDK and Zod), speaking to Trilium exclusively through its public ETAPI. Your memory lives in *your* Trilium instance — inspectable, editable, and portable, never locked inside a vendor's black box.
31
31
 
@@ -48,8 +48,9 @@ Placement, naming, labels, deduplication, relation bookkeeping, lifecycle aging,
48
48
  - **Structure is enforced, not requested** — every content kind has a canonical structure, served by `template()` and held on write: a new thread requires its goal, thread/diary/session entries open with an identification line (which LLM, which environment, which session type), threads carry exactly one Resolution (owned by `resolve()`), duplicate section headings are detected, and `Last updated` stamps are server-maintained.
49
49
  - **Domains born complete** — creating a knowledge domain creates its book *and* its canonical Sources note (marker legend, stamp, grouped source list, revision table), so every claim has a sourcing home from the first write.
50
50
  - **A visible graph** — `graph()` renders the whole relation graph (or any note's neighborhood) as a Mermaid flowchart, maintained as a native Trilium note.
51
+ - **Byte-preserving artifacts** — text is UTF-8, while binary content uses strict standard base64 in the tool contract and raw bytes on the wire; binary reads return an explicit MIME/base64 envelope instead of replacement characters.
51
52
  - **One-call day orientation** — `day()` serves the previous session, its change log, everything touched since, and the month's deliverables in a single call.
52
- - **Resilient plumbing** — every backend call is timeout-bounded with retry on idempotent reads; all writes are idempotent or duplicate-guarded, so crashes and retries never double-write; content surgery survives the editor's own HTML rewriting; renaming a domain cascades to everything inside it; the maintenance sweep heals drift it finds.
53
+ - **Resilient plumbing** — every backend call is timeout-bounded with retry on idempotent reads; routine core writes are idempotent or duplicate-guarded, while full-mode raw overwrites remain explicit; content surgery survives the editor's own HTML rewriting; renaming a domain cascades to everything inside it; the maintenance sweep heals drift it finds.
53
54
  - **Multi-agent by default** — all write-classified tools serialize behind a process-wide FIFO lock, so two agents (an interactive session and an automated run, say) writing through one hosted instance queue in arrival order instead of racing; reads stay fully parallel. Deletion catch-up in the daily log keeps a note deleted after its day's close from vanishing without a trace — and its window is configurable (`deletionCatchupDays` in the lifecycle policy), defaulting to Trilium's 7-day retention.
54
55
  - **Ask in prose, split on a seam** — `consistency(subject="…")` finds every note asserting about a fact however it is phrased, no regex guessing; `consistency(pattern, staleAfterDays=N)` surfaces figures held in exactly one untouched note, the ones that rot silently because nothing disagrees with them; `read(ids=[…])` batches a multi-note orientation into one round trip; and `split(noteId, sections=[…], into="…")` is the write half of the oversized-note problem — it lifts whole sections into a new note and leaves a pointer back. Information notes can carry a `#mandate` marker so a scoped session finds the one note it must obey without reading every note's prose.
55
56
 
@@ -65,7 +66,7 @@ BrainLLM (#brainLlmRoot)
65
66
  ├── 🤖 LLM Responsibilities · Protocols · Self-correction · Diary/ (the assistant's self-model + daily diary)
66
67
  ├── 🗂️ Memory Sessions/ · Threads/ (daily session logs + multi-session work)
67
68
  ├── 📚 Knowledge Master/ · Domains/<domain>/{ Sources, info } (learned facts beyond/contra training)
68
- └── 💡 Insights Logs/ · Graph (the brain's record of itself)
69
+ └── 💡 Insights Logs/ · Graph · Claims (the brain's record of itself)
69
70
  ```
70
71
 
71
72
  | Note class | Kinds | Behavior |
@@ -87,7 +88,7 @@ Knowledge is a **typed graph**: a closed vocabulary of 16 relations (`extends`,
87
88
  ### Prerequisites
88
89
 
89
90
  - [Bun](https://bun.sh) v1.0+
90
- - A running [TriliumNext](https://github.com/TriliumNext/Notes) instance (desktop app or server)
91
+ - A running [TriliumNext](https://github.com/TriliumNext/Trilium) instance (desktop app or server)
91
92
 
92
93
  ### 1. Install and build
93
94
 
@@ -108,7 +109,7 @@ To do it explicitly instead, either let `init` mint one:
108
109
  TRILIUM_BASE_URL=http://localhost:8080 TRILIUM_PASSWORD=your-trilium-password bun run init
109
110
  ```
110
111
 
111
- It prints the token once to save as `TRILIUM_ETAPI_TOKEN`, and bootstraps the brain in the same run. Or create one by hand in Trilium: **Options → ETAPI → Create token**.
112
+ It caches the token beside `brainllm.json` with owner-only permissions and does **not** print the secret. If you explicitly need a copy, run `bun run init -- --show-token`; the CLI is idempotent and adopts an existing `#brainLlmRoot` instead of creating a duplicate tree. Or create one by hand in Trilium: **Options → ETAPI → Create token**.
112
113
 
113
114
  ### 3. Configure your MCP client
114
115
 
@@ -135,7 +136,7 @@ For **Claude Desktop**, add to `claude_desktop_config.json` (see `config.example
135
136
  }
136
137
  ```
137
138
 
138
- For **Claude Code**, the same server block works in `.mcp.json`. A DXT desktop-extension manifest (`manifest.json`) is also included for one-click installs.
139
+ For **Claude Code**, the same server block works in `.mcp.json`. A schema-valid MCPB manifest (`manifest.json`) is included for hosts that support custom Bun commands, but BrainLLM still requires Bun and this repository does not yet publish a `.mcpb` one-click artifact.
139
140
 
140
141
  ### 4. Bootstrap the brain
141
142
 
@@ -150,10 +151,16 @@ TRILIUM_BASE_URL=http://localhost:8080 TRILIUM_ETAPI_TOKEN=your-token bun run in
150
151
  Install the **operational skill** — the document that teaches Claude to treat BrainLLM as its own mind rather than a filing cabinet:
151
152
 
152
153
  ```bash
153
- # Claude Code / Cowork skills directory
154
+ # Claude Code / Cowork skills directory (macOS/Linux)
154
155
  cp -r skills/brainllm ~/.claude/skills/brainllm
155
156
  ```
156
157
 
158
+ ```powershell
159
+ # Windows PowerShell
160
+ New-Item -ItemType Directory -Force "$env:USERPROFILE\.agents\skills" | Out-Null
161
+ Copy-Item skills\brainllm "$env:USERPROFILE\.agents\skills\brainllm" -Recurse -Force
162
+ ```
163
+
157
164
  Then start a session. The model calls `start()`, orients, and operates the brain natively from there.
158
165
 
159
166
  ---
@@ -190,7 +197,7 @@ The **full mode** tools deliberately break this convention — they are named af
190
197
 
191
198
  ### Tool permissions
192
199
 
193
- Every tool declares whether it reads, writes, or destroys, so your client can group them and you can grant permission per group instead of per call. The current split is **31 read-only · 34 write · 6 destructive**, which lets reads run unattended while anything that touches your brain still asks.
200
+ Every tool declares whether it reads, writes, or destroys, so your client can group them and you can grant permission per group instead of per call. The current full-mode split is **33 read-only · 38 write · 6 destructive** (core mode is **22 read-only · 20 write · 2 destructive**), which lets reads run unattended while anything that touches your brain still asks.
194
201
 
195
202
  The classification lives in one reviewable table ([`src/annotations.ts`](./src/annotations.ts)) rather than scattered across registrations, and its default is deliberately unsafe-side: a tool missing from the table is treated as a **write**, never a read, and says so at startup. Three are worth knowing because they look like reads and aren't — `start()` creates today's diary and session stubs, `session()` runs the maintenance sweep, and `graph()` writes the rendered graph note.
196
203
 
@@ -226,7 +233,7 @@ The complete operational reference is [`skills/brainllm/SKILL.md`](./skills/brai
226
233
  | **stdio** | Local — Claude Desktop / Claude Code spawns BrainLLM as a child process | `PORT` unset (default) |
227
234
  | **HTTP connector** | Remote — clients reach BrainLLM over the network | `PORT` set (Railway injects it) |
228
235
 
229
- The HTTP connector serves a streamable-HTTP MCP endpoint at `/mcp` (one session per `mcp-session-id`, DELETE terminates, CORS-enabled with `mcp-session-id` exposed for browser clients), a **legacy SSE endpoint** at `/sse` (with `POST /messages`) for clients that predate streamable HTTP — older Cursor builds, Continue, and Python-SDK clients — and `GET /health`. The server root serves a plain page naming the endpoints, so a human or probing client landing on the origin sees what lives there. The server itself speaks plain HTTP — TLS is expected to terminate in front of it (Railway's edge does this automatically for you; on a bare VPS or your own Docker host, put it behind a reverse proxy such as Caddy, nginx, or a Cloudflare Tunnel). **Set `MCP_AUTH_TOKEN` on any deployment reachable outside a trusted network** — CORS defaults to `*`, so without a token any client that can reach the endpoint can call your Trilium brain. Idle sessions are evicted after 1 hour; request bodies are capped at 50 MB. Both transports sit behind the same authentication gate.
236
+ The HTTP connector serves a streamable-HTTP MCP endpoint at `/mcp` (one session per `mcp-session-id`, DELETE terminates, CORS-enabled with `mcp-session-id` exposed for browser clients), a **legacy SSE endpoint** at `/sse` (with `POST /messages`) for clients that predate streamable HTTP — older Cursor builds, Continue, and Python-SDK clients — and `GET /health`. The server root serves a plain page naming the endpoints, so a human or probing client landing on the origin sees what lives there. The server itself speaks plain HTTP — TLS is expected to terminate in front of it (Railway's edge does this automatically for you; on a bare VPS or your own Docker host, put it behind a reverse proxy such as Caddy, nginx, or a Cloudflare Tunnel). **HTTP mode now fails closed unless `MCP_AUTH_TOKEN` or `BRAINLLM_OWNER_PASSWORD` is configured**; `BRAINLLM_ALLOW_UNAUTHENTICATED_HTTP=true` is an explicit escape hatch for an isolated trusted network only. CORS defaults to `*`, so a reachable deployment must have a real authentication gate. Idle sessions are evicted after 1 hour; MCP request bodies are capped at 50 MiB by both the server and SDK transport. Both transports sit behind the same authentication gate.
230
237
 
231
238
  ### Docker / Railway
232
239
 
@@ -290,6 +297,9 @@ Without a volume, leave `BRAINLLM_CONFIG` unset — auto-discovery re-finds the
290
297
  | `PORT` | — | Presence switches to HTTP-connector mode |
291
298
  | `MCP_AUTH_TOKEN` | — | Static bearer token accepted on `/mcp` — what Claude Code and `mcp-remote` send |
292
299
  | `BRAINLLM_OWNER_PASSWORD` | — | Enables the OAuth 2.1 / CIMD endpoints, required for claude.ai. The password you type on the consent screen |
300
+ | `BRAINLLM_OAUTH_SECRET` | — | Optional 32+ character deployment signing secret; rotate it to invalidate old OAuth tokens |
301
+ | `BRAINLLM_ALLOW_UNAUTHENTICATED_HTTP` | `false` | Explicit trusted-network-only escape hatch when no HTTP credential is configured; never use on a public interface |
302
+ | `BRAINLLM_TRUST_PROXY` | `false` | Trust `x-forwarded-host`/`x-forwarded-proto` for OAuth discovery only when a trusted reverse proxy sets them; prefer `BRAINLLM_PUBLIC_URL` |
293
303
  | `BRAINLLM_PUBLIC_URL` | — | Override the derived public origin when a proxy rewrites `Host`. Must match the URL you enter in the client exactly |
294
304
  | `BRAINLLM_CONFIG` | — | Absolute file path for `brainllm.json` on persistent-volume deploys |
295
305
 
@@ -327,23 +337,27 @@ index.ts ─→ tools.ts ─┬→ trilium.ts ETAPI client: bounded/retrying
327
337
  ├→ lifecycle.ts protection, sweep, aging, the start digest
328
338
  ├→ journal.ts daily Insights log generation (regenerate-in-place)
329
339
  ├→ time.ts timezone-correct now/today (BRAINLLM_TZ)
340
+ ├→ rate-limit.ts bounded public HTTP budgets
341
+ ├→ network-security.ts CIMD public-network and redirect checks
330
342
  ├→ bootstrap.ts five-area tree builder
331
343
  └→ tools-*.ts per-area surface reads · full-mode raw ETAPI
332
344
  ```
333
345
 
334
- Key properties: every write is sanitized for Trilium/CKEditor 5 compatibility (mutations reported back as `sanitized[]`); a revision snapshot precedes every content mutation; every request carries the user's local time so Trilium stamps dates in the right day; all writes are idempotent or duplicate-guarded (see the retry-safety matrix in the Blueprint).
346
+ Key properties: every write is sanitized for Trilium/CKEditor 5 compatibility (mutations reported back as `sanitized[]`); a revision snapshot precedes routine content mutation; every request carries the user's local time so Trilium stamps dates in the right day; core writes are idempotent or duplicate-guarded, while raw full-mode overwrites are explicit.
335
347
 
336
348
  ## Development
337
349
 
338
350
  ```bash
339
351
  bun run dev # hot-reload dev server
340
352
  bun run build # bundle to dist/index.js
341
- bun test src/normalize.test.ts src/lifecycle.test.ts src/trilium.test.ts # unit tests
342
- bun run test # integration tests (requires a live Trilium instance)
353
+ bun run test:unit # all unit tests (Bun discovery; works on Windows and CI)
354
+ bun run test:http # local auth/header smoke (no Trilium required)
355
+ bun run test # integration tests (requires a live Trilium instance)
356
+ bun run validate:manifest # MCPB schema/icon validation
343
357
  bun run init # CLI bootstrap
344
358
  ```
345
359
 
346
- Repo layout: runtime source in `src/`, the operational skill in `skills/brainllm/`, developer scripts in `scripts/`, DXT manifest in `manifest.json`, container build in `Dockerfile`.
360
+ Repo layout: runtime source in `src/`, the operational skill in `skills/brainllm/`, developer scripts in `scripts/`, schema-valid MCPB manifest in `manifest.json`, container build in `Dockerfile`.
347
361
 
348
362
  ## Contributing
349
363
 
@@ -365,12 +379,18 @@ BrainLLM started as one person's daily-driver tool, so the code and docs still c
365
379
 
366
380
  1. **Open an issue first** for anything non-trivial — bugs with reproduction steps, or proposals with the use case spelled out.
367
381
  2. **Fork and branch**, keep changes focused, and match the existing code style.
368
- 3. **`bun run build` must pass clean** and unit tests must stay green (`bun test src/*.test.ts` — CI runs the same two checks on every push and PR); add tests for new normalize/lifecycle logic.
382
+ 3. **`bun run build` must pass clean** and unit tests must stay green (`bun run test:unit` — CI runs the same checks on every push and PR); add tests for new normalize/lifecycle logic.
369
383
  4. **Use conventional commits** (`fix:`, `feat:`, `docs:`, `refactor:`).
370
384
  5. **Update the docs that your change touches** — `README.md`, the skill package under `skills/brainllm/`, and `config.example.json`/`.env.example` where relevant. The skill is part of the product: a tool change without its skill update is half a change.
371
385
 
372
386
  Not sure where to start? Issues labeled `good first issue`, doc gaps, and the troubleshooting reference are all friendly entry points.
373
387
 
388
+ ## Security
389
+
390
+ Please do not report suspected vulnerabilities in a public issue. Follow the private reporting process in [`SECURITY.md`](./SECURITY.md); never include ETAPI tokens, OAuth credentials, or private brain content in a report.
391
+
392
+ ---
393
+
374
394
  ## Support the project
375
395
 
376
396
  BrainLLM is built and maintained by one person. If it's useful to you, sponsorship directly funds its continued development:
@@ -392,5 +412,5 @@ Starring the repo, reporting bugs, and spreading the word help too.
392
412
 
393
413
  ## Credits
394
414
 
395
- - [TriliumNext Notes](https://github.com/TriliumNext/Notes) — the open-source, self-hosted knowledge base that powers this server's backend. BrainLLM would not exist without the TriliumNext team's work.
415
+ - [TriliumNext Notes](https://github.com/TriliumNext/Trilium) — the open-source, self-hosted knowledge base that powers this server's backend. BrainLLM would not exist without the TriliumNext team's work.
396
416
  - [Model Context Protocol](https://modelcontextprotocol.io) — the open standard that lets one memory serve every MCP-capable client.
package/SECURITY.md ADDED
@@ -0,0 +1,33 @@
1
+ # Security Policy
2
+
3
+ ## Supported versions
4
+
5
+ Security fixes ship on the latest release line. Older versions may remain useful for compatibility, but upgrade before relying on them for an internet-facing deployment.
6
+
7
+ | Version | Supported |
8
+ |---|---|
9
+ | 12.4.x | ✅ |
10
+ | 12.3.x and earlier | ❌ |
11
+
12
+ ## Reporting a vulnerability
13
+
14
+ Do not open a public issue for a suspected vulnerability, and do not include credentials, ETAPI tokens, OAuth tokens, or private brain content in a report.
15
+
16
+ Use GitHub's private **Report a vulnerability** form for this repository, or email **miisodev@gmail.com** with:
17
+
18
+ - affected BrainLLM version and deployment mode (stdio, HTTP, Docker, or Railway);
19
+ - the request or sequence that reproduces the issue;
20
+ - the expected and observed behaviour;
21
+ - impact and any evidence already collected.
22
+
23
+ Please use a private channel until a fix and coordinated disclosure path are agreed. Do not test against data or accounts you do not own.
24
+
25
+ ## Deployment guidance
26
+
27
+ Run the latest tagged release rather than an unreleased branch or a mutable dependency lock. For an HTTP deployment reachable outside a trusted network, set `MCP_AUTH_TOKEN`; configure `BRAINLLM_OWNER_PASSWORD` only when hosted OAuth clients require it. Keep the Trilium instance, ETAPI token, OAuth store, and `brainllm.json` on protected storage, and restrict network access at the platform edge.
28
+
29
+ For credential rotation, replace `MCP_AUTH_TOKEN` and the ETAPI token in the provider/Trilium, set a fresh `BRAINLLM_OAUTH_SECRET` (32+ characters) to invalidate existing OAuth tokens, and remove obsolete local `*.oauth.json` / `*.token` files. Never put recovered credentials in a repository, shared context note, Docker context, or release artifact.
30
+
31
+ ## Scope
32
+
33
+ BrainLLM is an MCP server over TriliumNext's ETAPI boundary. Vulnerabilities in Trilium itself should be reported to the TriliumNext project unless the issue is caused by BrainLLM's use of that boundary.