@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/CHANGELOG.md +334 -0
- package/LICENSE +21 -0
- package/README.md +358 -0
- package/SECURITY.md +49 -0
- package/assets/social-preview.png +0 -0
- package/dist/dql.d.ts +37 -0
- package/dist/dql.d.ts.map +1 -0
- package/dist/dql.js +342 -0
- package/dist/dql.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +470 -0
- package/dist/index.js.map +1 -0
- package/dist/parser.d.ts +21 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +107 -0
- package/dist/parser.js.map +1 -0
- package/dist/tools.d.ts +143 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +445 -0
- package/dist/tools.js.map +1 -0
- package/dist/vault.d.ts +72 -0
- package/dist/vault.d.ts.map +1 -0
- package/dist/vault.js +424 -0
- package/dist/vault.js.map +1 -0
- package/docs/api.md +262 -0
- package/package.json +82 -0
package/docs/api.md
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
# enquire — API (v0.7)
|
|
2
|
+
|
|
3
|
+
**enquire is an MCP server for Obsidian vaults.** 12 MCP tools (10 read + 2 opt-in write), 2 MCP resources, 6 MCP prompts. The server speaks stdio JSON-RPC and is launched per-vault.
|
|
4
|
+
|
|
5
|
+
## CLI flags
|
|
6
|
+
|
|
7
|
+
| Flag | Default | Notes |
|
|
8
|
+
|------------------------|---------|--------------------------------------------|
|
|
9
|
+
| `--vault <path>` | (required) | Path to the Obsidian vault root. |
|
|
10
|
+
| `--enable-write` | off | Register the two write tools. |
|
|
11
|
+
| `--max-file-bytes <n>` | 5 MB | Max size for any single file read/write. |
|
|
12
|
+
| `--cache-size <n>` | 1024 | LRU cap for parsed-note cache. |
|
|
13
|
+
| `--persistent-cache` | off | Persist parsed-note cache to disk so cold starts skip re-parsing. **Stores full note bodies — see [Cache & privacy](../README.md#cache--privacy).** |
|
|
14
|
+
| `--cache-file <path>` | auto | Override the persistent-cache file location. Default: `~/Library/Caches/enquire/<vault-hash>.json` (macOS) or `~/.cache/enquire/<vault-hash>.json` (Linux). |
|
|
15
|
+
|
|
16
|
+
## Subcommands
|
|
17
|
+
|
|
18
|
+
| Subcommand | Args | What it does |
|
|
19
|
+
|---|---|---|
|
|
20
|
+
| `serve` (default) | see flags above | Start the MCP server over stdio. |
|
|
21
|
+
| `clear-cache` | `--vault <path>` `[--cache-file <path>]` | Delete the persistent-cache file for the given vault. Useful for purging stale or sensitive content. Returns 0 even if no cache file exists. |
|
|
22
|
+
|
|
23
|
+
## Read tools (always registered)
|
|
24
|
+
|
|
25
|
+
## `obsidian_list_notes`
|
|
26
|
+
|
|
27
|
+
List markdown notes in the vault. Filter by tag, folder, or modification date.
|
|
28
|
+
|
|
29
|
+
| Argument | Type | Notes |
|
|
30
|
+
|--------------|-----------------------|----------------------------------------------------|
|
|
31
|
+
| `tag` | `string?` | With or without leading `#`. Case-insensitive. |
|
|
32
|
+
| `folder` | `string?` | Subfolder relative to vault root. |
|
|
33
|
+
| `since_date` | `string?` | ISO 8601 (`YYYY-MM-DD`). mtime ≥ this date. |
|
|
34
|
+
| `limit` | `number?` (≤ 500) | Default 50. |
|
|
35
|
+
|
|
36
|
+
**Returns:** `Array<{ title, path, frontmatter, tags, mtime }>`, newest-first.
|
|
37
|
+
|
|
38
|
+
## `obsidian_read_note`
|
|
39
|
+
|
|
40
|
+
Read a single note. Provide either `path` or `title`.
|
|
41
|
+
|
|
42
|
+
| Argument | Type | Notes |
|
|
43
|
+
|----------|-----------|--------------------------------------------------------|
|
|
44
|
+
| `path` | `string?` | Vault-relative path, with or without `.md`. |
|
|
45
|
+
| `title` | `string?` | Filename without extension. Case-insensitive lookup. |
|
|
46
|
+
|
|
47
|
+
**Returns:** `{ path, title, content, frontmatter, wikilinks, embeds, tags, mtime }`. `content` is the body with frontmatter stripped. `wikilinks` and `embeds` share the same shape (`{ raw, target, section?, block?, alias? }`) and are surfaced separately.
|
|
48
|
+
|
|
49
|
+
## `obsidian_resolve_wikilink`
|
|
50
|
+
|
|
51
|
+
Resolve an Obsidian `[[wikilink]]` to a vault file. Handles aliases (`Note|alias`), section refs (`Note#Heading`), block refs (`Note^abc`), and relative paths (`../Folder/Note`) when `from_note` is supplied.
|
|
52
|
+
|
|
53
|
+
| Argument | Type | Notes |
|
|
54
|
+
|-------------------|------------|----------------------------------------------------------|
|
|
55
|
+
| `wikilink` | `string` | The target inside `[[ ]]` (brackets optional). |
|
|
56
|
+
| `from_note` | `string?` | Calling note path. Used to disambiguate same-name files and to anchor relative paths. |
|
|
57
|
+
| `include_content` | `boolean?` | Default `true`. Set `false` to skip reading the target. |
|
|
58
|
+
|
|
59
|
+
**Returns:** `{ found, path, title, content, section, block, alias }`. `found=false` when no match.
|
|
60
|
+
|
|
61
|
+
## `obsidian_search_text`
|
|
62
|
+
|
|
63
|
+
Case-insensitive substring search across the vault. Ranked by hit count.
|
|
64
|
+
|
|
65
|
+
| Argument | Type | Notes |
|
|
66
|
+
|----------|-------------------|----------------------------------------|
|
|
67
|
+
| `query` | `string` | Required. At least one non-space char. |
|
|
68
|
+
| `folder` | `string?` | Restrict to a subfolder. |
|
|
69
|
+
| `limit` | `number?` (≤ 200) | Default 25. |
|
|
70
|
+
|
|
71
|
+
**Returns:** `Array<{ path, snippet, score, line }>`. `snippet` is ~120 chars around the first hit.
|
|
72
|
+
|
|
73
|
+
## `obsidian_get_recent_edits`
|
|
74
|
+
|
|
75
|
+
List notes by modification time, newest-first. Useful for "what was I working on?" queries.
|
|
76
|
+
|
|
77
|
+
| Argument | Type | Notes |
|
|
78
|
+
|-----------------|-------------------|-----------------------------------------------|
|
|
79
|
+
| `since_minutes` | `number?` | Only include notes edited within this window. |
|
|
80
|
+
| `folder` | `string?` | Restrict to a subfolder. |
|
|
81
|
+
| `limit` | `number?` (≤ 200) | Default 20. |
|
|
82
|
+
|
|
83
|
+
**Returns:** `Array<{ title, path, frontmatter, tags, mtime }>`.
|
|
84
|
+
|
|
85
|
+
## `obsidian_get_backlinks`
|
|
86
|
+
|
|
87
|
+
List every note that links (or embeds) the target note. Ranked by hit count.
|
|
88
|
+
|
|
89
|
+
| Argument | Type | Notes |
|
|
90
|
+
|------------------|------------|-------------------------------------------------------------|
|
|
91
|
+
| `path` | `string?` | Target note path, vault-relative. |
|
|
92
|
+
| `title` | `string?` | Target note title (filename without `.md`). |
|
|
93
|
+
| `include_embeds` | `boolean?` | Default `true`. Set `false` to ignore `![[…]]` references. |
|
|
94
|
+
| `limit` | `number?` | Max results (default 50, ≤ 500). |
|
|
95
|
+
|
|
96
|
+
**Returns:** `Array<{ path, title, count, snippets, link_kind }>`. `link_kind` is `"wikilink"`, `"embed"`, or `"mixed"`. `snippets` are up to two ~120-char excerpts around the literal `[[…]]` / `![[…]]`.
|
|
97
|
+
|
|
98
|
+
## `obsidian_list_tags`
|
|
99
|
+
|
|
100
|
+
Enumerate every unique tag used in the vault with usage counts.
|
|
101
|
+
|
|
102
|
+
| Argument | Type | Notes |
|
|
103
|
+
|-------------|-----------|--------------------------------------------|
|
|
104
|
+
| `folder` | `string?` | Restrict to a subfolder. |
|
|
105
|
+
| `min_count` | `number?` | Drop tags used fewer than this (default 1).|
|
|
106
|
+
| `limit` | `number?` | Max results (default 200, ≤ 2000). |
|
|
107
|
+
|
|
108
|
+
**Returns:** `Array<{ tag, count, frontmatter_count, inline_count }>`, sorted by `count` desc.
|
|
109
|
+
|
|
110
|
+
> **Counting rules.** Each note contributes at most `+1` to a tag's `count` even if the tag appears in both the note's frontmatter and inline body. The note is credited to `frontmatter_count` if the tag was found in frontmatter, otherwise to `inline_count`. So `frontmatter_count + inline_count == count` for every tag.
|
|
111
|
+
|
|
112
|
+
## `obsidian_get_unresolved_wikilinks`
|
|
113
|
+
|
|
114
|
+
Find every `[[wikilink]]` (and `![[embed]]`) in the vault whose target does not resolve to a real file. Vault-hygiene utility — broken links, typos, intended-but-not-yet-created notes.
|
|
115
|
+
|
|
116
|
+
| Argument | Type | Notes |
|
|
117
|
+
|------------------|------------|-------------------------------------------------------------|
|
|
118
|
+
| `folder` | `string?` | Restrict the scan to a subfolder. |
|
|
119
|
+
| `include_embeds` | `boolean?` | Include `![[…]]` embeds (default `true`). |
|
|
120
|
+
| `limit` | `number?` | Max results (default 200, ≤ 2000). |
|
|
121
|
+
|
|
122
|
+
**Returns:** `Array<{ from_path, target, raw, kind, alias, section, block, line, snippet }>`. `kind` is `"wikilink"` or `"embed"`. `snippet` is a ~120-char window around the literal `[[…]]` / `![[…]]`.
|
|
123
|
+
|
|
124
|
+
## `obsidian_get_outbound_links`
|
|
125
|
+
|
|
126
|
+
Symmetric counterpart to `obsidian_get_backlinks`. For one note, list every outbound link (wikilink or embed) and its resolution status.
|
|
127
|
+
|
|
128
|
+
| Argument | Type | Notes |
|
|
129
|
+
|----------------------|------------|--------------------------------------------------------------|
|
|
130
|
+
| `path` | `string?` | Source note path; provide either this or `title`. |
|
|
131
|
+
| `title` | `string?` | Source note title (filename without `.md`). |
|
|
132
|
+
| `include_embeds` | `boolean?` | Include `![[…]]` embeds (default `true`). |
|
|
133
|
+
| `include_unresolved` | `boolean?` | Include links that don't resolve (default `true`). |
|
|
134
|
+
|
|
135
|
+
**Returns:** `{ from_path, from_title, links: Array<{ raw, target, kind, alias, section, block, resolved_path, resolved_title }> }`. `resolved_path` and `resolved_title` are `null` when the link doesn't resolve.
|
|
136
|
+
|
|
137
|
+
## `obsidian_dataview_query`
|
|
138
|
+
|
|
139
|
+
Run a minimal Dataview-style query. Phase-2 minimal — designed to cover the common shape, not to replicate the Obsidian Dataview plugin.
|
|
140
|
+
|
|
141
|
+
| Argument | Type | Notes |
|
|
142
|
+
|----------|----------|------------------------------------|
|
|
143
|
+
| `query` | `string` | The DQL string. See grammar below. |
|
|
144
|
+
|
|
145
|
+
### Grammar (subset)
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
QUERY ::= ("LIST" | "TABLE" COLUMNS) ("FROM" SOURCE)? WHERE? SORT? LIMIT?
|
|
149
|
+
COLUMNS ::= IDENT ("," IDENT)*
|
|
150
|
+
SOURCE ::= "\"" PATH "\"" -- folder
|
|
151
|
+
| "#" TAG -- tag
|
|
152
|
+
WHERE ::= "WHERE" CONJ ("OR" CONJ)*
|
|
153
|
+
CONJ ::= PRED ("AND" PRED)*
|
|
154
|
+
PRED ::= IDENT OP VALUE
|
|
155
|
+
OP ::= "=" | "!=" | "contains" | "like"
|
|
156
|
+
VALUE ::= "\"" STRING "\"" | NUMBER | "true" | "false" | "null" | BARE
|
|
157
|
+
SORT ::= "SORT" IDENT ("ASC" | "DESC")?
|
|
158
|
+
LIMIT ::= "LIMIT" INTEGER
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
`OR` has lower precedence than `AND` — `WHERE a = 1 AND b = 2 OR c = 3` parses as `(a = 1 AND b = 2) OR (c = 3)`. Use parentheses-style alternatives in the future once we add them; for now you can express any DNF directly.
|
|
162
|
+
|
|
163
|
+
`like` is a SQL-LIKE-style wildcard match (case-insensitive). `*` matches any run of characters; `\*` is a literal asterisk. Examples: `file.name like "draft*"`, `status like "in*progress"`.
|
|
164
|
+
|
|
165
|
+
### Special fields
|
|
166
|
+
|
|
167
|
+
| Field | Meaning |
|
|
168
|
+
|---------------|-----------------------------------------------|
|
|
169
|
+
| `file.name` | Filename without `.md`. |
|
|
170
|
+
| `file.path` | Vault-relative path. |
|
|
171
|
+
| `file.mtime` | ISO 8601 modification timestamp. |
|
|
172
|
+
| `file.tags` | Combined frontmatter + inline tags (array). |
|
|
173
|
+
| any other | Reads the matching frontmatter field. |
|
|
174
|
+
|
|
175
|
+
`contains` on an array field tests membership; on a string field, substring match (case-insensitive).
|
|
176
|
+
|
|
177
|
+
**Returns:** `{ query, rows: Array<Record<string, unknown>> }`. Every row always carries `file.path`, `file.name`, `file.mtime`. `TABLE` rows additionally carry the requested columns.
|
|
178
|
+
|
|
179
|
+
### Examples
|
|
180
|
+
|
|
181
|
+
```
|
|
182
|
+
LIST FROM "01_Projects"
|
|
183
|
+
LIST FROM #idea WHERE status = "active"
|
|
184
|
+
TABLE status, priority FROM "01_Projects" WHERE done = false SORT priority ASC LIMIT 10
|
|
185
|
+
LIST FROM #people WHERE file.tags contains "core-team"
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Not supported (yet)
|
|
189
|
+
|
|
190
|
+
- Expressions / arithmetic / function calls (`length(...)`, `regexmatch(...)`, etc.)
|
|
191
|
+
- `FLATTEN`, `GROUP BY`, joins, embedded queries
|
|
192
|
+
- `SOURCE` combinations beyond a single folder or single tag (no `FROM "a" OR #b`)
|
|
193
|
+
- Parentheses for explicit precedence in `WHERE`
|
|
194
|
+
|
|
195
|
+
### Row caps
|
|
196
|
+
|
|
197
|
+
If the query has no explicit `LIMIT`, results are capped at **1000 rows** by default to prevent runaway responses on large vaults. Use `LIMIT n` (any positive integer) to override.
|
|
198
|
+
|
|
199
|
+
## Write tools (opt-in)
|
|
200
|
+
|
|
201
|
+
Both write tools are **only registered when the server is started with `--enable-write`**. Without that flag the tools are not advertised to the client at all.
|
|
202
|
+
|
|
203
|
+
### `obsidian_create_note`
|
|
204
|
+
|
|
205
|
+
Create a new note at the given vault-relative path.
|
|
206
|
+
|
|
207
|
+
| Argument | Type | Notes |
|
|
208
|
+
|---------------|------------|---------------------------------------------------------------|
|
|
209
|
+
| `path` | `string` | Vault-relative path; `.md` is appended if missing. |
|
|
210
|
+
| `content` | `string` | Markdown body (frontmatter is supplied separately). |
|
|
211
|
+
| `frontmatter` | `object?` | Flat key/value YAML to render. Arrays render as block lists. |
|
|
212
|
+
| `overwrite` | `boolean?` | Default `false`. Existing notes are not clobbered without it. |
|
|
213
|
+
|
|
214
|
+
**Returns:** `{ path, mtime, bytes }`. Throws if the path escapes the vault, the file would exceed `--max-file-bytes`, or the file exists and `overwrite=false`.
|
|
215
|
+
|
|
216
|
+
### `obsidian_append_to_note`
|
|
217
|
+
|
|
218
|
+
Append a markdown block to an existing note.
|
|
219
|
+
|
|
220
|
+
| Argument | Type | Notes |
|
|
221
|
+
|-------------|------------|-------------------------------------------------------------|
|
|
222
|
+
| `path` | `string?` | Path of the target note. Provide either this or `title`. |
|
|
223
|
+
| `title` | `string?` | Title (filename without `.md`). |
|
|
224
|
+
| `content` | `string` | Markdown to append. |
|
|
225
|
+
| `separator` | `string?` | Inserted between existing body and new content (default `"\n\n"`). |
|
|
226
|
+
|
|
227
|
+
**Returns:** `{ path, mtime, appended_bytes }`. Refuses to grow the file past `--max-file-bytes`.
|
|
228
|
+
|
|
229
|
+
## MCP resources
|
|
230
|
+
|
|
231
|
+
| URI | Type | Description |
|
|
232
|
+
|------------------------------|----------------|--------------------------------------------|
|
|
233
|
+
| `obsidian://vault/info` | static JSON | Root, note count, write flag, byte/cache limits, server version. |
|
|
234
|
+
| `obsidian://note/{notePath}` | template (md) | Each markdown note. `notePath` is the URI-encoded vault-relative path. |
|
|
235
|
+
|
|
236
|
+
The note template implements `list`, so MCP clients with a resource browser will see the full vault enumerated on connect.
|
|
237
|
+
|
|
238
|
+
## MCP prompts
|
|
239
|
+
|
|
240
|
+
| Prompt | Args | What it sets up |
|
|
241
|
+
|-------------------------|----------------------------|-----------------------------------------------|
|
|
242
|
+
| `summarize_recent_edits`| `since_minutes?` | Walks recent edits, reads top-3, produces a writeup. |
|
|
243
|
+
| `review_tag` | `tag` | Pulls every note for a tag, surfaces open threads. |
|
|
244
|
+
| `find_orphans` | `folder?` | Finds notes with zero inbound links — archive candidates. |
|
|
245
|
+
| `weekly_review` | `folder?` | Aggregates the last 7 days of edits; groups by tag; surfaces shipped / open / stuck. |
|
|
246
|
+
| `extract_todos` | `folder?`, `tag?` | Greps TODO / FIXME / QUESTION across the vault, groups by note, picks a top-leverage next action. |
|
|
247
|
+
| `process_inbox` | `folder` (required) | Walks an inbox folder, proposes Move / Merge / Promote / Archive for each note. |
|
|
248
|
+
|
|
249
|
+
## Path safety
|
|
250
|
+
|
|
251
|
+
Every path argument is resolved relative to the vault root and rejected if it escapes the root via `..`. The server never reads outside the vault.
|
|
252
|
+
|
|
253
|
+
## Skipped directories
|
|
254
|
+
|
|
255
|
+
The walker ignores `.git`, `.obsidian`, `.trash`, `node_modules`, and any other dot-directory.
|
|
256
|
+
|
|
257
|
+
## Roadmap
|
|
258
|
+
|
|
259
|
+
- Persistent cross-vault search/link index (current persistent cache is per-vault and cold-start only)
|
|
260
|
+
- Full DQL: expressions, `FLATTEN`, `GROUP BY`, parenthesized precedence
|
|
261
|
+
- Higher-level write tools: rename/move with wikilink rewrites, tag refactor
|
|
262
|
+
- Graph queries (multi-hop link traversal)
|
package/package.json
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@oomkapwn/enquire-mcp",
|
|
3
|
+
"version": "0.7.4",
|
|
4
|
+
"description": "enquire — MCP server for Obsidian vaults. Named after Tim Berners-Lee's 1980 prototype of the World Wide Web. Wikilinks, frontmatter, backlinks, basic Dataview, MCP resources & prompts. Read-only by default; opt-in writes.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"enquire-mcp": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"docs",
|
|
12
|
+
"assets/social-preview.png",
|
|
13
|
+
"README.md",
|
|
14
|
+
"LICENSE",
|
|
15
|
+
"CHANGELOG.md",
|
|
16
|
+
"SECURITY.md"
|
|
17
|
+
],
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc && chmod +x dist/index.js",
|
|
23
|
+
"dev": "tsc --watch",
|
|
24
|
+
"start": "node dist/index.js",
|
|
25
|
+
"test": "vitest run",
|
|
26
|
+
"test:watch": "vitest",
|
|
27
|
+
"test:coverage": "vitest run --coverage",
|
|
28
|
+
"lint": "biome check",
|
|
29
|
+
"lint:fix": "biome check --write",
|
|
30
|
+
"format": "biome format --write",
|
|
31
|
+
"render:preview": "node scripts/render-social-preview.mjs",
|
|
32
|
+
"prepublishOnly": "npm run lint && npm run build && npm test"
|
|
33
|
+
},
|
|
34
|
+
"keywords": [
|
|
35
|
+
"obsidian",
|
|
36
|
+
"obsidian-md",
|
|
37
|
+
"mcp",
|
|
38
|
+
"model-context-protocol",
|
|
39
|
+
"claude",
|
|
40
|
+
"claude-code",
|
|
41
|
+
"cursor",
|
|
42
|
+
"codex",
|
|
43
|
+
"enquire",
|
|
44
|
+
"berners-lee",
|
|
45
|
+
"wikilinks",
|
|
46
|
+
"backlinks",
|
|
47
|
+
"frontmatter",
|
|
48
|
+
"dataview",
|
|
49
|
+
"markdown",
|
|
50
|
+
"pkm",
|
|
51
|
+
"knowledge-management",
|
|
52
|
+
"ai-agent",
|
|
53
|
+
"hypertext"
|
|
54
|
+
],
|
|
55
|
+
"author": "Alex (@OomkaBear)",
|
|
56
|
+
"license": "MIT",
|
|
57
|
+
"repository": {
|
|
58
|
+
"type": "git",
|
|
59
|
+
"url": "git+https://github.com/oomkapwn/enquire-mcp.git"
|
|
60
|
+
},
|
|
61
|
+
"homepage": "https://github.com/oomkapwn/enquire-mcp#readme",
|
|
62
|
+
"bugs": {
|
|
63
|
+
"url": "https://github.com/oomkapwn/enquire-mcp/issues"
|
|
64
|
+
},
|
|
65
|
+
"engines": {
|
|
66
|
+
"node": ">=20"
|
|
67
|
+
},
|
|
68
|
+
"dependencies": {
|
|
69
|
+
"@modelcontextprotocol/sdk": "^1.0.4",
|
|
70
|
+
"commander": "^14.0.3",
|
|
71
|
+
"gray-matter": "^4.0.3",
|
|
72
|
+
"zod": "^4.4.2"
|
|
73
|
+
},
|
|
74
|
+
"devDependencies": {
|
|
75
|
+
"@biomejs/biome": "^2.4.14",
|
|
76
|
+
"@types/node": "^25.6.0",
|
|
77
|
+
"@vitest/coverage-v8": "^4.1.5",
|
|
78
|
+
"sharp": "^0.34.5",
|
|
79
|
+
"typescript": "^6.0.3",
|
|
80
|
+
"vitest": "^4.1.5"
|
|
81
|
+
}
|
|
82
|
+
}
|