@pyxmate/memory 1.23.0 → 1.23.1
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/dist/cli/pyx-mem.mjs +112 -34
- package/package.json +1 -1
package/dist/cli/pyx-mem.mjs
CHANGED
|
@@ -2223,7 +2223,7 @@ function createProxyServer(client, version, uploadLocalFile) {
|
|
|
2223
2223
|
return server;
|
|
2224
2224
|
}
|
|
2225
2225
|
async function runMcpProxyServer(opts) {
|
|
2226
|
-
const version = opts.version ?? (true ? "1.23.
|
|
2226
|
+
const version = opts.version ?? (true ? "1.23.1" : "0.0.0-dev");
|
|
2227
2227
|
const read = await opts.readCredentials();
|
|
2228
2228
|
if (!read.ok) {
|
|
2229
2229
|
throw new Error(
|
|
@@ -2269,7 +2269,7 @@ async function runMcpProxyServer(opts) {
|
|
|
2269
2269
|
|
|
2270
2270
|
// src/cli/commands/mcp-install.ts
|
|
2271
2271
|
import { spawnSync as nodeSpawnSync } from "child_process";
|
|
2272
|
-
import { existsSync as existsSync3
|
|
2272
|
+
import { existsSync as existsSync3 } from "fs";
|
|
2273
2273
|
import { homedir as homedir2 } from "os";
|
|
2274
2274
|
import { join as join2 } from "path";
|
|
2275
2275
|
|
|
@@ -2389,7 +2389,20 @@ function indent(text) {
|
|
|
2389
2389
|
}
|
|
2390
2390
|
|
|
2391
2391
|
// src/cli/commands/memory-rules-merge.ts
|
|
2392
|
-
import {
|
|
2392
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
2393
|
+
import {
|
|
2394
|
+
chmodSync,
|
|
2395
|
+
existsSync as existsSync2,
|
|
2396
|
+
linkSync,
|
|
2397
|
+
lstatSync,
|
|
2398
|
+
mkdirSync as mkdirSync2,
|
|
2399
|
+
readFileSync as readFileSync2,
|
|
2400
|
+
realpathSync,
|
|
2401
|
+
renameSync as renameSync2,
|
|
2402
|
+
rmSync,
|
|
2403
|
+
statSync,
|
|
2404
|
+
writeFileSync as writeFileSync2
|
|
2405
|
+
} from "fs";
|
|
2393
2406
|
import { dirname as dirname3 } from "path";
|
|
2394
2407
|
var RULES_BEGIN_MARKER = "<!-- pyx-memory:begin (managed by `pyx-mem mcp install`; edit outside these markers) -->";
|
|
2395
2408
|
var RULES_END_MARKER = "<!-- pyx-memory:end -->";
|
|
@@ -2397,6 +2410,15 @@ var BEGIN_MARKER_PREFIX = "<!-- pyx-memory:begin";
|
|
|
2397
2410
|
var PM_HEADING_RE = /^ {0,3}##[ \t]+Persistent Memory[ \t]*$/;
|
|
2398
2411
|
var H1_OR_H2_RE = /^ {0,3}#{1,2}[ \t]+\S/;
|
|
2399
2412
|
var FENCE_RE = /^ {0,3}(`{3,}|~{3,})/;
|
|
2413
|
+
var CLOSING_FENCE_RE = /^ {0,3}(`{3,}|~{3,})[ \t]*$/;
|
|
2414
|
+
var BEGIN_MARKER_RE = /^ {0,3}<!-- pyx-memory:begin(?:[ \t][^>]*)?-->[ \t]*$/;
|
|
2415
|
+
var END_MARKER_RE = /^ {0,3}<!-- pyx-memory:end -->[ \t]*$/;
|
|
2416
|
+
function isBeginMarker(line) {
|
|
2417
|
+
return BEGIN_MARKER_RE.test(line);
|
|
2418
|
+
}
|
|
2419
|
+
function isEndMarker(line) {
|
|
2420
|
+
return END_MARKER_RE.test(line);
|
|
2421
|
+
}
|
|
2400
2422
|
function fenceMask(lines) {
|
|
2401
2423
|
const mask = new Array(lines.length).fill(false);
|
|
2402
2424
|
let openIdx = -1;
|
|
@@ -2411,7 +2433,7 @@ function fenceMask(lines) {
|
|
|
2411
2433
|
openLen = fence.length;
|
|
2412
2434
|
continue;
|
|
2413
2435
|
}
|
|
2414
|
-
if (fence.charAt(0) === delim && fence.length >= openLen) {
|
|
2436
|
+
if (CLOSING_FENCE_RE.test(lines[i] ?? "") && fence.charAt(0) === delim && fence.length >= openLen) {
|
|
2415
2437
|
for (let j = openIdx; j <= i; j++) mask[j] = true;
|
|
2416
2438
|
openIdx = -1;
|
|
2417
2439
|
delim = "";
|
|
@@ -2432,36 +2454,29 @@ function findCompleteManagedRange(lines, mask) {
|
|
|
2432
2454
|
for (let i = 0; i < lines.length; i++) {
|
|
2433
2455
|
const line = lines[i];
|
|
2434
2456
|
if (line === void 0 || mask[i]) continue;
|
|
2435
|
-
|
|
2436
|
-
if (begin
|
|
2437
|
-
if (t.startsWith(BEGIN_MARKER_PREFIX)) begin = i;
|
|
2438
|
-
} else if (t === RULES_END_MARKER) {
|
|
2439
|
-
return [begin, i + 1];
|
|
2440
|
-
}
|
|
2457
|
+
if (isBeginMarker(line)) begin = i;
|
|
2458
|
+
else if (begin !== -1 && isEndMarker(line)) return [begin, i + 1];
|
|
2441
2459
|
}
|
|
2442
2460
|
return null;
|
|
2443
2461
|
}
|
|
2444
|
-
function
|
|
2462
|
+
function findBeginLines(lines, mask) {
|
|
2463
|
+
const found = [];
|
|
2445
2464
|
for (let i = 0; i < lines.length; i++) {
|
|
2446
2465
|
const line = lines[i];
|
|
2447
|
-
if (line !== void 0 && !mask[i] && line
|
|
2466
|
+
if (line !== void 0 && !mask[i] && isBeginMarker(line)) found.push(i);
|
|
2448
2467
|
}
|
|
2449
|
-
return
|
|
2468
|
+
return found;
|
|
2450
2469
|
}
|
|
2451
2470
|
function findLegacyPyxRange(lines, mask) {
|
|
2452
2471
|
for (let i = 0; i < lines.length; i++) {
|
|
2453
2472
|
const line = lines[i];
|
|
2454
2473
|
if (line === void 0 || mask[i] || !PM_HEADING_RE.test(line)) continue;
|
|
2455
2474
|
const end = nextH1OrH2(lines, mask, i + 1);
|
|
2456
|
-
|
|
2475
|
+
const own = lines.slice(i, end).filter((l, k) => !mask[i + k] && !isBeginMarker(l) && !isEndMarker(l));
|
|
2476
|
+
if (own.join("\n").toLowerCase().includes("pyx-memory")) return [i, end];
|
|
2457
2477
|
}
|
|
2458
2478
|
return null;
|
|
2459
2479
|
}
|
|
2460
|
-
function hasPyxMemoryRules(content) {
|
|
2461
|
-
const lines = content.replace(/\r\n/g, "\n").split("\n");
|
|
2462
|
-
const mask = fenceMask(lines);
|
|
2463
|
-
return findCompleteManagedRange(lines, mask) !== null || findOrphanBeginLine(lines, mask) !== -1 || findLegacyPyxRange(lines, mask) !== null;
|
|
2464
|
-
}
|
|
2465
2480
|
function spliceBlock(lines, from, toExclusive, blockLines) {
|
|
2466
2481
|
const before = lines.slice(0, from);
|
|
2467
2482
|
const after = lines.slice(toExclusive);
|
|
@@ -2478,7 +2493,13 @@ function joinWithTrailingNewline(lines, eol) {
|
|
|
2478
2493
|
const body = lines.join(eol);
|
|
2479
2494
|
return body.endsWith(eol) ? body : `${body}${eol}`;
|
|
2480
2495
|
}
|
|
2496
|
+
var BOM = "\uFEFF";
|
|
2481
2497
|
function mergeMemoryRules(existing, section) {
|
|
2498
|
+
const bom = existing.startsWith(BOM) ? BOM : "";
|
|
2499
|
+
const merged = mergeBody(existing.slice(bom.length), section);
|
|
2500
|
+
return { ...merged, content: bom + merged.content };
|
|
2501
|
+
}
|
|
2502
|
+
function mergeBody(existing, section) {
|
|
2482
2503
|
if (section.includes(BEGIN_MARKER_PREFIX) || section.includes(RULES_END_MARKER)) {
|
|
2483
2504
|
throw new Error("pyx-memory rules section must not contain the managed markers.");
|
|
2484
2505
|
}
|
|
@@ -2495,9 +2516,9 @@ function mergeMemoryRules(existing, section) {
|
|
|
2495
2516
|
const next2 = spliceBlock(lines, complete[0], complete[1], blockLines);
|
|
2496
2517
|
return { content: joinWithTrailingNewline(next2, eol), action: "updated" };
|
|
2497
2518
|
}
|
|
2498
|
-
const
|
|
2499
|
-
if (
|
|
2500
|
-
lines =
|
|
2519
|
+
const orphans = new Set(findBeginLines(lines, mask));
|
|
2520
|
+
if (orphans.size > 0) {
|
|
2521
|
+
lines = lines.filter((_, i) => !orphans.has(i));
|
|
2501
2522
|
mask = fenceMask(lines);
|
|
2502
2523
|
}
|
|
2503
2524
|
const legacy = findLegacyPyxRange(lines, mask);
|
|
@@ -2508,12 +2529,53 @@ function mergeMemoryRules(existing, section) {
|
|
|
2508
2529
|
const next = spliceBlock(lines, lines.length, lines.length, blockLines);
|
|
2509
2530
|
return { content: joinWithTrailingNewline(next, eol), action: "appended" };
|
|
2510
2531
|
}
|
|
2532
|
+
function refreshMemoryRules(file, section) {
|
|
2533
|
+
const bom = file.startsWith(BOM) ? BOM : "";
|
|
2534
|
+
const refreshed = refreshBody(file.slice(bom.length), section);
|
|
2535
|
+
return refreshed === null ? null : bom + refreshed;
|
|
2536
|
+
}
|
|
2537
|
+
function refreshBody(existing, section) {
|
|
2538
|
+
const lines = existing.replace(/\r\n/g, "\n").split("\n");
|
|
2539
|
+
const complete = findCompleteManagedRange(lines, fenceMask(lines));
|
|
2540
|
+
if (!complete) return null;
|
|
2541
|
+
const [first, last] = [complete[0], complete[1] - 1];
|
|
2542
|
+
const starts = [0];
|
|
2543
|
+
for (let i = 0; i < existing.length; i++) if (existing[i] === "\n") starts.push(i + 1);
|
|
2544
|
+
const from = starts[first] ?? 0;
|
|
2545
|
+
const lastStart = starts[last] ?? 0;
|
|
2546
|
+
const to = lastStart + (lines[last] ?? "").length;
|
|
2547
|
+
const eol = existing.slice(from).match(/\r?\n/)?.[0] ?? "\n";
|
|
2548
|
+
const block = [RULES_BEGIN_MARKER, ...section.split(/\r?\n/), RULES_END_MARKER].join(eol);
|
|
2549
|
+
return existing.slice(0, from) + block + existing.slice(to);
|
|
2550
|
+
}
|
|
2551
|
+
function changedDuringWrite(filePath) {
|
|
2552
|
+
return new Error(`${filePath} changed while updating the pyx-memory rules; retry.`);
|
|
2553
|
+
}
|
|
2554
|
+
function readUtf8Strict(path) {
|
|
2555
|
+
try {
|
|
2556
|
+
return new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(readFileSync2(path));
|
|
2557
|
+
} catch (err) {
|
|
2558
|
+
if (err instanceof TypeError) {
|
|
2559
|
+
throw new Error(`${path} is not valid UTF-8; the pyx-memory rules were not changed.`);
|
|
2560
|
+
}
|
|
2561
|
+
throw err;
|
|
2562
|
+
}
|
|
2563
|
+
}
|
|
2511
2564
|
function writeMemoryRulesFile(opts) {
|
|
2512
2565
|
const { filePath, section } = opts;
|
|
2513
2566
|
const fileExists = existsSync2(filePath);
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2567
|
+
if (!fileExists && lstatSync(filePath, { throwIfNoEntry: false })?.isSymbolicLink()) {
|
|
2568
|
+
throw new Error(`${filePath} is a broken link; fix or remove it, then retry.`);
|
|
2569
|
+
}
|
|
2570
|
+
const target = fileExists ? realpathSync(filePath) : filePath;
|
|
2571
|
+
const existing = fileExists ? readUtf8Strict(target) : "";
|
|
2572
|
+
let merged = null;
|
|
2573
|
+
if (!opts.refreshOnly) merged = mergeMemoryRules(existing, section);
|
|
2574
|
+
else {
|
|
2575
|
+
const content = refreshMemoryRules(existing, section);
|
|
2576
|
+
if (content !== null) merged = { content, action: "updated" };
|
|
2577
|
+
}
|
|
2578
|
+
if (merged === null || merged.content === existing) {
|
|
2517
2579
|
return {
|
|
2518
2580
|
action: "unchanged",
|
|
2519
2581
|
filePath,
|
|
@@ -2521,15 +2583,32 @@ function writeMemoryRulesFile(opts) {
|
|
|
2521
2583
|
`
|
|
2522
2584
|
};
|
|
2523
2585
|
}
|
|
2524
|
-
const parent = dirname3(
|
|
2586
|
+
const parent = dirname3(target);
|
|
2525
2587
|
if (!existsSync2(parent)) mkdirSync2(parent, { recursive: true });
|
|
2526
|
-
const mode = fileExists ? statSync(
|
|
2527
|
-
const tmpPath = `${
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2588
|
+
const mode = fileExists ? statSync(target).mode & 511 : 420;
|
|
2589
|
+
const tmpPath = `${target}.${process.pid}.${randomUUID4()}.tmp`;
|
|
2590
|
+
try {
|
|
2591
|
+
writeFileSync2(tmpPath, merged.content, { mode });
|
|
2592
|
+
chmodSync(tmpPath, mode);
|
|
2593
|
+
if (fileExists) {
|
|
2594
|
+
if (readUtf8Strict(target) !== existing) throw changedDuringWrite(filePath);
|
|
2595
|
+
renameSync2(tmpPath, target);
|
|
2596
|
+
} else {
|
|
2597
|
+
try {
|
|
2598
|
+
linkSync(tmpPath, target);
|
|
2599
|
+
} catch (err) {
|
|
2600
|
+
if (err.code === "EEXIST") throw changedDuringWrite(filePath);
|
|
2601
|
+
throw err;
|
|
2602
|
+
}
|
|
2603
|
+
rmSync(tmpPath);
|
|
2604
|
+
}
|
|
2605
|
+
} catch (err) {
|
|
2606
|
+
rmSync(tmpPath, { force: true });
|
|
2607
|
+
throw err;
|
|
2608
|
+
}
|
|
2609
|
+
const verb = merged.action === "created" ? "Wrote" : merged.action === "appended" ? "Added" : "Updated";
|
|
2531
2610
|
return {
|
|
2532
|
-
action,
|
|
2611
|
+
action: merged.action,
|
|
2533
2612
|
filePath,
|
|
2534
2613
|
message: `${verb} the pyx-memory rules in ${filePath}.
|
|
2535
2614
|
`
|
|
@@ -2894,8 +2973,7 @@ function reconcileMemoryRules(opts = {}) {
|
|
|
2894
2973
|
const filePath = rulesFilePath(target, opts);
|
|
2895
2974
|
try {
|
|
2896
2975
|
if (!existsSync3(filePath)) continue;
|
|
2897
|
-
|
|
2898
|
-
const result = writeMemoryRulesFile({ filePath, section });
|
|
2976
|
+
const result = writeMemoryRulesFile({ filePath, section, refreshOnly: true });
|
|
2899
2977
|
if (result.action !== "unchanged") {
|
|
2900
2978
|
process.stderr.write(
|
|
2901
2979
|
`pyx-memory: refreshed the "## Persistent Memory" rules in ${filePath}
|