Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Ensure shell scripts use LF for Docker/Linux compatibility
docker-entrypoint.sh text eol=lf
8 changes: 5 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,9 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
&& GH_VERSION="2.63.2" \
&& ARCH=$(uname -m) \
&& case "$ARCH" in \
x86_64) GH_ARCH="amd64" ;; \
aarch64|arm64) GH_ARCH="arm64" ;; \
*) echo "Unsupported architecture: $ARCH" && exit 1 ;; \
x86_64) GH_ARCH="amd64" ;; \
aarch64|arm64) GH_ARCH="arm64" ;; \
*) echo "Unsupported architecture: $ARCH" && exit 1 ;; \
esac \
&& curl -L "https://github.com/cli/cli/releases/download/v${GH_VERSION}/gh_${GH_VERSION}_linux_${GH_ARCH}.tar.gz" -o gh.tar.gz \
&& tar -xzf gh.tar.gz \
Expand Down Expand Up @@ -167,7 +167,9 @@ RUN git config --system --add safe.directory '*' && \
git config --system credential.helper '!gh auth git-credential'

# Copy entrypoint script for fixing permissions on mounted volumes
# Strip CRLF if present (Windows checkout) so Linux can execute it
COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
RUN sed -i 's/\r$//' /usr/local/bin/docker-entrypoint.sh
RUN chmod +x /usr/local/bin/docker-entrypoint.sh

# Note: We stay as root here so entrypoint can fix permissions
Expand Down
2 changes: 1 addition & 1 deletion apps/server/src/providers/cli-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -606,7 +606,7 @@ export abstract class CliProvider extends BaseProvider {
if (typeof options.prompt === 'string') {
return {
...options,
prompt: `${systemText}\n\n---\n\n${options.prompt}`,
prompt: `${systemText}\n\n=== IMPLEMENTATION TASK ===\n\n${options.prompt}`,
systemPrompt: undefined,
};
}
Expand Down
180 changes: 156 additions & 24 deletions apps/server/src/providers/cursor-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,15 @@ import { execSync } from 'child_process';
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import { findCliInWsl, isWslAvailable } from '@automaker/platform';
import {
findCliInWsl,
isWslAvailable,
windowsToWslPath,
spawnJSONLProcess,
execInWsl,
getAutomakerDir,
type SubprocessOptions,
} from '@automaker/platform';
import {
CliProvider,
type CliSpawnConfig,
Expand All @@ -29,7 +37,7 @@ import type {
ModelDefinition,
ContentBlock,
} from './types.js';
import { validateBareModelId } from '@automaker/types';
import { validateBareModelId, calculateReasoningTimeout } from '@automaker/types';
import { validateApiKey } from '../lib/auth-utils.js';
import { getEffectivePermissions, detectProfile } from '../services/cursor-config-service.js';
import {
Expand All @@ -42,7 +50,7 @@ import {
CURSOR_MODEL_MAP,
} from '@automaker/types';
import { createLogger, isAbortError } from '@automaker/utils';
import { spawnJSONLProcess, execInWsl } from '@automaker/platform';
import { randomBytes } from 'crypto';

// Create logger for this module
const logger = createLogger('CursorProvider');
Expand Down Expand Up @@ -209,9 +217,9 @@ function formatCursorToolResult(toolCall: CursorToolCallEvent['tool_call']): str
for (const [key, handler] of Object.entries(CURSOR_TOOL_HANDLERS)) {
const toolData = toolCall[key as keyof typeof toolCall] as
| {
args?: unknown;
result?: { success?: unknown; rejected?: { reason: string } };
}
args?: unknown;
result?: { success?: unknown; rejected?: { reason: string } };
}
| undefined;

if (toolData?.result) {
Expand Down Expand Up @@ -399,9 +407,14 @@ export class CursorProvider extends CliProvider {
};
}

/**
* Max prompt length to pass as CLI arg. Longer prompts use a temp file to avoid
* platform command-line limits (~32KB Windows, ~2MB Linux).
*/
private static readonly PROMPT_ARG_MAX_LENGTH = 24 * 1024;

/**
* 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') {
Expand All @@ -416,13 +429,17 @@ export class CursorProvider extends CliProvider {
}
}

buildCliArgs(options: ExecuteOptions): string[] {
buildCliArgs(
options: ExecuteOptions,
extras?: { omitPrompt?: boolean }
): string[] {
// Model is already bare (no prefix) - validated by executeQuery
const model = 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.
// Build CLI arguments for cursor-agent.
// NOTE: cursor-agent does NOT support reading from stdin (unlike codex). Passing "-"
// as the prompt causes it to literally receive "-" as the message ("your message was
// just a dash"). We pass the prompt as the final positional argument instead.
const cliArgs: string[] = [];

// If using Cursor IDE (cliPath is 'cursor' not 'cursor-agent'), add 'agent' subcommand
Expand Down Expand Up @@ -455,12 +472,96 @@ export class CursorProvider extends CliProvider {
cliArgs.push('--resume', options.sdkSessionId);
}

// Use '-' to indicate reading prompt from stdin
cliArgs.push('-');
// Pass prompt as final positional argument (cursor-agent has no stdin support for prompts).
// Omit when using temp-file fallback for long prompts.
if (!extras?.omitPrompt) {
cliArgs.push(this.extractPromptText(options));
}

return cliArgs;
}

/**
* Escape a string for use inside a bash single-quoted literal.
* Single quotes are escaped as '\'' (end quote, escaped quote, start quote).
*/
private escapeForBashSingleQuoted(s: string): string {
return "'" + s.replace(/'/g, "'\\''") + "'";
}

/**
* Build subprocess options when using a temp file for the prompt.
* Runs cursor-agent via bash -c '... "$(cat /path)"' to avoid command-line length limits.
*/
private buildSubprocessOptionsWithPromptFile(
options: ExecuteOptions,
cliArgs: string[],
promptFilePath: string
): SubprocessOptions {
this.ensureCliDetected();
if (!this.cliPath) {
throw new Error(`${this.getCliName()} CLI not found. ${this.getInstallInstructions()}`);
}

const cwd = options.cwd || process.cwd();
const timeout = calculateReasoningTimeout(options.reasoningEffort, 120000);

const filteredEnv: Record<string, string> = {};
for (const [key, value] of Object.entries(process.env)) {
if (value !== undefined) {
filteredEnv[key] = value;
}
}

const escapedArgs = cliArgs.map((a) => this.escapeForBashSingleQuoted(a)).join(' ');
const escapedPath = this.escapeForBashSingleQuoted(promptFilePath);

if (this.useWsl && this.wslCliPath) {
const wslCwd = windowsToWslPath(cwd);
const wslPromptPath = windowsToWslPath(promptFilePath);
const wslEscapedPath = this.escapeForBashSingleQuoted(wslPromptPath);
const fullCommand = `${this.escapeForBashSingleQuoted(this.wslCliPath)} ${escapedArgs} "$(cat ${wslEscapedPath})"`;

const wslArgs = this.wslDistribution
? ['-d', this.wslDistribution, '--cd', wslCwd, 'bash', '-c', fullCommand]
: ['--cd', wslCwd, 'bash', '-c', fullCommand];

return {
command: 'wsl.exe',
args: wslArgs,
cwd,
env: filteredEnv,
abortController: options.abortController,
timeout,
};
}

// Native (Linux/macOS) or npx/direct on Windows
const cliPath = this.cliPath;
const fullCommand = `${this.escapeForBashSingleQuoted(cliPath)} ${escapedArgs} "$(cat ${escapedPath})"`;

if (this.detectedStrategy === 'npx') {
const allArgs = [...this.npxArgs, 'bash', '-c', fullCommand];
return {
command: 'npx',
args: allArgs,
cwd,
env: filteredEnv,
abortController: options.abortController,
timeout,
};
}

return {
command: 'bash',
args: ['-c', fullCommand],
cwd,
env: filteredEnv,
abortController: options.abortController,
timeout,
};
Comment on lines +540 to +566
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
printf '\nWindows detection branches:\n'
rg -n -C2 "useWsl: false|strategy: 'cmd'|strategy: 'direct'" apps/server/src/providers/cursor-provider.ts
printf '\nTemp-file subprocess branches:\n'
rg -n -C2 "buildSubprocessOptionsWithPromptFile|command: 'bash'|command: 'wsl.exe'" apps/server/src/providers/cursor-provider.ts

Repository: AutoMaker-Org/automaker

Length of output: 1606


🏁 Script executed:

sed -n '497,563p' apps/server/src/providers/cursor-provider.ts

Repository: AutoMaker-Org/automaker

Length of output: 2242


🏁 Script executed:

sed -n '400,500p' apps/server/src/providers/cursor-provider.ts | head -80

Repository: AutoMaker-Org/automaker

Length of output: 2726


🏁 Script executed:

rg -n "buildSubprocessOptions[^W]|strategy.*cmd|strategy.*direct" apps/server/src/providers/cursor-provider.ts | head -20

Repository: AutoMaker-Org/automaker

Length of output: 468


🏁 Script executed:

rg -n "private buildSubprocessOptions\(" apps/server/src/providers/cursor-provider.ts -A 100 | head -150

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

rg -n "buildSubprocessOptions" apps/server/src/providers/cursor-provider.ts

Repository: AutoMaker-Org/automaker

Length of output: 348


🏁 Script executed:

rg -n "buildSubprocessOptions[^W]" apps/server/src/providers/cursor-provider.ts -B 2 -A 50

Repository: AutoMaker-Org/automaker

Length of output: 3550


🏁 Script executed:

grep -n "buildSubprocessOptions" apps/server/src/providers/cursor-provider.ts | grep -v "buildSubprocessOptionsWithPromptFile"

Repository: AutoMaker-Org/automaker

Length of output: 223


🏁 Script executed:

rg -n "^\s*(private|public|protected).*buildSubprocessOptions" apps/server/src/providers/cursor-provider.ts

Repository: AutoMaker-Org/automaker

Length of output: 118


🏁 Script executed:

rg -n "buildSubprocessOptions\s*\(" apps/server/src/providers/cursor-provider.ts -B 5 | grep -E "(private|public|protected|buildSubprocessOptions)" | head -20

Repository: AutoMaker-Org/automaker

Length of output: 144


🏁 Script executed:

grep -n "class.*CursorProvider\|extends" apps/server/src/providers/cursor-provider.ts | head -5

Repository: AutoMaker-Org/automaker

Length of output: 169


🏁 Script executed:

rg -l "class CliProvider" apps/server/src/

Repository: AutoMaker-Org/automaker

Length of output: 108


🏁 Script executed:

rg -n "buildSubprocessOptions" apps/server/src/providers/ -l

Repository: AutoMaker-Org/automaker

Length of output: 245


🏁 Script executed:

rg -n "buildSubprocessOptions" apps/server/src/providers/cli-provider.ts -A 50 | head -100

Repository: AutoMaker-Org/automaker

Length of output: 3838


🏁 Script executed:

rg -n "buildSubprocessOptions" apps/server/src/providers/cli-provider.ts -A 80 | tail -50

Repository: AutoMaker-Org/automaker

Length of output: 1883


🏁 Script executed:

sed -n '446,510p' apps/server/src/providers/cli-provider.ts

Repository: AutoMaker-Org/automaker

Length of output: 2209


🏁 Script executed:

sed -n '510,525p' apps/server/src/providers/cli-provider.ts

Repository: AutoMaker-Org/automaker

Length of output: 441


Add handler for native Windows (cmd/direct) strategy in buildSubprocessOptionsWithPromptFile to prevent bash command failure on long prompts.

The method buildSubprocessOptionsWithPromptFile (lines 540-563) only handles WSL and NPX strategies, then defaults to bash -c. However, detectCli() returns strategy: 'cmd' or strategy: 'direct' with useWsl: false for native Windows installations (cursor-agent.exe, .cmd, direct exe paths). When a prompt exceeds 24 KB and triggers this method, Windows native installs will attempt to run bash -c, which fails because those systems don't necessarily have bash available. The parent class buildSubprocessOptions correctly handles these strategies by returning a direct command spawn, but the temp-file variant doesn't.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/providers/cursor-provider.ts` around lines 540 - 563,
buildSubprocessOptionsWithPromptFile currently falls through to "bash -c" for
non-npx cases, which breaks native Windows ('cmd' or 'direct') installs; update
the function to explicitly handle this.detectedStrategy === 'cmd' ||
this.detectedStrategy === 'direct' (and not using WSL) by returning a direct
spawn instead of the bash wrapper: use this.cliPath as the command and pass the
prompt file reference/escaped args as normal args (use the same
escapedArgs/escapedPath variables used in the existing code) and preserve cwd,
filteredEnv, abortController/options.timeout; keep the existing npx branch
intact and only add this new branch before the final bash fallback so Windows
native installs run the binary directly.

}

/**
* Convert Cursor event to AutoMaker ProviderMessage format
* Made public as required by CliProvider abstract method
Expand Down Expand Up @@ -503,7 +604,7 @@ export class CursorProvider extends CliProvider {
const toolCallKeys = Object.keys(toolCall);
logger.warn(
`[UNHANDLED TOOL_CALL] Unknown tool call structure. Keys: ${toolCallKeys.join(', ')}. ` +
`Full tool_call: ${JSON.stringify(toolCall).substring(0, 500)}`
`Full tool_call: ${JSON.stringify(toolCall).substring(0, 500)}`
);
return null;
}
Expand Down Expand Up @@ -861,23 +962,45 @@ export class CursorProvider extends CliProvider {
const serverCount = Object.keys(options.mcpServers).length;
logger.warn(
`MCP servers configured (${serverCount}) but not yet supported by Cursor CLI in AutoMaker. ` +
`MCP support for Cursor will be added in a future release. ` +
`The configured MCP servers will be ignored for this execution.`
`MCP support for Cursor will be added in a future release. ` +
`The configured MCP servers will be ignored for this execution.`
);
}

// Embed system prompt into user prompt (Cursor CLI doesn't support separate system messages)
const effectiveOptions = this.embedSystemPromptIntoPrompt(options);

// Extract prompt text to pass via stdin (avoids shell escaping issues)
// Extract prompt text (cursor-agent expects it as positional arg, not stdin)
const promptText = this.extractPromptText(effectiveOptions);

const cliArgs = this.buildCliArgs(effectiveOptions);
const subprocessOptions = this.buildSubprocessOptions(options, cliArgs);
const useTempFile = promptText.length > CursorProvider.PROMPT_ARG_MAX_LENGTH;
let promptFilePath: string | null = null;

let cliArgs: string[];
let subprocessOptions: ReturnType<typeof this.buildSubprocessOptions>;

if (useTempFile) {
// Temp file fallback for long prompts (avoids platform command-line limits)
const cwd = options.cwd || process.cwd();
const automakerDir = getAutomakerDir(cwd);
const promptId = randomBytes(8).toString('hex');
promptFilePath = path.join(automakerDir, `.cursor-prompt-${promptId}`);
fs.mkdirSync(automakerDir, { recursive: true });
fs.writeFileSync(promptFilePath, promptText, 'utf8');
Comment on lines +992 to +993
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify how temp prompt files are written
rg -n -A2 -B2 "writeFileSync\\(promptFilePath" apps/server/src/providers/cursor-provider.ts

Repository: AutoMaker-Org/automaker

Length of output: 394


🏁 Script executed:

#!/bin/bash
# Find all writeFileSync calls in cursor-provider.ts to see if there are other cases
rg -n "writeFileSync" apps/server/src/providers/cursor-provider.ts

# Also check for other prompt/sensitive file writes in the server directory
rg -n "writeFileSync.*prompt" apps/server/src/

Repository: AutoMaker-Org/automaker

Length of output: 239


🏁 Script executed:

#!/bin/bash
# Check for all writeFileSync calls in cursor-provider.ts to see if other files need protection
rg -n "writeFileSync" apps/server/src/providers/cursor-provider.ts -A 1 -B 1

# Check if there are other sensitive file writes (cache, config, state, etc.) in the file
rg -n "writeFileSync" apps/server/src/providers/cursor-provider.ts | wc -l

Repository: AutoMaker-Org/automaker

Length of output: 215


🏁 Script executed:

#!/bin/bash
# Get more context around the writeFileSync call to understand the sensitivity
sed -n '970,1010p' apps/server/src/providers/cursor-provider.ts

# Also check for other writeFileSync calls in the broader server directory
rg -n "writeFileSync" apps/server/src/ | head -20

Repository: AutoMaker-Org/automaker

Length of output: 2586


🏁 Script executed:

#!/bin/bash
# Check the secure-fs.ts implementation to understand the pattern
cat -n apps/server/src/lib/secure-fs.ts

# Check how auth.ts uses secureFs
grep -n -A 3 -B 3 "secureFs.writeFileSync" apps/server/src/lib/auth.ts

Repository: AutoMaker-Org/automaker

Length of output: 1360


🏁 Script executed:

#!/bin/bash
# Check what fs module is imported in cursor-provider.ts
head -50 apps/server/src/providers/cursor-provider.ts | grep -n "import.*fs"

# Check if cursor-provider.ts should be using secureFs instead
grep -n "^import" apps/server/src/providers/cursor-provider.ts | head -20

Repository: AutoMaker-Org/automaker

Length of output: 633


🏁 Script executed:

#!/bin/bash
# Check the context around the other writeFileSync calls to see if they also write sensitive data
# Start with gemini-usage-service.ts
sed -n '745,755p' apps/server/src/services/gemini-usage-service.ts

# Check cursor-config-manager.ts
sed -n '55,65p' apps/server/src/providers/cursor-config-manager.ts

# Check the deauth files
sed -n '20,30p' apps/server/src/routes/setup/routes/deauth-cursor.ts

Repository: AutoMaker-Org/automaker

Length of output: 1153


Use secureFs for writing sensitive prompt files with restrictive permissions.

These temp prompt files contain sensitive LLM content and should use the established secureFs pattern (from @automaker/platform) with restricted file mode, matching how API keys are handled in lib/auth.ts. Replace the plain fs import and call with secureFs.writeFileSync() to ensure files are written with mode 0o600.

Proposed fix
-      fs.writeFileSync(promptFilePath, promptText, 'utf8');
+      secureFs.writeFileSync(promptFilePath, promptText, { encoding: 'utf8', mode: 0o600 });

Also update the import to use secureFs for this operation (or import it alongside fs).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/providers/cursor-provider.ts` around lines 989 - 990, The
code currently uses fs.mkdirSync and fs.writeFileSync to create automakerDir and
write prompt files (variables automakerDir, promptFilePath, promptText) but
these prompt files contain sensitive LLM content and must be written with
restricted permissions; import secureFs from '@automaker/platform' (or alongside
fs) and replace fs.writeFileSync(promptFilePath, promptText, 'utf8') with
secureFs.writeFileSync(promptFilePath, promptText, { encoding: 'utf8', mode:
0o600 }) while still ensuring the directory exists (you may keep
fs.mkdirSync(automakerDir, { recursive: true }) or use secureFs if available for
dir creation) — follow the same pattern used in lib/auth.ts for API key files.

logger.debug(
`Prompt length ${promptText.length} exceeds limit; using temp file: ${promptFilePath}`
);

// Pass prompt via stdin to avoid shell interpretation of special characters
// like $(), backticks, etc. that may appear in file content
subprocessOptions.stdinData = promptText;
cliArgs = this.buildCliArgs(effectiveOptions, { omitPrompt: true });
subprocessOptions = this.buildSubprocessOptionsWithPromptFile(
options,
cliArgs,
promptFilePath
);
Comment on lines +986 to +1003
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Expand the try/finally to cover temp-file creation.

promptFilePath is written before the try starts. If anything throws during setup after that point—buildSubprocessOptionsWithPromptFile(), getEffectivePermissions(), or detectProfile()—the finally never runs and the full prompt is left on disk.

Also applies to: 1026-1128

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/providers/cursor-provider.ts` around lines 983 - 1000, The
temp prompt file is created and written before the surrounding try/finally, so
failures in subsequent setup (e.g., buildSubprocessOptionsWithPromptFile,
buildCliArgs, getEffectivePermissions, detectProfile) can leave promptFilePath
on disk; move the temp-file creation and write (getAutomakerDir, randomBytes,
path.join, fs.mkdirSync, fs.writeFileSync) inside the same try block that
currently cleans up in finally (or expand the try to start before creating the
file) so that the finally always runs to delete promptFilePath when useTempFile
is true; keep existing calls to buildCliArgs,
buildSubprocessOptionsWithPromptFile, and subprocessOptions construction
unchanged but ensure they execute inside that try so cleanup is guaranteed.

} else {
cliArgs = this.buildCliArgs(effectiveOptions);
subprocessOptions = this.buildSubprocessOptions(options, cliArgs);
}

let sessionId: string | undefined;

Expand Down Expand Up @@ -926,8 +1049,8 @@ export class CursorProvider extends CliProvider {
.join(',') || 'unknown';
logger.info(
`[RAW TOOL_CALL] call_id=${toolEvent.call_id} types=[${toolTypes}]` +
(tc.shellToolCall ? ` cmd="${tc.shellToolCall.args?.command}"` : '') +
(tc.writeToolCall ? ` path="${tc.writeToolCall.args?.path}"` : '')
(tc.shellToolCall ? ` cmd="${tc.shellToolCall.args?.command}"` : '') +
(tc.writeToolCall ? ` path="${tc.writeToolCall.args?.path}"` : '')
);
}
}
Expand Down Expand Up @@ -993,6 +1116,15 @@ export class CursorProvider extends CliProvider {
);
}
throw error;
} finally {
if (promptFilePath) {
try {
fs.unlinkSync(promptFilePath);
logger.debug(`Removed temp prompt file: ${promptFilePath}`);
} catch {
// Ignore cleanup errors
}
}
}
}

Expand Down
1 change: 1 addition & 0 deletions apps/ui/nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ map $http_upgrade $connection_upgrade {

server {
listen 80;
listen [::]:80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
Expand Down
9 changes: 7 additions & 2 deletions docker-entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,17 @@ if [ ! -d "/home/automaker/.npm" ]; then
fi
chown -R automaker:automaker /home/automaker/.npm

# Ensure cursor-agent can create/use its config directory (~/.config/cursor)
# cursor-agent requires this directory for auth and cache; fails with EACCES if missing
CURSOR_CONFIG_DIR="/home/automaker/.config/cursor"
mkdir -p "$CURSOR_CONFIG_DIR"
chown -R automaker:automaker /home/automaker/.config
chmod -R 700 /home/automaker/.config

# If CURSOR_AUTH_TOKEN is set, write it to the cursor auth file
# On Linux, cursor-agent uses ~/.config/cursor/auth.json for file-based credential storage
# The env var CURSOR_AUTH_TOKEN is also checked directly by cursor-agent
if [ -n "$CURSOR_AUTH_TOKEN" ]; then
CURSOR_CONFIG_DIR="/home/automaker/.config/cursor"
mkdir -p "$CURSOR_CONFIG_DIR"
# Write auth.json with the access token
cat > "$CURSOR_CONFIG_DIR/auth.json" << EOF
{
Expand Down
Loading