Commit graph

129 commits

Author SHA1 Message Date
A
afa17d09ff
test: remove Bun.spawnSync subprocess calls from ssh-keys tests (#2101)
* test: remove Bun.spawnSync subprocess calls from ssh-keys tests

Replace Bun.spawnSync calls to ssh-keygen in createFakeKeyPair helper
with plain file writes, and mock Bun.spawnSync via spyOn for all tests
that exercise getKeyType, generateSshKey, and getSshFingerprint.

Cuts test runtime from 1212ms to ~47ms (25x speedup) and brings the
test file into compliance with the CLAUDE.md no-subprocess-spawning
policy.

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

* fix: apply biome formatting to ssh-keys test

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

---------

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-02 04:25:24 -05:00
Ahmed Abushagur
297e7ff21c
fix(sprite): add 60s timeout to destroyServer to prevent hanging (#2096)
The sprite destroy command can hang indefinitely when the Sprite API
is unresponsive. Add a 60s timeout using the existing killWithTimeout
utility (same pattern as runSprite).

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: L <6723574+louisgv@users.noreply.github.com>
Co-authored-by: A <258483684+la14-1@users.noreply.github.com>
2026-03-01 22:37:57 -08:00
A
3911b5bc28
refactor: resolve conflicts — merge packages/shared into packages/cli/src/shared (#2092)
Rebased fix/issue-2083 onto main after commands.ts split (PR #2095).
Key resolutions:
- commands.ts: kept HEAD shim (re-exports from ./commands/index.ts)
- package.json: kept PR version 0.12.0 without @openrouter/spawn-shared dep
- Fixed @openrouter/spawn-shared imports in commands/shared.ts, commands/update.ts,
  and __tests__/orchestrate.test.ts that were added after the PR branched

All 1390 tests pass, biome lint clean.

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-01 22:05:41 -08:00
A
dc489fa652
docs: update __tests__/README.md to reflect current test structure (#2098)
The README was referencing commands.test.ts and integration.test.ts which
no longer exist (split into 20+ specialized files), and incorrectly stated
the test runner was vitest (banned — project uses bun:test). Rewrote to
accurately document all 44 test files with their coverage scope.

Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 00:04:53 -05:00
A
aa7584096d
test: centralize @clack/prompts mock in test-helpers.ts (#2090)
* test: centralize @clack/prompts mock in test-helpers.ts

Adds mockClackPrompts() factory to test-helpers.ts, eliminating ~15-line
duplicate mock.module blocks from 19 test files. When @clack/prompts adds
a new export, only one file needs updating instead of 19.

Fixes #2080

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

* style: fix Biome formatting after merge with main

Agent: pr-maintainer
Co-Authored-By: Claude Sonnet 4.5 <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-01 20:27:24 -08:00
A
65b872afa3
refactor: split commands.ts into per-command modules (#2095)
* refactor: split commands.ts into per-command modules

commands.ts (3,522 lines) is split into focused modules under
packages/cli/src/commands/:
- shared.ts: helpers, entity resolution, credentials
- interactive.ts: cmdInteractive, cmdAgentInteractive
- run.ts: cmdRun, cmdRunHeadless
- list.ts: cmdList, cmdLast, cmdListClear
- delete.ts: cmdDelete
- info.ts: cmdMatrix, cmdAgents, cmdClouds, cmdAgentInfo, cmdCloudInfo
- update.ts: cmdUpdate
- help.ts: cmdHelp
- pick.ts: cmdPick
- index.ts: barrel re-export

commands.ts is kept as a 2-line compatibility shim so all existing
imports from "./commands.js" continue to work unchanged.

Fixes #2076

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

* fix: apply biome formatting

Co-Authored-By: Claude Sonnet 4.5 <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-01 23:24:15 -05:00
A
4802852fac
fix: derive agent lists dynamically in usage messages (#2089)
Six of seven cloud main.ts files had hardcoded agent lists that were
stale (missing hermes, added in #2084). Replace all hardcoded lists
with Object.keys(agents).join(", ") so they stay in sync automatically
when new agents are added.

Agent: code-health

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
2026-03-01 23:21:15 -05:00
A
29cb0d4c69
test: add unit tests for shared/orchestrate.ts (#2093)
* test: add unit tests for shared/orchestrate.ts

Add 19 focused tests for runOrchestration covering:
- Cloud lifecycle method ordering (authenticate -> provision -> install -> launch)
- API key acquisition and injection into agent.envVars
- process.exit forwarding of interactiveSession exit codes
- Optional hooks: preProvision (non-fatal on error), configure, preLaunch
- Model selection gating via agent.modelPrompt / modelDefault
- Restart loop wrapping for non-local clouds vs raw passthrough for local
- saveLaunchCmd receives unwrapped command
- prepareStdinForHandoff and offerGithubAuth integration

Fixes #2077

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

* style: fix Biome formatting in orchestrate.test.ts

Agent: pr-maintainer
Co-Authored-By: Claude Sonnet 4.5 <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-01 23:19:33 -05:00
A
608e8b4bdd
refactor: eliminate 7 identical agents.ts boilerplate files (#2088)
* refactor: eliminate 7 identical agents.ts boilerplate files

Adds createCloudAgents() factory to shared/agent-setup.ts, reducing
each cloud's agents.ts from 16-line copy-paste to a single call.
Net reduction of 49 lines across 9 files.

Fixes #2078

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

* chore: apply biome formatting

---------

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-01 22:06:57 -05:00
A
b755c6966c
feat: add local/hermes to complete the 7x7 matrix (#2091)
Fixes #2079 — local/hermes was the only remaining missing entry in the
cloud×agent matrix. All 49 entries are now implemented.

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-01 22:04:38 -05:00
A
2605c9cb83
refactor: Remove dead code and stale references (#2086)
- Add getSpawnCloudConfigPath(cloud) helper to shared/ui.ts, eliminating
  four identical 3-line getConfigPath() functions across hetzner, daytona,
  digitalocean, and aws cloud modules
- Remove duplicate homedir/join imports from hetzner, daytona, digitalocean,
  and aws now that the shared helper centralizes the path construction
- Update commands.ts hasCloudConfigCredentials to use the shared helper
  and drop its stale homedir import
- Bump CLI to 0.11.24 (patch)

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-01 20:43:12 -05:00
A
9043143a5a
test: Remove duplicate and theatrical tests (#2085)
Remove 14 redundant tests across two files:

- check-entity.test.ts: Remove 6 individual "valid entities" tests
  (claude, codex, cline as agents; sprite, hetzner, vultr as clouds) that
  are fully covered by the loop-based "all manifest agents/clouds validate
  correctly" describe blocks which exhaustively test all entities.

- check-entity.test.ts: Remove 6 individual "wrong-type detection" tests
  (3 clouds-as-agents, 3 agents-as-clouds) that are covered by the loop
  tests "should reject every agent key when checked as cloud" and
  "should reject every cloud key when checked as agent".

- cloud-init.test.ts: Consolidate 3 NODE_INSTALL_CMD tests into 1.
  "is a non-empty string" is theatrical (tests the constant exists, not
  what it does). Merge with the two content checks into a single test.

Test count: 1385 → 1371.

Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 20:41:40 -05:00
A
d713f9650f
feat: add hermes agent to 4 clouds, bump install wait to 600s (#2084)
- Add hermes shim scripts for GCP, Hetzner, DigitalOcean, and Daytona
- Update manifest.json matrix entries from "missing" to "implemented"
- Bump default INSTALL_WAIT from 300s to 600s to fix zeroclaw timeout
  on small VMs where Rust compilation takes 8-12 minutes
- Update cloud READMEs with hermes usage docs
- Bump CLI version to 0.11.18

Co-authored-by: Ahmed Abushagur <ahmed@abushagur.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 19:31:50 -05:00
A
69d1971abf
fix(security): remove space from token validation charset in key-request.sh (#2074)
API tokens never contain spaces; allowing them risks word splitting
in downstream unquoted uses of these env vars. Updated both the shell
regex in key-request.sh and the corresponding TypeScript regexes in
digitalocean.ts to stay in sync.

Fixes #2072

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-01 17:10:22 -05:00
A
bb4deaf24c
fix: reset stale cache flag, guard gcloud null, validate DO config (#2073)
- manifest.ts: Reset _staleCache on successful fetch/cache load so
  isStaleCache() doesn't falsely report stale data after reconnecting
- gcp.ts: Replace getGcloudCmd()! with requireGcloudCmd() that throws
  a descriptive error instead of crashing with null dereference
- digitalocean.ts: Replace unvalidated JSON.parse return with
  parseJsonObj() + isString()/isNumber() guards for type safety

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-01 17:08:38 -05:00
A
b066b3a1ac
test: Remove duplicate and theatrical tests (#2070)
* test: Remove duplicate and theatrical tests

Remove 4 duplicate tests spread across security and command resolution test files:

- security-edge-cases.test.ts: Remove "should accept prompts with dollar signs in
  safe contexts" (duplicate of security.test.ts "should accept dollar signs in
  non-expansion contexts")
- security-edge-cases.test.ts: Remove "should accept prompts with pipe to non-shell
  commands" (duplicate of security.test.ts "should accept prompts with pipes to
  other commands")
- security-edge-cases.test.ts: Remove "should accept prompts with semicolons not
  followed by rm" (duplicate of security-encoding.test.ts "should accept semicolons
  not followed by rm")
- commands-swap-resolve.test.ts: Remove "should not log resolution for already-
  lowercase exact keys" (duplicate of commands-resolve-run.test.ts "should not log
  resolution when exact keys are used" — identical cmdRun("claude", "sprite") call)

No functional behavior changes. Test count: 1389 → 1385.

* fix: remove trailing blank line for biome format

---------

Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: L <6723574+louisgv@users.noreply.github.com>
2026-03-01 15:53:27 -05:00
A
dbec3e768c
fix(security): add restrictive file permissions to Sprite saveVmConnection (#2068)
The Sprite saveVmConnection() wrote ~/.spawn/last-connection.json without
restrictive permissions (defaulting to umask 0o644/0o755), unlike the shared
saveVmConnection() in history.ts which correctly uses mode 0o700 for the
directory and 0o600 for the file. On multi-user systems this could expose
server names and connection metadata to other users.

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-01 15:44:15 -05:00
A
43843a882b
refactor: Remove dead setupOpenclawBatched export and unused batched setup mechanism (#2069)
- Delete the exported `setupOpenclawBatched` function from `agent-setup.ts` — it was
  never imported or called anywhere in the codebase (confirmed via exhaustive grep)
- Remove the unused `setup?` field from the `AgentConfig` interface in `agents.ts` —
  no agent implementation ever assigned this property
- Remove the dead `if (agent.setup)` branch from `orchestrate.ts` — the batched path
  was always unreachable because no agent provided a `setup` callback

Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 15:43:43 -05:00
A
8025376ee6
fix: use ignore stdin for SSH commands to prevent deadlock on Hetzner and DigitalOcean (#2066)
runServer and runServerCapture on Hetzner and DigitalOcean used stdio:["pipe",...]
for stdin but called proc.stdin!.end() AFTER await proc.exited. If a remote SSH
command reads from stdin (apt prompts, read calls), the process deadlocks until the
5-minute timeout fires. AWS and GCP correctly use stdio:["ignore",...].

Fix: change stdin from "pipe" to "ignore" in runServer and runServerCapture for
both Hetzner and DigitalOcean, removing the now-unnecessary stdin.end() calls.

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-01 18:48:33 +00:00
A
631722151c
fix(hetzner): add SPAWN_CUSTOM guard to promptServerType (#2065)
Every other cloud provider (GCP, DO, Daytona) gates their size/type
picker behind SPAWN_CUSTOM !== "1" so users get a fast default launch.
Hetzner's promptLocation had the guard but promptServerType was missing
it, causing an unexpected interactive picker on the cheapest/most-used
cloud when running without --custom.

Bump CLI to 0.11.19.

Agent: team-lead

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 12:41:32 -05:00
A
902f3091d3
test: Remove duplicate and theatrical tests (#2061)
* test: Remove duplicate and theatrical tests

- Remove 3 duplicate/always-pass tests from commands-update-download.test.ts:
  "should reject script without shebang via validateScriptContent" (already covered
  in download-and-failure.test.ts and cmdrun-happy-path.test.ts),
  "should reject script with dangerous pattern" (duplicate + always-pass or-chain),
  "should show script-not-found message when both URLs 404" (duplicate of existing 404 test)
- Remove 5 theatrical tests from custom-flag.test.ts that only verify
  constant arrays have entries with defined id/label fields (SERVER_TYPES,
  LOCATIONS, DROPLET_SIZES, DO_REGIONS, SANDBOX_SIZES) — these test constant
  existence, not behavior, and fail due to @openrouter/spawn-shared import error
- Bump CLI version to 0.11.18

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

* fix: Remove trailing blank lines in custom-flag.test.ts for biome format

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: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
2026-03-01 09:09:29 -08:00
A
cef14ce9ea
fix(sprite): pass timeoutSecs through to runSprite, add kill-on-timeout (#2060)
runSprite was wired as CloudRunner.runServer but silently dropped the
timeoutSecs parameter. All other clouds (Hetzner, DO, AWS, GCP, Daytona)
implement kill-on-timeout via setTimeout+killWithTimeout; Sprite had zero
timeout protection, so a hung agent install (e.g. ZeroClaw's 600s Rust
compile, Claude Code's 300s install) would hang forever on Sprite.

Matches the pattern used by every other cloud provider.

Agent: team-lead

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-01 08:21:26 -05:00
A
40e14c2b6b
test: Remove duplicate and theatrical tests (#2058)
- manifest-type-contracts.test.ts: Replace 42 per-agent/per-cloud
  silently-skipping tests (if field === undefined { return }) with 6
  aggregate tests that filter to entries that actually have the field
  and assert the field count > 0 so the test can't pass vacuously.
  Affected: pre_launch, config_files, notes (agents); defaults, notes,
  icon (clouds).

- history.test.ts: Remove always-pass test "throws for SPAWN_HOME
  pointing to /root when user home is different" — it silently returns
  early whenever the CI environment runs as root (which it always does),
  providing zero signal. The adjacent "throws for SPAWN_HOME outside
  home directory" test already covers this semantic.

Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 08:20:44 -05:00
Ahmed Abushagur
45caf4b96b
fix(sprite): fix all 6 Sprite agent installs for E2E (#2057)
* fix(sprite): fix all 6 Sprite agent installs for E2E

- Use `npm install -g --prefix` instead of `npm config set prefix` to
  avoid creating .npmrc that conflicts with nvm on Sprite VMs
- Fix shell environment setup to only modify .bash_profile (not .bashrc)
  so non-interactive bash -c commands retain PATH config
- Add $HOME/.cargo/bin to PATH for zeroclaw (Sprite has no ~/.cargo/env)
- Add $HOME/.local/bin to PATH config for Sprite shell environment
- Add sprite E2E cloud driver with org detection, config corruption fix,
  direct command embedding (not $1 positional), and retry logic
- Fix provision.sh to kill full process tree after timeout (prevents
  orphaned sprite exec sessions from corrupting config)
- Fix verify.sh zeroclaw check to not rely on ~/.cargo/env existing

Tested: 6/6 Sprite agents pass E2E (claude, codex, openclaw, zeroclaw,
opencode, kilocode). Hermes is not in the Sprite manifest.

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

* fix: biome format - collapse runSprite call to single line

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: L <6723574+louisgv@users.noreply.github.com>
2026-03-01 07:15:09 -05:00
A
41adfbdb0a
test: Remove duplicate and theatrical tests (#2054)
* test: Remove duplicate and theatrical tests

- check-entity.test.ts: Remove 'kind parameter consistency' describe block
  (9 tests) that fully duplicated coverage already provided by 'valid entities',
  'wrong-type detection: cloud given as agent', and 'wrong-type detection: agent
  given as cloud' describes. Also remove redundant loop assertions ('should
  return true for all three agent keys' etc.) that repeated what the individual
  named tests already covered.
- manifest-cache-lifecycle.test.ts: Replace Record<string, any> with
  Record<string, AgentDef> and Record<string, CloudDef> for type safety.

1401 tests pass, 0 fail. Lint clean.

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

* fix: remove extra blank line to pass Biome format check

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

* test: Remove duplicate and theatrical tests

Remove redundant if-guards around always-present agent metadata fields in
manifest-type-contracts.test.ts. All 12 metadata fields (creator, repo,
license, created, added, github_stars, stars_updated, language, runtime,
category, tagline, tags) are present on all 7 agents, making the
if (agent.X !== undefined) guards always-truthy dead code that misleads
readers into thinking tests might be skipped. Restructure into proper
per-agent describe blocks to make the test structure honest and clear.

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

* fix: Apply Biome formatting to array literal

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

* test: Remove duplicate and theatrical tests

Fix always-pass anti-pattern in manifest-type-contracts.test.ts where
optional field type tests were gated by `if (field !== undefined)` OUTSIDE
the `it()` block. When no agent/cloud had the field, zero tests registered,
giving false confidence.

Changes:
- Agent optional field types: move condition inside `it()`, test always runs
- Cloud optional field types: same fix, tests always register for all clouds
- Interactive prompts structure: consolidate filtered loop into one `it()` that
  iterates internally, avoiding silently-absent test registrations
- Config files structure: same consolidation pattern

Before: 551 pass, 64 fail (optional field tests only registered per-agent)
After:  566 pass, 64 fail (optional field tests register for every agent/cloud)

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

* style: fix biome lint errors - add block statements to early returns

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

* style: apply biome formatter to block statements

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

---------

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-01 04:16:05 -05:00
A
210519a590
fix(security): document PKCE migration path for DigitalOcean OAuth (#2056)
Adds explicit monitoring obligation and step-by-step migration
checklist to the DO_CLIENT_SECRET comment. Tracks when PKCE was last
verified unsupported (2026-03) and what to do when it becomes
available, addressing the technical debt tracking request from #2041.

Fixes #2041

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-01 04:11:40 -05:00
A
84133fb036
fix(security): replace validateLaunchCmd blocklist with allowlist (#2053)
* fix(security): replace validateLaunchCmd blocklist with allowlist

The blocklist pattern />\\s*\\// (redirection to absolute path) matched
2>/dev/null, which appears in every valid launch command generated by
agent-setup.ts. This caused mergeLastConnection() to reject and discard
all connection data, breaking the spawn list → "Enter agent" reconnect
flow and spawn last.

Replace the blocklist with a strict allowlist: each semicolon-separated
segment must match one of:
  - source ~/.<rc-file> [2>/dev/null]
  - export PATH=<safe-path>
  - <binary> [simple-args]

This simultaneously fixes the false-positive and closes the latent
injection gap (the old blocklist only blocked '; rm' but not arbitrary
'; <other-cmd>').

Fixes #2052

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

* style: apply biome formatter to fix CI format check

Co-Authored-By: Claude Sonnet 4.5 <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-01 03:12:27 -05:00
A
9be0c9597d
fix: spawn last reconnects to existing VM instead of always reprovisioning (#2051)
`cmdLast()` was always calling `cmdRun()`, creating a brand-new VM every
time. Wire it into `handleRecordAction()` instead, which already contains
the reconnect-vs-rerun logic used by `spawn list`: if the latest history
record has a live connection (IP + server ID), the user is offered options
to enter the agent or SSH in; only if no connection info exists (or the
user chooses "Spawn a new VM") does it provision a fresh instance.

Also bumps CLI version 0.11.13 → 0.11.14.

Fixes #2050

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-01 01:49:36 -05:00
A
e1ef024981
test: Remove duplicate and theatrical tests (#2047)
* test: Remove duplicate and theatrical tests

- check-entity.test.ts: Remove 'kind parameter consistency' describe block
  (9 tests) that fully duplicated coverage already provided by 'valid entities',
  'wrong-type detection: cloud given as agent', and 'wrong-type detection: agent
  given as cloud' describes. Also remove redundant loop assertions ('should
  return true for all three agent keys' etc.) that repeated what the individual
  named tests already covered.
- manifest-cache-lifecycle.test.ts: Replace Record<string, any> with
  Record<string, AgentDef> and Record<string, CloudDef> for type safety.

1401 tests pass, 0 fail. Lint clean.

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

* fix: remove extra blank line to pass Biome format check

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

* test: Remove duplicate and theatrical tests

Remove redundant if-guards around always-present agent metadata fields in
manifest-type-contracts.test.ts. All 12 metadata fields (creator, repo,
license, created, added, github_stars, stars_updated, language, runtime,
category, tagline, tags) are present on all 7 agents, making the
if (agent.X !== undefined) guards always-truthy dead code that misleads
readers into thinking tests might be skipped. Restructure into proper
per-agent describe blocks to make the test structure honest and clear.

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

* fix: Apply Biome formatting to array literal

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

---------

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-01 00:11:48 -05:00
A
708326a693
refactor: Remove dead exports across TypeScript modules (#2044)
Remove `export` from functions that are only used internally within their
own file and never imported elsewhere. Affected modules:

- `history.ts`: `mergeLastConnection` (only called internally by `getActiveServers`/`filterHistory`)
- `update-check.ts`: `isUpdateBackedOff` (only called internally by `checkForUpdates`)
- `aws/aws.ts`: `waitForSsh` (only called internally by `waitForCloudInit`)
- `gcp/gcp.ts`: `waitForSsh` (only called internally by `waitForCloudInit`)
- `daytona/daytona.ts`: `waitForSsh` (only called internally by `waitForCloudInit`)
- `shared/agent-setup.ts`: 11 implementation helpers (`installAgent`, `uploadConfigFile`,
  `installClaudeCode`, `setupClaudeCodeConfig`, `promptGithubAuth`, `setupCodexConfig`,
  `setupOpenclawConfig`, `startGateway`, `setupZeroclawConfig`, `ensureSwapSpace`,
  `openCodeInstallCmd`) — all only used within `createAgents()`

All 1410 tests pass, biome lint clean (0 errors).

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-02-28 20:39:38 -05:00
A
ad38a66c96
fix: extend HOME hardening to local.ts and commands.ts (missed by #2026/#2036) (#2037)
When HOME is unset (containers, systemd, cron, some CI), two files still used
`process.env.HOME || ""` which produces broken paths:
- local/local.ts:38 — uploadFile() expands ~ to "", writing config files to
  filesystem root (e.g. /.openclaw/openclaw.json) instead of ~/.openclaw/
- commands.ts:898 — hasCloudConfigCredentials() checks "" + .config/spawn/
  resolving to /.config/spawn/{cloud}.json, silently failing credential
  detection and causing false "Missing credentials" warnings on every run

Fix: add `import { homedir } from "node:os"` to both files and change
`process.env.HOME || ""` to `process.env.HOME || homedir()`.

Completes the HOME hardening series started in #2026 and #2036.

Agent: team-lead

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-28 15:27:29 -08:00
A
0904e53c85
fix: surface OAuth denial error immediately instead of waiting 120s (#2039)
When a user denies OAuth access on OpenRouter or DigitalOcean, the CLI
now immediately shows a clear error message and falls back to manual
key entry, instead of silently waiting the full 120s poll timeout.

Changes:
- OpenRouter OAuth: check for `error` query param on callback, set
  `oauthDenied` flag, show denial-specific HTML page in browser, break
  polling loop early, and log a clear terminal error
- DigitalOcean OAuth: add `oauthDenied` flag (error detection already
  existed but the polling loop still waited 120s), break loop early

Fixes #2038

Agent: code-health

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-28 14:42:32 -08:00
A
44f67462ed
fix: extend HOME hardening to ssh-keys, sprite, gcp (3 files missed by #2026) (#2036)
When HOME is unset (containers, systemd, cron), process.env.HOME produces
literal "undefined" in path strings:
- ssh-keys.ts: SSH discovery/generation writes to "undefined/.ssh/"
- sprite.ts: CLI detection misses ~/.local/bin, PATH update corrupted
- gcp.ts: gcloud detection misses ~/google-cloud-sdk/bin, PATH corrupted

Same fix as #2026: use `process.env.HOME || homedir()` via `join()` for
robust OS-level fallback when HOME is unset.

Agent: team-lead

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 13:51:09 -08:00
A
8975c3c986
fix(openclaw): add pre-launch tip for sequential channel setup (#2035)
Display a hint before launching `openclaw tui` warning users to set
up channels one at a time. Concurrent token pastes trigger a race
condition inside OpenClaw's TUI that causes setup to hang.

Adds an optional `preLaunchMsg` field to `AgentConfig` so any agent
can surface a user-visible tip just before its interactive session
starts. OpenClaw sets this to advise sequential channel onboarding.

Fixes #2030

Agent: issue-fixer

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-28 13:11:56 -08:00
A
7003a8ad40
fix: replace module-level process.env.HOME with homedir() in config paths (#2026)
Fixes #2025

Silent credential loss in Docker/CI when HOME is unset. Use node:os
homedir() which has OS-level fallbacks and matches history.ts pattern.
Prefer process.env.HOME when set to respect test sandboxing overrides.

Agent: code-health

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-28 12:54:12 -08:00
A
a8b7bb7fb9
test: consolidate wasteful one-per-flag tests in unknown-flags suite (#2029)
Remove 18 redundant/theatrical tests from unknown-flags.test.ts:

- Removed duplicate 'should detect --verbose as unknown' test (same name,
  same assertion, nearly identical inputs as the test 28 lines above it)
- Consolidated 14 individual 'allows known flags' tests — each called
  findUnknownFlag([flag]) with a single flag and expected null — into one
  data-driven loop over all 17 flags; same coverage, 13 fewer test cases
- Removed 'should contain --name flag' which is fully subsumed by the
  immediately following 'should contain all expected flags' test that
  already verifies --name along with 22 other flags

Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 20:42:04 +00:00
A
912d8305c5
fix: add missing hermes agent to createAgents() and update sprite agents list (#2024)
The hermes agent was added to manifest.json and sh/sprite/hermes.sh in
feat #2023, but createAgents() in shared/agent-setup.ts was not updated.
This caused sh/sprite/hermes.sh to throw "Unknown agent: hermes" when
resolveAgent() was called.

- Add hermes entry to createAgents() with correct install cmd, envVars, and launchCmd
- Update sprite/main.ts usage error message to include hermes

Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 12:08:18 -05:00
A
ba06e49d97
test: Remove duplicate and theatrical tests (#2020)
Remove 162 lines of duplicate/theatrical test code across 5 files:

- manifest-integrity.test.ts: Drop 5 crude toBeTruthy() field checks
  superseded by manifest-type-contracts.test.ts with precise type assertions
- manifest-type-contracts.test.ts: Remove dead deps block (0 agents have
  deps) and Dotenv configuration describe block (0 agents have dotenv) —
  both generated zero tests at runtime
- commands-error-paths.test.ts: Remove 5-test "swapped arguments detection"
  block duplicated by the more thorough commands-swap-resolve.test.ts
- run-path-credential-display.test.ts: Remove 5-test "implementation checks"
  block that retested getImplementedClouds/Agents already covered by
  commands-exported-utils.test.ts; also remove now-unused imports
- manifest.test.ts: Remove redundant toBeDefined() assertion after an
  already-present toHaveProperty() check on the same field

Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 07:49:55 -05:00
A
ae3f4001cc
refactor: Remove dead code and stale references (#2017)
Remove stale comments in test files that referenced deleted test files
(commands-untested.test.ts, commands-helpers.test.ts) and remove
"Agent: X" metadata annotations that became obsolete after the
theatrical test cleanup.

All 1424 tests pass, biome lint clean.

Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
2026-02-28 02:07:48 -08:00
Ahmed Abushagur
d5461adc16
feat: SPAWN_CLI_DIR env var to force local source in e2e (#2015)
* feat: SPAWN_CLI_DIR env var to force local source in e2e and shell scripts

When SPAWN_CLI_DIR is set, the entire toolchain uses local TypeScript
source instead of downloading pre-bundled scripts from GitHub releases:

- e2e.sh: auto-sets SPAWN_CLI_DIR to repo root when running locally
- provision.sh: exports SPAWN_CLI_DIR into the headless subshell
- commands.ts: reads local shell scripts instead of fetching from CDN
- All 36 cloud/agent shell scripts: exec local main.ts when set

This enables e2e tests to validate local changes before they're released.

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

* fix(security): add path traversal defense to SPAWN_CLI_DIR script loading

Canonicalize the path via realpathSync and verify it stays inside the
resolved CLI directory before reading. Prevents SPAWN_CLI_DIR from
being used to read arbitrary files via ../ traversal.

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

* fix(security): harden SPAWN_CLI_DIR path traversal defense

- Validate cloud/agent names don't contain '..', '/' or '\' before
  constructing file paths
- Fix root-directory edge case in prefix check by handling trailing
  separator correctly

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
2026-02-28 04:14:36 -05:00
A
ee83e46d9b
test: Remove duplicate and theatrical tests (#2016)
Remove 3 duplicate tests from security-edge-cases.test.ts that were
already covered in security.test.ts:

- validateIdentifier: "reject 65-char identifier" (duplicate of
  security.test.ts "should reject overly long identifiers")
- validateScriptContent: "accept wget|sh" (duplicate of
  security.test.ts "should accept scripts with wget|bash")
- validatePrompt: "accept prompts at exactly the max length" (duplicate
  of security.test.ts "should accept prompts at the size limit")

The edge-cases file retains unique tests: 64-char boundary check,
single char identifiers, mkfs with multiple filesystems, extra-whitespace
pipe detection, etc.

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-02-28 04:11:48 -05:00
A
f0cadc2758
test: Remove duplicate and theatrical tests (#2009)
* test: Remove duplicate and theatrical tests

Found and removed three categories of test anti-patterns:

1. **Conditional always-pass guard** (`with-retry-result.test.ts`): The
   `if (!result.ok)` wrapper around `expect(result.error).toBeInstanceOf(Error)`
   was silently skippable if the condition ever evaluates false. Replaced with a
   type-narrowing early return (`if (result.ok) { return; }`) so the assertions
   always execute when the code path is reached.

2. **Duplicate `loadManifest` tests** (`manifest.test.ts`): Five tests covering
   stale-cache fallback, no-cache-network-fail, invalid-fetch-fallback, fetch
   timeout, and cached-instance reuse were exact duplicates of tests already
   in `manifest-cache-lifecycle.test.ts` (which covers these scenarios more
   thoroughly). Removed the duplicates; kept the three tests unique to
   `manifest.test.ts` (fetch URL validation, fresh-cache skips network,
   forceRefresh behavior).

3. **Duplicate manifest structural checks** (`manifest-type-contracts.test.ts`):
   The "Cross-referential consistency" (matrix coverage, no-invalid-refs, valid
   status values) and "Display name uniqueness" (unique agent/cloud names, no
   key collisions) describe blocks duplicated tests already present in
   `manifest-integrity.test.ts`. Removed the 6 redundant tests; the unique type
   validation tests (per-field `typeof` checks) remain.

Net result: -11 tests, 0 new failures, lint clean.

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

* fix: biome format issues in test files

Remove trailing blank line in manifest.test.ts and expand single-line
if block in with-retry-result.test.ts to satisfy biome formatter.

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: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
2026-02-28 00:08:57 -08:00
A
31b52e92c3
fix(daytona): handle daytona-sandbox sentinel in cmdConnect (#2011)
When a user selects a Daytona sandbox in `spawn list` and chooses
"SSH into VM", cmdConnect was missing the daytona-sandbox sentinel
handler. It fell through to the generic SSH path and tried to run
`ssh daytona@daytona-sandbox`, which fails with a cryptic error.

Added the handler mirroring the existing pattern in cmdEnterAgent.

Agent: code-health

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-27 22:47:58 -08:00
Ahmed Abushagur
b4b1b149e9
fix(gcp): remove invalid --subnet-region flag from instance creation (#2012)
`gcloud compute instances create` doesn't accept `--subnet-region`.
The subnet region is automatically inferred from `--zone`. This flag
causes all GCP provisioning to fail.

Bump CLI to 0.11.3.

Signed-off-by: Ahmed Abushagur <ahmed@abushagur.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 01:13:35 -05:00
A
87978b424d
refactor: Remove dead code and stale references (#2010)
Dead code removed:
- `cleanup_stale_apps` function in `sh/e2e/lib/cleanup.sh` — defined but
  never called; `e2e.sh` calls `cloud_cleanup_stale` directly instead
- `generateEnvConfig` and `AgentConfig` re-exports from all 7 cloud-specific
  `agents.ts` modules (aws, hetzner, gcp, digitalocean, daytona, local,
  sprite) — nothing imported these from the cloud modules; they were already
  available via `@openrouter/spawn-shared` and `../shared/agents`

All 1435 tests pass, biome lint is clean (0 errors).

Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 00:18:20 -05:00
A
607aa6397f
test: Remove duplicate and theatrical tests (#2007)
- Delete manifest-helpers.test.ts: all 5 describe blocks (isValidManifest,
  readCache corruption, matrixStatus edge cases, countImplemented case
  sensitivity, agentKeys/cloudKeys) are fully covered by the more
  comprehensive manifest-cache-lifecycle.test.ts
- Fix always-pass assertion in manifest.test.ts: replaced
  expect(typeof existsSync(...)).toBe("boolean") (always true) with
  expect(manifest.agents).toBeDefined() which actually tests the outcome

-- qa/dedup-scanner

Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
2026-02-27 22:41:05 -05:00
A
e063180f06
refactor: Remove dead code and stale references (#2008)
Remove `setupOpenclawBatched` from `packages/cli/src/shared/agent-setup.ts`.
This function was exported but never called anywhere in the codebase — it was
superseded by the composable `setupOpenclawConfig` + `startGateway` approach
used in `createAgents()`.

Bump CLI patch version to 0.11.7.

Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 20:38:06 -05:00
A
4edc886e55
fix(security): validate launch_cmd from history before shell execution (#2006)
* fix(security): validate launch_cmd from history before shell execution

launch_cmd from history.json was passed directly to bash -lc via SSH with no
validation, enabling command injection if the history file was tampered with.

Adds validateLaunchCmd() that blocks $(...), backticks, pipes, command chaining,
redirections, and variable expansion. Validation is applied at both merge time
(history.ts:mergeLastConnection) and execution time (commands.ts:cmdEnterAgent).

Fixes #2005

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

* style: apply biome formatting to security.ts

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

---------

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-27 18:15:17 -05:00
A
a678402e67
refactor: Remove dead code and stale references (#2003)
- Remove `runLocalCapture` from local/local.ts (exported but never called)
- Remove `listServers` from aws, hetzner, digitalocean, daytona modules
  (all exported but never imported or called anywhere)
- Remove `InstanceListSchema` from aws.ts (only used in removed listServers)
- Remove now-unused imports in daytona.ts (parseJsonRaw, toObjectArray, toRecord)
- Bump CLI version 0.11.4 → 0.11.5

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-02-27 13:43:58 -08:00
A
64c34a3cbd
test: Remove duplicate and theatrical tests (#1998)
* test: Remove duplicate and theatrical tests

- Remove duplicate `prioritizeCloudsByCredentials with real-world patterns`
  describe block from run-path-credential-display.test.ts (84 lines). All
  four of its scenarios were already covered by the primary
  `prioritizeCloudsByCredentials` describe block in the same file.

- Remove theatrical `SPAWN_CUSTOM env var propagation` describe block from
  custom-flag.test.ts (21 lines). Its two tests only verified that
  `process.env` assignment works, not any application code.

No test scenarios lost; pre-existing 64 failures are unchanged.

-- qa/dedup-scanner

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

* style: fix trailing blank lines in test files for biome format

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>
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
2026-02-27 16:13:10 -05:00