@plur-ai/cli 0.9.11 → 0.9.12
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.
|
@@ -2,7 +2,16 @@
|
|
|
2
2
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
3
3
|
import { join, dirname } from "path";
|
|
4
4
|
import { homedir, platform } from "os";
|
|
5
|
+
function findMcpShim() {
|
|
6
|
+
const name = platform() === "win32" ? "plur-mcp.cmd" : "plur-mcp";
|
|
7
|
+
const path = join(homedir(), ".plur", "bin", name);
|
|
8
|
+
return existsSync(path) ? path : null;
|
|
9
|
+
}
|
|
5
10
|
function buildMcpServerEntry() {
|
|
11
|
+
const shim = findMcpShim();
|
|
12
|
+
if (shim) {
|
|
13
|
+
return { command: shim, args: [] };
|
|
14
|
+
}
|
|
6
15
|
if (platform() === "win32") {
|
|
7
16
|
return {
|
|
8
17
|
command: "cmd.exe",
|
package/dist/commands/doctor.js
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
hasPlurMcp,
|
|
5
5
|
knownConfigFiles,
|
|
6
6
|
readConfig
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-OAIEWP3Q.js";
|
|
8
8
|
import {
|
|
9
9
|
outputJson,
|
|
10
10
|
outputText,
|
|
@@ -38,6 +38,30 @@ function hasStaleNpxHooks(config) {
|
|
|
38
38
|
}
|
|
39
39
|
return false;
|
|
40
40
|
}
|
|
41
|
+
function hasStaleNpxMcp(config) {
|
|
42
|
+
const servers = config.mcpServers ?? {};
|
|
43
|
+
const plur = servers.plur;
|
|
44
|
+
if (!plur) return false;
|
|
45
|
+
if (plur.command && plur.command.includes("npx")) return true;
|
|
46
|
+
const argsBlob = (plur.args ?? []).join(" ");
|
|
47
|
+
return argsBlob.includes("npx") && argsBlob.includes("@plur-ai/mcp");
|
|
48
|
+
}
|
|
49
|
+
function validateMcpShim() {
|
|
50
|
+
const name = platform() === "win32" ? "plur-mcp.cmd" : "plur-mcp";
|
|
51
|
+
const path = join(homedir(), ".plur", "bin", name);
|
|
52
|
+
if (!existsSync(path)) {
|
|
53
|
+
return { valid: false, shimPath: path, error: "shim not found \u2014 run `plur init` to create it (requires @plur-ai/mcp installed alongside CLI)" };
|
|
54
|
+
}
|
|
55
|
+
const content = readFileSync(path, "utf-8");
|
|
56
|
+
const match = content.match(/"([^"]+index\.js)"/);
|
|
57
|
+
if (!match) {
|
|
58
|
+
return { valid: false, shimPath: path, error: "shim has unexpected format" };
|
|
59
|
+
}
|
|
60
|
+
if (!existsSync(match[1])) {
|
|
61
|
+
return { valid: false, shimPath: path, error: `entrypoint missing: ${match[1]} \u2014 run \`plur init\` to fix` };
|
|
62
|
+
}
|
|
63
|
+
return { valid: true, shimPath: path };
|
|
64
|
+
}
|
|
41
65
|
function validateHookShim() {
|
|
42
66
|
const name = platform() === "win32" ? "plur-hook.cmd" : "plur-hook";
|
|
43
67
|
const path = join(homedir(), ".plur", "bin", name);
|
|
@@ -248,11 +272,17 @@ function buildReport(skipHandshake, flags) {
|
|
|
248
272
|
const config = readConfig(c.path);
|
|
249
273
|
return hasStaleNpxHooks(config);
|
|
250
274
|
});
|
|
275
|
+
const staleNpxMcp = configs.some((c) => {
|
|
276
|
+
if (!c.exists) return false;
|
|
277
|
+
const config = readConfig(c.path);
|
|
278
|
+
return hasStaleNpxMcp(config);
|
|
279
|
+
});
|
|
251
280
|
const hookShim = validateHookShim();
|
|
281
|
+
const mcpShim = validateMcpShim();
|
|
252
282
|
const handshakePromise = skipHandshake ? Promise.resolve({ ok: false, error: "skipped (--no-handshake)" }) : mcpHandshake();
|
|
253
283
|
return Promise.all([handshakePromise, checkEmbedder(flags)]).then(([handshake, embedder]) => {
|
|
254
284
|
const overall = hooksInstalled && mcpRegistered && (skipHandshake || handshake.ok) ? "ok" : "fail";
|
|
255
|
-
return { configs, hooksInstalled, mcpRegistered, datacoreCollision, staleNpxHooks, hookShim, handshake, embedder, overall };
|
|
285
|
+
return { configs, hooksInstalled, mcpRegistered, datacoreCollision, staleNpxHooks, staleNpxMcp, hookShim, mcpShim, handshake, embedder, overall };
|
|
256
286
|
});
|
|
257
287
|
}
|
|
258
288
|
function printText(report) {
|
|
@@ -294,6 +324,18 @@ function printText(report) {
|
|
|
294
324
|
outputText("\u26A0 Hooks still use npx \u2014 slow (200-2000ms per hook) and vulnerable to cache corruption.");
|
|
295
325
|
outputText(" Fix: run `plur init` to migrate to the local hook binary (<5ms per hook).");
|
|
296
326
|
}
|
|
327
|
+
if (report.mcpShim.valid) {
|
|
328
|
+
outputText(`\u2713 MCP shim: ${report.mcpShim.shimPath}`);
|
|
329
|
+
} else {
|
|
330
|
+
outputText(`\u2717 MCP shim: ${report.mcpShim.error}`);
|
|
331
|
+
}
|
|
332
|
+
if (report.staleNpxMcp) {
|
|
333
|
+
outputText("");
|
|
334
|
+
outputText("\u26A0 plur MCP still launched via npx \u2014 vulnerable to ENOTEMPTY cache corruption on version bumps (#234).");
|
|
335
|
+
outputText(" This is the same bug class as #178 (which fixed hooks). Symptom: Claude Code");
|
|
336
|
+
outputText(" sessions silently lose PLUR memory after a new @plur-ai/mcp publish.");
|
|
337
|
+
outputText(" Fix: run `plur init` to migrate to the local MCP binary (no npx, no race).");
|
|
338
|
+
}
|
|
297
339
|
outputText("");
|
|
298
340
|
if (report.handshake.ok) {
|
|
299
341
|
outputText(`\u2713 MCP handshake: ${report.handshake.serverName} v${report.handshake.serverVersion}`);
|
|
@@ -3,10 +3,11 @@ import {
|
|
|
3
3
|
} from "./chunk-O6WTH7H7.js";
|
|
4
4
|
|
|
5
5
|
// src/commands/hook-inject.ts
|
|
6
|
-
import { existsSync, writeFileSync, readFileSync, appendFileSync, mkdirSync, readSync, statSync } from "fs";
|
|
7
|
-
import {
|
|
6
|
+
import { existsSync, writeFileSync, readFileSync, appendFileSync, mkdirSync, readSync, statSync, readdirSync, unlinkSync } from "fs";
|
|
7
|
+
import { join } from "path";
|
|
8
8
|
import { tmpdir, homedir } from "os";
|
|
9
9
|
import { randomUUID } from "crypto";
|
|
10
|
+
import { readProjectConfig } from "@plur-ai/core";
|
|
10
11
|
var MAX_REMOTE_TASK_CHARS = 1e3;
|
|
11
12
|
var MAX_REMOTE_RESPONSE_BYTES = 128 * 1024;
|
|
12
13
|
var REMOTE_TIMEOUT_MS = 1500;
|
|
@@ -22,83 +23,6 @@ function logRemoteAttempt(entry) {
|
|
|
22
23
|
}
|
|
23
24
|
}
|
|
24
25
|
var REMINDER_INTERVAL_MS = 10 * 60 * 1e3;
|
|
25
|
-
function findProjectConfigPath(startDir = process.cwd()) {
|
|
26
|
-
const home = resolve(homedir());
|
|
27
|
-
let dir = resolve(startDir);
|
|
28
|
-
const MAX_DEPTH = 12;
|
|
29
|
-
for (let depth = 0; depth < MAX_DEPTH; depth++) {
|
|
30
|
-
if (dir !== home) {
|
|
31
|
-
const candidate = join(dir, ".plur.yaml");
|
|
32
|
-
if (existsSync(candidate)) return candidate;
|
|
33
|
-
}
|
|
34
|
-
if (existsSync(join(dir, ".git"))) return null;
|
|
35
|
-
if (dir === home || dir === "/" || dir === ".") return null;
|
|
36
|
-
const parent = dirname(dir);
|
|
37
|
-
if (parent === dir) return null;
|
|
38
|
-
dir = parent;
|
|
39
|
-
}
|
|
40
|
-
return null;
|
|
41
|
-
}
|
|
42
|
-
function unquoteYamlValue(v) {
|
|
43
|
-
return v.replace(/^(['"])(.*)\1$/, "$2");
|
|
44
|
-
}
|
|
45
|
-
function readProjectConfig() {
|
|
46
|
-
const configPath = findProjectConfigPath();
|
|
47
|
-
if (!configPath) return {};
|
|
48
|
-
try {
|
|
49
|
-
const content = readFileSync(configPath, "utf8").replace(/^/, "");
|
|
50
|
-
const config = {};
|
|
51
|
-
let inListKey = null;
|
|
52
|
-
let listAcc = [];
|
|
53
|
-
const finishList = () => {
|
|
54
|
-
if (inListKey === "remote_scopes") {
|
|
55
|
-
config.remote_scopes = listAcc;
|
|
56
|
-
}
|
|
57
|
-
inListKey = null;
|
|
58
|
-
listAcc = [];
|
|
59
|
-
};
|
|
60
|
-
for (const rawLine of content.split("\n")) {
|
|
61
|
-
const line = rawLine.replace(/\r$/, "");
|
|
62
|
-
const trimmed = line.trim();
|
|
63
|
-
if (trimmed.startsWith("#") || !trimmed) continue;
|
|
64
|
-
if (inListKey === "remote_scopes" && trimmed.startsWith("-")) {
|
|
65
|
-
listAcc.push(unquoteYamlValue(trimmed.slice(1).trim()));
|
|
66
|
-
continue;
|
|
67
|
-
}
|
|
68
|
-
if (inListKey) finishList();
|
|
69
|
-
const colonIdx = trimmed.indexOf(":");
|
|
70
|
-
if (colonIdx < 0) continue;
|
|
71
|
-
const key = trimmed.slice(0, colonIdx).trim();
|
|
72
|
-
const value = unquoteYamlValue(trimmed.slice(colonIdx + 1).trim());
|
|
73
|
-
switch (key) {
|
|
74
|
-
case "domain":
|
|
75
|
-
config.domain = value;
|
|
76
|
-
break;
|
|
77
|
-
case "scope":
|
|
78
|
-
config.scope = value;
|
|
79
|
-
break;
|
|
80
|
-
case "remote_url":
|
|
81
|
-
config.remote_url = value;
|
|
82
|
-
break;
|
|
83
|
-
case "remote_token":
|
|
84
|
-
config.remote_token = value;
|
|
85
|
-
break;
|
|
86
|
-
case "remote_scopes":
|
|
87
|
-
if (value === "" || value === "|" || value === ">") {
|
|
88
|
-
inListKey = "remote_scopes";
|
|
89
|
-
listAcc = [];
|
|
90
|
-
} else {
|
|
91
|
-
config.remote_scopes = value.split(",").map((s) => unquoteYamlValue(s.trim())).filter(Boolean);
|
|
92
|
-
}
|
|
93
|
-
break;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
finishList();
|
|
97
|
-
return config;
|
|
98
|
-
} catch {
|
|
99
|
-
return {};
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
26
|
async function tryRemoteInject(config, task) {
|
|
103
27
|
if (!config.remote_url || !config.remote_token) return null;
|
|
104
28
|
const startTs = Date.now();
|
|
@@ -228,6 +152,44 @@ function extractEventTask(input, event) {
|
|
|
228
152
|
return "";
|
|
229
153
|
}
|
|
230
154
|
}
|
|
155
|
+
function processDeferredWrapups() {
|
|
156
|
+
const plurDir = process.env.PLUR_PATH ?? join(homedir(), ".plur");
|
|
157
|
+
const sessionsDir = join(plurDir, "sessions");
|
|
158
|
+
if (!existsSync(sessionsDir)) return null;
|
|
159
|
+
const notices = [];
|
|
160
|
+
try {
|
|
161
|
+
const files = readdirSync(sessionsDir).filter((f) => f.endsWith(".checkpoint.json"));
|
|
162
|
+
const now = Date.now();
|
|
163
|
+
const staleMin = parseInt(process.env.PLUR_CHECKPOINT_STALE_MIN ?? "5", 10);
|
|
164
|
+
const STALE_THRESHOLD_MS = Math.max(1, staleMin) * 60 * 1e3;
|
|
165
|
+
for (const file of files) {
|
|
166
|
+
const path = join(sessionsDir, file);
|
|
167
|
+
try {
|
|
168
|
+
const checkpoint = JSON.parse(readFileSync(path, "utf8"));
|
|
169
|
+
const lastCheckpoint = new Date(checkpoint.last_checkpoint).getTime();
|
|
170
|
+
if (now - lastCheckpoint < STALE_THRESHOLD_MS) continue;
|
|
171
|
+
const started = new Date(checkpoint.started_at);
|
|
172
|
+
const ended = new Date(checkpoint.last_checkpoint);
|
|
173
|
+
const durationMin = Math.round((ended.getTime() - started.getTime()) / 6e4);
|
|
174
|
+
const durationStr = durationMin >= 60 ? `${Math.floor(durationMin / 60)}h ${durationMin % 60}m` : `${durationMin}m`;
|
|
175
|
+
notices.push(
|
|
176
|
+
`Previous session (${durationStr}, ${checkpoint.stop_count} responses${checkpoint.cwd ? ", " + checkpoint.cwd.split("/").slice(-2).join("/") : ""}) ended without wrap-up.`
|
|
177
|
+
);
|
|
178
|
+
unlinkSync(path);
|
|
179
|
+
} catch {
|
|
180
|
+
try {
|
|
181
|
+
unlinkSync(path);
|
|
182
|
+
} catch {
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
} catch {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
if (notices.length === 0) return null;
|
|
190
|
+
return `[PLUR] ${notices.join(" ")}
|
|
191
|
+
Consider running plur_session_end with engram_suggestions when this session ends.`;
|
|
192
|
+
}
|
|
231
193
|
async function run(args, flags) {
|
|
232
194
|
const isRehydrate = args.includes("--rehydrate");
|
|
233
195
|
const eventIdx = args.indexOf("--event");
|
|
@@ -359,6 +321,8 @@ ${parts2.join("\n")}` };
|
|
|
359
321
|
if (sessionId) parts.push(`Session ID: ${sessionId}`);
|
|
360
322
|
if (projectConfig.domain) parts.push(`Project domain: ${projectConfig.domain}`);
|
|
361
323
|
if (projectConfig.scope) parts.push(`Project scope: ${projectConfig.scope} \u2014 use this scope for plur_learn calls`);
|
|
324
|
+
const deferredNotice = processDeferredWrapups();
|
|
325
|
+
if (deferredNotice) parts.push("", deferredNotice);
|
|
362
326
|
}
|
|
363
327
|
if (context) {
|
|
364
328
|
parts.push("");
|
|
@@ -1,14 +1,47 @@
|
|
|
1
1
|
// src/commands/hook-learn-check.ts
|
|
2
2
|
import { readSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
3
3
|
import { join } from "path";
|
|
4
|
-
import { tmpdir } from "os";
|
|
5
|
-
var
|
|
4
|
+
import { tmpdir, homedir } from "os";
|
|
5
|
+
var LEARN_INTERVAL = 3;
|
|
6
|
+
var CHECKPOINT_INTERVAL = parseInt(process.env.PLUR_CHECKPOINT_INTERVAL || "10", 10);
|
|
7
|
+
function sessionKey() {
|
|
8
|
+
const raw = process.env.CLAUDE_SESSION_ID || String(process.ppid || "unknown");
|
|
9
|
+
return raw.replace(/[^a-zA-Z0-9_-]/g, "").slice(0, 64) || "default";
|
|
10
|
+
}
|
|
6
11
|
function counterPath() {
|
|
7
12
|
const dir = join(tmpdir(), "plur-sessions");
|
|
8
13
|
mkdirSync(dir, { recursive: true });
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
14
|
+
return join(dir, `${sessionKey()}.stop-count`);
|
|
15
|
+
}
|
|
16
|
+
function plurPath() {
|
|
17
|
+
return process.env.PLUR_PATH ?? join(homedir(), ".plur");
|
|
18
|
+
}
|
|
19
|
+
function checkpointDir() {
|
|
20
|
+
const dir = join(plurPath(), "sessions");
|
|
21
|
+
mkdirSync(dir, { recursive: true });
|
|
22
|
+
return dir;
|
|
23
|
+
}
|
|
24
|
+
function writeCheckpoint(count, cwd) {
|
|
25
|
+
const id = sessionKey();
|
|
26
|
+
const dir = checkpointDir();
|
|
27
|
+
const path = join(dir, `${id}.checkpoint.json`);
|
|
28
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
29
|
+
const dateStr = now.slice(0, 10);
|
|
30
|
+
let startedAt = now;
|
|
31
|
+
try {
|
|
32
|
+
const existing = JSON.parse(readFileSync(path, "utf8"));
|
|
33
|
+
if (existing.started_at) startedAt = existing.started_at;
|
|
34
|
+
} catch {
|
|
35
|
+
}
|
|
36
|
+
const checkpoint = {
|
|
37
|
+
session_id: id,
|
|
38
|
+
started_at: startedAt,
|
|
39
|
+
last_checkpoint: now,
|
|
40
|
+
stop_count: count,
|
|
41
|
+
cwd,
|
|
42
|
+
observation_file: `${dateStr}.jsonl`
|
|
43
|
+
};
|
|
44
|
+
writeFileSync(path, JSON.stringify(checkpoint, null, 2) + "\n");
|
|
12
45
|
}
|
|
13
46
|
function readStdinRaw() {
|
|
14
47
|
try {
|
|
@@ -31,6 +64,12 @@ function readStdinRaw() {
|
|
|
31
64
|
var LEARN_PROMPT = `[PLUR] Did you discover, learn, or get corrected on something in your last response? If yes \u2014 call plur_learn now before moving on. If no \u2014 continue.`;
|
|
32
65
|
async function run(_args, _flags) {
|
|
33
66
|
const raw = readStdinRaw();
|
|
67
|
+
let cwd = process.cwd();
|
|
68
|
+
try {
|
|
69
|
+
const data = JSON.parse(raw);
|
|
70
|
+
if (data.cwd) cwd = data.cwd;
|
|
71
|
+
} catch {
|
|
72
|
+
}
|
|
34
73
|
const cPath = counterPath();
|
|
35
74
|
let count = 1;
|
|
36
75
|
try {
|
|
@@ -42,7 +81,13 @@ async function run(_args, _flags) {
|
|
|
42
81
|
writeFileSync(cPath, String(count));
|
|
43
82
|
} catch {
|
|
44
83
|
}
|
|
45
|
-
if (count %
|
|
84
|
+
if (count % CHECKPOINT_INTERVAL === 0) {
|
|
85
|
+
try {
|
|
86
|
+
writeCheckpoint(count, cwd);
|
|
87
|
+
} catch {
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (count % LEARN_INTERVAL !== 0) {
|
|
46
91
|
process.stdout.write(raw);
|
|
47
92
|
return;
|
|
48
93
|
}
|
package/dist/commands/init.js
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
mergePlurMcp,
|
|
6
6
|
readConfig,
|
|
7
7
|
writeConfig
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-OAIEWP3Q.js";
|
|
9
9
|
import {
|
|
10
10
|
outputText
|
|
11
11
|
} from "./chunk-7U4W4J3G.js";
|
|
@@ -49,6 +49,51 @@ exec "${nodeBin}" "${entrypoint}" "$@"
|
|
|
49
49
|
writeFileSync(join(binDir, "plur-hook.meta.json"), JSON.stringify(meta, null, 2) + "\n");
|
|
50
50
|
return { shimPath: target, status: "installed" };
|
|
51
51
|
}
|
|
52
|
+
function resolveMcpEntrypoint() {
|
|
53
|
+
const cliEntry = resolveCliEntrypoint();
|
|
54
|
+
let dir = dirname(cliEntry);
|
|
55
|
+
const MAX_DEPTH = 12;
|
|
56
|
+
for (let depth = 0; depth < MAX_DEPTH; depth++) {
|
|
57
|
+
const candidate = join(dir, "node_modules", "@plur-ai", "mcp", "dist", "index.js");
|
|
58
|
+
if (existsSync(candidate)) return candidate;
|
|
59
|
+
const adjacent = join(dir, "..", "@plur-ai", "mcp", "dist", "index.js");
|
|
60
|
+
if (existsSync(adjacent)) return adjacent;
|
|
61
|
+
const parent = dirname(dir);
|
|
62
|
+
if (parent === dir) break;
|
|
63
|
+
dir = parent;
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
function mcpShimPath() {
|
|
68
|
+
const name = platform() === "win32" ? "plur-mcp.cmd" : "plur-mcp";
|
|
69
|
+
return join(homedir(), ".plur", "bin", name);
|
|
70
|
+
}
|
|
71
|
+
function installMcpBinary() {
|
|
72
|
+
const binDir = join(homedir(), ".plur", "bin");
|
|
73
|
+
mkdirSync(binDir, { recursive: true });
|
|
74
|
+
const entrypoint = resolveMcpEntrypoint();
|
|
75
|
+
const nodeBin = process.execPath;
|
|
76
|
+
if (!entrypoint) {
|
|
77
|
+
return { shimPath: "", status: "skipped: @plur-ai/mcp not installed alongside CLI" };
|
|
78
|
+
}
|
|
79
|
+
const target = mcpShimPath();
|
|
80
|
+
if (platform() === "win32") {
|
|
81
|
+
writeFileSync(target, `@echo off\r
|
|
82
|
+
"${nodeBin}" "${entrypoint}" %*\r
|
|
83
|
+
`);
|
|
84
|
+
} else {
|
|
85
|
+
writeFileSync(target, `#!/bin/sh
|
|
86
|
+
exec "${nodeBin}" "${entrypoint}" "$@"
|
|
87
|
+
`, { mode: 493 });
|
|
88
|
+
try {
|
|
89
|
+
chmodSync(target, 493);
|
|
90
|
+
} catch {
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const meta = { entrypoint, node: nodeBin, installed: (/* @__PURE__ */ new Date()).toISOString() };
|
|
94
|
+
writeFileSync(join(binDir, "plur-mcp.meta.json"), JSON.stringify(meta, null, 2) + "\n");
|
|
95
|
+
return { shimPath: target, status: "installed" };
|
|
96
|
+
}
|
|
52
97
|
function buildEnforcementHooks(cmd) {
|
|
53
98
|
return {
|
|
54
99
|
SessionStart: [
|
|
@@ -320,6 +365,7 @@ function hooksStatusFor(before, after, hadHooks) {
|
|
|
320
365
|
async function run(args, flags) {
|
|
321
366
|
const shim = installHookBinary();
|
|
322
367
|
const cmd = shim.shimPath || "npx @plur-ai/cli";
|
|
368
|
+
const mcpShim = installMcpBinary();
|
|
323
369
|
const PLUR_HOOKS_ENFORCEMENT = buildEnforcementHooks(cmd);
|
|
324
370
|
const PLUR_HOOKS_INJECTION = buildInjectionHooks(cmd);
|
|
325
371
|
const injectionPath = findSettingsPath(flags, args);
|
|
@@ -375,6 +421,7 @@ async function run(args, flags) {
|
|
|
375
421
|
outputText("PLUR installed for Claude Code.");
|
|
376
422
|
outputText("");
|
|
377
423
|
outputText(`Hook binary: ${shim.status}${shim.shimPath ? ` (${shim.shimPath})` : ""}`);
|
|
424
|
+
outputText(`MCP binary: ${mcpShim.status}${mcpShim.shimPath ? ` (${mcpShim.shimPath})` : ""}`);
|
|
378
425
|
outputText("");
|
|
379
426
|
outputText("Architecture: One global engram store (~/.plur/), enforcement hooks global, injection hooks project-scoped.");
|
|
380
427
|
outputText("Multi-project scoping via domain/scope fields on engrams, not separate installs.");
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plur-ai/cli",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.12",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"plur": "dist/index.js"
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"dist"
|
|
11
11
|
],
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@plur-ai/core": "0.9.
|
|
13
|
+
"@plur-ai/core": "0.9.12"
|
|
14
14
|
},
|
|
15
15
|
"devDependencies": {
|
|
16
16
|
"@types/node": "^25.5.0"
|