Commit graph

2386 commits

Author SHA1 Message Date
A
f2f981bd0a
fix(e2e): reduce Hetzner batch parallelism from 3 to 2 (#3112)
Prevents server_limit_reached errors when pre-existing servers (e.g.
spawn-szil) consume quota during E2E batch 1.

Fixes #3111

Agent: test-engineer

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-31 03:08:18 +07:00
A
2077816b61
docs: sync README commands table with help.ts (--prompt, --prompt-file) (#3106)
Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
2026-03-31 03:05:56 +07:00
A
02cf129bc0
fix(spawn-fix): load API keys via config file, not just process.env (#3095)
Previously buildFixScript() resolved env templates directly from
process.env, silently writing empty values when the user authenticated
via OAuth (key stored in ~/.config/spawn/openrouter.json). Now fixSpawn()
loads the saved key before building the script, matching orchestrate.ts.

Fixes #3094

Agent: code-health

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 03:03:47 +07:00
A
52e78bdeb8
fix(manifest): correct cursor repo to cursor/cursor and update star counts (#3092)
The cursor agent's repo was set to anysphere/cursor (private, returns 404),
which caused the stars-update script to store the raw 404 error object as
github_stars instead of a number — breaking the manifest-type-contracts test.

Fix: update repo to the public cursor/cursor repo (32,526 stars as of 2026-03-29).
Also applies the daily star count updates for all other agents.

-- qa/e2e-tester

Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
2026-03-31 03:00:24 +07:00
A
9624141844
fix(security): expand $HOME before path validation in downloadFile (#3080)
Fixes #3080

Prevents path traversal via other $VAR expansions by normalizing
$HOME to ~ before the strict path regex check, removing the need
to allow $ in the charset.

Applied to all 5 cloud providers:
- digitalocean: downloadFile
- aws: downloadFile
- sprite: downloadFileSprite
- gcp: uploadFile + downloadFile
- hetzner: downloadFile

Also bumps CLI version to 0.27.7.

Agent: security-auditor

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 19:56:05 +00:00
A
ccbe52ccc2
fix: add cursor to agent lists in spawn skill files (#3108)
Some checks are pending
CLI Release / Build and release CLI (push) Waiting to run
Lint / ShellCheck (push) Waiting to run
Lint / Biome Lint (push) Waiting to run
Lint / macOS Compatibility (push) Waiting to run
cursor is a fully implemented agent across all 6 clouds but was missing
from the available agents list in spawn skill instructions injected onto
child VMs. This caused claude, codex, hermes, junie, kilocode, openclaw,
opencode, and zeroclaw to be unaware they could delegate work to cursor.

Signed-off-by: Ahmed Abushagur <ahmed@abushagur.com>
Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Ahmed Abushagur <ahmed@abushagur.com>
2026-03-29 22:49:04 -07:00
A
749f79a9c2
feat(status): add agent alive probe via SSH (#3109)
`spawn status` now probes running servers by SSHing in and running
`{agent} --version` to verify the agent binary is installed and
executable. Results show in a new "Probe" column (live/down/—) and
as `agent_alive` in JSON output. Only "running" servers are probed;
gone/stopped/unknown servers are skipped.

The probe function is injectable via opts for testability.

Co-authored-by: spawn-bot <spawn-bot@openrouter.ai>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 22:44:46 -07:00
A
ddce16a438
fix(cursor): update proxy model list to current models (#3105)
Replace outdated models (Claude Sonnet 4, GPT-4o) with current ones:
- Claude Sonnet 4.6 (default), Claude Haiku 4.5
- GPT-4.1
- Gemini 2.5 Pro, Gemini 2.5 Flash

Co-authored-by: spawn-bot <spawn-bot@openrouter.ai>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 21:25:58 -07:00
A
1378ed1c23
docs: sync README with source of truth (#3097)
- update tagline: 8 agents/48 combos -> 9 agents/54 combos
- add Cursor CLI row to matrix table

manifest.json has 9 agents (cursor was added but README matrix
was not updated) and 54 implemented entries.

Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: Ahmed Abushagur <ahmed@abushagur.com>
2026-03-29 21:06:50 -07:00
A
9892355ede
fix(cursor): set CURSOR_API_KEY to skip browser login (#3104)
Cursor CLI requires authentication before making API calls. Without
CURSOR_API_KEY set, it falls back to browser-based OAuth which fails
because the proxy spoofs api2.cursor.sh to localhost, breaking the
OAuth callback. Setting a dummy CURSOR_API_KEY makes Cursor use the
/auth/exchange_user_api_key endpoint instead, which the proxy already
handles with a fake JWT.

Co-authored-by: spawn-bot <spawn-bot@openrouter.ai>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 21:05:26 -07:00
A
b73761897a
fix: remove --trust flag from Cursor CLI launch command (#3101)
Cursor CLI v2026.03.25 only allows --trust in headless/print mode.
Launching interactively with --trust causes immediate exit with error.

Co-authored-by: spawn-bot <spawn-bot@openrouter.ai>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Ahmed Abushagur <ahmed@abushagur.com>
2026-03-29 20:46:39 -07:00
A
0bd8930c09
fix(digitalocean): use canonical DIGITALOCEAN_ACCESS_TOKEN env var (#3099)
Replaces all references to DO_API_TOKEN with DIGITALOCEAN_ACCESS_TOKEN,
matching DigitalOcean's official CLI and API documentation. This includes
TypeScript source, tests, shell scripts, Packer config, CI workflows,
and documentation.

Supersedes #3068 (rebased onto current main).

Agent: pr-maintainer

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-30 08:48:56 +07:00
A
b9473f25b8
feat: add OpenRouter proxy for Cursor CLI agent (#3100)
Cursor CLI uses a proprietary ConnectRPC/protobuf protocol with BiDi
streaming over HTTP/2. It validates API keys against Cursor's own servers
and hardcodes api2.cursor.sh for agent streaming — making direct
OpenRouter integration impossible.

This adds a local translation proxy that intercepts Cursor's protocol
and routes LLM traffic through OpenRouter:

Architecture:
  Cursor CLI → Caddy (HTTPS/H2, port 443) → split routing:
    /agent.v1.AgentService/* → H2C Node.js (BiDi streaming → OpenRouter)
    everything else → HTTP/1.1 Node.js (fake auth, models, config)

Key components:
- cursor-proxy.ts: proxy scripts + deployment functions
- Caddy reverse proxy for TLS + HTTP/2 termination
- /etc/hosts spoofing to intercept api2.cursor.sh
- Hand-rolled protobuf codec for AgentServerMessage format
- SSE stream translation (OpenRouter → ConnectRPC protobuf frames)

Proto schemas reverse-engineered from Cursor CLI binary v2026.03.25:
- AgentServerMessage.InteractionUpdate.TextDeltaUpdate.text
- agent.v1.ModelDetails (model_id, display_model_id, display_name)
- TurnEndedUpdate (input_tokens, output_tokens)

Tested end-to-end on Sprite VM: Cursor CLI printed proxy response with
EXIT=0.

Co-authored-by: Ahmed Abushagur <ahmed@abushagur.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 17:59:00 -07:00
Ahmed Abushagur
ccd86005ce
fix: scope local warning to openclaw-only + improve spawn skill docs (#3074)
Some checks failed
CLI Release / Build and release CLI (push) Has been cancelled
Lint / ShellCheck (push) Has been cancelled
Lint / Biome Lint (push) Has been cancelled
Lint / macOS Compatibility (push) Has been cancelled
- Revert local security warning to openclaw-only (was blocking all agents)
- Update spawn skill to document how to run prompts on child VMs:
  - Always use `bash -lc` (binaries in ~/.local/bin/ need login shell)
  - Claude uses `-p` not `--print` or `--headless`
  - Add `--dangerously-skip-permissions` for unattended child VMs
  - Don't waste tokens with `which`/`find` or creating non-root users
- Sync all on-disk skill files with embedded version

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 22:54:24 -07:00
A
a29d0d8a15
fix(security): replace variable-stored shell code with named function in verify.sh (#3073)
Some checks are pending
Lint / ShellCheck (push) Waiting to run
Lint / Biome Lint (push) Waiting to run
Lint / macOS Compatibility (push) Waiting to run
Fixes #3070

The port_check / port_check_r variables stored executable shell code as
strings and expanded them via ${port_check} inside cloud_exec commands.
This is an eval-equivalent pattern: if any part of the variable were ever
derived from dynamic input, it would be directly exploitable as command
injection.

Replace the pattern with _check_port_18789() remote function definitions
inside each cloud_exec call. The function is defined and called entirely
on the remote side — no shell code is stored in local bash variables.

Affected functions:
- _openclaw_ensure_gateway (2 usages)
- _openclaw_restart_gateway (1 usage)
- _openclaw_verify_gateway_resilience (3 usages)

Agent: security-auditor

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 11:25:00 +07:00
A
4db068d0c4
fix(github-auth): add sudo availability check before use (#3072)
In rootless containers or environments without sudo, the script
previously failed with cryptic errors. Now fails fast with a clear
error message when non-root and sudo is unavailable.

Fixes #3069

Agent: security-auditor

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-28 08:39:22 +07:00
A
4ad72132c4
docs: sync README tagline with manifest (5 clouds → 6, 40 → 48 combinations) (#3067)
Some checks are pending
CLI Release / Build and release CLI (push) Waiting to run
Lint / ShellCheck (push) Waiting to run
Lint / Biome Lint (push) Waiting to run
Lint / macOS Compatibility (push) Waiting to run
Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
2026-03-28 00:24:20 +07:00
A
df13e70e6c
docs: sync README with source of truth (#3063)
* docs: sync README with source of truth

manifest.json marks cursor agent as disabled:true, but README still showed
9 agents / 54 combinations in the tagline and had a Cursor CLI row in the
matrix table. Updated tagline to 8 agents / 48 combinations and removed
the Cursor CLI row from the matrix.

-- qa/record-keeper

* fix: correct agent/cloud/combination counts in README tagline

The tagline claimed "8 agents. 6 clouds. 48 working combinations." but
the local cloud should be excluded from the user-facing count (users
don't deploy to their own machine via a cloud provider). With cursor
disabled, the correct counts are 8 agents x 5 non-local clouds = 40
working combinations.

Agent: pr-maintainer
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-27 07:37:21 -07:00
A
d666ab173c
test: consolidate duplicate agent envVars tests into data-driven table (#3064)
Five separate it() blocks each checking one agent's env vars (openclaw,
zeroclaw, hermes, kilocode, opencode) were collapsed into a single
data-driven table test. The new test checks all 8 env-var expectations
in one loop with clear per-assertion failure messages.

Tests removed: 5 individual envVars tests
Tests added: 1 consolidated table test
Net: -4 tests (1951 vs 1955), same coverage

Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: L <6723574+louisgv@users.noreply.github.com>
2026-03-27 19:53:19 +07:00
A
f9b81475fe
fix(cursor): remove stale ~/.cursor/bin references missed in #3058 migration (#3066)
Clean up three remaining stale references to ~/.cursor/bin that were
not caught in the #3058 path migration:

- manifest.json: update notes field to reflect ~/.local/bin/agent
- sh/e2e/lib/provision.sh: remove ~/.cursor/bin from path_prefix
- sh/e2e/lib/verify.sh: remove ~/.cursor/bin from binary check PATH

Fixes #3065

Agent: issue-fixer

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-27 19:51:10 +07:00
A
11f0c334aa
fix(digitalocean): fail fast when droplet quota is exhausted, list existing droplets (#3062)
Some checks failed
CLI Release / Build and release CLI (push) Waiting to run
Lint / ShellCheck (push) Waiting to run
Lint / Biome Lint (push) Waiting to run
Lint / macOS Compatibility (push) Waiting to run
Build Docker Images / build (claude) (push) Has been cancelled
Build Docker Images / build (codex) (push) Has been cancelled
Build Docker Images / build (cursor) (push) Has been cancelled
Build Docker Images / build (hermes) (push) Has been cancelled
Build Docker Images / build (junie) (push) Has been cancelled
Build Docker Images / build (kilocode) (push) Has been cancelled
Build Docker Images / build (openclaw) (push) Has been cancelled
Build Docker Images / build (opencode) (push) Has been cancelled
Build Docker Images / build (zeroclaw) (push) Has been cancelled
- E2E: _digitalocean_max_parallel() now returns 0 (not 1) when no capacity
- E2E: run_agents_for_cloud() skips cloud with actionable error when capacity is 0
- CLI: checkAccountStatus() includes droplet names in limit-reached error message

Fixes #3059

Agent: code-health

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 18:49:18 +07:00
A
db77121414
fix: reject disabled agents in CLI validation instead of silently proceeding (#3061)
resolveEntityKey() and checkEntity() checked manifest.agents[input] directly,
bypassing the disabled filter in agentKeys(). This let users run `spawn cursor
<cloud>` even though cursor is disabled, wasting time provisioning a VM for an
agent that can't route through OpenRouter. Now both functions check the disabled
flag and show the disabled_reason to the user.

Also removes stale cursor references from spawn skill templates injected into
child VMs.

Agent: code-health

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-27 10:22:18 +00:00
A
1cfa9ca1a7
fix(cursor): update binary path from ~/.cursor/bin to ~/.local/bin (#3058)
The cursor installer changed its binary install location from
~/.cursor/bin/agent to ~/.local/bin/agent (as of 2026-03-25 release).

Updates:
- agent-setup.ts: fix PATH in install, launchCmd, updateCmd, and
  the pathScript written to ~/.bashrc/~/.zshrc
- verify.sh: fix E2E binary check to look in ~/.local/bin first
- Bump CLI to 0.27.3

-- qa/e2e-tester

Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
2026-03-27 02:37:40 -07:00
A
e8cf33daad
test: remove duplicate and theatrical tests (#3057)
* test: remove duplicate in-memory cache tests and fix missing cache reset

Two tests verifying in-memory cache returns the same instance without
re-fetching were duplicated across manifest.test.ts and
manifest-cache-lifecycle.test.ts. The strongest version (checks both object
identity and fetch call count) already lives in the combined-fallback-chain
describe block in manifest-cache-lifecycle.test.ts, so the two weaker
duplicates are removed.

Also fixes missing _resetCacheForTesting() calls in beforeEach for the
in-memory cache behavior and combined fallback chain describe blocks —
without it, in-memory state from a prior test could contaminate later tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test: remove duplicate and theatrical tests

Consolidate 5 near-identical manifest rejection tests into a single
data-driven loop, and collapse 4 identical logging-function smoke tests
into a data-driven loop. Both changes eliminate copy-paste repetition
while preserving exact test coverage.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 16:26:34 +07:00
A
0bca96af58
fix(local): show security warning for all local agent installations (#3060)
Previously the warning only appeared for openclaw. Per security review, the
risk disclosure (full filesystem/shell/network access) applies equally to
all local agents.

Agent: pr-maintainer

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 16:24:15 +07:00
Ahmed Abushagur
dfc3e625a2
fix: temporarily disable Cursor CLI agent (#3055)
Cursor CLI uses a proprietary ConnectRPC protocol and validates API keys
against Cursor's own servers — it cannot route through OpenRouter. All
infra (scripts, setup code, matrix entries) is preserved for re-enabling
when Cursor adds BYOK/custom endpoint support.

Adds `disabled` field to AgentDef and filters disabled agents from the
picker via agentKeys().

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: L <6723574+louisgv@users.noreply.github.com>
2026-03-27 02:08:04 -07:00
A
e44705d925
fix(ux): reduce SSH wait verbosity and clarify agent handoff (#3056)
- Replace repeated 'SSH port closed (N/36)' with periodic updates every 5 attempts
- Add clear 'Provisioning complete. Connecting...' line before agent attach

Fixes #3053

Agent: ux-engineer

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-27 15:22:46 +07:00
Ahmed Abushagur
e0dca0cad9
fix: add child VM usage tips to spawn skill to prevent token waste (#3054)
The skill now documents that --headless only provisions (doesn't run
the prompt), that agent binaries are at ~/.local/bin/ (not on PATH),
and that --print should be used for one-shot prompts as root instead
of fighting with permission restrictions.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 14:33:56 +07:00
Ahmed Abushagur
d1229e94ce
ci: add cursor agent to packer snapshot pipeline (#3050)
Adds cursor to packer/agents.json so nightly DO snapshot builds
include the Cursor CLI pre-installed.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: L <6723574+louisgv@users.noreply.github.com>
2026-03-27 13:43:46 +07:00
Ahmed Abushagur
3687fb38c3
ci: add cursor agent to tarball build pipeline (#3049)
Cursor CLI installs a native binary via curl, so it needs both x86_64
and arm64 builds. Also adds cursor.com to the allowed domains list.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: L <6723574+louisgv@users.noreply.github.com>
2026-03-27 13:42:46 +07:00
Ahmed Abushagur
dcb740ec68
ci: add cursor agent to Docker image pipeline (#3051)
Adds cursor.Dockerfile and includes cursor in the docker.yml matrix
so nightly builds produce ghcr.io/openrouterteam/spawn-cursor:latest.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 13:41:27 +07:00
A
ccee04f53d
docs(tests): add missing test file entries to __tests__/README.md (#3047)
Four test files existed on disk but were not documented in the README index:
- pull-history.test.ts
- recursive-spawn.test.ts
- spawn-skill.test.ts
- star-prompt.test.ts

Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
2026-03-27 12:18:34 +07:00
A
088e33b30e
fix(e2e): correct stale test expectation for hermes timeout fallback (#3044)
Some checks are pending
CLI Release / Build and release CLI (push) Waiting to run
Lint / ShellCheck (push) Waiting to run
Lint / Biome Lint (push) Waiting to run
Lint / macOS Compatibility (push) Waiting to run
When AGENT_TIMEOUT_hermes is non-numeric, get_agent_timeout() skips the
env var and uses the built-in _AGENT_TIMEOUT_hermes=3600, NOT the global
AGENT_TIMEOUT=1800. The test expected ${AGENT_TIMEOUT} (1800) but the
function correctly returns 3600 (hermes built-in default). This test was
failing silently, masking the correct behavior.

Also filed OpenRouterTeam/spawn#3042 for cursor missing from e2e framework.

Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: L <6723574+louisgv@users.noreply.github.com>
2026-03-26 19:02:23 -07:00
A
eeab2cac1f
docs: sync README with source of truth (#3041)
Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: L <6723574+louisgv@users.noreply.github.com>
2026-03-26 18:59:50 -07:00
A
4686310758
test: remove duplicate TTY mock boilerplate in picker-cov.test.ts (#3043)
6 TTY interaction tests each repeated 20+ lines of identical stty/spawnSync
mock setup. Extracted into a shared makeSttySpawnSyncSpy() helper inside the
describe block, eliminating ~150 lines of duplicated boilerplate while keeping
all 32 tests passing (biome clean, bun test passing).

-- qa/dedup-scanner

Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: L <6723574+louisgv@users.noreply.github.com>
2026-03-27 08:41:53 +07:00
A
1c8011cae5
fix(e2e): add cursor agent to e2e test framework (#3045)
Add cursor to ALL_AGENTS, verify_cursor, input_test_cursor, and their
dispatch cases so e2e sweeps cover the cursor agent.

Fixes #3042

Agent: issue-fixer

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-27 08:40:51 +07:00
A
499eb494c6
fix(security): use StrictHostKeyChecking=accept-new in all SSH connections (#3037)
Replace StrictHostKeyChecking=no with accept-new across all E2E cloud
drivers (aws, gcp, digitalocean, hetzner), the shared SSH_BASE_OPTS
constant, and pull-history.ts. accept-new trusts new hosts on first
connection (needed for freshly provisioned VMs) but verifies on
subsequent connections, preventing MITM attacks on reconnect.

Fixes #3031

Agent: style-reviewer

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-26 18:04:40 -07:00
A
917d34d034
fix(e2e): ensure openclaw binary available in --fast mode on Sprite (#3040)
* fix(e2e): ensure agent binary available after spawnrc fallback

When the provision timeout kills the CLI before agent install completes
(common in --fast mode on Sprite), the manual .spawnrc fallback creates
credentials but does not verify the agent binary is present. This causes
"openclaw not found" failures in E2E verification.

Add _ensure_agent_binary() that runs after the manual .spawnrc fallback:
1. Checks if the agent binary exists on the remote VM
2. If missing, runs the agent's install command directly
3. Verifies the binary is available after install

Also adds cursor agent to the env vars fallback and binary check.

Fixes #3028

Agent: ux-engineer
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix(security): add --proto '=https' to cursor install curl command

Agent: pr-maintainer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-27 07:36:45 +07:00
A
7080d80472
fix(security): prevent race condition in GitHub token file permissions (#3035)
Before this change, gh auth login wrote the token file with default
permissions, and chmod 600 was applied afterward — leaving a window
where the file could be read by other users on multi-user systems.

Now the credential directory is created with 700 permissions and umask
is set to 077 before the write, so the token file is created with
restrictive permissions from the start.

Agent: complexity-hunter
Fixes #3030

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-26 16:59:42 -07:00
A
0eed96f381
fix(security): silently skip invalid connection fields in headless output (#3039)
Validate each connection field (ip, user, server_id, server_name) from
history individually before including it in headless output. Invalid
fields are silently omitted rather than reported via headlessError(),
preventing attacker-controlled data in tampered history files from being
surfaced in error messages.

Fixes #3032

Agent: test-engineer

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-26 16:58:39 -07:00
A
f685374567
fix(security): use uploadConfigFile for config deployment, chmod 600 openclaw config (#3038)
Replace base64-into-shell interpolation with SCP-based uploadConfigFile()
for Claude Code settings.json and Cursor CLI config files. This eliminates
the attack surface of injecting encoded payloads into shell command strings.

Add chmod 600 on ~/.openclaw/openclaw.json after writing the Telegram bot
token to prevent other users on the VM from reading the token in plaintext.

Fixes #3033
Fixes #3034

Agent: security-auditor

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-27 06:15:03 +07:00
A
a7b1596b98
docs: sync README with source of truth (#3026)
* docs: sync README commands table with help.ts source of truth

remove 5 command rows from the README commands table that are not present
in packages/cli/src/commands/help.ts getHelpUsageSection():
- spawn list --flat
- spawn list --json
- spawn tree
- spawn tree --json
- spawn history export

these commands exist in code (index.ts, list.ts) but are not listed in the
canonical help section, which is the Gate 2 source of truth per qa/record-keeper
protocol.

* fix: restore documentation for working commands (spawn tree, list --flat, --json, history export)

Agent: pr-maintainer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: add 5 missing commands to help.ts getHelpUsageSection()

Add spawn tree, spawn tree --json, spawn list --flat, spawn list --json,
and spawn history export to the help text. These commands are implemented
in the codebase but were missing from --help output.

Addresses reviewer feedback to add commands to help.ts source of truth
rather than removing them from README.

Bump version 0.26.6 -> 0.26.7

Agent: pr-maintainer
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

---------

Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 06:13:44 +07:00
A
aafdb8655f
fix(security): pipe encoded commands via stdin in GCP/AWS exec functions (#3036)
Replace shell interpolation of base64-encoded commands in SSH invocations
with stdin piping. Previously the encoded command was interpolated into the
remote shell string; now it is passed via stdin to `base64 -d | bash`,
making the approach structurally immune to command injection regardless
of the encoded content.

Fixes #3029
Fixes #3022

Agent: code-health

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-27 06:11:50 +07:00
A
4ac4a7e0cf
feat: recursive spawn tree passback (#3023)
* feat: pull child spawn history back to parent for `spawn tree`

When the interactive session ends (or headless mode completes), the
parent downloads the child VM's history.json and merges records into
local history. Before downloading, it runs `spawn pull-history` on the
child, which recursively pulls from all grandchildren — so the full
tree collapses up to the root regardless of depth.

Changes:
- Add getParentFields() — sets parent_id/depth on saveSpawnRecord calls
- Add pullChildHistory() — downloads + merges child history after session
- Add `spawn pull-history` command for recursive SSH-based history pull
- Add 11 tests for parseAndMergeChildHistory

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: trigger CI recompute

Agent: pr-maintainer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(security): validate user/ip params before SSH exec in pull-history

Agent: pr-maintainer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(security): use shared validators for SSH params in pull-history and delete

Replace inline regex checks in pull-history.ts with validateUsername()
and validateConnectionIP() from security.ts, matching the pattern used
across connect.ts, fix.ts, and link.ts. Also add the same validation
to delete.ts:pullChildHistory which had no SSH parameter validation.

orchestrate.ts uses the runner abstraction (not raw user@ip), so its
SSH params come from the cloud provider, not untrusted history records.

Agent: pr-maintainer
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Ahmed Abushagur <ahmed@abushagur.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
2026-03-26 15:21:50 -07:00
A
a8e63648da
test: remove duplicate and theatrical tests in spawn-skill (#3027)
Consolidate 15 repetitive it() blocks in spawn-skill.test.ts into
data-driven table tests:

- getSpawnSkillPath: 8 separate 'returns correct path for X' tests
  collapsed into one table-driven it() iterating all 8 agent/path pairs
- isAppendMode: 7 separate 'returns false for X' tests (one per
  non-hermes agent) collapsed into a single loop-based it() — all
  tested the same code path with the same expected value

Coverage is unchanged: all agent/path pairs are still asserted, the
hermes=true case and the nonexistent=undefined case are preserved as
individual tests. Test count drops from 45 to 30 in this file.

Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: L <6723574+louisgv@users.noreply.github.com>
2026-03-27 05:10:41 +07:00
Ahmed Abushagur
c61736e511
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>
2026-03-26 13:53:49 -07:00
A
2dd87c986d
feat(cli): add star-the-repo nudge after successful spawns (#3025)
Some checks are pending
CLI Release / Build and release CLI (push) Waiting to run
Lint / ShellCheck (push) Waiting to run
Lint / Biome Lint (push) Waiting to run
Lint / macOS Compatibility (push) Waiting to run
Shows a non-intrusive " Enjoying Spawn? Star us on GitHub!" message
to returning users (2+ successful spawns) after a successful spawn
session completes. Shown at most once per 30 days.

- New `maybeShowStarPrompt()` in `shared/star-prompt.ts`
- Tracks `starPromptShownAt` in `~/.config/spawn/preferences.json`
- Called after `execScript()` returns success in cmdRun, cmdInteractive,
  and cmdAgentInteractive (skipped in headless mode)
- The `execScript()` return type changed from `void` to `boolean`
  to indicate whether the script ran successfully
- Added 7 unit tests covering all gate conditions

Fixes #3020

Agent: issue-fixer

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-27 03:15:12 +07:00
A
255ffbf8b7
fix(security): use grep -F for literal string matching in PATH checks (#3021)
Fixes #3019

Replace `grep -qx` with `grep -qxF` in the `ensure_in_path` function
to prevent regex pattern injection. Without -F, attacker-controlled
SPAWN_INSTALL_DIR or BUN_INSTALL env vars containing regex metacharacters
(e.g. `/.*`) could cause false positive/negative PATH matches, potentially
bypassing the symlink creation logic.

Agent: issue-fixer

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-27 02:56:07 +07:00
A
f2044f8d62
fix: add --yes/-y to KNOWN_FLAGS so spawn delete --name <name> --yes works (#3024)
PR #3015 added --yes and -y flags to the delete command but didn't add
them to KNOWN_FLAGS in flags.ts. This caused `spawn delete --name foo --yes`
to fail with "Unknown flag: --yes" because checkUnknownFlags runs before
dispatchDeleteCommand strips these flags.

Also adds delete-specific flags to --help documentation.

Agent: code-health

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 02:54:33 +07:00
Ahmed Abushagur
0f48e4dae5
feat: headless delete via spawn delete --name <name> --yes (#3015)
Agents running on spawned VMs couldn't delete child spawns because
`spawn delete` requires an interactive terminal for the picker UI.

Added --name and --yes flags: when both are provided in non-interactive
mode, the server matching the name is deleted without prompts. This
enables agents to manage their own child VMs programmatically.

Updated all skill files to teach agents the headless delete syntax.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: A <258483684+la14-1@users.noreply.github.com>
2026-03-26 12:30:15 -07:00