mnueron 0.1.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.
Files changed (52) hide show
  1. package/ARCHITECTURE.md +161 -0
  2. package/INSTALL.md +262 -0
  3. package/LICENSE +21 -0
  4. package/README.md +305 -0
  5. package/dashboard/index.html +838 -0
  6. package/dist/cli.js +685 -0
  7. package/dist/cli.js.map +1 -0
  8. package/dist/config.js +44 -0
  9. package/dist/config.js.map +1 -0
  10. package/dist/dashboard/server.js +234 -0
  11. package/dist/dashboard/server.js.map +1 -0
  12. package/dist/detectors/claude_code.js +72 -0
  13. package/dist/detectors/claude_code.js.map +1 -0
  14. package/dist/detectors/claude_desktop.js +37 -0
  15. package/dist/detectors/claude_desktop.js.map +1 -0
  16. package/dist/detectors/cursor.js +36 -0
  17. package/dist/detectors/cursor.js.map +1 -0
  18. package/dist/detectors/extra.js +59 -0
  19. package/dist/detectors/extra.js.map +1 -0
  20. package/dist/detectors/index.js +14 -0
  21. package/dist/detectors/index.js.map +1 -0
  22. package/dist/detectors/json_detector.js +95 -0
  23. package/dist/detectors/json_detector.js.map +1 -0
  24. package/dist/detectors/types.js +13 -0
  25. package/dist/detectors/types.js.map +1 -0
  26. package/dist/import/claude.js +82 -0
  27. package/dist/import/claude.js.map +1 -0
  28. package/dist/import/openai.js +102 -0
  29. package/dist/import/openai.js.map +1 -0
  30. package/dist/index.js +77 -0
  31. package/dist/index.js.map +1 -0
  32. package/dist/plugins/loader.js +175 -0
  33. package/dist/plugins/loader.js.map +1 -0
  34. package/dist/plugins/types.js +24 -0
  35. package/dist/plugins/types.js.map +1 -0
  36. package/dist/setup.js +123 -0
  37. package/dist/setup.js.map +1 -0
  38. package/dist/store/chunking.js +150 -0
  39. package/dist/store/chunking.js.map +1 -0
  40. package/dist/store/embeddings.js +126 -0
  41. package/dist/store/embeddings.js.map +1 -0
  42. package/dist/store/local.js +720 -0
  43. package/dist/store/local.js.map +1 -0
  44. package/dist/store/provider.js +7 -0
  45. package/dist/store/provider.js.map +1 -0
  46. package/dist/store/redactor.js +114 -0
  47. package/dist/store/redactor.js.map +1 -0
  48. package/dist/store/remote.js +62 -0
  49. package/dist/store/remote.js.map +1 -0
  50. package/dist/tools.js +312 -0
  51. package/dist/tools.js.map +1 -0
  52. package/package.json +55 -0
@@ -0,0 +1,161 @@
1
+ # mnueron architecture
2
+
3
+ ## The two-mode design
4
+
5
+ The MCP server is **storage-agnostic**. It depends on a `Provider` interface
6
+ with five methods (`save`, `search`, `list`, `delete`, `namespaces`). Two
7
+ implementations satisfy it:
8
+
9
+ | Provider | When | Storage | Cost |
10
+ | --- | --- | --- | --- |
11
+ | `LocalProvider` | Default | SQLite + FTS5 in `~/.mnueron/memories.db` | $0 |
12
+ | `RemoteProvider` | When `MNUERON_API_URL` + `MNUERON_API_TOKEN` are set | HTTPS → hosted Postgres + pgvector | Whatever you charge |
13
+
14
+ Flipping modes is **one env-var change** on the client. The MCP tool surface
15
+ and behavior stay identical. This is the property that makes the local repo
16
+ both a free OSS giveaway *and* the on-ramp to the hosted product.
17
+
18
+ ## Tenancy model (hosted mode)
19
+
20
+ The hosted backend is multi-tenant by design. The unit of isolation is `org`:
21
+
22
+ ```
23
+ ┌────────────────────────────────────────┐
24
+ │ org (a company) │
25
+ │ ┌──────────────┐ ┌──────────────┐ │
26
+ │ │ namespace A │ │ namespace B │ │ ← e.g. one per app
27
+ │ │ memories… │ │ memories… │ │
28
+ │ └──────────────┘ └──────────────┘ │
29
+ │ │
30
+ │ users (1..N belong via org_members) │
31
+ │ api_tokens (each bound to user+org) │
32
+ └────────────────────────────────────────┘
33
+ ```
34
+
35
+ - **Users** can belong to multiple orgs (personal + work, multiple clients,
36
+ contractors). The `org_members` table holds the M:N relation with a role
37
+ per (org, user).
38
+ - **API tokens** are bound to `(user, org)`. The token itself decides which
39
+ org's memories you see. A user with three orgs has three different tokens.
40
+ - **Namespaces** are sub-buckets inside an org. The two apps you mentioned —
41
+ Claude-app and OpenAI-app under the same company — get two namespaces
42
+ under one org. Same billing, separate memory pools.
43
+ - **Memories** are scoped to one `(org_id, namespace_id)`. The `org_id` is
44
+ the load-bearing isolation column.
45
+
46
+ ## Three layers of isolation (defense in depth)
47
+
48
+ Even if app code has a bug, cross-org leaks should not be possible.
49
+
50
+ ### 1. Token resolution
51
+
52
+ Every request carries `Authorization: Bearer mn_…`. The middleware hashes the
53
+ token, looks it up in `api_tokens`, and gets back `(user_id, org_id, token_id)`.
54
+ That triple is stored on the request object and is the only source of truth
55
+ for "which org are we acting as." Headers, query params, and request bodies
56
+ are never trusted to specify the org.
57
+
58
+ ### 2. Postgres Row-Level Security
59
+
60
+ After resolving the token, the backend runs:
61
+
62
+ ```sql
63
+ SELECT set_config('app.current_org_id', '<the resolved org_id>', false);
64
+ SET ROLE mnueron_app;
65
+ ```
66
+
67
+ Then it runs the actual query. The `mnueron_app` role has RLS policies that
68
+ make every read/write `WHERE org_id = current_setting('app.current_org_id')`.
69
+ A `SELECT * FROM memories` from within that role returns only this org's
70
+ rows. If `app.current_org_id` isn't set, the policy returns zero rows by
71
+ default — fail-closed, not fail-open.
72
+
73
+ ```sql
74
+ CREATE POLICY p_memories_isolation ON memories
75
+ USING (org_id::text = current_setting('app.current_org_id', true));
76
+ ```
77
+
78
+ This means the **database** enforces the boundary, not the application. A
79
+ forgotten `WHERE org_id = ?` in app code can't leak data.
80
+
81
+ ### 3. Audit log
82
+
83
+ Every mutation writes a row to `audit_log` with `(org_id, user_id, token_id,
84
+ action, target_id, metadata)`. Companies that ask "who touched our data
85
+ when" can be answered from one table. Cheap to write, expensive not to have
86
+ when an enterprise customer asks for it.
87
+
88
+ ## Multi-app, multi-LLM
89
+
90
+ The storage layer is provider-agnostic and LLM-agnostic. Three concrete
91
+ patterns:
92
+
93
+ **Two apps under one company.** One org, two API tokens (one per app), each
94
+ defaulting to its own namespace. Memories tagged by app are separate but
95
+ queryable across both if you ever want a cross-app view.
96
+
97
+ **Claude-app and OpenAI-app sharing memory.** Both apps hit the same
98
+ `/v1/memories/search` endpoint. The HTTP API is the contract; the agent
99
+ framework on top doesn't matter. Use the Anthropic SDK for one, OpenAI SDK
100
+ for the other, but they both pull from the same memory pool.
101
+
102
+ **Different companies (multi-tenant SaaS).** Each company gets an `org`.
103
+ Their users sign up under it. Token-bound-to-org guarantees one company's
104
+ memories never reach another's tools or eyes.
105
+
106
+ ## What's open-core
107
+
108
+ | | OSS (MIT, free forever) | Hosted (paid) |
109
+ | --- | --- | --- |
110
+ | Local MCP server | ✓ | (uses same client) |
111
+ | SQLite local store | ✓ | — |
112
+ | Importers (Claude, OpenAI) | ✓ | ✓ |
113
+ | CLI | ✓ | ✓ |
114
+ | Hosted Postgres backend | — | ✓ |
115
+ | Multi-device sync | — | ✓ |
116
+ | Web dashboard | — | ✓ |
117
+ | Team sharing | — | ✓ |
118
+ | SSO + audit | — | Enterprise tier |
119
+ | BAAs / compliance posture | — | Enterprise tier |
120
+
121
+ The `server/` directory in this repo is the hosted backend reference
122
+ implementation. It's yours; the open-core licensing convention is to keep
123
+ the server source-available but not OSI-OSS, so competitors can't repackage
124
+ your hosting as their own product. (See PostHog, Sentry, Mattermost for
125
+ established versions of this.)
126
+
127
+ ## Performance notes
128
+
129
+ - **Recall latency target: <300ms p95.** The hosted server's hybrid search
130
+ uses BM25 (tsvector) + cosine (pgvector HNSW) and fuses with reciprocal
131
+ rank. Both indexes are HNSW/GIN, so 10K-row scans aren't a problem until
132
+ ~10M rows per org. Past that, partition by org_id.
133
+ - **Embeddings on the write path are blocking in the current skeleton.** In
134
+ production, move them to a worker queue: respond 200 immediately, queue the
135
+ embedding job, fill `embedding` async. Recall still works on tsvector while
136
+ embedding is pending.
137
+ - **Summarization (LLM compression) is always async.** Run nightly via the
138
+ Anthropic Batch API for 50% off list price. Users don't notice.
139
+
140
+ ## Cost at scale (recap)
141
+
142
+ | Users | Monthly infra | Monthly LLM (Haiku batch) | Total | Revenue @ $12/mo | Margin |
143
+ | ---: | ---: | ---: | ---: | ---: | ---: |
144
+ | 100 | $130 | $20 | $150 | $1,200 | 88% |
145
+ | 1,000 | $640 | $115 | $760 | $12,000 | 94% |
146
+ | 10,000 | $4,200 | $1,100 | $5,300 | $120,000 | 96% |
147
+
148
+ The dominant variable cost is LLM compression (session summarization). Use
149
+ prompt caching for repeated context and the Batch API for non-realtime
150
+ summarization to cut that ~50–60%.
151
+
152
+ ## Threat model (one paragraph)
153
+
154
+ The realistic threats are: (a) token theft via compromised dev machine, (b)
155
+ cross-tenant leak via app-layer bug, (c) malicious memory content trying to
156
+ prompt-inject downstream agents. Mitigations: short-lived tokens with
157
+ rotation + last-used tracking; RLS at the DB layer (covered above); strip
158
+ control sequences and tag user-provided memory clearly when returning to
159
+ agents so prompt-injection at recall time is contained, not silent. Secret
160
+ redaction at write time (regex + entropy scanner) before content hits storage
161
+ catches the most common "agent accidentally captured an AWS key" failure.
package/INSTALL.md ADDED
@@ -0,0 +1,262 @@
1
+ # Installing MNUERON
2
+
3
+ Three install paths depending on what you want to do.
4
+
5
+ | You want to… | Use this section |
6
+ | --- | --- |
7
+ | Use it with Claude Desktop / Cursor / etc. on one machine (free, local) | [Path A](#path-a-local-mode-free-1-machine) |
8
+ | Use it across multiple machines / share with a team / build it into a SaaS | [Path B](#path-b-hosted-mode-multi-machine) |
9
+ | Use it from your own Python or .NET app | [Path C](#path-c-app-sdk) |
10
+
11
+ You can do A first, then add B and C later. Nothing locks you in.
12
+
13
+ ---
14
+
15
+ ## Prerequisites
16
+
17
+ | Need | Version | How to check |
18
+ | --- | --- | --- |
19
+ | Node.js | 20+ | `node --version` |
20
+ | npm | 10+ | `npm --version` |
21
+ | (For Path C, Python) Python | 3.9+ | `python --version` |
22
+ | (For Path C, .NET) .NET SDK | 6.0+ | `dotnet --version` |
23
+ | (For Path B) Supabase account or any Postgres 15+ with pgvector | — | Free tier at supabase.com |
24
+
25
+ If you don't have Node.js: install from https://nodejs.org/ (LTS version).
26
+
27
+ ---
28
+
29
+ ## Path A: Local mode (free, 1 machine)
30
+
31
+ This is the fastest path. Everything runs on your computer; nothing leaves it.
32
+ Memories live in `~/.mnueron/memories.db` (or `%USERPROFILE%\.mnueron\` on Windows).
33
+
34
+ ### Step 1 — Install from source
35
+
36
+ ```bash
37
+ git clone https://github.com/yourorg/mnueron.git
38
+ cd mnueron
39
+ npm install
40
+ npm run build
41
+ ```
42
+
43
+ (Once we publish to npm, this becomes `npm install -g mnueron` and skip the rest.)
44
+
45
+ ### Step 2 — Run the setup wizard
46
+
47
+ ```bash
48
+ node dist/cli.js setup
49
+ ```
50
+
51
+ The wizard scans for installed AI tools and configures each one. Expected output:
52
+
53
+ ```
54
+ 🧠 mnueron — persistent memory for AI dev tools
55
+ mode: local SQLite
56
+
57
+ Configured:
58
+ ✓ Claude Desktop added
59
+ ✓ Cursor added
60
+
61
+ Not detected:
62
+ Claude Code
63
+ Windsurf
64
+ Cline (VS Code)
65
+
66
+ ✨ Done. Restart any running AI tool to load the memory plugin.
67
+ ```
68
+
69
+ ### Step 3 — Verify it works
70
+
71
+ Restart Claude Desktop (or any AI tool that was configured). In a new chat,
72
+ ask:
73
+
74
+ > "What memory tools do you have available?"
75
+
76
+ The AI should list `memory_save`, `memory_recall`, `memory_list`, `memory_delete`,
77
+ `memory_namespaces`, and `memory_import_chat`. If it doesn't, see [Troubleshooting](#troubleshooting).
78
+
79
+ ### Step 4 — (Optional) Import your past chat history
80
+
81
+ ```bash
82
+ # claude.ai → Settings → Privacy → Export data → wait for email
83
+ # Unzip the archive, find conversations.json
84
+
85
+ node dist/cli.js import ~/Downloads/conversations.json --ns personal
86
+ ```
87
+
88
+ Each past Claude conversation becomes one searchable memory. Same command
89
+ works for ChatGPT exports — auto-detects format.
90
+
91
+ You're done with Path A.
92
+
93
+ ---
94
+
95
+ ## Path B: Hosted mode (multi-machine)
96
+
97
+ For when you want the same memories on your laptop, desktop, and work box —
98
+ or you're going to offer this as a service. The backend is multi-tenant by
99
+ design with row-level security per organization.
100
+
101
+ ### Step 1 — Provision the database
102
+
103
+ Easiest path is Supabase free tier. **See `server/SUPABASE_SETUP.md` for the
104
+ full 15-minute walkthrough** — it covers:
105
+
106
+ - Creating the Supabase project
107
+ - Enabling the `vector` extension
108
+ - Applying the schema (`server/supabase_schema.sql`)
109
+ - Generating your first API token via the included `mnueron_signup()` function
110
+ - Getting the connection string
111
+
112
+ Skip ahead to Step 2 below when your database is ready.
113
+
114
+ ### Step 2 — Run the backend
115
+
116
+ ```bash
117
+ cd server
118
+ npm install express pg dotenv @types/pg @types/express
119
+
120
+ # Create server/.env with your connection string
121
+ cat > .env <<EOF
122
+ DATABASE_URL=postgresql://postgres.xxx:password@aws-0-region.pooler.supabase.com:6543/postgres
123
+ OPENAI_API_KEY=sk-... # optional, omit to fall back to BM25-only search
124
+ PORT=3111
125
+ EOF
126
+
127
+ # Run it
128
+ npx tsx index.ts
129
+ ```
130
+
131
+ You should see `mnueron-server listening on :3111`.
132
+
133
+ ### Step 3 — Make the server publicly reachable
134
+
135
+ For "any computer, anywhere" access, the API needs a public HTTPS URL. Cheapest options:
136
+
137
+ - **Railway** — push the `server/` folder to GitHub, link to Railway, set env vars, done. ~$5/mo.
138
+ - **Lightsail** — $5/mo container, same shape as a Node app you'd deploy normally.
139
+ - **Fly.io** — $1.94/mo for a small VM.
140
+ - **Render** — $7/mo for always-on.
141
+
142
+ Whichever you pick, the result is a URL like `https://api.your-mnueron.com`.
143
+
144
+ ### Step 4 — Point your local client at the hosted backend
145
+
146
+ ```bash
147
+ node dist/cli.js setup --hosted https://api.your-mnueron.com --token mnu_xxxxxxxxxxxx
148
+ ```
149
+
150
+ Repeat on every machine you want to use the hosted memory from. Same token = same memories everywhere.
151
+
152
+ ---
153
+
154
+ ## Path C: App SDK
155
+
156
+ For when you want your own Python or C# app to read and write memories. You
157
+ need a hosted backend (Path B) or you can hit the local server on
158
+ `http://localhost:3111` for development.
159
+
160
+ ### Python
161
+
162
+ ```bash
163
+ cd sdks/python
164
+ pip install -e .
165
+ ```
166
+
167
+ Then in your app:
168
+
169
+ ```python
170
+ from mnueron import Mnueron
171
+
172
+ with Mnueron(api_key="mnu_xxx", base_url="https://api.your-mnueron.com") as mem:
173
+ mem.save("User prefers concise replies", namespace=f"user-{user_id}")
174
+ results = mem.search("how does user like responses?", namespace=f"user-{user_id}")
175
+ for r in results:
176
+ print(r.content, r.score)
177
+ ```
178
+
179
+ Or use the async version (`AsyncMnueron`) for `asyncio`-based apps.
180
+
181
+ ### .NET / C#
182
+
183
+ Drop `sdks/csharp/MnueronClient.cs` into any .NET 6+ project. No NuGet package needed.
184
+
185
+ ```csharp
186
+ using Mnueron;
187
+
188
+ using var mem = new MnueronClient("mnu_xxx", "https://api.your-mnueron.com");
189
+ await mem.SaveAsync("User prefers concise replies", $"user-{userId}");
190
+ var results = await mem.SearchAsync("how does user like responses?", $"user-{userId}");
191
+ ```
192
+
193
+ ---
194
+
195
+ ## Configuration reference
196
+
197
+ | Variable | Used by | Purpose |
198
+ | --- | --- | --- |
199
+ | `MNUERON_API_URL` | CLI, MCP server | Hosted backend base URL. If unset, falls back to local SQLite. |
200
+ | `MNUERON_API_TOKEN` | CLI, MCP server | Bearer token for hosted mode. Required if `MNUERON_API_URL` is set. |
201
+ | `MNUERON_API_KEY` | Python SDK | Same as `MNUERON_API_TOKEN`. Both names work. |
202
+ | `MNUERON_DB_PATH` | CLI, MCP server (local mode) | Override the SQLite path. Default: `~/.mnueron/memories.db`. |
203
+ | `MNUERON_NAMESPACE` | CLI, MCP server | Default namespace when one isn't specified. Default: `default`. |
204
+ | `DATABASE_URL` | Backend (Path B) | Postgres connection string. |
205
+ | `OPENAI_API_KEY` | Backend (Path B) | Embedding provider. Optional — falls back to BM25-only search. |
206
+ | `PORT` | Backend (Path B) | Default 3111. |
207
+
208
+ ---
209
+
210
+ ## Uninstall
211
+
212
+ ```bash
213
+ node dist/cli.js setup --uninstall
214
+ ```
215
+
216
+ Removes mnueron from every tool's MCP config. Your local memories at
217
+ `~/.mnueron/` are preserved unless you delete the folder yourself.
218
+
219
+ ---
220
+
221
+ ## Troubleshooting
222
+
223
+ **Claude Desktop doesn't see the memory tools after setup.**
224
+ - Make sure you fully quit and restarted Claude Desktop (not just closed the window — quit the app).
225
+ - Check that the config file exists and is valid JSON:
226
+ - macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
227
+ - Windows: `%APPDATA%\Claude\claude_desktop_config.json`
228
+ - Look for a `mcpServers.mnueron` entry. If missing, re-run `setup`.
229
+
230
+ **`mnueron setup` says "tool not detected" but the tool is installed.**
231
+ - Run the tool once before setup so it creates its config directory.
232
+ - For Cursor, the detector looks for `~/.cursor/` — open Cursor once if it's a fresh install.
233
+
234
+ **Hosted mode: server returns 401 unauthorized.**
235
+ - Token mismatch. Check that `MNUERON_API_TOKEN` is set to the **raw** token (starts with `mnu_`), not the hash stored in the database.
236
+ - If you've lost the raw token, generate a new one and update the database `api_tokens.token_hash`.
237
+
238
+ **Hosted mode: server returns empty results for everything.**
239
+ - RLS is blocking. Check that `app.current_org_id` is being set per request. Look in `server/index.ts` for the `withTenantScope` helper.
240
+
241
+ **`pgvector` errors when applying the schema.**
242
+ - Extension not enabled. On Supabase: Dashboard → Database → Extensions → toggle `vector` on.
243
+ - On RDS or self-hosted Postgres: `sudo apt install postgresql-15-pgvector` then `CREATE EXTENSION vector;`.
244
+
245
+ **`bcrypt` or `better-sqlite3` errors during `npm install` on Windows.**
246
+ - These packages need a C++ build chain. Install `windows-build-tools` via npm or just use a Node version with prebuilt binaries (recent LTS works out of the box).
247
+
248
+ ---
249
+
250
+ ## Next steps after install
251
+
252
+ Once mnueron is working, the highest-value memories to save first:
253
+
254
+ 1. **Project conventions** — `memory_save` with content like `"In this project: TypeScript strict mode, prettier with 2-space indent, no default exports, file names kebab-case"`.
255
+ 2. **Tech stack facts** — `"Backend is FastAPI + Postgres + Redis. Frontend is Next.js 14 app router."`.
256
+ 3. **Decisions and their rationale** — `"We chose pgvector over Pinecone for cost and self-host reasons (Mar 2026)."`.
257
+
258
+ The "click" moment — when mnueron feels indispensable — usually arrives about
259
+ two weeks in, when a new chat starts and the AI orients itself without you
260
+ re-explaining anything.
261
+
262
+ Anything else, file an issue or open a PR.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 MNUERON contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.