fix: reject OpenCode desktop app as CLI

This commit is contained in:
Bohdan Triapitsyn
2026-07-02 00:48:09 +03:00
parent 37a9179656
commit bd8ab070e7
3 changed files with 113 additions and 20 deletions
+31 -14
View File
@@ -196,10 +196,30 @@ function isMacOpenCodeAppBundlePath(candidate: string): boolean {
return process.platform === 'darwin' && /\/OpenCode\.app\/Contents\/MacOS\/(?:OpenCode|opencode-cli)$/i.test(candidate);
}
function isWindowsOpenCodeDesktopAppPath(candidate: string): boolean {
if (process.platform !== 'win32' || typeof candidate !== 'string') {
return false;
}
const localAppData = typeof process.env.LOCALAPPDATA === 'string' && process.env.LOCALAPPDATA.trim()
? path.resolve(process.env.LOCALAPPDATA).toLowerCase()
: '';
if (!localAppData) {
return false;
}
const normalized = path.resolve(candidate).toLowerCase();
return normalized.startsWith(`${localAppData}${path.sep}`)
&& normalized.endsWith(`${path.sep}programs${path.sep}opencode${path.sep}opencode.exe`);
}
function isKnownOpenCodeDesktopAppPath(candidate: string): boolean {
return isMacOpenCodeAppBundlePath(candidate) || isWindowsOpenCodeDesktopAppPath(candidate);
}
function createConfiguredOpencodeBinaryError(raw: string, normalized: string): Error {
const messageSuffix = 'OpenChamber needs the standalone opencode CLI. Install it and set openchamber.opencodeBinary to the CLI path, for example ~/.opencode/bin/opencode, or leave the setting empty to use PATH lookup.';
if (isMacOpenCodeAppBundlePath(raw) || isMacOpenCodeAppBundlePath(normalized)) {
return new Error(`Configured OpenCode binary points at the macOS desktop app bundle, not the CLI: ${normalized}. ${messageSuffix}`);
if (isKnownOpenCodeDesktopAppPath(raw) || isKnownOpenCodeDesktopAppPath(normalized)) {
const platformName = process.platform === 'win32' ? 'Windows desktop app install' : 'macOS desktop app bundle';
return new Error(`Configured OpenCode binary points at the ${platformName}, not the CLI: ${normalized}. ${messageSuffix}`);
}
try {
@@ -254,7 +274,7 @@ function validateConfiguredOpencodeBinaryForManagedStart(): string | null {
return null;
}
if (isExecutable(normalized) && !isMacOpenCodeAppBundlePath(normalized)) {
if (isExecutable(normalized) && !isKnownOpenCodeDesktopAppPath(normalized)) {
return normalized;
}
@@ -271,7 +291,7 @@ function resolveOpencodeCliPath(): string | null {
}
})();
if (configured && isExecutable(configured) && !isMacOpenCodeAppBundlePath(configured)) {
if (configured && isExecutable(configured) && !isKnownOpenCodeDesktopAppPath(configured)) {
return configured;
}
@@ -288,7 +308,7 @@ function resolveOpencodeCliPath(): string | null {
}
})();
if (sharedFromOpenChamber && isExecutable(sharedFromOpenChamber) && !isMacOpenCodeAppBundlePath(sharedFromOpenChamber)) {
if (sharedFromOpenChamber && isExecutable(sharedFromOpenChamber) && !isKnownOpenCodeDesktopAppPath(sharedFromOpenChamber)) {
return sharedFromOpenChamber;
}
@@ -302,13 +322,13 @@ function resolveOpencodeCliPath(): string | null {
.filter(Boolean);
for (const candidate of explicit) {
if (isExecutable(candidate)) {
if (isExecutable(candidate) && !isKnownOpenCodeDesktopAppPath(candidate)) {
return candidate;
}
}
if (cachedDetectedOpencodeCliPath) {
if (isExecutable(cachedDetectedOpencodeCliPath)) {
if (isExecutable(cachedDetectedOpencodeCliPath) && !isKnownOpenCodeDesktopAppPath(cachedDetectedOpencodeCliPath)) {
return cachedDetectedOpencodeCliPath;
}
cachedDetectedOpencodeCliPath = undefined;
@@ -327,7 +347,6 @@ function resolveOpencodeCliPath(): string | null {
const winFallbacks = (() => {
const userProfile = process.env.USERPROFILE || home;
const appData = process.env.APPDATA || path.join(userProfile, 'AppData', 'Roaming');
const localAppData = process.env.LOCALAPPDATA || '';
const programData = process.env.ProgramData || 'C:\\ProgramData';
const npmDir = path.join(appData, 'npm');
@@ -344,14 +363,12 @@ function resolveOpencodeCliPath(): string | null {
// Bun global install
path.join(userProfile, '.bun', 'bin', 'opencode.exe'),
path.join(userProfile, '.bun', 'bin', 'opencode.cmd'),
// Some installers use LocalAppData
localAppData ? path.join(localAppData, 'Programs', 'opencode', 'opencode.exe') : '',
].filter(Boolean);
})();
if (process.platform !== 'win32') {
const fromPath = findExecutableInPath('opencode');
if (fromPath) {
if (fromPath && !isKnownOpenCodeDesktopAppPath(fromPath)) {
cachedDetectedOpencodeCliPath = fromPath;
return fromPath;
}
@@ -359,7 +376,7 @@ function resolveOpencodeCliPath(): string | null {
const fallbacks = process.platform === 'win32' ? winFallbacks : unixFallbacks;
for (const candidate of fallbacks) {
if (isExecutable(candidate)) {
if (isExecutable(candidate) && !isKnownOpenCodeDesktopAppPath(candidate)) {
cachedDetectedOpencodeCliPath = candidate;
return candidate;
}
@@ -367,7 +384,7 @@ function resolveOpencodeCliPath(): string | null {
if (process.platform === 'win32') {
const fromPath = findExecutableInPath('opencode');
if (fromPath) {
if (fromPath && !isKnownOpenCodeDesktopAppPath(fromPath)) {
cachedDetectedOpencodeCliPath = fromPath;
return fromPath;
}
@@ -382,7 +399,7 @@ function resolveOpencodeCliPath(): string | null {
.split(/\r?\n/)
.map((line) => line.trim())
.filter(Boolean);
const found = lines.find((line) => isExecutable(line));
const found = lines.find((line) => isExecutable(line) && !isKnownOpenCodeDesktopAppPath(line));
if (found) {
cachedDetectedOpencodeCliPath = found;
return found;