Theseus 0.3.126: Claude connector detects Microsoft Store install of Claude Desktop
The Store install of Claude Desktop lives under %LOCALAPPDATA%\Packages\Claude_<publisher-id>\, and its "AppData\Roaming" is virtualised to LocalCache\Roaming inside that package. The classic %APPDATA%\Claude\ folder is never read by the Store app, so a claude_desktop_config.json dropped there is invisible to it; Theseus's old existence check against path.dirname of that classic path was both false-negative (nothing in classic, Store present) and false-positive (empty classic folder left over, Store the real install) — the user saw "Not installed on this computer" with Claude Desktop clearly running. desktopConfigPath now looks for a Claude_<id> package first and uses its LocalCache\Roaming\Claude path when present; otherwise it falls back to the classic path. Added a desktopInstalled() helper that treats any of: the config dir, a Store package, %LOCALAPPDATA%\AnthropicClaude, %LOCALAPPDATA%\Programs\Claude, or Program Files\Claude as proof of an install. register() mkdirSyncs the chosen path so the Store case succeeds on first write.
This commit is contained in:
parent
f3a8d883e2
commit
d07f2ff757
2 changed files with 49 additions and 4 deletions
|
|
@ -104,12 +104,53 @@ const claudeCode = {
|
||||||
|
|
||||||
// ---- Claude Desktop ------------------------------------------------------
|
// ---- Claude Desktop ------------------------------------------------------
|
||||||
|
|
||||||
|
// Pick the Claude Desktop config path, preferring whichever install is
|
||||||
|
// actually present on this box.
|
||||||
|
// • Classic installer / per-user: %APPDATA%\Claude\claude_desktop_config.json
|
||||||
|
// • Microsoft Store install: the Store virtualises AppData under
|
||||||
|
// %LOCALAPPDATA%\Packages\Claude_<publisher-id>\LocalCache\Roaming\Claude\
|
||||||
|
// — the Store app's "AppData\Roaming" is that folder, not the user's.
|
||||||
|
// If both exist (or neither), the classic path wins; register() creates
|
||||||
|
// a fresh config under whichever directory is already there.
|
||||||
function desktopConfigPath(opts = {}) {
|
function desktopConfigPath(opts = {}) {
|
||||||
const { e, platform, home } = env(opts);
|
const { e, platform, home } = env(opts);
|
||||||
if (platform === "win32") return path.join(e.APPDATA || path.join(home, "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
|
if (platform === "win32") {
|
||||||
|
// A Microsoft Store install of Claude Desktop virtualises AppData under
|
||||||
|
// its package folder; the Store app never reads the classic %APPDATA%\
|
||||||
|
// Claude\ path, so a config dropped there is invisible. Prefer the Store
|
||||||
|
// path whenever a Claude_<id> package is present. Fall back to classic
|
||||||
|
// otherwise.
|
||||||
|
try {
|
||||||
|
const packagesDir = path.join(e.LOCALAPPDATA || path.join(home, "AppData", "Local"), "Packages");
|
||||||
|
if (fs.existsSync(packagesDir)) {
|
||||||
|
const match = fs.readdirSync(packagesDir).find((n) => /^Claude_/i.test(n));
|
||||||
|
if (match) return path.join(packagesDir, match, "LocalCache", "Roaming", "Claude", "claude_desktop_config.json");
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
return path.join(e.APPDATA || path.join(home, "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
|
||||||
|
}
|
||||||
if (platform === "darwin") return path.join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
if (platform === "darwin") return path.join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
||||||
return path.join(e.XDG_CONFIG_HOME || path.join(home, ".config"), "Claude", "claude_desktop_config.json");
|
return path.join(e.XDG_CONFIG_HOME || path.join(home, ".config"), "Claude", "claude_desktop_config.json");
|
||||||
}
|
}
|
||||||
|
// A broader "is Claude Desktop installed" signal than just the config
|
||||||
|
// folder existing — the Store install may not have created the roaming
|
||||||
|
// folder yet, and the Program Files install leaves per-user folders empty
|
||||||
|
// until the first launch. Any of these says "yes, it is here".
|
||||||
|
function desktopInstalled(opts = {}) {
|
||||||
|
const { e, platform, home } = env(opts);
|
||||||
|
if (fs.existsSync(path.dirname(desktopConfigPath(opts)))) return true;
|
||||||
|
if (platform !== "win32") return false;
|
||||||
|
const packagesDir = path.join(e.LOCALAPPDATA || path.join(home, "AppData", "Local"), "Packages");
|
||||||
|
try {
|
||||||
|
if (fs.existsSync(packagesDir) && fs.readdirSync(packagesDir).some((n) => /^Claude_/i.test(n))) return true;
|
||||||
|
} catch {}
|
||||||
|
for (const p of [
|
||||||
|
path.join(e.LOCALAPPDATA || path.join(home, "AppData", "Local"), "AnthropicClaude"),
|
||||||
|
path.join(e.LOCALAPPDATA || path.join(home, "AppData", "Local"), "Programs", "Claude"),
|
||||||
|
path.join(e["PROGRAMFILES"] || "C:\\Program Files", "Claude"),
|
||||||
|
]) { try { if (fs.existsSync(p)) return true; } catch {} }
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Claude Desktop starts stdio servers only: it runs Theseus itself in Node
|
// Claude Desktop starts stdio servers only: it runs Theseus itself in Node
|
||||||
// mode with the relay, which reads the address from Theseus's endpoint file.
|
// mode with the relay, which reads the address from Theseus's endpoint file.
|
||||||
|
|
@ -143,13 +184,17 @@ const claudeDesktop = {
|
||||||
const cfg = readJson(file);
|
const cfg = readJson(file);
|
||||||
const entry = cfg && cfg.mcpServers ? cfg.mcpServers[NAME] : null;
|
const entry = cfg && cfg.mcpServers ? cfg.mcpServers[NAME] : null;
|
||||||
return {
|
return {
|
||||||
installed: fs.existsSync(path.dirname(file)),
|
installed: desktopInstalled(opts),
|
||||||
configured: !!entry,
|
configured: !!entry,
|
||||||
current: !!params.url && sameDesktop(entry, desktopEntry(params)),
|
current: !!params.url && sameDesktop(entry, desktopEntry(params)),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
async register(params, opts = {}) {
|
async register(params, opts = {}) {
|
||||||
const file = desktopConfigPath(opts);
|
const file = desktopConfigPath(opts);
|
||||||
|
// Create the config directory if needed — the Store install virtualises
|
||||||
|
// AppData so the Roaming\Claude folder may be absent until first write;
|
||||||
|
// Claude Desktop will pick up the file we drop next to it.
|
||||||
|
try { fs.mkdirSync(path.dirname(file), { recursive: true }); } catch {}
|
||||||
if (!fs.existsSync(path.dirname(file))) throw new Error("Claude Desktop was not found on this computer (no Claude settings folder)");
|
if (!fs.existsSync(path.dirname(file))) throw new Error("Claude Desktop was not found on this computer (no Claude settings folder)");
|
||||||
editDesktopConfig(file, (cfg) => {
|
editDesktopConfig(file, (cfg) => {
|
||||||
if (!cfg.mcpServers || typeof cfg.mcpServers !== "object") cfg.mcpServers = {};
|
if (!cfg.mcpServers || typeof cfg.mcpServers !== "object") cfg.mcpServers = {};
|
||||||
|
|
@ -165,4 +210,4 @@ const claudeDesktop = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = { claudeCode, claudeDesktop, findClaudeCli, claudeCodeConfigPath, desktopConfigPath, httpEntry, desktopEntry };
|
module.exports = { claudeCode, claudeDesktop, findClaudeCli, claudeCodeConfigPath, desktopConfigPath, desktopInstalled, httpEntry, desktopEntry };
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "theseus-navigator",
|
"name": "theseus-navigator",
|
||||||
"version": "0.3.125",
|
"version": "0.3.126",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"description": "Theseus Navigator — a browser that follows the thread. By Silent Mode, a Deviant project.",
|
"description": "Theseus Navigator — a browser that follows the thread. By Silent Mode, a Deviant project.",
|
||||||
"author": "Silent Mode",
|
"author": "Silent Mode",
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue