@echomem/mcp 1.4.45 → 1.4.46
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/setup.js +116 -4
- package/package.json +1 -1
package/dist/setup.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import http from "node:http";
|
|
17
17
|
import { randomUUID } from "node:crypto";
|
|
18
|
-
import { execFileSync, spawn } from "node:child_process";
|
|
18
|
+
import { execFileSync, spawn, spawnSync, } from "node:child_process";
|
|
19
19
|
import { Worker } from "node:worker_threads";
|
|
20
20
|
import fs from "node:fs";
|
|
21
21
|
import os from "node:os";
|
|
@@ -505,6 +505,118 @@ function claudeCodeLocalEchoMemProjects(configPath) {
|
|
|
505
505
|
.map(([projectPath]) => projectPath)
|
|
506
506
|
.sort();
|
|
507
507
|
}
|
|
508
|
+
function versionedClaudeCodeExecutables(root, executable) {
|
|
509
|
+
let versions;
|
|
510
|
+
try {
|
|
511
|
+
versions = fs.readdirSync(root, { withFileTypes: true });
|
|
512
|
+
}
|
|
513
|
+
catch {
|
|
514
|
+
return [];
|
|
515
|
+
}
|
|
516
|
+
return versions
|
|
517
|
+
.filter((entry) => entry.isDirectory())
|
|
518
|
+
.sort((left, right) => right.name.localeCompare(left.name, undefined, { numeric: true }))
|
|
519
|
+
.map((entry) => path.join(root, entry.name, executable))
|
|
520
|
+
.filter((candidate) => fs.existsSync(candidate));
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* Return every safe Claude Code launcher location worth trying. Claude Desktop bundles the CLI,
|
|
524
|
+
* but Windows Store/MSIX installs expose its real files below the package LocalCache instead of
|
|
525
|
+
* the caller's ordinary APPDATA/PATH view.
|
|
526
|
+
*/
|
|
527
|
+
export function claudeCodeCommandCandidates(options = {}) {
|
|
528
|
+
const platform = options.platform ?? process.platform;
|
|
529
|
+
const env = options.env ?? process.env;
|
|
530
|
+
const homeDir = options.homeDir ?? os.homedir();
|
|
531
|
+
const candidates = [];
|
|
532
|
+
const configured = env.CLAUDE_CODE_EXECUTABLE?.trim();
|
|
533
|
+
if (configured)
|
|
534
|
+
candidates.push(configured);
|
|
535
|
+
candidates.push("claude");
|
|
536
|
+
if (platform === "win32") {
|
|
537
|
+
const appData = env.APPDATA?.trim() || path.join(homeDir, "AppData", "Roaming");
|
|
538
|
+
const localAppData = env.LOCALAPPDATA?.trim() || path.join(homeDir, "AppData", "Local");
|
|
539
|
+
candidates.push(...versionedClaudeCodeExecutables(path.join(appData, "Claude", "claude-code"), "claude.exe"), ...versionedClaudeCodeExecutables(path.join(localAppData, "Claude", "claude-code"), "claude.exe"));
|
|
540
|
+
const packagesRoot = path.join(localAppData, "Packages");
|
|
541
|
+
try {
|
|
542
|
+
for (const entry of fs.readdirSync(packagesRoot, { withFileTypes: true })) {
|
|
543
|
+
if (!entry.isDirectory() || !/^Claude_/i.test(entry.name))
|
|
544
|
+
continue;
|
|
545
|
+
candidates.push(...versionedClaudeCodeExecutables(path.join(packagesRoot, entry.name, "LocalCache", "Roaming", "Claude", "claude-code"), "claude.exe"));
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
catch {
|
|
549
|
+
/* A non-Store install has no Packages directory. */
|
|
550
|
+
}
|
|
551
|
+
const executionAlias = path.join(localAppData, "Microsoft", "WindowsApps", "claude.exe");
|
|
552
|
+
if (fs.existsSync(executionAlias))
|
|
553
|
+
candidates.push(executionAlias);
|
|
554
|
+
}
|
|
555
|
+
else if (platform === "darwin") {
|
|
556
|
+
for (const candidate of [
|
|
557
|
+
"/Applications/Claude.app/Contents/Resources/claude",
|
|
558
|
+
path.join(homeDir, "Applications", "Claude.app", "Contents", "Resources", "claude"),
|
|
559
|
+
"/opt/homebrew/bin/claude",
|
|
560
|
+
"/usr/local/bin/claude",
|
|
561
|
+
]) {
|
|
562
|
+
if (fs.existsSync(candidate))
|
|
563
|
+
candidates.push(candidate);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
return [...new Set(candidates)];
|
|
567
|
+
}
|
|
568
|
+
function escapeWindowsCmdCommand(value) {
|
|
569
|
+
return value.replace(/([()\][%!^"`<>&|;, *?])/g, "^$1");
|
|
570
|
+
}
|
|
571
|
+
function escapeWindowsCmdArgument(value) {
|
|
572
|
+
let escaped = value
|
|
573
|
+
.replace(/(?=(\\+?)?)\1"/g, "$1$1\\\"")
|
|
574
|
+
.replace(/(?=(\\+?)?)\1$/g, "$1$1");
|
|
575
|
+
escaped = `"${escaped}"`;
|
|
576
|
+
return escaped.replace(/([()\][%!^"`<>&|;, *?])/g, "^$1");
|
|
577
|
+
}
|
|
578
|
+
function execClaudeCodeSync(args, options, commandCandidates) {
|
|
579
|
+
let candidates = commandCandidates ? [...commandCandidates] : claudeCodeCommandCandidates();
|
|
580
|
+
if (process.platform === "win32" && !commandCandidates) {
|
|
581
|
+
try {
|
|
582
|
+
const pathMatches = execFileSync("where.exe", ["claude"], {
|
|
583
|
+
encoding: "utf8",
|
|
584
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
585
|
+
timeout: 3000,
|
|
586
|
+
windowsHide: true,
|
|
587
|
+
}).split(/\r?\n/).map((candidate) => candidate.trim()).filter(Boolean);
|
|
588
|
+
candidates = [...new Set([...pathMatches, ...candidates])];
|
|
589
|
+
}
|
|
590
|
+
catch {
|
|
591
|
+
/* The bundled Desktop candidates below remain available. */
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
let lastError = new Error("Claude Code CLI was not found.");
|
|
595
|
+
for (const command of candidates) {
|
|
596
|
+
try {
|
|
597
|
+
if (process.platform === "win32" && /\.(cmd|bat)$/i.test(command)) {
|
|
598
|
+
const shellCommand = [escapeWindowsCmdCommand(command), ...args.map(escapeWindowsCmdArgument)].join(" ");
|
|
599
|
+
const spawnOptions = {
|
|
600
|
+
...options,
|
|
601
|
+
windowsHide: true,
|
|
602
|
+
windowsVerbatimArguments: true,
|
|
603
|
+
};
|
|
604
|
+
const result = spawnSync(process.env.ComSpec || "cmd.exe", ["/d", "/s", "/c", `"${shellCommand}"`], spawnOptions);
|
|
605
|
+
if (result.error)
|
|
606
|
+
throw result.error;
|
|
607
|
+
if (result.status !== 0) {
|
|
608
|
+
throw new Error(result.stderr?.trim() || `Claude Code exited with status ${result.status ?? "unknown"}.`);
|
|
609
|
+
}
|
|
610
|
+
return result.stdout || "";
|
|
611
|
+
}
|
|
612
|
+
return execFileSync(command, args, process.platform === "win32" ? { ...options, windowsHide: true } : options);
|
|
613
|
+
}
|
|
614
|
+
catch (error) {
|
|
615
|
+
lastError = error;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
throw lastError;
|
|
619
|
+
}
|
|
508
620
|
export function writeClaudeCodeConfig(entry, options = {}) {
|
|
509
621
|
// EchoMem belongs at user scope so every Claude Code project resolves the same durable runtime.
|
|
510
622
|
// Older CLI versions wrote local/project entries, which take precedence over user scope and can
|
|
@@ -520,12 +632,12 @@ export function writeClaudeCodeConfig(entry, options = {}) {
|
|
|
520
632
|
});
|
|
521
633
|
const runClaude = (args, cwd) => {
|
|
522
634
|
try {
|
|
523
|
-
|
|
635
|
+
execClaudeCodeSync(args, {
|
|
524
636
|
cwd,
|
|
525
637
|
encoding: "utf8",
|
|
526
638
|
stdio: ["ignore", "pipe", "pipe"],
|
|
527
639
|
timeout: 10000,
|
|
528
|
-
});
|
|
640
|
+
}, options.claudeCommands);
|
|
529
641
|
return true;
|
|
530
642
|
}
|
|
531
643
|
catch {
|
|
@@ -765,7 +877,7 @@ function inspectClaudeCodeConfig(client, desiredVersion) {
|
|
|
765
877
|
if (!fs.existsSync(home(".claude")))
|
|
766
878
|
return null;
|
|
767
879
|
try {
|
|
768
|
-
const output =
|
|
880
|
+
const output = execClaudeCodeSync(["mcp", "list"], {
|
|
769
881
|
encoding: "utf8",
|
|
770
882
|
stdio: ["ignore", "pipe", "ignore"],
|
|
771
883
|
timeout: 3000,
|
package/package.json
CHANGED