diff --git a/apps/server/src/providers/cursor-provider.ts b/apps/server/src/providers/cursor-provider.ts index 14effe8e..941eb967 100644 --- a/apps/server/src/providers/cursor-provider.ts +++ b/apps/server/src/providers/cursor-provider.ts @@ -140,12 +140,18 @@ export class CursorProvider extends CliProvider { // shell escaping issues when content contains $(), backticks, etc. const cliArgs: string[] = [ '-p', // Print mode (non-interactive) - '--force', // Allow file modifications '--output-format', 'stream-json', '--stream-partial-output', // Real-time streaming ]; + // Only add --force if NOT in read-only mode + // Without --force, Cursor CLI suggests changes but doesn't apply them + // With --force, Cursor CLI can actually edit files + if (!options.readOnly) { + cliArgs.push('--force'); + } + // Add model if not auto if (model !== 'auto') { cliArgs.push('--model', model); diff --git a/apps/server/src/providers/types.ts b/apps/server/src/providers/types.ts index 5fcdb3e6..2d7bfebe 100644 --- a/apps/server/src/providers/types.ts +++ b/apps/server/src/providers/types.ts @@ -34,6 +34,12 @@ export interface ExecuteOptions { conversationHistory?: ConversationMessage[]; // Previous messages for context sdkSessionId?: string; // Claude SDK session ID for resuming conversations settingSources?: Array<'user' | 'project' | 'local'>; // Claude filesystem settings to load + /** + * If true, the provider should run in read-only mode (no file modifications). + * For Cursor CLI, this omits the --force flag, making it suggest-only. + * Default: false (allows edits) + */ + readOnly?: boolean; } /** diff --git a/apps/server/src/routes/app-spec/generate-features-from-spec.ts b/apps/server/src/routes/app-spec/generate-features-from-spec.ts index 8b8e4a4a..8aea59ba 100644 --- a/apps/server/src/routes/app-spec/generate-features-from-spec.ts +++ b/apps/server/src/routes/app-spec/generate-features-from-spec.ts @@ -132,6 +132,7 @@ IMPORTANT: Do not ask for clarification. The specification is provided above. Ge maxTurns: 250, allowedTools: ['Read', 'Glob', 'Grep'], abortController, + readOnly: true, // Feature generation only reads code, doesn't write })) { messageCount++; diff --git a/apps/server/src/routes/app-spec/generate-spec.ts b/apps/server/src/routes/app-spec/generate-spec.ts index 87ccf1e7..98985c1e 100644 --- a/apps/server/src/routes/app-spec/generate-spec.ts +++ b/apps/server/src/routes/app-spec/generate-spec.ts @@ -132,6 +132,7 @@ ${JSON.stringify(specOutputSchema, null, 2)}`; maxTurns: 250, allowedTools: ['Read', 'Glob', 'Grep'], abortController, + readOnly: true, // Spec generation only reads code, we write the spec ourselves })) { messageCount++; diff --git a/apps/server/src/routes/backlog-plan/generate-plan.ts b/apps/server/src/routes/backlog-plan/generate-plan.ts index 78d6f141..8ff58b68 100644 --- a/apps/server/src/routes/backlog-plan/generate-plan.ts +++ b/apps/server/src/routes/backlog-plan/generate-plan.ts @@ -183,6 +183,7 @@ Please analyze the current backlog and the user's request, then provide a JSON p allowedTools: [], // No tools needed for this abortController, settingSources: autoLoadClaudeMd ? ['user', 'project'] : undefined, + readOnly: true, // Plan generation only generates text, doesn't write files }); let responseText = ''; diff --git a/apps/server/src/routes/context/routes/describe-file.ts b/apps/server/src/routes/context/routes/describe-file.ts index 3b30347c..08da54ca 100644 --- a/apps/server/src/routes/context/routes/describe-file.ts +++ b/apps/server/src/routes/context/routes/describe-file.ts @@ -207,6 +207,7 @@ File: ${fileName}${truncated ? ' (truncated)' : ''}`; cwd, maxTurns: 1, allowedTools: [], + readOnly: true, // File description only reads, doesn't write })) { if (msg.type === 'assistant' && msg.message?.content) { for (const block of msg.message.content) { diff --git a/apps/server/src/routes/context/routes/describe-image.ts b/apps/server/src/routes/context/routes/describe-image.ts index 3b87bd7b..c445a243 100644 --- a/apps/server/src/routes/context/routes/describe-image.ts +++ b/apps/server/src/routes/context/routes/describe-image.ts @@ -371,6 +371,7 @@ export function createDescribeImageHandler( cwd, maxTurns: 1, allowedTools: ['Read'], // Allow Read tool so Cursor can read the image if needed + readOnly: true, // Image description only reads, doesn't write })) { if (msg.type === 'assistant' && msg.message?.content) { for (const block of msg.message.content) { diff --git a/apps/server/src/routes/enhance-prompt/routes/enhance.ts b/apps/server/src/routes/enhance-prompt/routes/enhance.ts index 3cbea851..30a3b1a0 100644 --- a/apps/server/src/routes/enhance-prompt/routes/enhance.ts +++ b/apps/server/src/routes/enhance-prompt/routes/enhance.ts @@ -97,6 +97,7 @@ async function executeWithCursor(prompt: string, model: string): Promise prompt, model, cwd: process.cwd(), // Enhancement doesn't need a specific working directory + readOnly: true, // Prompt enhancement only generates text, doesn't write files })) { if (msg.type === 'assistant' && msg.message?.content) { for (const block of msg.message.content) { diff --git a/apps/server/src/routes/github/routes/validate-issue.ts b/apps/server/src/routes/github/routes/validate-issue.ts index 29f55ed2..b51435b8 100644 --- a/apps/server/src/routes/github/routes/validate-issue.ts +++ b/apps/server/src/routes/github/routes/validate-issue.ts @@ -111,6 +111,7 @@ ${prompt}`; prompt: cursorPrompt, model, cwd: projectPath, + readOnly: true, // Issue validation only reads code, doesn't write })) { if (msg.type === 'assistant' && msg.message?.content) { for (const block of msg.message.content) { diff --git a/apps/server/src/routes/suggestions/generate-suggestions.ts b/apps/server/src/routes/suggestions/generate-suggestions.ts index a0eb60f1..fd4c6a50 100644 --- a/apps/server/src/routes/suggestions/generate-suggestions.ts +++ b/apps/server/src/routes/suggestions/generate-suggestions.ts @@ -202,6 +202,7 @@ ${JSON.stringify(suggestionsSchema, null, 2)}`; maxTurns: 250, allowedTools: ['Read', 'Glob', 'Grep'], abortController, + readOnly: true, // Suggestions only reads code, doesn't write })) { if (msg.type === 'assistant' && msg.message?.content) { for (const block of msg.message.content) {