Skip to content

Commit 0817dc7

Browse files
committed
Refactor agent path handling for Windows in Cursor Agent plugin
- Introduced a default agent path function for Windows to streamline the agent binary path configuration. - Updated the README to clarify the default agent binary path on Windows. - Enhanced the `AcpClient` and `CursorAgentSettingTab` to utilize the new path handling logic, ensuring compatibility with Windows shell requirements.
1 parent f85f6a3 commit 0817dc7

5 files changed

Lines changed: 35 additions & 10 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ npx tsc -noEmit -skipLibCheck
9898

9999
## Troubleshooting
100100

101+
- **Windows — default path and `agent.cmd`** — If you leave **Agent binary path** empty, the plugin uses `%LOCALAPPDATA%\cursor-agent\agent.cmd` (the usual Cursor CLI shim on Windows), not `~/.local/bin/agent`. If you set the path yourself, use the full path to `agent.cmd` (or another real executable). On recent Node.js versions, `.cmd` / `.bat` / `.ps1` shims must be launched with a shell; the plugin does that automatically for those extensions.
101102
- **`agent: command not found` in Terminal** — The installer often puts the binary at `~/.local/bin/agent`, which may not be on your shell `PATH`. Either add that directory to `PATH` in `~/.zshrc`, or log in with the full path:
102103
`~/.local/bin/agent login`
103104
In the plugin, set **Agent binary path** to that full path (the default is already `~/.local/bin/agent` expanded to an absolute path, which does **not** rely on `PATH`).

package-lock.json

Lines changed: 4 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/acp/client.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { spawn, type ChildProcessWithoutNullStreams } from "child_process";
22
import * as fs from "fs";
33
import * as readline from "readline";
4+
import { agentPathRequiresWindowsShell } from "../agentModels";
45
import { augmentPathEnv } from "../util/agentEnv";
56
import type { AgentFileLogger } from "../logging/agentFileLog";
67
import type { JsonRpcRequest, JsonRpcResponse, SessionPromptPart } from "./types";
@@ -106,6 +107,7 @@ export class AcpClient {
106107
this.proc = spawn(opt.agentPath, args, {
107108
env: mergedEnv,
108109
stdio: ["pipe", "pipe", "pipe"],
110+
...(agentPathRequiresWindowsShell(opt.agentPath) ? { shell: true } : {}),
109111
});
110112

111113
this.proc.stderr?.on("data", (chunk: Buffer) => {

src/agentModels.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,35 @@ import * as path from "path";
55
import type { AgentFileLogger } from "./logging/agentFileLog";
66
import { augmentPathEnv } from "./util/agentEnv";
77

8+
/** Default install layout for the Cursor Agent CLI on Windows (see `%LOCALAPPDATA%\\cursor-agent\\`). */
9+
function defaultAgentPathWhenUnset(): string {
10+
if (process.platform === "win32") {
11+
const localAppData =
12+
process.env.LOCALAPPDATA ?? path.join(os.homedir(), "AppData", "Local");
13+
return path.join(localAppData, "cursor-agent", "agent.cmd");
14+
}
15+
return path.join(os.homedir(), ".local", "bin", "agent");
16+
}
17+
818
export function expandAgentPath(configured: string): string {
919
const p = configured.trim();
1020
if (p) return p.replace(/^~(?=$|[\\/])/, os.homedir());
11-
return path.join(os.homedir(), ".local", "bin", "agent");
21+
return defaultAgentPathWhenUnset();
22+
}
23+
24+
/**
25+
* Node 22+ on Windows: `spawn` / `execFile` on `.cmd`, `.bat`, or `.ps1` without `shell: true`
26+
* fails with `EINVAL` (security-related behavior). Cursor’s Windows shim is usually `agent.cmd`.
27+
*/
28+
export function agentPathRequiresWindowsShell(agentPath: string): boolean {
29+
if (process.platform !== "win32") return false;
30+
return /\.(cmd|bat|ps1)$/i.test(agentPath.trim());
1231
}
1332

1433
export function agentBinaryExists(agentPath: string): boolean {
1534
try {
35+
if (!fs.existsSync(agentPath)) return false;
36+
if (process.platform === "win32") return true;
1637
fs.accessSync(agentPath, fs.constants.X_OK);
1738
return true;
1839
} catch {
@@ -76,6 +97,7 @@ export async function fetchAvailableModels(
7697
timeout: 45000,
7798
maxBuffer: 4 * 1024 * 1024,
7899
env,
100+
...(agentPathRequiresWindowsShell(agentPath) ? { shell: true as const } : {}),
79101
};
80102

81103
const run = (args: string[]) =>

src/ui/CursorAgentSettingTab.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,11 @@ export class CursorAgentSettingTab extends PluginSettingTab {
2222

2323
new Setting(containerEl)
2424
.setName("Agent binary path")
25-
.setDesc("Leave empty to use ~/.local/bin/agent.")
25+
.setDesc(
26+
process.platform === "win32"
27+
? "Leave empty to use %LOCALAPPDATA%\\cursor-agent\\agent.cmd (Cursor’s Windows shim)."
28+
: "Leave empty to use ~/.local/bin/agent."
29+
)
2630
.addText((t) =>
2731
t
2832
.setPlaceholder("~/.local/bin/agent")

0 commit comments

Comments
 (0)