@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.
- package/README.md +3 -1
- package/dist/{chunk-Z227RPMX.js → chunk-UINIIUBY.js} +329 -60
- package/dist/{chunk-CYXWRLDH.js → chunk-WA3JKTZ7.js} +1 -1
- package/dist/index.d.ts +45 -1
- package/dist/index.js +201 -74
- package/dist/{server-XTPKBBQN.js → server-EUMEOVXB.js} +2 -2
- package/dist/tools-export.js +2 -2
- package/package.json +2 -2
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-
|
|
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 =
|
|
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 =
|
|
121
|
+
var _shimCandidate = join2(homedir2(), ".plur", "bin", _shimName);
|
|
63
122
|
var CLI = existsSync(_shimCandidate) ? _shimCandidate : "npx @plur-ai/cli";
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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 =
|
|
126
|
-
const globalClaudeMd =
|
|
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 =
|
|
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 =
|
|
218
|
+
const projectMcp = join2(process.cwd(), ".mcp.json");
|
|
143
219
|
if (existsSync(projectMcp)) return projectMcp;
|
|
144
|
-
const globalMcp =
|
|
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(
|
|
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 =
|
|
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
|
|
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
|
|
198
|
-
|
|
199
|
-
|
|
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(
|
|
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 =
|
|
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(
|
|
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 =
|
|
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(
|
|
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(
|
|
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 ??
|
|
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-
|
|
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-
|
|
9
|
+
} from "./chunk-UINIIUBY.js";
|
|
10
10
|
import {
|
|
11
11
|
VERSION
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-WA3JKTZ7.js";
|
|
13
13
|
|
|
14
14
|
// src/server.ts
|
|
15
15
|
import { Server, ProtocolError, ProtocolErrorCode } from "@modelcontextprotocol/server";
|
package/dist/tools-export.js
CHANGED
|
@@ -2,8 +2,8 @@ import {
|
|
|
2
2
|
CURSOR_CORE_TOOL_NAMES,
|
|
3
3
|
getToolDefinitions,
|
|
4
4
|
validateToolArgs
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import "./chunk-
|
|
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.
|
|
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.
|
|
19
|
+
"@plur-ai/core": "0.21.0"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"@types/node": "^25.5.0"
|