@oomkapwn/enquire-mcp 2.9.0 → 2.11.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.
@@ -0,0 +1,54 @@
1
+ import type { EmbeddingModel } from "./embeddings.js";
2
+ /** Severity buckets surfaced in the diagnostic UI. */
3
+ export type CheckStatus = "ok" | "warn" | "missing" | "error";
4
+ export interface DoctorCheck {
5
+ /** Stable id for programmatic consumers (e.g. JSON output). */
6
+ id: string;
7
+ /** Human-readable label (rendered next to the status icon). */
8
+ label: string;
9
+ status: CheckStatus;
10
+ /** Optional detail line printed below the label. */
11
+ detail?: string;
12
+ /** Optional hint — usually the command that fixes it. */
13
+ hint?: string;
14
+ }
15
+ export interface DoctorResult {
16
+ vault: string;
17
+ /** True iff every `missing`/`error` check is absent (`warn` is OK). */
18
+ ready: boolean;
19
+ checks: DoctorCheck[];
20
+ /** Tally for quick consumer reporting. */
21
+ summary: {
22
+ ok: number;
23
+ warn: number;
24
+ missing: number;
25
+ error: number;
26
+ };
27
+ }
28
+ /** Render one DoctorCheck to a multi-line string. */
29
+ export declare function formatCheck(check: DoctorCheck): string;
30
+ /** Render a full DoctorResult to a banner string. */
31
+ export declare function formatDoctorResult(result: DoctorResult): string;
32
+ export interface RunDoctorOptions {
33
+ vault: string;
34
+ /** Override default cache root (mostly for tests). */
35
+ modelCacheRoot?: string;
36
+ /** Override default embed-db location. */
37
+ embedFile?: string;
38
+ /** Override default FTS5 index location. */
39
+ indexFile?: string;
40
+ /** Default model alias to check for (matches DEFAULT_MODEL_ALIAS). */
41
+ modelAlias?: string;
42
+ /**
43
+ * Embedding-model catalog entry — passed in to avoid pulling
44
+ * `@huggingface/transformers` into this module. Caller resolves it via
45
+ * `resolveModel(alias)` from src/embeddings.ts.
46
+ */
47
+ modelEntry?: EmbeddingModel;
48
+ }
49
+ /**
50
+ * Run all the diagnostic checks. Pure data — caller decides how to
51
+ * render (CLI banner, JSON, MCP tool response).
52
+ */
53
+ export declare function runDoctor(opts: RunDoctorOptions): Promise<DoctorResult>;
54
+ //# sourceMappingURL=doctor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../src/doctor.ts"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAItD,sDAAsD;AACtD,MAAM,MAAM,WAAW,GAAG,IAAI,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC;AAE9D,MAAM,WAAW,WAAW;IAC1B,+DAA+D;IAC/D,EAAE,EAAE,MAAM,CAAC;IACX,+DAA+D;IAC/D,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,WAAW,CAAC;IACpB,oDAAoD;IACpD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yDAAyD;IACzD,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,uEAAuE;IACvE,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,0CAA0C;IAC1C,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CACvE;AAYD,qDAAqD;AACrD,wBAAgB,WAAW,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CAatD;AAED,qDAAqD;AACrD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAY/D;AA4DD,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,sDAAsD;IACtD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,0CAA0C;IAC1C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4CAA4C;IAC5C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sEAAsE;IACtE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,UAAU,CAAC,EAAE,cAAc,CAAC;CAC7B;AAED;;;GAGG;AACH,wBAAsB,SAAS,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,YAAY,CAAC,CAwO7E"}
package/dist/doctor.js ADDED
@@ -0,0 +1,369 @@
1
+ // Diagnostic + auto-setup for enquire-mcp.
2
+ //
3
+ // v2.11.0 — closes the biggest UX gap in the project: setup friction.
4
+ // Before this, getting full hybrid retrieval required 3 separate commands
5
+ // (`install-model` → `build-embeddings` → `serve --persistent-index`),
6
+ // and there was no quick way to see "is everything ready?" without
7
+ // triggering each codepath.
8
+ //
9
+ // Two new subcommands:
10
+ //
11
+ // enquire-mcp doctor --vault <path>
12
+ // Read-only health check. Lists every prerequisite for full hybrid
13
+ // retrieval (vault path, optional deps, embedding model cache, FTS5
14
+ // index, embed.db). Color-coded ✓ / ⚠ / ✗. Returns 0 if everything
15
+ // is ready, 1 if any critical piece is missing.
16
+ //
17
+ // enquire-mcp setup --vault <path>
18
+ // Runs the install + build sequence in order, with progress messages
19
+ // at each stage. Calls install-model + cold-build FTS5 + build-
20
+ // embeddings under the hood. Idempotent — re-running on a fully
21
+ // set-up vault is a no-op pass.
22
+ //
23
+ // Both are pure orchestration over existing CLI/library code — no new
24
+ // runtime deps, no schema changes. Same privacy filter applies (the
25
+ // doctor walks the vault via Vault.listMarkdown so excluded paths are
26
+ // hidden from its counts).
27
+ import { existsSync, promises as fs, statSync } from "node:fs";
28
+ import * as os from "node:os";
29
+ import * as path from "node:path";
30
+ import { defaultIndexFile, FtsIndex } from "./fts5.js";
31
+ import { Vault } from "./vault.js";
32
+ /** Simple ANSI color helpers — autodetect TTY so piped output stays clean. */
33
+ const isTty = process.stdout.isTTY === true;
34
+ const c = {
35
+ green: (s) => (isTty ? `\x1b[32m${s}\x1b[0m` : s),
36
+ yellow: (s) => (isTty ? `\x1b[33m${s}\x1b[0m` : s),
37
+ red: (s) => (isTty ? `\x1b[31m${s}\x1b[0m` : s),
38
+ dim: (s) => (isTty ? `\x1b[2m${s}\x1b[0m` : s),
39
+ bold: (s) => (isTty ? `\x1b[1m${s}\x1b[0m` : s)
40
+ };
41
+ /** Render one DoctorCheck to a multi-line string. */
42
+ export function formatCheck(check) {
43
+ const icon = check.status === "ok"
44
+ ? c.green("✓")
45
+ : check.status === "warn"
46
+ ? c.yellow("⚠")
47
+ : check.status === "missing"
48
+ ? c.red("✗")
49
+ : c.red("✗");
50
+ const lines = [`${icon} ${check.label}`];
51
+ if (check.detail)
52
+ lines.push(c.dim(` ${check.detail}`));
53
+ if (check.hint && check.status !== "ok")
54
+ lines.push(c.dim(` → ${check.hint}`));
55
+ return lines.join("\n");
56
+ }
57
+ /** Render a full DoctorResult to a banner string. */
58
+ export function formatDoctorResult(result) {
59
+ const lines = [];
60
+ lines.push(c.bold(`enquire-mcp doctor — ${result.vault}`));
61
+ lines.push("");
62
+ for (const check of result.checks)
63
+ lines.push(formatCheck(check));
64
+ lines.push("");
65
+ const { ok, warn, missing, error } = result.summary;
66
+ const verdict = result.ready
67
+ ? c.green(`READY — all critical checks pass (${ok} ok, ${warn} warnings)`)
68
+ : c.red(`NOT READY — ${missing + error} missing/error, ${warn} warnings, ${ok} ok`);
69
+ lines.push(verdict);
70
+ return lines.join("\n");
71
+ }
72
+ /**
73
+ * Candidate locations where transformers.js may have cached embedding model
74
+ * weights. We probe all of them and report `ok` if any contains data.
75
+ *
76
+ * Why multiple paths:
77
+ * - transformers.js v3+ default: `<package>/.cache/Xenova/...` (lives
78
+ * inside `node_modules/@huggingface/transformers/.cache`, the
79
+ * library's own cache dir relative to its install location).
80
+ * - Older HuggingFace Hub convention: `~/.cache/huggingface/...`.
81
+ * - macOS XDG override: `~/Library/Caches/huggingface/...`.
82
+ * - Custom env var: HF_HOME or TRANSFORMERS_CACHE if the user set them.
83
+ *
84
+ * We don't try to load transformers.js to read `env.cacheDir` — that
85
+ * would defeat the doctor's "fast read-only health check" promise on
86
+ * users who haven't installed the optional dep at all.
87
+ */
88
+ function candidateModelCacheRoots() {
89
+ const candidates = [];
90
+ // 1. transformers.js v3+ default (lives inside the package itself).
91
+ // Find the @huggingface/transformers install directory.
92
+ // require.resolve doesn't exist in ESM; we walk node_modules ourselves
93
+ // from cwd. If transformers.js isn't installed, this candidate just
94
+ // won't exist on disk and gets filtered out.
95
+ candidates.push(path.join(process.cwd(), "node_modules", "@huggingface", "transformers", ".cache"));
96
+ // 2. HuggingFace Hub conventions.
97
+ if (process.env.HF_HOME)
98
+ candidates.push(path.join(process.env.HF_HOME, "hub"));
99
+ if (process.env.TRANSFORMERS_CACHE)
100
+ candidates.push(process.env.TRANSFORMERS_CACHE);
101
+ candidates.push(path.join(os.homedir(), ".cache", "huggingface", "transformers.js"));
102
+ candidates.push(path.join(os.homedir(), ".cache", "huggingface"));
103
+ // 3. macOS XDG-ish convention.
104
+ if (process.platform === "darwin") {
105
+ candidates.push(path.join(os.homedir(), "Library", "Caches", "huggingface"));
106
+ }
107
+ return candidates;
108
+ }
109
+ /**
110
+ * Default `.embed.db` location for a given vault root — same convention as
111
+ * the rest of the codebase. Mirrors `embedDbPath` in src/index.ts.
112
+ */
113
+ function defaultEmbedDbFile(vaultRoot) {
114
+ return defaultIndexFile(vaultRoot).replace(/\.fts5\.db$/, ".embed.db");
115
+ }
116
+ /**
117
+ * Probe whether an optional dep is loadable in this process. Uses a
118
+ * dynamic import inside a try/catch so we never crash the diagnostic
119
+ * on a missing or broken native binding.
120
+ */
121
+ async function probeOptionalDep(spec) {
122
+ try {
123
+ await import(spec);
124
+ return true;
125
+ }
126
+ catch {
127
+ return false;
128
+ }
129
+ }
130
+ /**
131
+ * Run all the diagnostic checks. Pure data — caller decides how to
132
+ * render (CLI banner, JSON, MCP tool response).
133
+ */
134
+ export async function runDoctor(opts) {
135
+ const checks = [];
136
+ const vault = new Vault(opts.vault);
137
+ // 1. Vault path exists + is readable.
138
+ let vaultExists = false;
139
+ try {
140
+ await vault.ensureExists();
141
+ vaultExists = true;
142
+ const noteCount = (await vault.listMarkdown()).length;
143
+ const pdfCount = (await vault.listFilesByExtension(".pdf")).length;
144
+ const canvasCount = (await vault.listFilesByExtension(".canvas")).length;
145
+ checks.push({
146
+ id: "vault",
147
+ label: `Vault accessible at ${opts.vault}`,
148
+ status: "ok",
149
+ detail: `${noteCount} markdown · ${pdfCount} pdf · ${canvasCount} canvas (privacy filter applied)`
150
+ });
151
+ }
152
+ catch (err) {
153
+ const msg = err instanceof Error ? err.message : String(err);
154
+ checks.push({
155
+ id: "vault",
156
+ label: `Vault path ${opts.vault}`,
157
+ status: "error",
158
+ detail: msg,
159
+ hint: "Check the path exists and is a directory"
160
+ });
161
+ }
162
+ // 2. better-sqlite3 — gates --persistent-index + ML embed-db.
163
+ const hasSqlite = await probeOptionalDep("better-sqlite3");
164
+ checks.push({
165
+ id: "dep:better-sqlite3",
166
+ label: "better-sqlite3 (FTS5 BM25 + embedding store)",
167
+ status: hasSqlite ? "ok" : "missing",
168
+ detail: hasSqlite ? "loaded; native binding works" : undefined,
169
+ hint: hasSqlite ? undefined : "npm install better-sqlite3 (or remove --omit=optional from your install)"
170
+ });
171
+ // 3. @huggingface/transformers — gates ML embeddings + reranker.
172
+ const hasTransformers = await probeOptionalDep("@huggingface/transformers");
173
+ checks.push({
174
+ id: "dep:transformers",
175
+ label: "@huggingface/transformers (ML embeddings + cross-encoder reranker)",
176
+ status: hasTransformers ? "ok" : "missing",
177
+ detail: hasTransformers ? "loaded; ONNX runtime available" : undefined,
178
+ hint: hasTransformers ? undefined : "npm install @huggingface/transformers"
179
+ });
180
+ // 4. pdfjs-dist — gates obsidian_read_pdf + PDF retrieval.
181
+ const hasPdfjs = await probeOptionalDep("pdfjs-dist/legacy/build/pdf.mjs");
182
+ checks.push({
183
+ id: "dep:pdfjs",
184
+ label: "pdfjs-dist (PDF read + indexing)",
185
+ status: hasPdfjs ? "ok" : "warn",
186
+ detail: hasPdfjs ? "loaded" : "PDFs in vault won't be indexable",
187
+ hint: hasPdfjs ? undefined : "npm install pdfjs-dist@^4.10.38 (skip if you have no PDFs)"
188
+ });
189
+ // 5. tesseract.js + @napi-rs/canvas — gates obsidian_ocr_pdf.
190
+ const [hasTesseract, hasCanvas] = await Promise.all([
191
+ probeOptionalDep("tesseract.js"),
192
+ probeOptionalDep("@napi-rs/canvas")
193
+ ]);
194
+ if (hasTesseract && hasCanvas) {
195
+ checks.push({
196
+ id: "dep:ocr",
197
+ label: "tesseract.js + @napi-rs/canvas (OCR for scanned PDFs)",
198
+ status: "ok",
199
+ detail: "both loaded; PDF OCR ready"
200
+ });
201
+ }
202
+ else {
203
+ checks.push({
204
+ id: "dep:ocr",
205
+ label: "tesseract.js + @napi-rs/canvas (OCR for scanned PDFs)",
206
+ status: "warn",
207
+ detail: `tesseract.js=${hasTesseract ? "ok" : "missing"} · canvas=${hasCanvas ? "ok" : "missing"}`,
208
+ hint: "npm install tesseract.js @napi-rs/canvas (skip if you have no scanned PDFs)"
209
+ });
210
+ }
211
+ // 6. Embedding model cache — does the user have weights downloaded?
212
+ // Probe every candidate path; whichever has Xenova-style model dirs
213
+ // wins. Fall back to "missing" only if every candidate is empty/absent.
214
+ const cacheRoots = opts.modelCacheRoot ? [opts.modelCacheRoot] : candidateModelCacheRoots();
215
+ let foundCacheRoot = null;
216
+ let cachedCount = 0;
217
+ let cacheBytes = 0;
218
+ for (const cacheRoot of cacheRoots) {
219
+ if (!existsSync(cacheRoot))
220
+ continue;
221
+ try {
222
+ // Look for at least one Xenova/* directory or any direct model dir
223
+ // (transformers.js stores models as `Xenova/<model-id>`).
224
+ const xenovaPath = path.join(cacheRoot, "Xenova");
225
+ if (existsSync(xenovaPath)) {
226
+ const sub = await fs.readdir(xenovaPath, { withFileTypes: true });
227
+ const models = sub.filter((e) => e.isDirectory());
228
+ if (models.length > 0) {
229
+ foundCacheRoot = cacheRoot;
230
+ cachedCount = models.length;
231
+ // Best-effort size sum — bounded per model dir.
232
+ for (const m of models) {
233
+ try {
234
+ const files = await fs.readdir(path.join(xenovaPath, m.name));
235
+ for (const f of files) {
236
+ try {
237
+ cacheBytes += statSync(path.join(xenovaPath, m.name, f)).size;
238
+ }
239
+ catch {
240
+ /* skip */
241
+ }
242
+ }
243
+ }
244
+ catch {
245
+ /* skip */
246
+ }
247
+ }
248
+ break;
249
+ }
250
+ }
251
+ }
252
+ catch {
253
+ /* try next candidate */
254
+ }
255
+ }
256
+ if (foundCacheRoot && cachedCount > 0) {
257
+ checks.push({
258
+ id: "model:cache",
259
+ label: "Embedding model cache",
260
+ status: "ok",
261
+ detail: `${cachedCount} model(s) cached under ${foundCacheRoot}/Xenova/ (~${Math.round(cacheBytes / 1024 / 1024)} MB)`
262
+ });
263
+ }
264
+ else {
265
+ checks.push({
266
+ id: "model:cache",
267
+ label: "Embedding model cache",
268
+ status: "missing",
269
+ detail: "no Xenova model weights found in any standard cache location",
270
+ hint: opts.modelEntry
271
+ ? `enquire-mcp install-model ${opts.modelEntry.alias} (~${opts.modelEntry.approxSizeMB} MB)`
272
+ : "enquire-mcp install-model multilingual"
273
+ });
274
+ }
275
+ // 7. FTS5 index — does the persistent index exist for this vault?
276
+ if (vaultExists) {
277
+ const indexFile = opts.indexFile ?? defaultIndexFile(vault.root);
278
+ if (existsSync(indexFile) && hasSqlite) {
279
+ // Open + close to count files/chunks. If something's off, surface it
280
+ // as a warn (not missing — caller can still serve without the index).
281
+ try {
282
+ const idx = new FtsIndex({ file: indexFile, vaultRoot: vault.root });
283
+ await idx.open();
284
+ const totalFiles = idx.totalFiles();
285
+ const totalChunks = idx.totalChunks();
286
+ idx.close();
287
+ checks.push({
288
+ id: "index:fts5",
289
+ label: "FTS5 BM25 index",
290
+ status: "ok",
291
+ detail: `${indexFile} — ${totalFiles} files / ${totalChunks} chunks`
292
+ });
293
+ }
294
+ catch (err) {
295
+ const msg = err instanceof Error ? err.message : String(err);
296
+ checks.push({
297
+ id: "index:fts5",
298
+ label: "FTS5 BM25 index",
299
+ status: "warn",
300
+ detail: `${indexFile} present but failed to open: ${msg}`,
301
+ hint: `enquire-mcp clear-index --vault ${opts.vault} && enquire-mcp index --vault ${opts.vault}`
302
+ });
303
+ }
304
+ }
305
+ else {
306
+ checks.push({
307
+ id: "index:fts5",
308
+ label: "FTS5 BM25 index",
309
+ status: "warn",
310
+ detail: hasSqlite ? `${indexFile} not built` : "needs better-sqlite3 first",
311
+ hint: hasSqlite ? `enquire-mcp index --vault ${opts.vault}` : "install better-sqlite3 first"
312
+ });
313
+ }
314
+ }
315
+ // 8. Embedding index — does the .embed.db exist for this vault?
316
+ if (vaultExists) {
317
+ const embedFile = opts.embedFile ?? defaultEmbedDbFile(vault.root);
318
+ if (existsSync(embedFile) && hasSqlite && hasTransformers) {
319
+ // Don't open the file (loading the model is expensive); just stat it
320
+ // and rely on the existence + size check.
321
+ try {
322
+ const sz = statSync(embedFile).size;
323
+ checks.push({
324
+ id: "index:embed",
325
+ label: "Embedding index (.embed.db)",
326
+ status: "ok",
327
+ detail: `${embedFile} — ${(sz / 1024 / 1024).toFixed(1)} MB`
328
+ });
329
+ }
330
+ catch (err) {
331
+ const msg = err instanceof Error ? err.message : String(err);
332
+ checks.push({
333
+ id: "index:embed",
334
+ label: "Embedding index (.embed.db)",
335
+ status: "warn",
336
+ detail: msg,
337
+ hint: `enquire-mcp clear-embeddings --vault ${opts.vault} && enquire-mcp build-embeddings --vault ${opts.vault}`
338
+ });
339
+ }
340
+ }
341
+ else {
342
+ const blockers = [];
343
+ if (!hasSqlite)
344
+ blockers.push("better-sqlite3");
345
+ if (!hasTransformers)
346
+ blockers.push("@huggingface/transformers");
347
+ checks.push({
348
+ id: "index:embed",
349
+ label: "Embedding index (.embed.db)",
350
+ status: "warn",
351
+ detail: blockers.length > 0
352
+ ? `blocked on: ${blockers.join(", ")}`
353
+ : `${embedFile} not built — semantic-search-only path will use TF-IDF cosine`,
354
+ hint: blockers.length > 0
355
+ ? `npm install ${blockers.join(" ")}`
356
+ : `enquire-mcp build-embeddings --vault ${opts.vault}`
357
+ });
358
+ }
359
+ }
360
+ // Tally the summary.
361
+ const summary = { ok: 0, warn: 0, missing: 0, error: 0 };
362
+ for (const ch of checks)
363
+ summary[ch.status] += 1;
364
+ // "ready" means: no missing or error. Warnings are advisory — you can
365
+ // still serve a useful subset of the surface (e.g. without ML embeddings).
366
+ const ready = summary.missing === 0 && summary.error === 0;
367
+ return { vault: opts.vault, ready, checks, summary };
368
+ }
369
+ //# sourceMappingURL=doctor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../src/doctor.ts"],"names":[],"mappings":"AAAA,2CAA2C;AAC3C,EAAE;AACF,sEAAsE;AACtE,0EAA0E;AAC1E,uEAAuE;AACvE,mEAAmE;AACnE,4BAA4B;AAC5B,EAAE;AACF,uBAAuB;AACvB,EAAE;AACF,sCAAsC;AACtC,wEAAwE;AACxE,yEAAyE;AACzE,wEAAwE;AACxE,qDAAqD;AACrD,EAAE;AACF,qCAAqC;AACrC,0EAA0E;AAC1E,qEAAqE;AACrE,qEAAqE;AACrE,qCAAqC;AACrC,EAAE;AACF,sEAAsE;AACtE,oEAAoE;AACpE,sEAAsE;AACtE,2BAA2B;AAE3B,OAAO,EAAE,UAAU,EAAE,QAAQ,IAAI,EAAE,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC/D,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACvD,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AA0BnC,8EAA8E;AAC9E,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,KAAK,IAAI,CAAC;AAC5C,MAAM,CAAC,GAAG;IACR,KAAK,EAAE,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;IACzD,MAAM,EAAE,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1D,GAAG,EAAE,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;IACvD,GAAG,EAAE,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;IACtD,IAAI,EAAE,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;CACxD,CAAC;AAEF,qDAAqD;AACrD,MAAM,UAAU,WAAW,CAAC,KAAkB;IAC5C,MAAM,IAAI,GACR,KAAK,CAAC,MAAM,KAAK,IAAI;QACnB,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC;QACd,CAAC,CAAC,KAAK,CAAC,MAAM,KAAK,MAAM;YACvB,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;YACf,CAAC,CAAC,KAAK,CAAC,MAAM,KAAK,SAAS;gBAC1B,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;gBACZ,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACrB,MAAM,KAAK,GAAa,CAAC,GAAG,IAAI,KAAK,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC;IACpD,IAAI,KAAK,CAAC,MAAM;QAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAC1D,IAAI,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,MAAM,KAAK,IAAI;QAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IACjF,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,qDAAqD;AACrD,MAAM,UAAU,kBAAkB,CAAC,MAAoB;IACrD,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,wBAAwB,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAC3D,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACf,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM;QAAE,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC;IAClE,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACf,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,MAAM,CAAC,OAAO,CAAC;IACpD,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK;QAC1B,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,qCAAqC,EAAE,QAAQ,IAAI,YAAY,CAAC;QAC1E,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,eAAe,OAAO,GAAG,KAAK,mBAAmB,IAAI,cAAc,EAAE,KAAK,CAAC,CAAC;IACtF,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACpB,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,SAAS,wBAAwB;IAC/B,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,oEAAoE;IACpE,wDAAwD;IACxD,uEAAuE;IACvE,oEAAoE;IACpE,6CAA6C;IAC7C,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,cAAc,EAAE,cAAc,EAAE,QAAQ,CAAC,CAAC,CAAC;IACpG,kCAAkC;IAClC,IAAI,OAAO,CAAC,GAAG,CAAC,OAAO;QAAE,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC;IAChF,IAAI,OAAO,CAAC,GAAG,CAAC,kBAAkB;QAAE,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;IACpF,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,aAAa,EAAE,iBAAiB,CAAC,CAAC,CAAC;IACrF,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC;IAClE,+BAA+B;IAC/B,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAClC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC;IAC/E,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC;AAED;;;GAGG;AACH,SAAS,kBAAkB,CAAC,SAAiB;IAC3C,OAAO,gBAAgB,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,aAAa,EAAE,WAAW,CAAC,CAAC;AACzE,CAAC;AAED;;;;GAIG;AACH,KAAK,UAAU,gBAAgB,CAAC,IAAY;IAC1C,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC;QACnB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAoBD;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,IAAsB;IACpD,MAAM,MAAM,GAAkB,EAAE,CAAC;IACjC,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAEpC,sCAAsC;IACtC,IAAI,WAAW,GAAG,KAAK,CAAC;IACxB,IAAI,CAAC;QACH,MAAM,KAAK,CAAC,YAAY,EAAE,CAAC;QAC3B,WAAW,GAAG,IAAI,CAAC;QACnB,MAAM,SAAS,GAAG,CAAC,MAAM,KAAK,CAAC,YAAY,EAAE,CAAC,CAAC,MAAM,CAAC;QACtD,MAAM,QAAQ,GAAG,CAAC,MAAM,KAAK,CAAC,oBAAoB,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC;QACnE,MAAM,WAAW,GAAG,CAAC,MAAM,KAAK,CAAC,oBAAoB,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC;QACzE,MAAM,CAAC,IAAI,CAAC;YACV,EAAE,EAAE,OAAO;YACX,KAAK,EAAE,uBAAuB,IAAI,CAAC,KAAK,EAAE;YAC1C,MAAM,EAAE,IAAI;YACZ,MAAM,EAAE,GAAG,SAAS,eAAe,QAAQ,UAAU,WAAW,kCAAkC;SACnG,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7D,MAAM,CAAC,IAAI,CAAC;YACV,EAAE,EAAE,OAAO;YACX,KAAK,EAAE,cAAc,IAAI,CAAC,KAAK,EAAE;YACjC,MAAM,EAAE,OAAO;YACf,MAAM,EAAE,GAAG;YACX,IAAI,EAAE,0CAA0C;SACjD,CAAC,CAAC;IACL,CAAC;IAED,8DAA8D;IAC9D,MAAM,SAAS,GAAG,MAAM,gBAAgB,CAAC,gBAAgB,CAAC,CAAC;IAC3D,MAAM,CAAC,IAAI,CAAC;QACV,EAAE,EAAE,oBAAoB;QACxB,KAAK,EAAE,8CAA8C;QACrD,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;QACpC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC,8BAA8B,CAAC,CAAC,CAAC,SAAS;QAC9D,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,0EAA0E;KACzG,CAAC,CAAC;IAEH,iEAAiE;IACjE,MAAM,eAAe,GAAG,MAAM,gBAAgB,CAAC,2BAA2B,CAAC,CAAC;IAC5E,MAAM,CAAC,IAAI,CAAC;QACV,EAAE,EAAE,kBAAkB;QACtB,KAAK,EAAE,oEAAoE;QAC3E,MAAM,EAAE,eAAe,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;QAC1C,MAAM,EAAE,eAAe,CAAC,CAAC,CAAC,gCAAgC,CAAC,CAAC,CAAC,SAAS;QACtE,IAAI,EAAE,eAAe,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,uCAAuC;KAC5E,CAAC,CAAC;IAEH,2DAA2D;IAC3D,MAAM,QAAQ,GAAG,MAAM,gBAAgB,CAAC,iCAAiC,CAAC,CAAC;IAC3E,MAAM,CAAC,IAAI,CAAC;QACV,EAAE,EAAE,WAAW;QACf,KAAK,EAAE,kCAAkC;QACzC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM;QAChC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,kCAAkC;QAChE,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,4DAA4D;KAC1F,CAAC,CAAC;IAEH,8DAA8D;IAC9D,MAAM,CAAC,YAAY,EAAE,SAAS,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QAClD,gBAAgB,CAAC,cAAc,CAAC;QAChC,gBAAgB,CAAC,iBAAiB,CAAC;KACpC,CAAC,CAAC;IACH,IAAI,YAAY,IAAI,SAAS,EAAE,CAAC;QAC9B,MAAM,CAAC,IAAI,CAAC;YACV,EAAE,EAAE,SAAS;YACb,KAAK,EAAE,uDAAuD;YAC9D,MAAM,EAAE,IAAI;YACZ,MAAM,EAAE,4BAA4B;SACrC,CAAC,CAAC;IACL,CAAC;SAAM,CAAC;QACN,MAAM,CAAC,IAAI,CAAC;YACV,EAAE,EAAE,SAAS;YACb,KAAK,EAAE,uDAAuD;YAC9D,MAAM,EAAE,MAAM;YACd,MAAM,EAAE,gBAAgB,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,aAAa,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,EAAE;YAClG,IAAI,EAAE,6EAA6E;SACpF,CAAC,CAAC;IACL,CAAC;IAED,oEAAoE;IACpE,oEAAoE;IACpE,wEAAwE;IACxE,MAAM,UAAU,GAAG,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,wBAAwB,EAAE,CAAC;IAC5F,IAAI,cAAc,GAAkB,IAAI,CAAC;IACzC,IAAI,WAAW,GAAG,CAAC,CAAC;IACpB,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC;YAAE,SAAS;QACrC,IAAI,CAAC;YACH,mEAAmE;YACnE,0DAA0D;YAC1D,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;YAClD,IAAI,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;gBAC3B,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;gBAClE,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;gBAClD,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBACtB,cAAc,GAAG,SAAS,CAAC;oBAC3B,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC;oBAC5B,gDAAgD;oBAChD,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;wBACvB,IAAI,CAAC;4BACH,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;4BAC9D,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;gCACtB,IAAI,CAAC;oCACH,UAAU,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;gCAChE,CAAC;gCAAC,MAAM,CAAC;oCACP,UAAU;gCACZ,CAAC;4BACH,CAAC;wBACH,CAAC;wBAAC,MAAM,CAAC;4BACP,UAAU;wBACZ,CAAC;oBACH,CAAC;oBACD,MAAM;gBACR,CAAC;YACH,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,wBAAwB;QAC1B,CAAC;IACH,CAAC;IACD,IAAI,cAAc,IAAI,WAAW,GAAG,CAAC,EAAE,CAAC;QACtC,MAAM,CAAC,IAAI,CAAC;YACV,EAAE,EAAE,aAAa;YACjB,KAAK,EAAE,uBAAuB;YAC9B,MAAM,EAAE,IAAI;YACZ,MAAM,EAAE,GAAG,WAAW,0BAA0B,cAAc,cAAc,IAAI,CAAC,KAAK,CAAC,UAAU,GAAG,IAAI,GAAG,IAAI,CAAC,MAAM;SACvH,CAAC,CAAC;IACL,CAAC;SAAM,CAAC;QACN,MAAM,CAAC,IAAI,CAAC;YACV,EAAE,EAAE,aAAa;YACjB,KAAK,EAAE,uBAAuB;YAC9B,MAAM,EAAE,SAAS;YACjB,MAAM,EAAE,8DAA8D;YACtE,IAAI,EAAE,IAAI,CAAC,UAAU;gBACnB,CAAC,CAAC,6BAA6B,IAAI,CAAC,UAAU,CAAC,KAAK,OAAO,IAAI,CAAC,UAAU,CAAC,YAAY,MAAM;gBAC7F,CAAC,CAAC,wCAAwC;SAC7C,CAAC,CAAC;IACL,CAAC;IAED,kEAAkE;IAClE,IAAI,WAAW,EAAE,CAAC;QAChB,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,gBAAgB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACjE,IAAI,UAAU,CAAC,SAAS,CAAC,IAAI,SAAS,EAAE,CAAC;YACvC,qEAAqE;YACrE,sEAAsE;YACtE,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,IAAI,QAAQ,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;gBACrE,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;gBACjB,MAAM,UAAU,GAAG,GAAG,CAAC,UAAU,EAAE,CAAC;gBACpC,MAAM,WAAW,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC;gBACtC,GAAG,CAAC,KAAK,EAAE,CAAC;gBACZ,MAAM,CAAC,IAAI,CAAC;oBACV,EAAE,EAAE,YAAY;oBAChB,KAAK,EAAE,iBAAiB;oBACxB,MAAM,EAAE,IAAI;oBACZ,MAAM,EAAE,GAAG,SAAS,MAAM,UAAU,YAAY,WAAW,SAAS;iBACrE,CAAC,CAAC;YACL,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBAC7D,MAAM,CAAC,IAAI,CAAC;oBACV,EAAE,EAAE,YAAY;oBAChB,KAAK,EAAE,iBAAiB;oBACxB,MAAM,EAAE,MAAM;oBACd,MAAM,EAAE,GAAG,SAAS,gCAAgC,GAAG,EAAE;oBACzD,IAAI,EAAE,mCAAmC,IAAI,CAAC,KAAK,iCAAiC,IAAI,CAAC,KAAK,EAAE;iBACjG,CAAC,CAAC;YACL,CAAC;QACH,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,IAAI,CAAC;gBACV,EAAE,EAAE,YAAY;gBAChB,KAAK,EAAE,iBAAiB;gBACxB,MAAM,EAAE,MAAM;gBACd,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC,GAAG,SAAS,YAAY,CAAC,CAAC,CAAC,4BAA4B;gBAC3E,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,6BAA6B,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,8BAA8B;aAC7F,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,gEAAgE;IAChE,IAAI,WAAW,EAAE,CAAC;QAChB,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,kBAAkB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACnE,IAAI,UAAU,CAAC,SAAS,CAAC,IAAI,SAAS,IAAI,eAAe,EAAE,CAAC;YAC1D,qEAAqE;YACrE,0CAA0C;YAC1C,IAAI,CAAC;gBACH,MAAM,EAAE,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC;gBACpC,MAAM,CAAC,IAAI,CAAC;oBACV,EAAE,EAAE,aAAa;oBACjB,KAAK,EAAE,6BAA6B;oBACpC,MAAM,EAAE,IAAI;oBACZ,MAAM,EAAE,GAAG,SAAS,MAAM,CAAC,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK;iBAC7D,CAAC,CAAC;YACL,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBAC7D,MAAM,CAAC,IAAI,CAAC;oBACV,EAAE,EAAE,aAAa;oBACjB,KAAK,EAAE,6BAA6B;oBACpC,MAAM,EAAE,MAAM;oBACd,MAAM,EAAE,GAAG;oBACX,IAAI,EAAE,wCAAwC,IAAI,CAAC,KAAK,4CAA4C,IAAI,CAAC,KAAK,EAAE;iBACjH,CAAC,CAAC;YACL,CAAC;QACH,CAAC;aAAM,CAAC;YACN,MAAM,QAAQ,GAAa,EAAE,CAAC;YAC9B,IAAI,CAAC,SAAS;gBAAE,QAAQ,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;YAChD,IAAI,CAAC,eAAe;gBAAE,QAAQ,CAAC,IAAI,CAAC,2BAA2B,CAAC,CAAC;YACjE,MAAM,CAAC,IAAI,CAAC;gBACV,EAAE,EAAE,aAAa;gBACjB,KAAK,EAAE,6BAA6B;gBACpC,MAAM,EAAE,MAAM;gBACd,MAAM,EACJ,QAAQ,CAAC,MAAM,GAAG,CAAC;oBACjB,CAAC,CAAC,eAAe,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;oBACtC,CAAC,CAAC,GAAG,SAAS,+DAA+D;gBACjF,IAAI,EACF,QAAQ,CAAC,MAAM,GAAG,CAAC;oBACjB,CAAC,CAAC,eAAe,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE;oBACrC,CAAC,CAAC,wCAAwC,IAAI,CAAC,KAAK,EAAE;aAC3D,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,qBAAqB;IACrB,MAAM,OAAO,GAAG,EAAE,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;IACzD,KAAK,MAAM,EAAE,IAAI,MAAM;QAAE,OAAO,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACjD,sEAAsE;IACtE,2EAA2E;IAC3E,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,KAAK,CAAC,IAAI,OAAO,CAAC,KAAK,KAAK,CAAC,CAAC;IAE3D,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;AACvD,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAIA,OAAO,EAAE,SAAS,EAAoB,MAAM,yCAAyC,CAAC;AAMtF,OAAO,EAAkC,QAAQ,EAAE,MAAM,WAAW,CAAC;AAwCrE,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAW5C,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IACnC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC;;mCAE+B;IAC/B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;8EAC0E;IAC1E,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,qEAAqE;IACrE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qEAAqE;IACrE,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAcD,iBAAe,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAsVnC;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC1B,OAAO,EAAE,YAAY,GAAG,IAAI,CAAC;IAC7B,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC1B,cAAc,EAAE;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;CACtC;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,UAAU,CAAC,CAkE/E;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,YAAY,GAAG,SAAS,CA8F9E;AAED,iBAAe,WAAW,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAoD5D;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAY1D;AAo7DD,iBAAS,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAM3D;AAsCD,OAAO,EAAE,IAAI,EAAE,gBAAgB,EAAE,WAAW,EAAE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAIA,OAAO,EAAE,SAAS,EAAoB,MAAM,yCAAyC,CAAC;AAMtF,OAAO,EAAkC,QAAQ,EAAE,MAAM,WAAW,CAAC;AAyCrE,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAW5C,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IACnC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC;;mCAE+B;IAC/B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;8EAC0E;IAC1E,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,qEAAqE;IACrE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qEAAqE;IACrE,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAcD,iBAAe,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAmcnC;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC1B,OAAO,EAAE,YAAY,GAAG,IAAI,CAAC;IAC7B,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC1B,cAAc,EAAE;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;CACtC;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,UAAU,CAAC,CAkE/E;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,YAAY,GAAG,SAAS,CA8F9E;AAED,iBAAe,WAAW,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAoD5D;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAY1D;AAy9DD,iBAAS,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAM3D;AAsCD,OAAO,EAAE,IAAI,EAAE,gBAAgB,EAAE,WAAW,EAAE,CAAC"}
package/dist/index.js CHANGED
@@ -9,10 +9,10 @@ import { z } from "zod";
9
9
  import { EmbedDb } from "./embed-db.js";
10
10
  import { DEFAULT_MODEL_ALIAS, EMBEDDING_MODELS, loadEmbedder, resolveModel } from "./embeddings.js";
11
11
  import { chunkContent, defaultIndexFile, FtsIndex } from "./fts5.js";
12
- import { appendToNote, archiveNote, chatThreadAppend, chatThreadRead, contextPack, createNote, dataviewQuery, embeddingsSearch, findPath, findSimilar, frontmatterGet, frontmatterSearch, frontmatterSet, getBacklinks, getNoteNeighbors, getOpenQuestions, getOutboundLinks, getRecentEdits, getUnresolvedWikilinks, getVaultStats, lintWiki, listCanvases, listNotes, listPdfs, listTags, openInUi, paperAudit, readCanvas, readNote, readPdf, renameNote, replaceInNotes, resolveWikilink, searchHybrid, searchText, semanticSearch, validateNoteProposal } from "./tools.js";
12
+ import { appendToNote, archiveNote, chatThreadAppend, chatThreadRead, contextPack, createNote, dataviewQuery, embeddingsSearch, findPath, findSimilar, frontmatterGet, frontmatterSearch, frontmatterSet, getBacklinks, getNoteNeighbors, getOpenQuestions, getOutboundLinks, getRecentEdits, getUnresolvedWikilinks, getVaultStats, lintWiki, listCanvases, listNotes, listPdfs, listTags, ocrPdf, openInUi, paperAudit, readCanvas, readNote, readPdf, renameNote, replaceInNotes, resolveWikilink, searchHybrid, searchText, semanticSearch, validateNoteProposal } from "./tools.js";
13
13
  import { Vault } from "./vault.js";
14
14
  import { VaultWatcher } from "./watcher.js";
15
- const VERSION = "2.9.0";
15
+ const VERSION = "2.11.0";
16
16
  /** Default location for the persistent embedding index, alongside .fts5.db. */
17
17
  function embedDbPath(vaultRoot) {
18
18
  // Match the FTS5 location convention by stripping the .fts5.db extension
@@ -254,6 +254,94 @@ async function main() {
254
254
  process.stdout.write(`enquire: no embedding index files at ${file}\n`);
255
255
  }
256
256
  });
257
+ // v2.11.0 — diagnostic + zero-touch onboarding. `doctor` is read-only and
258
+ // returns 0 if everything is ready, 1 if any critical setup is missing.
259
+ // `setup` runs the install + build sequence in order, idempotent.
260
+ program
261
+ .command("doctor")
262
+ .description("Run a read-only health check: verify the vault path, optional deps (better-sqlite3 / transformers / pdfjs / tesseract / canvas), embedding-model cache, FTS5 index, and embed-db. Returns 0 if everything is ready for full hybrid retrieval, 1 if any critical piece is missing. Color-coded ✓ / ⚠ / ✗ output. Use this when you're unsure what's set up vs not.")
263
+ .requiredOption("--vault <path>", "Path to the Obsidian vault root")
264
+ .option("--json", "Emit machine-readable JSON instead of the colored banner")
265
+ .action(async (opts) => {
266
+ const { runDoctor, formatDoctorResult } = await import("./doctor.js");
267
+ const result = await runDoctor({
268
+ vault: opts.vault,
269
+ modelEntry: EMBEDDING_MODELS[DEFAULT_MODEL_ALIAS]
270
+ });
271
+ if (opts.json) {
272
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
273
+ }
274
+ else {
275
+ process.stdout.write(`${formatDoctorResult(result)}\n`);
276
+ }
277
+ if (!result.ready)
278
+ process.exit(1);
279
+ });
280
+ program
281
+ .command("setup")
282
+ .description("Zero-touch onboarding: run `install-model` + `index` + `build-embeddings` in sequence so a fresh vault is fully indexed for hybrid retrieval (BM25 + TF-IDF + ML embeddings) in a single command. Idempotent — re-running on a fully set-up vault is a fast no-op pass that just reports the existing state.")
283
+ .requiredOption("--vault <path>", "Path to the Obsidian vault root")
284
+ .option("--embedding-model <alias>", `Model alias (default: ${DEFAULT_MODEL_ALIAS})`, DEFAULT_MODEL_ALIAS)
285
+ .option("--include-pdfs", "Also index PDFs (FTS5 + embeddings). Off by default; opt-in because PDF extraction is slower.")
286
+ .option("--skip-embeddings", "Skip the install-model + build-embeddings steps (only build FTS5)")
287
+ .action(async (opts) => {
288
+ const v = new Vault(opts.vault);
289
+ await v.ensureExists();
290
+ process.stdout.write(`enquire setup — ${opts.vault}\n\n`);
291
+ // Step 1: FTS5 index.
292
+ process.stdout.write(">> Step 1/3: Cold-build FTS5 index\n");
293
+ const indexFile = defaultIndexFile(v.root);
294
+ const idx = new FtsIndex({ file: indexFile, vaultRoot: v.root });
295
+ await idx.open();
296
+ try {
297
+ const ftsReport = await syncFtsIndex(v, idx);
298
+ process.stdout.write(` FTS5 (md): added=${ftsReport.added} updated=${ftsReport.updated} unchanged=${ftsReport.unchanged} chunks=${ftsReport.total_chunks}\n`);
299
+ if (opts.includePdfs) {
300
+ const pdfReport = await syncPdfFtsIndex(v, idx);
301
+ process.stdout.write(` FTS5 (pdf): added=${pdfReport.added} updated=${pdfReport.updated} unchanged=${pdfReport.unchanged} chunks=${pdfReport.total_chunks}\n`);
302
+ }
303
+ }
304
+ finally {
305
+ idx.close();
306
+ }
307
+ if (opts.skipEmbeddings) {
308
+ process.stdout.write("\n>> Step 2-3 skipped (--skip-embeddings)\n");
309
+ process.stdout.write("\nSetup partial. Run without --skip-embeddings to enable ML hybrid retrieval.\n");
310
+ return;
311
+ }
312
+ // Step 2: Install-model.
313
+ process.stdout.write("\n>> Step 2/3: Install embedding model\n");
314
+ const model = resolveModel(opts.embeddingModel);
315
+ const t0 = Date.now();
316
+ const embedder = await loadEmbedder(opts.embeddingModel);
317
+ const [smokeVec] = await embedder.embed(["hello"]);
318
+ if (!smokeVec || smokeVec.length !== model.dim) {
319
+ throw new Error(`Model ${model.alias} loaded but dim mismatch: ${smokeVec?.length} vs ${model.dim}`);
320
+ }
321
+ process.stdout.write(` model ${model.alias} ready (${model.dim}-dim, ${Date.now() - t0}ms warmup, cached under ~/.cache/huggingface/)\n`);
322
+ // Step 3: build-embeddings.
323
+ process.stdout.write("\n>> Step 3/3: Build embedding index\n");
324
+ const embedFile = embedDbPath(v.root);
325
+ const db = new EmbedDb({ file: embedFile, vaultRoot: v.root, modelAlias: model.alias, dim: model.dim });
326
+ await db.open();
327
+ try {
328
+ const embReport = await syncEmbedDb(v, db, embedder);
329
+ process.stdout.write(` embed-db (md): added=${embReport.added} updated=${embReport.updated} unchanged=${embReport.unchanged} chunks=${embReport.total_chunks}\n`);
330
+ if (opts.includePdfs) {
331
+ const pdfReport = await syncPdfEmbedDb(v, db, embedder);
332
+ process.stdout.write(` embed-db (pdf): added=${pdfReport.added} updated=${pdfReport.updated} unchanged=${pdfReport.unchanged} chunks=${pdfReport.total_chunks}\n`);
333
+ }
334
+ }
335
+ finally {
336
+ db.close();
337
+ }
338
+ process.stdout.write("\n✓ Setup complete. Now run:\n");
339
+ process.stdout.write(` enquire-mcp serve --vault ${opts.vault} --persistent-index`);
340
+ if (opts.includePdfs)
341
+ process.stdout.write(" --include-pdfs");
342
+ process.stdout.write("\n");
343
+ process.stdout.write(`Or check status: enquire-mcp doctor --vault ${opts.vault}\n`);
344
+ });
257
345
  await program.parseAsync(process.argv);
258
346
  }
259
347
  /**
@@ -1148,7 +1236,7 @@ rerankerConfig = null) {
1148
1236
  }, async (args) => textResult(await listPdfs(vault, args)));
1149
1237
  server.registerTool("obsidian_read_pdf", {
1150
1238
  title: "Extract text from a PDF (page-by-page)",
1151
- description: "Extracts plain text from one PDF, returning per-page text + a `full_text` join + doc-level metadata (title/author/subject/etc). Image-only / scanned PDFs surface `has_text: false` so agents can detect-and-recommend OCR. Optional `pages` slice (1-indexed inclusive range) for partial reads of long documents. Read-only. Same path-safety + privacy filter as `obsidian_read_note`. Powered by Mozilla's PDF.js (Apache-2.0).",
1239
+ description: "Extracts plain text from one PDF, returning per-page text + a `full_text` join + doc-level metadata (title/author/subject/etc). Image-only / scanned PDFs surface `has_text: false` so agents can detect-and-recommend OCR via `obsidian_ocr_pdf` (v2.10.0). Optional `pages` slice (1-indexed inclusive range) for partial reads of long documents. Read-only. Same path-safety + privacy filter as `obsidian_read_note`. Powered by Mozilla's PDF.js (Apache-2.0).",
1152
1240
  annotations: { ...READ_ONLY, title: "Read PDF" },
1153
1241
  inputSchema: {
1154
1242
  path: z.string().describe("Vault-relative path of the .pdf file (with or without .pdf)"),
@@ -1159,6 +1247,33 @@ rerankerConfig = null) {
1159
1247
  include_metadata: z.boolean().optional().describe("Include doc-level metadata in result (default true)")
1160
1248
  }
1161
1249
  }, async (args) => textResult(await readPdf(vault, args)));
1250
+ // v2.10.0 — OCR for image-only / scanned PDFs. Completes the v2.7-v2.8
1251
+ // PDF retrieval story: when `obsidian_read_pdf` returns `has_text: false`,
1252
+ // the agent calls `obsidian_ocr_pdf` to extract text via Tesseract.js.
1253
+ // Tesseract.js + @napi-rs/canvas are optionalDependencies — clean
1254
+ // install-hint error if missing. ~1-2s per page on M1 CPU.
1255
+ server.registerTool("obsidian_ocr_pdf", {
1256
+ title: "OCR a scanned/image-only PDF (Tesseract.js)",
1257
+ description: "Runs Tesseract OCR over each page of an image-only / scanned PDF, returning per-page text + per-page confidence + mean confidence + the same shape as `obsidian_read_pdf`. Use this when `obsidian_read_pdf` returns `has_text: false` (typical for scans, photographed paper, image-only PDFs). Multilingual via `lang` (default `'eng'`; multi-lang via `'+'`, e.g. `'eng+rus'`). Optional `pages` range and `scale` (DPI multiplier, default 2 ~ 150 DPI, capped at 4). ~1-2s per page on M1 CPU. Read-only. Powered by Tesseract.js (Apache-2.0; trained-data files download on first use into the local cache, ~10 MB per language) + @napi-rs/canvas for PDF→bitmap rendering. Both gated to `optionalDependencies` so the markdown-only path stays zero-cost.",
1258
+ annotations: { ...READ_ONLY, title: "OCR PDF" },
1259
+ inputSchema: {
1260
+ path: z.string().describe("Vault-relative path of the .pdf file (with or without .pdf)"),
1261
+ lang: z
1262
+ .string()
1263
+ .optional()
1264
+ .describe("Tesseract language pack(s). Default 'eng'. Multi-lang via '+': 'eng+rus' for English+Russian mixed scans. Common: 'eng', 'rus', 'jpn', 'chi_sim', 'fra', 'deu'."),
1265
+ pages: z
1266
+ .tuple([z.number().int().positive(), z.number().int().positive()])
1267
+ .optional()
1268
+ .describe("Optional 1-indexed inclusive page range, e.g. [2, 5] OCRs pages 2..5"),
1269
+ scale: z
1270
+ .number()
1271
+ .min(0.5)
1272
+ .max(4)
1273
+ .optional()
1274
+ .describe("Render scale (DPI multiplier). Default 2 (~150 DPI). Higher = better OCR on small text but slower.")
1275
+ }
1276
+ }, async (args) => textResult(await ocrPdf(vault, args)));
1162
1277
  // v2.0.0-beta.3: gated — see comment on obsidian_search_text above.
1163
1278
  if (diagnosticSearchTools)
1164
1279
  server.registerTool("obsidian_semantic_search", {