mirror of
https://github.com/OpenRouterTeam/spawn.git
synced 2026-10-19 09:13:28 +00:00
fix(cursor-proxy): macOS local cursor CLI compatibility (#3426)
## Problem `spawn cursor local` fails on macOS with four cascading errors: ```bash chmod: /usr/local/bin/caddy: No such file or directory bash: line 4: caddy: command not found bash: /etc/hosts: Permission denied bash: line 24: setsid: command not found ``` These occur in packages/cli/src/shared/cursor-proxy.ts (setupCursorProxy and startCursorProxy). Even though local/agents.ts wires runLocal as the runServer callback, the proxy scripts it constructs were written exclusively for Linux: 1. Caddy downloads the wrong binary — `setupCursorProxy` calls `https://caddyserver.com/api/download?os=linux&arch=amd64` unconditionally. On macOS this downloads a Linux ELF binary that immediately fails to execute. 2. Caddy install path is root-owned on macOS — the script writes to `/usr/local/bin/caddy`. On macOS this directory either doesn't exist (no Homebrew) or is owned by a system process, so `chmod +x` immediately fails without sudo. 3. `/etc/hosts` is read-only without root on macOS — the domain-spoofing step runs echo `"127.0.0.1 ..." >> /etc/hosts` directly, which fails with Permission denied because `/etc/hosts` is only writable by root on macOS (unlike some Linux setups where the invoking user may own it). 4. `setsid` is Linux-only — the non-systemd fallback in startCursorProxy calls `setsid` to detach backend processes. `setsid` is a util-linux command; it doesn't ship on macOS (or BSD, or Windows). `nohup … &` is the portable equivalent. The downstream symptom of all four: Caddy never starts, the proxy is never listening, and Cursor's auth exchange fails — which surfaces as "The provided API key is invalid" even when the key is valid. ## Fix - OS/arch detection — query `process.platform` and `process.arch` (already available in Bun) to select the correct Caddy download (os=darwin, arch=arm64 | amd64 for macOS; os=linux for Linux). - User-writable install path — install Caddy to `~/.local/bin/caddy` (guaranteed writable, always exists after `mkdir -p`) instead of `/usr/local/bin/`. Add `~/.local/bin` to `PATH` in the execution environment. - `sudo` for `/etc/hosts` — prefix the `sed` and `echo` host entries with `sudo`. Wrap in a try/catch with a clear fallback message if `sudo` isn't available (some sandboxed environments). - Replace `setsid` with `nohup` — swap `setsid $NODE ...` for `nohup $NODE ... < /dev/null >> /tmp/cursor-proxy.log 2>&1 &` in the non-systemd branch. `nohup` is POSIX and present on macOS, Linux, and BSD. ## Affected code - `packages/cli/src/shared/cursor-proxy.ts` — `installCaddy` shell fragment, `hosts-spoofing` fragment, `startCursorProxy` non-systemd branch - No changes to the CloudRunner interface, tests, or other agents ## Testing Tested on macOS (Apple Silicon, M3) with SPAWN_CLI_DIR pointing at a local build: - `spawn cursor local` completes all setup steps without errors - Caddy starts on port 443, unary backend on 18644, bidi backend on 18645 - Cursor authenticates and routes completions through OpenRouter
This commit is contained in:
parent
18bbe3e9a5
commit
2221f1155b
2 changed files with 31 additions and 12 deletions
4
packages/cli/package-lock.json
generated
4
packages/cli/package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "@openrouter/spawn",
|
||||
"version": "0.2.82",
|
||||
"version": "1.0.45",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@openrouter/spawn",
|
||||
"version": "0.2.82",
|
||||
"version": "1.0.45",
|
||||
"dependencies": {
|
||||
"@clack/prompts": "^1.0.0",
|
||||
"picocolors": "^1.1.1"
|
||||
|
|
|
|||
|
|
@ -279,11 +279,22 @@ export async function setupCursorProxy(runner: CloudRunner): Promise<void> {
|
|||
logStep("Deploying Cursor→OpenRouter proxy...");
|
||||
|
||||
// 1. Install Caddy if not present
|
||||
// Detect OS and arch at runtime so this works on macOS (darwin/arm64|amd64)
|
||||
// and Linux (linux/amd64|arm64). Write to ~/.local/bin which is user-writable
|
||||
// on every platform -- /usr/local/bin is SIP-protected on macOS and requires
|
||||
// root elsewhere.
|
||||
const installCaddy = [
|
||||
"set -e",
|
||||
'if command -v caddy >/dev/null 2>&1; then echo "caddy already installed"; exit 0; fi',
|
||||
'echo "Installing Caddy..."',
|
||||
'curl -sf "https://caddyserver.com/api/download?os=linux&arch=amd64" -o /usr/local/bin/caddy',
|
||||
"chmod +x /usr/local/bin/caddy",
|
||||
'OS=$(uname -s | tr "[:upper:]" "[:lower:]")',
|
||||
"ARCH=$(uname -m)",
|
||||
'[ "$ARCH" = "x86_64" ] && ARCH="amd64"',
|
||||
'[ "$ARCH" = "aarch64" ] && ARCH="arm64"',
|
||||
'mkdir -p "$HOME/.local/bin"',
|
||||
'curl -fsSL "https://caddyserver.com/api/download?os=${OS}&arch=${ARCH}" -o "$HOME/.local/bin/caddy"',
|
||||
'chmod +x "$HOME/.local/bin/caddy"',
|
||||
'export PATH="$HOME/.local/bin:$PATH"',
|
||||
"caddy version",
|
||||
].join("\n");
|
||||
|
||||
|
|
@ -322,18 +333,23 @@ export async function setupCursorProxy(runner: CloudRunner): Promise<void> {
|
|||
logInfo("Proxy scripts deployed");
|
||||
|
||||
// 3. Configure /etc/hosts for domain spoofing
|
||||
// Requires elevated privileges on all platforms (/etc/hosts is root-owned).
|
||||
// Use a temp-file swap via sudo cp to avoid sed -i portability issues
|
||||
// (macOS sed requires a backup suffix with -i; Linux does not).
|
||||
const hostsScript = [
|
||||
// Remove any existing cursor entries
|
||||
'sed -i "/cursor\\.sh/d" /etc/hosts 2>/dev/null || true',
|
||||
// Add our entries
|
||||
`echo "127.0.0.1 ${CURSOR_DOMAINS.join(" ")}" >> /etc/hosts`,
|
||||
// Build a clean copy without existing cursor entries then append ours
|
||||
'grep -v "cursor.sh" /etc/hosts > /tmp/hosts_cursor_new 2>/dev/null || cp /etc/hosts /tmp/hosts_cursor_new',
|
||||
`echo "127.0.0.1 ${CURSOR_DOMAINS.join(" ")}" >> /tmp/hosts_cursor_new`,
|
||||
"sudo cp /tmp/hosts_cursor_new /etc/hosts",
|
||||
"rm -f /tmp/hosts_cursor_new",
|
||||
].join(" && ");
|
||||
|
||||
await wrapSshCall(runner.runServer(hostsScript));
|
||||
logInfo("Hosts spoofing configured");
|
||||
|
||||
// 4. Install Caddy's internal CA cert
|
||||
const trustScript = "caddy trust 2>/dev/null || true";
|
||||
// Include ~/.local/bin in PATH since Caddy may have been installed there above.
|
||||
const trustScript = 'export PATH="$HOME/.local/bin:$PATH" && caddy trust 2>/dev/null || true';
|
||||
await wrapSshCall(runner.runServer(trustScript, 30));
|
||||
logInfo("Caddy CA trusted");
|
||||
|
||||
|
|
@ -353,7 +369,7 @@ CONF`,
|
|||
|
||||
/**
|
||||
* Start the Cursor proxy services (Caddy + two Node.js backends).
|
||||
* Uses systemd if available, falls back to setsid/nohup.
|
||||
* Uses systemd if available, falls back to nohup (POSIX -- works on macOS and Linux).
|
||||
*/
|
||||
export async function startCursorProxy(runner: CloudRunner): Promise<void> {
|
||||
logStep("Starting Cursor proxy services...");
|
||||
|
|
@ -370,6 +386,8 @@ export async function startCursorProxy(runner: CloudRunner): Promise<void> {
|
|||
|
||||
const script = [
|
||||
"source ~/.spawnrc 2>/dev/null",
|
||||
// Ensure ~/.local/bin (where Caddy may be installed) is in PATH for this script.
|
||||
'export PATH="$HOME/.local/bin:$PATH"',
|
||||
nodeFind,
|
||||
|
||||
// Start unary backend
|
||||
|
|
@ -395,7 +413,8 @@ export async function startCursorProxy(runner: CloudRunner): Promise<void> {
|
|||
" $_sudo systemctl daemon-reload",
|
||||
" $_sudo systemctl restart cursor-proxy-unary",
|
||||
" else",
|
||||
" setsid $NODE ~/.cursor/proxy/unary.mjs < /dev/null &",
|
||||
// setsid is Linux-only; nohup is POSIX and works on macOS and Linux.
|
||||
" nohup $NODE ~/.cursor/proxy/unary.mjs < /dev/null > /tmp/cursor-proxy-unary.log 2>&1 &",
|
||||
" fi",
|
||||
"fi",
|
||||
|
||||
|
|
@ -423,7 +442,7 @@ export async function startCursorProxy(runner: CloudRunner): Promise<void> {
|
|||
" $_sudo systemctl daemon-reload",
|
||||
" $_sudo systemctl restart cursor-proxy-bidi",
|
||||
" else",
|
||||
" setsid $NODE ~/.cursor/proxy/bidi.mjs < /dev/null &",
|
||||
" nohup $NODE ~/.cursor/proxy/bidi.mjs < /dev/null > /tmp/cursor-proxy-bidi.log 2>&1 &",
|
||||
" fi",
|
||||
"fi",
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue