fix(cursor): Pass prompt via stdin to avoid shell escaping issues

When passing file content (containing TypeScript code) to cursor-agent via
WSL, bash was interpreting shell metacharacters like $(), backticks, etc.
as command substitution, causing errors like "/bin/bash: typescript\r':
command not found".

Changes:
- subprocess.ts: Add stdinData option to SubprocessOptions interface
- subprocess.ts: Write stdinData to stdin when provided
- cursor-provider.ts: Extract prompt text separately and pass via stdin
- cursor-provider.ts: Use '-' as prompt arg to indicate reading from stdin

This ensures file content with code examples is passed safely without
shell interpretation.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Kacper
2025-12-30 15:07:08 +01:00
parent 34e51ddc3d
commit ed66fdd57d
2 changed files with 43 additions and 13 deletions

View File

@@ -114,24 +114,30 @@ export class CursorProvider extends CliProvider {
};
}
buildCliArgs(options: ExecuteOptions): string[] {
// Extract model (strip 'cursor-' prefix if present)
const model = stripProviderPrefix(options.model || 'auto');
// Build prompt content
let promptText: string;
/**
* Extract prompt text from ExecuteOptions
* Used to pass prompt via stdin instead of CLI args to avoid shell escaping issues
*/
private extractPromptText(options: ExecuteOptions): string {
if (typeof options.prompt === 'string') {
promptText = options.prompt;
return options.prompt;
} else if (Array.isArray(options.prompt)) {
promptText = options.prompt
return options.prompt
.filter((p) => p.type === 'text' && p.text)
.map((p) => p.text)
.join('\n');
} else {
throw new Error('Invalid prompt format');
}
}
buildCliArgs(options: ExecuteOptions): string[] {
// Extract model (strip 'cursor-' prefix if present)
const model = stripProviderPrefix(options.model || 'auto');
// Build CLI arguments for cursor-agent
// NOTE: Prompt is NOT included here - it's passed via stdin to avoid
// shell escaping issues when content contains $(), backticks, etc.
const cliArgs: string[] = [
'-p', // Print mode (non-interactive)
'--force', // Allow file modifications
@@ -145,8 +151,8 @@ export class CursorProvider extends CliProvider {
cliArgs.push('--model', model);
}
// Add the prompt
cliArgs.push(promptText);
// Use '-' to indicate reading prompt from stdin
cliArgs.push('-');
return cliArgs;
}
@@ -439,9 +445,16 @@ export class CursorProvider extends CliProvider {
);
}
// Extract prompt text to pass via stdin (avoids shell escaping issues)
const promptText = this.extractPromptText(options);
const cliArgs = this.buildCliArgs(options);
const subprocessOptions = this.buildSubprocessOptions(options, cliArgs);
// Pass prompt via stdin to avoid shell interpretation of special characters
// like $(), backticks, etc. that may appear in file content
subprocessOptions.stdinData = promptText;
let sessionId: string | undefined;
// Dedup state for Cursor-specific text block handling

View File

@@ -12,6 +12,12 @@ export interface SubprocessOptions {
env?: Record<string, string>;
abortController?: AbortController;
timeout?: number; // Milliseconds of no output before timeout
/**
* Data to write to stdin after process spawns.
* Use this for passing prompts/content that may contain shell metacharacters.
* Avoids shell interpretation issues when passing data as CLI arguments.
*/
stdinData?: string;
}
export interface SubprocessResult {
@@ -24,22 +30,33 @@ export interface SubprocessResult {
* Spawns a subprocess and streams JSONL output line-by-line
*/
export async function* spawnJSONLProcess(options: SubprocessOptions): AsyncGenerator<unknown> {
const { command, args, cwd, env, abortController, timeout = 30000 } = options;
const { command, args, cwd, env, abortController, timeout = 30000, stdinData } = options;
const processEnv = {
...process.env,
...env,
};
console.log(`[SubprocessManager] Spawning: ${command} ${args.slice(0, -1).join(' ')}`);
// Log command without stdin data (which may be large/sensitive)
console.log(`[SubprocessManager] Spawning: ${command} ${args.join(' ')}`);
console.log(`[SubprocessManager] Working directory: ${cwd}`);
if (stdinData) {
console.log(`[SubprocessManager] Passing ${stdinData.length} bytes via stdin`);
}
const childProcess: ChildProcess = spawn(command, args, {
cwd,
env: processEnv,
stdio: ['ignore', 'pipe', 'pipe'],
// Use 'pipe' for stdin when we need to write data, otherwise 'ignore'
stdio: [stdinData ? 'pipe' : 'ignore', 'pipe', 'pipe'],
});
// Write stdin data if provided
if (stdinData && childProcess.stdin) {
childProcess.stdin.write(stdinData);
childProcess.stdin.end();
}
let stderrOutput = '';
let lastOutputTime = Date.now();
let timeoutHandle: NodeJS.Timeout | null = null;