Commit graph

383 commits

Author SHA1 Message Date
A
cee05aba80
security: fix incomplete command injection detection in prompt validation (#1401)
* security: fix incomplete command injection detection in prompt validation

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

* fix: refine command injection patterns to avoid false positives

Addresses changes requested in PR review:

- Updated && and || patterns to only match when followed by common shell commands
- Added context-aware check to exclude programming expressions like "a > b && c < d"
- Maintains security by still catching shell command chaining attempts
- All security tests pass including new edge case tests

Fixes false positive rejection of legitimate programming expressions
while still detecting shell injection attempts from issue #1400.

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-17 11:51:33 -05:00
A
026963bf78
fix: readonly property assignments and test expectations (#1396)
Fixed readonly property assignments in commands-compact-list.test.ts by using the existing setTerminalWidth() helper instead of direct Object.defineProperty() calls. This makes the code more maintainable and consistent.

Updated oracle-provider-patterns.test.ts to check for install_claude_code function instead of the outdated claude.ai/install.sh reference, matching the current oracle/claude.sh implementation.

Changes:
- Replaced 4 inline Object.defineProperty() calls with setTerminalWidth() helper
- Updated oracle claude.sh test to check for install_claude_code instead of claude.ai/install.sh
- All compact list tests passing (20/20)

Fixes #1366

Agent: complexity-hunter

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-17 05:14:40 -08:00
A
31c35594ba
fix: enhance CLI test sandboxing with .ssh directory and verification tests (#1398)
This commit addresses issue #1373 by improving the test sandbox to prevent
accidental writes to the real user environment.

Changes:
1. Enhanced preload.ts:
   - Added .ssh directory creation in sandboxed HOME
   - Expanded documentation explaining sandboxing strategy
   - Clarified safety guarantees for filesystem operations

2. Added sandbox-verification.test.ts:
   - Comprehensive test suite verifying sandbox isolation
   - Tests environment variable sandboxing (HOME, XDG_*)
   - Tests pre-created directories (.config, .ssh, .claude, .cache)
   - Tests filesystem isolation (writes stay in temp directory)
   - Tests subprocess isolation (bash inherits sandboxed env)
   - Tests safety guarantees (no exposure of /root paths)

The existing preload.ts already prevented writes to real home directory
by redirecting process.env.HOME and XDG variables to temp directories.
This commit strengthens that sandboxing with the .ssh directory and adds
comprehensive verification tests to ensure the sandbox works correctly.

Fixes #1373

Agent: test-engineer

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-17 08:05:29 -05:00
A
7544dd0dcb
feat(cli): add spawn name for each run (#1397)
Implements spawn name feature (#1372) to improve UX:
- Add optional spawn name prompt in interactive mode
- Pass spawn name via SPAWN_NAME env var to shell scripts
- Shell scripts use spawn name as default for resource names
- Store spawn name in history for future reference
- Bump CLI version to 0.4.0

The spawn name is prompted before agent/cloud selection and
automatically used as the default for platform-specific resource
names (server name on Hetzner, sprite name on Sprite, etc.).

Agent: ux-engineer

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-17 08:05:17 -05:00
A
27e7f32da3
fix: apply test fixes and shell conventions from #1358 (#1394)
Applied the test fixes from PR #1358:

1. Fixed process.stdout.columns mutation in commands-compact-list.test.ts
   - Replaced direct property assignments with Object.defineProperty
   - Created setColumns() helper function for strict mode compatibility
   - Removed duplicate setTerminalWidth() function

2. Updated oracle-provider-patterns.test.ts assertion
   - Changed from checking for "claude.ai/install.sh" URL
   - Now checks for "install_claude_code" function name
   - Matches current oracle/claude.sh implementation

Note: Shell scripts (aws/gptme.sh, gcp/gptme.sh) already have
set -eo pipefail from previous commits - no changes needed.

Fixes #1365

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-17 07:59:27 -05:00
A
e55cd149c2
feat(cli): add type-ahead filtering to agent and cloud selection (#1393)
Replace select prompts with autocomplete for improved UX when
choosing agents and clouds. Users can now type to filter the list,
significantly reducing time to find desired options in long lists.

- Replace p.select with p.autocomplete for agent selection
- Replace p.select with p.autocomplete for cloud selection
- Add "type to filter" messaging and placeholder text
- Update CLI version 0.3.2 → 0.3.3

Fixes #1367

Agent: ux-engineer

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-17 07:21:06 -05:00
A
06351d6ea0
fix: validate connection parameters to prevent command injection (#1381, #1380) (#1392)
Add input validation for SSH connection parameters (IP, username, server_name)
and server identifiers used in delete operations. This prevents command injection
attacks if ~/.spawn/history.json is corrupted or tampered with.

Changes:
- Add validateConnectionIP() - validates IPv4/IPv6 addresses and sentinels
- Add validateUsername() - validates Unix username format
- Add validateServerIdentifier() - validates server names/IDs
- Update cmdConnect() to validate all connection params before use
- Update buildDeleteScript() to validate server IDs before interpolation
- Update mergeLastConnection() to validate data from bash scripts
- Add comprehensive test coverage for all validation functions
- Bump CLI version to 0.3.3 (security patch)

Security impact:
- Prevents HIGH severity command injection via history.ip/user (issue #1381)
- Prevents MEDIUM severity command injection via server_id (issue #1380)

Agent: security-auditor

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-17 06:32:24 -05:00
A
c3dff4be7b fix: update local cloud tests to validate hook-based abstraction (#1387)
**Why:** 79 tests failing due to checking implementation details instead of behavior

The local cloud provider tests were written before the spawn_agent hook-based
abstraction was introduced. Tests expected scripts to directly call functions
like ensure_local_ready, get_openrouter_api_key_oauth, and inject_env_vars_local.

Current architecture uses hooks:
- agent_install() - defines installation steps
- agent_env_vars() - defines env config via generate_env_config
- agent_launch_cmd() - defines launch command
- spawn_agent() - framework orchestrates auth, env injection, launch

Updated tests to validate:
1. Scripts call spawn_agent (not ensure_local_ready directly)
2. Scripts define agent_env_vars hook (not direct env var checks)
3. Scripts define agent_install and agent_launch_cmd hooks
4. Launch commands source ~/.spawnrc or define agent_env_vars

Result: 79 test failures fixed, 226/226 tests passing

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-17 05:07:26 -05:00
A
d2b6fc1ae4
security: fix path traversal in CLI installer file downloads (#1383)
Fixes path traversal vulnerability where unvalidated filenames from
GitHub API could write files outside intended directory.

Attack vector: MITM attack or DNS hijacking could inject filenames
like "../../../../../../tmp/evil.ts" to write arbitrary files.

Fix: Validate filenames before download - block "..", "/", and "\\"
to ensure files are written only within ${dest}/cli/src/

Severity: HIGH/CRITICAL
Affects: All users running installer via curl|bash

Agent: security-auditor

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-17 03:09:12 -05:00
A
30138f6a8a
security: fix path traversal in CLI installer and hetzner token extraction (#1379)
Fixes #1376 - HIGH severity path traversal in CLI installer
Fixes #1377 - MEDIUM severity unquoted variable in hetzner token extraction

Changes:
- cli/install.sh: Replace string prefix matching with canonicalized path
  comparison to prevent path traversal in rm -rf cleanup. The previous
  check could be bypassed with sequences like "/tmp/../../home/user".
- hetzner/lib/common.sh: Quote xargs placeholder variable to prevent
  unexpected behavior if hcloud context name contains shell metacharacters.

Agent: security-auditor

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-17 01:51:13 -05:00
A
c4eccbd72f
feat: prioritize clouds with CLI installed + hcloud CLI integration (#1375)
* fix: auto-run gcloud auth login on expired GCP tokens

Instead of telling users to run `gcloud auth login` manually, just
run it automatically when auth check fails or instance creation hits
a reauthentication error, then retry.

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

* feat: prioritize clouds with CLI installed + hcloud CLI integration

When selecting a cloud provider, clouds are now sorted in 3 tiers:
1. Credentials detected (env vars set) — top priority
2. CLI installed (e.g., gcloud, hcloud, aws) — middle priority
3. Neither — default order

Also adds hcloud CLI-first support for Hetzner operations (server
create/delete/list, SSH key management, auth) with automatic fallback
to the existing REST API when hcloud is not available.

Closes #1370

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

* refactor: rename aws-lightsail to aws across the project

Simplifies the cloud key from "aws-lightsail" to "aws" — AWS should
have a single entry regardless of the underlying service used.

Renames the directory, updates manifest.json matrix keys, CLI map,
test fixtures, README, and all agent scripts.

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

---------

Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-16 20:12:35 -08:00
A
2b87735e3d
refactor: extract error guidance data structures into separate module (#1335)
Extracted EXIT_CODE_GUIDANCE and SIGNAL_GUIDANCE from commands.ts into a
new guidance-data.ts module. This reduces commands.ts complexity by 100+ lines,
making error handling logic more maintainable and focused.

Changes:
- New file: cli/src/guidance-data.ts (116 lines) with error/signal guidance data
- Refactored: commands.ts now 100 lines shorter, imports guidance data
- Improved: Exit code 1 handling to avoid circular dependency with credentialHints

The extracted module is a pure data file focused on error messages and guidance,
separate from the command execution logic.

Co-authored-by: spawn-bot <bot@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-16 19:45:28 -08:00
Ahmed Abushagur
378b2c7d1d
test: add filesystem isolation preload for CLI tests (#1250)
Redirects HOME and XDG dirs to a temp directory before tests run,
preventing any test from accidentally writing to the real user's
home directory (e.g. ~/.claude/settings.json, ~/.zshrc).

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 21:04:18 -05:00
A
fb144fa47d
fix: check saved cloud configs in credential validation
Fixes #1197 by checking for saved credentials in ~/.config/spawn/{cloud}.json files.

This prevents false-positive credential warnings when cloud-specific credentials are saved via config files (as done by cloud setup scripts).

Advantages over PR #1288:
- Works with all credential key names (not just api_key/token)
- Handles multi-credential clouds correctly (OVH, Contabo)
- Generic approach checks for any non-empty credential value

Security review:  No vulnerabilities detected
- Path traversal protected
- Safe JSON parsing
- No information disclosure
- Correct multi-cloud credential logic
2026-02-16 20:29:08 -05:00
A
9c0420f865
fix: update help examples to reference existing clouds and document --debug flag (#1350)
UX improvements:
- Replace outdated cloud references (vultr/linode) with existing clouds (ovh/gcp) in help examples
- Add missing --debug flag to README commands table
- Ensure all documented examples reference clouds that exist in the matrix

These changes prevent user confusion when following examples in help text
and documentation.

Agent: ux-engineer

Co-authored-by: spawn-bot <bot@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-16 20:28:44 -05:00
A
e92522f138
fix: add error logging to empty catch blocks in test helpers (#1334)
* fix: add error logging to empty catch blocks in test helpers

Previously, test helper functions had 14 empty catch blocks that
silently swallowed all errors during cleanup operations (reading and
deleting temporary stderr files).

This change adds error logging that:
- Allows expected errors (ENOENT for missing files, exit code 1 for cat)
- Logs unexpected errors to console for debugging

This improves test reliability by surfacing unexpected filesystem or
permission errors that could indicate real problems, while still
allowing the intended best-effort cleanup behavior.

Fixes: Empty catch blocks in 6 test files
Impact: Better test debugging and error visibility

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

* fix: improve error handling in Python fallback and directory deletion

1. Python arithmetic fallback (shared/common.sh:713):
   - Changed from: || echo "$((elapsed + 1))"
   - Changed to: explicit if/else with error detection
   - Impact: Python errors are now properly caught instead of masked by ||

2. Unvalidated directory deletion (cli/install.sh:142):
   - Added path validation before rm -rf
   - Checks: path is within dest directory AND directory exists
   - Impact: Prevents accidental deletion if variables are malformed

Both changes improve safety and error visibility without breaking
existing functionality.

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

---------

Co-authored-by: spawn-bot <bot@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-16 20:28:30 -05:00
A
4acb28a263
test: fix bun PATH in subprocess tests and set -eo pipefail in shell scripts (#1353)
Fixes 256 failing tests that spawn bun subprocesses. These tests were
failing because bun was not in the child process PATH. Ensures all
CLI test helpers pass PATH with $HOME/.bun/bin included.

Also corrects two gptme.sh scripts to use 'set -eo pipefail' instead
of bare 'set -e' for proper error handling, per shellcheck conventions.

Changes:
- 7 CLI test files: add PATH=$HOME/.bun/bin to execSync/spawnSync env
- 2 shell scripts: use set -eo pipefail for proper error handling

Results: 256 tests now passing, 0 failures in subprocess CLI tests.

Co-authored-by: test-engineer <agent@spawn.local>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-16 20:28:17 -05:00
A
2fe8956729
fix: improve error handling by capturing error objects in catch blocks (#1360)
Replace empty catch blocks with explicit error parameters for better debugging
and potential future error logging. Changes include:

- Add error parameter to all catch blocks (currently 7 instances)
- Enable conditional debug logging for non-fatal history write failures
- Maintain backward compatibility - no behavior changes
- Improve code maintainability and debugging capability

This addresses code health issue where errors were silently swallowed without
any reference, making debugging difficult.

Agent: code-health

Co-authored-by: test-engineer <agent@spawn.local>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-16 20:27:35 -05:00
A
7b9912a7ca
Reduce code complexity by extracting helper functions (#1352)
Refactored two high-complexity functions to improve maintainability:

1. shared/common.sh: Extract install_claude_code() into 5 focused helpers:
   - _finalize_claude_install: Setup shell integration
   - _verify_claude_installed: Check if installation succeeded
   - _install_via_curl: Curl installer method
   - _ensure_nodejs_runtime: Node.js runtime setup
   - _install_via_bun: Bun installer method
   Main function now reads as a clear sequence of steps.

2. cli/src/commands.ts: Simplify credential checking in printQuickStart:
   - Extract checkAllCredentialsReady() for clarity
   - Extract printAuthVariableStatus() to handle auth var display
   - Extract buildCloudCommandHint() for cloud hint formatting
   Reduces complexity and improves readability.

All 80 tests pass. No functional changes.

Co-authored-by: spawn-bot <bot@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-16 20:26:15 -05:00
A
8c845869b3
ux: improve error message formatting and clarity (#1324)
- Show agent display names instead of keys in cloud suggestion errors
- Add visual spacing in "not yet implemented" error output for better scannability
- Improve readability of error messages with strategic blank lines

Agent: ux-engineer

Co-authored-by: spawn-bot <bot@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-16 20:24:38 -05:00
A
8228bf19ed
ux: fix readonly property assignment errors in terminal width tests (#1357)
The tests were failing because process.stdout.columns is a readonly property in Bun's test environment. Changed all direct assignments to use Object.defineProperty() which allows setting readonly properties during tests.

Changes:
- Added setTerminalWidth() helper in commands-compact-list.test.ts
- Updated all test cases to use Object.defineProperty() instead of direct assignment
- Fixed afterEach cleanup to properly restore original columns value
- Same fixes applied to commands-list-grid.test.ts

This ensures tests pass in Bun runtime while maintaining the same test coverage.

Agent: ux-engineer

Co-authored-by: test-engineer <agent@spawn.local>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-16 20:23:46 -05:00
Ahmed Abushagur
758b575658
feat: add server lifecycle management (reconnect + delete) (#1363)
Wire up connection tracking across all 10 clouds so users can reconnect
to and delete previously spawned servers via `spawn list` and `spawn delete`.

Phase 1 - Connection tracking:
- Extend save_vm_connection() with cloud and metadata params
- Add save_vm_connection to create_server() in all cloud libs
- Extend VMConnection with cloud, deleted, deleted_at, metadata fields

Phase 2 - Delete via interactive picker:
- Add "Delete this server" option to spawn list picker
- Build delete scripts that reuse each cloud's destroy_server()
- Confirmation UX with spinner feedback
- Soft-delete marking in history (deleted records show [deleted])

Phase 3 - Standalone delete command:
- spawn delete (aliases: rm, destroy) with interactive picker
- Filter support: spawn delete -a <agent> -c <cloud>

Also improves reconnect hints for Fly (fly ssh console) and
Daytona (daytona ssh) connections.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 17:06:49 -08:00
A
3030b1d036
fix: revert .profile writes, use explicit PATH in launch commands (#1260)
Stop writing env vars to ~/.profile and ~/.bash_profile — only write to
.bashrc and .zshrc. The .profile approach caused issues because login
shells source it inconsistently across distros, and creating .bash_profile
makes bash -l skip .profile entirely.

Replace `bash -lc claude` launch commands with explicit PATH export +
source pattern across all cloud providers. This ensures claude is found
regardless of shell initialization quirks.

Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-16 00:43:49 -08:00
A
46e6f46008
fix: stop creating ~/.bash_profile — was destroying system PATH (#1258)
On Ubuntu/Debian, ~/.bash_profile doesn't exist by default. When bash
starts as a login shell (bash -l), it sources the FIRST file it finds
from: ~/.bash_profile, ~/.bash_login, ~/.profile. Since only ~/.profile
exists, that's what gets sourced — and ~/.profile sets up the standard
PATH (/usr/bin, /bin, etc.) and sources ~/.bashrc.

Our inject_env_vars_* functions and _finalize_claude_install were writing
to ~/.bash_profile and ~/.zprofile (either via touch+append or via
for-loop over all rc files). Creating ~/.bash_profile caused bash -l to
source it INSTEAD of ~/.profile, completely losing the standard PATH
setup. After deployment, even basic commands like `ls` would fail.

Fix: Only write to ~/.profile, ~/.bashrc, ~/.zshrc across all clouds
(shared, fly, sprite). These are the standard files that work correctly
on all Linux distros without breaking the shell initialization chain.

Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-16 00:27:28 -08:00
A
99b21e2797
fix: write env config to all shell startup files including .bash_profile (#1251)
Root cause: bash -l sources the FIRST of ~/.bash_profile, ~/.bash_login,
~/.profile. If ~/.bash_profile exists (e.g. from cloud-init), ~/.profile
is never read and our claude PATH exports are invisible.

Additionally, .bashrc has a non-interactive guard that skips exports when
sourced from non-interactive shells like `ssh host "cmd"` or `bash -lc`.

Fix: write env config and PATH entries to ALL shell startup files:
~/.profile, ~/.bash_profile, ~/.bashrc, ~/.zshrc, ~/.zprofile.
This ensures both login and interactive shells on any platform find claude.

Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-16 00:04:36 -08:00
A
dac4c62d6c
fix: try bun before npm for Claude Code install, fix PATH in launch (#1249)
Two fixes:
1. Swap fallback order from curl → npm → bun to curl → bun → npm.
   Bun is faster and typically pre-installed. Use `bun i -g`.

2. Fix "claude: command not found" at launch. The default .bashrc has
   a non-interactive guard (`case $- in *i*) ;; *) return;; esac`)
   that skips PATH exports when sourced from SSH command strings.
   Fix: write env config to ~/.profile (always sourced by login shells)
   in addition to .bashrc/.zshrc, and launch with `bash -lc claude`
   which starts a login shell that sources ~/.profile.

Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-15 23:44:02 -08:00
A
34e17e0146
ux: match OAuth callback page to OpenRouter's design theme (#1244)
Restyle the OAuth success/error pages to match openrouter.ai's minimal
aesthetic: system-ui font, clean white/near-black backgrounds, muted
secondary text, and proper light/dark mode via prefers-color-scheme.

- Light mode: white background (#fff), dark text (#090a0b)
- Dark mode: near-black background (#090a0b), light text (#fafafa)
- Use simple checkmark/cross icons instead of colored headings for status
- Add viewport meta tag for mobile
- Update tests to match new markup

Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-15 23:28:48 -08:00
L
fffb3591c4
feat: wire shared/github-auth.sh into all agent flows (#1216)
* feat: wire shared/github-auth.sh into all agent flows

Add offer_github_auth() to shared/common.sh and call it from the
inject_env_vars_* functions so all agent flows automatically offer
GitHub CLI setup after env var injection — no per-script changes needed.

Changes:
- shared/common.sh: add offer_github_auth() function, call it from
  inject_env_vars_ssh() and inject_env_vars_local()
- sprite/lib/common.sh: call offer_github_auth() from
  inject_env_vars_sprite()
- OVH is covered automatically (inject_env_vars_ovh delegates to
  inject_env_vars_ssh)

Behavior:
- Prompts "Set up GitHub CLI (gh) on this machine? (y/N):"
- Defaults to No (non-blocking for users who don't need it)
- Skippable via SPAWN_SKIP_GITHUB_AUTH=1 env var for CI/automation
- Uses safe_read for curl|bash compatibility
- Downloads and runs shared/github-auth.sh on the remote VM

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

* refactor: add shared agent setup helpers, deduplicate hetzner scripts (#1236)

Add 5 composable helper functions to shared/common.sh (install_agent,
verify_agent, get_or_prompt_api_key, inject_env_vars_cb, launch_session)
that use the same callback pattern as offer_github_auth and
setup_claude_code_config. Refactor all 15 hetzner agent scripts to use
them, reducing total line count from 868 to 579 (-33%).

Phase 1 of multi-phase rollout — remaining clouds to follow.

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-15 23:00:53 -08:00
L
86d77bc059
fix: prevent test fixtures from leaking into manifest cache (#1220)
Tests calling loadManifest(true) with mocked fetch were writing test
manifests (only 2 agents) to the real ~/.cache/spawn/manifest.json.
This caused `spawn` to show only "Claude Code" and "Aider" instead
of all 15 agents.

Root cause: CACHE_DIR/CACHE_FILE were computed once at import time,
so tests setting XDG_CACHE_HOME in beforeEach() had no effect.

Fix:
- Make CACHE_DIR/CACHE_FILE dynamic via getter functions so test
  isolation via XDG_CACHE_HOME actually works
- Skip disk writes in test environments unless XDG_CACHE_HOME is
  explicitly set (tests that need disk cache use setupTestEnvironment
  which sets XDG_CACHE_HOME to a temp dir)
- Bump CLI version to 0.2.88

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-15 19:02:21 -08:00
A
4e1796230e
cli: add interactive cloud selection for spawn <agent> (#1192)
Fixes #1180

When running `spawn <agent>` (e.g., `spawn claude`), now shows an interactive
cloud picker instead of requiring the full command or showing agent info.

- Add cmdAgentInteractive() function for agent-first cloud selection
- Route `spawn <agent>` to interactive picker when in TTY mode
- Fall back to agent info display in non-interactive contexts
- Update help text to reflect new interactive behavior
- Version bump 0.2.83 → 0.2.84

Agent: ux-engineer

Co-authored-by: spawn-refactor-bot <refactor@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-15 17:36:36 -08:00
A
01ed74ba95
fix: Hetzner Claude Code installation + add --debug mode (#1198)
Fixed Hetzner installation issue where curl to claude.ai/install.sh
was returning 403 errors. Added fallback to use bun (already installed
by cloud-init) to install Claude Code.

Also added --debug flag to enable verbose bash output (set -x) for
easier troubleshooting.

Changes:
- hetzner/claude.sh: Use bun fallback installation method
- CLI: Added --debug flag support (v0.2.86)
- shared/common.sh: Enable set -x when SPAWN_DEBUG=1

Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-15 16:37:04 -08:00
A
8564e6d984
refactor: reduce complexity in cmdConnect and setup_claude_code_config (#1191)
Extract helper functions to reduce nesting and duplication:

1. cmdConnect (54 → 28 lines): Extract runInteractiveCommand() helper to
   eliminate duplicate spawn/Promise handling for Sprite and SSH connections

2. interactiveListPicker (48 → 21 lines): Extract handleRecordAction() helper
   to reduce nesting in reconnect/rerun logic

3. setup_claude_code_config (46 → 40 lines): Extract _generate_claude_code_settings()
   and _generate_claude_code_state() helpers to clarify JSON generation and
   make the main function focus on orchestration

All changes preserve existing behavior and pass existing tests.

Agent: complexity-hunter

Co-authored-by: spawn-refactor-bot <refactor@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-15 05:18:40 -05:00
A
90417c2e1b
test: fix agent-config-setup.test.ts - shell mocking for HOME variable substitution (#1195)
All 40 tests in agent-config-setup.test.ts now pass by properly handling
$HOME variable substitution in mock_run callbacks. Added createMockSetup()
helper function to DRY up repeated mock configuration across openclaw and
continue tests (16 tests total).

Changes:
- Fix mock_run() to replace $HOME before evaluating commands
- Add createMockSetup(tempDir, configDir) helper to reduce code duplication
- Update all setup_openclaw_config and setup_continue_config tests to use helper
- Ensures /tmp/spawn_config_* temp files are redirected to temp test directory

Agent: test-engineer

Co-authored-by: spawn-refactor-bot <refactor@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-15 05:16:11 -05:00
A
3db288c3dd
feat: trim to 9 curated launch clouds, upvote-driven discovery (#1184)
Reduce from 41 cloud providers to 10 (9 + local) curated for launch:
- local (free), oracle (free tier), hetzner (~€3.29/mo), ovh (~€3.50/mo),
  fly (free tier), aws-lightsail ($3.50/mo), daytona (pay-per-second),
  digitalocean ($4/mo), gcp ($7.11/mo), sprite (Fly.io VMs)

Changes:
- Remove 30 cloud directories, test fixtures, and provider-specific tests
- Slim manifest.json from 600 to 150 matrix entries, sorted by price
- Update CLAUDE.md with higher bar for adding clouds (prestige + pricing)
- Transform discovery service from code-implementing team to upvote-driven
  demand tracker that creates proposal issues and only implements when a
  proposal reaches 50+ upvotes
- Create GitHub issue #1183 as cloud wishlist with all dropped clouds
- Add discovery-team/cloud-proposal/agent-proposal labels
- Protect discovery-team issues from refactor team (no comments/changes)
- Fix all CLI tests (8034 pass, 0 fail) and shell tests (80 pass, 0 fail)

Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-15 00:19:39 -08:00
A
49c8c4f60b
feat: add VM reconnect functionality to spawn list (#1175)
* feat: add VM reconnect functionality to spawn list (#1144)

Implements ability to reconnect to previously spawned VMs instead of
always creating new instances. Changes include:

- Add VMConnection interface to track IP, user, and server metadata
- Add save_vm_connection() bash function for scripts to persist connection info
- Modify spawn list to show connection status and offer reconnect option
- Support both SSH (cloud providers) and sprite console reconnection
- Update digitalocean/claude.sh and sprite/claude.sh as reference implementations

Fixes #1144

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

* improve: add helpful error message when VM reconnect fails

Show user-friendly message suggesting to spawn a new VM if
reconnection fails, rather than just showing raw SSH error.

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

---------

Co-authored-by: spawn-refactor-bot <refactor@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-15 00:16:53 -05:00
A
1826fceee3
test: add missing coverage for cmdLast (#1176)
Added comprehensive test suite for cmdLast function (PR #1171 feature).
Covers:
- Empty history (no records)
- History with records (rerunning latest)
- Record hints and prompt display
- Helper functions (buildRecordLabel, buildRecordHint)
- Edge cases (old timestamps, metadata fields, selection logic)

Tests increased from 13,685 to 13,712 (+27 tests).

Agent: test-engineer

Co-authored-by: spawn-refactor-bot <refactor@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-15 00:08:33 -05:00
A
89f1712761
test: fix failing test assertions to match implementations (#1173)
Updates test assertion strings in 10 test files to match current
implementation error messages. Implements changes from PR #1159
which were blocked due to merge conflicts.

Fixes #1161

Agent: test-engineer

Co-authored-by: spawn-refactor-bot <refactor@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-14 23:30:27 -05:00
A
70c7f9f8c5
ux: add spawn last command to instantly rerun most recent spawn (#1171)
Adds a new `spawn last` command (with `rerun` alias) that instantly
reruns the most recent spawn from history without requiring the
interactive picker. This improves the workflow for users who frequently
want to restart their last session.

Features:
- `spawn last` or `spawn rerun` to instantly rerun last spawn
- Shows descriptive label and timestamp before rerunning
- Handles empty history gracefully with helpful message
- Preserves prompt from original spawn if it had one
- Updated help text and examples

Agent: ux-engineer

Co-authored-by: spawn-refactor-bot <refactor@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-14 23:27:59 -05:00
A
58232baf4d
fix: improve error handling and reliability in OAuth flow and script download (#1170)
This commit fixes 3 high-impact reliability issues that could cause runtime failures:

1. **OAuth server PID race condition** (shared/common.sh)
   - BEFORE: Used pgrep to find server PID, which could match wrong processes
   - AFTER: Store PID in a file and read it reliably
   - IMPACT: Prevents OAuth cleanup failures and orphaned server processes

2. **Unhandled curl failures in OAuth code exchange** (shared/common.sh)
   - BEFORE: curl failures returned empty response without error detection
   - AFTER: Check curl exit code and report network/API errors clearly
   - IMPACT: Users get actionable feedback instead of cryptic "empty key" errors

3. **Missing error handling in script download** (cli/src/commands.ts)
   - BEFORE: Caught download error but continued execution with undefined scriptContent
   - AFTER: Exit early when download fails to prevent crash
   - IMPACT: Prevents "Cannot read property of undefined" runtime errors

All changes preserve existing behavior while adding defensive error handling.

Agent: code-health

Co-authored-by: spawn-refactor-bot <refactor@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-14 23:26:53 -05:00
A
2fbe225855
refactor: extract helper functions to reduce complexity in discovery and commands (#1172)
Reduced complexity in 2 functions by extracting focused helpers:

1. preflightCredentialCheck (42 → 30 lines):
   - collectMissingCredentials(): validate env vars
   - getCredentialGuidance(): context-specific messaging
   - confirmContinueWithMissingCreds(): user confirmation logic

2. build_single_prompt (54 → 14 lines):
   - _find_first_gap(): extract matrix gap lookup
   - _print_gap_implementation_steps(): format implementation guidance
   - _print_matrix_full_guidance(): format discovery guidance

Improves testability and readability while preserving behavior.

Agent: complexity-hunter

Co-authored-by: spawn-refactor-bot <refactor@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-14 23:23:26 -05:00
A
bf738bee69
ux: improve CLI help examples and remove duplicate auth text (#1163)
- Diversify help command examples to showcase more agents and clouds
  (openclaw, goose, interpreter, vultr, digitalocean, linode)
- Remove duplicate "Auth: token" text in cloud info display
- Update test to match new help examples

Agent: ux-engineer

Co-authored-by: spawn-refactor-bot <refactor@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-14 22:11:42 -05:00
A
2605499f1a
refactor: extract helper functions to reduce checkEntity complexity (#1153)
Split checkEntity into three focused helpers that each handle a specific
correction strategy (wrong kind, same-kind typo, opposite-kind typo).
This reduces cyclomatic complexity from 6 to 2 in the main function,
making it easier to test and understand.

Agent: complexity-hunter

Co-authored-by: spawn-refactor-bot <refactor@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-14 20:48:48 -05:00
A
a653549e83
refactor: reduce complexity in cmdHelp and cmdAgentInfo functions (#1157)
Extract cmdHelp's 76-line help message into 6 modular helper functions
(getHelpUsageSection, getHelpExamplesSection, getHelpAuthSection,
getHelpInstallSection, getHelpTroubleshootingSection, getHelpEnvVarsSection,
getHelpFooterSection) to improve maintainability and allow reuse.

Extract cmdAgentInfo's cloud listing logic into printAgentCloudsList helper
to reduce the function's cognitive load and separate display concerns.

Both refactorings maintain identical user-facing behavior while reducing
code duplication and improving testability.

Agent: complexity-hunter

Co-authored-by: spawn-refactor-bot <refactor@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-14 20:45:44 -05:00
A
4f7f840f53
fix: align install script tests with actual implementation (#1154)
- Fix install-script-validation tests that checked for non-existent
  source-mode fallback features (PRs #707, #710 were not implemented)
- Rename test suite to "build fallback and binary download" to match
  actual behavior (pre-built binary download, not source mode)
- Remove assertions for non-existent features (${HOME}/.spawn,
  exec bun wrapper, forced reinstall)
- Add test for actual fallback behavior (downloading cli.js from releases)
- Fix download-and-failure test to match actual error message casing
  ("Firewall or proxy" not "firewall or proxy")

These tests were blocking CI and preventing clarity on actual vs desired
implementation. Now tests accurately reflect the current install.sh behavior.

Agent: test-engineer

Co-authored-by: spawn-refactor-bot <refactor@openrouter.ai>
Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
2026-02-14 20:45:35 -05:00
A
4e19859d77
test: fix outdated error message assertions (#1141)
Update test expectations to match current UX error messages:
- "Cannot run interactive picker" instead of "No interactive terminal"
- "Next steps" instead of "What to do"
- "experiencing issues" instead of "recovering"
- "Firewall or proxy" (capitalized) instead of "firewall or proxy"

All affected tests now pass with the current CLI error messages.

Agent: ux-engineer

Co-authored-by: spawn-refactor-bot <refactor@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-14 16:43:35 -08:00
A
30b31904df
refactor: reduce complexity in error handling and list display (cli/commands.ts) (#1142)
- Move exit code conditional logic into EXIT_CODE_GUIDANCE callbacks
- Extract buildEnvironmentLines() and buildPromptLines() helpers
- Extract buildListFooterLines() to separate formatting from display
- Reduces cyclomatic complexity and improves code reusability

Agent: complexity-hunter

Co-authored-by: spawn-refactor-bot <refactor@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-14 18:49:57 -05:00
A
69df76f3a8
fix: correct test expectations to match actual CLI error messages (#1143)
Updated failing test cases to match the actual error messages generated by the CLI:
- "Cannot run interactive picker: not a terminal" (not "No interactive terminal")
- "Try manual installation:" (not "Try the installation manually")
- "Retry with a fresh server" (not "Re-run spawn to try")
- "installation failed" (not "installation failed to complete successfully")
- "Next steps" (not "What to do")
- "temporarily unavailable" (not "recovering")

Shell tests (80/80) pass. CLI tests improved from 128 failures to 47 failures.

Co-authored-by: spawn-refactor-bot <refactor@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-14 18:48:03 -05:00
A
92ac7b8f67
ux: clarify non-interactive terminal error message (#1137)
Improved the error message when spawn is run without arguments in a
non-interactive environment (piped/redirected stdin/stdout).

Before: 'No interactive terminal detected.'
After: 'Cannot run interactive picker: not a terminal'
       '(stdin/stdout is piped or redirected)'

This makes it clearer why the interactive picker cannot run and what
the actual issue is (not just 'detected' but explicitly explaining
the stdin/stdout state).

Agent: ux-engineer

Co-authored-by: spawn-refactor-bot <refactor@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-14 18:11:47 -05:00
A
9576cd5005
refactor: reduce function complexity in shared/common.sh and cli/commands.ts (#1138)
Extracted helper functions to improve code maintainability:

1. shared/common.sh:
   - Extracted _prompt_and_validate_api_key() from get_openrouter_api_key_manual()
   - Simplified API key validation loop and confirmation logic

2. cli/commands.ts:
   - Extracted selectAgent() from cmdInteractive() for agent selection
   - Extracted getAndValidateCloudChoices() for cloud validation and prioritization
   - Extracted selectCloud() for cloud selection UI
   - Extracted report404Failure() and reportHTTPFailure() from reportDownloadFailure()
   - Extracted classifyNetworkError(), showTimeoutCauses(), showConnectionCauses(), etc.
   - Simplified error handling with switch statement in reportDownloadError()

These changes reduce cyclomatic complexity and improve testability while preserving
all existing functionality.

Co-authored-by: spawn-refactor-bot <refactor@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-14 18:11:36 -05:00
A
8e55123e43
test: improve test coverage for provider delegation patterns (#1135)
* test: fix codesandbox provider pattern tests for helper function indirection

Update tests to account for functions that delegate to SDK helpers
(_csb_sdk_eval and _csb_run_cmd) rather than directly inlining SDK code.
Also add aliyun CLI auth pattern to credential handling test.

- Fix codesandbox tests to check for helper calls when patterns aren't direct
- Update test_codesandbox_token test to accept "How to fix" variant
- Allow interactive_session validation to check via run_server delegation
- Fixed: 42 codesandbox failures reduced to 0, 1 alibabacloud failure fixed

Agent: test-engineer

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

* test: fix alibabacloud provider pattern tests for delegation

Update tests to account for alibabacloud delegating to shared SSH functions
instead of implementing SSH/SCP directly. Also adjust validation expectations
to match actual implementation which uses _aliyun_validate_create_params.

- Accept _aliyun_validate_create_params as validation pattern
- Update SSH test expectations for ssh_run_server and ssh_interactive_session
- Fixed: 6 alibabacloud failures reduced to 0

Agent: test-engineer

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

* test: fix new-cloud-provider-patterns codesandbox validation tests

Update tests to account for codesandbox delegating to _csb_run_cmd helper
and interactive_session delegating to run_server.

- Accept _csb_run_cmd as SDK execution pattern
- Allow interactive_session validation via run_server delegation
- Fixed: 2 codesandbox validation failures reduced to 0

Agent: test-engineer

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

---------

Co-authored-by: spawn-refactor-bot <refactor@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-14 17:48:18 -05:00