@echomem/mcp 1.4.48 → 1.4.49

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.
@@ -85,41 +85,80 @@ export function resolveClaudeProjectsDir(opts = {}) {
85
85
  const root = configuredRoot("CLAUDE_CONFIG_DIR", ".claude", opts);
86
86
  return resolveReadableDirectory(root ? path.join(root, "projects") : null);
87
87
  }
88
- /**
89
- * Resolve Claude Desktop's local Cowork sandbox root.
90
- *
91
- * Cowork does not write into the normal `~/.claude/projects` profile. Claude Desktop creates an
92
- * OS-specific support directory and stores each local-agent session below
93
- * `local-agent-mode-sessions/<org>/<conversation>/local_<id>/`. The actual transcript is nested
94
- * again under that sandbox's `.claude/projects` directory.
95
- */
96
- export function resolveClaudeCoworkSessionsDir(opts = {}) {
88
+ /** Resolve every readable Claude Desktop support directory, including Windows Store/MSIX data. */
89
+ export function resolveClaudeDesktopSupportRoots(opts = {}) {
97
90
  const homeDir = normalizedHomeDir(opts.homeDir ?? os.homedir());
98
91
  if (!homeDir)
99
- return null;
92
+ return [];
100
93
  const env = opts.env ?? process.env;
101
94
  const configured = env.CLAUDE_DESKTOP_SUPPORT_DIR;
102
- let supportRoot = null;
95
+ const platform = opts.platform ?? process.platform;
96
+ const candidates = [];
103
97
  if (typeof configured === "string" && configured.trim()) {
104
- supportRoot = expandCurrentUserHome(configured, homeDir);
98
+ candidates.push(expandCurrentUserHome(configured, homeDir));
105
99
  }
106
- else {
107
- const platform = opts.platform ?? process.platform;
108
- if (platform === "darwin") {
109
- supportRoot = path.join(homeDir, "Library", "Application Support", "Claude");
110
- }
111
- else if (platform === "win32") {
112
- const appData = typeof env.APPDATA === "string" && env.APPDATA.trim()
113
- ? expandCurrentUserHome(env.APPDATA, homeDir)
114
- : path.join(homeDir, "AppData", "Roaming");
115
- supportRoot = appData ? path.join(appData, "Claude") : null;
116
- }
117
- else {
118
- const configHome = typeof env.XDG_CONFIG_HOME === "string" && env.XDG_CONFIG_HOME.trim()
119
- ? expandCurrentUserHome(env.XDG_CONFIG_HOME, homeDir)
120
- : path.join(homeDir, ".config");
121
- supportRoot = configHome ? path.join(configHome, "Claude") : null;
100
+ if (platform === "darwin") {
101
+ candidates.push(path.join(homeDir, "Library", "Application Support", "Claude"));
102
+ }
103
+ else if (platform === "win32") {
104
+ const appData = typeof env.APPDATA === "string" && env.APPDATA.trim()
105
+ ? expandCurrentUserHome(env.APPDATA, homeDir)
106
+ : path.join(homeDir, "AppData", "Roaming");
107
+ if (appData)
108
+ candidates.push(path.join(appData, "Claude"));
109
+ const localAppData = typeof env.LOCALAPPDATA === "string" && env.LOCALAPPDATA.trim()
110
+ ? expandCurrentUserHome(env.LOCALAPPDATA, homeDir)
111
+ : path.join(homeDir, "AppData", "Local");
112
+ if (localAppData) {
113
+ const packagesRoot = path.join(localAppData, "Packages");
114
+ try {
115
+ for (const entry of fs.readdirSync(packagesRoot, { withFileTypes: true })) {
116
+ if (!entry.isDirectory() || !/^Claude_/i.test(entry.name))
117
+ continue;
118
+ candidates.push(path.join(packagesRoot, entry.name, "LocalCache", "Roaming", "Claude"));
119
+ }
120
+ }
121
+ catch {
122
+ /* A non-Store install has no Packages directory. */
123
+ }
122
124
  }
123
125
  }
124
- return resolveReadableDirectory(supportRoot ? path.join(supportRoot, "local-agent-mode-sessions") : null);
126
+ else {
127
+ const configHome = typeof env.XDG_CONFIG_HOME === "string" && env.XDG_CONFIG_HOME.trim()
128
+ ? expandCurrentUserHome(env.XDG_CONFIG_HOME, homeDir)
129
+ : path.join(homeDir, ".config");
130
+ if (configHome)
131
+ candidates.push(path.join(configHome, "Claude"));
132
+ }
133
+ const roots = [];
134
+ const seenRealpaths = new Set();
135
+ for (const candidate of candidates) {
136
+ const resolved = resolveReadableDirectory(candidate);
137
+ if (!resolved || seenRealpaths.has(resolved))
138
+ continue;
139
+ seenRealpaths.add(resolved);
140
+ roots.push(resolved);
141
+ }
142
+ return roots;
143
+ }
144
+ /**
145
+ * Resolve every Claude Desktop Cowork sandbox root. Cowork transcripts live below
146
+ * `local-agent-mode-sessions/<org>/<conversation>/local_<id>/.claude/projects`; Windows Store
147
+ * installs can have several private Claude package containers below `Packages`.
148
+ */
149
+ export function resolveClaudeCoworkSessionRoots(opts = {}) {
150
+ const roots = [];
151
+ const seenRealpaths = new Set();
152
+ for (const supportRoot of resolveClaudeDesktopSupportRoots(opts)) {
153
+ const resolved = resolveReadableDirectory(path.join(supportRoot, "local-agent-mode-sessions"));
154
+ if (!resolved || seenRealpaths.has(resolved))
155
+ continue;
156
+ seenRealpaths.add(resolved);
157
+ roots.push(resolved);
158
+ }
159
+ return roots;
160
+ }
161
+ /** Backward-compatible first-root resolver. New discovery code should scan every returned root. */
162
+ export function resolveClaudeCoworkSessionsDir(opts = {}) {
163
+ return resolveClaudeCoworkSessionRoots(opts)[0] ?? null;
125
164
  }
package/dist/migrate.js CHANGED
@@ -29,7 +29,7 @@ import axios from "axios";
29
29
  import { KeyStore, echoConfigDir } from "./keystore.js";
30
30
  import { fetchEncryptionConfig } from "./encryption.js";
31
31
  import { discoverCodexSessionFiles } from "./codex-session-files.js";
32
- import { resolveClaudeCoworkSessionsDir, resolveClaudeProjectsDir } from "./local-data-paths.js";
32
+ import { resolveClaudeCoworkSessionRoots, resolveClaudeProjectsDir } from "./local-data-paths.js";
33
33
  import { eachJsonLine, walkLocalFiles } from "./local-jsonl.js";
34
34
  const API_BASE = (process.env.ECHO_API_BASE_URL || "https://echo-mem-chrome.vercel.app").replace(/\/$/, "");
35
35
  const RATE_MAX = 28; // stay under the import-jobs /run limit of 30 / 60s
@@ -232,10 +232,7 @@ function claudeSessionCandidates(opts = {}) {
232
232
  // Supplying an explicit Claude root is an isolated discovery request (tests, diagnostics, or a
233
233
  // moved profile). Callers can still add Cowork roots explicitly. Normal onboarding supplies no
234
234
  // roots, so the platform-default Cowork directory is included automatically.
235
- const defaultCoworkRoot = opts.claudeRoot === undefined
236
- ? resolveClaudeCoworkSessionsDir()
237
- : null;
238
- const coworkRoots = opts.coworkRoots ?? (defaultCoworkRoot ? [defaultCoworkRoot] : []);
235
+ const coworkRoots = opts.coworkRoots ?? (opts.claudeRoot === undefined ? resolveClaudeCoworkSessionRoots() : []);
239
236
  for (const root of coworkRoots) {
240
237
  for (const filePath of userCreatedClaudeFiles(root)) {
241
238
  const conversationId = coworkConversationId(filePath, root);
package/dist/setup.js CHANGED
@@ -30,6 +30,7 @@ import { cmdMigrate, applyAccountImportStatus, applyFastAccountImportStatus, dis
30
30
  import { syncCodexUsage } from "./codex-sync.js";
31
31
  import { renderSetupPage } from "./setup-page.js";
32
32
  import { parseSetupPreviewState } from "./setup-preview.js";
33
+ import { resolveClaudeDesktopSupportRoots } from "./local-data-paths.js";
33
34
  import { installSaveCheckpointHooks, installSourceSessionHooks } from "./hud/hooks.js";
34
35
  import { MCP_PACKAGE_LABEL, MCP_PACKAGE_NAME, MCP_PACKAGE_VERSION, MCP_UPDATE_ALL_COMMAND, MCP_UPDATE_COMMAND } from "./package-metadata.js";
35
36
  import { checkLatestUpdateStatus, compareSemver, readCachedUpdateStatus } from "./update-check.js";
@@ -220,10 +221,18 @@ export function knownClients() {
220
221
  const appSupport = process.platform === "darwin"
221
222
  ? home("Library", "Application Support")
222
223
  : process.env.APPDATA || home(".config");
224
+ const defaultClaudeDesktopRoot = path.join(appSupport, "Claude");
225
+ const claudeDesktopRoots = resolveClaudeDesktopSupportRoots();
226
+ const claudeDesktopClients = (claudeDesktopRoots.length > 0 ? claudeDesktopRoots : [defaultClaudeDesktopRoot]).map((supportRoot) => ({
227
+ id: "claude-desktop",
228
+ label: "Claude Desktop",
229
+ kind: "json",
230
+ configPath: path.join(supportRoot, "claude_desktop_config.json"),
231
+ }));
223
232
  return [
224
233
  { id: "cursor", label: "Cursor", kind: "json", configPath: home(".cursor", "mcp.json") },
225
234
  { id: "windsurf", label: "Windsurf", kind: "json", configPath: home(".codeium", "windsurf", "mcp_config.json") },
226
- { id: "claude-desktop", label: "Claude Desktop", kind: "json", configPath: path.join(appSupport, "Claude", "claude_desktop_config.json") },
235
+ ...claudeDesktopClients,
227
236
  { id: "claude-code", label: "Claude Code", kind: "snippet", note: "run: claude mcp add-json -s user echomem '<entry>' (or add to .mcp.json)" },
228
237
  { id: "codex", label: "Codex", kind: "command", detectDir: codexHome(), configPath: path.join(codexHome(), "config.toml"), note: "add to ~/.codex/config.toml under [mcp_servers.echomem]" },
229
238
  ];
@@ -236,7 +245,7 @@ export function detectClients() {
236
245
  if (c.kind === "command")
237
246
  return fs.existsSync(c.detectDir);
238
247
  if (c.id === "claude-code")
239
- return fs.existsSync(home(".claude"));
248
+ return claudeCodeCliAvailable();
240
249
  return false;
241
250
  });
242
251
  }
@@ -655,6 +664,19 @@ function execClaudeCodeSync(args, options, commandCandidates) {
655
664
  }
656
665
  throw lastError;
657
666
  }
667
+ export function claudeCodeCliAvailable() {
668
+ try {
669
+ execClaudeCodeSync(["--version"], {
670
+ encoding: "utf8",
671
+ stdio: ["ignore", "pipe", "ignore"],
672
+ timeout: 3000,
673
+ });
674
+ return true;
675
+ }
676
+ catch {
677
+ return false;
678
+ }
679
+ }
658
680
  export function writeClaudeCodeConfig(entry, options = {}) {
659
681
  // EchoMem belongs at user scope so every Claude Code project resolves the same durable runtime.
660
682
  // Older CLI versions wrote local/project entries, which take precedence over user scope and can
@@ -2817,7 +2839,7 @@ function selectSetupTargets(requested, all) {
2817
2839
  return fs.existsSync(path.dirname(client.configPath));
2818
2840
  if (client.kind === "command")
2819
2841
  return fs.existsSync(client.detectDir);
2820
- return client.id === "claude-code" && fs.existsSync(home(".claude"));
2842
+ return client.id === "claude-code" && claudeCodeCliAvailable();
2821
2843
  });
2822
2844
  }
2823
2845
  return requested ? knownClients().filter((client) => client.id === requested) : detectClients();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@echomem/mcp",
3
- "version": "1.4.48",
3
+ "version": "1.4.49",
4
4
  "description": "EchoMem MCP bridge for cross-agent memory, local history import, and recall",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",