@modootoday/pmcp 0.6.0 → 0.7.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/README.md CHANGED
@@ -160,11 +160,18 @@ export default { fetch: (request: Request) => skills.fetch(request) };
160
160
 
161
161
  Put Origin and Host validation in front of it when it is mounted bare.
162
162
 
163
+ Pass `semantic: { cacheDir, localOnly: true }` to rank by meaning: the handler encodes
164
+ the catalog at start with `@huggingface/transformers` and the multilingual model, so an
165
+ intent in one language finds a description in another. Requests rank lexically until
166
+ `ready()` settles, and stay lexical when the encoder cannot load (`onError` says why).
167
+
163
168
  ## Runtime and licence
164
169
 
165
- Node.js 22 or later, or Bun 1.3 or later. Default ranking is lexical. The optional
166
- `index` command requires a separately installed encoder; it is not part of the
167
- default installation.
170
+ Node.js 22 or later, or Bun 1.3 or later. Default ranking is lexical. Semantic
171
+ ranking (the `index` command, or the HTTP handler's `semantic` option) needs
172
+ `@huggingface/transformers` installed separately; it is not part of the default
173
+ installation. The default encoder is `Xenova/multilingual-e5-small` (MIT, about
174
+ 130 MB), so vectors indexed with the earlier English-only default are re-encoded.
168
175
 
169
176
  Elastic License 2.0. See `LICENSE` and `NOTICE` for terms and dependency notices.
170
177
  Documentation: <https://pmcp.build>.
@@ -1,131 +1,3 @@
1
- import {
2
- normaliseIntent
3
- } from "./chunk-5TX2IAVD.js";
4
-
5
- // src/cache.ts
6
- async function openDatabase(path) {
7
- try {
8
- const { DatabaseSync } = await import(`node:${"sqlite"}`);
9
- return { db: new DatabaseSync(path), provider: "node:sqlite" };
10
- } catch {
11
- }
12
- try {
13
- const { Database } = await import(`bun:${"sqlite"}`);
14
- const bun = new Database(path, { create: true });
15
- return {
16
- db: {
17
- exec: (sql) => bun.run(sql),
18
- prepare: (sql) => bun.query(sql),
19
- close: () => bun.close()
20
- },
21
- provider: "bun:sqlite"
22
- };
23
- } catch {
24
- return null;
25
- }
26
- }
27
- var DEFAULT_INTENT_CACHE_LIMIT = 4096;
28
- var SCHEMA = `
29
- CREATE TABLE IF NOT EXISTS intent_vector (
30
- intent TEXT NOT NULL PRIMARY KEY,
31
- model_id TEXT NOT NULL,
32
- dims INTEGER NOT NULL,
33
- vector BLOB NOT NULL,
34
- used_at INTEGER NOT NULL
35
- );
36
- CREATE INDEX IF NOT EXISTS intent_vector_used_at ON intent_vector (used_at);
37
- `;
38
- async function openIntentCache(options) {
39
- const opened = await openDatabase(options.path);
40
- if (!opened) return null;
41
- const { db, provider } = opened;
42
- try {
43
- db.exec("PRAGMA journal_mode = WAL");
44
- db.exec(SCHEMA);
45
- } catch {
46
- db.close();
47
- return null;
48
- }
49
- const limit = options.limit ?? DEFAULT_INTENT_CACHE_LIMIT;
50
- const now = options.now ?? (() => Date.now());
51
- let hits = 0;
52
- let misses = 0;
53
- let mismatched = 0;
54
- const selectRow = db.prepare("SELECT model_id, dims, vector FROM intent_vector WHERE intent = ?");
55
- const touch = db.prepare(
56
- "UPDATE intent_vector SET used_at = ? WHERE intent = ?"
57
- );
58
- const upsert = db.prepare(
59
- `INSERT INTO intent_vector (intent, model_id, dims, vector, used_at)
60
- VALUES (?, ?, ?, ?, ?)
61
- ON CONFLICT(intent) DO UPDATE SET
62
- model_id = excluded.model_id,
63
- dims = excluded.dims,
64
- vector = excluded.vector,
65
- used_at = excluded.used_at`
66
- );
67
- const countRows = db.prepare(
68
- "SELECT count(*) AS n FROM intent_vector"
69
- );
70
- const evict = db.prepare(
71
- `DELETE FROM intent_vector WHERE intent IN (
72
- SELECT intent FROM intent_vector ORDER BY used_at ASC LIMIT ?
73
- )`
74
- );
75
- return {
76
- get(intent) {
77
- const key = normaliseIntent(intent);
78
- const row = selectRow.get(key) ?? null;
79
- if (!row) {
80
- misses += 1;
81
- return null;
82
- }
83
- if (row.model_id !== options.modelId || row.dims !== options.dims) {
84
- mismatched += 1;
85
- return null;
86
- }
87
- hits += 1;
88
- touch.run(now(), key);
89
- const bytes = row.vector;
90
- return new Float32Array(
91
- bytes.buffer.slice(
92
- bytes.byteOffset,
93
- bytes.byteOffset + bytes.byteLength
94
- )
95
- );
96
- },
97
- put(intent, vector) {
98
- if (vector.length !== options.dims) {
99
- throw new Error(
100
- `intent vector is ${vector.length} wide, cache holds ${options.dims}`
101
- );
102
- }
103
- const key = normaliseIntent(intent);
104
- upsert.run(
105
- key,
106
- options.modelId,
107
- options.dims,
108
- new Uint8Array(vector.buffer, vector.byteOffset, vector.byteLength),
109
- now()
110
- );
111
- const rows = countRows.get()?.n ?? 0;
112
- if (rows > limit) evict.run(rows - limit);
113
- },
114
- stats() {
115
- return {
116
- hits,
117
- misses,
118
- mismatched,
119
- rows: countRows.get()?.n ?? 0,
120
- provider
121
- };
122
- },
123
- close() {
124
- db.close();
125
- }
126
- };
127
- }
128
-
129
1
  // src/config.ts
130
2
  import { existsSync, readFileSync } from "fs";
131
3
  import { dirname, join, resolve } from "path";
@@ -1215,9 +1087,6 @@ function readSpec(config) {
1215
1087
  }
1216
1088
 
1217
1089
  export {
1218
- openDatabase,
1219
- DEFAULT_INTENT_CACHE_LIMIT,
1220
- openIntentCache,
1221
1090
  CONFIG_FILE,
1222
1091
  ConfigError,
1223
1092
  findConfig,
@@ -1235,4 +1104,4 @@ export {
1235
1104
  TOOLS,
1236
1105
  readSpec
1237
1106
  };
1238
- //# sourceMappingURL=chunk-6TSHDCUY.js.map
1107
+ //# sourceMappingURL=chunk-GNJSZCRH.js.map
@@ -0,0 +1,160 @@
1
+ import {
2
+ openDatabase
3
+ } from "./chunk-ZLELYZHL.js";
4
+
5
+ // src/embed.ts
6
+ var DEFAULT_MODEL_ID = "Xenova/multilingual-e5-small";
7
+ var PLAIN = {
8
+ queryPrefix: "",
9
+ passagePrefix: "",
10
+ pooling: "mean"
11
+ };
12
+ var E5 = {
13
+ queryPrefix: "query: ",
14
+ passagePrefix: "passage: ",
15
+ pooling: "mean"
16
+ };
17
+ var KNOWN_ENCODERS = {
18
+ "Xenova/multilingual-e5-small": E5,
19
+ "Xenova/multilingual-e5-base": E5,
20
+ "Xenova/multilingual-e5-large": E5,
21
+ "Xenova/bge-m3": { ...PLAIN, pooling: "cls" }
22
+ };
23
+ function encoderSpec(modelId) {
24
+ return KNOWN_ENCODERS[modelId] ?? PLAIN;
25
+ }
26
+ var ENTRY_SCHEMA = `
27
+ CREATE TABLE IF NOT EXISTS entry_vector (
28
+ name TEXT NOT NULL,
29
+ model_id TEXT NOT NULL,
30
+ dims INTEGER NOT NULL,
31
+ description_hash TEXT NOT NULL,
32
+ vector BLOB NOT NULL,
33
+ PRIMARY KEY (name, model_id)
34
+ );
35
+ `;
36
+ var TRANSFORMERS = "@huggingface/transformers";
37
+ async function loadEmbedder(modelId = DEFAULT_MODEL_ID, options = {}) {
38
+ let module;
39
+ try {
40
+ module = options.module ?? await import(TRANSFORMERS);
41
+ } catch {
42
+ return null;
43
+ }
44
+ const factory = module.pipeline;
45
+ if (typeof factory !== "function") return null;
46
+ if (module.env) {
47
+ if (options.cacheDir !== void 0)
48
+ module.env["cacheDir"] = options.cacheDir;
49
+ if (options.localOnly) module.env["allowRemoteModels"] = false;
50
+ }
51
+ const encode = await factory("feature-extraction", modelId, {
52
+ dtype: options.dtype ?? "q8"
53
+ });
54
+ const spec = encoderSpec(modelId);
55
+ let dims = 0;
56
+ const run = async (text) => {
57
+ const output = await encode(text, {
58
+ pooling: spec.pooling,
59
+ normalize: true
60
+ });
61
+ const vector = Float32Array.from(output.data);
62
+ dims = vector.length;
63
+ return vector;
64
+ };
65
+ return {
66
+ modelId,
67
+ get dims() {
68
+ return dims;
69
+ },
70
+ embed: (text) => run(spec.queryPrefix + text),
71
+ embedPassage: (text) => run(spec.passagePrefix + text)
72
+ };
73
+ }
74
+ async function encodeEntries(embedder, entries, previous = /* @__PURE__ */ new Map()) {
75
+ const out = /* @__PURE__ */ new Map();
76
+ const passage = (text) => embedder.embedPassage ? embedder.embedPassage(text) : embedder.embed(text);
77
+ for (const entry of entries) {
78
+ const hash = describeHash(entry.description);
79
+ const kept = previous.get(entry.name);
80
+ if (kept && kept.hash === hash) {
81
+ out.set(entry.name, kept);
82
+ continue;
83
+ }
84
+ out.set(entry.name, { hash, vector: await passage(entry.description) });
85
+ }
86
+ return out;
87
+ }
88
+ function describeHash(description) {
89
+ let hash = 2166136261;
90
+ for (let i = 0; i < description.length; i++) {
91
+ hash ^= description.charCodeAt(i);
92
+ hash = Math.imul(hash, 16777619) >>> 0;
93
+ }
94
+ return hash.toString(16).padStart(8, "0");
95
+ }
96
+ async function openEntryVectors(path) {
97
+ const opened = await openDatabase(path);
98
+ if (opened === null) return null;
99
+ const db = opened.db;
100
+ db.exec(ENTRY_SCHEMA);
101
+ return {
102
+ read(modelId) {
103
+ const out = /* @__PURE__ */ new Map();
104
+ const rows = db.prepare(
105
+ "SELECT name, dims, vector FROM entry_vector WHERE model_id = ?"
106
+ ).all(modelId);
107
+ for (const row of rows) {
108
+ const bytes = row.vector;
109
+ const vector = new Float32Array(
110
+ bytes.buffer.slice(
111
+ bytes.byteOffset,
112
+ bytes.byteOffset + bytes.byteLength
113
+ )
114
+ );
115
+ if (vector.length === row.dims) out.set(row.name, vector);
116
+ }
117
+ return out;
118
+ },
119
+ stale(modelId, entries) {
120
+ const rows = db.prepare(
121
+ "SELECT name, description_hash FROM entry_vector WHERE model_id = ?"
122
+ ).all(modelId);
123
+ const stored = new Map(
124
+ rows.map((row) => [row.name, row.description_hash])
125
+ );
126
+ const out = [];
127
+ for (const [name, description] of entries) {
128
+ if (stored.get(name) !== describeHash(description)) out.push(name);
129
+ }
130
+ return out;
131
+ },
132
+ write(modelId, name, description, vector) {
133
+ db.prepare(
134
+ `INSERT INTO entry_vector (name, model_id, dims, description_hash, vector)
135
+ VALUES (?, ?, ?, ?, ?)
136
+ ON CONFLICT(name, model_id) DO UPDATE SET
137
+ dims = excluded.dims,
138
+ description_hash = excluded.description_hash,
139
+ vector = excluded.vector`
140
+ ).run(
141
+ name,
142
+ modelId,
143
+ vector.length,
144
+ describeHash(description),
145
+ new Uint8Array(vector.buffer, vector.byteOffset, vector.byteLength)
146
+ );
147
+ },
148
+ close() {
149
+ db.close();
150
+ }
151
+ };
152
+ }
153
+
154
+ export {
155
+ DEFAULT_MODEL_ID,
156
+ loadEmbedder,
157
+ encodeEntries,
158
+ openEntryVectors
159
+ };
160
+ //# sourceMappingURL=chunk-NI2OLZPA.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createSkillServer
3
- } from "./chunk-5TX2IAVD.js";
3
+ } from "./chunk-QHFORYIN.js";
4
4
 
5
5
  // src/stdio.ts
6
6
  import { serveStdio } from "@modelcontextprotocol/server/stdio";
@@ -35,4 +35,4 @@ export {
35
35
  serve,
36
36
  main
37
37
  };
38
- //# sourceMappingURL=chunk-FXBLBKP6.js.map
38
+ //# sourceMappingURL=chunk-POJOFL4L.js.map
@@ -1129,7 +1129,7 @@ function registerSkillTools(server, tools) {
1129
1129
  return server;
1130
1130
  }
1131
1131
  function createSkillServer(options) {
1132
- const server = new McpServer({ name: SERVER_NAME, version: "0.6.0" });
1132
+ const server = new McpServer({ name: SERVER_NAME, version: "0.7.0" });
1133
1133
  const tools = createSkillTools(options);
1134
1134
  registerSkillsExtension(server, tools.skills);
1135
1135
  return registerSkillTools(server, tools);
@@ -1167,4 +1167,4 @@ export {
1167
1167
  registerSkillTools,
1168
1168
  createSkillServer
1169
1169
  };
1170
- //# sourceMappingURL=chunk-5TX2IAVD.js.map
1170
+ //# sourceMappingURL=chunk-QHFORYIN.js.map
@@ -0,0 +1,134 @@
1
+ import {
2
+ normaliseIntent
3
+ } from "./chunk-QHFORYIN.js";
4
+
5
+ // src/cache.ts
6
+ async function openDatabase(path) {
7
+ try {
8
+ const { DatabaseSync } = await import(`node:${"sqlite"}`);
9
+ return { db: new DatabaseSync(path), provider: "node:sqlite" };
10
+ } catch {
11
+ }
12
+ try {
13
+ const { Database } = await import(`bun:${"sqlite"}`);
14
+ const bun = new Database(path, { create: true });
15
+ return {
16
+ db: {
17
+ exec: (sql) => bun.run(sql),
18
+ prepare: (sql) => bun.query(sql),
19
+ close: () => bun.close()
20
+ },
21
+ provider: "bun:sqlite"
22
+ };
23
+ } catch {
24
+ return null;
25
+ }
26
+ }
27
+ var DEFAULT_INTENT_CACHE_LIMIT = 4096;
28
+ var SCHEMA = `
29
+ CREATE TABLE IF NOT EXISTS intent_vector (
30
+ intent TEXT NOT NULL PRIMARY KEY,
31
+ model_id TEXT NOT NULL,
32
+ dims INTEGER NOT NULL,
33
+ vector BLOB NOT NULL,
34
+ used_at INTEGER NOT NULL
35
+ );
36
+ CREATE INDEX IF NOT EXISTS intent_vector_used_at ON intent_vector (used_at);
37
+ `;
38
+ async function openIntentCache(options) {
39
+ const opened = await openDatabase(options.path);
40
+ if (!opened) return null;
41
+ const { db, provider } = opened;
42
+ try {
43
+ db.exec("PRAGMA journal_mode = WAL");
44
+ db.exec(SCHEMA);
45
+ } catch {
46
+ db.close();
47
+ return null;
48
+ }
49
+ const limit = options.limit ?? DEFAULT_INTENT_CACHE_LIMIT;
50
+ const now = options.now ?? (() => Date.now());
51
+ let hits = 0;
52
+ let misses = 0;
53
+ let mismatched = 0;
54
+ const selectRow = db.prepare("SELECT model_id, dims, vector FROM intent_vector WHERE intent = ?");
55
+ const touch = db.prepare(
56
+ "UPDATE intent_vector SET used_at = ? WHERE intent = ?"
57
+ );
58
+ const upsert = db.prepare(
59
+ `INSERT INTO intent_vector (intent, model_id, dims, vector, used_at)
60
+ VALUES (?, ?, ?, ?, ?)
61
+ ON CONFLICT(intent) DO UPDATE SET
62
+ model_id = excluded.model_id,
63
+ dims = excluded.dims,
64
+ vector = excluded.vector,
65
+ used_at = excluded.used_at`
66
+ );
67
+ const countRows = db.prepare(
68
+ "SELECT count(*) AS n FROM intent_vector"
69
+ );
70
+ const evict = db.prepare(
71
+ `DELETE FROM intent_vector WHERE intent IN (
72
+ SELECT intent FROM intent_vector ORDER BY used_at ASC LIMIT ?
73
+ )`
74
+ );
75
+ return {
76
+ get(intent) {
77
+ const key = normaliseIntent(intent);
78
+ const row = selectRow.get(key) ?? null;
79
+ if (!row) {
80
+ misses += 1;
81
+ return null;
82
+ }
83
+ if (row.model_id !== options.modelId || row.dims !== options.dims) {
84
+ mismatched += 1;
85
+ return null;
86
+ }
87
+ hits += 1;
88
+ touch.run(now(), key);
89
+ const bytes = row.vector;
90
+ return new Float32Array(
91
+ bytes.buffer.slice(
92
+ bytes.byteOffset,
93
+ bytes.byteOffset + bytes.byteLength
94
+ )
95
+ );
96
+ },
97
+ put(intent, vector) {
98
+ if (vector.length !== options.dims) {
99
+ throw new Error(
100
+ `intent vector is ${vector.length} wide, cache holds ${options.dims}`
101
+ );
102
+ }
103
+ const key = normaliseIntent(intent);
104
+ upsert.run(
105
+ key,
106
+ options.modelId,
107
+ options.dims,
108
+ new Uint8Array(vector.buffer, vector.byteOffset, vector.byteLength),
109
+ now()
110
+ );
111
+ const rows = countRows.get()?.n ?? 0;
112
+ if (rows > limit) evict.run(rows - limit);
113
+ },
114
+ stats() {
115
+ return {
116
+ hits,
117
+ misses,
118
+ mismatched,
119
+ rows: countRows.get()?.n ?? 0,
120
+ provider
121
+ };
122
+ },
123
+ close() {
124
+ db.close();
125
+ }
126
+ };
127
+ }
128
+
129
+ export {
130
+ openDatabase,
131
+ DEFAULT_INTENT_CACHE_LIMIT,
132
+ openIntentCache
133
+ };
134
+ //# sourceMappingURL=chunk-ZLELYZHL.js.map
package/dist/cli.js CHANGED
@@ -1,25 +1,29 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ loadEmbedder,
4
+ openEntryVectors
5
+ } from "./chunk-NI2OLZPA.js";
2
6
  import {
3
7
  ConfigError,
4
8
  ProjectError,
5
9
  TOOLS,
6
10
  doctor,
7
11
  findConfig,
8
- openDatabase,
9
12
  project,
10
13
  readConfig,
11
14
  readSpec,
12
15
  renderFrontmatter,
13
16
  spawnRunner,
14
17
  splitFrontmatter
15
- } from "./chunk-6TSHDCUY.js";
18
+ } from "./chunk-GNJSZCRH.js";
19
+ import "./chunk-ZLELYZHL.js";
16
20
  import {
17
21
  serve
18
- } from "./chunk-FXBLBKP6.js";
22
+ } from "./chunk-POJOFL4L.js";
19
23
  import {
20
24
  kindOf,
21
25
  readCatalog
22
- } from "./chunk-5TX2IAVD.js";
26
+ } from "./chunk-QHFORYIN.js";
23
27
 
24
28
  // src/cli/command.ts
25
29
  var ArgumentError = class extends Error {
@@ -198,109 +202,6 @@ var Ui = class {
198
202
  }
199
203
  };
200
204
 
201
- // src/embed.ts
202
- var DEFAULT_MODEL_ID = "Xenova/all-MiniLM-L6-v2";
203
- var ENTRY_SCHEMA = `
204
- CREATE TABLE IF NOT EXISTS entry_vector (
205
- name TEXT NOT NULL,
206
- model_id TEXT NOT NULL,
207
- dims INTEGER NOT NULL,
208
- description_hash TEXT NOT NULL,
209
- vector BLOB NOT NULL,
210
- PRIMARY KEY (name, model_id)
211
- );
212
- `;
213
- var TRANSFORMERS = "@huggingface/transformers";
214
- async function loadEmbedder(modelId = DEFAULT_MODEL_ID) {
215
- let module;
216
- try {
217
- module = await import(TRANSFORMERS);
218
- } catch {
219
- return null;
220
- }
221
- const factory = module.pipeline;
222
- if (typeof factory !== "function") return null;
223
- const encode = await factory("feature-extraction", modelId);
224
- let dims = 0;
225
- return {
226
- modelId,
227
- get dims() {
228
- return dims;
229
- },
230
- async embed(text) {
231
- const output = await encode(text, { pooling: "mean", normalize: true });
232
- const vector = Float32Array.from(output.data);
233
- dims = vector.length;
234
- return vector;
235
- }
236
- };
237
- }
238
- function describeHash(description) {
239
- let hash = 2166136261;
240
- for (let i = 0; i < description.length; i++) {
241
- hash ^= description.charCodeAt(i);
242
- hash = Math.imul(hash, 16777619) >>> 0;
243
- }
244
- return hash.toString(16).padStart(8, "0");
245
- }
246
- async function openEntryVectors(path) {
247
- const opened = await openDatabase(path);
248
- if (opened === null) return null;
249
- const db = opened.db;
250
- db.exec(ENTRY_SCHEMA);
251
- return {
252
- read(modelId) {
253
- const out = /* @__PURE__ */ new Map();
254
- const rows = db.prepare(
255
- "SELECT name, dims, vector FROM entry_vector WHERE model_id = ?"
256
- ).all(modelId);
257
- for (const row of rows) {
258
- const bytes = row.vector;
259
- const vector = new Float32Array(
260
- bytes.buffer.slice(
261
- bytes.byteOffset,
262
- bytes.byteOffset + bytes.byteLength
263
- )
264
- );
265
- if (vector.length === row.dims) out.set(row.name, vector);
266
- }
267
- return out;
268
- },
269
- stale(modelId, entries) {
270
- const rows = db.prepare(
271
- "SELECT name, description_hash FROM entry_vector WHERE model_id = ?"
272
- ).all(modelId);
273
- const stored = new Map(
274
- rows.map((row) => [row.name, row.description_hash])
275
- );
276
- const out = [];
277
- for (const [name, description] of entries) {
278
- if (stored.get(name) !== describeHash(description)) out.push(name);
279
- }
280
- return out;
281
- },
282
- write(modelId, name, description, vector) {
283
- db.prepare(
284
- `INSERT INTO entry_vector (name, model_id, dims, description_hash, vector)
285
- VALUES (?, ?, ?, ?, ?)
286
- ON CONFLICT(name, model_id) DO UPDATE SET
287
- dims = excluded.dims,
288
- description_hash = excluded.description_hash,
289
- vector = excluded.vector`
290
- ).run(
291
- name,
292
- modelId,
293
- vector.length,
294
- describeHash(description),
295
- new Uint8Array(vector.buffer, vector.byteOffset, vector.byteLength)
296
- );
297
- },
298
- close() {
299
- db.close();
300
- }
301
- };
302
- }
303
-
304
205
  // src/commands/roots.ts
305
206
  var CATALOG_OPTIONS = [
306
207
  {
@@ -454,7 +355,7 @@ var indexCommand = {
454
355
  return 0;
455
356
  }
456
357
  for (const entry2 of todo) {
457
- const vector = await embedder.embed(entry2.description);
358
+ const vector = embedder.embedPassage ? await embedder.embedPassage(entry2.description) : await embedder.embed(entry2.description);
458
359
  store.write(embedder.modelId, entry2.name, entry2.description, vector);
459
360
  }
460
361
  const indexed = store.read(embedder.modelId).size;
@@ -2407,7 +2308,7 @@ function dispatch(argv, options = {}) {
2407
2308
  }
2408
2309
  return run(command, rest, { ui, env, cwd });
2409
2310
  }
2410
- var VERSION = "0.6.0";
2311
+ var VERSION = "0.7.0";
2411
2312
  async function run(command, argv, context) {
2412
2313
  try {
2413
2314
  return await command.run({
package/dist/http.d.ts CHANGED
@@ -1,5 +1,40 @@
1
1
  import { S as SkillEntry } from './catalog-5v7jrGwP.js';
2
- import { S as SkillServerOptions } from './server-Dm71qZgM.js';
2
+ import { E as Embedder, S as SkillServerOptions } from './server-wpYiAJ2n.js';
3
+
4
+ /**
5
+ * The optional encoder and the store for the vectors it produces.
6
+ *
7
+ * Neither the model nor its runtime is a dependency: a caller who wants
8
+ * semantic ranking installs the peer and runs `pmcp index`, and everything
9
+ * here degrades to null rather than throwing when that has not happened.
10
+ */
11
+
12
+ /**
13
+ * What `pmcp index` writes and what the server reads back. Multilingual, so a
14
+ * Korean intent finds an English description (measured 20261005: 8/8 Korean
15
+ * and 8/8 English intents in the top three over a 99-skill catalog).
16
+ */
17
+ declare const DEFAULT_MODEL_ID = "Xenova/multilingual-e5-small";
18
+ interface LoadEmbedderOptions {
19
+ /** Where model files live; a host bakes them into its image here. */
20
+ readonly cacheDir?: string;
21
+ /** Refuse to download, so a missing model is an error rather than a fetch. */
22
+ readonly localOnly?: boolean;
23
+ /**
24
+ * Weight precision. q8 is a quarter of fp32's size (118 MB against 470 MB for
25
+ * the default model) and ranked the same in the 20261005 measurement.
26
+ */
27
+ readonly dtype?: "q8" | "fp32";
28
+ /** Injected for tests. The optional peer is imported otherwise. */
29
+ readonly module?: unknown;
30
+ }
31
+ /**
32
+ * Loads the encoder, or returns null when the peer is not installed.
33
+ *
34
+ * Absence is not failure anywhere in this package: the caller reports it and
35
+ * ranking stays lexical.
36
+ */
37
+ declare function loadEmbedder(modelId?: string, options?: LoadEmbedderOptions): Promise<Embedder | null>;
3
38
 
4
39
  /**
5
40
  * The catalog over streamable HTTP, for a hosted skills server.
@@ -9,6 +44,15 @@ import { S as SkillServerOptions } from './server-Dm71qZgM.js';
9
44
  * tiers tiersFor() grants. The catalog is read once and filtered per request.
10
45
  */
11
46
 
47
+ /** Semantic ranking for a hosted catalog: the encoder runs in the host process. */
48
+ interface SkillHttpSemanticOptions extends Omit<LoadEmbedderOptions, "module"> {
49
+ /** Defaults to the multilingual model pmcp index uses. */
50
+ readonly modelId?: string;
51
+ /** Injected for tests, or by a host that already holds an encoder. */
52
+ readonly embedder?: Embedder;
53
+ /** Told when the encoder cannot load; ranking then stays lexical. */
54
+ readonly onError?: (error: unknown) => void;
55
+ }
12
56
  interface SkillCaller {
13
57
  /** Stable id of the signed-in user, recorded by the host, never by pmcp. */
14
58
  readonly id: string;
@@ -21,6 +65,8 @@ interface SkillHttpOptions extends SkillServerOptions {
21
65
  readonly tiersFor?: (caller: SkillCaller) => readonly string[];
22
66
  /** Sent in WWW-Authenticate on a refused request, e.g. a resource metadata URL. */
23
67
  readonly challenge?: string;
68
+ /** Encode the catalog at start so find ranks by meaning across languages. */
69
+ readonly semantic?: SkillHttpSemanticOptions;
24
70
  }
25
71
  declare const DEFAULT_TIERS: readonly string[];
26
72
  declare function visibleTo(entries: readonly SkillEntry[], tiers: readonly string[]): SkillEntry[];
@@ -28,7 +74,9 @@ interface SkillHttpHandler {
28
74
  fetch(request: Request): Promise<Response>;
29
75
  /** Re-read the catalog, for a host that deploys new skill releases in place. */
30
76
  reload(): void;
77
+ /** Settles once the catalog is encoded, or at once when ranking is lexical. */
78
+ ready(): Promise<"semantic" | "lexical">;
31
79
  }
32
80
  declare function createSkillHttpHandler(options: SkillHttpOptions): SkillHttpHandler;
33
81
 
34
- export { DEFAULT_TIERS, type SkillCaller, type SkillHttpHandler, type SkillHttpOptions, createSkillHttpHandler, visibleTo };
82
+ export { DEFAULT_MODEL_ID, DEFAULT_TIERS, Embedder, type SkillCaller, type SkillHttpHandler, type SkillHttpOptions, type SkillHttpSemanticOptions, createSkillHttpHandler, loadEmbedder, visibleTo };
package/dist/http.js CHANGED
@@ -1,7 +1,13 @@
1
+ import {
2
+ DEFAULT_MODEL_ID,
3
+ encodeEntries,
4
+ loadEmbedder
5
+ } from "./chunk-NI2OLZPA.js";
6
+ import "./chunk-ZLELYZHL.js";
1
7
  import {
2
8
  createSkillServer,
3
9
  readCatalog
4
- } from "./chunk-5TX2IAVD.js";
10
+ } from "./chunk-QHFORYIN.js";
5
11
 
6
12
  // src/http.ts
7
13
  import {
@@ -17,6 +23,25 @@ function createSkillHttpHandler(options) {
17
23
  let all = load(options);
18
24
  const tiersFor = options.tiersFor ?? (() => DEFAULT_TIERS);
19
25
  const handlers = /* @__PURE__ */ new Map();
26
+ let embedder;
27
+ let encoded = /* @__PURE__ */ new Map();
28
+ let vectors;
29
+ const semantic = options.semantic;
30
+ const encode = async () => {
31
+ if (!semantic) return "lexical";
32
+ try {
33
+ embedder ??= semantic.embedder ?? await loadEmbedder(semantic.modelId, semantic) ?? void 0;
34
+ if (!embedder)
35
+ throw new Error("@huggingface/transformers is not installed");
36
+ encoded = await encodeEntries(embedder, all, encoded);
37
+ vectors = new Map([...encoded].map(([name, v]) => [name, v.vector]));
38
+ return "semantic";
39
+ } catch (error) {
40
+ semantic.onError?.(error);
41
+ return "lexical";
42
+ }
43
+ };
44
+ let ready = encode();
20
45
  const handlerFor = (tiers) => {
21
46
  const key = [...tiers].sort().join(",");
22
47
  const cached = handlers.get(key);
@@ -24,6 +49,7 @@ function createSkillHttpHandler(options) {
24
49
  const handler = createMcpHandler(
25
50
  () => createSkillServer({
26
51
  ...options,
52
+ ...embedder && vectors ? { embedder, vectors } : {},
27
53
  loadCatalog: () => visibleTo(all, tiers)
28
54
  })
29
55
  );
@@ -54,12 +80,17 @@ function createSkillHttpHandler(options) {
54
80
  reload() {
55
81
  all = load(options);
56
82
  handlers.clear();
57
- }
83
+ vectors = void 0;
84
+ ready = encode();
85
+ },
86
+ ready: () => ready
58
87
  };
59
88
  }
60
89
  export {
90
+ DEFAULT_MODEL_ID,
61
91
  DEFAULT_TIERS,
62
92
  createSkillHttpHandler,
93
+ loadEmbedder,
63
94
  visibleTo
64
95
  };
65
96
  //# sourceMappingURL=http.js.map
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { I as IntentCache, S as SkillServerOptions, a as SkillTools } from './server-Dm71qZgM.js';
2
- export { C as CATALOG_PAGE_MAX, b as CATALOG_PAGE_SIZE, c as CallResponse, d as CatalogItem, e as CatalogResponse, D as DEFAULT_FIND_LIMIT, f as DESCRIBE_FILE_LIMIT, E as Embedder, F as FindOptions, g as FindResult, h as IntentCacheStats, K as KindFilter, M as Match, R as READ_BUDGET_BYTES, i as Ranking, j as ReadResult, k as SkillFilters, l as cosine, m as createSkillTools, n as find, o as lexicalScore, p as normaliseIntent } from './server-Dm71qZgM.js';
1
+ import { I as IntentCache, S as SkillServerOptions, a as SkillTools } from './server-wpYiAJ2n.js';
2
+ export { C as CATALOG_PAGE_MAX, b as CATALOG_PAGE_SIZE, c as CallResponse, d as CatalogItem, e as CatalogResponse, D as DEFAULT_FIND_LIMIT, f as DESCRIBE_FILE_LIMIT, E as Embedder, F as FindOptions, g as FindResult, h as IntentCacheStats, K as KindFilter, M as Match, R as READ_BUDGET_BYTES, i as Ranking, j as ReadResult, k as SkillFilters, l as cosine, m as createSkillTools, n as find, o as lexicalScore, p as normaliseIntent } from './server-wpYiAJ2n.js';
3
3
  import { S as SkillEntry } from './catalog-5v7jrGwP.js';
4
4
  export { a as ASSET_KINDS, A as AssetKind, C as CatalogOptions, M as MAX_SKILL_DEPTH, b as SKIPPED_DIRS, d as displayPath, k as kindOf, r as readBody, c as readCatalog, e as readFrontmatter, w as withoutBody } from './catalog-5v7jrGwP.js';
5
5
  import { McpServer } from '@modelcontextprotocol/server';
package/dist/index.js CHANGED
@@ -1,7 +1,6 @@
1
1
  import {
2
2
  CONFIG_FILE,
3
3
  ConfigError,
4
- DEFAULT_INTENT_CACHE_LIMIT,
5
4
  LOCK_FILE,
6
5
  ProjectError,
7
6
  TOOLS,
@@ -9,12 +8,15 @@ import {
9
8
  deriveName,
10
9
  doctor,
11
10
  findConfig,
12
- openIntentCache,
13
11
  plan,
14
12
  project,
15
13
  readConfig,
16
14
  readSpec
17
- } from "./chunk-6TSHDCUY.js";
15
+ } from "./chunk-GNJSZCRH.js";
16
+ import {
17
+ DEFAULT_INTENT_CACHE_LIMIT,
18
+ openIntentCache
19
+ } from "./chunk-ZLELYZHL.js";
18
20
  import {
19
21
  ASSET_KINDS,
20
22
  CATALOG_PAGE_MAX,
@@ -46,7 +48,7 @@ import {
46
48
  registerSkillsExtension,
47
49
  skillUri,
48
50
  withoutBody
49
- } from "./chunk-5TX2IAVD.js";
51
+ } from "./chunk-QHFORYIN.js";
50
52
  export {
51
53
  ASSET_KINDS,
52
54
  CATALOG_PAGE_MAX,
@@ -56,7 +56,10 @@ interface FindResult {
56
56
  interface Embedder {
57
57
  readonly modelId: string;
58
58
  readonly dims: number;
59
+ /** Encodes an intent, the query side. */
59
60
  embed(text: string): Promise<Float32Array>;
61
+ /** Encodes a description, for models that write the two sides differently. */
62
+ embedPassage?(text: string): Promise<Float32Array>;
60
63
  }
61
64
  interface FindOptions {
62
65
  readonly entries: readonly SkillEntry[];
package/dist/stdio.js CHANGED
@@ -3,8 +3,8 @@ import {
3
3
  main,
4
4
  resolveRoots,
5
5
  serve
6
- } from "./chunk-FXBLBKP6.js";
7
- import "./chunk-5TX2IAVD.js";
6
+ } from "./chunk-POJOFL4L.js";
7
+ import "./chunk-QHFORYIN.js";
8
8
  export {
9
9
  main,
10
10
  resolveRoots,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modootoday/pmcp",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "license": "Elastic-2.0",
5
5
  "author": "modootoday",
6
6
  "description": "An MCP server that finds the skills your installed packages already ship. Reads SKILL.md out of node_modules by shape rather than by path, so it sees the skills, .agent, .claude, .gemini and .codex conventions alike, and keeps session startup flat as the number of installed skills grows.",