Commit graph

136 commits

Author SHA1 Message Date
A
21f7e7683f
refactor: deduplicate remaining 6 clouds into shared agent-setup pattern (#1704)
Convert gcp, daytona, digitalocean, hetzner, sprite, and local clouds
to use shared/agent-setup.ts and shared/orchestrate.ts, matching the
pattern established by AWS and Fly. Each cloud's agents.ts is now a
~26-line thin wrapper; each main.ts uses runOrchestration().

- Delete gcp/lib/common.sh (406 lines of dead bash code)
- Delete cli/src/fly/oauth.ts and cli/src/fly/ui.ts re-export wrappers
- Fix all fly/oauth and fly/ui imports to use shared/ directly
- Update test thresholds for reduced bash cloud count
- Bump CLI version to 0.6.3

Net reduction: ~2,850 lines removed.

Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-22 09:20:34 -08:00
A
eac5713ef0
refactor: deduplicate AWS/Fly agent setup into shared modules (#1700)
Extract ~800 lines of duplicated agent helpers and orchestration logic
from aws/agents.ts and fly/agents.ts into shared modules:

- shared/agent-setup.ts: CloudRunner interface, installAgent,
  uploadConfigFile, installClaudeCode, setupClaudeCodeConfig,
  GitHub auth, config helpers, createAgents(), resolveAgent()
- shared/orchestrate.ts: CloudOrchestrator interface + 12-step
  runOrchestration() pipeline
- shared/agents.ts: AgentConfig type + generateEnvConfig (single source)

Each cloud becomes a thin wrapper (~25-60 lines) that constructs a
CloudRunner/CloudOrchestrator from its provider-specific functions.

Also fixes pre-existing test breakage (aws.test.ts imported renamed
exports LIGHTSAIL_BUNDLES/BundleTier → BUNDLES/Bundle) and removes
dead aws/lib/common.sh reference from test/e2e.sh.

Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-22 08:40:21 -08:00
A
55df28137d
feat: convert gcp/ cloud provider from Bash to TypeScript (#1694)
Security review approved. All issues resolved.
2026-02-22 08:51:50 -05:00
A
850327c29d
feat: convert aws/ cloud provider from Bash to TypeScript (#1693)
Migrates AWS Lightsail from 609-line bash (aws/lib/common.sh) to TypeScript,
following the established Fly.io/local provider patterns. Type safety eliminates
SigV4 signing bugs, @clack/prompts provides interactive bundle/region pickers,
and error handling is explicit.

- cli/src/aws/aws.ts — Core: AWS CLI wrapper, SigV4 REST API, auth, provisioning, SSH
- cli/src/aws/agents.ts — Agent configs and install helpers
- cli/src/aws/main.ts — Orchestrator
- aws/*.sh — Converted to thin bun shims with bash fallback (curl|bash compatible)
- cli/package.json — Version bump to 0.6.0

Fixes #1675

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-22 07:50:54 -05:00
A
435d9125d5
feat: convert local/ cloud provider from Bash to TypeScript (#1688)
Creates cli/src/local/{main,local,agents}.ts following the Fly.io
pattern. All 6 agent .sh files replaced with thin bun shims.
Extracts shared oauth.ts and ui.ts to cli/src/shared/ for reuse
across cloud providers. Updates fly/ to re-export from shared.

Fixes #1681

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-22 10:49:40 +00:00
A
0f4df7be71
feat: pre-built Docker image for OpenClaw on Fly.io (#1686)
Eliminates the slow waitForCloudInit() + bun install phase by booting
a pre-built image with Node.js, bun, and openclaw already installed.
The image is rebuilt daily via GitHub Actions to pick up new releases.

Other agents are unaffected — they still use ubuntu:24.04 + cloud-init.

Co-authored-by: spawn-bot <spawn-bot@openrouter.ai>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 02:50:46 -05:00
A
4cec25c6b7
fix: pass spawn name through cmdRun and headless flows (#1674)
cmdRun (spawn <agent> <cloud>) was not collecting or passing the spawn
name, so SPAWN_NAME was never set in the script environment and the
history record lacked a name. cmdRunHeadless had the same gap.

- Add promptSpawnName() call to cmdRun and pass result to execScript
- Wire spawnName through HeadlessOptions to runBashHeadless
- Add --name CLI flag to set SPAWN_NAME from the command line
- Skip interactive name prompt when SPAWN_NAME is already set
- Bump CLI to 0.5.33

Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-21 21:52:52 -08:00
A
461d945212
chore: bump CLI version to 0.5.32 (#1666)
Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-21 20:30:14 -08:00
A
760fa22dea
fix: bump fly default VM to 4GB, add 10GB volume, hide keepalive dots (#1654)
- Default VM memory: 1024MB → 4096MB (all agents except ZeroClaw
  which stays at 1024MB). Prevents OOM kills during native installs.
- Attach a 10GB persistent volume at /data with a /root/work symlink
  so agents have enough storage to clone repos and work.
  Configurable via FLY_VOLUME_SIZE env var.
- Keepalive: changed dots to spaces so they're invisible in terminal.

Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-21 17:56:02 -08:00
A
8650ad15d8
feat: interactive VM size and volume prompts for Fly.io (#1655)
Users can now choose VM size (1x/2x/4x shared CPU tiers) and opt into
persistent volumes during provisioning instead of getting hardcoded defaults.
FLY_VM_MEMORY env var still works for CI/headless mode.

Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-21 17:19:09 -08:00
A
b43d3f1b70
fix: combine gateway start + port wait into single SSH session (#1642)
The old flow opened up to 60+ separate fly ssh console sessions to
poll port 18789 after starting the gateway daemon. Each session opens
a new WireGuard tunnel which is slow and flaky.

Now: one SSH session starts the daemon, then polls the port in-band
with a simple for loop. Output from the loop also serves as a
keepalive for flyctl.

Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-21 16:16:08 -08:00
A
09d9f597ac
fix: use openclaw curl installer to prevent fly ssh hang (#1640)
bun install -g openclaw spawns child processes that keep stdout/stderr
FDs open, preventing fly ssh console from detecting EOF. Replace with
the official curl installer (--no-onboard) which handles Node detection
and cleanup without leaving orphan processes on the pipe.

See: https://docs.openclaw.ai/install

Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-21 15:58:05 -08:00
A
42f2b66b55
fix: keep stdin pipe open during fly ssh to prevent session teardown (#1638)
flyctl tears down the WireGuard transport when stdin closes ("session
forcibly closed; the remote process may still be running"). This
killed long-running commands like `bun install -g openclaw`.

Instead of calling stdin.end() immediately, keep the pipe open for
the duration of the command and close it after the process exits.
The pipe still prevents interactive prompts from hanging (no data
flows through it), but flyctl no longer interprets the closed fd
as a signal to kill the session.

Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-21 15:44:14 -08:00
A
b4be9b9d2f
fix: use pipe+close for fly ssh stdin instead of ignore (#1632)
fly ssh console with stdin as /dev/null ("ignore") can cause the
connection to hang — flyctl doesn't get a clean EOF signal to know
when to close the transport. Switch to "pipe" and immediately call
stdin.end() so flyctl receives a proper EOF.

Applied to runServer, runServerCapture, and uploadFile.

Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-21 15:12:44 -08:00
A
246351874e
fix: skip unnecessary apt recommends (python3) during fly VM setup (#1629)
git on Ubuntu 24.04 pulls in python3 via recommended packages. Use
--no-install-recommends to install only direct dependencies. Also
added ca-certificates explicitly since it's needed for HTTPS but
won't be auto-pulled without recommends.

Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-21 15:02:55 -08:00
A
cbd8c87a6d
fix: prevent terminal hang during fly agent install + fatal preLaunch (#1628)
Two fixes:

1. runServer() was inheriting stdin, so commands like `claude install`
   that try to read input would hang the terminal indefinitely. Changed
   stdin to "ignore" (/dev/null) for non-interactive remote commands.

2. preLaunch failures (e.g. OpenClaw gateway) were silently swallowed,
   dropping users into a broken TUI with no gateway. Now preLaunch
   errors propagate — users get a clear error instead of a mystery hang.

Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-21 14:53:07 -08:00
A
cbecb9cbea
fix: suppress interactive dpkg prompts during fly VM setup (#1626)
tzdata (pulled in as a Node.js dependency) tries to run
dpkg-reconfigure interactively, which fails on headless Fly
machines. Set DEBIAN_FRONTEND=noninteractive so apt silently
accepts defaults.

Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-21 14:35:27 -08:00
A
0f59f0e844
fix: fly machine wait timeout exceeds API max of 60s (#1619)
The Fly Machines API enforces a [1s, 1m0s] range on
WaitMachineRequest.Timeout. We were passing 90s, which caused an
invalid_argument error and prevented machines from starting.

Lower the default to 60s (the API maximum) and retry up to 3 times
so slow-starting machines still have a full 3-minute window.

Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-21 14:00:13 -08:00
A
d69c4f0f02
fix: reject non-2xx responses in Fly.io token validation (#1614)
testFlyToken() fallback to /v1/user accepted 404 plain text responses
because hasError() only checks for JSON "error"/"errors" keys. Adding
resp.ok check ensures non-2xx responses are correctly rejected.

Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-21 13:47:40 -08:00
A
eff99caefe
fix: apply default spawn name when user presses Enter without typing (#1605)
promptSpawnName() used `placeholder` (visual hint only) without `defaultValue`,
so pressing Enter returned an empty string instead of applying the placeholder.
Now generates a unique default like `spawn-a3f2` with a random suffix to avoid
Fly.io global name collisions.

Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-21 11:46:59 -08:00
A
3570caa840
fix: accept localhost and hostnames in validateConnectionIP (#1531)
validateConnectionIP rejected "localhost" (written by local cloud) and
hostnames like "ssh.app.daytona.io" (written by Daytona), causing
mergeLastConnection to silently discard connection data. This broke
spawn list and spawn delete for these providers.

- Add "localhost" to CONNECTION_SENTINELS
- Add HOSTNAME_PATTERN for valid multi-label DNS hostnames
- Update tests: localhost now valid, add hostname acceptance/rejection tests

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-20 11:49:23 -05:00
L
eea43adcad
fix: re-exec with new binary after auto-update for all invocations (#1526)
Two bugs in reExecWithArgs():

1. args.length === 0 early exit:
   Running bare `spawn` (interactive picker) after an auto-update would
   print "Run your spawn command again" and exit, requiring the user to
   manually re-invoke. Now always re-exec so the new flow triggers
   immediately.

2. process.argv[1] stale binary path:
   If the installer places the updated binary in a different directory than
   the currently running binary (e.g. old: ~/.local/bin, new: /usr/local/bin),
   re-exec would run the old stale binary. Fix: add findUpdatedBinary() which
   resolves via `which spawn` (PATH lookup) first, falling back to
   process.argv[1] only if which fails.

Bump CLI version 0.5.17 → 0.5.18.

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-02-20 10:26:02 -05:00
A
7b6d6eed3b
fix: replace hardcoded history path in security.ts error messages (#1520)
* fix: replace hardcoded ~/.spawn/history.json path in security.ts error messages

Error messages in security validation functions (validateConnectionIP,
validateUsername, validateServerIdentifier, validateMetadataValue) hardcoded
~/.spawn/history.json as the fix path. This is wrong when SPAWN_HOME is set,
directing users to a nonexistent file. Replace all 9 occurrences with
'spawn list --clear' which works regardless of SPAWN_HOME and is simpler
than manually editing JSON.

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

* fix: bump cli version to 0.5.17

Required by CLAUDE.md: any change to cli/ needs a version bump.
PR #1520 changes security.ts error messages (cli/ change).

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-02-20 08:37:01 -05:00
A
703ab4ea4e
fix: use bare 'bun' in cli-entry-edge-cases test subprocess (#1514)
The test's runCli() helper used \${process.env.HOME}/.bun/bin/bun as
the subprocess command. The test preload sandboxes HOME to a temp dir,
so this path resolves to a nonexistent file, causing ENOENT and 49/56
test failures.

Fix: use bare "bun" (resolved via PATH), matching the pattern in
cli-version-and-dispatch.test.ts and cmdrun-resolution.test.ts.

All 56 tests in cli-entry-edge-cases.test.ts now pass.

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-20 03:49:35 -05:00
A
3ebc89d864
fix: correct spawn clear-history to spawn list --clear in error messages (#1508)
Two error messages told users to run 'spawn clear-history' when
encountering corrupted history files, but that command does not exist.
The actual command is 'spawn list --clear'. Users got a confusing
"Unknown agent or cloud: clear-history" error when following the advice.

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-20 01:55:17 -05:00
A
d8785708c9
feat: add cloud provider icons and metadata support (#1503)
Download favicon/icons for all 8 cloud providers into assets/clouds/:
- local.png     — OpenRouter apple-touch-icon (6.4K)
- hetzner.png   — Hetzner 180x180 apple icon (1.9K)
- fly.png       — Fly.io apple-touch-icon (6.4K)
- aws.png       — AWS 144x144 touch icon (3.1K)
- daytona.png   — Daytona favicon from Framer CDN (1.2K)
- digitalocean.png — DigitalOcean apple-touch-icon (6.0K)
- gcp.png       — Google Cloud super_cloud icon (4.2K)
- sprite.png    — Sprites.dev apple-touch-icon (1.9K)

Add assets/clouds/.sources.json tracking canonical source URLs.
Add optional `icon` field to CloudDef interface.
Update manifest.json with raw.githubusercontent.com icon URLs.
Add icon URL type validation test for clouds.
Bump CLI version 0.5.13 → 0.5.14.

Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-20 00:51:40 -05:00
A
6ae650b5e8
feat: add agent stats & metadata to manifest (#1501)
Enrich each agent entry with curated metadata fields: creator, repo,
license, created/added dates, GitHub stars, language, runtime, category,
tagline, and tags. This helps users compare and choose agents.

- Extend AgentDef interface with 12 optional metadata fields
- Add metadata to all 6 agents in manifest.json
- Add type validation tests for new fields
- Bump CLI version 0.5.12 → 0.5.13

Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-19 21:21:18 -08:00
A
0ae9e0bd12
test: fix 53 CLI test failures + critical test/run.sh shell exit bug (#1483)
Why: `set -eo pipefail` + `output=$(shellcheck ...)` on line 659 of
test/run.sh causes immediate exit when shellcheck finds any warning,
preventing the entire shell test suite from running. 53 CLI tests also
fail due to stale assertions after agents/clouds were removed in recent
PRs.

Fixes:
- test/run.sh:659 — add `|| true` to shellcheck command substitution so
  shell test suite runs to completion even when scripts have warnings
- manifest-real-data.test.ts — lower agent count min from 10→5,
  matrix count min from 80→40 (now 6 agents, 48 matrix entries)
- agent-env-injection-contract.test.ts — lower script count min
  from 70→40 (now 47 implemented scripts)
- script-conventions.test.ts — same script count fix (70→40)
- cloud-lib-source-chain.test.ts — lower cloud lib min from 9→8
  (OVH removed, now 8 clouds)
- commands-credential-display-internals.test.ts — add missing
  @clack/prompts mock (tests call p.log.error but never mocked it)
- commands-exported-helpers-edges.test.ts — fix environment-dependent
  assertion: only check credential-based hintOverrides, not
  CLI-installed ones (sprite CLI is installed in CI/dev)
- agent-config-setup.test.ts — fix stale model ID assertion
  ("openrouter/anthropic/..." → "anthropic/...") and stale mkdir
  command ("rm -rf && mkdir" → "mkdir -p")
- agent-info-quickstart.test.ts — remove sprite from singleAuthManifest
  fixture (sprite CLI installed causes sprite to be prioritized over
  hetzner, breaking 4 tests); update count assertions for single cloud

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-19 17:55:43 -05:00
L
57d7d2b014
feat: add icon URLs to all agent manifest entries (#1482)
Add GitHub org avatar URLs as icon fields for all 6 agents,
sourced from the GitHub API (avatars.githubusercontent.com):

- claude:    u/76263028 (Anthropic)
- openclaw:  u/139423088 (OpenRouterTeam)
- zeroclaw:  u/261820148 (zeroclaw-labs)
- codex:     u/14957082 (OpenAI)
- opencode:  u/208539476 (opencode-ai)
- kilocode:  u/201822503 (Kilo-Org)

All use s=200&v=4 for consistent 200px square sizing.
Add optional icon?: string field to AgentDef TypeScript type.
Bump CLI version 0.5.10 → 0.5.11.

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-02-19 13:32:01 -08:00
L
a67d83ed38
feat: reorder agents and remove NanoClaw (#1477)
* feat: add ZeroClaw agent (14.9k stars, native OpenRouter support)

Add ZeroClaw — a Rust-based autonomous AI assistant framework by
Harvard/MIT/Sundai.Club communities — across all 8 clouds.

Scripts: local, hetzner, digitalocean, fly, aws, gcp, daytona, sprite
Install: bootstrap.sh with --install-rust + --install-system-deps
Config:  zeroclaw onboard --provider openrouter (via agent_configure)
Env:     OPENROUTER_API_KEY + ZEROCLAW_PROVIDER=openrouter (native support)
Launch:  zeroclaw agent

Note: ZeroClaw compiles from Rust source (~5-10 min build time).
A build-time warning is shown to set expectations.

Also update test/mock-curl-script.sh to stub zeroclaw install URLs and
add zeroclaw to mock agent binaries in test/mock.sh.

Bump CLI version 0.5.8 → 0.5.9.

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

* feat: reorder agents and remove NanoClaw

New agent order: claude → openclaw → zeroclaw → codex → opencode → kilocode

- Remove NanoClaw (8 scripts + manifest entry + matrix entries + README row)
- Reorder manifest.json agents section to match new order
- Reorder matrix entries by cloud (local/hetzner/fly/aws/daytona/digitalocean/gcp/sprite)
  with agents in new order within each cloud block
- Update README matrix table row order
- Update test/mock.sh mock agent binary list to match
- Bump CLI version 0.5.9 → 0.5.10

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

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-02-19 11:39:03 -08:00
L
f7458952b0
feat: remove Cline, gptme, Plandex, and Continue agents (#1475)
Delete 32 agent scripts ({cloud}/{cline,gptme,plandex,continue}.sh across
8 clouds), remove the 4 agents from manifest.json with all their matrix
entries, update README matrix rows, remove stale mock agent binaries and
plandex.ai URL patterns from test harness, update CLI help examples to use
remaining agents, and bump version 0.5.7 → 0.5.8.

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-02-19 11:12:46 -08:00
L
32522882c1
feat: remove OVH cloud and make featured_cloud an array (#1474)
- Remove OVH as a cloud provider: delete ovh/ directory (lib + 11 agent
  scripts), remove from manifest.json clouds and all ovh/* matrix entries,
  update README matrix table, remove OVH destroy case in CLI commands,
  and clean up all test harness references (mock.sh, mock-curl-script.sh,
  record.sh, e2e.sh, cloud-lib-api-surface.test.ts, test-infra-sync.test.ts)

- Make featured_cloud an array (string[]) so agents can recommend multiple
  clouds; update manifest.ts type, all 10 manifest.json values, and the
  prioritizeCloudsByCredentials() comparison in commands.ts

- Sandbox OAuth in subprocess tests: add OPENROUTER_API_KEY=sk-or-test-fake
  to the default env in cli-entry-edge-cases.test.ts and
  cmdrun-resolution.test.ts so get_or_prompt_api_key() never triggers the
  real OAuth browser flow during test runs

- Fix upload-file-security.test.ts SSH cloud count (5→4) after OVH removal

- Bump CLI version 0.5.6 → 0.5.7

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-02-19 11:06:27 -08:00
A
bc83ab0559
fix: deduplicate isInteractiveTTY and remove dead OVH env wrapper (#1457)
- Export isInteractiveTTY from commands.ts and import in index.ts,
  removing the duplicate definition that was missing !! boolean coercion
- Remove unused inject_env_vars_ovh function from ovh/lib/common.sh
  (all OVH scripts use spawn_agent which calls _spawn_inject_env_vars)
- Bump CLI version to 0.5.6

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-19 01:54:47 -05:00
A
76b172ea41
security: validate GCP metadata in delete script to prevent command injection (#1452)
The buildDeleteScript function in commands.ts interpolated connection.metadata.zone
and connection.metadata.project directly into a bash script string without validation.
A tampered history file could inject arbitrary shell commands via these fields
(e.g., zone='"; rm -rf /; echo "' would escape the double quotes).

Add validateMetadataValue() to security.ts and call it before interpolating
GCP zone and project values into the delete script.

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-18 20:22:41 -08:00
A
3a0ce830e5
fix: resolve unknown --default flag in CLI picker (#1449)
Add --default to KNOWN_FLAGS so it is recognized even if the `spawn pick`
early-return path is bypassed (e.g. due to Bun kqueue/TTY errors on certain
platforms). Also wrap cmdPick in a try/catch so TTY errors produce a clean
error message instead of an unhandled rejection.

Sync test copies of KNOWN_FLAGS that had drifted: unknown-flags.test.ts was
missing --debug, --headless, --output, --clear, -a, -c, --agent, --cloud;
index-dispatch-routing.test.ts had the same gaps. Fix an incorrect test that
expected --output to be flagged as unknown (it has been a known flag since
--headless/--output were added).

Fixes #1447

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-18 15:24:37 -05:00
A
e4bf4d86a4
feat: add spawn pick command and interactive GCP project/zone/machine-type pickers (#1443)
- New cli/src/picker.ts: modular picker module with pickToTTY() that renders
  an arrow-key UI directly to /dev/tty, works even when stdout is captured by
  bash $() subshell substitution and stdin is piped with options.

- New spawn pick subcommand: reads options from stdin as tab-separated lines
  (value\tLabel\tHint), shows clack-style picker via /dev/tty, writes selected
  value to stdout.  Falls back to a numbered list when no TTY is available.

  Usage from bash:
    zone=$(printf 'us-central1-a\tIowa\nus-east1-b\tVirginia\n' \
             | spawn pick --prompt "Select zone" --default "us-central1-a")

- gcp/lib/common.sh: interactive project, zone, and machine-type pickers for
  all GCP agent scripts.  Each picker respects env var overrides (GCP_PROJECT,
  GCP_ZONE, GCP_MACHINE_TYPE) and skips the prompt when already set.  Uses
  spawn pick for a nice arrow-key UI when available; falls back to
  _display_and_select (fzf or numbered list) from shared/common.sh.

  - _gcp_machine_type_options(): curated list of 8 popular instance types
  - _gcp_zone_options(): 12 curated zones across US / EU / APAC / AU
  - _gcp_project_options(): live list via gcloud projects list
  - _gcp_pick_{machine_type,zone,project}(): picker wrappers
  - _gcp_resolve_project(): now prompts interactively instead of erroring when
    no project is configured
  - create_server(): now calls pickers before provisioning instead of silently
    using defaults

- cli version bump 0.5.2 to 0.5.3

Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-18 11:30:52 -05:00
A
79076bbdab
feat: add update-team skill and fix test cleanup (#1435)
* fix: clean up test directories after cmdlist integration tests

The cmdlist-integration.test.ts was creating temporary directories in
beforeEach but never cleaning them up in afterEach, leaving 1,560
test directories in /root (spawn-cmdlist-test-*).

Added rmSync cleanup in afterEach to remove the test directory after
each test run. Bumped CLI version to 0.5.2.

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

* feat: add update-team skill for managing agent team services

This skill automates updating and restarting agent team services
(discovery, refactor, security, qa-cycle) with the latest configuration
from setup-agent-team.

Features:
- Reads latest setup-agent-team SKILL.md for best practices
- Identifies all deployed services via wrapper scripts
- Validates wrapper scripts have required env vars and correct paths
- Validates systemd service files for compliance
- Updates wrapper scripts and systemd configs as needed
- Restarts services and verifies health
- Supports --check-only for dry-run mode
- Can target specific services or update all

Usage:
- claude /update-team                    # Update all services
- claude /update-team discovery          # Update specific service
- claude /update-team --check-only       # Check without changes

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 23:16:48 -08:00
A
8a4f5873f9
feat: remove Oracle Cloud, add featured_cloud per agent (#1430)
Oracle Cloud is removed as a supported provider. Each agent now has a
`featured_cloud` field in manifest.json that controls cloud sort order
in the CLI picker — featured clouds appear after credential-detected
clouds but before CLI-installed ones, with a "recommended" hint.

Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-17 22:52:41 -08:00
A
c097a9d234
feat: add headless SDK mode for programmatic provisioning (#1420)
* feat: add headless SDK mode for programmatic provisioning (#1181)

Add --headless and --output json flags to enable non-interactive
provisioning with structured JSON output on stdout.

- --headless: disables prompts, OAuth browser flows, and SSH sessions
- --output json: outputs structured SpawnResult JSON on stdout
- Exit code contract: 0=success, 1=execution, 2=download, 3=validation
- Upfront credential validation (fail-fast before provisioning)
- Script stdout piped to stderr to keep JSON output clean
- SPAWN_HEADLESS=1 env var set for bash scripts

Closes #1181

-- refactor/ux-engineer

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

* fix: restore critical test mocks for fly SSH readiness checks

The PR inadvertently removed essential mock logic:
- fly ssh mock no longer responded to 'echo ok' commands
- timeout/gtimeout mocks were removed (needed for SSH polling)
- python3 mock was removed (needed for JSON parsing)
- /tmp/spawn_* cleanup was removed from test teardown

This caused 29 fly/* test failures with 'SSH connectivity failed'.

Restores the exact mock implementations from main branch.

---------

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-17 15:32:14 -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
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
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
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
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
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
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
de42958eca
ux: fix install and upgrade success messages (#1113)
Fixes two UX issues identified in #1106:

1. Install script: Raw escape codes weren't rendering in log_info
   - Before: "Run \033[1mspawn\033[0m\033[0;32m to get started\033[0m"
   - After: Uses printf with proper color variable interpolation

2. Update command: Confusing message after `spawn update`
   - Before: "Run your spawn command again to use the new version"
   - After: "Run spawn again to use the new version"
   - The word "your" implied the user had run some other command,
     but they explicitly ran `spawn update`

Agent: ux-engineer

Co-authored-by: Spawn Refactor Service <refactor@spawn.service>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-14 12:45:36 -05:00
A
58823bcc4f
fix(ux): show credential readiness in agents list, matrix, and preflight check (#1061)
- `spawn agents` now shows "N ready" indicator when clouds have credentials
- `spawn matrix` compact view adds a "Ready" column showing credential count
- Preflight credential check gives context-specific guidance: mentions OAuth
  browser flow when only OPENROUTER_API_KEY is missing, improving clarity

Agent: ux-engineer

Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-14 01:55:52 -05:00