mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-01-30 06:12:03 +00:00
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:
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user