mirror of
https://github.com/OpenRouterTeam/spawn.git
synced 2026-05-16 20:01:08 +00:00
feat: add Cursor CLI agent across all clouds (#3018)
* feat: add Cursor CLI agent across all clouds Adds Cursor's terminal-based AI coding agent (the `agent` command from cursor.com/cli) to the spawn matrix. Routes LLM requests through OpenRouter via --endpoint flag and CURSOR_API_KEY env var. - manifest.json: new cursor agent entry + all 6 cloud matrix entries - agent-setup.ts: install, configure, launch, and update definitions - Shell scripts for all 6 clouds (local, hetzner, aws, do, gcp, sprite) - Config: writes ~/.cursor/cli-config.json with full permissions - Icon: cursor.png from cursor.com/apple-touch-icon.png - All cloud READMEs updated with cursor.sh usage - CLI version bumped to 0.26.0 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add spawn skill injection for Cursor CLI Writes a .cursor/rules/spawn.mdc rule file with alwaysApply: true during setup, teaching the Cursor agent how to use the spawn CLI to provision child cloud VMs. Uses the same base64 upload pattern as other agent config files. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Signed-off-by: Ahmed Abushagur <ahmed@abushagur.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: A <258483684+la14-1@users.noreply.github.com>
This commit is contained in:
parent
2dd87c986d
commit
c61736e511
17 changed files with 397 additions and 1 deletions
|
|
@ -15,6 +15,7 @@ Last verified: 2026-03-13
|
|||
| Kilo Code | _(provider default)_ | `KILO_PROVIDER_TYPE=openrouter` — model selection handled by Kilo Code natively |
|
||||
| Hermes | _(provider default)_ | `OPENAI_BASE_URL=https://openrouter.ai/api/v1` + `OPENAI_API_KEY` — model selection handled by Hermes |
|
||||
| Junie | _(provider default)_ | `JUNIE_OPENROUTER_API_KEY` — model selection handled by Junie natively |
|
||||
| Cursor CLI | _(provider default)_ | `--endpoint https://openrouter.ai/api/v1` + `CURSOR_API_KEY` — model selection via `--model` flag or `/model` in-session |
|
||||
|
||||
## When to update
|
||||
|
||||
|
|
|
|||
|
|
@ -30,5 +30,9 @@
|
|||
"junie": {
|
||||
"url": "custom:Junie_Icon.svg (official JetBrains Junie icon, converted to PNG)",
|
||||
"ext": "png"
|
||||
},
|
||||
"cursor": {
|
||||
"url": "https://cursor.com/apple-touch-icon.png",
|
||||
"ext": "png"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
BIN
assets/agents/cursor.png
Normal file
BIN
assets/agents/cursor.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.9 KiB |
|
|
@ -304,6 +304,55 @@
|
|||
"jetbrains",
|
||||
"byok"
|
||||
]
|
||||
},
|
||||
"cursor": {
|
||||
"name": "Cursor CLI",
|
||||
"description": "Cursor's terminal-based AI coding agent — autonomous coding with plan, agent, and ask modes",
|
||||
"url": "https://cursor.com/cli",
|
||||
"install": "curl https://cursor.com/install -fsS | bash",
|
||||
"launch": "agent",
|
||||
"env": {
|
||||
"OPENROUTER_API_KEY": "${OPENROUTER_API_KEY}",
|
||||
"CURSOR_API_KEY": "${OPENROUTER_API_KEY}"
|
||||
},
|
||||
"config_files": {
|
||||
"~/.cursor/cli-config.json": {
|
||||
"version": 1,
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Shell(*)",
|
||||
"Read(*)",
|
||||
"Write(*)",
|
||||
"WebFetch(*)",
|
||||
"Mcp(*)"
|
||||
],
|
||||
"deny": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"notes": "Works with OpenRouter via --endpoint flag pointing to openrouter.ai/api/v1 and CURSOR_API_KEY set to OpenRouter key. Binary installs to ~/.cursor/bin/agent.",
|
||||
"icon": "https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/assets/agents/cursor.png",
|
||||
"featured_cloud": [
|
||||
"digitalocean",
|
||||
"sprite"
|
||||
],
|
||||
"creator": "Anysphere",
|
||||
"repo": "anysphere/cursor",
|
||||
"license": "Proprietary",
|
||||
"created": "2025-01",
|
||||
"added": "2026-03",
|
||||
"github_stars": 10000,
|
||||
"stars_updated": "2026-03-26",
|
||||
"language": "TypeScript",
|
||||
"runtime": "binary",
|
||||
"category": "cli",
|
||||
"tagline": "Cursor's AI coding agent — plan, build, and ship from the terminal",
|
||||
"tags": [
|
||||
"coding",
|
||||
"terminal",
|
||||
"agentic",
|
||||
"cursor"
|
||||
]
|
||||
}
|
||||
},
|
||||
"clouds": {
|
||||
|
|
@ -450,6 +499,12 @@
|
|||
"aws/junie": "implemented",
|
||||
"digitalocean/junie": "implemented",
|
||||
"gcp/junie": "implemented",
|
||||
"sprite/junie": "implemented"
|
||||
"sprite/junie": "implemented",
|
||||
"local/cursor": "implemented",
|
||||
"hetzner/cursor": "implemented",
|
||||
"aws/cursor": "implemented",
|
||||
"digitalocean/cursor": "implemented",
|
||||
"gcp/cursor": "implemented",
|
||||
"sprite/cursor": "implemented"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -169,6 +169,71 @@ async function setupClaudeCodeConfig(runner: CloudRunner, apiKey: string): Promi
|
|||
logInfo("Claude Code configured");
|
||||
}
|
||||
|
||||
// ─── Cursor CLI Config ────────────────────────────────────────────────────────
|
||||
|
||||
async function setupCursorConfig(runner: CloudRunner, _apiKey: string): Promise<void> {
|
||||
logStep("Configuring Cursor CLI for OpenRouter...");
|
||||
|
||||
const configJson = `{
|
||||
"version": 1,
|
||||
"permissions": {
|
||||
"allow": ["Shell(*)", "Read(*)", "Write(*)", "WebFetch(*)", "Mcp(*)"],
|
||||
"deny": []
|
||||
}
|
||||
}`;
|
||||
|
||||
// Spawn skill rule — injected into .cursor/rules/ so the agent knows
|
||||
// it can provision child VMs via the spawn CLI.
|
||||
const spawnRule = [
|
||||
"---",
|
||||
"description: Spawn CLI — provision child cloud VMs with AI coding agents",
|
||||
"alwaysApply: true",
|
||||
"---",
|
||||
"",
|
||||
"# Spawn — Create Child VMs",
|
||||
"",
|
||||
"You have the `spawn` CLI installed. Use it to provision cloud VMs with AI agents.",
|
||||
"",
|
||||
"## Usage",
|
||||
"",
|
||||
"```bash",
|
||||
'spawn <agent> <cloud> --headless --output json --prompt "task description"',
|
||||
"```",
|
||||
"",
|
||||
"## Agents: claude, codex, openclaw, zeroclaw, opencode, kilocode, hermes, junie, cursor",
|
||||
"## Clouds: hetzner, digitalocean, aws, gcp, sprite",
|
||||
"",
|
||||
"The command returns JSON with connection details. Use this to delegate subtasks",
|
||||
"to specialized agents running on separate cloud VMs.",
|
||||
"",
|
||||
].join("\n");
|
||||
|
||||
const configB64 = Buffer.from(configJson).toString("base64");
|
||||
if (!/^[A-Za-z0-9+/=]+$/.test(configB64)) {
|
||||
throw new Error("Unexpected characters in base64 output");
|
||||
}
|
||||
|
||||
const ruleB64 = Buffer.from(spawnRule).toString("base64");
|
||||
if (!/^[A-Za-z0-9+/=]+$/.test(ruleB64)) {
|
||||
throw new Error("Unexpected characters in base64 output");
|
||||
}
|
||||
|
||||
const script = [
|
||||
"mkdir -p ~/.cursor ~/.cursor/rules",
|
||||
`printf '%s' '${configB64}' | base64 -d > ~/.cursor/cli-config.json`,
|
||||
"chmod 600 ~/.cursor/cli-config.json",
|
||||
// Inject spawn skill as a Cursor rule
|
||||
`printf '%s' '${ruleB64}' | base64 -d > ~/.cursor/rules/spawn.mdc`,
|
||||
"chmod 644 ~/.cursor/rules/spawn.mdc",
|
||||
// Persist PATH so agent binary is available
|
||||
'grep -q ".cursor/bin" ~/.bashrc 2>/dev/null || printf \'\\nexport PATH="$HOME/.cursor/bin:$PATH"\\n\' >> ~/.bashrc',
|
||||
'grep -q ".cursor/bin" ~/.zshrc 2>/dev/null || printf \'\\nexport PATH="$HOME/.cursor/bin:$PATH"\\n\' >> ~/.zshrc',
|
||||
].join(" && ");
|
||||
|
||||
await runner.runServer(script);
|
||||
logInfo("Cursor CLI configured");
|
||||
}
|
||||
|
||||
// ─── GitHub Auth ─────────────────────────────────────────────────────────────
|
||||
|
||||
let githubAuthRequested = false;
|
||||
|
|
@ -1115,6 +1180,28 @@ function createAgents(runner: CloudRunner): Record<string, AgentConfig> {
|
|||
'export PATH="$HOME/.npm-global/bin:$HOME/.bun/bin:$PATH"; ' +
|
||||
"npm install -g ${_NPM_G_FLAGS:-} @jetbrains/junie-cli@latest",
|
||||
},
|
||||
|
||||
cursor: {
|
||||
name: "Cursor CLI",
|
||||
cloudInitTier: "minimal",
|
||||
preProvision: detectGithubAuth,
|
||||
install: () =>
|
||||
installAgent(
|
||||
runner,
|
||||
"Cursor CLI",
|
||||
"curl https://cursor.com/install -fsS | bash && " +
|
||||
'export PATH="$HOME/.cursor/bin:$PATH" && ' +
|
||||
"agent --version",
|
||||
),
|
||||
envVars: (apiKey) => [
|
||||
`OPENROUTER_API_KEY=${apiKey}`,
|
||||
`CURSOR_API_KEY=${apiKey}`,
|
||||
],
|
||||
configure: (apiKey) => setupCursorConfig(runner, apiKey),
|
||||
launchCmd: () =>
|
||||
'source ~/.spawnrc 2>/dev/null; export PATH="$HOME/.cursor/bin:$PATH"; agent --endpoint https://openrouter.ai/api/v1',
|
||||
updateCmd: 'export PATH="$HOME/.cursor/bin:$PATH"; agent update',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -60,6 +60,12 @@ bash <(curl -fsSL https://openrouter.ai/labs/spawn/aws/hermes.sh)
|
|||
bash <(curl -fsSL https://openrouter.ai/labs/spawn/aws/junie.sh)
|
||||
```
|
||||
|
||||
#### Cursor CLI
|
||||
|
||||
```bash
|
||||
bash <(curl -fsSL https://openrouter.ai/labs/spawn/aws/cursor.sh)
|
||||
```
|
||||
|
||||
## Non-Interactive Mode
|
||||
|
||||
```bash
|
||||
|
|
|
|||
26
sh/aws/cursor.sh
Normal file
26
sh/aws/cursor.sh
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
# Thin shim: ensures bun is available, runs bundled aws.js (local or from GitHub release)
|
||||
|
||||
_ensure_bun() {
|
||||
if command -v bun &>/dev/null; then return 0; fi
|
||||
printf '\033[0;36mInstalling bun...\033[0m\n' >&2
|
||||
curl -fsSL --proto '=https' --show-error https://bun.sh/install?version=1.3.9 | bash >/dev/null || { printf '\033[0;31mFailed to install bun\033[0m\n' >&2; exit 1; }
|
||||
export PATH="$HOME/.bun/bin:$PATH"
|
||||
command -v bun &>/dev/null || { printf '\033[0;31mbun not found after install\033[0m\n' >&2; exit 1; }
|
||||
}
|
||||
|
||||
_ensure_bun
|
||||
|
||||
# SPAWN_CLI_DIR override — force local source (used by e2e tests)
|
||||
if [[ -n "${SPAWN_CLI_DIR:-}" && -f "$SPAWN_CLI_DIR/packages/cli/src/aws/main.ts" ]]; then
|
||||
exec bun run "$SPAWN_CLI_DIR/packages/cli/src/aws/main.ts" cursor "$@"
|
||||
fi
|
||||
|
||||
# Remote — download and run compiled TypeScript bundle
|
||||
AWS_JS=$(mktemp)
|
||||
trap 'rm -f "$AWS_JS"' EXIT
|
||||
curl -fsSL --proto '=https' "https://github.com/OpenRouterTeam/spawn/releases/download/aws-latest/aws.js" -o "$AWS_JS" \
|
||||
|| { printf '\033[0;31mFailed to download aws.js\033[0m\n' >&2; exit 1; }
|
||||
exec bun run "$AWS_JS" cursor "$@"
|
||||
|
|
@ -52,6 +52,12 @@ bash <(curl -fsSL https://openrouter.ai/labs/spawn/digitalocean/hermes.sh)
|
|||
bash <(curl -fsSL https://openrouter.ai/labs/spawn/digitalocean/junie.sh)
|
||||
```
|
||||
|
||||
#### Cursor CLI
|
||||
|
||||
```bash
|
||||
bash <(curl -fsSL https://openrouter.ai/labs/spawn/digitalocean/cursor.sh)
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Variable | Description | Default |
|
||||
|
|
|
|||
84
sh/digitalocean/cursor.sh
Normal file
84
sh/digitalocean/cursor.sh
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
# Thin shim: ensures bun is available, runs bundled digitalocean.js (local or from GitHub release)
|
||||
# Includes restart loop for SIGTERM recovery on DigitalOcean
|
||||
|
||||
_AGENT_NAME="cursor"
|
||||
_MAX_RETRIES=3
|
||||
|
||||
_ensure_bun() {
|
||||
if command -v bun &>/dev/null; then return 0; fi
|
||||
printf '\033[0;36mInstalling bun...\033[0m\n' >&2
|
||||
curl -fsSL --proto '=https' --show-error https://bun.sh/install?version=1.3.9 | bash >/dev/null || { printf '\033[0;31mFailed to install bun\033[0m\n' >&2; exit 1; }
|
||||
export PATH="$HOME/.bun/bin:$PATH"
|
||||
command -v bun &>/dev/null || { printf '\033[0;31mbun not found after install\033[0m\n' >&2; exit 1; }
|
||||
}
|
||||
|
||||
# Run command in the foreground so bun gets full terminal access (raw mode,
|
||||
# arrow keys for interactive prompts). The old pattern backgrounded the child
|
||||
# with & + wait so a SIGTERM trap could forward the signal, but that removed
|
||||
# bun from the foreground process group and broke @clack/prompts multiselect.
|
||||
# Now SIGTERM is detected from exit code 143 (128 + 15) after the child exits.
|
||||
_run_with_restart() {
|
||||
# In headless mode (E2E / --headless), skip the restart loop entirely.
|
||||
# Restarting in headless mode creates duplicate droplets, exhausting the
|
||||
# account's droplet quota and causing all subsequent agents to fail.
|
||||
if [ "${SPAWN_HEADLESS:-}" = "1" ]; then
|
||||
"$@"
|
||||
return $?
|
||||
fi
|
||||
|
||||
local attempt=0
|
||||
local backoff=2
|
||||
while [ "$attempt" -lt "$_MAX_RETRIES" ]; do
|
||||
attempt=$((attempt + 1))
|
||||
|
||||
"$@"
|
||||
local exit_code=$?
|
||||
|
||||
# Normal exit
|
||||
if [ "$exit_code" -eq 0 ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# SIGTERM (143) or SIGKILL (137) — attempt restart
|
||||
if [ "$exit_code" -eq 143 ] || [ "$exit_code" -eq 137 ]; then
|
||||
printf '\033[0;33m[spawn/%s] Agent process terminated (exit %s). The droplet is likely still running.\033[0m\n' \
|
||||
"$_AGENT_NAME" "$exit_code" >&2
|
||||
printf '\033[0;33m[spawn/%s] Check your DigitalOcean dashboard: https://cloud.digitalocean.com/droplets\033[0m\n' \
|
||||
"$_AGENT_NAME" >&2
|
||||
if [ "$attempt" -lt "$_MAX_RETRIES" ]; then
|
||||
printf '\033[0;33m[spawn/%s] Restarting (attempt %s/%s, backoff %ss)...\033[0m\n' \
|
||||
"$_AGENT_NAME" "$((attempt + 1))" "$_MAX_RETRIES" "$backoff" >&2
|
||||
sleep "$backoff"
|
||||
backoff=$((backoff * 2))
|
||||
continue
|
||||
else
|
||||
printf '\033[0;31m[spawn/%s] Max restart attempts reached (%s). Giving up.\033[0m\n' \
|
||||
"$_AGENT_NAME" "$_MAX_RETRIES" >&2
|
||||
return "$exit_code"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Other failure — exit with the original code
|
||||
return "$exit_code"
|
||||
done
|
||||
}
|
||||
|
||||
_ensure_bun
|
||||
|
||||
# SPAWN_CLI_DIR override — force local source (used by e2e tests)
|
||||
if [[ -n "${SPAWN_CLI_DIR:-}" && -f "$SPAWN_CLI_DIR/packages/cli/src/digitalocean/main.ts" ]]; then
|
||||
_run_with_restart bun run "$SPAWN_CLI_DIR/packages/cli/src/digitalocean/main.ts" "$_AGENT_NAME" "$@"
|
||||
exit $?
|
||||
fi
|
||||
|
||||
# Remote — download bundled digitalocean.js from GitHub release
|
||||
DO_JS=$(mktemp)
|
||||
trap 'rm -f "$DO_JS"' EXIT
|
||||
curl -fsSL --proto '=https' "https://github.com/OpenRouterTeam/spawn/releases/download/digitalocean-latest/digitalocean.js" -o "$DO_JS" \
|
||||
|| { printf '\033[0;31mFailed to download digitalocean.js\033[0m\n' >&2; exit 1; }
|
||||
|
||||
_run_with_restart bun run "$DO_JS" "$_AGENT_NAME" "$@"
|
||||
exit $?
|
||||
|
|
@ -54,6 +54,12 @@ bash <(curl -fsSL https://openrouter.ai/labs/spawn/gcp/hermes.sh)
|
|||
bash <(curl -fsSL https://openrouter.ai/labs/spawn/gcp/junie.sh)
|
||||
```
|
||||
|
||||
#### Cursor CLI
|
||||
|
||||
```bash
|
||||
bash <(curl -fsSL https://openrouter.ai/labs/spawn/gcp/cursor.sh)
|
||||
```
|
||||
|
||||
## Non-Interactive Mode
|
||||
|
||||
```bash
|
||||
|
|
|
|||
27
sh/gcp/cursor.sh
Normal file
27
sh/gcp/cursor.sh
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
# Thin shim: ensures bun is available, runs bundled gcp.js (local or from GitHub release)
|
||||
|
||||
_ensure_bun() {
|
||||
if command -v bun &>/dev/null; then return 0; fi
|
||||
printf '\033[0;36mInstalling bun...\033[0m\n' >&2
|
||||
curl -fsSL --proto '=https' --show-error https://bun.sh/install?version=1.3.9 | bash >/dev/null || { printf '\033[0;31mFailed to install bun\033[0m\n' >&2; exit 1; }
|
||||
export PATH="$HOME/.bun/bin:$PATH"
|
||||
command -v bun &>/dev/null || { printf '\033[0;31mbun not found after install\033[0m\n' >&2; exit 1; }
|
||||
}
|
||||
|
||||
_ensure_bun
|
||||
|
||||
# SPAWN_CLI_DIR override — force local source (used by e2e tests)
|
||||
if [[ -n "${SPAWN_CLI_DIR:-}" && -f "$SPAWN_CLI_DIR/packages/cli/src/gcp/main.ts" ]]; then
|
||||
exec bun run "$SPAWN_CLI_DIR/packages/cli/src/gcp/main.ts" cursor "$@"
|
||||
fi
|
||||
|
||||
# Remote — download bundled gcp.js from GitHub release
|
||||
GCP_JS=$(mktemp)
|
||||
trap 'rm -f "$GCP_JS"' EXIT
|
||||
curl -fsSL --proto '=https' "https://github.com/OpenRouterTeam/spawn/releases/download/gcp-latest/gcp.js" -o "$GCP_JS" \
|
||||
|| { printf '\033[0;31mFailed to download gcp.js\033[0m\n' >&2; exit 1; }
|
||||
|
||||
exec bun run "$GCP_JS" cursor "$@"
|
||||
|
|
@ -52,6 +52,12 @@ bash <(curl -fsSL https://openrouter.ai/labs/spawn/hetzner/hermes.sh)
|
|||
bash <(curl -fsSL https://openrouter.ai/labs/spawn/hetzner/junie.sh)
|
||||
```
|
||||
|
||||
#### Cursor CLI
|
||||
|
||||
```bash
|
||||
bash <(curl -fsSL https://openrouter.ai/labs/spawn/hetzner/cursor.sh)
|
||||
```
|
||||
|
||||
## Non-Interactive Mode
|
||||
|
||||
```bash
|
||||
|
|
|
|||
26
sh/hetzner/cursor.sh
Normal file
26
sh/hetzner/cursor.sh
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
# Thin shim: ensures bun is available, runs bundled hetzner.js (local or from GitHub release)
|
||||
|
||||
_ensure_bun() {
|
||||
if command -v bun &>/dev/null; then return 0; fi
|
||||
printf '\033[0;36mInstalling bun...\033[0m\n' >&2
|
||||
curl -fsSL --proto '=https' --show-error https://bun.sh/install?version=1.3.9 | bash >/dev/null || { printf '\033[0;31mFailed to install bun\033[0m\n' >&2; exit 1; }
|
||||
export PATH="$HOME/.bun/bin:$PATH"
|
||||
command -v bun &>/dev/null || { printf '\033[0;31mbun not found after install\033[0m\n' >&2; exit 1; }
|
||||
}
|
||||
|
||||
_ensure_bun
|
||||
|
||||
# SPAWN_CLI_DIR override — force local source (used by e2e tests)
|
||||
if [[ -n "${SPAWN_CLI_DIR:-}" && -f "$SPAWN_CLI_DIR/packages/cli/src/hetzner/main.ts" ]]; then
|
||||
exec bun run "$SPAWN_CLI_DIR/packages/cli/src/hetzner/main.ts" cursor "$@"
|
||||
fi
|
||||
|
||||
# Remote — download and run compiled TypeScript bundle
|
||||
HETZNER_JS=$(mktemp)
|
||||
trap 'rm -f "$HETZNER_JS"' EXIT
|
||||
curl -fsSL --proto '=https' "https://github.com/OpenRouterTeam/spawn/releases/download/hetzner-latest/hetzner.js" -o "$HETZNER_JS" \
|
||||
|| { printf '\033[0;31mFailed to download hetzner.js\033[0m\n' >&2; exit 1; }
|
||||
exec bun run "$HETZNER_JS" cursor "$@"
|
||||
|
|
@ -17,6 +17,7 @@ spawn opencode local
|
|||
spawn kilocode local
|
||||
spawn hermes local
|
||||
spawn junie local
|
||||
spawn cursor local
|
||||
```
|
||||
|
||||
Or run directly without the CLI:
|
||||
|
|
@ -30,6 +31,7 @@ bash <(curl -fsSL https://openrouter.ai/labs/spawn/local/opencode.sh)
|
|||
bash <(curl -fsSL https://openrouter.ai/labs/spawn/local/kilocode.sh)
|
||||
bash <(curl -fsSL https://openrouter.ai/labs/spawn/local/hermes.sh)
|
||||
bash <(curl -fsSL https://openrouter.ai/labs/spawn/local/junie.sh)
|
||||
bash <(curl -fsSL https://openrouter.ai/labs/spawn/local/cursor.sh)
|
||||
```
|
||||
|
||||
## Non-Interactive Mode
|
||||
|
|
|
|||
27
sh/local/cursor.sh
Normal file
27
sh/local/cursor.sh
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
# Thin shim: ensures bun is available, runs bundled local.js (local or from GitHub release)
|
||||
|
||||
_ensure_bun() {
|
||||
if command -v bun &>/dev/null; then return 0; fi
|
||||
printf '\033[0;36mInstalling bun...\033[0m\n' >&2
|
||||
curl -fsSL --proto '=https' --show-error https://bun.sh/install?version=1.3.9 | bash >/dev/null || { printf '\033[0;31mFailed to install bun\033[0m\n' >&2; exit 1; }
|
||||
export PATH="$HOME/.bun/bin:$PATH"
|
||||
command -v bun &>/dev/null || { printf '\033[0;31mbun not found after install\033[0m\n' >&2; exit 1; }
|
||||
}
|
||||
|
||||
_ensure_bun
|
||||
|
||||
# SPAWN_CLI_DIR override — force local source (used by e2e tests)
|
||||
if [[ -n "${SPAWN_CLI_DIR:-}" && -f "$SPAWN_CLI_DIR/packages/cli/src/local/main.ts" ]]; then
|
||||
exec bun run "$SPAWN_CLI_DIR/packages/cli/src/local/main.ts" cursor "$@"
|
||||
fi
|
||||
|
||||
# Remote — download bundled local.js from GitHub release
|
||||
LOCAL_JS=$(mktemp)
|
||||
trap 'rm -f "$LOCAL_JS"' EXIT
|
||||
curl -fsSL --proto '=https' "https://github.com/OpenRouterTeam/spawn/releases/download/local-latest/local.js" -o "$LOCAL_JS" \
|
||||
|| { printf '\033[0;31mFailed to download local.js\033[0m\n' >&2; exit 1; }
|
||||
|
||||
exec bun run "$LOCAL_JS" cursor "$@"
|
||||
|
|
@ -52,6 +52,12 @@ bash <(curl -fsSL https://openrouter.ai/labs/spawn/sprite/hermes.sh)
|
|||
bash <(curl -fsSL https://openrouter.ai/labs/spawn/sprite/junie.sh)
|
||||
```
|
||||
|
||||
#### Cursor CLI
|
||||
|
||||
```bash
|
||||
bash <(curl -fsSL https://openrouter.ai/labs/spawn/sprite/cursor.sh)
|
||||
```
|
||||
|
||||
## Non-Interactive Mode
|
||||
|
||||
```bash
|
||||
|
|
|
|||
27
sh/sprite/cursor.sh
Normal file
27
sh/sprite/cursor.sh
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
# Thin shim: ensures bun is available, runs bundled sprite.js (local or from GitHub release)
|
||||
|
||||
_ensure_bun() {
|
||||
if command -v bun &>/dev/null; then return 0; fi
|
||||
printf '\033[0;36mInstalling bun...\033[0m\n' >&2
|
||||
curl -fsSL --proto '=https' --show-error https://bun.sh/install?version=1.3.9 | bash >/dev/null || { printf '\033[0;31mFailed to install bun\033[0m\n' >&2; exit 1; }
|
||||
export PATH="$HOME/.bun/bin:$PATH"
|
||||
command -v bun &>/dev/null || { printf '\033[0;31mbun not found after install\033[0m\n' >&2; exit 1; }
|
||||
}
|
||||
|
||||
_ensure_bun
|
||||
|
||||
# SPAWN_CLI_DIR override — force local source (used by e2e tests)
|
||||
if [[ -n "${SPAWN_CLI_DIR:-}" && -f "$SPAWN_CLI_DIR/packages/cli/src/sprite/main.ts" ]]; then
|
||||
exec bun run "$SPAWN_CLI_DIR/packages/cli/src/sprite/main.ts" cursor "$@"
|
||||
fi
|
||||
|
||||
# Remote — download bundled sprite.js from GitHub release
|
||||
SPRITE_JS=$(mktemp)
|
||||
trap 'rm -f "$SPRITE_JS"' EXIT
|
||||
curl -fsSL --proto '=https' "https://github.com/OpenRouterTeam/spawn/releases/download/sprite-latest/sprite.js" -o "$SPRITE_JS" \
|
||||
|| { printf '\033[0;31mFailed to download sprite.js\033[0m\n' >&2; exit 1; }
|
||||
|
||||
exec bun run "$SPRITE_JS" cursor "$@"
|
||||
Loading…
Add table
Add a link
Reference in a new issue