@oomkapwn/enquire-mcp 2.15.0 → 2.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +159 -0
- package/README.md +7 -5
- package/dist/embed-db.d.ts +54 -0
- package/dist/embed-db.d.ts.map +1 -1
- package/dist/embed-db.js +124 -15
- package/dist/embed-db.js.map +1 -1
- package/dist/hnsw.d.ts +72 -0
- package/dist/hnsw.d.ts.map +1 -1
- package/dist/hnsw.js +98 -1
- package/dist/hnsw.js.map +1 -1
- package/dist/index.d.ts +21 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +113 -25
- package/dist/index.js.map +1 -1
- package/docs/api.md +5 -1
- package/package.json +1 -1
package/dist/hnsw.d.ts
CHANGED
|
@@ -6,6 +6,42 @@ export interface LabeledVector {
|
|
|
6
6
|
/** L2-normalized vector. Caller is responsible for the normalization. */
|
|
7
7
|
vector: Float32Array;
|
|
8
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* v2.16.0 — sidecar metadata persisted alongside the .hnsw.bin index. Used
|
|
11
|
+
* for staleness detection on boot: if `signature` matches the current
|
|
12
|
+
* embed-db's signature (computed by `EmbedDb.computeSignature()`), the
|
|
13
|
+
* pre-built HNSW is loaded; otherwise it's rebuilt from scratch.
|
|
14
|
+
*
|
|
15
|
+
* Stored as JSON next to the binary index (`<file>.meta.json`). Keep this
|
|
16
|
+
* format stable — bumping `formatVersion` invalidates all on-disk
|
|
17
|
+
* indexes for users on the new version (they'll rebuild on next boot,
|
|
18
|
+
* which is harmless but visible).
|
|
19
|
+
*/
|
|
20
|
+
export interface HnswPersistedMeta {
|
|
21
|
+
formatVersion: 1;
|
|
22
|
+
/** Embedder dim — must match the corpus the index will be queried with. */
|
|
23
|
+
dim: number;
|
|
24
|
+
/** Vector count at write time. */
|
|
25
|
+
size: number;
|
|
26
|
+
/**
|
|
27
|
+
* Embed-db signature at write time — when this differs from the current
|
|
28
|
+
* embed-db's signature, the persisted index is stale and should be
|
|
29
|
+
* rebuilt. We use rowcount + max-id + dim as a tractable signature
|
|
30
|
+
* (full content-hash would require reading every vector).
|
|
31
|
+
*/
|
|
32
|
+
signature: string;
|
|
33
|
+
/** Row label → source row map needed to reconstruct hits. JSON-friendly. */
|
|
34
|
+
rowsByLabel: Record<string, {
|
|
35
|
+
rel_path: string;
|
|
36
|
+
chunk_index: number;
|
|
37
|
+
line_start: number;
|
|
38
|
+
line_end: number;
|
|
39
|
+
text_preview: string;
|
|
40
|
+
kind: "md" | "pdf";
|
|
41
|
+
}>;
|
|
42
|
+
/** ISO timestamp of the write — informational. */
|
|
43
|
+
writtenAt: string;
|
|
44
|
+
}
|
|
9
45
|
/** Build-time HNSW parameters. Defaults tuned for 384-dim cosine on PKM data. */
|
|
10
46
|
export interface HnswBuildOptions {
|
|
11
47
|
/** Embedding dimensionality (must match the corpus). */
|
|
@@ -52,6 +88,25 @@ export interface HnswIndex {
|
|
|
52
88
|
labels: number[];
|
|
53
89
|
distances: number[];
|
|
54
90
|
};
|
|
91
|
+
/**
|
|
92
|
+
* v2.16.0 — persist the index to disk for fast reload on next serve
|
|
93
|
+
* start. Writes the binary index to `<file>.bin` and a JSON meta
|
|
94
|
+
* sidecar to `<file>.meta.json` containing the embed-db signature,
|
|
95
|
+
* dim, size, and label→row map. Returns true on successful write.
|
|
96
|
+
*
|
|
97
|
+
* Caller is responsible for choosing `file` (typically alongside the
|
|
98
|
+
* embed-db with `.hnsw` suffix). We separate binary + meta files so
|
|
99
|
+
* a partial write (e.g. crash mid-flush) leaves the meta missing,
|
|
100
|
+
* which the loader treats as "no usable index" → rebuild from scratch.
|
|
101
|
+
*/
|
|
102
|
+
saveTo(file: string, rowsByLabel: ReadonlyMap<number, {
|
|
103
|
+
rel_path: string;
|
|
104
|
+
chunk_index: number;
|
|
105
|
+
line_start: number;
|
|
106
|
+
line_end: number;
|
|
107
|
+
text_preview: string;
|
|
108
|
+
kind: "md" | "pdf";
|
|
109
|
+
}>, signature: string): Promise<boolean>;
|
|
55
110
|
}
|
|
56
111
|
/**
|
|
57
112
|
* Build a fresh in-memory HNSW from labeled vectors.
|
|
@@ -65,6 +120,23 @@ export interface HnswIndex {
|
|
|
65
120
|
* is less than the input count, or if `hnswlib-wasm` failed to load.
|
|
66
121
|
*/
|
|
67
122
|
export declare function buildHnsw(vectors: ReadonlyArray<LabeledVector>, opts: HnswBuildOptions): Promise<HnswIndex>;
|
|
123
|
+
/**
|
|
124
|
+
* v2.16.0 — load a previously-persisted HNSW index from disk. Returns
|
|
125
|
+
* `null` (with a stderr warning) if:
|
|
126
|
+
* • Either the .bin or .meta.json file is missing
|
|
127
|
+
* • The meta's `signature` doesn't match the caller's current signature
|
|
128
|
+
* • The meta's `formatVersion` doesn't match
|
|
129
|
+
* • The native lib fails to load the .bin (corrupt / dim mismatch)
|
|
130
|
+
*
|
|
131
|
+
* On success returns `{ index, rowsByLabel }` so the caller can wire
|
|
132
|
+
* both into `searchHybrid`'s `hnsw` context without rebuilding from
|
|
133
|
+
* scratch. Typical boot-time win: ~25s rebuild → ~50ms load on a
|
|
134
|
+
* 50K-chunk vault.
|
|
135
|
+
*/
|
|
136
|
+
export declare function loadHnswFromDisk(file: string, expectedSignature: string): Promise<{
|
|
137
|
+
index: HnswIndex;
|
|
138
|
+
rowsByLabel: Map<number, HnswPersistedMeta["rowsByLabel"][string]>;
|
|
139
|
+
} | null>;
|
|
68
140
|
/**
|
|
69
141
|
* Convert HNSW search results to EmbedSearchHit using a label → source-row
|
|
70
142
|
* lookup. The label was assigned by the caller at build time (typically
|
package/dist/hnsw.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hnsw.d.ts","sourceRoot":"","sources":["../src/hnsw.ts"],"names":[],"mappings":"AA0CA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEpD,4DAA4D;AAC5D,MAAM,WAAW,aAAa;IAC5B,wFAAwF;IACxF,KAAK,EAAE,MAAM,CAAC;IACd,yEAAyE;IACzE,MAAM,EAAE,YAAY,CAAC;CACtB;AAED,iFAAiF;AACjF,MAAM,WAAW,gBAAgB;IAC/B,wDAAwD;IACxD,GAAG,EAAE,MAAM,CAAC;IACZ,8EAA8E;IAC9E,WAAW,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,CAAC,CAAC,EAAE,MAAM,CAAC;IACX;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oEAAoE;IACpE,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,4BAA4B;AAC5B,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,6BAA6B;IAC7B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,+CAA+C;IAC/C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,SAAS,CAAC,QAAQ,EAAE,YAAY,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,gBAAgB,GAAG;QAAE,MAAM,EAAE,MAAM,EAAE,CAAC;QAAC,SAAS,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"hnsw.d.ts","sourceRoot":"","sources":["../src/hnsw.ts"],"names":[],"mappings":"AA0CA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEpD,4DAA4D;AAC5D,MAAM,WAAW,aAAa;IAC5B,wFAAwF;IACxF,KAAK,EAAE,MAAM,CAAC;IACd,yEAAyE;IACzE,MAAM,EAAE,YAAY,CAAC;CACtB;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,iBAAiB;IAChC,aAAa,EAAE,CAAC,CAAC;IACjB,2EAA2E;IAC3E,GAAG,EAAE,MAAM,CAAC;IACZ,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb;;;;;OAKG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB,4EAA4E;IAC5E,WAAW,EAAE,MAAM,CACjB,MAAM,EACN;QACE,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,EAAE,MAAM,CAAC;QACpB,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,EAAE,MAAM,CAAC;QACjB,YAAY,EAAE,MAAM,CAAC;QACrB,IAAI,EAAE,IAAI,GAAG,KAAK,CAAC;KACpB,CACF,CAAC;IACF,kDAAkD;IAClD,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,iFAAiF;AACjF,MAAM,WAAW,gBAAgB;IAC/B,wDAAwD;IACxD,GAAG,EAAE,MAAM,CAAC;IACZ,8EAA8E;IAC9E,WAAW,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,CAAC,CAAC,EAAE,MAAM,CAAC;IACX;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oEAAoE;IACpE,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,4BAA4B;AAC5B,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,6BAA6B;IAC7B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,+CAA+C;IAC/C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,SAAS,CAAC,QAAQ,EAAE,YAAY,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,gBAAgB,GAAG;QAAE,MAAM,EAAE,MAAM,EAAE,CAAC;QAAC,SAAS,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IACjH;;;;;;;;;;OAUG;IACH,MAAM,CACJ,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,WAAW,CACtB,MAAM,EACN;QACE,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,EAAE,MAAM,CAAC;QACpB,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,EAAE,MAAM,CAAC;QACjB,YAAY,EAAE,MAAM,CAAC;QACrB,IAAI,EAAE,IAAI,GAAG,KAAK,CAAC;KACpB,CACF,EACD,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,CAAC,CAAC;CACrB;AAmDD;;;;;;;;;;GAUG;AACH,wBAAsB,SAAS,CAAC,OAAO,EAAE,aAAa,CAAC,aAAa,CAAC,EAAE,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,SAAS,CAAC,CAqCjH;AAgDD;;;;;;;;;;;;GAYG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,MAAM,EACZ,iBAAiB,EAAE,MAAM,GACxB,OAAO,CAAC;IAAE,KAAK,EAAE,SAAS,CAAC;IAAC,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC,CAAA;CAAE,GAAG,IAAI,CAAC,CAwD1G;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE;IAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IAAC,SAAS,EAAE,MAAM,EAAE,CAAA;CAAE,EACjD,UAAU,EAAE,WAAW,CACrB,MAAM,EACN;IACE,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,IAAI,GAAG,KAAK,CAAC;CACpB,CACF,GACA,cAAc,EAAE,CAsBlB"}
|
package/dist/hnsw.js
CHANGED
|
@@ -104,9 +104,17 @@ export async function buildHnsw(vectors, opts) {
|
|
|
104
104
|
// an intermediate; we use a plain spread which is fast and explicit.
|
|
105
105
|
ctor.addPoint(Array.from(v.vector), v.label);
|
|
106
106
|
}
|
|
107
|
+
return wrapNativeIndex(ctor, dim, vectors.length);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* v2.16.0 — wrap a native hnswlib-node index (built fresh OR loaded from
|
|
111
|
+
* disk) as our `HnswIndex` type. Factored out of `buildHnsw` so the
|
|
112
|
+
* load-from-disk path returns the same shape without re-running addPoint.
|
|
113
|
+
*/
|
|
114
|
+
function wrapNativeIndex(ctor, dim, size) {
|
|
107
115
|
return {
|
|
108
116
|
dim,
|
|
109
|
-
size
|
|
117
|
+
size,
|
|
110
118
|
searchKnn(queryVec, k, qOpts) {
|
|
111
119
|
if (queryVec.length !== dim) {
|
|
112
120
|
throw new Error(`HnswIndex.searchKnn: query dim ${queryVec.length} ≠ index dim ${dim}`);
|
|
@@ -117,9 +125,98 @@ export async function buildHnsw(vectors, opts) {
|
|
|
117
125
|
ctor.setEf(ef);
|
|
118
126
|
const result = ctor.searchKnn(Array.from(queryVec), k, undefined);
|
|
119
127
|
return { labels: result.neighbors, distances: result.distances };
|
|
128
|
+
},
|
|
129
|
+
async saveTo(file, rowsByLabel, signature) {
|
|
130
|
+
const fs = await import("node:fs/promises");
|
|
131
|
+
const path = await import("node:path");
|
|
132
|
+
// Write the binary index to <file>.bin and the JSON meta sidecar
|
|
133
|
+
// to <file>.meta.json. We separate them so a partial-write (e.g.
|
|
134
|
+
// crash mid-flush) leaves meta missing → loader rebuilds.
|
|
135
|
+
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
136
|
+
const binFile = `${file}.bin`;
|
|
137
|
+
const metaFile = `${file}.meta.json`;
|
|
138
|
+
// hnswlib-node's writeIndex writes to a host filesystem path
|
|
139
|
+
// directly — much simpler than the WASM Emscripten FS plumbing.
|
|
140
|
+
await ctor.writeIndex(binFile);
|
|
141
|
+
const meta = {
|
|
142
|
+
formatVersion: 1,
|
|
143
|
+
dim,
|
|
144
|
+
size,
|
|
145
|
+
signature,
|
|
146
|
+
rowsByLabel: Object.fromEntries(rowsByLabel),
|
|
147
|
+
writtenAt: new Date().toISOString()
|
|
148
|
+
};
|
|
149
|
+
await fs.writeFile(metaFile, JSON.stringify(meta, null, 2), "utf8");
|
|
150
|
+
return true;
|
|
120
151
|
}
|
|
121
152
|
};
|
|
122
153
|
}
|
|
154
|
+
/**
|
|
155
|
+
* v2.16.0 — load a previously-persisted HNSW index from disk. Returns
|
|
156
|
+
* `null` (with a stderr warning) if:
|
|
157
|
+
* • Either the .bin or .meta.json file is missing
|
|
158
|
+
* • The meta's `signature` doesn't match the caller's current signature
|
|
159
|
+
* • The meta's `formatVersion` doesn't match
|
|
160
|
+
* • The native lib fails to load the .bin (corrupt / dim mismatch)
|
|
161
|
+
*
|
|
162
|
+
* On success returns `{ index, rowsByLabel }` so the caller can wire
|
|
163
|
+
* both into `searchHybrid`'s `hnsw` context without rebuilding from
|
|
164
|
+
* scratch. Typical boot-time win: ~25s rebuild → ~50ms load on a
|
|
165
|
+
* 50K-chunk vault.
|
|
166
|
+
*/
|
|
167
|
+
export async function loadHnswFromDisk(file, expectedSignature) {
|
|
168
|
+
const fs = await import("node:fs/promises");
|
|
169
|
+
const binFile = `${file}.bin`;
|
|
170
|
+
const metaFile = `${file}.meta.json`;
|
|
171
|
+
let metaRaw;
|
|
172
|
+
try {
|
|
173
|
+
metaRaw = await fs.readFile(metaFile, "utf8");
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
return null; // No meta → no persisted index (or partial write).
|
|
177
|
+
}
|
|
178
|
+
let meta;
|
|
179
|
+
try {
|
|
180
|
+
meta = JSON.parse(metaRaw);
|
|
181
|
+
}
|
|
182
|
+
catch (err) {
|
|
183
|
+
process.stderr.write(`enquire: HNSW meta at ${metaFile} is malformed; rebuilding — ${err instanceof Error ? err.message : String(err)}\n`);
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
if (meta.formatVersion !== 1) {
|
|
187
|
+
process.stderr.write(`enquire: HNSW meta format ${meta.formatVersion} ≠ expected 1; rebuilding (this happens on enquire-mcp upgrade)\n`);
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
if (meta.signature !== expectedSignature) {
|
|
191
|
+
process.stderr.write(`enquire: HNSW persisted index is stale (signature mismatch — embed-db changed since last write); rebuilding\n`);
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
// Bin file present?
|
|
195
|
+
try {
|
|
196
|
+
await fs.access(binFile);
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
process.stderr.write(`enquire: HNSW meta exists but ${binFile} is missing; rebuilding\n`);
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
// Load the native binary.
|
|
203
|
+
const lib = await loadHnswlib();
|
|
204
|
+
const ctor = new lib.HierarchicalNSW("cosine", meta.dim);
|
|
205
|
+
try {
|
|
206
|
+
await ctor.readIndex(binFile);
|
|
207
|
+
}
|
|
208
|
+
catch (err) {
|
|
209
|
+
process.stderr.write(`enquire: HNSW readIndex failed at ${binFile}; rebuilding — ${err instanceof Error ? err.message : String(err)}\n`);
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
const index = wrapNativeIndex(ctor, meta.dim, meta.size);
|
|
213
|
+
// Reconstruct the row map.
|
|
214
|
+
const rowsByLabel = new Map();
|
|
215
|
+
for (const [labelStr, row] of Object.entries(meta.rowsByLabel)) {
|
|
216
|
+
rowsByLabel.set(Number.parseInt(labelStr, 10), row);
|
|
217
|
+
}
|
|
218
|
+
return { index, rowsByLabel };
|
|
219
|
+
}
|
|
123
220
|
/**
|
|
124
221
|
* Convert HNSW search results to EmbedSearchHit using a label → source-row
|
|
125
222
|
* lookup. The label was assigned by the caller at build time (typically
|
package/dist/hnsw.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hnsw.js","sourceRoot":"","sources":["../src/hnsw.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,EAAE;AACF,4EAA4E;AAC5E,2EAA2E;AAC3E,2EAA2E;AAC3E,wEAAwE;AACxE,wEAAwE;AACxE,+EAA+E;AAC/E,EAAE;AACF,kDAAkD;AAClD,EAAE;AACF,sBAAsB;AACtB,wEAAwE;AACxE,yEAAyE;AACzE,oEAAoE;AACpE,wEAAwE;AACxE,uEAAuE;AACvE,2EAA2E;AAC3E,wEAAwE;AACxE,0EAA0E;AAC1E,EAAE;AACF,yEAAyE;AACzE,uEAAuE;AACvE,uEAAuE;AACvE,sEAAsE;AACtE,wEAAwE;AACxE,4DAA4D;AAC5D,EAAE;AACF,mEAAmE;AACnE,sEAAsE;AACtE,qEAAqE;AACrE,kEAAkE;AAClE,EAAE;AACF,iEAAiE;AACjE,wEAAwE;AACxE,oEAAoE;AACpE,EAAE;AACF,sEAAsE;AACtE,0EAA0E;AAC1E,iEAAiE;AACjE,WAAW;
|
|
1
|
+
{"version":3,"file":"hnsw.js","sourceRoot":"","sources":["../src/hnsw.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -39,6 +39,19 @@ export interface ServeOptions {
|
|
|
39
39
|
hnswEf?: string;
|
|
40
40
|
/** v2.15.0 — late-chunking context windowing for embeddings (default 0 chars). */
|
|
41
41
|
lateChunkContext?: string;
|
|
42
|
+
/** v2.16.0 — persist HNSW index to disk for fast reload on next serve.
|
|
43
|
+
* Default true (the persistence is a pure optimization; corrupt files
|
|
44
|
+
* fall back to rebuild gracefully). Pass `--no-hnsw-persist` to opt out. */
|
|
45
|
+
hnswPersist?: boolean;
|
|
46
|
+
/** v2.17.0 — vector storage encoding for the persistent embed db.
|
|
47
|
+
* - `"f32"` (default) — Float32 BLOB, identical to v2.16- behavior.
|
|
48
|
+
* - `"int8"` — int8-quantized BLOB + per-vector (vMin, scale) Float32
|
|
49
|
+
* tuple. ~4× storage reduction at ~1-2% recall@10 cost.
|
|
50
|
+
* Mode is per-database; switching modes triggers a full rebuild
|
|
51
|
+
* (the meta-table contamination guard treats it as a schema change).
|
|
52
|
+
* Must match the mode used at build-embeddings time — serving with a
|
|
53
|
+
* different mode would auto-rebuild the index. */
|
|
54
|
+
quantizeEmbeddings?: "f32" | "int8";
|
|
42
55
|
}
|
|
43
56
|
declare function main(): Promise<void>;
|
|
44
57
|
/**
|
|
@@ -128,5 +141,12 @@ export declare function buildEmbedText(chunks: ReadonlyArray<{
|
|
|
128
141
|
contextChars: number;
|
|
129
142
|
}): string;
|
|
130
143
|
declare function parsePositiveInt(raw: string, flag: string): number;
|
|
131
|
-
|
|
144
|
+
/**
|
|
145
|
+
* v2.17.0 — validate a `--quantize-embeddings <mode>` value. Accepts the
|
|
146
|
+
* canonical `"f32"` / `"int8"` plus a few user-friendly aliases (`"none"`
|
|
147
|
+
* for f32; `"q8"` for int8). Anything else throws with the exact list of
|
|
148
|
+
* accepted values so the user can fix the typo immediately.
|
|
149
|
+
*/
|
|
150
|
+
declare function parseQuantizationMode(raw: string | undefined): "f32" | "int8" | undefined;
|
|
151
|
+
export { main, parsePositiveInt, parseQuantizationMode, startServer };
|
|
132
152
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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;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;IACtB;;;+BAG2B;IAC3B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,+DAA+D;IAC/D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kFAAkF;IAClF,gBAAgB,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -12,7 +12,7 @@ import { chunkContent, defaultIndexFile, FtsIndex } from "./fts5.js";
|
|
|
12
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.
|
|
15
|
+
const VERSION = "2.17.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
|
|
@@ -50,7 +50,11 @@ async function main() {
|
|
|
50
50
|
.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).")
|
|
51
51
|
.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`.")
|
|
52
52
|
.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.")
|
|
53
|
+
.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.")
|
|
54
|
+
.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`.")
|
|
53
55
|
.action(async (opts) => {
|
|
56
|
+
// Validate up-front so a bad value fails before we touch the vault.
|
|
57
|
+
parseQuantizationMode(opts.quantizeEmbeddings);
|
|
54
58
|
await startServer(opts);
|
|
55
59
|
});
|
|
56
60
|
// v2.6.0 — remote-MCP HTTP transport. Mirrors `serve` flags + adds HTTP
|
|
@@ -84,6 +88,7 @@ async function main() {
|
|
|
84
88
|
.option("--disabled-tools <name...>", "Skip registration of specific tools by name.")
|
|
85
89
|
.option("--enabled-tools <name...>", "Strict allowlist — when set, ONLY listed tools register.")
|
|
86
90
|
.option("--diagnostic-search-tools", "Register the four single-ranker search tools alongside obsidian_search.")
|
|
91
|
+
.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.")
|
|
87
92
|
.action(async (opts) => {
|
|
88
93
|
const tokenFromArg = typeof opts.bearerToken === "string" ? opts.bearerToken.trim() : "";
|
|
89
94
|
const tokenFromEnv = typeof opts.bearerTokenEnv === "string" ? (process.env[opts.bearerTokenEnv] ?? "").trim() : "";
|
|
@@ -107,8 +112,11 @@ async function main() {
|
|
|
107
112
|
? parsePositiveInt(opts.sessionIdleTimeoutMs, "--session-idle-timeout-ms")
|
|
108
113
|
: 30 * 60 * 1000;
|
|
109
114
|
const maxSessionsCap = opts.maxSessions !== undefined ? parsePositiveInt(opts.maxSessions, "--max-sessions") : 100;
|
|
115
|
+
// v2.17.0 — fail fast on a typo'd quantization mode.
|
|
116
|
+
const quantMode = parseQuantizationMode(opts.quantizeEmbeddings);
|
|
110
117
|
const httpOpts = {
|
|
111
118
|
...opts,
|
|
119
|
+
...(quantMode !== undefined ? { quantizeEmbeddings: quantMode } : {}),
|
|
112
120
|
port: portNum,
|
|
113
121
|
host: opts.host ?? "127.0.0.1",
|
|
114
122
|
bearerToken,
|
|
@@ -229,12 +237,20 @@ async function main() {
|
|
|
229
237
|
.option("--read-paths <pattern...>", "Strict allowlist of glob patterns (repeatable)")
|
|
230
238
|
.option("--include-pdfs", "v2.8.0 — also embed PDF chunks. Off by default; PDF extraction + embedding is ~10-30x slower than markdown per file.")
|
|
231
239
|
.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.")
|
|
240
|
+
.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`.")
|
|
232
241
|
.action(async (opts) => {
|
|
233
242
|
const model = resolveModel(opts.embeddingModel);
|
|
234
243
|
const vault = new Vault(opts.vault, { excludeGlobs: opts.excludeGlob, readPaths: opts.readPaths });
|
|
235
244
|
await vault.ensureExists();
|
|
236
245
|
const embedFile = opts.embedFile ?? embedDbPath(vault.root);
|
|
237
|
-
const
|
|
246
|
+
const quantization = parseQuantizationMode(opts.quantizeEmbeddings) ?? "f32";
|
|
247
|
+
const db = new EmbedDb({
|
|
248
|
+
file: embedFile,
|
|
249
|
+
vaultRoot: vault.root,
|
|
250
|
+
modelAlias: model.alias,
|
|
251
|
+
dim: model.dim,
|
|
252
|
+
quantization
|
|
253
|
+
});
|
|
238
254
|
const lateChunkContext = opts.lateChunkContext !== undefined
|
|
239
255
|
? Math.max(0, parsePositiveInt(opts.lateChunkContext, "--late-chunk-context"))
|
|
240
256
|
: 0;
|
|
@@ -243,7 +259,7 @@ async function main() {
|
|
|
243
259
|
process.stderr.write(`enquire: loading embedder ${model.alias} (${model.hfId})...\n`);
|
|
244
260
|
const embedder = await loadEmbedder(opts.embeddingModel);
|
|
245
261
|
const report = await syncEmbedDb(vault, db, embedder, { lateChunkContext });
|
|
246
|
-
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}` : ""}\n`);
|
|
262
|
+
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`);
|
|
247
263
|
if (opts.includePdfs) {
|
|
248
264
|
const pdfReport = await syncPdfEmbedDb(vault, db, embedder, { lateChunkContext });
|
|
249
265
|
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`);
|
|
@@ -303,6 +319,7 @@ async function main() {
|
|
|
303
319
|
.option("--embedding-model <alias>", `Model alias (default: ${DEFAULT_MODEL_ALIAS})`, DEFAULT_MODEL_ALIAS)
|
|
304
320
|
.option("--include-pdfs", "Also index PDFs (FTS5 + embeddings). Off by default; opt-in because PDF extraction is slower.")
|
|
305
321
|
.option("--skip-embeddings", "Skip the install-model + build-embeddings steps (only build FTS5)")
|
|
322
|
+
.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.")
|
|
306
323
|
.action(async (opts) => {
|
|
307
324
|
const v = new Vault(opts.vault);
|
|
308
325
|
await v.ensureExists();
|
|
@@ -341,11 +358,18 @@ async function main() {
|
|
|
341
358
|
// Step 3: build-embeddings.
|
|
342
359
|
process.stdout.write("\n>> Step 3/3: Build embedding index\n");
|
|
343
360
|
const embedFile = embedDbPath(v.root);
|
|
344
|
-
const
|
|
361
|
+
const quantization = parseQuantizationMode(opts.quantizeEmbeddings) ?? "f32";
|
|
362
|
+
const db = new EmbedDb({
|
|
363
|
+
file: embedFile,
|
|
364
|
+
vaultRoot: v.root,
|
|
365
|
+
modelAlias: model.alias,
|
|
366
|
+
dim: model.dim,
|
|
367
|
+
quantization
|
|
368
|
+
});
|
|
345
369
|
await db.open();
|
|
346
370
|
try {
|
|
347
371
|
const embReport = await syncEmbedDb(v, db, embedder);
|
|
348
|
-
process.stdout.write(` embed-db (md): added=${embReport.added} updated=${embReport.updated} unchanged=${embReport.unchanged} chunks=${embReport.total_chunks}\n`);
|
|
372
|
+
process.stdout.write(` embed-db (md): added=${embReport.added} updated=${embReport.updated} unchanged=${embReport.unchanged} chunks=${embReport.total_chunks}${quantization !== "f32" ? ` quantization=${quantization}` : ""}\n`);
|
|
349
373
|
if (opts.includePdfs) {
|
|
350
374
|
const pdfReport = await syncPdfEmbedDb(v, db, embedder);
|
|
351
375
|
process.stdout.write(` embed-db (pdf): added=${pdfReport.added} updated=${pdfReport.updated} unchanged=${pdfReport.unchanged} chunks=${pdfReport.total_chunks}\n`);
|
|
@@ -358,6 +382,8 @@ async function main() {
|
|
|
358
382
|
process.stdout.write(` enquire-mcp serve --vault ${opts.vault} --persistent-index`);
|
|
359
383
|
if (opts.includePdfs)
|
|
360
384
|
process.stdout.write(" --include-pdfs");
|
|
385
|
+
if (quantization !== "f32")
|
|
386
|
+
process.stdout.write(` --quantize-embeddings ${quantization}`);
|
|
361
387
|
process.stdout.write("\n");
|
|
362
388
|
process.stdout.write(`Or check status: enquire-mcp doctor --vault ${opts.vault}\n`);
|
|
363
389
|
});
|
|
@@ -562,31 +588,76 @@ export async function prepareServerDeps(opts) {
|
|
|
562
588
|
// the meta directly without reopening. For now we accept the
|
|
563
589
|
// over-default-fallback risk and recommend doctor's output.
|
|
564
590
|
const model = resolveModel(undefined);
|
|
565
|
-
|
|
591
|
+
// v2.17.0 — pass through the quantization mode from CLI so the
|
|
592
|
+
// schema check matches what build-embeddings wrote. Default
|
|
593
|
+
// "f32" matches v2.16- behavior for users who don't set it.
|
|
594
|
+
const quantization = opts.quantizeEmbeddings ?? "f32";
|
|
595
|
+
const db = new EmbedDb({
|
|
596
|
+
file: embedFile,
|
|
597
|
+
vaultRoot: vault.root,
|
|
598
|
+
modelAlias: model.alias,
|
|
599
|
+
dim: model.dim,
|
|
600
|
+
quantization
|
|
601
|
+
});
|
|
566
602
|
await db.open();
|
|
567
603
|
try {
|
|
568
604
|
const startMs = Date.now();
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
605
|
+
// v2.16.0 — try to load from disk first if persistence is enabled.
|
|
606
|
+
// Skip-rebuild path: ~50ms read vs ~25s build for 50K-chunk
|
|
607
|
+
// vault when nothing changed since last serve. Staleness
|
|
608
|
+
// detected via `EmbedDb.computeSignature()` mismatch.
|
|
609
|
+
const persistFile = `${embedFile.replace(/\.embed\.db$/, "")}.hnsw`;
|
|
610
|
+
const signature = db.computeSignature();
|
|
611
|
+
const efOverride = opts.hnswEf ? parsePositiveInt(opts.hnswEf, "--hnsw-ef") : undefined;
|
|
612
|
+
let loaded = null;
|
|
613
|
+
if (opts.hnswPersist !== false) {
|
|
614
|
+
const { loadHnswFromDisk } = await import("./hnsw.js");
|
|
615
|
+
const loadResult = await loadHnswFromDisk(persistFile, signature);
|
|
616
|
+
if (loadResult) {
|
|
617
|
+
loaded = { index: loadResult.index, rowByLabel: loadResult.rowsByLabel };
|
|
618
|
+
process.stderr.write(`enquire: HNSW index loaded from disk (${loadResult.index.size} vectors, dim=${loadResult.index.dim}, ${Date.now() - startMs}ms — signature matched)\n`);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
if (loaded) {
|
|
622
|
+
hnswContext = {
|
|
623
|
+
index: loaded.index,
|
|
624
|
+
rowByLabel: loaded.rowByLabel,
|
|
625
|
+
...(efOverride !== undefined ? { ef: efOverride } : {})
|
|
626
|
+
};
|
|
572
627
|
}
|
|
573
628
|
else {
|
|
574
|
-
const
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
629
|
+
const rows = db.getAllVectors();
|
|
630
|
+
if (rows.length === 0) {
|
|
631
|
+
process.stderr.write(`enquire: --use-hnsw passed but embed-db is empty; skipping HNSW build.\n`);
|
|
632
|
+
}
|
|
633
|
+
else {
|
|
634
|
+
const { buildHnsw } = await import("./hnsw.js");
|
|
635
|
+
const index = await buildHnsw(rows.map((r) => ({ label: r.label, vector: r.vector })), { dim: model.dim, maxElements: rows.length });
|
|
636
|
+
const rowByLabel = new Map();
|
|
637
|
+
for (const r of rows) {
|
|
638
|
+
rowByLabel.set(r.label, {
|
|
639
|
+
rel_path: r.rel_path,
|
|
640
|
+
chunk_index: r.chunk_index,
|
|
641
|
+
line_start: r.line_start,
|
|
642
|
+
line_end: r.line_end,
|
|
643
|
+
text_preview: r.text_preview,
|
|
644
|
+
kind: r.kind
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
hnswContext = { index, rowByLabel, ...(efOverride !== undefined ? { ef: efOverride } : {}) };
|
|
648
|
+
process.stderr.write(`enquire: HNSW index built (${rows.length} vectors, dim=${model.dim}, ${Date.now() - startMs}ms)\n`);
|
|
649
|
+
// v2.16.0 — persist the freshly-built index for next serve start.
|
|
650
|
+
if (opts.hnswPersist !== false) {
|
|
651
|
+
try {
|
|
652
|
+
await index.saveTo(persistFile, rowByLabel, signature);
|
|
653
|
+
process.stderr.write(`enquire: HNSW index persisted to ${persistFile}.bin (+ .meta.json)\n`);
|
|
654
|
+
}
|
|
655
|
+
catch (err) {
|
|
656
|
+
// Non-fatal — persistence is an optimization. Log + continue.
|
|
657
|
+
process.stderr.write(`enquire: HNSW persist failed (continuing with in-memory index) — ${err instanceof Error ? err.message : String(err)}\n`);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
586
660
|
}
|
|
587
|
-
const efOverride = opts.hnswEf ? parsePositiveInt(opts.hnswEf, "--hnsw-ef") : undefined;
|
|
588
|
-
hnswContext = { index, rowByLabel, ...(efOverride !== undefined ? { ef: efOverride } : {}) };
|
|
589
|
-
process.stderr.write(`enquire: HNSW index built (${rows.length} vectors, dim=${model.dim}, ${Date.now() - startMs}ms)\n`);
|
|
590
661
|
}
|
|
591
662
|
}
|
|
592
663
|
finally {
|
|
@@ -2461,6 +2532,23 @@ function parsePositiveInt(raw, flag) {
|
|
|
2461
2532
|
}
|
|
2462
2533
|
return n;
|
|
2463
2534
|
}
|
|
2535
|
+
/**
|
|
2536
|
+
* v2.17.0 — validate a `--quantize-embeddings <mode>` value. Accepts the
|
|
2537
|
+
* canonical `"f32"` / `"int8"` plus a few user-friendly aliases (`"none"`
|
|
2538
|
+
* for f32; `"q8"` for int8). Anything else throws with the exact list of
|
|
2539
|
+
* accepted values so the user can fix the typo immediately.
|
|
2540
|
+
*/
|
|
2541
|
+
function parseQuantizationMode(raw) {
|
|
2542
|
+
if (raw === undefined)
|
|
2543
|
+
return undefined;
|
|
2544
|
+
const norm = raw.trim().toLowerCase();
|
|
2545
|
+
if (norm === "" || norm === "f32" || norm === "float32" || norm === "none")
|
|
2546
|
+
return "f32";
|
|
2547
|
+
if (norm === "int8" || norm === "i8" || norm === "q8")
|
|
2548
|
+
return "int8";
|
|
2549
|
+
throw new Error(`--quantize-embeddings must be "f32" or "int8" (got "${raw}"). ` +
|
|
2550
|
+
`Aliases: "none"/"float32" → f32, "q8"/"i8" → int8.`);
|
|
2551
|
+
}
|
|
2464
2552
|
function encodeNotePath(relPath) {
|
|
2465
2553
|
return relPath.split(path.sep).map(encodeURIComponent).join("/");
|
|
2466
2554
|
}
|
|
@@ -2494,5 +2582,5 @@ if (isCliEntry) {
|
|
|
2494
2582
|
process.exit(1);
|
|
2495
2583
|
});
|
|
2496
2584
|
}
|
|
2497
|
-
export { main, parsePositiveInt, startServer };
|
|
2585
|
+
export { main, parsePositiveInt, parseQuantizationMode, startServer };
|
|
2498
2586
|
//# sourceMappingURL=index.js.map
|