Files
shockbot/agents/cursor.ts
T
Colin McDonnell 06a19567c0 Switch to payload
2025-11-18 23:15:44 -08:00

131 lines
4.4 KiB
TypeScript

import { spawn } from "node:child_process";
import { mkdirSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { log } from "../utils/cli.ts";
import { addInstructions } from "./instructions.ts";
import { agent, type ConfigureMcpServersParams, installFromCurl } from "./shared.ts";
export const cursor = agent({
name: "cursor",
inputKeys: ["cursor_api_key"],
install: async () => {
return await installFromCurl({
installUrl: "https://cursor.com/install",
executableName: "cursor-agent",
});
},
run: async ({ payload, apiKey, cliPath, githubInstallationToken, mcpServers }) => {
process.env.CURSOR_API_KEY = apiKey;
process.env.GITHUB_INSTALLATION_TOKEN = githubInstallationToken;
configureCursorMcpServers({ mcpServers, cliPath });
try {
// Run cursor-agent in non-interactive mode with the prompt
// Using -p flag for prompt, --output-format text for plain text output
// and --approve-mcps to automatically approve all MCP servers
const fullPrompt = addInstructions(payload);
// Find temp directory from cliPath to set HOME for MCP config lookup
const tempDir = cliPath.split("/.local/bin/")[0];
log.info("Running Cursor CLI...");
// Use spawn to handle streaming output
// Use --print flag explicitly for non-interactive mode
return new Promise((resolve) => {
const child = spawn(
cliPath,
["--print", fullPrompt, "--output-format", "text", "--approve-mcps", "--force"],
{
cwd: process.cwd(), // Run in current working directory
env: {
...process.env,
CURSOR_API_KEY: apiKey,
GITHUB_INSTALLATION_TOKEN: githubInstallationToken,
HOME: tempDir, // Set HOME so Cursor CLI can find .cursor/mcp.json
},
stdio: ["ignore", "pipe", "pipe"], // Ignore stdin, pipe stdout/stderr
}
);
let stdout = "";
let stderr = "";
// Log when process starts
child.on("spawn", () => {
log.debug("Cursor CLI process spawned");
});
child.stdout?.on("data", (data) => {
const text = data.toString();
stdout += text;
// Stream output in real-time
process.stdout.write(text);
});
child.stderr?.on("data", (data) => {
const text = data.toString();
stderr += text;
// Log errors as they come - but also write to stdout so we can see it
process.stderr.write(text);
log.warning(text);
});
// Handle process exit
child.on("close", (code, signal) => {
if (signal) {
log.warning(`Cursor CLI terminated by signal: ${signal}`);
}
if (code === 0) {
log.success("Cursor CLI completed successfully");
resolve({
success: true,
output: stdout.trim(),
});
} else {
const errorMessage = stderr || `Cursor CLI exited with code ${code}`;
log.error(`Cursor CLI failed: ${errorMessage}`);
resolve({
success: false,
error: errorMessage,
output: stdout.trim(),
});
}
});
child.on("error", (error) => {
const errorMessage = error.message || String(error);
log.error(`Cursor CLI execution failed: ${errorMessage}`);
resolve({
success: false,
error: errorMessage,
output: stdout.trim(),
});
});
});
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
log.error(`Cursor execution failed: ${errorMessage}`);
return {
success: false,
error: errorMessage,
output: "",
};
}
},
});
/**
* Configure MCP servers for Cursor by writing to the Cursor configuration file.
* For cursor, we need to add the MCP servers to the Cursor configuration file manually as there is no CLI command to do this.
*/
function configureCursorMcpServers({ mcpServers, cliPath }: ConfigureMcpServersParams) {
const tempDir = cliPath.split("/.local/bin/")[0];
const cursorConfigDir = join(tempDir, ".cursor");
const mcpConfigPath = join(cursorConfigDir, "mcp.json");
mkdirSync(cursorConfigDir, { recursive: true });
writeFileSync(mcpConfigPath, JSON.stringify({ mcpServers }, null, 2), "utf-8");
}