mrplex 0.0.3 → 0.2.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 +160 -198
- package/dist/cli/config.d.ts +0 -4
- package/dist/cli/config.js.map +1 -1
- package/dist/cli/main.js +97 -14
- package/dist/cli/main.js.map +1 -1
- package/dist/embed/cmd-hook.d.ts +3 -3
- package/dist/embed/cmd-hook.js +3 -3
- package/dist/embed/config.d.ts +3 -17
- package/dist/embed/config.js +6 -38
- package/dist/embed/config.js.map +1 -1
- package/dist/embed/hook.d.ts +4 -3
- package/dist/embed/hook.js +4 -3
- package/dist/embed/hook.js.map +1 -1
- package/dist/embed/http-hook.d.ts +3 -2
- package/dist/embed/http-hook.js +3 -2
- package/dist/embed/http-hook.js.map +1 -1
- package/dist/mcp/tools.js +2 -2
- package/dist/mcp/tools.js.map +1 -1
- package/dist/shell/guard.d.ts +2 -1
- package/dist/shell/guard.js +11 -4
- package/dist/shell/guard.js.map +1 -1
- package/dist/shell/policy.d.ts +25 -3
- package/dist/shell/policy.js +57 -2
- package/dist/shell/policy.js.map +1 -1
- package/dist/sync/reconcile.js +47 -3
- package/dist/sync/reconcile.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,285 +2,247 @@
|
|
|
2
2
|
|
|
3
3
|
*Markdown Repos, plexed.*
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
mrplex turns a folder of Markdown files into a queryable, versioned knowledge store — without taking your files hostage. Your notes stay ordinary `.md` on disk, readable and editable by any tool you already use. On top of that folder mrplex adds four things a plain directory can't give you:
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
- **Search** over your YAML frontmatter — filter with a small expression language, run full-text search across bodies, and (optionally) rank by meaning.
|
|
8
|
+
- **Versions** — every write is kept, so you can see history, diff two points, and never lose an overwrite to a race.
|
|
9
|
+
- **A link graph** that tracks how your documents connect and survives renames, so moving a file doesn't break the links pointing at it.
|
|
10
|
+
- **One model over three surfaces** — the same store is reachable from the CLI, from an MCP server, and over REST.
|
|
8
11
|
|
|
9
|
-
|
|
12
|
+
That last point is the reason mrplex exists: it gives an AI agent a way to *explore and maintain* a vault the way a person would — by asking precise questions and following relationships — instead of grepping around and guessing.
|
|
10
13
|
|
|
11
|
-
|
|
12
|
-
npm install -g mrplex
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
Prefer not to install globally? `npx mrplex …` works the same way.
|
|
14
|
+
## Quickstart
|
|
16
15
|
|
|
17
|
-
|
|
16
|
+
Install it (Node 20.11 or newer):
|
|
18
17
|
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
export MRPLEX_REPO=starship
|
|
18
|
+
```sh
|
|
19
|
+
$ npm install -g mrplex
|
|
22
20
|
```
|
|
23
21
|
|
|
24
|
-
|
|
22
|
+
Point it at a folder of Markdown and start asking questions:
|
|
25
23
|
|
|
26
|
-
```
|
|
27
|
-
mrplex
|
|
28
|
-
|
|
24
|
+
```sh
|
|
25
|
+
$ mrplex repos create notes
|
|
26
|
+
created repo notes
|
|
27
|
+
$ mrplex config set-repo notes # remember the default; no more -r on every command
|
|
28
|
+
config: repo set
|
|
29
|
+
$ mrplex sync ~/notes --once
|
|
30
|
+
sync notes @ ~/notes: through=v0 actions=128 feed=0
|
|
31
|
+
$ mrplex query --text 'that thing you half remember'
|
|
32
|
+
$path
|
|
33
|
+
---------------------------
|
|
34
|
+
projects/roadmap.md
|
|
35
|
+
meeting-notes/2026-08-12.md
|
|
29
36
|
```
|
|
30
37
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
> **From a git checkout?** Run `npm install && npm link` (or `npm run cli -- …`). Every `mrplex …` command below is equivalent to `npx mrplex …`.
|
|
38
|
+
That's the whole loop: create a repo, load a folder into it, then query. A repo is just a named collection of documents inside one database (which defaults to `./mrplex.db` — no configuration needed). Any folder of `.md` works, Obsidian vaults included.
|
|
34
39
|
|
|
35
|
-
|
|
40
|
+
`sync` is two-way. With `--once` it reconciles the folder and the store a single time and exits; drop the flag and it keeps running, watching for changes on both sides. To do that it records a little bookkeeping — two keys, `$version` and `$content_hash` — in each file's frontmatter. That's how a file on disk remembers which stored version it came from, so edits made outside mrplex are matched up correctly on the next sync.
|
|
36
41
|
|
|
37
|
-
|
|
42
|
+
## A guided tour: the USS Meridian
|
|
38
43
|
|
|
39
|
-
|
|
44
|
+
The repository ships a demo vault in `fixtures/starship/` — the crew, missions, logs, and equipment of a fictional survey starship, all short interlinked notes. It's the easiest way to see what the queries actually do.
|
|
40
45
|
|
|
41
|
-
|
|
46
|
+
First copy it out of the checkout and load it. (Copy rather than sync in place: `sync` writes those `$version` tracking keys into the files, and there's no reason to dirty the committed fixture.)
|
|
42
47
|
|
|
43
|
-
```
|
|
44
|
-
git clone https://github.com/usergenic/mrplex
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
mrplex sync fixtures/starship --once -r starship
|
|
48
|
+
```sh
|
|
49
|
+
$ git clone https://github.com/usergenic/mrplex
|
|
50
|
+
$ cp -r mrplex/fixtures/starship .
|
|
51
|
+
$ mrplex repos create starship
|
|
52
|
+
created repo starship
|
|
53
|
+
$ mrplex config set-repo starship
|
|
54
|
+
config: repo set
|
|
55
|
+
$ mrplex sync starship --once
|
|
56
|
+
sync starship @ starship: through=v0 actions=30 feed=0
|
|
53
57
|
```
|
|
54
58
|
|
|
55
|
-
|
|
59
|
+
### Ask about your frontmatter
|
|
56
60
|
|
|
57
|
-
|
|
61
|
+
Filters are written in [CEL](https://cel.dev/) — a small, safe expression language — and evaluated against each document's frontmatter, plus a few built-in fields like `$path` and `$body`. So "what equipment is broken right now?" is a plain boolean expression over the `status` field. Use `-s` (repeatable) to choose which fields come back in the table:
|
|
58
62
|
|
|
59
|
-
|
|
63
|
+
```sh
|
|
64
|
+
$ mrplex query --filter 'status == "damaged" || status == "offline"' -s title -s status -s maintainer
|
|
65
|
+
title status maintainer
|
|
66
|
+
---------------------------- ------- -------------------
|
|
67
|
+
Shuttle Corvid offline /crew/dax-thorne.md
|
|
68
|
+
Number Three Plasma Manifold damaged /crew/bexley-orr.md
|
|
69
|
+
```
|
|
60
70
|
|
|
61
|
-
|
|
71
|
+
### Ask about the links between documents
|
|
62
72
|
|
|
63
|
-
|
|
64
|
-
# What's broken or offline right now?
|
|
65
|
-
mrplex query --filter 'status == "damaged" || status == "offline"'
|
|
73
|
+
Some questions are about *relationships* rather than fields — "who reports to the captain?" The frontmatter in each crew file has a `reports_to` link, and mrplex indexes those into a graph you can query directly. `$has(target, field)` means "which documents link to `target` through `field`":
|
|
66
74
|
|
|
67
|
-
|
|
68
|
-
mrplex query --filter '$
|
|
75
|
+
```sh
|
|
76
|
+
$ mrplex query --filter '$has("crew/kestrel-vance.md", "reports_to")'
|
|
77
|
+
$path
|
|
78
|
+
---------------------
|
|
79
|
+
crew/quill-vasquez.md
|
|
80
|
+
crew/dax-thorne.md
|
|
81
|
+
crew/aria-okonkwo.md
|
|
69
82
|
```
|
|
70
83
|
|
|
71
|
-
|
|
72
|
-
PATH STATUS
|
|
73
|
-
equipment/plasma-manifold-3.md damaged
|
|
74
|
-
equipment/shuttle-corvid.md offline
|
|
75
|
-
…
|
|
84
|
+
### Combine filters, text, and meaning
|
|
76
85
|
|
|
77
|
-
|
|
78
|
-
crew/aria-okonkwo.md Commander Aria Okonkwo
|
|
79
|
-
crew/dax-thorne.md Lieutenant Commander Dax Thorne
|
|
80
|
-
crew/quill-vasquez.md Doctor Quill Vasquez
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
Compose with full-text search — filter, text, and semantic (when configured) all AND together:
|
|
86
|
+
A filter, a full-text search, and a semantic search can all be given at once; results must satisfy all of them (they combine with AND). Here: log entries — `type == "log"` — that also mention "coolant":
|
|
84
87
|
|
|
85
|
-
```
|
|
86
|
-
mrplex query --text '
|
|
87
|
-
|
|
88
|
+
```sh
|
|
89
|
+
$ mrplex query --filter 'type == "log"' --text 'coolant'
|
|
90
|
+
$path
|
|
91
|
+
---------------------
|
|
92
|
+
logs/orr-4413-1.md
|
|
93
|
+
logs/thorne-4413-2.md
|
|
88
94
|
```
|
|
89
95
|
|
|
90
|
-
|
|
96
|
+
By default a query returns just the paths of the matches. You then fetch the full content of only the documents you actually need. This keeps result sets small — which matters a lot when the thing running the query is an agent paying by the token.
|
|
97
|
+
|
|
98
|
+
### Rename a file without breaking its backlinks
|
|
91
99
|
|
|
92
|
-
|
|
100
|
+
This is the payoff of tracking links as a graph. mrplex ties each link to a document's *identity*, not to the path string someone typed. Rename a document and everything that pointed at it still points at it.
|
|
93
101
|
|
|
94
|
-
|
|
95
|
-
# Every mission indexed by the mission log MOC
|
|
96
|
-
mrplex query --filter '$in_static("moc/missions.md")'
|
|
102
|
+
`docs mv` moves a document. The `--prev` flag is how mrplex prevents two writers from clobbering each other: you pass the version you're basing your change on, and the write is rejected if someone else moved first (more on that below).
|
|
97
103
|
|
|
98
|
-
|
|
99
|
-
mrplex
|
|
104
|
+
```sh
|
|
105
|
+
$ V=$(mrplex --json docs get equipment/plasma-manifold-3.md | jq -r .version_id)
|
|
106
|
+
$ mrplex docs mv equipment/manifold-3.md --prev "$V"
|
|
107
|
+
v31
|
|
108
|
+
wrote starship/equipment/manifold-3.md @ v31 (author: mrplex)
|
|
100
109
|
```
|
|
101
110
|
|
|
102
|
-
|
|
111
|
+
Seven documents linked to the old path. Ask the graph who links to the *new* path, and all seven are already there — the relationships followed the document:
|
|
103
112
|
|
|
104
|
-
```
|
|
105
|
-
mrplex
|
|
113
|
+
```sh
|
|
114
|
+
$ mrplex query --filter '$has("equipment/manifold-3.md")'
|
|
115
|
+
$path
|
|
116
|
+
---------------------------
|
|
117
|
+
missions/the-cinder-run.md
|
|
118
|
+
logs/thorne-4413-2.md
|
|
119
|
+
logs/orr-4420-1.md
|
|
120
|
+
logs/orr-4413-1.md
|
|
121
|
+
equipment/coolant-loop-b.md
|
|
122
|
+
crew/dax-thorne.md
|
|
123
|
+
crew/bexley-orr.md
|
|
106
124
|
```
|
|
107
125
|
|
|
108
|
-
The
|
|
126
|
+
The graph is correct, but the *link text* those files still contain on disk (`equipment/plasma-manifold-3.md`) is now out of date. `links stale` shows you exactly where, and `links repair` rewrites it for you:
|
|
109
127
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
128
|
+
```sh
|
|
129
|
+
$ mrplex links stale
|
|
130
|
+
crew/bexley-orr.md: "equipment/plasma-manifold-3.md" → "equipment/manifold-3.md"
|
|
131
|
+
crew/dax-thorne.md: "equipment/plasma-manifold-3.md" → "equipment/manifold-3.md"
|
|
132
|
+
equipment/coolant-loop-b.md: "equipment/plasma-manifold-3.md" → "equipment/manifold-3.md"
|
|
133
|
+
logs/orr-4413-1.md: "equipment/plasma-manifold-3.md" → "equipment/manifold-3.md"
|
|
134
|
+
logs/orr-4420-1.md: "equipment/plasma-manifold-3.md" → "equipment/manifold-3.md"
|
|
135
|
+
logs/thorne-4413-2.md: "equipment/plasma-manifold-3.md" → "equipment/manifold-3.md"
|
|
136
|
+
missions/the-cinder-run.md: "equipment/plasma-manifold-3.md" → "equipment/manifold-3.md"
|
|
137
|
+
$ mrplex links repair
|
|
138
|
+
repaired 7 doc(s), skipped 0
|
|
114
139
|
```
|
|
115
140
|
|
|
116
|
-
###
|
|
117
|
-
|
|
118
|
-
Every write inserts a new version; nothing is overwritten. Rename a document and the link graph follows its identity:
|
|
141
|
+
### Explore a neighborhood
|
|
119
142
|
|
|
120
|
-
|
|
121
|
-
# See current version id, then move the manifold note
|
|
122
|
-
V=$(mrplex --json docs get equipment/plasma-manifold-3.md | jq -r .version_id)
|
|
123
|
-
mrplex docs mv equipment/manifold-3.md --prev "$V"
|
|
143
|
+
`query` finds documents that match. `graph` shows you how a document connects — the links going out of it and coming into it, out to a chosen number of hops. `--render summary` prints it as an indented outline (`→` is a link out, `←` a link in, and the parenthetical is the field the link came from):
|
|
124
144
|
|
|
125
|
-
|
|
126
|
-
mrplex
|
|
127
|
-
|
|
145
|
+
```sh
|
|
146
|
+
$ mrplex graph --roots equipment/manifold-3.md --degrees 1 --render summary
|
|
147
|
+
equipment/manifold-3.md (0)
|
|
148
|
+
→($body) equipment/coolant-loop-b.md, missions/the-cinder-run.md
|
|
149
|
+
→(maintainer) crew/bexley-orr.md
|
|
150
|
+
→(related) equipment/coolant-loop-b.md
|
|
151
|
+
←($body) crew/bexley-orr.md, crew/dax-thorne.md, equipment/coolant-loop-b.md, logs/orr-4413-1.md, ...
|
|
152
|
+
... (each neighbor expands the same way, out to the requested degree)
|
|
128
153
|
```
|
|
129
154
|
|
|
130
|
-
|
|
155
|
+
Use `--render mermaid` instead to get a diagram you can paste into any Markdown renderer.
|
|
131
156
|
|
|
132
|
-
|
|
133
|
-
mrplex docs history equipment/manifold-3.md
|
|
134
|
-
mrplex docs diff equipment/manifold-3.md --from v1 --to v2
|
|
135
|
-
```
|
|
157
|
+
### Versions, always
|
|
136
158
|
|
|
137
|
-
|
|
159
|
+
Every write appends a new version rather than overwriting — so history is never lost. That's also what makes `--prev` work: it's *optimistic concurrency*. You tell a write which version you started from; if someone else has written in the meantime, yours is rejected and you're handed the current version to reconcile against, instead of silently clobbering their change. Deleting a document moves it aside to `:deleted/…` rather than erasing it, so a delete can be undone with an ordinary write. When you need the record, `docs history` lists a document's versions and `docs diff --from v1 --to v2` shows what changed.
|
|
138
160
|
|
|
139
|
-
|
|
161
|
+
## Connect an agent
|
|
140
162
|
|
|
141
|
-
CLI, MCP, and REST are
|
|
163
|
+
The CLI, the MCP server, and the REST API are three doors into the same store. For a database only you touch, the quickest way to give an agent access is MCP over local stdio — for example, in a client's MCP configuration:
|
|
142
164
|
|
|
143
165
|
```json
|
|
144
166
|
{
|
|
145
167
|
"mcpServers": {
|
|
146
168
|
"mrplex": {
|
|
147
169
|
"command": "mrplex",
|
|
148
|
-
"args": ["mcp-stdio", "--unsafe", "--database", "./
|
|
149
|
-
"env": { "MRPLEX_REPO": "starship" }
|
|
170
|
+
"args": ["mcp-stdio", "--unsafe", "--database", "./mrplex.db"]
|
|
150
171
|
}
|
|
151
172
|
}
|
|
152
173
|
}
|
|
153
174
|
```
|
|
154
175
|
|
|
155
|
-
|
|
176
|
+
`--unsafe` runs the store with no authentication — every caller has full access. That's an acceptable choice for a private database file on your own machine, and the wrong choice for anything reachable over a network. The next section is what you use instead — when more than one principal is involved, or when you want to guard against a destructive agent mishap.
|
|
156
177
|
|
|
157
|
-
|
|
178
|
+
## Access control
|
|
158
179
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
180
|
+
When you need real principals — say, an agent that may read and write documents, plus a separate admin identity that can create and delete repos — mrplex generates a starter policy and signing keys for you, so there's no YAML to write by hand:
|
|
181
|
+
|
|
182
|
+
```sh
|
|
183
|
+
$ mrplex policy create policy.yaml --principal agent
|
|
184
|
+
wrote policy.yaml
|
|
185
|
+
$ mrplex key mint agent --policy policy.yaml # a day-to-day key for the agent
|
|
186
|
+
$ mrplex serve --policy policy.yaml --audit audit.jsonl
|
|
162
187
|
```
|
|
163
188
|
|
|
164
|
-
|
|
189
|
+
`policy create` takes the filename to write and scaffolds two roles: a `maintainer` (the `--principal` you name) for everyday reads and writes, and an `operator` admin for repo management. `policy check` validates a policy and can print out exactly what a given principal is allowed to do.
|
|
165
190
|
|
|
166
|
-
|
|
191
|
+
You can enforce that policy in three arrangements: embedded in the server (`serve --policy`), in front of the stdio launcher (`mcp-stdio --policy`), or as a standalone proxy ahead of an existing engine (`proxy --policy --upstream`). Edit the file and send the server `SIGHUP` (`kill -HUP`) to reload it without downtime; `mrplex login` signs in through an OIDC device flow. Full details are in [docs/archive/security.md](docs/archive/security.md).
|
|
167
192
|
|
|
168
|
-
|
|
169
|
-
|--------|----------------|
|
|
170
|
-
| **Document** | One Markdown file with YAML frontmatter, addressed by repo-relative path |
|
|
171
|
-
| **Version** | Every write appends; `prev_version_id` optimistic concurrency rejects stale writes |
|
|
172
|
-
| **Query** | CEL filters over frontmatter + `$path` / `$body` / `$updated_at` intrinsics; composes with FTS and semantic search |
|
|
173
|
-
| **Link graph** | Derived index over inline links, wikilinks, and frontmatter repo-root paths; `$in`, `$has`, `$backlinks`, `$links` in CEL |
|
|
174
|
-
| **Graph** | BFS neighborhood expansion over the link index — *how* things connect, not just *which* match |
|
|
175
|
-
| **Surfaces** | `mrplex` CLI (local or `--server`), MCP at `/mcp` or `mcp-stdio`, REST at `/repos/{repo}/…` |
|
|
176
|
-
|
|
177
|
-
Two layers: a **full-trust kernel** (no in-engine auth) and an optional **access-and-identity shell** (API keys, OIDC, per-path write policy, audit log). See [docs/archive/security.md](docs/archive/security.md) for trust boundaries and deployment shapes.
|
|
178
|
-
|
|
179
|
-
## Features
|
|
180
|
-
|
|
181
|
-
<details>
|
|
182
|
-
<summary>Full feature list</summary>
|
|
183
|
-
|
|
184
|
-
- **Versioned Markdown store** — every write inserts; `docs.put` handles in-place update and move; any past state is addressable
|
|
185
|
-
- **Byte-exact frontmatter** — `frontmatter_raw` (verbatim YAML) or `frontmatter` (JSON); exactly one; round-trips are byte-exact via raw
|
|
186
|
-
- **Optimistic concurrency** — stale `prev_version_id` → `stale_prev` with current version returned
|
|
187
|
-
- **Deletion as move** — `:deleted/…` paths; idempotent delete; restore via `docs.put`
|
|
188
|
-
- **Unified diff** — `docs.diff`, REST `/diff`, MCP `docs_diff`, CLI `mrplex docs diff`; `patch(1)`-applicable output
|
|
189
|
-
- **CEL filter queries** — frontmatter fields + `$`-intrinsics; `list()` polymorphism for scalar-or-list fields
|
|
190
|
-
- **Link graph** — inline, wikilink, and frontmatter path extraction (no per-field config); backlinks survive renames; set algebra (`$in("moc/**") && !$in("moc/draft.md")`); `$in_static` for link-only membership; `links stale` / `repair` / `backfill`
|
|
191
|
-
- **Graph exploration** — BFS with direction lens, visibility filter, graph-only `$degrees` intrinsic; CLI `--render summary|yaml|mermaid|json`
|
|
192
|
-
- **Full-text search** — SQLite FTS5 or Postgres `websearch_to_tsquery`; composes with filter via AND
|
|
193
|
-
- **Semantic search** — pluggable `--embedder` hook; chunker + backlog worker; `$semantic_score` in `select`; no hook → `semantic_unavailable`
|
|
194
|
-
- **HTTP surfaces** — MCP (Streamable HTTP + STDIO), REST with `If-Match` / content negotiation / `MOVE`
|
|
195
|
-
- **CLI** — thin MCP client; `--database` local or `--server` remote; identical commands over both
|
|
196
|
-
- **Storage** — SQLite (default) or Postgres+pgvector; same kernel test suite on both
|
|
197
|
-
- **Path policy** — configurable sigils and disallowed chars; NFC + case-insensitive identity, case-preserved storage
|
|
198
|
-
- **Canonical paths** — API responses use slashless repo-relative paths; leading `/` accepted as root alias on input only
|
|
199
|
-
|
|
200
|
-
Prior design docs in [docs/archive/](docs/archive/) may be out of date where later work supersedes them.
|
|
201
|
-
|
|
202
|
-
</details>
|
|
203
|
-
|
|
204
|
-
## Authentication
|
|
205
|
-
|
|
206
|
-
For anything beyond single-user local use, run the **authenticating shell** — `serve --policy`. It reads declarative YAML (roles, principals, grants, key hashes, OIDC bindings), authenticates each request, and dispatches against a per-principal *guarded* kernel: read visibility narrowed, writes enforced per-path, author derived from the credential, every call audited.
|
|
207
|
-
|
|
208
|
-
```yaml
|
|
209
|
-
# policy.yaml
|
|
210
|
-
roles:
|
|
211
|
-
editor:
|
|
212
|
-
grants:
|
|
213
|
-
- repo: notes
|
|
214
|
-
read: "**"
|
|
215
|
-
write: ["drafts/**", "inbox/**"]
|
|
216
|
-
operator:
|
|
217
|
-
grants:
|
|
218
|
-
- { repo: "*", read: "**", write: "**" }
|
|
219
|
-
destructive: true
|
|
220
|
-
|
|
221
|
-
principals:
|
|
222
|
-
brendan:
|
|
223
|
-
author: Brendan Baldwin <brendan@example.com>
|
|
224
|
-
roles: [operator]
|
|
225
|
-
keys:
|
|
226
|
-
- sha256:... # `mrplex key mint brendan --policy policy.yaml`
|
|
227
|
-
ann:
|
|
228
|
-
roles: [editor]
|
|
229
|
-
oidc: { email: ann@example.com }
|
|
230
|
-
```
|
|
193
|
+
One rule worth repeating: never expose `serve --unsafe` to an untrusted network — it is, by definition, an open door.
|
|
231
194
|
|
|
232
|
-
|
|
233
|
-
mrplex key mint brendan --policy policy.yaml
|
|
234
|
-
mrplex serve --policy policy.yaml --audit audit.jsonl --port 8321 &
|
|
235
|
-
curl -H "Authorization: Bearer $KEY" http://127.0.0.1:8321/repos
|
|
236
|
-
```
|
|
195
|
+
## How it works
|
|
237
196
|
|
|
238
|
-
|
|
197
|
+
mrplex is built from a few concepts:
|
|
239
198
|
|
|
240
|
-
|
|
199
|
+
| Concept | What it is |
|
|
200
|
+
| -------------- | ------------------------------------------------------------------------------------------ |
|
|
201
|
+
| **Document** | One Markdown file with YAML frontmatter, addressed by its path within a repo |
|
|
202
|
+
| **Version** | Every write appends a new one; `--prev` rejects a write based on a stale version |
|
|
203
|
+
| **Query** | A CEL filter over frontmatter and `$path` / `$body` / `$updated_at`, combined with full-text and semantic search |
|
|
204
|
+
| **Link graph** | Links from Markdown syntax, wikilinks, and frontmatter paths, tracked by identity so renames don't break them |
|
|
205
|
+
| **Graph walk** | A breadth-first tour of that link graph — *how* documents connect, not just which ones match |
|
|
206
|
+
| **Surfaces** | The CLI, an MCP server, and a REST API, all over the same store |
|
|
241
207
|
|
|
242
|
-
mrplex
|
|
208
|
+
Underneath, mrplex is two layers. The **kernel** is the store itself — documents, versions, queries, the graph — and it is full-trust: it has no notion of users, so whoever holds the database file holds everything. Around it is an optional **access-and-identity shell** that adds keys, OIDC, per-path permissions, and an audit log when you need them. The store runs on SQLite by default, or Postgres with pgvector when you want it; the same test suite runs against both, so behavior matches.
|
|
243
209
|
|
|
244
|
-
|
|
245
|
-
npm install -g @mrplex/embedder
|
|
246
|
-
export MRPLEX_EMBEDDER=mrplex-embedder # or: mrplex config set-embedder mrplex-embedder
|
|
210
|
+
## Semantic search
|
|
247
211
|
|
|
248
|
-
mrplex
|
|
249
|
-
mrplex embed backfill -r starship
|
|
250
|
-
mrplex query -r starship --semantic 'distress beacon star map' # uses env/config
|
|
251
|
-
# or per-invocation in local (--database) mode:
|
|
252
|
-
mrplex query -r starship --embedder mrplex-embedder --semantic 'distress beacon star map'
|
|
253
|
-
```
|
|
212
|
+
mrplex never calls an embedding provider on its own — you decide what does the embedding by wiring in a hook. The companion package `@mrplex/embedder` runs a small local model on CPU, which is enough to get started:
|
|
254
213
|
|
|
255
|
-
|
|
214
|
+
```sh
|
|
215
|
+
$ npm install -g @mrplex/embedder
|
|
216
|
+
$ mrplex serve --unsafe --embedder mrplex-embedder
|
|
217
|
+
$ mrplex embed backfill # embed everything already in the repo
|
|
218
|
+
$ mrplex query --semantic 'distress beacon star map'
|
|
219
|
+
```
|
|
256
220
|
|
|
257
|
-
|
|
221
|
+
Point `--embedder` at a command (like the one above) or at an `http(s)://` URL for a remote embedding service. If no hook is configured, a semantic query returns a clear `semantic_unavailable` error rather than silently ranking by nothing. And treat semantic results as a shortlist of candidates worth reading, not a final answer — skim the top hits before you rely on them. The hook protocol is documented in [packages/embedder/README.md](packages/embedder/README.md).
|
|
258
222
|
|
|
259
|
-
|
|
260
|
-
npm install
|
|
261
|
-
npm link
|
|
262
|
-
npm test
|
|
263
|
-
npm run typecheck
|
|
264
|
-
npm run lint
|
|
265
|
-
npm run build
|
|
266
|
-
```
|
|
223
|
+
## Configuration
|
|
267
224
|
|
|
268
|
-
|
|
225
|
+
Every setting resolves in the same order: a command-line **flag**, then an **environment variable**, then the **config file**, then a built-in default. Save the defaults you're tired of typing:
|
|
269
226
|
|
|
270
|
-
```
|
|
271
|
-
|
|
227
|
+
```sh
|
|
228
|
+
$ mrplex config set-database ./mrplex.db
|
|
229
|
+
$ mrplex config set-author "Ada Lovelace <ada@example.com>"
|
|
272
230
|
```
|
|
273
231
|
|
|
274
|
-
|
|
232
|
+
The matching environment variables are `MRPLEX_DATABASE`, `MRPLEX_REPO`, `MRPLEX_AUTHOR`, and `MRPLEX_EMBEDDER`.
|
|
275
233
|
|
|
276
|
-
|
|
234
|
+
A note on repo scope: your default repo (from `config set-repo` or `MRPLEX_REPO`) applies to every command, `query` included. To search more than one repo, say so on that command: `query -r` can be repeated and accepts glob patterns, and `-r '*'` searches every repo in the database. The wildcard only works as an explicit flag — a saved default can never quietly widen a search to your whole database.
|
|
277
235
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
236
|
+
## Development
|
|
237
|
+
|
|
238
|
+
```sh
|
|
239
|
+
$ npm install && npm link
|
|
240
|
+
$ npm test
|
|
241
|
+
$ npm run typecheck && npm run lint && npm run build
|
|
282
242
|
```
|
|
283
243
|
|
|
244
|
+
CI runs typecheck, lint, and tests on Ubuntu and macOS across Node 20 and 22, plus a Postgres + pgvector parity run (`npm run pg:up` starts a local instance). If `better-sqlite3` reports a `NODE_MODULE_VERSION` mismatch, rebuild it with `npm rebuild better-sqlite3`.
|
|
245
|
+
|
|
284
246
|
## License
|
|
285
247
|
|
|
286
248
|
MIT — see [LICENSE](LICENSE).
|
package/dist/cli/config.d.ts
CHANGED
|
@@ -18,10 +18,6 @@ export type CliConfig = {
|
|
|
18
18
|
author?: string;
|
|
19
19
|
/** Default --embedder (command or http(s):// URL). */
|
|
20
20
|
embedder?: string;
|
|
21
|
-
/** @deprecated Use `embedder`. */
|
|
22
|
-
embed_url?: string;
|
|
23
|
-
/** @deprecated Use `embedder`. */
|
|
24
|
-
embed_cmd?: string;
|
|
25
21
|
};
|
|
26
22
|
export declare function configPath(): string;
|
|
27
23
|
export declare function loadConfig(): CliConfig;
|
package/dist/cli/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/cli/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC5E,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/cli/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC5E,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAY1C,MAAM,UAAU,UAAU;IACxB,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,CAAC;IAC5E,OAAO,IAAI,CAAC,SAAS,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC;AAClD,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,MAAM,IAAI,GAAG,UAAU,EAAE,CAAC;IAC1B,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACxC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAY,CAAC;QAC3C,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YAC3E,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,OAAO,MAAmB,CAAC;IAC7B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,2EAA2E;QAC3E,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,EAAE,CAAC;QAChE,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,IAAe;IACxC,MAAM,IAAI,GAAG,UAAU,EAAE,CAAC;IAC1B,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,aAAa,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC3E,IAAI,CAAC;QACH,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACzB,CAAC;IAAC,MAAM,CAAC;QACP,yDAAyD;IAC3D,CAAC;AACH,CAAC"}
|