@plur-ai/mcp 0.21.0 → 0.21.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/README.md +31 -0
- package/dist/{chunk-UINIIUBY.js → chunk-22X2SQNV.js} +253 -45
- package/dist/{chunk-WA3JKTZ7.js → chunk-OQDR6UF6.js} +1 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.js +92 -54
- package/dist/server-QOHVV2Z7.js +1007 -0
- package/dist/tools-export.js +2 -2
- package/package.json +2 -2
- package/dist/server-EUMEOVXB.js +0 -483
package/dist/index.d.ts
CHANGED
|
@@ -48,6 +48,13 @@ interface HookEntry {
|
|
|
48
48
|
async?: boolean;
|
|
49
49
|
}>;
|
|
50
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* Write the PLUR section into CLAUDE.md with core's `upsertInstructionSection`
|
|
53
|
+
* — the same logic as `plur init`: only a section PLUR shipped is replaced,
|
|
54
|
+
* one the user wrote or edited is kept and the new one added beside it, and a
|
|
55
|
+
* timestamped backup precedes any change to an existing file.
|
|
56
|
+
*/
|
|
57
|
+
declare function installClaudeMd(claudeMdPath?: string): Promise<string>;
|
|
51
58
|
/**
|
|
52
59
|
* Merge PLUR hooks into Claude Code settings (#1279). No I/O apart from
|
|
53
60
|
* reading plur-hook.meta.json for the exec-form check.
|
|
@@ -72,4 +79,4 @@ declare function applyPlurHooks(settings: Settings, hooksMap: Record<string, Hoo
|
|
|
72
79
|
status: 'installed' | 'healed' | 'already';
|
|
73
80
|
};
|
|
74
81
|
|
|
75
|
-
export { type HookEntry, type Settings, applyPlurHooks, buildPlurHooks, compareSemver, extractManifestVersion };
|
|
82
|
+
export { type HookEntry, type Settings, applyPlurHooks, buildPlurHooks, compareSemver, extractManifestVersion, installClaudeMd };
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
VERSION
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-OQDR6UF6.js";
|
|
5
5
|
|
|
6
6
|
// src/index.ts
|
|
7
7
|
import { existsSync, readFileSync as readFileSync2, writeFileSync, mkdirSync, readdirSync, statSync } from "fs";
|
|
@@ -169,6 +169,8 @@ function buildPlurHooks(cli) {
|
|
|
169
169
|
};
|
|
170
170
|
}
|
|
171
171
|
var PLUR_HOOKS = buildPlurHooks(CLI);
|
|
172
|
+
var PLUR_INSTRUCTIONS_MARKER = "<!-- plur-instructions-v4 -->";
|
|
173
|
+
var MEMORY_FOOTER_RULE = "End every reply with one short line: `Memory \u2014 recalled N \xB7 used: ENG-\u2026, ENG-\u2026 \xB7 written: ENG-\u2026` (recalled as a count; used and written as ids only, no statements), or `Memory \u2014 none`. Only count/list ids you actually saw this turn; never invent an id. Give details only if the user asks.";
|
|
172
174
|
var CLAUDE_MD_SECTION = `## PLUR Memory
|
|
173
175
|
|
|
174
176
|
You have persistent memory via PLUR. Corrections, preferences, and conventions persist across sessions as engrams.
|
|
@@ -189,37 +191,59 @@ Hooks inject engrams automatically on every first message \u2014 you do not need
|
|
|
189
191
|
|
|
190
192
|
Do not ask permission to use these tools \u2014 they are your memory system.
|
|
191
193
|
|
|
194
|
+
### Memory line on every reply
|
|
195
|
+
|
|
196
|
+
${MEMORY_FOOTER_RULE}
|
|
197
|
+
|
|
192
198
|
### When corrected
|
|
193
199
|
|
|
194
200
|
When the user corrects you ("no, use X not Y", "that's wrong"):
|
|
195
201
|
1. Call \`plur_learn\` immediately \u2014 before continuing the task
|
|
196
202
|
2. Call \`plur_feedback\` with negative signal on the wrong engram if one was injected
|
|
197
203
|
3. Then continue with the corrected approach
|
|
204
|
+
|
|
205
|
+
${PLUR_INSTRUCTIONS_MARKER}
|
|
198
206
|
`;
|
|
199
|
-
function
|
|
200
|
-
const marker = "## PLUR Memory";
|
|
207
|
+
function defaultClaudeMdPath() {
|
|
201
208
|
const projectClaudeMd = join2(process.cwd(), "CLAUDE.md");
|
|
202
209
|
const globalClaudeMd = join2(homedir2(), "CLAUDE.md");
|
|
203
|
-
|
|
204
|
-
if (existsSync(claudeMdPath)) {
|
|
205
|
-
const content = readFileSync2(claudeMdPath, "utf8");
|
|
206
|
-
if (content.includes(marker)) {
|
|
207
|
-
return `already in ${claudeMdPath}`;
|
|
208
|
-
}
|
|
209
|
-
writeFileSync(claudeMdPath, content.trimEnd() + "\n\n" + CLAUDE_MD_SECTION);
|
|
210
|
-
return `added to ${claudeMdPath}`;
|
|
211
|
-
}
|
|
212
|
-
writeFileSync(claudeMdPath, `# CLAUDE.md
|
|
213
|
-
|
|
214
|
-
${CLAUDE_MD_SECTION}`);
|
|
215
|
-
return `created ${claudeMdPath}`;
|
|
210
|
+
return existsSync(projectClaudeMd) ? projectClaudeMd : existsSync(globalClaudeMd) ? globalClaudeMd : projectClaudeMd;
|
|
216
211
|
}
|
|
217
|
-
function
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
|
|
212
|
+
async function installClaudeMd(claudeMdPath = defaultClaudeMdPath()) {
|
|
213
|
+
const { upsertInstructionSection, writeWithBackup, SHIPPED_PLUR_SECTIONS } = await import("@plur-ai/core");
|
|
214
|
+
const existing = existsSync(claudeMdPath) ? readFileSync2(claudeMdPath, "utf8") : null;
|
|
215
|
+
const r = upsertInstructionSection(existing, {
|
|
216
|
+
section: CLAUDE_MD_SECTION,
|
|
217
|
+
title: "# CLAUDE.md",
|
|
218
|
+
heading: "## PLUR Memory",
|
|
219
|
+
marker: PLUR_INSTRUCTIONS_MARKER,
|
|
220
|
+
shipped: SHIPPED_PLUR_SECTIONS
|
|
221
|
+
});
|
|
222
|
+
if (r.status === "skipped") return `not written to ${claudeMdPath}: ${r.skipReason}`;
|
|
223
|
+
let backup;
|
|
224
|
+
try {
|
|
225
|
+
backup = r.status === "already" ? null : writeWithBackup(claudeMdPath, r.content, existing);
|
|
226
|
+
} catch (err) {
|
|
227
|
+
if (err.code === "PLUR_REFUSED") return `not written to ${claudeMdPath}: ${err.message}`;
|
|
228
|
+
throw err;
|
|
229
|
+
}
|
|
230
|
+
const head = {
|
|
231
|
+
created: `created ${claudeMdPath}`,
|
|
232
|
+
added: `added to ${claudeMdPath}`,
|
|
233
|
+
upgraded: `upgraded in ${claudeMdPath}`,
|
|
234
|
+
already: `already in ${claudeMdPath}`
|
|
235
|
+
}[r.status];
|
|
236
|
+
const notes = [];
|
|
237
|
+
if (backup) notes.push(`backup: ${backup}`);
|
|
238
|
+
if (r.keptSections > 0) {
|
|
239
|
+
notes.push(
|
|
240
|
+
`left ${r.keptSections} older "## PLUR Memory" section${r.keptSections === 1 ? "" : "s"} untouched because ${r.keptSections === 1 ? "it has" : "they have"} text PLUR did not write \u2014 remove it yourself once you have kept what you need`
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
if (r.closedOpenBlock) {
|
|
244
|
+
notes.push(`closed the ${r.closedOpenBlock === "fence" ? "code block" : "HTML comment"} left open at the end of the file before adding the section`);
|
|
245
|
+
}
|
|
246
|
+
return notes.length ? `${head} (${notes.join("; ")})` : head;
|
|
223
247
|
}
|
|
224
248
|
function readJsonObjectForWrite(path) {
|
|
225
249
|
if (!existsSync(path)) return { data: {}, ok: true };
|
|
@@ -232,34 +256,46 @@ function readJsonObjectForWrite(path) {
|
|
|
232
256
|
}
|
|
233
257
|
return { data: {}, ok: false };
|
|
234
258
|
}
|
|
235
|
-
function
|
|
236
|
-
const { data, ok } = readJsonObjectForWrite(configPath);
|
|
237
|
-
if (!ok) {
|
|
238
|
-
return `skipped \u2014 ${configPath} exists but is not a JSON object; writing would discard your other MCP servers. Fix it by hand, then re-run \`plur-mcp init\``;
|
|
239
|
-
}
|
|
240
|
-
const config = data;
|
|
241
|
-
const servers = config.mcpServers ?? {};
|
|
259
|
+
function healRaceyEntry(servers) {
|
|
242
260
|
const existing = servers.plur;
|
|
243
|
-
if (existing)
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
261
|
+
if (!existing || typeof existing !== "object") return false;
|
|
262
|
+
const args = existing.args ?? [];
|
|
263
|
+
const spec = args.filter((a) => a !== "-y" && !a.startsWith("-"))[0] ?? "";
|
|
264
|
+
if (existing.command !== "npx" || !/^@plur-ai\/mcp(@latest)?$/.test(spec)) return false;
|
|
265
|
+
const healed = { ...existing, ...MCP_SERVER_CONFIG };
|
|
266
|
+
if (JSON.stringify(healed) === JSON.stringify(existing)) return false;
|
|
267
|
+
servers.plur = healed;
|
|
268
|
+
return true;
|
|
269
|
+
}
|
|
270
|
+
async function installClaudeUserMcp() {
|
|
271
|
+
const { writeWithBackup, registerClaudeUserMcp } = await import("@plur-ai/core");
|
|
272
|
+
const legacyPath = join2(homedir2(), ".claude", "mcp.json");
|
|
273
|
+
const legacyRaw = existsSync(legacyPath) ? readFileSync2(legacyPath, "utf8") : null;
|
|
274
|
+
const legacy = readJsonObjectForWrite(legacyPath);
|
|
275
|
+
const oldServers = legacy.ok ? legacy.data.mcpServers : void 0;
|
|
276
|
+
const oldEntry = oldServers && typeof oldServers === "object" && !Array.isArray(oldServers) ? oldServers.plur : void 0;
|
|
277
|
+
const r = registerClaudeUserMcp({
|
|
278
|
+
userPath: join2(homedir2(), ".claude.json"),
|
|
279
|
+
entry: () => ({ ...MCP_SERVER_CONFIG }),
|
|
280
|
+
heal: (config) => healRaceyEntry(config.mcpServers) ? "upgraded stale npx entry" : null,
|
|
281
|
+
legacyEntry: oldEntry,
|
|
282
|
+
legacyPath,
|
|
283
|
+
rerunCommand: "plur-mcp init"
|
|
284
|
+
});
|
|
285
|
+
if (!r.ok || oldEntry === void 0) return { ok: r.ok, status: r.message };
|
|
286
|
+
let status = r.message;
|
|
287
|
+
const next = { ...legacy.data };
|
|
288
|
+
const rest = { ...oldServers };
|
|
289
|
+
delete rest.plur;
|
|
290
|
+
if (Object.keys(rest).length > 0) next.mcpServers = rest;
|
|
291
|
+
else delete next.mcpServers;
|
|
292
|
+
try {
|
|
293
|
+
const backup = writeWithBackup(legacyPath, JSON.stringify(next, null, 2) + "\n", legacyRaw);
|
|
294
|
+
status += `; removed the unused entry from ${legacyPath}${backup ? ` (backup: ${backup})` : ""}`;
|
|
295
|
+
} catch (err) {
|
|
296
|
+
status += `; could not remove the unused entry from ${legacyPath} (${err.message}) \u2014 Claude Code ignores it, remove it by hand`;
|
|
256
297
|
}
|
|
257
|
-
|
|
258
|
-
config.mcpServers = servers;
|
|
259
|
-
const dir = join2(configPath, "..");
|
|
260
|
-
mkdirSync(dir, { recursive: true });
|
|
261
|
-
writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
262
|
-
return `added to ${configPath}`;
|
|
298
|
+
return { ok: true, status };
|
|
263
299
|
}
|
|
264
300
|
function isPlurCommandHook(h) {
|
|
265
301
|
return typeof h.command === "string" && isPlurHookSpec({ command: h.command, args: h.args });
|
|
@@ -356,12 +392,13 @@ async function runInit() {
|
|
|
356
392
|
} catch {
|
|
357
393
|
}
|
|
358
394
|
results.push(`Search: ${searchMode}`);
|
|
359
|
-
const
|
|
360
|
-
const mcpStatus =
|
|
395
|
+
const mcp = await installClaudeUserMcp();
|
|
396
|
+
const mcpStatus = mcp.status;
|
|
397
|
+
if (!mcp.ok) process.exitCode = 1;
|
|
361
398
|
results.push(`MCP: ${mcpStatus}`);
|
|
362
399
|
const hooksStatus = installHooks();
|
|
363
400
|
results.push(`Hooks: ${hooksStatus}`);
|
|
364
|
-
const claudeMdStatus = installClaudeMd();
|
|
401
|
+
const claudeMdStatus = await installClaudeMd();
|
|
365
402
|
results.push(`CLAUDE.md: ${claudeMdStatus}`);
|
|
366
403
|
const { Plur } = await import("@plur-ai/core");
|
|
367
404
|
const plur = new Plur({ path: paths.root });
|
|
@@ -441,14 +478,14 @@ if (arg === "--version" || arg === "-v") {
|
|
|
441
478
|
}
|
|
442
479
|
if (arg === "init") {
|
|
443
480
|
await runInit();
|
|
444
|
-
process.exit(0);
|
|
481
|
+
process.exit(process.exitCode ?? 0);
|
|
445
482
|
}
|
|
446
483
|
if (arg === "packs") {
|
|
447
484
|
await runPacks();
|
|
448
485
|
process.exit(0);
|
|
449
486
|
}
|
|
450
487
|
if (arg === "serve" || arg === void 0) {
|
|
451
|
-
const { runStdio } = await import("./server-
|
|
488
|
+
const { runStdio } = await import("./server-QOHVV2Z7.js");
|
|
452
489
|
runStdio().catch((err) => {
|
|
453
490
|
console.error("Failed to start PLUR MCP server:", err);
|
|
454
491
|
process.exit(1);
|
|
@@ -462,5 +499,6 @@ export {
|
|
|
462
499
|
applyPlurHooks,
|
|
463
500
|
buildPlurHooks,
|
|
464
501
|
compareSemver,
|
|
465
|
-
extractManifestVersion
|
|
502
|
+
extractManifestVersion,
|
|
503
|
+
installClaudeMd
|
|
466
504
|
};
|