Commit graph

428 commits

Author SHA1 Message Date
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
d7ff0739a2
fix: fly auth token deprecated + org picker + macaroon tokens (#1603)
* fix: fly auth token deprecated + org picker + macaroon discharge tokens

Three fixes for the fly/ TypeScript provider:

1. `fly auth token` is deprecated — newer flyctl outputs a message, not
   a token. Now tries `fly tokens create org --expiry 24h` first, with
   `fly auth token` as fallback. Uses org tokens (not deploy) since
   spawn needs to create new apps.

2. Token sanitization stripped macaroon discharge tokens at commas
   (`fm2_[^ ,]*` → `fm2_\S+`). The full composite token
   `fm2_xxx,fm2_yyy,fo1_zzz` is now preserved.

3. Org picker upgraded from numbered 1/2 input to arrow-key interactive
   selector with cursor navigation, scroll windowing, and fallback to
   numbered list when TTY is unavailable.

Also fixes: testFlyToken fallback sent `Bearer FlyV1 ...` (double prefix)
for macaroon tokens — now dispatches FlyV1 vs Bearer correctly.

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

* docs: never run test/mock.sh locally — opens browser, CI only

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-21 11:06:19 -08:00
A
2ef621cc69
refactor: convert fly/ cloud provider from bash to TypeScript (#1601) (#1602)
Replace fly/lib/common.sh (741 lines of bash) with a TypeScript
implementation using Bun runtime. The fly/ provider was the most
complex bash code in the project — recent fixes (#1597, #1599, #1600)
highlight the pain of debugging HTTP calls, JSON parsing, and multi-step
auth flows in shell.

New TypeScript modules:
- fly/lib/ui.ts — logging, prompts, validation (zero deps)
- fly/lib/fly.ts — API client (fetch), auth chain, org listing, provisioning
- fly/lib/oauth.ts — OpenRouter OAuth via Bun.serve(), key management
- fly/lib/agents.ts — typed agent configs for all 6 agents
- fly/main.ts — orchestrator entry point

Agent .sh files become thin shims (~30 lines) that install bun if needed,
download TS sources for curl|bash execution, and delegate to main.ts.

Test coverage:
- 44 TypeScript unit tests (bun test) for pure logic
- 4 fly failure-mode tests (mock.sh) for error scenarios
- All existing test suites pass (110 run.sh, 76 mock.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-21 10:41:34 -08:00
A
9acc239001
fix: validate token characters in _load_token_from_config to prevent curl injection (#1547)
* fix: validate token characters in _load_token_from_config to prevent curl injection

Tokens loaded from ~/.config/spawn/{cloud}.json were exported without
character validation. A tampered config file containing a token with
embedded newlines could exploit the _curl_api function's -K - (stdin
config) mechanism to inject arbitrary curl directives (e.g., output,
url), since curl interprets newlines in the config format as directive
separators.

Add allowlist validation (^[a-zA-Z0-9._/@:-]+$) matching the pattern
already used in key-request.sh _try_load_env_var and validate_api_token,
making all three token-loading paths consistent.

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

* fix: address review feedback on token validation PR

- Update backslash test to expect validation failure (backslashes not
  valid in any known API token format; the old expectation was wrong
  after validation was added)
- Fix test so exit code comes from _load_token_from_config directly,
  not the trailing echo which always exits 0
- Add comment in shared/common.sh explaining why the pattern includes
  colon vs key-request.sh pattern (Fly.io FlyV1 tokens use colons)

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

* fix: address review feedback — widen token charset for base64 segments

The original regex rejected + and = which are valid base64 characters
found in API tokens (e.g. sk-or-v1-abc/def+ghi==). This caused a
pre-existing test to fail. Widen the allowlist to include + and =
while keeping the security comment documenting the pattern difference
with key-request.sh.

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-02-21 01:18:34 -05:00
A
53e6de7f55
fix: validate mock-curl-script.sh stays in sync with mock.sh in test-infra-sync (#1550)
The test-infra-sync test validates that mock.sh's _strip_api_base() and
_validate_body() cover all clouds with fixtures. However, the actual
runtime mock used by tests is mock-curl-script.sh, which has its own
copies of these functions. Nothing enforced these copies staying in sync,
so a contributor could update mock.sh to pass validation while the
runtime mock silently fails to handle new cloud URLs.

Add cross-file sync tests that verify both files handle the same cloud
patterns for _strip_api_base() and _validate_body(). Also refactor
helpers to accept content as a parameter for reuse across both files.

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-21 01:18:15 -05:00
A
3c4d92cc9f
test: fix 25 test failures from mock.module global pollution and sandbox env (#1539)
Add autocomplete mock to 38 @clack/prompts mock.module declarations
that were missing it. Bun's mock.module is process-global, so when any
other test file's mock wins the race, p.autocomplete was undefined,
causing 17 cmd-interactive tests to fail non-deterministically.

Also guard sandbox-verification tests with describe.skipIf(!isSandboxed)
so the 8 meta-tests skip cleanly when running from repo root (where
bunfig.toml preload is not active) instead of failing.

Result: 6995 pass, 0 fail from cli/; 6978 pass, 0 fail, 17 skip from root.

Agent: test-engineer

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 19:36:20 -05:00
A
38b972f5ce
fix: use destroy_server for sprite delete to support org users (#1538)
The sprite case in buildDeleteScript called `sprite destroy` directly,
bypassing ensure_sprite_authenticated and destroy_server. This meant
SPRITE_ORG was never detected, so org users got "sprite not found"
errors and orphaned sprites continued incurring charges.

Align with every other cloud (hetzner, digitalocean, fly, gcp, aws,
daytona) by calling ensure_sprite_authenticated then destroy_server,
which applies _sprite_org_flags automatically.

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 19:34:58 -05:00
A
c7e1c73c8a
fix: unbreak spawn delete and align error handling conventions (#1534)
spawn delete was broken for all clouds because execDeleteServer passed
inline scripts (without shebangs) through runBash, which calls
validateScriptContent requiring a #! prefix. Extract spawnBash helper
and add runBashTrusted for locally-generated delete scripts that already
validate their inputs via validateServerIdentifier/validateMetadataValue.

Also fix instanceof Error usage in manifest.ts and history.ts to use
duck typing, matching the convention documented in index.ts and
commands.ts. Fix stale comment in security.ts that claimed colons were
in the server ID allowlist when the regex excludes them.

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 18:17:29 -05:00
A
7d83bb6191
test: sync 21 failing tests with current source behavior (#1535)
Tests fell out of sync with recent source changes:
- _display_and_select: check for "server types" (agnostic of UI path)
- opencode_install_cmd: check for "tr A-Z a-z" (new OS detection)
- _curl_api: test non-auth headers (auth now via -K stdin)
- ensure_gh_auth: use valid token prefix, match new log messages
- GITHUB_TOKEN piping: match _gh_token variable name
- daytona: remove from exec-based clouds (uses SSH)
- cmdrun/prompt-file: add --dry-run to prevent script execution timeouts
- sandbox: clean stale /root/subprocess-test.txt before assertion

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-20 18:17:21 -05:00
A
c69c12c8db
fix: validate RAW_BASE URL in update-check to prevent future injection (#1533)
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-20 12:52:02 -05:00
A
2bb1b82bc3
fix: align tests with re-exec update behavior and sprite upload classification (#1532)
- update-check.test.ts: mock execFileSync for re-exec path added in eea43ad,
  account for findUpdatedBinary() "which spawn" call, update bare-spawn test
  to expect re-exec instead of "Run your spawn command again"
- upload-file-security.test.ts: fix sprite classification to match
  "sprite $(...) exec" with org flags; remove daytona from strict allowlist
  regression list (uses printf %q escaping, validated by general exec tests)
- version-comparison.test.ts: mock execFileSync for auto-update integration test

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-20 11:52:35 -05: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
be48fe8576
fix: display spawn names in list output (#1523)
Users who name their spawns via the interactive "Name your spawn" prompt
cannot see those names in `spawn list` output. Multiple spawns of the
same agent/cloud combo (e.g. two "Claude Code on Hetzner") are
indistinguishable despite having different names.

Show the spawn name in both interactive picker labels and non-interactive
table output so users can tell their spawns apart.

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 10:22:14 -05:00
A
eff1dc2512
fix: repair Daytona delete and Fly.io reconnect in spawn list (#1521)
- Remove nonexistent `ensure_daytona_cli` call from Daytona delete script
  (causes "command not found" error when running `spawn delete` on Daytona)
- Add Fly.io SSH handler in cmdConnect to use `fly ssh console -a NAME`
  instead of falling through to broken `ssh root@fly-ssh` path

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 09:26:31 -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
3097e5a153
fix: allow freeform display names in spawn name prompt (#1511)
Change "Enter a name for this spawn (optional)" to "Name your spawn"
and remove the restrictive alphanumeric-only validation. Display names
can now include spaces, uppercase, and special characters (e.g.
"My Claude Box"). The shell scripts derive a kebab-case slug for the
actual cloud resource name via _to_kebab_case() in shared/common.sh.

Bump CLI version 0.5.14 → 0.5.15.

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 02:56:59 -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
b8f757f184
fix: resolve 8 cli-entry-edge-cases test failures (#1509)
Switch runCli helper from execSync to spawnSync so stderr is always
captured (execSync only returns stderr on non-zero exits, causing
extra-arg warning tests to fail). Add --dry-run to tests that pass
valid agent+cloud combos to avoid triggering actual script execution
and timing out under bun's 5s per-test limit.

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-20 01:51:36 -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
A
48d418ccb5
fix: update OpenClaw and OpenCode repository URLs (#1478)
Point OpenClaw to https://github.com/openclaw/openclaw and OpenCode to
https://github.com/anomalyco/opencode. Update the OpenCode install command
and binary download URL to match the new repo.

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 11:53:15 -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
5612cda40b
feat: remove Aider, Goose, Open Interpreter, Gemini CLI, Amazon Q from matrix (#1472)
These 5 agents are being dropped from the Spawn matrix. This removes
45 agent scripts across 9 clouds, cleans the manifest, test fixtures,
READMEs, CLI source, and shared library comments.

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 12:31:00 -05:00
A
6449f29f3e
security: validate connection info in cmdRunHeadless JSON output (#1464)
* security: validate connection info in cmdRunHeadless JSON output

Fixes #1461

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

* security: separate validation errors from benign errors in cmdRunHeadless

Silently swallowing validation errors in the broad catch block allowed
tampered connection data to pass through without alerting users. Now
validation failures trigger headlessError() exit with VALIDATION_ERROR
code, while file read/parse errors remain non-fatal.

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

---------

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-19 08:36:29 -05:00
A
f621651fc0
ux: move spawn name prompt after agent/cloud selection (#1458)
The interactive flows asked users to name their spawn before they had
selected an agent or cloud, which was confusing since they didn't know
what they were naming. Move promptSpawnName() to after agent/cloud
selection and credential preflight so users have full context.

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-19 01:57:25 -05: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
930adeecb6
fix: update stale test assertions after Oracle removal and security changes (#1454)
Tests were failing due to code changes that were not reflected in test
assertions:
- env injection uses mktemp paths (/tmp/spawn_env_*) not /tmp/env_config
- Oracle Cloud removal reduced cloud count from 10 to 9 and SSH clouds from 6 to 5
- install.sh clone_cli uses safe canonical path rm (${repo_dir}) not ${dest}/repo
- Fly.io fixture coverage requires api.machines.dev in URL pattern map
- spawn_agent calls get_or_prompt_api_key internally for API key acquisition

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-19 00:52:47 -05:00
A
f9b07d86de
fix: correct test parameter mismatches causing 8 persistent test failures (#1455)
_multi_creds_validate tests in two files were missing the required
help_url parameter (3rd positional arg), causing env vars intended as
the 4th+ args to be consumed as help_url. This meant unset-on-failure
tests only unset 1 of N vars instead of all N.

inject_env_vars_ssh/local tests expected the old hardcoded path
/tmp/env_config but the code now uses randomized /tmp/spawn_env_*
names (a prior security fix to prevent symlink race conditions).

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 00:11:26 -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
ae4aa90bb2
fix: gh CLI setup on remote VMs — pass local token through (#1444)
Fixes GitHub CLI authentication on remote VMs by passing local token through to remote installation script. Uses printf '%q' for safe shell escaping to prevent command injection.
2026-02-18 18:22:33 +00: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
980a7b30f9
security: fix incomplete command injection detection gaps (#1437)
* security: fix incomplete command injection detection gaps in validatePrompt

Addresses remaining gaps identified in issue #1431:
- Add stderr/fd redirection detection (2>, 2>&1, 1>&2)
- Add heredoc detection (<< EOF, <<- EOF)
- Add process substitution detection (<(cmd), >(cmd))
- Add redirection to unextensioned filenames/paths (> output, > foo/bar)
- Add test cases for all new patterns

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

* fix: address PR review - broaden injection detection patterns

- fd redirection: /\d+>\s*&?\d*/ covers fds 3-9 (not just 1 and 2)
- heredoc: /<<-?\s*'?\w+'?/ matches quoted delimiters like << 'EOF'
- append redirect: />>?\s*[a-zA-Z_]\w{2,}/ matches >> as well as >
- Added test cases for all 3 bypass patterns

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.5 <noreply@anthropic.com>
2026-02-18 04:24:36 -05:00
A
6bfdb7da54
fix: add missing text and autocomplete mocks to cmd-interactive tests (#1438)
* fix: add missing text and autocomplete mocks to cmd-interactive tests

17 tests in cmd-interactive.test.ts were failing with
"p.text is not a function" because the @clack/prompts mock didn't
include the text() prompt (added for spawn name input) or
autocomplete() (used for agent selection). Adds both mocks to restore
full test coverage of cmdInteractive.

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

* fix: update stale version strings in update-check tests

The update-check tests mock "latest" version as 0.3.0, but the current
CLI version is 0.5.2. Since 0.3.0 < 0.5.2, compareVersions returns
false and the auto-update logic never fires, causing 5 tests to fail.
Replace mock version with 99.0.0 to future-proof against further bumps.

Agent: test-engineer
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-18 04:22:38 -05:00
Ahmed Abushagur
d9e6d058e0
fix: use uv --upgrade to ensure Python 3.13-compatible Pillow across all clouds (#1436)
aider-chat on Python 3.13 fails with `ImportError: cannot import name
'_imaging' from 'PIL'` when an old Pillow version (pre-10.4) is resolved
— those releases have no Python 3.13 binary wheels, so the C extension
is missing at runtime.

Replace `--with 'Pillow>=10.2.0'` (which was silently broken — the `>`
and single quotes get mangled by `printf '%q'` in run_server before the
command reaches the remote machine) with `--upgrade`, which forces all
transitive deps including Pillow to their latest compatible versions.

Also adds a plain-text echo before the install so users see progress
instead of a silent hang during the 2-4 minute install.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-18 03:21:59 -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
94b09ab29e
security: fix path traversal risk in SPAWN_HOME validation (#1402)
* security: fix path traversal risk in SPAWN_HOME validation

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

* fix: add missing join import and update tests for SPAWN_HOME security validation

Addresses security review feedback on PR #1402:
- Add missing 'join' import to cli-version-and-dispatch.test.ts
- Update all test files to use homedir() instead of tmpdir() for SPAWN_HOME

The security fix in history.ts now enforces that SPAWN_HOME must be within
the user's home directory. All tests have been updated to use home-based
test directories instead of /tmp paths.

Changes:
- cli/src/__tests__/cli-version-and-dispatch.test.ts: Add join to path imports
- All test files: Replace tmpdir() with homedir() and /tmp/spawn- with /.spawn-test-

Tests:
- bun test history.test.ts:  69 pass
- bun test clear-history.test.ts:  27 pass
- bun test cli-version-and-dispatch.test.ts:  62 pass
- bun test list-table-rendering.test.ts:  8 pass

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 12:57:01 -05:00
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