@plur-ai/mcp 0.20.0 → 0.21.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.
@@ -1,5 +1,5 @@
1
1
  // src/version.ts
2
- var VERSION = "0.20.0";
2
+ var VERSION = "0.21.0";
3
3
 
4
4
  export {
5
5
  VERSION
package/dist/index.d.ts CHANGED
@@ -27,5 +27,49 @@ declare function compareSemver(a: string, b: string): number;
27
27
  * anchors to start-of-line so a leading space breaks the match.
28
28
  */
29
29
  declare function extractManifestVersion(skillMdPath: string): string | null;
30
+ /**
31
+ * Hook set for `plur-mcp init`. Mirrors `buildInjectionHooks` in
32
+ * @plur-ai/cli's init.ts, which this package cannot import. The rehydrate
33
+ * entry must stay identical to the cli's; test/init-hooks.test.ts fails if
34
+ * the two diverge (#1279).
35
+ */
36
+ declare function buildPlurHooks(cli: string): Record<string, HookEntry[]>;
37
+ interface Settings {
38
+ hooks?: Record<string, HookEntry[]>;
39
+ [key: string]: unknown;
40
+ }
41
+ interface HookEntry {
42
+ matcher?: string;
43
+ hooks: Array<{
44
+ type: string;
45
+ command: string;
46
+ args?: string[];
47
+ timeout?: number;
48
+ async?: boolean;
49
+ }>;
50
+ }
51
+ /**
52
+ * Merge PLUR hooks into Claude Code settings (#1279). No I/O apart from
53
+ * reading plur-hook.meta.json for the exec-form check.
54
+ * A hook is PLUR's only when isPlurHookSpec says so: the whole command is
55
+ * PLUR's launcher (the shim, with any slash direction or quoting, or the npx
56
+ * fallback, or on Windows the exec form `plur init` writes) followed by a
57
+ * `hook-*` subcommand and plain arguments (decisions H2, F4).
58
+ * - No PLUR hook present: append the full set ('installed').
59
+ * - PLUR hooks present: remove PLUR's hooks from PostCompact, which cannot
60
+ * carry context (#1274), one hook at a time, dropping an entry only when
61
+ * it has no hooks left. If a PLUR rehydrate was among them, add the
62
+ * SessionStart(compact) entry unless one is there ('healed'). A file with
63
+ * PLUR hooks but no rehydrate (the global file `plur init --project` writes) gets
64
+ * none added. Nothing else changes, so a fuller
65
+ * `plur init` install is not replaced by this smaller set.
66
+ * - Otherwise 'already', and the settings come back unchanged.
67
+ * User hooks are never removed or reordered, including a user hook that
68
+ * shares an entry with a PLUR hook.
69
+ */
70
+ declare function applyPlurHooks(settings: Settings, hooksMap: Record<string, HookEntry[]>): {
71
+ settings: Settings;
72
+ status: 'installed' | 'healed' | 'already';
73
+ };
30
74
 
31
- export { compareSemver, extractManifestVersion };
75
+ export { type HookEntry, type Settings, applyPlurHooks, buildPlurHooks, compareSemver, extractManifestVersion };
package/dist/index.js CHANGED
@@ -1,13 +1,72 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  VERSION
4
- } from "./chunk-CYXWRLDH.js";
4
+ } from "./chunk-WA3JKTZ7.js";
5
5
 
6
6
  // src/index.ts
7
- import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, statSync } from "fs";
8
- import { join } from "path";
7
+ import { existsSync, readFileSync as readFileSync2, writeFileSync, mkdirSync, readdirSync, statSync } from "fs";
8
+ import { join as join2 } from "path";
9
9
  import { fileURLToPath } from "url";
10
- import { homedir, platform } from "os";
10
+ import { homedir as homedir2, platform } from "os";
11
+
12
+ // src/hook-command.ts
13
+ import { homedir } from "os";
14
+ import { readFileSync } from "fs";
15
+ import { join } from "path";
16
+ function isPlurHookSpec(spec) {
17
+ const command = typeof spec.command === "string" ? spec.command : "";
18
+ const args = Array.isArray(spec.args) ? spec.args.filter((a) => typeof a === "string") : [];
19
+ if (args.length === 0) return isPlurHookCommand(command);
20
+ const norm = (p) => p.replace(/\\/g, "/").replace(/"/g, "").toLowerCase();
21
+ const cmd = norm(command);
22
+ if (/(^|\/)cmd(\.exe)?$/.test(cmd)) return isPlurHookCommand(args.filter((a) => a.toLowerCase() !== "/c").join(" "));
23
+ if (!/(^|\/)node(\.exe)?$/.test(cmd)) return false;
24
+ if (!/^hook-[a-z0-9][a-z0-9-]*$/.test(args[1] ?? "")) return false;
25
+ const target = normEntry(args[0]);
26
+ return recordedCliEntries().some((e) => normEntry(e) === target);
27
+ }
28
+ function normEntry(p) {
29
+ return p.replace(/\\/g, "/").replace(/"/g, "").toLowerCase();
30
+ }
31
+ function entriesOf(meta) {
32
+ if (!meta || typeof meta !== "object") return [];
33
+ const m = meta;
34
+ if (Array.isArray(m.entrypoints)) return m.entrypoints.filter((e) => typeof e === "string" && e.length > 0);
35
+ return typeof m.entrypoint === "string" && m.entrypoint.length > 0 ? [m.entrypoint] : [];
36
+ }
37
+ function recordedCliEntries() {
38
+ try {
39
+ return entriesOf(JSON.parse(readFileSync(join(homedir(), ".plur", "bin", "plur-hook.meta.json"), "utf8")));
40
+ } catch {
41
+ return [];
42
+ }
43
+ }
44
+ var PLAIN = "[^\\s\"'`$&;|<>()]";
45
+ var HOOK_TAIL = `[ \\t]+hook-[a-z0-9][a-z0-9-]*(?:[ \\t]+${PLAIN}+)*[ \\t]*$`;
46
+ var SHIM_FILE = "(?:plur-hook(?:\\.cmd)?|(?<=/(?:\\.plur|plur~\\d+)/bin/)plur-h~\\d+\\.cmd)";
47
+ var SHIM_PATH = [
48
+ `(?:${PLAIN}*/)?${SHIM_FILE}`,
49
+ `"(?:[^"]*/)?${SHIM_FILE}"`
50
+ ].join("|");
51
+ var SHIM_FORM = new RegExp(`^(?:&[ \\t]+)?(?:${SHIM_PATH})${HOOK_TAIL}`);
52
+ var NPX_FORM = new RegExp(`^npx(?:[ \\t]+-y)?[ \\t]+@plur-ai/cli(?:@${PLAIN}+)?${HOOK_TAIL}`);
53
+ var HOME_SHIM_TAIL = new RegExp(`^${HOOK_TAIL}`);
54
+ function homeShimPaths() {
55
+ const bin = `${homedir().replace(/\\/g, "/").toLowerCase().replace(/\/+$/, "")}/.plur/bin/`;
56
+ if (/[\r\n]/.test(bin)) return [];
57
+ return [`${bin}plur-hook.cmd`, `${bin}plur-hook`];
58
+ }
59
+ var MAX_HOOK_COMMAND_LENGTH = 8192;
60
+ function isPlurHookCommand(command) {
61
+ return command.length <= MAX_HOOK_COMMAND_LENGTH && matchesPlurHookLauncher(command);
62
+ }
63
+ function matchesPlurHookLauncher(command) {
64
+ const normalised = command.trim().replace(/\\/g, "/").toLowerCase();
65
+ if (SHIM_FORM.test(normalised) || NPX_FORM.test(normalised)) return true;
66
+ return homeShimPaths().some((p) => normalised.startsWith(p) && HOME_SHIM_TAIL.test(normalised.slice(p.length)));
67
+ }
68
+
69
+ // src/index.ts
11
70
  var HELP = `plur-mcp v${VERSION} \u2014 persistent memory for AI agents
12
71
 
13
72
  Usage:
@@ -49,7 +108,7 @@ function compareSemver(a, b) {
49
108
  }
50
109
  function extractManifestVersion(skillMdPath) {
51
110
  try {
52
- const content = readFileSync(skillMdPath, "utf8");
111
+ const content = readFileSync2(skillMdPath, "utf8");
53
112
  const fmMatch = content.match(/^---\s*\n([\s\S]*?)\n---/m);
54
113
  if (!fmMatch) return null;
55
114
  const versionMatch = fmMatch[1].match(/^version:\s*"?([^"\n]+)"?\s*$/m);
@@ -59,40 +118,57 @@ function extractManifestVersion(skillMdPath) {
59
118
  }
60
119
  }
61
120
  var _shimName = platform() === "win32" ? "plur-hook.cmd" : "plur-hook";
62
- var _shimCandidate = join(homedir(), ".plur", "bin", _shimName);
121
+ var _shimCandidate = join2(homedir2(), ".plur", "bin", _shimName);
63
122
  var CLI = existsSync(_shimCandidate) ? _shimCandidate : "npx @plur-ai/cli";
64
- var PLUR_HOOKS = {
65
- // --- Session lifecycle ---
66
- UserPromptSubmit: [{
67
- hooks: [{ type: "command", command: `${CLI} hook-inject`, timeout: 15 }]
68
- }],
69
- PostCompact: [{
70
- matcher: "auto|manual",
71
- hooks: [{ type: "command", command: `${CLI} hook-inject --rehydrate`, timeout: 15 }]
72
- }],
73
- // Auto-close the memory lifecycle at session end (Claude Code SessionEnd,
74
- // shipped v1.0.85) — captures a closing episode and cleans up the session
75
- // checkpoint even if the agent forgot to call plur_session_end (#217).
76
- SessionEnd: [{
77
- hooks: [{ type: "command", command: `${CLI} hook-session-end`, timeout: 5 }]
78
- }],
79
- // --- Contextual injection ---
80
- PreToolUse: [
81
- { matcher: "EnterPlanMode", hooks: [{ type: "command", command: `${CLI} hook-inject --event plan_mode`, timeout: 10 }] },
82
- { matcher: "Skill", hooks: [{ type: "command", command: `${CLI} hook-inject --event skill`, timeout: 10 }] },
83
- { matcher: "Agent", hooks: [{ type: "command", command: `${CLI} hook-inject --event agent`, timeout: 10 }] },
84
- { matcher: "Bash|Edit|Write|Agent", hooks: [{ type: "command", command: `${CLI} hook-observe`, timeout: 3 }] }
85
- ],
86
- PostToolUse: [
87
- { matcher: "Bash|Edit|Write|Agent", hooks: [{ type: "command", command: `${CLI} hook-observe --post`, timeout: 3 }] }
88
- ],
89
- SubagentStart: [
90
- { matcher: ".*", hooks: [{ type: "command", command: `${CLI} hook-inject --event subagent`, timeout: 10 }] }
91
- ],
92
- Stop: [
93
- { matcher: "*", hooks: [{ type: "command", command: `${CLI} hook-learn-check`, timeout: 2 }] }
94
- ]
95
- };
123
+ function buildPlurHooks(cli) {
124
+ return {
125
+ // --- Session lifecycle ---
126
+ // Sync with a 20s budget, like `plur init` (#1313): async context reaches
127
+ // Claude Code only at the next safe point, so a first reply without tool
128
+ // calls had no memory. hook-inject exits by itself after 15s, so the
129
+ // timeout sits above that. Keep in step with CLAUDE_INJECT_TIMEOUT_S in
130
+ // @plur-ai/cli's lib/claude-inject-budget.ts.
131
+ UserPromptSubmit: [{
132
+ hooks: [{ type: "command", command: `${cli} hook-inject`, timeout: 20 }]
133
+ }],
134
+ // Re-inject after compaction. SessionStart with matcher "compact" fires
135
+ // right after compaction and can carry context; PostCompact cannot
136
+ // (#1274, #1279). Re-running init moves an old PostCompact entry here.
137
+ SessionStart: [{
138
+ matcher: "compact",
139
+ hooks: [{ type: "command", command: `${cli} hook-inject --rehydrate`, timeout: 20 }]
140
+ }, {
141
+ // `claude --resume` keeps the session id, and SessionEnd below deleted
142
+ // its folder-question nonces: the resumed session is asked again with a
143
+ // fresh nonce (#1347, option C). Identical to `plur init`'s entry.
144
+ matcher: "resume",
145
+ hooks: [{ type: "command", command: `${cli} hook-session-resume`, timeout: 3 }]
146
+ }],
147
+ // Auto-close the memory lifecycle at session end (Claude Code SessionEnd,
148
+ // shipped v1.0.85) — captures a closing episode and cleans up the session
149
+ // checkpoint even if the agent forgot to call plur_session_end (#217).
150
+ SessionEnd: [{
151
+ hooks: [{ type: "command", command: `${cli} hook-session-end`, timeout: 5 }]
152
+ }],
153
+ // --- Contextual injection ---
154
+ PreToolUse: [
155
+ { matcher: "EnterPlanMode", hooks: [{ type: "command", command: `${cli} hook-inject --event plan_mode`, timeout: 10 }] },
156
+ { matcher: "Skill", hooks: [{ type: "command", command: `${cli} hook-inject --event skill`, timeout: 10 }] },
157
+ { matcher: "Agent", hooks: [{ type: "command", command: `${cli} hook-inject --event agent`, timeout: 10 }] },
158
+ { matcher: "Bash|Edit|Write|Agent", hooks: [{ type: "command", command: `${cli} hook-observe`, timeout: 3 }] }
159
+ ],
160
+ PostToolUse: [
161
+ { matcher: "Bash|Edit|Write|Agent", hooks: [{ type: "command", command: `${cli} hook-observe --post`, timeout: 3 }] }
162
+ ],
163
+ SubagentStart: [
164
+ { matcher: ".*", hooks: [{ type: "command", command: `${cli} hook-inject --event subagent`, timeout: 10 }] }
165
+ ],
166
+ Stop: [
167
+ { matcher: "*", hooks: [{ type: "command", command: `${cli} hook-learn-check`, timeout: 2 }] }
168
+ ]
169
+ };
170
+ }
171
+ var PLUR_HOOKS = buildPlurHooks(CLI);
96
172
  var CLAUDE_MD_SECTION = `## PLUR Memory
97
173
 
98
174
  You have persistent memory via PLUR. Corrections, preferences, and conventions persist across sessions as engrams.
@@ -122,11 +198,11 @@ When the user corrects you ("no, use X not Y", "that's wrong"):
122
198
  `;
123
199
  function installClaudeMd() {
124
200
  const marker = "## PLUR Memory";
125
- const projectClaudeMd = join(process.cwd(), "CLAUDE.md");
126
- const globalClaudeMd = join(homedir(), "CLAUDE.md");
201
+ const projectClaudeMd = join2(process.cwd(), "CLAUDE.md");
202
+ const globalClaudeMd = join2(homedir2(), "CLAUDE.md");
127
203
  const claudeMdPath = existsSync(projectClaudeMd) ? projectClaudeMd : existsSync(globalClaudeMd) ? globalClaudeMd : projectClaudeMd;
128
204
  if (existsSync(claudeMdPath)) {
129
- const content = readFileSync(claudeMdPath, "utf8");
205
+ const content = readFileSync2(claudeMdPath, "utf8");
130
206
  if (content.includes(marker)) {
131
207
  return `already in ${claudeMdPath}`;
132
208
  }
@@ -139,16 +215,16 @@ ${CLAUDE_MD_SECTION}`);
139
215
  return `created ${claudeMdPath}`;
140
216
  }
141
217
  function findMcpConfig() {
142
- const projectMcp = join(process.cwd(), ".mcp.json");
218
+ const projectMcp = join2(process.cwd(), ".mcp.json");
143
219
  if (existsSync(projectMcp)) return projectMcp;
144
- const globalMcp = join(homedir(), ".claude", "mcp.json");
220
+ const globalMcp = join2(homedir2(), ".claude", "mcp.json");
145
221
  if (existsSync(globalMcp)) return globalMcp;
146
222
  return projectMcp;
147
223
  }
148
224
  function readJsonObjectForWrite(path) {
149
225
  if (!existsSync(path)) return { data: {}, ok: true };
150
226
  try {
151
- const parsed = JSON.parse(readFileSync(path, "utf8"));
227
+ const parsed = JSON.parse(readFileSync2(path, "utf8"));
152
228
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
153
229
  return { data: parsed, ok: true };
154
230
  }
@@ -180,40 +256,89 @@ function writeMcpConfig(configPath) {
180
256
  }
181
257
  servers.plur = MCP_SERVER_CONFIG;
182
258
  config.mcpServers = servers;
183
- const dir = join(configPath, "..");
259
+ const dir = join2(configPath, "..");
184
260
  mkdirSync(dir, { recursive: true });
185
261
  writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
186
262
  return `added to ${configPath}`;
187
263
  }
264
+ function isPlurCommandHook(h) {
265
+ return typeof h.command === "string" && isPlurHookSpec({ command: h.command, args: h.args });
266
+ }
267
+ function isPlurHook(entry) {
268
+ return (entry.hooks ?? []).some(isPlurCommandHook);
269
+ }
270
+ var REHYDRATE = /(?:^|\s)hook-inject\s+--rehydrate(?:\s|$)/;
271
+ function launchLine(h) {
272
+ const args = Array.isArray(h.args) ? h.args.filter((a) => typeof a === "string") : [];
273
+ return [h.command, ...args].join(" ");
274
+ }
275
+ function isPlurRehydrateHook(h) {
276
+ return isPlurCommandHook(h) && REHYDRATE.test(launchLine(h).replace(/\\/g, "/").toLowerCase());
277
+ }
278
+ function isPlurRehydrate(entry) {
279
+ return (entry.hooks ?? []).some(isPlurRehydrateHook);
280
+ }
281
+ function hasPlurSubcommand(entries, subcommand) {
282
+ const re = new RegExp(`(?:^|\\s)${subcommand}(?:\\s|$)`);
283
+ return (entries ?? []).some((e) => (e.hooks ?? []).some((h) => isPlurCommandHook(h) && re.test(h.command.replace(/\\/g, "/").toLowerCase())));
284
+ }
285
+ var isResumeEntry = (e) => e.matcher === "resume";
286
+ function stripPlurHooks(entries) {
287
+ const kept = [];
288
+ for (const entry of entries) {
289
+ if (!isPlurHook(entry)) {
290
+ kept.push(entry);
291
+ continue;
292
+ }
293
+ const rest = entry.hooks.filter((h) => !isPlurCommandHook(h));
294
+ if (rest.length > 0) kept.push({ ...entry, hooks: rest });
295
+ }
296
+ return kept;
297
+ }
298
+ function applyPlurHooks(settings, hooksMap) {
299
+ const hooks = { ...settings.hooks ?? {} };
300
+ const installed = Object.values(hooks).some((entries) => (entries ?? []).some(isPlurHook));
301
+ if (!installed) {
302
+ for (const [event, entries] of Object.entries(hooksMap)) {
303
+ hooks[event] = [...hooks[event] ?? [], ...entries];
304
+ }
305
+ return { settings: { ...settings, hooks }, status: "installed" };
306
+ }
307
+ let changed = false;
308
+ let movedRehydrate = false;
309
+ if (hooks.PostCompact?.some(isPlurHook)) {
310
+ movedRehydrate = hooks.PostCompact.some(isPlurRehydrate);
311
+ const kept = stripPlurHooks(hooks.PostCompact);
312
+ if (kept.length > 0) hooks.PostCompact = kept;
313
+ else delete hooks.PostCompact;
314
+ changed = true;
315
+ }
316
+ if (movedRehydrate && !(hooks.SessionStart ?? []).some(isPlurRehydrate)) {
317
+ hooks.SessionStart = [...hooks.SessionStart ?? [], ...(hooksMap.SessionStart ?? []).filter((e) => !isResumeEntry(e))];
318
+ changed = true;
319
+ }
320
+ if (hasPlurSubcommand(hooks.SessionEnd, "hook-session-end") && !hasPlurSubcommand(hooks.SessionStart, "hook-session-resume")) {
321
+ const resume = (hooksMap.SessionStart ?? []).filter(isResumeEntry);
322
+ if (resume.length > 0) {
323
+ hooks.SessionStart = [...hooks.SessionStart ?? [], ...resume];
324
+ changed = true;
325
+ }
326
+ }
327
+ return changed ? { settings: { ...settings, hooks }, status: "healed" } : { settings, status: "already" };
328
+ }
188
329
  function installHooks() {
189
- const projectSettings = join(process.cwd(), ".claude", "settings.json");
190
- const globalSettings = join(homedir(), ".claude", "settings.json");
191
- const settingsPath = existsSync(join(process.cwd(), ".claude")) ? projectSettings : globalSettings;
330
+ const settingsPath = join2(homedir2(), ".claude", "settings.json");
192
331
  const settingsRead = readJsonObjectForWrite(settingsPath);
193
332
  if (!settingsRead.ok) {
194
333
  return `skipped \u2014 ${settingsPath} exists but is not a JSON object; writing would discard your other settings. Fix it by hand, then re-run \`plur-mcp init\``;
195
334
  }
196
335
  const settings = settingsRead.data;
197
- const hooks = settings.hooks ?? {};
198
- for (const entries of Object.values(hooks)) {
199
- for (const entry of entries) {
200
- for (const h of entry.hooks ?? []) {
201
- if (h.command.includes("@plur-ai/cli")) {
202
- return `already installed in ${settingsPath}`;
203
- }
204
- }
205
- }
206
- }
207
- const existing = settings.hooks ?? {};
208
- const merged = { ...existing };
209
- for (const [event, newEntries] of Object.entries(PLUR_HOOKS)) {
210
- merged[event] = [...merged[event] ?? [], ...newEntries];
211
- }
212
- settings.hooks = merged;
213
- const dir = join(settingsPath, "..");
336
+ const { settings: next, status } = applyPlurHooks(settings, PLUR_HOOKS);
337
+ if (status === "already") return `already installed in ${settingsPath}`;
338
+ const dir = join2(settingsPath, "..");
214
339
  mkdirSync(dir, { recursive: true });
215
- writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
216
- return `installed in ${settingsPath}`;
340
+ writeFileSync(settingsPath, JSON.stringify(next, null, 2) + "\n");
341
+ return status === "healed" ? `updated PLUR hooks (SessionStart compact/resume) in ${settingsPath}` : `installed in ${settingsPath}`;
217
342
  }
218
343
  async function runInit() {
219
344
  const results = [];
@@ -240,27 +365,27 @@ async function runInit() {
240
365
  results.push(`CLAUDE.md: ${claudeMdStatus}`);
241
366
  const { Plur } = await import("@plur-ai/core");
242
367
  const plur = new Plur({ path: paths.root });
243
- const bundledPacksDir = join(fileURLToPath(import.meta.url), "..", "..", "packs");
368
+ const bundledPacksDir = join2(fileURLToPath(import.meta.url), "..", "..", "packs");
244
369
  let packsStatus = "no bundled packs found";
245
370
  if (existsSync(bundledPacksDir)) {
246
371
  const installed = plur.listPacks();
247
372
  const installedByName = new Map(installed.map((p) => [p.name, p.manifest?.version]));
248
- const entries = readdirSync(bundledPacksDir).filter((e) => statSync(join(bundledPacksDir, e)).isDirectory());
373
+ const entries = readdirSync(bundledPacksDir).filter((e) => statSync(join2(bundledPacksDir, e)).isDirectory());
249
374
  const newPacks = [];
250
375
  const upgradedPacks = [];
251
376
  for (const entry of entries) {
252
- const bundledManifestPath = join(bundledPacksDir, entry, "SKILL.md");
377
+ const bundledManifestPath = join2(bundledPacksDir, entry, "SKILL.md");
253
378
  const bundledVersion = existsSync(bundledManifestPath) ? extractManifestVersion(bundledManifestPath) : null;
254
379
  const installedVersion = installedByName.get(entry);
255
380
  if (!installedByName.has(entry)) {
256
381
  try {
257
- await plur.installPack(join(bundledPacksDir, entry));
382
+ await plur.installPack(join2(bundledPacksDir, entry));
258
383
  newPacks.push(entry);
259
384
  } catch {
260
385
  }
261
386
  } else if (bundledVersion && (!installedVersion || compareSemver(bundledVersion, installedVersion) > 0)) {
262
387
  try {
263
- await plur.installPack(join(bundledPacksDir, entry));
388
+ await plur.installPack(join2(bundledPacksDir, entry));
264
389
  upgradedPacks.push(
265
390
  `${entry} ${installedVersion ?? "unknown"}\u2192${bundledVersion}`
266
391
  );
@@ -295,7 +420,7 @@ async function runInit() {
295
420
  }
296
421
  }
297
422
  async function runPacks() {
298
- const plurPath = process.env.PLUR_PATH ?? join(homedir(), ".plur");
423
+ const plurPath = process.env.PLUR_PATH ?? join2(homedir2(), ".plur");
299
424
  const { Plur } = await import("@plur-ai/core");
300
425
  const { packsCommand } = await import("./packs-cli-XXZTSBHW.js");
301
426
  const plur = new Plur({ path: plurPath });
@@ -323,7 +448,7 @@ if (arg === "packs") {
323
448
  process.exit(0);
324
449
  }
325
450
  if (arg === "serve" || arg === void 0) {
326
- const { runStdio } = await import("./server-XTPKBBQN.js");
451
+ const { runStdio } = await import("./server-EUMEOVXB.js");
327
452
  runStdio().catch((err) => {
328
453
  console.error("Failed to start PLUR MCP server:", err);
329
454
  process.exit(1);
@@ -334,6 +459,8 @@ Run plur-mcp --help for usage.`);
334
459
  process.exit(1);
335
460
  }
336
461
  export {
462
+ applyPlurHooks,
463
+ buildPlurHooks,
337
464
  compareSemver,
338
465
  extractManifestVersion
339
466
  };
@@ -6,10 +6,10 @@ import {
6
6
  resolveToolProfile,
7
7
  setActiveToolProfile,
8
8
  validateToolArgs
9
- } from "./chunk-Z227RPMX.js";
9
+ } from "./chunk-UINIIUBY.js";
10
10
  import {
11
11
  VERSION
12
- } from "./chunk-CYXWRLDH.js";
12
+ } from "./chunk-WA3JKTZ7.js";
13
13
 
14
14
  // src/server.ts
15
15
  import { Server, ProtocolError, ProtocolErrorCode } from "@modelcontextprotocol/server";
@@ -2,8 +2,8 @@ import {
2
2
  CURSOR_CORE_TOOL_NAMES,
3
3
  getToolDefinitions,
4
4
  validateToolArgs
5
- } from "./chunk-Z227RPMX.js";
6
- import "./chunk-CYXWRLDH.js";
5
+ } from "./chunk-UINIIUBY.js";
6
+ import "./chunk-WA3JKTZ7.js";
7
7
 
8
8
  // src/tools-export.ts
9
9
  function getToolSchemas(profile) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@plur-ai/mcp",
3
3
  "mcpName": "io.github.plur-ai/plur",
4
- "version": "0.20.0",
4
+ "version": "0.21.0",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "plur-mcp": "dist/index.js"
@@ -16,7 +16,7 @@
16
16
  "@modelcontextprotocol/client": "2.0.0-beta.4",
17
17
  "@modelcontextprotocol/core": "2.0.0-beta.4",
18
18
  "zod": "^3.23.0",
19
- "@plur-ai/core": "0.20.0"
19
+ "@plur-ai/core": "0.21.0"
20
20
  },
21
21
  "devDependencies": {
22
22
  "@types/node": "^25.5.0"