Commit graph

151 commits

Author SHA1 Message Date
A
0aea348b8f
fix(ux): stop spinner before credential prompts during delete (#2144)
When credentials expire during server deletion, the spinner was running
simultaneously with interactive credential prompts, creating confusing
overlapping UI. Extract ensureDeleteCredentials() to run all credential
checks (which may prompt the user) before starting the deletion spinner.

All 6 cloud providers are covered: AWS, Hetzner, DigitalOcean, GCP,
Daytona, and Sprite.

Fixes #2141

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 11:55:45 -08:00
A
931fbed8b3
fix(ux): clarify credential detection messages to avoid confusion (#2147)
Distinguish between 'no local credentials' and 'using spawn-cached credentials'
so users understand why an initial failure message is followed by a success.

Fixes #2142

Agent: ux-engineer

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 11:54:18 -08:00
A
cfa1ae7a08
fix(security): add --proto '=https' to all curl bun installer calls (#2138)
* fix(security): add --proto '=https' to all curl bun installer calls

Fixes #2134

All _ensure_bun() functions across aws, hetzner, gcp, local, daytona,
and sprite scripts now enforce HTTPS-only downloads via --proto '=https'.
This prevents MITM attacks during bun installation on remote VMs.
DigitalOcean scripts were already correct and are not changed.

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

* fix(security): add --proto '=https' to bun installer in TS files

Address security reviewer feedback: the same MITM vulnerability
existed in 5 TypeScript programmatic provisioning files.

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

* fix(security): quote --proto '=https' in su -c curl calls

The aws.ts and gcp.ts files had --proto =https without quotes inside
su -c '...' blocks. Uses double quotes ("=https") to properly nest
inside the single-quoted su -c argument while maintaining protocol
restriction.

Agent: security-auditor
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.6 <noreply@anthropic.com>
2026-03-03 11:52:54 -08:00
A
f1ca7808c4
fix(ux): remove duplicate OAuth browser fallback URL message (#2143)
The DigitalOcean OAuth flow printed two near-identical fallback URL
messages: one manually before calling openBrowser(), and one from
openBrowser() itself. Remove the manual one since openBrowser()
already handles the fallback.

Fixes #2140

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 14:50:30 -05:00
A
91960b5e80
fix: exit process when remote session ends (#2148)
After showing post-session messages, the local process now exits cleanly
instead of requiring an extra Ctrl+C. The root cause was that after main()
resolved, lingering event loop handles (from @clack/prompts stdin listeners,
fetch connections, etc.) prevented Node/Bun from exiting naturally.

The fix adds process.exit(0) on successful main() completion, which covers
all session paths (bash script execution via execScript, SSH reconnection
via cmdConnect, and agent re-entry via cmdEnterAgent).

Fixes #2145

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-03 11:48:43 -08:00
A
79aa70c390
test: add coverage for untested ui utility functions (#2135)
* test: add coverage for 6 untested pure utility functions in shared/ui.ts

Adds tests for validateServerName, validateRegionName, validateModelId,
toKebabCase, sanitizeTermValue (security-critical), and jsonEscape.
These exported functions previously had zero test coverage.

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

* style: apply biome formatting to ui-utils test file

Address formatting review feedback: reformats destructuring import
to match project style.

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.6 <noreply@anthropic.com>
2026-03-03 11:22:06 -08:00
A
22a06e3237
test: Remove duplicate and theatrical tests (#2136)
- Remove cmdlast "should not call cmdRun when no history exists" test which
  admitted in its own comment that it could not verify its stated intent and
  simply duplicated the assertion from the previous test in the same describe block.

- Fix always-pass risk in manifest-type-contracts: "Interactive prompts
  structure" and "Config files structure" tests iterated over optional agent
  fields with a bare continue when the field was absent, meaning both tests
  would vacuously pass if no agents had those fields. Added guard assertions
  (expect(length).toBeGreaterThan(0)) matching the pattern used by sibling
  tests in the same file.

Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 11:50:54 -05:00
A
8de2c17c99
refactor: Remove dead code and stale references (#2132)
* refactor: Remove dead code and stale references

- Remove unused variables and functions in test files:
  - cmdlast.test.ts: remove unused cmdRunMock and consoleOutput function
  - cmdlist-integration.test.ts: remove unused resolveDisplayName import and consoleErrorOutput function
  - cmd-listing-output.test.ts: remove unused getTerminalWidth import
  - commands-update-download.test.ts: remove unused callIndex variable
  - download-and-failure.test.ts: remove unused callCount variable and unused init parameter
  - manifest-cache-lifecycle.test.ts: remove unused m1 variable
  - manifest-integrity.test.ts: fix unused key in for-loop destructuring
  - manifest-type-contracts.test.ts: fix 9 unused loop variables, remove implicit any let,
    replace while-exec loop with matchAll to resolve noAssignInExpressions error
- Fixes biome lint errors from 22 down to 0

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

* style: apply biome format to fix CI check

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-03 09:49:53 -05:00
A
6881719b1a
fix(security): pipe base64 via stdin in daytona uploadFile (#2133)
Eliminates b64 interpolation into the remote shell command string,
providing defense-in-depth alongside existing path validation.

Fixes #2130

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-03 08:32:40 -05:00
A
99a0f58937
test: fix always-pass in-memory cache test to assert fetch not called again (#2131)
The "should use fresh disk cache without calling fetch" test only checked
toHaveProperty("agents"), which would pass even if fetch was called again.
Renamed to reflect actual behavior (in-memory cache path) and added
assertions: expect(m2).toBe(m1) and fetch call count unchanged.

Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 08:30:33 -05:00
Ahmed Abushagur
300b330106
fix: address 4 reliability issues across codebase (#2129)
* fix: address 4 reliability issues across codebase

1. sprite.ts: add --force to destroy command (stdin is "ignore" so
   interactive prompts would hang until 60s timeout)

2. verify.sh: replace /dev/tcp port checks with ss -tln primary
   (Debian/Ubuntu bash compiled without /dev/tcp support)

3. verify.sh: make _openclaw_restart_gateway a hard failure instead
   of log_warn (matching _openclaw_ensure_gateway behavior)

4. agent-setup.ts: add ss -tln port check + "already running" early
   exit + increase timeout from 120s to 300s (gateway takes ~3min
   to initialize on AWS medium instances)

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

* fix: biome format - use consistent double quotes in portCheck

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-03 03:18:44 -05:00
A
c9b8ee5997
refactor: Remove dead code and stale references (#2128)
- sprite/sprite.ts: Replace duplicate saveVmConnection implementation
  with a call to the shared saveVmConnection from history.ts. The local
  version duplicated the mkdir + writeFileSync logic already provided by
  the shared function, just with Sprite-specific hardcoded values.
  Remove now-unused writeFileSync, mkdirSync, and getSpawnDir imports.
- Bump CLI version 0.12.5 → 0.12.6 (patch)

Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 22:05:38 -08:00
A
8bc0a0291b
test: fix always-pass cache test to assert fetch was not called (#2127)
The "should use disk cache when fresh" test in manifest.test.ts set up
a mock fetch with a comment saying it "should not be called" but never
asserted expect(global.fetch).not.toHaveBeenCalled(). The test passed
whether or not the cache was actually used, providing no signal.


-- qa/dedup-scanner

Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 00:04:48 -05:00
A
7b650a0103
test: Remove duplicate and theatrical tests (#2124)
* test: Remove duplicate and theatrical tests

Remove 18 duplicate tests from run-path-credential-display.test.ts
that repeated coverage already provided by dedicated test files:
- "entity validation for run path" (7 tests) duplicated check-entity.test.ts
- "key resolution for run path" (6 tests) duplicated fuzzy-key-matching.test.ts
- "run-path validation sequence integration" (5 tests) duplicated
  check-entity.test.ts, fuzzy-key-matching.test.ts, and script-failure-guidance.test.ts

Replace the three duplicate describe blocks with a focused 2-test
describe("isRetryableExitCode") block that covers the only unique
assertions in that section. Also remove unused spyOn import and
unused mockExit variable.

Bump version 0.12.4 → 0.12.5.

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

* fix(fmt): collapse import to single line for biome format compliance

Agent: team-lead
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-02 22:06:38 -05:00
Ahmed Abushagur
f7b3bc91c9
fix: show all polling status on single updating line (#2115)
Add logStepInline/logStepDone helpers to ui.ts and convert all 9
polling loops (DO droplet, DO cloud-init, AWS instance, AWS cloud-init,
Hetzner cloud-init, Daytona SSH, Sprite connectivity, GCP startup,
shared SSH port) from multi-line spam to a single line that updates
in place.

Signed-off-by: Ahmed Abushagur <ahmed@abushagur.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: L <6723574+louisgv@users.noreply.github.com>
2026-03-02 18:10:27 -05:00
A
37c1881613
fix(security): validate AWS region immediately after reading from env (#2119)
Adds validateRegionName() check immediately wherever awsRegion is
assigned from environment variables, rather than waiting until
createInstance(). Prevents malicious region values from being used
in SigV4 signing and shell commands.

Fixes #2113

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-02 13:52:20 -08:00
A
2a23ebcaf2
fix(security): restrict OAuth auth code regex to alphanumeric only (#2116)
Removes underscore and hyphen from the OAuth authorization code
validation regex, restricting it to alphanumeric characters only.
Defense in depth: if the code is ever used in logging or other
contexts, special characters won't create injection opportunities.

Fixes #2114

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-02 13:49:12 -08:00
A
97a92f3d4f
fix(digitalocean): throw on non-2xx in doApi() wrapper (#2112)
* fix(digitalocean): throw on non-2xx in doApi() wrapper

Make doApi() throw on non-2xx responses, matching hetznerApi and daytonaApi.
5/7 call sites were silently swallowing 401/403/404/422 errors by only
destructuring text and ignoring status.

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

* style: fix biome formatting in doApi() signature

Function signature needed multi-line format to match biome expectations.

Agent: code-health
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.6 <noreply@anthropic.com>
2026-03-02 12:47:00 -08:00
A
0e145c2e8a
refactor: Remove dead getState() exports from cloud modules (#2108)
Removed `getState()` from hetzner, gcp, daytona, sprite, and digitalocean
modules. These functions were exported but never called from production code
or tests. The aws module retains its `getState()` which is tested in
custom-flag.test.ts to verify region state mutation.

Also bumps CLI patch version (0.12.2 → 0.12.3) as required per project rules.

Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 10:58:48 -08:00
A
9c7fd0c7da
fix: add 30s fetch timeout to all cloud API client wrappers (#2110)
* fix: add 30s AbortSignal.timeout to all cloud API fetch wrappers

All four cloud provider API client wrapper functions (lightsailRest,
hetznerApi, doFetch, daytonaApi) were missing fetch timeouts, while
every other fetch call in the codebase already used AbortSignal.timeout.
A stalled TCP connection to any cloud provider would cause the CLI to
hang indefinitely with no user feedback or recovery path.

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

* fix: apply biome formatting to fetch timeout changes

---------

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-02 13:55:34 -05:00
A
1c35f648b9
test: Remove conditional always-pass guards in manifest-integrity (#2107)
The script content tests had `if (existsSync(scriptPath))` guards
that silently skipped files if they weren't found on disk. Since
the prior test in the same describe block already asserts that all
implemented entries have script files, these guards were dead code
that could mask failures. Remove them so every sampled script is
unconditionally read and validated.

Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 11:50:12 -05:00
A
e3229ebe65
fix(digitalocean): throw on non-2xx responses to prevent silent destroy failures (#2106)
The destroyServer function only checked for status 204 (success) and
responses containing a `message` field. Any other non-204 response
(e.g., 403 with no message, 500 with HTML body) fell through to the
success path, logging "Droplet destroyed" and returning normally.

This caused `spawn delete` to mark the droplet as deleted in history
while it was still running and incurring charges.

Now all non-204 responses unconditionally throw, matching the pattern
established in Hetzner (#2105) and Daytona (#2102).

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-02 09:22:25 -05:00
A
c61a9c6085
fix(hetzner): throw on non-2xx responses to prevent silent failures (#2105)
hetznerApi() was returning raw response text on non-2xx final attempts
instead of throwing, identical to the bug fixed in daytonaApi() by PR #2102.

Impact: a 5xx when fetching SSH keys caused createServer() to receive null
from parseJsonObj(), fall back to an empty SSH key list, and provision a
server the user cannot SSH into -- with no error or warning.

Fix matches the pattern from lightsailRest() (AWS) and PR #2102 (Daytona):
throw with the HTTP status code after retries exhaust on any !resp.ok response.

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-02 05:36:42 -08:00
A
23acf62e1a
refactor: Remove dead code and stale references (#2104)
Update stale comments referencing the monolithic commands.ts (replaced
by commands/ directory in #2095). The shim at commands.ts still exists
for backward compat but all internal code paths now live under commands/.

- Fix test file comments: point checkEntity to commands/shared.ts,
  cmdInteractive to commands/interactive.ts, cmdRun paths to commands/run.ts,
  cmdUpdate to commands/update.ts, cmdCloudInfo to commands/info.ts,
  cmdListClear to commands/list.ts
- Fix guidance-data.ts: update stale extraction comment and circular-dep
  notes to reference commands/run.ts instead of commands.ts
- Fix CLAUDE.md file structure diagram: show commands/ directory and
  note commands.ts is a compatibility shim
- Fix packages/cli/README.md: update directory structure diagram and
  "Adding a New Command" guide to reflect the per-module layout

Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 08:32:02 -05:00
A
9a88460b1d
fix(daytona): throw on non-2xx responses to prevent silent destroy failures (#2102)
daytonaApi() returned the raw response body on all final attempts regardless
of HTTP status. destroyServer() checked hasApiError() which only matched 4xx
patterns, so persistent 500/502/503 responses were silently treated as
success — users were told "Sandbox destroyed" when billing continued.

Fix: throw on !resp.ok after retries exhaust, consistent with other cloud
modules (aws, gcp). destroyServer() now uses try/catch. testDaytonaToken()
already had try/catch so the hasApiError() check was redundant.

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-02 07:17:30 -05:00
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
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
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