mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-04 09:13:08 +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)
|
* Extract prompt text from ExecuteOptions
|
||||||
const model = stripProviderPrefix(options.model || 'auto');
|
* Used to pass prompt via stdin instead of CLI args to avoid shell escaping issues
|
||||||
|
*/
|
||||||
// Build prompt content
|
private extractPromptText(options: ExecuteOptions): string {
|
||||||
let promptText: string;
|
|
||||||
if (typeof options.prompt === 'string') {
|
if (typeof options.prompt === 'string') {
|
||||||
promptText = options.prompt;
|
return options.prompt;
|
||||||
} else if (Array.isArray(options.prompt)) {
|
} else if (Array.isArray(options.prompt)) {
|
||||||
promptText = options.prompt
|
return options.prompt
|
||||||
.filter((p) => p.type === 'text' && p.text)
|
.filter((p) => p.type === 'text' && p.text)
|
||||||
.map((p) => p.text)
|
.map((p) => p.text)
|
||||||
.join('\n');
|
.join('\n');
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Invalid prompt format');
|
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
|
// 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[] = [
|
const cliArgs: string[] = [
|
||||||
'-p', // Print mode (non-interactive)
|
'-p', // Print mode (non-interactive)
|
||||||
'--force', // Allow file modifications
|
'--force', // Allow file modifications
|
||||||
@@ -145,8 +151,8 @@ export class CursorProvider extends CliProvider {
|
|||||||
cliArgs.push('--model', model);
|
cliArgs.push('--model', model);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the prompt
|
// Use '-' to indicate reading prompt from stdin
|
||||||
cliArgs.push(promptText);
|
cliArgs.push('-');
|
||||||
|
|
||||||
return cliArgs;
|
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 cliArgs = this.buildCliArgs(options);
|
||||||
const subprocessOptions = this.buildSubprocessOptions(options, cliArgs);
|
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;
|
let sessionId: string | undefined;
|
||||||
|
|
||||||
// Dedup state for Cursor-specific text block handling
|
// Dedup state for Cursor-specific text block handling
|
||||||
|
|||||||
@@ -12,6 +12,12 @@ export interface SubprocessOptions {
|
|||||||
env?: Record<string, string>;
|
env?: Record<string, string>;
|
||||||
abortController?: AbortController;
|
abortController?: AbortController;
|
||||||
timeout?: number; // Milliseconds of no output before timeout
|
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 {
|
export interface SubprocessResult {
|
||||||
@@ -24,22 +30,33 @@ export interface SubprocessResult {
|
|||||||
* Spawns a subprocess and streams JSONL output line-by-line
|
* Spawns a subprocess and streams JSONL output line-by-line
|
||||||
*/
|
*/
|
||||||
export async function* spawnJSONLProcess(options: SubprocessOptions): AsyncGenerator<unknown> {
|
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 = {
|
const processEnv = {
|
||||||
...process.env,
|
...process.env,
|
||||||
...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}`);
|
console.log(`[SubprocessManager] Working directory: ${cwd}`);
|
||||||
|
if (stdinData) {
|
||||||
|
console.log(`[SubprocessManager] Passing ${stdinData.length} bytes via stdin`);
|
||||||
|
}
|
||||||
|
|
||||||
const childProcess: ChildProcess = spawn(command, args, {
|
const childProcess: ChildProcess = spawn(command, args, {
|
||||||
cwd,
|
cwd,
|
||||||
env: processEnv,
|
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 stderrOutput = '';
|
||||||
let lastOutputTime = Date.now();
|
let lastOutputTime = Date.now();
|
||||||
let timeoutHandle: NodeJS.Timeout | null = null;
|
let timeoutHandle: NodeJS.Timeout | null = null;
|
||||||
|
|||||||
Reference in New Issue
Block a user