fix(cursor): detect Windows native CLI installs

This commit is contained in:
DhanushSantosh
2026-02-04 10:16:19 +05:30
parent d4b7a0c57d
commit c9e721bda7

View File

@@ -14,6 +14,7 @@ import { execSync } from 'child_process';
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import * as os from 'os'; import * as os from 'os';
import { findCliInWsl, isWslAvailable } from '@automaker/platform';
import { import {
CliProvider, CliProvider,
type CliSpawnConfig, type CliSpawnConfig,
@@ -286,15 +287,113 @@ export class CursorProvider extends CliProvider {
getSpawnConfig(): CliSpawnConfig { getSpawnConfig(): CliSpawnConfig {
return { return {
windowsStrategy: 'wsl', // cursor-agent requires WSL on Windows windowsStrategy: 'direct',
commonPaths: { commonPaths: {
linux: [ linux: [
path.join(os.homedir(), '.local/bin/cursor-agent'), // Primary symlink location path.join(os.homedir(), '.local/bin/cursor-agent'), // Primary symlink location
'/usr/local/bin/cursor-agent', '/usr/local/bin/cursor-agent',
], ],
darwin: [path.join(os.homedir(), '.local/bin/cursor-agent'), '/usr/local/bin/cursor-agent'], darwin: [path.join(os.homedir(), '.local/bin/cursor-agent'), '/usr/local/bin/cursor-agent'],
// Windows paths are not used - we check for WSL installation instead win32: [
win32: [], path.join(
process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'),
'Programs',
'Cursor',
'resources',
'app',
'bin',
'cursor-agent.exe'
),
path.join(
process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'),
'Programs',
'Cursor',
'resources',
'app',
'bin',
'cursor-agent.cmd'
),
path.join(
process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'),
'Programs',
'Cursor',
'resources',
'app',
'bin',
'cursor.exe'
),
path.join(
process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'),
'Programs',
'Cursor',
'cursor.exe'
),
path.join(
process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'),
'Programs',
'cursor',
'resources',
'app',
'bin',
'cursor-agent.exe'
),
path.join(
process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'),
'Programs',
'cursor',
'resources',
'app',
'bin',
'cursor-agent.cmd'
),
path.join(
process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'),
'Programs',
'cursor',
'resources',
'app',
'bin',
'cursor.exe'
),
path.join(
process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'),
'Programs',
'cursor',
'cursor.exe'
),
path.join(
process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'),
'npm',
'cursor-agent.cmd'
),
path.join(
process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'),
'npm',
'cursor.cmd'
),
path.join(
process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'),
'.npm-global',
'bin',
'cursor-agent.cmd'
),
path.join(
process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'),
'.npm-global',
'bin',
'cursor.cmd'
),
path.join(
process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'),
'pnpm',
'cursor-agent.cmd'
),
path.join(
process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'),
'pnpm',
'cursor.cmd'
),
],
}, },
}; };
} }
@@ -487,6 +586,92 @@ export class CursorProvider extends CliProvider {
* 2. Cursor IDE with 'cursor agent' subcommand support * 2. Cursor IDE with 'cursor agent' subcommand support
*/ */
protected detectCli(): CliDetectionResult { protected detectCli(): CliDetectionResult {
if (process.platform === 'win32') {
const findInPath = (command: string): string | null => {
try {
const result = execSync(`where ${command}`, {
encoding: 'utf8',
timeout: 5000,
stdio: ['pipe', 'pipe', 'pipe'],
windowsHide: true,
})
.trim()
.split(/\r?\n/)[0];
if (result && fs.existsSync(result)) {
return result;
}
} catch {
// Not in PATH
}
return null;
};
const isCursorAgentBinary = (cliPath: string) =>
cliPath.toLowerCase().includes('cursor-agent');
const supportsCursorAgentSubcommand = (cliPath: string) => {
try {
execSync(`"${cliPath}" agent --version`, {
encoding: 'utf8',
timeout: 5000,
stdio: 'pipe',
windowsHide: true,
});
return true;
} catch {
return false;
}
};
const pathResult = findInPath('cursor-agent') || findInPath('cursor');
if (pathResult) {
if (isCursorAgentBinary(pathResult) || supportsCursorAgentSubcommand(pathResult)) {
return {
cliPath: pathResult,
useWsl: false,
strategy: pathResult.toLowerCase().endsWith('.cmd') ? 'cmd' : 'direct',
};
}
}
const config = this.getSpawnConfig();
for (const candidate of config.commonPaths.win32 || []) {
const resolved = candidate;
if (!fs.existsSync(resolved)) {
continue;
}
if (isCursorAgentBinary(resolved) || supportsCursorAgentSubcommand(resolved)) {
return {
cliPath: resolved,
useWsl: false,
strategy: resolved.toLowerCase().endsWith('.cmd') ? 'cmd' : 'direct',
};
}
}
const wslLogger = (msg: string) => logger.debug(msg);
if (isWslAvailable({ logger: wslLogger })) {
const wslResult = findCliInWsl('cursor-agent', { logger: wslLogger });
if (wslResult) {
logger.debug(
`Using cursor-agent via WSL (${wslResult.distribution || 'default'}): ${wslResult.wslPath}`
);
return {
cliPath: 'wsl.exe',
useWsl: true,
wslCliPath: wslResult.wslPath,
wslDistribution: wslResult.distribution,
strategy: 'wsl',
};
}
}
logger.debug('cursor-agent not found on Windows');
return { cliPath: null, useWsl: false, strategy: 'direct' };
}
// First try standard detection (PATH, common paths, WSL) // First try standard detection (PATH, common paths, WSL)
const result = super.detectCli(); const result = super.detectCli();
if (result.cliPath) { if (result.cliPath) {
@@ -495,7 +680,7 @@ export class CursorProvider extends CliProvider {
// Cursor-specific: Check versions directory for any installed version // Cursor-specific: Check versions directory for any installed version
// This handles cases where cursor-agent is installed but not in PATH // This handles cases where cursor-agent is installed but not in PATH
if (process.platform !== 'win32' && fs.existsSync(CursorProvider.VERSIONS_DIR)) { if (fs.existsSync(CursorProvider.VERSIONS_DIR)) {
try { try {
const versions = fs const versions = fs
.readdirSync(CursorProvider.VERSIONS_DIR) .readdirSync(CursorProvider.VERSIONS_DIR)
@@ -521,33 +706,31 @@ export class CursorProvider extends CliProvider {
// If cursor-agent not found, try to find 'cursor' IDE and use 'cursor agent' subcommand // If cursor-agent not found, try to find 'cursor' IDE and use 'cursor agent' subcommand
// The Cursor IDE includes the agent as a subcommand: cursor agent // The Cursor IDE includes the agent as a subcommand: cursor agent
if (process.platform !== 'win32') { const cursorPaths = [
const cursorPaths = [ '/usr/bin/cursor',
'/usr/bin/cursor', '/usr/local/bin/cursor',
'/usr/local/bin/cursor', path.join(os.homedir(), '.local/bin/cursor'),
path.join(os.homedir(), '.local/bin/cursor'), '/opt/cursor/cursor',
'/opt/cursor/cursor', ];
];
for (const cursorPath of cursorPaths) { for (const cursorPath of cursorPaths) {
if (fs.existsSync(cursorPath)) { if (fs.existsSync(cursorPath)) {
// Verify cursor agent subcommand works // Verify cursor agent subcommand works
try { try {
execSync(`"${cursorPath}" agent --version`, { execSync(`"${cursorPath}" agent --version`, {
encoding: 'utf8', encoding: 'utf8',
timeout: 5000, timeout: 5000,
stdio: 'pipe', stdio: 'pipe',
}); });
logger.debug(`Using cursor agent via Cursor IDE: ${cursorPath}`); logger.debug(`Using cursor agent via Cursor IDE: ${cursorPath}`);
// Return cursor path but we'll use 'cursor agent' subcommand // Return cursor path but we'll use 'cursor agent' subcommand
return { return {
cliPath: cursorPath, cliPath: cursorPath,
useWsl: false, useWsl: false,
strategy: 'native', strategy: 'native',
}; };
} catch { } catch {
// cursor agent subcommand doesn't work, try next path // cursor agent subcommand doesn't work, try next path
}
} }
} }
} }