@oomkapwn/enquire-mcp 0.7.4

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 ADDED
@@ -0,0 +1,358 @@
1
+ <div align="center">
2
+
3
+ <a href="https://github.com/oomkapwn/enquire-mcp"><img src="./assets/social-preview.png" alt="enquire — MCP server for Obsidian vaults. Wikilinks, frontmatter, backlinks, Dataview, MCP resources & prompts. Named after Tim Berners-Lee's 1980 prototype of the WWW." width="100%"></a>
4
+
5
+ # enquire
6
+
7
+ ### MCP server for Obsidian vaults
8
+
9
+ **Give Claude Code, Cursor, Codex, and Devin first-class access to your Obsidian vault — wikilinks resolved, frontmatter typed, backlinks indexed, Dataview queries, MCP resources, and read-only safety by default.**
10
+
11
+ [![CI](https://github.com/oomkapwn/enquire-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/oomkapwn/enquire-mcp/actions/workflows/ci.yml)
12
+ [![npm version](https://img.shields.io/npm/v/@oomkapwn/enquire-mcp.svg)](https://www.npmjs.com/package/@oomkapwn/enquire-mcp)
13
+ [![License: MIT](https://img.shields.io/badge/license-MIT-yellow.svg)](./LICENSE)
14
+ [![Node](https://img.shields.io/badge/node-%E2%89%A520-brightgreen.svg)](#develop)
15
+ [![MCP](https://img.shields.io/badge/MCP-1.29-8A2BE2.svg)](https://modelcontextprotocol.io/)
16
+ [![tests](https://img.shields.io/badge/tests-140%20passing-brightgreen.svg)](#develop)
17
+ [![coverage](https://img.shields.io/badge/coverage-82%25%20lines-brightgreen.svg)](#develop)
18
+ [![lint](https://img.shields.io/badge/lint-biome-60a5fa.svg)](https://biomejs.dev/)
19
+
20
+ </div>
21
+
22
+ > **enquire** is an [MCP](https://modelcontextprotocol.io/) server purpose-built for **[Obsidian](https://obsidian.md/) vaults**. Drop it in front of any vault and your AI assistant stops guessing at filesystem paths and starts reasoning about your notes the way you do — following `[[wikilinks]]`, respecting frontmatter, walking backlinks, running Dataview-style queries, all over stdio MCP.
23
+ >
24
+ > Named after [**ENQUIRE**](https://en.wikipedia.org/wiki/ENQUIRE) — the program Tim Berners-Lee wrote at CERN in 1980 to track «the complex web of relationships between people, programs, machines and ideas». ENQUIRE was the direct prototype of the World Wide Web. enquire-mcp brings the same idea to your AI: hyperlinked notes, structured access, no plugin required.
25
+
26
+ ```text
27
+ You: "What was I working on yesterday in the Apollo project?"
28
+ Claude: → obsidian_get_recent_edits({ since_minutes: 1440, folder: "01_Projects" })
29
+ → obsidian_read_note({ title: "Apollo" })
30
+ → obsidian_get_backlinks({ title: "Apollo" })
31
+ "You shipped the v0.3 spec, opened 3 open questions in [[Apollo/Open Threads]],
32
+ and 2 daily notes link back to it. Top blocker: the auth review."
33
+ ```
34
+
35
+ ---
36
+
37
+ ## Why enquire exists (vs other Obsidian-MCP options)
38
+
39
+ There are several Obsidian-MCP servers out there. enquire differentiates on three axes — **standalone**, **read-rich**, and **safe-by-default**:
40
+
41
+ | Capability (✅ = good for you) | Most Obsidian-MCPs | enquire |
42
+ |---|:---:|:---:|
43
+ | Works with `.md` files | ✅ | ✅ |
44
+ | **Standalone** — runs without Obsidian's Local REST API plugin | ❌ usually requires it | ✅ direct vault read |
45
+ | Resolves `[[Wikilink]]` with alias, section, block, `../` relative | partial | ✅ full |
46
+ | Surfaces `![[Embed]]` separately from links | ❌ | ✅ |
47
+ | Finds every note linking to X (**backlinks**) | rare | ✅ ranked + snippets |
48
+ | Finds every **broken `[[wikilink]]`** in the vault | ❌ | ✅ vault-hygiene tool |
49
+ | Lists **outbound links** for one note with resolution status | ❌ | ✅ |
50
+ | Built-in **Dataview-style queries** (`LIST` / `TABLE`, `AND`/`OR`, `LIKE`) | only via Obsidian plugin | ✅ first-class |
51
+ | **MCP resources** for browsing the vault as a tree | ❌ | ✅ |
52
+ | **MCP prompts** (`summarize_recent`, `weekly_review`, `find_orphans`, `extract_todos`, `process_inbox`) | ❌ | ✅ 6 prompts |
53
+ | **Read-only by default** (write tools require explicit flag) | ❌ usually write-default | ✅ `--enable-write` |
54
+ | Symlink-escape safety, realpath-checked reads & writes | rare | ✅ |
55
+ | Persistent on-disk cache for warm cold-starts | ❌ | ✅ `--persistent-cache` |
56
+ | TypeScript strict + Biome lint + 140 unit tests | varies | ✅ |
57
+
58
+ That's the gap. enquire closes it in ~2000 lines of TypeScript and four runtime dependencies.
59
+
60
+ > **Not affiliated with Obsidian.md.** Obsidian and the Obsidian logo are trademarks of Dynalist Inc. enquire-mcp is an independent open-source project that reads Obsidian-format vaults. The name «enquire» is a tribute to Tim Berners-Lee's 1980 hypertext system, not a trademark claim against any party.
61
+
62
+ ---
63
+
64
+ ## Who is this for?
65
+
66
+ - **Obsidian users on Claude Code / Cursor / Codex / Devin** (or any MCP-compatible client) who want the assistant to draft notes that actually link properly, follow `[[…]]`, and respect frontmatter.
67
+ - **Agentic workflow builders** who need a structured layer over a markdown vault — `dataview_query`, `get_backlinks`, `list_tags` are the kind of primitives that compose into real automations.
68
+ - **Tinkerers** who want to wire their PKM into LLM pipelines without writing a parser. We did the parsing.
69
+
70
+ ---
71
+
72
+ ## Quick start
73
+
74
+ ```bash
75
+ # 1. Get the code
76
+ git clone https://github.com/oomkapwn/enquire-mcp
77
+ cd enquire-mcp && npm install && npm run build
78
+
79
+ # 2. Wire into Claude Code (~/.claude.json or .mcp.json)
80
+ ```
81
+
82
+ ```json
83
+ {
84
+ "mcpServers": {
85
+ "obsidian": {
86
+ "command": "node",
87
+ "args": [
88
+ "/absolute/path/to/enquire-mcp/dist/index.js",
89
+ "serve",
90
+ "--vault", "/Users/you/Documents/Obsidian Vault"
91
+ ]
92
+ }
93
+ }
94
+ }
95
+ ```
96
+
97
+ <details>
98
+ <summary><b>Or use <code>npx</code> (no global install)</b></summary>
99
+
100
+ ```json
101
+ {
102
+ "mcpServers": {
103
+ "obsidian": {
104
+ "command": "npx",
105
+ "args": ["-y", "@oomkapwn/enquire-mcp", "serve", "--vault", "/Users/you/Documents/Obsidian Vault"]
106
+ }
107
+ }
108
+ }
109
+ ```
110
+
111
+ </details>
112
+
113
+ <details>
114
+ <summary><b>Or install globally</b></summary>
115
+
116
+ ```bash
117
+ npm install -g @oomkapwn/enquire-mcp
118
+ enquire-mcp serve --vault ~/Documents/Obsidian\ Vault
119
+ ```
120
+
121
+ </details>
122
+
123
+ Restart your client. The server logs `enquire <version> ready (read-only, vault=…)` on stderr — that's your "it's connected" signal.
124
+
125
+ ---
126
+
127
+ ## What you get
128
+
129
+ ### 10 read tools (always on)
130
+
131
+ | Tool | What it does |
132
+ |---|---|
133
+ | `obsidian_list_notes` | Filter by tag / folder / modified-since. Returns title, path, frontmatter, tags, mtime — newest first. |
134
+ | `obsidian_read_note` | Body + frontmatter + wikilinks + embeds + tags for a note (by path or title). |
135
+ | `obsidian_resolve_wikilink` | `[[Note]]`, `[[Note#Heading]]`, `[[Folder/Note\|alias]]`, `![[Embed]]`, `[[../relative/path]]` — all resolved to a real file. |
136
+ | `obsidian_search_text` | Ranked case-insensitive substring search with snippets and line numbers. |
137
+ | `obsidian_get_recent_edits` | Newest-first stream, optional time window. |
138
+ | `obsidian_get_backlinks` | Every note linking the target, ranked by hit count, with snippets. Distinguishes wikilink vs embed vs mixed. |
139
+ | `obsidian_get_outbound_links` | Symmetric counterpart to backlinks — every link a note points to, with resolution status. |
140
+ | `obsidian_get_unresolved_wikilinks` | Vault-hygiene: every `[[broken]]` link in the vault. |
141
+ | `obsidian_list_tags` | Every unique tag with frontmatter / inline counts. |
142
+ | `obsidian_dataview_query` | `LIST` / `TABLE` with `FROM`, `WHERE`, `SORT`, `LIMIT`. Supports `AND` / `OR` / `=` / `!=` / `contains` / `like`. |
143
+
144
+ ### 2 write tools (opt-in via `--enable-write`)
145
+
146
+ | Tool | What it does |
147
+ |---|---|
148
+ | `obsidian_create_note` | Create a note with optional frontmatter. Refuses to overwrite by default. |
149
+ | `obsidian_append_to_note` | Append a markdown block to an existing note. Configurable separator. |
150
+
151
+ ### MCP resources
152
+
153
+ - `obsidian://vault/info` — root, note count, write flag, byte/cache limits, server version.
154
+ - `obsidian://note/{path}` — every note as a first-class browsable resource. Compatible clients show your vault as a tree.
155
+
156
+ ### MCP prompts
157
+
158
+ | Prompt | Args | What it scaffolds |
159
+ |---|---|---|
160
+ | `summarize_recent_edits` | `since_minutes?` | "What was I working on?" workflow. |
161
+ | `review_tag` | `tag` | Pull every note for a tag, surface open threads. |
162
+ | `find_orphans` | `folder?` | Notes with zero inbound links — archive candidates. |
163
+ | `weekly_review` | `folder?` | Last 7 days of edits, grouped by tag — shipped / open / stuck. |
164
+ | `extract_todos` | `folder?`, `tag?` | Every TODO / FIXME / QUESTION, grouped by note. |
165
+ | `process_inbox` | `folder` | Move / Merge / Promote / Archive proposals for an inbox folder. |
166
+
167
+ ---
168
+
169
+ ## Example workflows
170
+
171
+ ### 1. Scan tagged ideas
172
+ > "Show me notes tagged `#idea` from the last two weeks."
173
+
174
+ `obsidian_list_notes({ tag: "idea", since_date: "2026-04-18" })`
175
+
176
+ ### 2. Follow wikilinks
177
+ > "Read [[Project Apollo]] and summarise the open questions."
178
+
179
+ `obsidian_resolve_wikilink({ wikilink: "Project Apollo" })` → walk any `[[…]]` inside the result.
180
+
181
+ ### 3. Pick up where you left off
182
+ > "What was I editing today?"
183
+
184
+ `obsidian_get_recent_edits({ since_minutes: 720 })`
185
+
186
+ ### 4. Audit a hub note
187
+ > "Which notes link to [[Project Apollo]]?"
188
+
189
+ `obsidian_get_backlinks({ title: "Project Apollo" })` → ranked list with snippets.
190
+
191
+ ### 5. Run a Dataview-style query
192
+ > "List active ideas, sorted by mtime."
193
+
194
+ ```text
195
+ TABLE status FROM #idea WHERE status = "active" SORT file.mtime DESC
196
+ ```
197
+
198
+ ### 6. Daily journaling (write mode)
199
+ > "Append a 'shipped today' bullet to today's daily note."
200
+
201
+ With `--enable-write`: `obsidian_append_to_note({ title: "2026-05-03", content: "- Shipped enquire v0.7.1" })`
202
+
203
+ ---
204
+
205
+ ## Architecture
206
+
207
+ ```
208
+ ┌─────────────────┐ stdio JSON-RPC ┌─────────────────────┐
209
+ │ Claude Code / │ ◄────────────────────► │ enquire │
210
+ │ Cursor / Codex │ tools/resources/ │ (this server) │
211
+ └─────────────────┘ prompts └─────────┬───────────┘
212
+
213
+ ┌───────────┼────────────┐
214
+ │ │ │
215
+ ▼ ▼ ▼
216
+ ┌─────────┐ ┌──────────┐ ┌──────────┐
217
+ │ Vault │ │ Parser │ │ DQL │
218
+ │ walker │ │ (gray- │ │ engine │
219
+ │ + cache │ │ matter) │ │ │
220
+ └────┬────┘ └──────────┘ └──────────┘
221
+
222
+
223
+ ┌─────────────────────┐
224
+ │ ~/Documents/ │
225
+ │ Obsidian Vault/ │
226
+ │ *.md *.md *.md ... │
227
+ └─────────────────────┘
228
+ ```
229
+
230
+ - **Vault walker** — recursive, skips `.git` / `.obsidian` / `.trash` / dot-dirs / symlinks. Realpath-checks every read and write to prevent symlink-escape attacks.
231
+ - **Cache** — mtime-keyed, LRU-evicted. Default cap 1024 entries.
232
+ - **Parser** — `gray-matter` for YAML, hand-rolled regex for wikilinks / embeds / tags. Fenced code blocks are stripped before tag extraction.
233
+ - **DQL engine** — quote-aware tokenizer for keywords (`FROM`, `WHERE`, `SORT`, `LIMIT`, `AND`); won't mis-split on `WHERE x = "foo SORT bar"`.
234
+
235
+ ---
236
+
237
+ ## Configuration
238
+
239
+ | Flag | Default | What it does |
240
+ |---|---|---|
241
+ | `--vault <path>` | (required) | Path to the Obsidian vault root. |
242
+ | `--enable-write` | off | Register the two write tools. Server is otherwise strictly read-only. |
243
+ | `--max-file-bytes <n>` | 5 MB | Refuse to read or write any file larger. |
244
+ | `--cache-size <n>` | 1024 | LRU cap for the parsed-note cache. |
245
+ | `--persistent-cache` | off | Persist parsed-note cache to disk; warm cold-starts on large vaults. **Privacy: full note bodies are written to the cache file. See "Cache & privacy" below.** |
246
+ | `--cache-file <path>` | auto | Override persistent-cache file location. |
247
+
248
+ ### Cache & privacy
249
+
250
+ When `--persistent-cache` is on, parsed notes are serialized to disk so subsequent server restarts skip re-parsing. By default the file lives at `~/Library/Caches/enquire/<sha1>.json` on macOS, `~/.cache/enquire/<sha1>.json` on Linux. Important caveats:
251
+
252
+ - **Full note bodies are stored**, not just metadata. Anyone who can read your home cache directory can read your vault.
253
+ - **File mode is `0600`** (user-read/write only) and the parent directory is `0700`. We don't trust shared home directories.
254
+ - **Deleted notes are purged** on the next server start: when enquire sees a cached entry whose source file no longer exists, it drops the entry from memory and rewrites the cache file without it on shutdown.
255
+ - **Stale entries** (file mtime changed since cache write) are silently dropped on load; the source file is re-parsed.
256
+ - **Manual purge:** run `enquire-mcp clear-cache --vault <path>` to delete the cache file for a specific vault.
257
+ - The cache file is **never read or written for vaults other than the one whose realpath matches the cache file's `root` field** — protects against cross-vault content leaks if you share a cache dir.
258
+
259
+ The server logs `WRITE-ENABLED` to stderr on boot when the flag is on, so you can verify the mode at a glance.
260
+
261
+ ---
262
+
263
+ ## Security
264
+
265
+ - **Read-only by default.** Write tools are not even registered unless `--enable-write` is passed.
266
+ - **Path traversal blocked.** Every resolved path is checked against the vault root via `realpath`.
267
+ - **Symlink-escape blocked.** Symlinks inside the vault that resolve outside (file or directory) are skipped on listing and rejected on read/write.
268
+ - **DoS guard.** Default 5 MB cap on any single file read or write; bounded LRU cache.
269
+ - **YAML.** Parsed via `gray-matter` (`js-yaml` `safeLoad` under the hood) — no code execution.
270
+
271
+ Found a security issue? See [SECURITY.md](./SECURITY.md).
272
+
273
+ ---
274
+
275
+ ## FAQ
276
+
277
+ **Does it support Roam / Logseq / TiddlyWiki?**
278
+ No. Obsidian's wikilink semantics, frontmatter conventions, and folder structure are baked in. Other tools are out of scope.
279
+
280
+ **Will it modify my vault?**
281
+ Not unless you start it with `--enable-write`. By default the server is strictly read-only. With write enabled, the two write tools refuse to overwrite existing notes (`obsidian_create_note` requires `overwrite=true`) and refuse to write outside the vault even if a parent dir is symlinked away.
282
+
283
+ **Does it work over the network?**
284
+ No. It's a local stdio MCP server, designed for one client process per vault. There's no HTTP transport, no auth, no rate limiting — and that's intentional.
285
+
286
+ **My DQL query returned nothing.**
287
+ Verify the source. `LIST FROM "01_Projects"` matches notes whose path starts with `01_Projects/`. `LIST FROM #idea` matches notes carrying the `idea` tag. Mix them with `WHERE`. See [docs/api.md](./docs/api.md) for the supported subset and grammar.
288
+
289
+ **What about the full Dataview plugin?**
290
+ We implement a deliberately small subset (`LIST` / `TABLE`, `FROM "folder" | #tag`, `WHERE pred (AND|OR pred)*`, `SORT`, `LIMIT`; ops `=`, `!=`, `contains`, `like`). No arithmetic / functions / `FLATTEN` / `GROUP BY` / joins / parenthesized precedence. PRs that close those gaps are welcome — see [CONTRIBUTING.md](./CONTRIBUTING.md).
291
+
292
+ **How big a vault can it handle?**
293
+ Tested daily against a 117-note vault. The walker is O(notes) per call; the cache makes repeat reads O(1). For 10k+ vaults a persistent index would help — that's on the Phase 3 roadmap.
294
+
295
+ **Why scoped npm name (`@oomkapwn/enquire-mcp`)?**
296
+ A scoped name protects the brand and side-steps the very crowded `obsidian-mcp` / `mcp-obsidian` namespace on npm. The CLI binary is `enquire-mcp` to match the npm package name.
297
+
298
+ ---
299
+
300
+ ## Develop
301
+
302
+ ```bash
303
+ npm test # 130+ unit tests
304
+ npm run test:coverage # vitest --coverage (v8 provider)
305
+ npm run lint # biome check
306
+ npm run lint:fix # biome check --write (auto-fixes)
307
+ npm run dev # tsc --watch
308
+ node scripts/smoke.mjs [vault-path] # end-to-end JSON-RPC smoke
309
+ ```
310
+
311
+ Coverage on the latest release: **82% lines · 78% statements · 73% branches**. CI uploads the full HTML report as a workflow artifact (`coverage-report`).
312
+
313
+ Build runs `tsc` and marks `dist/index.js` executable. CI tests Node 20 / 22 / 24, runs the smoke against a synthetic vault, generates a coverage report, and runs `npm audit --audit-level=high`.
314
+
315
+ ---
316
+
317
+ ## Troubleshooting
318
+
319
+ - **"Vault not found" on boot** — pass an absolute path or `~`-prefixed shell-expanded path to `--vault`. Relative paths resolve against the process cwd.
320
+ - **"Path escapes vault root"** — the path you passed (or a wikilink resolved to) leaves the vault, often via a symlink. The server intentionally refuses these reads.
321
+ - **"File too large" on a `.md` you didn't expect** — usually a sync conflict or binary accidentally renamed `.md`. Bump `--max-file-bytes` if you really want it ingested.
322
+ - **Write tool not visible to the client** — start the server with `--enable-write`, *after* the `serve` subcommand.
323
+ - **Russian / non-ASCII titles** — supported. Both inline `#тег` and YAML `tags: [идея]` are picked up.
324
+
325
+ ---
326
+
327
+ ## Versioning & releases
328
+
329
+ Semantic versioning. See [CHANGELOG.md](./CHANGELOG.md) for the full history.
330
+
331
+ - **0.7.x** — current. Renamed twice (`obsidian-mcp` → `memex` → `enquire-mcp`) to escape the crowded `obsidian-mcp` namespace and to land on a name with a clear historical referent — Tim Berners-Lee's 1980 ENQUIRE prototype of the Web. 10 read tools, 2 opt-in write tools, MCP resources + prompts, persistent on-disk cache (opt-in), DQL with `AND`/`OR`/`LIKE`, hardened security.
332
+ - **Roadmap** — graph queries (multi-hop, hub/orphan detection); refactoring tools (`rename_note`, `rename_tag` with wikilink rewrite); DQL expressions / parentheses / `FLATTEN` / `GROUP BY`; performance benchmarks against 10k+ vaults.
333
+
334
+ ---
335
+
336
+ ## Contributing
337
+
338
+ Bug fixes welcome. New tools should pass the bar of "useful against an Obsidian vault on day 1." See [CONTRIBUTING.md](./CONTRIBUTING.md).
339
+
340
+ ---
341
+
342
+ ## Support the project
343
+
344
+ If enquire-mcp saves you keystrokes or makes your AI assistant smarter about your vault, **consider [starring the repo](https://github.com/oomkapwn/enquire-mcp) to show your ❤️ and support.** Stars help other Obsidian users find the project, and they tell us where to invest the next cycle.
345
+
346
+ Other ways to help:
347
+ - 🐛 **File a bug** — the more concrete the repro (vault shape, exact tool call, server stderr), the faster the fix. Use [the bug template](https://github.com/oomkapwn/enquire-mcp/issues/new?template=bug_report.yml).
348
+ - 💡 **Propose a feature** — open a [feature request](https://github.com/oomkapwn/enquire-mcp/issues/new?template=feature_request.yml) and we'll align on scope before any code is written.
349
+ - 🔧 **Send a PR** — bug fixes always welcome. New tools should pass the «useful against an Obsidian vault on day 1» bar. See [CONTRIBUTING.md](./CONTRIBUTING.md).
350
+ - 💬 **Tell us how you use it** — open a thread in [Discussions](https://github.com/oomkapwn/enquire-mcp/discussions) and we'll figure out what to build next from real workflows.
351
+
352
+ ---
353
+
354
+ ## License & credits
355
+
356
+ [MIT](./LICENSE). Built by [@OomkaBear](https://github.com/oomkapwn). Powered by [Model Context Protocol](https://modelcontextprotocol.io/), [`gray-matter`](https://github.com/jonschlinkert/gray-matter), [`commander`](https://github.com/tj/commander.js), and the patience of one specific Obsidian vault that didn't deserve to be parsed by hand.
357
+
358
+ Named after [ENQUIRE](https://en.wikipedia.org/wiki/ENQUIRE) — the program Tim Berners-Lee wrote at CERN in 1980 to track «the complex web of relationships between people, programs, machines and ideas». ENQUIRE was the direct prototype of the World Wide Web. enquire-mcp brings the same idea to your AI: hyperlinked notes, structured access, no plugin required.
package/SECURITY.md ADDED
@@ -0,0 +1,49 @@
1
+ # Security policy
2
+
3
+ ## Reporting a vulnerability
4
+
5
+ If you've found a security issue in enquire, **please don't open a public GitHub issue**. Instead:
6
+
7
+ 1. Email the maintainer at `oomkapwn@gmail.com` with the subject `enquire security`.
8
+ 2. Include a reproducer if you have one — vault layout, exact CLI flags, the operation that triggered the issue.
9
+ 3. Expect an acknowledgement within 72 hours.
10
+
11
+ I'll work on a fix in private, cut a patch release, and then publicly disclose with credit (or anonymously, your call).
12
+
13
+ ## Scope
14
+
15
+ In scope:
16
+ - Path traversal, symlink-escape, or any way to read/write files outside the configured vault root
17
+ - Resource exhaustion (DoS) via crafted markdown, frontmatter, or DQL input
18
+ - Unintended code execution via YAML, regex, or input parsing
19
+ - Cache or memory issues that grow unbounded under attacker-controlled input
20
+
21
+ Out of scope (won't accept reports):
22
+ - Behavior controlled by `--enable-write` — yes, write tools can write notes; that's the point. Reports here need to show writes outside the vault or other privilege escalation.
23
+ - Issues that require a malicious MCP client (the client is the trusted party; if it's compromised, all bets are off).
24
+ - Vulnerabilities in dependencies — please report those upstream first.
25
+
26
+ ## Supported versions
27
+
28
+ Only the latest minor release receives security patches. We bump the patch version for security fixes and call them out clearly in [CHANGELOG.md](./CHANGELOG.md).
29
+
30
+ ## Hardening already in place
31
+
32
+ - Realpath-based check on every read and write target — symlinks inside the vault that resolve outside are rejected.
33
+ - Walker skips symlinks entirely.
34
+ - Default 5 MB cap on any single file read or write (configurable via `--max-file-bytes`). Persistent-cache load enforces the same per-entry cap.
35
+ - Bounded parsed-note cache (default 1024 entries, LRU eviction). Persistent cache file is bounded at 50 MB by default.
36
+ - Read-only by default; write tools require an explicit CLI flag.
37
+ - YAML parsed via `gray-matter` (`js-yaml` safeLoad) — no code execution.
38
+ - DQL parser respects quoted strings; no shell, no `eval`, no template expansion. Empty `OR`/`AND` groups and empty `FROM #` / `FROM ""` are rejected to prevent silently-overbroad queries.
39
+
40
+ ## Persistent cache: privacy posture
41
+
42
+ When `--persistent-cache` is enabled, full note bodies are written to a JSON file under `~/Library/Caches/enquire/` (macOS) or `~/.cache/enquire/` (Linux).
43
+
44
+ - File mode is **`0600`**, parent directory mode is **`0700`** — restricted to the user account.
45
+ - Cache file is rejected if its `root` field doesn't match the current vault realpath (cross-vault protection).
46
+ - Cache file is rejected if its declared `version` doesn't match the current schema version.
47
+ - Deleted notes: on load, entries whose source file no longer exists are dropped from memory AND the cache is marked dirty so the next save rewrites the file without those entries.
48
+ - Manual purge: `enquire-mcp clear-cache --vault <path>` deletes the cache file.
49
+ - **Caveat:** anyone with read access to your user account can read the cache file. If your threat model includes other local users on the same machine, do not use `--persistent-cache`.
Binary file
package/dist/dql.d.ts ADDED
@@ -0,0 +1,37 @@
1
+ import type { Vault } from "./vault.js";
2
+ export type Source = {
3
+ type: "all";
4
+ } | {
5
+ type: "folder";
6
+ path: string;
7
+ } | {
8
+ type: "tag";
9
+ tag: string;
10
+ };
11
+ export type Op = "=" | "!=" | "contains" | "like";
12
+ export interface Predicate {
13
+ field: string;
14
+ op: Op;
15
+ value: string | number | boolean | null;
16
+ }
17
+ /** A WHERE clause is a disjunction of conjunctions: (A AND B) OR (C AND D). */
18
+ export type WhereGroups = Predicate[][];
19
+ export interface DataviewQuery {
20
+ kind: "LIST" | "TABLE";
21
+ columns: string[];
22
+ source: Source;
23
+ where: WhereGroups;
24
+ sort?: {
25
+ field: string;
26
+ dir: "ASC" | "DESC";
27
+ };
28
+ limit?: number;
29
+ }
30
+ export declare class DqlParseError extends Error {
31
+ }
32
+ export declare function parseDql(input: string): DataviewQuery;
33
+ export declare const DEFAULT_DQL_ROW_LIMIT = 1000;
34
+ export declare function runDql(vault: Vault, query: DataviewQuery, opts?: {
35
+ defaultLimit?: number;
36
+ }): Promise<Array<Record<string, unknown>>>;
37
+ //# sourceMappingURL=dql.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dql.d.ts","sourceRoot":"","sources":["../src/dql.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAa,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnD,MAAM,MAAM,MAAM,GAAG;IAAE,IAAI,EAAE,KAAK,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvG,MAAM,MAAM,EAAE,GAAG,GAAG,GAAG,IAAI,GAAG,UAAU,GAAG,MAAM,CAAC;AAElD,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,EAAE,CAAC;IACP,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;CACzC;AAED,+EAA+E;AAC/E,MAAM,MAAM,WAAW,GAAG,SAAS,EAAE,EAAE,CAAC;AAExC,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IACvB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,WAAW,CAAC;IACnB,IAAI,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,KAAK,GAAG,MAAM,CAAA;KAAE,CAAC;IAC9C,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,aAAc,SAAQ,KAAK;CAAG;AAI3C,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,aAAa,CA8BrD;AA+KD,eAAO,MAAM,qBAAqB,OAAO,CAAC;AAE1C,wBAAsB,MAAM,CAC1B,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,aAAa,EACpB,IAAI,GAAE;IAAE,YAAY,CAAC,EAAE,MAAM,CAAA;CAAO,GACnC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAoCzC"}