@echomem/mcp 1.4.45 → 1.4.47
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 +193 -50
- 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,11 +505,124 @@ 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
|
|
511
623
|
// keep launching a deleted npm cache or stale runtime. Migrate those only after user scope is safe.
|
|
512
624
|
const configPath = options.configPath ?? home(".claude.json");
|
|
625
|
+
let failureReason;
|
|
513
626
|
const emptyResult = () => ({
|
|
514
627
|
state: "unavailable",
|
|
515
628
|
removedLocalProjects: [],
|
|
@@ -517,18 +630,21 @@ export function writeClaudeCodeConfig(entry, options = {}) {
|
|
|
517
630
|
failedLocalProjects: [],
|
|
518
631
|
restoredPreviousUserEntry: false,
|
|
519
632
|
preservedDesktopManaged: false,
|
|
633
|
+
failureReason,
|
|
520
634
|
});
|
|
521
635
|
const runClaude = (args, cwd) => {
|
|
522
636
|
try {
|
|
523
|
-
|
|
637
|
+
execClaudeCodeSync(args, {
|
|
524
638
|
cwd,
|
|
525
639
|
encoding: "utf8",
|
|
526
640
|
stdio: ["ignore", "pipe", "pipe"],
|
|
527
641
|
timeout: 10000,
|
|
528
|
-
});
|
|
642
|
+
}, options.claudeCommands);
|
|
643
|
+
failureReason = undefined;
|
|
529
644
|
return true;
|
|
530
645
|
}
|
|
531
|
-
catch {
|
|
646
|
+
catch (error) {
|
|
647
|
+
failureReason = commandFailureMessage(error);
|
|
532
648
|
return false;
|
|
533
649
|
}
|
|
534
650
|
};
|
|
@@ -547,8 +663,10 @@ export function writeClaudeCodeConfig(entry, options = {}) {
|
|
|
547
663
|
if (previousUserEntry && !removeUser())
|
|
548
664
|
return emptyResult();
|
|
549
665
|
if (!addUser(desiredUserEntry)) {
|
|
666
|
+
const addFailureReason = failureReason;
|
|
550
667
|
if (previousUserEntry)
|
|
551
668
|
restoredPreviousUserEntry = addUser(previousUserEntry);
|
|
669
|
+
failureReason = addFailureReason;
|
|
552
670
|
return { ...emptyResult(), restoredPreviousUserEntry };
|
|
553
671
|
}
|
|
554
672
|
}
|
|
@@ -765,7 +883,7 @@ function inspectClaudeCodeConfig(client, desiredVersion) {
|
|
|
765
883
|
if (!fs.existsSync(home(".claude")))
|
|
766
884
|
return null;
|
|
767
885
|
try {
|
|
768
|
-
const output =
|
|
886
|
+
const output = execClaudeCodeSync(["mcp", "list"], {
|
|
769
887
|
encoding: "utf8",
|
|
770
888
|
stdio: ["ignore", "pipe", "ignore"],
|
|
771
889
|
timeout: 3000,
|
|
@@ -2821,6 +2939,7 @@ async function cmdSetup(flags) {
|
|
|
2821
2939
|
// --dev is already an explicit request to replace the managed runtime with a checkout.
|
|
2822
2940
|
const forceHeadless = flags["force-headless"] === true || typeof flags.dev === "string";
|
|
2823
2941
|
const configurationFailures = [];
|
|
2942
|
+
const configuredTargets = [];
|
|
2824
2943
|
if (targets.length === 0) {
|
|
2825
2944
|
console.log("No client auto-detected. Add this MCP server entry manually:\n");
|
|
2826
2945
|
console.log(JSON.stringify({ echomem: entry }, null, 2));
|
|
@@ -2828,66 +2947,90 @@ async function cmdSetup(flags) {
|
|
|
2828
2947
|
}
|
|
2829
2948
|
else {
|
|
2830
2949
|
for (const c of targets) {
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
else {
|
|
2837
|
-
console.log(`✅ Wrote EchoMem MCP entry to ${c.label}: ${c.configPath}`);
|
|
2838
|
-
}
|
|
2839
|
-
}
|
|
2840
|
-
else if (c.kind === "command") {
|
|
2841
|
-
const result = writeCodexConfig(c.configPath, entry, { forceHeadless });
|
|
2842
|
-
if (result === "wrote")
|
|
2843
|
-
console.log(`✅ Wrote EchoMem MCP entry to ${c.label}: ${c.configPath} — start a new Codex session to load it.`);
|
|
2844
|
-
else if (result === "desktop-managed")
|
|
2845
|
-
console.log(`✅ Kept the valid Echo Desktop-managed EchoMem entry for ${c.label}: ${c.configPath}`);
|
|
2846
|
-
else
|
|
2847
|
-
console.log(`✅ ${c.label} already has the EchoMem MCP entry: ${c.configPath}`);
|
|
2848
|
-
}
|
|
2849
|
-
else {
|
|
2850
|
-
const result = c.id === "claude-code"
|
|
2851
|
-
? writeClaudeCodeConfig(entry, { forceHeadless })
|
|
2852
|
-
: "unavailable";
|
|
2853
|
-
if (result !== "unavailable" && result.state === "wrote") {
|
|
2854
|
-
if (result.preservedDesktopManaged) {
|
|
2855
|
-
console.log(`✅ Kept the valid Echo Desktop-managed EchoMem user entry for ${c.label}.`);
|
|
2950
|
+
try {
|
|
2951
|
+
if (c.kind === "json") {
|
|
2952
|
+
const result = writeJsonClientConfig(c.configPath, entry, { forceHeadless });
|
|
2953
|
+
if (result === "desktop-managed") {
|
|
2954
|
+
console.log(`✅ Kept the valid Echo Desktop-managed EchoMem entry for ${c.label}: ${c.configPath}`);
|
|
2856
2955
|
}
|
|
2857
2956
|
else {
|
|
2858
|
-
console.log(`✅ Wrote EchoMem MCP entry to ${c.label}
|
|
2859
|
-
}
|
|
2860
|
-
if (result.removedLocalProjects.length > 0) {
|
|
2861
|
-
console.log(`✅ Removed ${result.removedLocalProjects.length} stale Claude Code project-local EchoMem ${result.removedLocalProjects.length === 1 ? "entry" : "entries"}.`);
|
|
2862
|
-
}
|
|
2863
|
-
if (result.skippedLocalProjects.length > 0) {
|
|
2864
|
-
console.log(`ℹ️ Ignored ${result.skippedLocalProjects.length} EchoMem local ${result.skippedLocalProjects.length === 1 ? "entry" : "entries"} for deleted project directories; they cannot shadow the user entry.`);
|
|
2957
|
+
console.log(`✅ Wrote EchoMem MCP entry to ${c.label}: ${c.configPath}`);
|
|
2865
2958
|
}
|
|
2959
|
+
configuredTargets.push(c);
|
|
2960
|
+
}
|
|
2961
|
+
else if (c.kind === "command") {
|
|
2962
|
+
const result = writeCodexConfig(c.configPath, entry, { forceHeadless });
|
|
2963
|
+
if (result === "wrote")
|
|
2964
|
+
console.log(`✅ Wrote EchoMem MCP entry to ${c.label}: ${c.configPath} — start a new Codex session to load it.`);
|
|
2965
|
+
else if (result === "desktop-managed")
|
|
2966
|
+
console.log(`✅ Kept the valid Echo Desktop-managed EchoMem entry for ${c.label}: ${c.configPath}`);
|
|
2967
|
+
else
|
|
2968
|
+
console.log(`✅ ${c.label} already has the EchoMem MCP entry: ${c.configPath}`);
|
|
2969
|
+
configuredTargets.push(c);
|
|
2866
2970
|
}
|
|
2867
2971
|
else {
|
|
2868
|
-
const
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2972
|
+
const result = c.id === "claude-code"
|
|
2973
|
+
? writeClaudeCodeConfig(entry, { forceHeadless })
|
|
2974
|
+
: "unavailable";
|
|
2975
|
+
if (result !== "unavailable" && result.state === "wrote") {
|
|
2976
|
+
if (result.preservedDesktopManaged) {
|
|
2977
|
+
console.log(`✅ Kept the valid Echo Desktop-managed EchoMem user entry for ${c.label}.`);
|
|
2978
|
+
}
|
|
2979
|
+
else {
|
|
2980
|
+
console.log(`✅ Wrote EchoMem MCP entry to ${c.label} via \`claude mcp add-json\` — start a new Claude Code session to load it.`);
|
|
2981
|
+
}
|
|
2982
|
+
if (result.removedLocalProjects.length > 0) {
|
|
2983
|
+
console.log(`✅ Removed ${result.removedLocalProjects.length} stale Claude Code project-local EchoMem ${result.removedLocalProjects.length === 1 ? "entry" : "entries"}.`);
|
|
2984
|
+
}
|
|
2985
|
+
if (result.skippedLocalProjects.length > 0) {
|
|
2986
|
+
console.log(`ℹ️ Ignored ${result.skippedLocalProjects.length} EchoMem local ${result.skippedLocalProjects.length === 1 ? "entry" : "entries"} for deleted project directories; they cannot shadow the user entry.`);
|
|
2987
|
+
}
|
|
2988
|
+
configuredTargets.push(c);
|
|
2989
|
+
}
|
|
2990
|
+
else {
|
|
2991
|
+
const failedProjects = result === "unavailable" ? [] : result.failedLocalProjects;
|
|
2992
|
+
const reason = result === "unavailable" ? undefined : result.failureReason;
|
|
2993
|
+
if (result !== "unavailable" && result.state === "needs-repair")
|
|
2994
|
+
configuredTargets.push(c);
|
|
2995
|
+
configurationFailures.push({
|
|
2996
|
+
client: c,
|
|
2997
|
+
reason: failedProjects.length > 0
|
|
2998
|
+
? `${c.label} still has project-local EchoMem overrides in: ${failedProjects.join(", ")}`
|
|
2999
|
+
: `${c.label} user-scoped EchoMem entry could not be verified${reason ? `: ${reason}` : ""}`,
|
|
3000
|
+
});
|
|
3001
|
+
}
|
|
2872
3002
|
}
|
|
2873
3003
|
}
|
|
3004
|
+
catch (error) {
|
|
3005
|
+
configurationFailures.push({
|
|
3006
|
+
client: c,
|
|
3007
|
+
reason: `${c.label} configuration was left unchanged: ${error instanceof Error ? error.message : String(error)}`,
|
|
3008
|
+
});
|
|
3009
|
+
}
|
|
2874
3010
|
}
|
|
2875
3011
|
}
|
|
2876
3012
|
if (configurationFailures.length > 0) {
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
3013
|
+
const explicitSingleClient = Boolean(requested && requested !== "all" && flags.all !== true);
|
|
3014
|
+
const failureMessage = [
|
|
3015
|
+
explicitSingleClient
|
|
3016
|
+
? "EchoMem could not configure the requested MCP client."
|
|
3017
|
+
: "EchoMem skipped MCP clients that were unavailable or could not be configured.",
|
|
3018
|
+
...configurationFailures.map((failure) => `- ${failure.reason}`),
|
|
3019
|
+
...configurationFailures.map((failure) => `Retry ${failure.client.label} later with: ${MCP_UPDATE_COMMAND} --client ${failure.client.id}`),
|
|
3020
|
+
].join("\n");
|
|
3021
|
+
if (explicitSingleClient)
|
|
3022
|
+
throw new Error(failureMessage);
|
|
3023
|
+
console.log(`⚠️ ${failureMessage}`);
|
|
3024
|
+
console.log("Continuing with the available clients; account connection and local-history import are not blocked.");
|
|
2882
3025
|
}
|
|
2883
3026
|
if (!flags["no-agents-md"]) {
|
|
2884
|
-
writeMemoryGuidanceForTargets(
|
|
3027
|
+
writeMemoryGuidanceForTargets(configuredTargets);
|
|
2885
3028
|
}
|
|
2886
3029
|
if (!flags["no-codex-skills"]) {
|
|
2887
|
-
writeCodexSkillsForTargets(
|
|
3030
|
+
writeCodexSkillsForTargets(configuredTargets);
|
|
2888
3031
|
}
|
|
2889
3032
|
if (flags["no-save-hooks"] !== true) {
|
|
2890
|
-
writeLifecycleHooksForTargets(
|
|
3033
|
+
writeLifecycleHooksForTargets(configuredTargets);
|
|
2891
3034
|
}
|
|
2892
3035
|
console.log("");
|
|
2893
3036
|
if (flags["skip-login"] || flags["no-login"]) {
|
|
@@ -2928,7 +3071,7 @@ async function cmdInit(flags) {
|
|
|
2928
3071
|
}
|
|
2929
3072
|
console.log("");
|
|
2930
3073
|
console.log("🎉 EchoMem is ready.");
|
|
2931
|
-
console.log(" • MCP memory is configured for every coding agent
|
|
3074
|
+
console.log(" • MCP memory is configured for every available coding agent EchoMem could connect to on this machine.");
|
|
2932
3075
|
console.log(" • Echo Desktop shows connection status and manages this device credential.");
|
|
2933
3076
|
console.log(' • Try it now: ask your agent — "search my EchoMem for what I\'ve been working on and recap it."');
|
|
2934
3077
|
}
|
package/package.json
CHANGED