@oomkapwn/enquire-mcp 3.6.0-rc.1 → 3.6.0-rc.2
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 +66 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +498 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +11 -149
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +46 -2797
- package/dist/index.js.map +1 -1
- package/dist/prompts.d.ts +3 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +677 -0
- package/dist/prompts.js.map +1 -0
- package/dist/server.d.ts +190 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +701 -0
- package/dist/server.js.map +1 -0
- package/dist/tool-manifest.d.ts +34 -0
- package/dist/tool-manifest.d.ts.map +1 -0
- package/dist/tool-manifest.js +278 -0
- package/dist/tool-manifest.js.map +1 -0
- package/dist/tool-registry.d.ts +43 -0
- package/dist/tool-registry.d.ts.map +1 -0
- package/dist/tool-registry.js +940 -0
- package/dist/tool-registry.js.map +1 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,72 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
4
4
|
|
|
5
|
+
## [3.6.0-rc.2] — 2026-05-15
|
|
6
|
+
|
|
7
|
+
> **TL;DR:** v3.6.0 Phase 2 of 4 — `src/index.ts` (3665 lines) split into 5 domain modules (`cli.ts` 702 + `server.ts` 877 + `tool-registry.ts` 1300 + `prompts.ts` 790) plus a slim 84-line entry point. NEW `src/tool-manifest.ts` (318 lines, 44 machine-readable tool entries) becomes the single source of truth — `tests/docs-consistency.test.ts` pivoted off regex-parsing source code and reads the manifest directly. Pure refactor: same CLI surface, same registered tools, same 712 tests pass. Published under npm dist-tag `rc`.
|
|
8
|
+
|
|
9
|
+
**Pre-release — v3.6.0 sprint Phase 2.**
|
|
10
|
+
|
|
11
|
+
### Changed — `src/index.ts` (3665 lines) → domain modules
|
|
12
|
+
|
|
13
|
+
Phase 1 (rc.1) split `tools.ts`. This RC does the same for `index.ts`. The pre-refactor file packed CLI definition, MCP server construction, tool registration, prompt definitions, sync routines, and utility helpers into a single 3665-line monolith. After rc.2:
|
|
14
|
+
|
|
15
|
+
| File | Lines | Purpose |
|
|
16
|
+
|---|---:|---|
|
|
17
|
+
| `src/index.ts` (slim) | 84 | `VERSION` literal (kept here so `scripts/check-version-consistency.mjs` regex still finds it) + CLI-entry guard + re-exports (`main`, `parsePositiveInt`, `parseQuantizationMode`, `startServer`, `buildMcpServer`, `buildEmbedText`, `formatReadyBanner`, `prepareServerDeps`, types `ServeOptions` / `ServerDeps`) |
|
|
18
|
+
| `src/cli.ts` | 702 | `main()` + commander program definition (all 12 subcommands) |
|
|
19
|
+
| `src/server.ts` | 877 | MCP server construction: `ServeOptions`, `ServerDeps`, `prepareServerDeps`, `buildMcpServer`, `startServer`, `formatReadyBanner`, `buildEmbedText`, `syncEmbedDb` / `syncFtsIndex` / `syncPdfFtsIndex` / `syncPdfEmbedDb` |
|
|
20
|
+
| `src/tool-registry.ts` | 1300 | `registerFtsTools` + `registerReadTools` + `registerWriteTools` + `registerResources` + `registerChunkResource` + helpers (`embedDbPath`, `parsePositiveInt`, `parseQuantizationMode`, `encodeNotePath`, `decodeNotePath`, `textResult`) |
|
|
21
|
+
| `src/prompts.ts` | 790 | `registerPrompts` with all 19 MCP prompt definitions |
|
|
22
|
+
|
|
23
|
+
The slim `index.ts` keeps the v3.5.x re-export surface so `src/http-transport.ts` + tests + external consumers don't need to know about the new layout. Module dependency graph is cycle-safe (the `cli.ts` ↔ `index.ts` VERSION cycle is a literal-value cycle, evaluated at module-init time; runtime-only, no TDZ surprises).
|
|
24
|
+
|
|
25
|
+
### Added — `src/tool-manifest.ts` (318 lines, 44 entries)
|
|
26
|
+
|
|
27
|
+
Machine-readable manifest of every MCP tool: `name`, `kind` (`read` | `fts` | `write` | `diagnostic`), `gating` (the `--persistent-index` / `--enable-write` / etc. clause), and a 1-line `summary`. Entries:
|
|
28
|
+
|
|
29
|
+
| Kind | Count | Gating |
|
|
30
|
+
|---|---:|---|
|
|
31
|
+
| `read` | 33 | always-on |
|
|
32
|
+
| `fts` | 1 | `--persistent-index + --diagnostic-search-tools` |
|
|
33
|
+
| `diagnostic` | 3 | `--diagnostic-search-tools` |
|
|
34
|
+
| `write` | 7 | `--enable-write` |
|
|
35
|
+
| **Total** | **44** | (matches the count math invariant in `tests/docs-consistency.test.ts`) |
|
|
36
|
+
|
|
37
|
+
The full `registerTool()` description argument stays at the registration site so MCP clients still see verbatim what they did pre-refactor. The manifest's `summary` is a 1-line distillation for docs / future auto-generation use cases.
|
|
38
|
+
|
|
39
|
+
### Changed — `tests/docs-consistency.test.ts` pivots to TOOL_MANIFEST
|
|
40
|
+
|
|
41
|
+
Pre-v3.6.0-rc.2, this file regex-parsed `src/index.ts` for `registerTool(` patterns + `function registerWriteTools(` markers + `if (diagnosticSearchTools) server.registerTool(` gating syntax. After the rc.2 monolith split, those patterns moved to `tool-registry.ts` — but rather than chase the regex paths, we **pivoted the entire tool-count invariants to read `TOOL_MANIFEST` directly**. Type-safe, no regex brittleness, single source of truth.
|
|
42
|
+
|
|
43
|
+
Surfaces still parsed by regex (no manifest yet):
|
|
44
|
+
- `src/prompts.ts` — registerPrompt() names (possible v3.6.0-rc.3: introduce `PROMPT_MANIFEST`)
|
|
45
|
+
- `src/cli.ts` — `.command()` subcommand names + the `serve` / `serve-http` flag blocks (for the shared-help-strings invariant)
|
|
46
|
+
|
|
47
|
+
### Validation
|
|
48
|
+
|
|
49
|
+
712 unit tests pass · 31 test files · branches 75.29% · lines 89.54% · statements 86.06% · functions 82.15% · lint clean (1 pre-existing info note about biome schema 2.4.15 vs locally-cached 2.4.14 CLI — resolves on CI which installs 2.4.15 fresh) · `tsc` strict + `noUncheckedIndexedAccess` clean · smoke pass · version-consistency green at `3.6.0-rc.2` (5 surfaces).
|
|
50
|
+
|
|
51
|
+
### Migration
|
|
52
|
+
|
|
53
|
+
**No-op for consumers.** Public npm package surface (44 tools, 19 prompts, CLI flags, `package.json` `exports` sub-paths) is identical. The refactor is internal file structure + new `tool-manifest.ts` as a documentation source-of-truth.
|
|
54
|
+
|
|
55
|
+
For contributors:
|
|
56
|
+
- `import { TOOL_MANIFEST } from "@oomkapwn/enquire-mcp/dist/tool-manifest.js"` — programmatically iterate tools by kind / gating / summary.
|
|
57
|
+
- `src/index.ts` re-exports the v3.5.x surface unchanged. Imports from `./index.js` continue to work.
|
|
58
|
+
|
|
59
|
+
### Method note
|
|
60
|
+
|
|
61
|
+
This RC removed an entire **class of brittleness** (regex-parsing source code to extract structured data). Replaced with type-safe, IDE-completable iteration over a typed const array. The methodology: when refactor causes drift in tests, don't chase the regex — pivot the test to a machine-readable structure that survives future refactors.
|
|
62
|
+
|
|
63
|
+
### npm dist-tag
|
|
64
|
+
|
|
65
|
+
Published under **`rc`** dist-tag. Users on `latest` stay on v3.5.14. To try: `npm install @oomkapwn/enquire-mcp@rc`.
|
|
66
|
+
|
|
67
|
+
### Next RC
|
|
68
|
+
|
|
69
|
+
`v3.6.0-rc.3`: Full TSDoc (`@param` / `@returns` / `@example`) on 44 tools + 19 prompts + 20 `src/` modules (~1300+ lines of doc-comments). Setup for `v3.6.0-rc.4` TypeDoc auto-generation.
|
|
70
|
+
|
|
5
71
|
## [3.6.0-rc.1] — 2026-05-15
|
|
6
72
|
|
|
7
73
|
> **TL;DR:** v3.6.0 Phase 1 of 4 — `src/tools.ts` (4252 lines) split into 5 domain modules under `src/tools/` with a barrel re-export. Pure refactor: same exported surface, same signatures, all 712 tests pass. Published to npm under dist-tag `rc` (NOT `latest`); install with `npm i @oomkapwn/enquire-mcp@rc` to try.
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAiCA,wBAAsB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CA4pB1C"}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { DIAGNOSTIC_SEARCH_TOOLS_HELP, ENABLE_WRITE_HELP, PERSISTENT_INDEX_HELP } from "./cli-help.js";
|
|
3
|
+
import { EmbedDb } from "./embed-db.js";
|
|
4
|
+
import { DEFAULT_MODEL_ALIAS, EMBEDDING_MODELS, loadEmbedder, resolveModel } from "./embeddings.js";
|
|
5
|
+
import { defaultIndexFile, FtsIndex } from "./fts5.js";
|
|
6
|
+
import { VERSION } from "./index.js";
|
|
7
|
+
import { startServer, syncEmbedDb, syncFtsIndex, syncPdfEmbedDb, syncPdfFtsIndex } from "./server.js";
|
|
8
|
+
import { embedDbPath, parsePositiveInt, parseQuantizationMode } from "./tool-registry.js";
|
|
9
|
+
import { Vault } from "./vault.js";
|
|
10
|
+
export async function main() {
|
|
11
|
+
const program = new Command();
|
|
12
|
+
program
|
|
13
|
+
.name("enquire-mcp")
|
|
14
|
+
.description("enquire — MCP server for Obsidian vaults. Named after Tim Berners-Lee's 1980 prototype of the WWW.")
|
|
15
|
+
.version(VERSION);
|
|
16
|
+
program
|
|
17
|
+
.command("serve", { isDefault: true })
|
|
18
|
+
.description("Start the MCP server over stdio")
|
|
19
|
+
.requiredOption("--vault <path>", "Path to the Obsidian vault root")
|
|
20
|
+
.option("--enable-write", ENABLE_WRITE_HELP)
|
|
21
|
+
.option("--max-file-bytes <n>", "Max bytes for any single file read/write (default 5MB)")
|
|
22
|
+
.option("--cache-size <n>", "Max parsed-note cache entries (default 1024)")
|
|
23
|
+
.option("--persistent-cache", "Persist parsed-note cache to disk so cold starts skip re-parsing")
|
|
24
|
+
.option("--cache-file <path>", "Override the persistent-cache file location")
|
|
25
|
+
.option("--persistent-index", PERSISTENT_INDEX_HELP)
|
|
26
|
+
.option("--index-file <path>", "Override the FTS5 index file location")
|
|
27
|
+
.option("--tokenize <mode>", "FTS5 tokenize mode: 'unicode61' (default; Latin/Cyrillic) or 'trigram' (CJK/mixed-script)")
|
|
28
|
+
.option("--exclude-glob <pattern...>", "Glob pattern(s) — paths matching any pattern are invisible to all tools and refuse direct reads. Supports `*`, `**`, `?`. Repeatable. Example: `--exclude-glob '02_Personal/**' '*.private.md'`.")
|
|
29
|
+
.option("--read-paths <pattern...>", "Strict allowlist — when set, ONLY paths matching one of these glob patterns are visible. Complement to --exclude-glob (denylist). If both are set: a path must match an allow-glob AND not match any exclude-glob. Same glob semantics as --exclude-glob (`*`, `**`, `?`). Repeatable. Example: `--read-paths '01_Projects/**' '99_Daily/**'`.")
|
|
30
|
+
.option("--watch", "Watch the vault for .md add/change/unlink events and incrementally invalidate the parsed-note cache (and refresh the FTS5 index when --persistent-index is also enabled). Off by default. Use this for long-running servers where you keep editing in Obsidian and want search to stay fresh without restarting.")
|
|
31
|
+
.option("--disabled-tools <name...>", "Skip registration of specific tools by exact name. Useful when you want to expose a smaller surface to a particular agent (e.g. read-only research agent gets only obsidian_search_text + obsidian_read_note). Repeatable. Names are the same as in `tools/list` — `obsidian_*`. Example: `--disabled-tools obsidian_dataview_query obsidian_full_text_search`.")
|
|
32
|
+
.option("--enabled-tools <name...>", "Strict allowlist — when set, ONLY listed tools register. Complement to --disabled-tools (denylist). If both are set: a tool must be in the allowlist AND not in the denylist. Repeatable. Example: `--enabled-tools obsidian_search_text obsidian_read_note obsidian_get_recent_edits`.")
|
|
33
|
+
.option("--diagnostic-search-tools", DIAGNOSTIC_SEARCH_TOOLS_HELP)
|
|
34
|
+
.option("--include-pdfs", 'v2.8.0 — also index PDF files into FTS5 (and embeddings, if `enquire-mcp build-embeddings --include-pdfs` ran). With `--persistent-index`, PDF chunks become first-class hits in `obsidian_search` results, surfaced with `kind: "pdf"` flag. Off by default — opt-in because PDF text extraction is slower than markdown (~50-200ms per page on M1 cold). Requires the `pdfjs-dist` optionalDependency (default-installed unless you used `--omit=optional`).')
|
|
35
|
+
.option("--enable-reranker", "v2.9.0 — enable BGE cross-encoder reranking on top of RRF in `obsidian_search`. After fusion, top-N candidates (default 50) are re-scored by a cross-encoder model and re-sorted. Adds ~30-50ms per query on M1 CPU; +5-10 NDCG@10 typical for retrieval quality. Off by default — opt-in because the cross-encoder model is downloaded from HuggingFace on first call (~25-110 MB depending on alias). Requires the `@huggingface/transformers` optionalDependency.")
|
|
36
|
+
.option("--reranker-model <alias>", "v2.9.0 (registry extended in v3.3.0) — reranker alias from RERANKER_MODELS. Default `rerank-multilingual` (Xenova/mxbai-rerank-xsmall-v1, ~25 MB, multilingual). Other options: `rerank-bge` (~110 MB, English), `rerank-bge-large` (~560 MB, English, +1-2 NDCG@10), `rerank-jina-tiny` (~33 MB, English, latency-optimized), `rerank-multilingual-large` (~280 MB, 50+ langs). Only effective with `--enable-reranker`.")
|
|
37
|
+
.option("--reranker-top-n <n>", "v2.9.0 — how many top RRF-fused candidates to rerank (default 50). Larger N improves recall ceiling but costs more reranker compute (~30-50ms per 50 pairs on M1). Only effective with `--enable-reranker`.")
|
|
38
|
+
.option("--use-hnsw", "v2.13.0 — build an in-memory HNSW vector index on serve start (or rebuild if `.embed.db` is missing). Sub-10ms top-K queries at any vault scale, vs O(n) brute-force without it. Build cost: ~5s for 8K chunks, ~25s for 50K, ~4min for 500K (one-time per serve). Recall@10 ≥ 98% vs brute-force at default params. Requires the `hnswlib-wasm` optionalDependency (~340 KB, pure WASM, no native binding).")
|
|
39
|
+
.option("--hnsw-ef <n>", "v2.13.0 — HNSW search-time beam width (default 100; must be ≥ requested k). Higher = more accurate, slightly slower. Common range: 50-500. Only effective with `--use-hnsw`.")
|
|
40
|
+
.option("--late-chunk-context <chars>", "v2.15.0 — late-chunking-style context windowing on embeddings. When > 0, prepends doc title + heading breadcrumb + tails of neighboring chunks (this many chars from each side) before sending to the embedder. Typical +2-5 NDCG@10 retrieval boost at zero new dep cost. Default 0 (off; matches v2.1.0+ breadcrumb-only behavior). Only effective during `build-embeddings` or auto-rebuild.")
|
|
41
|
+
.option("--no-hnsw-persist", "v2.16.0 — disable HNSW index persistence. By default (with --use-hnsw), the index is saved to a sidecar `.hnsw.bin` + `.meta.json` next to `.embed.db` after the first build, then re-loaded on subsequent serve starts when the embed-db signature matches. Skipping persistence means a fresh rebuild every serve start (~25s for 50K chunks). Pass this flag if you can't write to the cache dir or want diagnostic-fresh builds.")
|
|
42
|
+
.option("--quantize-embeddings <mode>", "v2.17.0 — vector storage encoding for the persistent embed db. `f32` (default) is identical to v2.16- behavior. `int8` cuts BLOB size ~4× (per-vector min+scale + int8 bytes) at ~1-2% recall@10 cost. Must match the mode used at `build-embeddings` time — otherwise the index auto-rebuilds on serve start. Accepts `f32`/`float32`/`none` and `int8`/`i8`/`q8`.")
|
|
43
|
+
.action(async (opts) => {
|
|
44
|
+
// Validate up-front so a bad value fails before we touch the vault.
|
|
45
|
+
parseQuantizationMode(opts.quantizeEmbeddings);
|
|
46
|
+
await startServer(opts);
|
|
47
|
+
});
|
|
48
|
+
// v2.6.0 — remote-MCP HTTP transport. Mirrors `serve` flags + adds HTTP
|
|
49
|
+
// surface (bearer auth, rate-limit, CORS). See docs/http-transport.md.
|
|
50
|
+
program
|
|
51
|
+
.command("serve-http")
|
|
52
|
+
.description("Start the MCP server over HTTP (Streamable HTTP transport). For remote-MCP use with claude.ai web, ChatGPT, Cursor HTTP mode, mobile clients. Requires --bearer-token (or --bearer-token-env). Bind to 127.0.0.1 by default — front with Tailscale Funnel / Cloudflare Tunnel for remote access.")
|
|
53
|
+
.requiredOption("--vault <path>", "Path to the Obsidian vault root")
|
|
54
|
+
.option("--port <n>", "TCP port (default 3000)", "3000")
|
|
55
|
+
.option("--host <host>", "Bind host (default 127.0.0.1 — explicit because 0.0.0.0 must be opt-in for remote-MCP)", "127.0.0.1")
|
|
56
|
+
.option("--bearer-token <token>", "Bearer token clients must present in the Authorization header. Generate with `enquire-mcp gen-token`. Required.")
|
|
57
|
+
.option("--bearer-token-env <name>", "Read the bearer token from this env var instead of --bearer-token (cleaner for systemd / .env / process listings). Either flag is required.")
|
|
58
|
+
.option("--mcp-path <path>", "URL path for the MCP endpoint (default /mcp)", "/mcp")
|
|
59
|
+
.option("--rate-limit <n>", "Max requests per minute per bearer token (default 120). Pass 0 to disable.", "120")
|
|
60
|
+
.option("--cors-origin <origin...>", "CORS allowlist (repeatable). Default empty — no Access-Control-Allow-Origin sent. Use '*' as a single entry to allow any origin (not compatible with credentialed Bearer requests; you almost always want explicit origins like https://claude.ai).")
|
|
61
|
+
.option("--health-path <path>", "URL path for the unauthenticated health probe (default /health)", "/health")
|
|
62
|
+
.option("--stateful", "v2.14.0 — run in stateful mode: sessions keyed by `Mcp-Session-Id`, persistent SSE for server-initiated notifications, DELETE /mcp for explicit termination. Required for ChatGPT custom GPT actions and other clients expecting persistent state across requests. Off by default (stateless minimizes attack surface and is the right choice for short-running tools).")
|
|
63
|
+
.option("--session-idle-timeout-ms <n>", "v2.14.0 — evict stateful sessions idle longer than this many milliseconds. Default 1800000 (30 min). Only effective with --stateful.")
|
|
64
|
+
.option("--max-sessions <n>", "v2.14.0 — max concurrent stateful sessions. New sessions beyond this cap return 503 + Retry-After. Default 100. Only effective with --stateful.")
|
|
65
|
+
.option("--enable-write", ENABLE_WRITE_HELP)
|
|
66
|
+
.option("--max-file-bytes <n>", "Max bytes for any single file read/write (default 5MB)")
|
|
67
|
+
.option("--cache-size <n>", "Max parsed-note cache entries (default 1024)")
|
|
68
|
+
.option("--persistent-cache", "Persist parsed-note cache to disk so cold starts skip re-parsing")
|
|
69
|
+
.option("--cache-file <path>", "Override the persistent-cache file location")
|
|
70
|
+
.option("--persistent-index", PERSISTENT_INDEX_HELP)
|
|
71
|
+
.option("--index-file <path>", "Override the FTS5 index file location")
|
|
72
|
+
.option("--tokenize <mode>", "FTS5 tokenize mode: 'unicode61' (default) or 'trigram'")
|
|
73
|
+
.option("--exclude-glob <pattern...>", "Privacy denylist (same semantics as `serve`).")
|
|
74
|
+
.option("--read-paths <pattern...>", "Privacy allowlist (same semantics as `serve`).")
|
|
75
|
+
.option("--watch", "Watch the vault for .md changes and refresh indexes incrementally.")
|
|
76
|
+
.option("--disabled-tools <name...>", "Skip registration of specific tools by name.")
|
|
77
|
+
.option("--enabled-tools <name...>", "Strict allowlist — when set, ONLY listed tools register.")
|
|
78
|
+
.option("--diagnostic-search-tools", DIAGNOSTIC_SEARCH_TOOLS_HELP)
|
|
79
|
+
.option("--quantize-embeddings <mode>", "v2.17.0 — vector storage encoding for the persistent embed db (`f32` default, `int8` for ~4× smaller BLOBs). Must match the mode used at `build-embeddings` time.")
|
|
80
|
+
.action(async (opts) => {
|
|
81
|
+
const tokenFromArg = typeof opts.bearerToken === "string" ? opts.bearerToken.trim() : "";
|
|
82
|
+
const tokenFromEnv = typeof opts.bearerTokenEnv === "string" ? (process.env[opts.bearerTokenEnv] ?? "").trim() : "";
|
|
83
|
+
const bearerToken = tokenFromArg.length > 0 ? tokenFromArg : tokenFromEnv;
|
|
84
|
+
if (!bearerToken) {
|
|
85
|
+
process.stderr.write("enquire serve-http: --bearer-token (or --bearer-token-env <name>) is required.\n" +
|
|
86
|
+
" Generate one with: enquire-mcp gen-token\n");
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
// --port accepts 0 as "kernel-assigned ephemeral" — useful for tests
|
|
90
|
+
// and for scenarios where the user binds via a tunnel and doesn't
|
|
91
|
+
// care which local port. So we use a non-negative-integer check
|
|
92
|
+
// here, NOT parsePositiveInt (which would reject 0).
|
|
93
|
+
const portNum = Number(opts.port ?? "3000");
|
|
94
|
+
if (!Number.isFinite(portNum) || !Number.isInteger(portNum) || portNum < 0 || portNum > 65535) {
|
|
95
|
+
throw new Error(`--port must be an integer in [0, 65535]; got "${opts.port}"`);
|
|
96
|
+
}
|
|
97
|
+
// v2.14.0 — stateful-mode opts. Tolerate missing flags (default to
|
|
98
|
+
// standard values) and validate parsed integers.
|
|
99
|
+
const sessionIdleMs = opts.sessionIdleTimeoutMs !== undefined
|
|
100
|
+
? parsePositiveInt(opts.sessionIdleTimeoutMs, "--session-idle-timeout-ms")
|
|
101
|
+
: 30 * 60 * 1000;
|
|
102
|
+
const maxSessionsCap = opts.maxSessions !== undefined ? parsePositiveInt(opts.maxSessions, "--max-sessions") : 100;
|
|
103
|
+
// v2.17.0 — fail fast on a typo'd quantization mode.
|
|
104
|
+
const quantMode = parseQuantizationMode(opts.quantizeEmbeddings);
|
|
105
|
+
const httpOpts = {
|
|
106
|
+
...opts,
|
|
107
|
+
...(quantMode !== undefined ? { quantizeEmbeddings: quantMode } : {}),
|
|
108
|
+
port: portNum,
|
|
109
|
+
host: opts.host ?? "127.0.0.1",
|
|
110
|
+
bearerToken,
|
|
111
|
+
mcpPath: opts.mcpPath ?? "/mcp",
|
|
112
|
+
rateLimitPerMinute: opts.rateLimit !== undefined ? Number(opts.rateLimit) : 120,
|
|
113
|
+
corsOrigins: opts.corsOrigin ?? [],
|
|
114
|
+
healthPath: opts.healthPath ?? "/health",
|
|
115
|
+
stateful: opts.stateful === true,
|
|
116
|
+
sessionIdleTimeoutMs: sessionIdleMs,
|
|
117
|
+
maxSessions: maxSessionsCap
|
|
118
|
+
};
|
|
119
|
+
if (!Number.isFinite(httpOpts.rateLimitPerMinute) ||
|
|
120
|
+
httpOpts.rateLimitPerMinute < 0 ||
|
|
121
|
+
!Number.isInteger(httpOpts.rateLimitPerMinute)) {
|
|
122
|
+
throw new Error(`--rate-limit must be a non-negative integer; got "${opts.rateLimit}"`);
|
|
123
|
+
}
|
|
124
|
+
const { startHttpServer } = await import("./http-transport.js");
|
|
125
|
+
await startHttpServer(httpOpts);
|
|
126
|
+
});
|
|
127
|
+
// v2.6.0 — convenience helper. Same as `node -e
|
|
128
|
+
// 'console.log(require("crypto").randomBytes(32).toString("base64url"))'`
|
|
129
|
+
// but discoverable in --help.
|
|
130
|
+
program
|
|
131
|
+
.command("gen-token")
|
|
132
|
+
.description("Generate a fresh 32-byte base64url bearer token suitable for `serve-http --bearer-token`.")
|
|
133
|
+
.action(async () => {
|
|
134
|
+
const { generateBearerToken } = await import("./http-transport.js");
|
|
135
|
+
process.stdout.write(`${generateBearerToken()}\n`);
|
|
136
|
+
});
|
|
137
|
+
program
|
|
138
|
+
.command("clear-cache")
|
|
139
|
+
.description("Delete the persistent-cache file for a given vault")
|
|
140
|
+
.requiredOption("--vault <path>", "Vault whose cache to delete")
|
|
141
|
+
.option("--cache-file <path>", "Override the persistent-cache file location")
|
|
142
|
+
.action(async (opts) => {
|
|
143
|
+
const vault = new Vault(opts.vault, { persistentCache: true, cacheFile: opts.cacheFile });
|
|
144
|
+
await vault.ensureExists();
|
|
145
|
+
const removed = await vault.clearDiskCache();
|
|
146
|
+
if (removed) {
|
|
147
|
+
process.stdout.write(`enquire: removed cache file ${vault.cacheFile}\n`);
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
process.stdout.write(`enquire: no cache file at ${vault.cacheFile}\n`);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
program
|
|
154
|
+
.command("clear-index")
|
|
155
|
+
.description("Delete the FTS5 search-index files (.fts5.db + WAL/SHM sidecar) for a given vault")
|
|
156
|
+
.requiredOption("--vault <path>", "Vault whose index to delete")
|
|
157
|
+
.option("--index-file <path>", "Override the FTS5 index file location")
|
|
158
|
+
.action(async (opts) => {
|
|
159
|
+
const vault = new Vault(opts.vault);
|
|
160
|
+
await vault.ensureExists();
|
|
161
|
+
const indexFile = opts.indexFile ?? defaultIndexFile(vault.root);
|
|
162
|
+
const idx = new FtsIndex({ file: indexFile, vaultRoot: vault.root });
|
|
163
|
+
const removed = await idx.clearOnDisk();
|
|
164
|
+
if (removed) {
|
|
165
|
+
process.stdout.write(`enquire: removed fts5 index files at ${indexFile}\n`);
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
process.stdout.write(`enquire: no fts5 index files at ${indexFile}\n`);
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
program
|
|
172
|
+
.command("index")
|
|
173
|
+
.description("Cold-build (or refresh) the FTS5 search index for a vault. Useful before first --persistent-index use.")
|
|
174
|
+
.requiredOption("--vault <path>", "Path to the Obsidian vault root")
|
|
175
|
+
.option("--index-file <path>", "Override the FTS5 index file location")
|
|
176
|
+
.option("--tokenize <mode>", "FTS5 tokenize mode: 'unicode61' (default) or 'trigram'")
|
|
177
|
+
.option("--include-pdfs", "v2.8.0 — also index PDFs into the FTS5 index. Off by default; PDF extraction is slower than markdown.")
|
|
178
|
+
.action(async (opts) => {
|
|
179
|
+
const tokenize = opts.tokenize === "trigram" ? "trigram" : "unicode61";
|
|
180
|
+
const vault = new Vault(opts.vault);
|
|
181
|
+
await vault.ensureExists();
|
|
182
|
+
const indexFile = opts.indexFile ?? defaultIndexFile(vault.root);
|
|
183
|
+
const idx = new FtsIndex({ file: indexFile, vaultRoot: vault.root, tokenize });
|
|
184
|
+
await idx.open();
|
|
185
|
+
try {
|
|
186
|
+
const report = await syncFtsIndex(vault, idx);
|
|
187
|
+
process.stdout.write(`enquire: index ${indexFile} (md) — added=${report.added} updated=${report.updated} deleted=${report.deleted} unchanged=${report.unchanged} total_chunks=${report.total_chunks}\n`);
|
|
188
|
+
if (opts.includePdfs) {
|
|
189
|
+
const pdfReport = await syncPdfFtsIndex(vault, idx);
|
|
190
|
+
process.stdout.write(`enquire: index ${indexFile} (pdf) — added=${pdfReport.added} updated=${pdfReport.updated} deleted=${pdfReport.deleted} unchanged=${pdfReport.unchanged} total_chunks=${pdfReport.total_chunks}\n`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
finally {
|
|
194
|
+
idx.close();
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
// v2.0 alpha — ML embeddings subcommands.
|
|
198
|
+
program
|
|
199
|
+
.command("install-model")
|
|
200
|
+
.description(`Pre-download an embedding model so the first \`obsidian_embeddings_search\` call doesn't block on a ${EMBEDDING_MODELS[DEFAULT_MODEL_ALIAS]?.approxSizeMB}MB HuggingFace download. Models are cached under ~/.cache/huggingface/transformers.js/ and are reused across vaults.`)
|
|
201
|
+
.argument("[alias]", `Model alias (${Object.keys(EMBEDDING_MODELS).join(" | ")})`, DEFAULT_MODEL_ALIAS)
|
|
202
|
+
.action(async (alias) => {
|
|
203
|
+
const model = resolveModel(alias);
|
|
204
|
+
process.stderr.write(`enquire: downloading ${model.hfId} (~${model.approxSizeMB}MB; ${model.dim}-dim, ${model.multilingual ? "multilingual" : "English-only"})...\n`);
|
|
205
|
+
const t0 = Date.now();
|
|
206
|
+
// Loading the embedder triggers the transformers.js model download +
|
|
207
|
+
// local cache write. We don't actually run inference — just verify the
|
|
208
|
+
// pipeline initializes successfully.
|
|
209
|
+
const embedder = await loadEmbedder(alias);
|
|
210
|
+
// Smoke: embed one tiny string so any ONNX-runtime failure surfaces here
|
|
211
|
+
// rather than at first MCP call.
|
|
212
|
+
const [vec] = await embedder.embed(["hello"]);
|
|
213
|
+
if (!vec || vec.length !== model.dim) {
|
|
214
|
+
throw new Error(`Model loaded but produced unexpected output dim=${vec?.length}`);
|
|
215
|
+
}
|
|
216
|
+
process.stdout.write(`enquire: model ${alias} ready (${model.dim}-dim, ${Date.now() - t0}ms warmup, cached under ~/.cache/huggingface/)\n`);
|
|
217
|
+
});
|
|
218
|
+
program
|
|
219
|
+
.command("build-embeddings")
|
|
220
|
+
.description("Cold-build (or refresh) the persistent embedding index for a vault. Required before `obsidian_embeddings_search` is useful. Uses the same paragraph-level chunking as the FTS5 index, so chunk identity matches across BM25 and embeddings.")
|
|
221
|
+
.requiredOption("--vault <path>", "Path to the Obsidian vault root")
|
|
222
|
+
.option("--embedding-model <alias>", `Model alias (default: ${DEFAULT_MODEL_ALIAS})`, DEFAULT_MODEL_ALIAS)
|
|
223
|
+
.option("--embed-file <path>", "Override the .embed.db file location")
|
|
224
|
+
.option("--exclude-glob <pattern...>", "Exclude paths matching glob (repeatable)")
|
|
225
|
+
.option("--read-paths <pattern...>", "Strict allowlist of glob patterns (repeatable)")
|
|
226
|
+
.option("--include-pdfs", "v2.8.0 — also embed PDF chunks. Off by default; PDF extraction + embedding is ~10-30x slower than markdown per file.")
|
|
227
|
+
.option("--late-chunk-context <chars>", "v2.15.0 — context-windowed embedding text (doc title + breadcrumb + neighbor-chunk tails of N chars). Default 0 (off). Typical 100-200 for +2-5 NDCG@10.")
|
|
228
|
+
.option("--quantize-embeddings <mode>", "v2.17.0 — vector storage encoding. `f32` (default) is identical to v2.16- behavior. `int8` uses asymmetric scalar quantization (per-vector min + scale + int8 bytes) for ~4× smaller BLOBs at ~1-2% recall@10 cost. Switching modes triggers a full rebuild via the schema-mismatch path. Accepts `f32`/`float32`/`none` and `int8`/`i8`/`q8`.")
|
|
229
|
+
.action(async (opts) => {
|
|
230
|
+
const model = resolveModel(opts.embeddingModel);
|
|
231
|
+
const vault = new Vault(opts.vault, { excludeGlobs: opts.excludeGlob, readPaths: opts.readPaths });
|
|
232
|
+
await vault.ensureExists();
|
|
233
|
+
const embedFile = opts.embedFile ?? embedDbPath(vault.root);
|
|
234
|
+
const quantization = parseQuantizationMode(opts.quantizeEmbeddings) ?? "f32";
|
|
235
|
+
const db = new EmbedDb({
|
|
236
|
+
file: embedFile,
|
|
237
|
+
vaultRoot: vault.root,
|
|
238
|
+
modelAlias: model.alias,
|
|
239
|
+
dim: model.dim,
|
|
240
|
+
quantization
|
|
241
|
+
});
|
|
242
|
+
const lateChunkContext = opts.lateChunkContext !== undefined
|
|
243
|
+
? Math.max(0, parsePositiveInt(opts.lateChunkContext, "--late-chunk-context"))
|
|
244
|
+
: 0;
|
|
245
|
+
await db.open();
|
|
246
|
+
try {
|
|
247
|
+
process.stderr.write(`enquire: loading embedder ${model.alias} (${model.hfId})...\n`);
|
|
248
|
+
const embedder = await loadEmbedder(opts.embeddingModel);
|
|
249
|
+
const report = await syncEmbedDb(vault, db, embedder, { lateChunkContext });
|
|
250
|
+
process.stdout.write(`enquire: embed db ${embedFile} (md) — added=${report.added} updated=${report.updated} deleted=${report.deleted} unchanged=${report.unchanged} total_chunks=${report.total_chunks}${lateChunkContext > 0 ? ` late-chunk-context=${lateChunkContext}` : ""}${quantization !== "f32" ? ` quantization=${quantization}` : ""}\n`);
|
|
251
|
+
if (opts.includePdfs) {
|
|
252
|
+
const pdfReport = await syncPdfEmbedDb(vault, db, embedder, { lateChunkContext });
|
|
253
|
+
process.stdout.write(`enquire: embed db ${embedFile} (pdf) — added=${pdfReport.added} updated=${pdfReport.updated} deleted=${pdfReport.deleted} unchanged=${pdfReport.unchanged} total_chunks=${pdfReport.total_chunks}\n`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
finally {
|
|
257
|
+
db.close();
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
program
|
|
261
|
+
.command("clear-embeddings")
|
|
262
|
+
.description("Delete the embedding index files (.embed.db + WAL/SHM sidecar) for a given vault")
|
|
263
|
+
.requiredOption("--vault <path>", "Vault whose embedding index to delete")
|
|
264
|
+
.option("--embed-file <path>", "Override the embedding-index file location")
|
|
265
|
+
.action(async (opts) => {
|
|
266
|
+
const vault = new Vault(opts.vault);
|
|
267
|
+
await vault.ensureExists();
|
|
268
|
+
const file = opts.embedFile ?? embedDbPath(vault.root);
|
|
269
|
+
// Use any model alias / dim for the delete path — bootstrapSchema uses
|
|
270
|
+
// them only when the file already exists with mismatched meta.
|
|
271
|
+
const db = new EmbedDb({ file, vaultRoot: vault.root, modelAlias: "n/a", dim: 1 });
|
|
272
|
+
const removed = await db.clearOnDisk();
|
|
273
|
+
if (removed) {
|
|
274
|
+
process.stdout.write(`enquire: removed embedding index files at ${file}\n`);
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
process.stdout.write(`enquire: no embedding index files at ${file}\n`);
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
// v2.11.0 — diagnostic + zero-touch onboarding. `doctor` is read-only and
|
|
281
|
+
// returns 0 if everything is ready, 1 if any critical setup is missing.
|
|
282
|
+
// `setup` runs the install + build sequence in order, idempotent.
|
|
283
|
+
program
|
|
284
|
+
.command("doctor")
|
|
285
|
+
.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.")
|
|
286
|
+
.requiredOption("--vault <path>", "Path to the Obsidian vault root")
|
|
287
|
+
.option("--json", "Emit machine-readable JSON instead of the colored banner")
|
|
288
|
+
.action(async (opts) => {
|
|
289
|
+
const { runDoctor, formatDoctorResult } = await import("./doctor.js");
|
|
290
|
+
const result = await runDoctor({
|
|
291
|
+
vault: opts.vault,
|
|
292
|
+
modelEntry: EMBEDDING_MODELS[DEFAULT_MODEL_ALIAS]
|
|
293
|
+
});
|
|
294
|
+
if (opts.json) {
|
|
295
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
process.stdout.write(`${formatDoctorResult(result)}\n`);
|
|
299
|
+
}
|
|
300
|
+
if (!result.ready)
|
|
301
|
+
process.exit(1);
|
|
302
|
+
});
|
|
303
|
+
program
|
|
304
|
+
.command("setup")
|
|
305
|
+
.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.")
|
|
306
|
+
.requiredOption("--vault <path>", "Path to the Obsidian vault root")
|
|
307
|
+
.option("--embedding-model <alias>", `Model alias (default: ${DEFAULT_MODEL_ALIAS})`, DEFAULT_MODEL_ALIAS)
|
|
308
|
+
.option("--include-pdfs", "Also index PDFs (FTS5 + embeddings). Off by default; opt-in because PDF extraction is slower.")
|
|
309
|
+
.option("--skip-embeddings", "Skip the install-model + build-embeddings steps (only build FTS5)")
|
|
310
|
+
.option("--quantize-embeddings <mode>", "v2.17.0 — vector storage encoding for the embed db (`f32` default, `int8` for ~4× smaller BLOBs). Same semantics as the `build-embeddings` flag.")
|
|
311
|
+
.action(async (opts) => {
|
|
312
|
+
const v = new Vault(opts.vault);
|
|
313
|
+
await v.ensureExists();
|
|
314
|
+
process.stdout.write(`enquire setup — ${opts.vault}\n\n`);
|
|
315
|
+
// Step 1: FTS5 index.
|
|
316
|
+
process.stdout.write(">> Step 1/3: Cold-build FTS5 index\n");
|
|
317
|
+
const indexFile = defaultIndexFile(v.root);
|
|
318
|
+
const idx = new FtsIndex({ file: indexFile, vaultRoot: v.root });
|
|
319
|
+
await idx.open();
|
|
320
|
+
try {
|
|
321
|
+
const ftsReport = await syncFtsIndex(v, idx);
|
|
322
|
+
process.stdout.write(` FTS5 (md): added=${ftsReport.added} updated=${ftsReport.updated} unchanged=${ftsReport.unchanged} chunks=${ftsReport.total_chunks}\n`);
|
|
323
|
+
if (opts.includePdfs) {
|
|
324
|
+
const pdfReport = await syncPdfFtsIndex(v, idx);
|
|
325
|
+
process.stdout.write(` FTS5 (pdf): added=${pdfReport.added} updated=${pdfReport.updated} unchanged=${pdfReport.unchanged} chunks=${pdfReport.total_chunks}\n`);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
finally {
|
|
329
|
+
idx.close();
|
|
330
|
+
}
|
|
331
|
+
if (opts.skipEmbeddings) {
|
|
332
|
+
process.stdout.write("\n>> Step 2-3 skipped (--skip-embeddings)\n");
|
|
333
|
+
process.stdout.write("\nSetup partial. Run without --skip-embeddings to enable ML hybrid retrieval.\n");
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
// Step 2: Install-model.
|
|
337
|
+
process.stdout.write("\n>> Step 2/3: Install embedding model\n");
|
|
338
|
+
const model = resolveModel(opts.embeddingModel);
|
|
339
|
+
const t0 = Date.now();
|
|
340
|
+
const embedder = await loadEmbedder(opts.embeddingModel);
|
|
341
|
+
const [smokeVec] = await embedder.embed(["hello"]);
|
|
342
|
+
if (!smokeVec || smokeVec.length !== model.dim) {
|
|
343
|
+
throw new Error(`Model ${model.alias} loaded but dim mismatch: ${smokeVec?.length} vs ${model.dim}`);
|
|
344
|
+
}
|
|
345
|
+
process.stdout.write(` model ${model.alias} ready (${model.dim}-dim, ${Date.now() - t0}ms warmup, cached under ~/.cache/huggingface/)\n`);
|
|
346
|
+
// Step 3: build-embeddings.
|
|
347
|
+
process.stdout.write("\n>> Step 3/3: Build embedding index\n");
|
|
348
|
+
const embedFile = embedDbPath(v.root);
|
|
349
|
+
const quantization = parseQuantizationMode(opts.quantizeEmbeddings) ?? "f32";
|
|
350
|
+
const db = new EmbedDb({
|
|
351
|
+
file: embedFile,
|
|
352
|
+
vaultRoot: v.root,
|
|
353
|
+
modelAlias: model.alias,
|
|
354
|
+
dim: model.dim,
|
|
355
|
+
quantization
|
|
356
|
+
});
|
|
357
|
+
await db.open();
|
|
358
|
+
try {
|
|
359
|
+
const embReport = await syncEmbedDb(v, db, embedder);
|
|
360
|
+
process.stdout.write(` embed-db (md): added=${embReport.added} updated=${embReport.updated} unchanged=${embReport.unchanged} chunks=${embReport.total_chunks}${quantization !== "f32" ? ` quantization=${quantization}` : ""}\n`);
|
|
361
|
+
if (opts.includePdfs) {
|
|
362
|
+
const pdfReport = await syncPdfEmbedDb(v, db, embedder);
|
|
363
|
+
process.stdout.write(` embed-db (pdf): added=${pdfReport.added} updated=${pdfReport.updated} unchanged=${pdfReport.unchanged} chunks=${pdfReport.total_chunks}\n`);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
finally {
|
|
367
|
+
db.close();
|
|
368
|
+
}
|
|
369
|
+
process.stdout.write("\n✓ Setup complete. Now run:\n");
|
|
370
|
+
process.stdout.write(` enquire-mcp serve --vault ${opts.vault} --persistent-index`);
|
|
371
|
+
if (opts.includePdfs)
|
|
372
|
+
process.stdout.write(" --include-pdfs");
|
|
373
|
+
if (quantization !== "f32")
|
|
374
|
+
process.stdout.write(` --quantize-embeddings ${quantization}`);
|
|
375
|
+
process.stdout.write("\n");
|
|
376
|
+
process.stdout.write(`Or check status: enquire-mcp doctor --vault ${opts.vault}\n`);
|
|
377
|
+
});
|
|
378
|
+
// v2.12.0 — retrieval-quality evaluation harness. Reads a JSONL file of
|
|
379
|
+
// queries with known-relevant doc paths, runs obsidian_search for each,
|
|
380
|
+
// computes NDCG@K + Recall@K + MRR. Pretty table by default, --json for
|
|
381
|
+
// machine output, --matrix to A/B several flag combinations.
|
|
382
|
+
program
|
|
383
|
+
.command("eval")
|
|
384
|
+
.description("Built-in retrieval-quality benchmark harness. Reads a JSONL file of queries with known-relevant doc paths, runs `obsidian_search` for each, computes NDCG@K + Recall@K + MRR + per-query latency. Pretty table output by default; `--json` for machine-readable output. `--matrix` runs all combinations of (graph_boost on/off × reranker on/off) side-by-side for systematic tuning. The only Obsidian-MCP with built-in retrieval evaluation.")
|
|
385
|
+
.requiredOption("--vault <path>", "Path to the Obsidian vault root")
|
|
386
|
+
.requiredOption("--queries <file>", "JSONL file with {query, relevant: ['path1', ...], id?} per line")
|
|
387
|
+
.option("--k <n>", "Top-K cutoff for NDCG / Recall (default 10)", "10")
|
|
388
|
+
.option("--matrix", "Run a 2x2 matrix of (graph_boost ± reranker) and print a comparison table")
|
|
389
|
+
.option("--reranker", "Enable cross-encoder reranking (same as serve --enable-reranker)")
|
|
390
|
+
.option("--reranker-model <alias>", `Reranker alias (default rerank-multilingual)`, "rerank-multilingual")
|
|
391
|
+
.option("--reranker-top-n <n>", "How many top RRF candidates to rerank (default 50)", "50")
|
|
392
|
+
.option("--persistent-index", "Open the FTS5 index for BM25 retrieval (recommended)")
|
|
393
|
+
.option("--per-query", "Print per-query scores in addition to aggregates (verbose)")
|
|
394
|
+
.option("--json", "Emit machine-readable JSON instead of the pretty table")
|
|
395
|
+
.action(async (opts) => {
|
|
396
|
+
const { readQueriesJsonl, runEval, formatEvalResult, formatEvalMatrix } = await import("./eval.js");
|
|
397
|
+
const k = parsePositiveInt(opts.k ?? "10", "--k");
|
|
398
|
+
const queries = await readQueriesJsonl(opts.queries);
|
|
399
|
+
if (queries.length === 0) {
|
|
400
|
+
process.stderr.write(`enquire eval: ${opts.queries} contains no queries\n`);
|
|
401
|
+
process.exit(1);
|
|
402
|
+
}
|
|
403
|
+
process.stderr.write(`enquire eval: loaded ${queries.length} queries from ${opts.queries}\n`);
|
|
404
|
+
const v = new Vault(opts.vault);
|
|
405
|
+
await v.ensureExists();
|
|
406
|
+
// Optional FTS5 index.
|
|
407
|
+
let ftsIndex = null;
|
|
408
|
+
if (opts.persistentIndex) {
|
|
409
|
+
const indexFile = defaultIndexFile(v.root);
|
|
410
|
+
ftsIndex = new FtsIndex({ file: indexFile, vaultRoot: v.root });
|
|
411
|
+
try {
|
|
412
|
+
await ftsIndex.open();
|
|
413
|
+
await syncFtsIndex(v, ftsIndex);
|
|
414
|
+
}
|
|
415
|
+
catch (err) {
|
|
416
|
+
ftsIndex.close();
|
|
417
|
+
throw err;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
const embedFile = embedDbPath(v.root);
|
|
421
|
+
try {
|
|
422
|
+
if (opts.matrix) {
|
|
423
|
+
// 2x2 matrix: (graph_boost ± reranker)
|
|
424
|
+
const configs = [
|
|
425
|
+
{ label: "baseline (RRF only)", searchOpts: { graph_boost: false } },
|
|
426
|
+
{ label: "+graph-boost", searchOpts: { graph_boost: true } },
|
|
427
|
+
{
|
|
428
|
+
label: "+reranker",
|
|
429
|
+
searchOpts: { graph_boost: false },
|
|
430
|
+
reranker: {
|
|
431
|
+
alias: opts.rerankerModel ?? "rerank-multilingual",
|
|
432
|
+
topN: parsePositiveInt(opts.rerankerTopN ?? "50", "--reranker-top-n")
|
|
433
|
+
}
|
|
434
|
+
},
|
|
435
|
+
{
|
|
436
|
+
label: "+graph-boost +reranker",
|
|
437
|
+
searchOpts: { graph_boost: true },
|
|
438
|
+
reranker: {
|
|
439
|
+
alias: opts.rerankerModel ?? "rerank-multilingual",
|
|
440
|
+
topN: parsePositiveInt(opts.rerankerTopN ?? "50", "--reranker-top-n")
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
];
|
|
444
|
+
const results = [];
|
|
445
|
+
for (const cfg of configs) {
|
|
446
|
+
process.stderr.write(`enquire eval: running config "${cfg.label}"...\n`);
|
|
447
|
+
const r = await runEval({
|
|
448
|
+
vault: v,
|
|
449
|
+
queries,
|
|
450
|
+
ftsIndex,
|
|
451
|
+
embedFile,
|
|
452
|
+
k,
|
|
453
|
+
label: cfg.label,
|
|
454
|
+
searchOpts: cfg.searchOpts,
|
|
455
|
+
...(cfg.reranker ? { reranker: cfg.reranker } : {})
|
|
456
|
+
});
|
|
457
|
+
results.push(r);
|
|
458
|
+
}
|
|
459
|
+
if (opts.json) {
|
|
460
|
+
process.stdout.write(`${JSON.stringify(results, null, 2)}\n`);
|
|
461
|
+
}
|
|
462
|
+
else {
|
|
463
|
+
process.stdout.write(`${formatEvalMatrix(results)}\n`);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
else {
|
|
467
|
+
// Single-config run.
|
|
468
|
+
const reranker = opts.reranker
|
|
469
|
+
? {
|
|
470
|
+
alias: opts.rerankerModel ?? "rerank-multilingual",
|
|
471
|
+
topN: parsePositiveInt(opts.rerankerTopN ?? "50", "--reranker-top-n")
|
|
472
|
+
}
|
|
473
|
+
: undefined;
|
|
474
|
+
const result = await runEval({
|
|
475
|
+
vault: v,
|
|
476
|
+
queries,
|
|
477
|
+
ftsIndex,
|
|
478
|
+
embedFile,
|
|
479
|
+
k,
|
|
480
|
+
label: reranker ? `with-reranker(${reranker.alias})` : "default",
|
|
481
|
+
...(reranker ? { reranker } : {})
|
|
482
|
+
});
|
|
483
|
+
if (opts.json) {
|
|
484
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
485
|
+
}
|
|
486
|
+
else {
|
|
487
|
+
process.stdout.write(`${formatEvalResult(result, { perQuery: opts.perQuery ?? false })}\n`);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
finally {
|
|
492
|
+
if (ftsIndex)
|
|
493
|
+
ftsIndex.close();
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
await program.parseAsync(process.argv);
|
|
497
|
+
}
|
|
498
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|