Commit graph

1294 commits

Author SHA1 Message Date
A
32db000ca4
fix: validate env-var tokens and align cloud_authenticate patterns (#1516)
- ensure_api_token_with_provider now validates env-var tokens with
  the provider test function, matching config-file token behavior.
  Previously, stale env-var tokens silently passed auth and failed
  at server creation with cryptic API errors.

- Add prompt_spawn_name to Hetzner and Daytona cloud_authenticate,
  matching the pattern used by AWS, Fly, GCP, DigitalOcean, and
  Sprite. Without this, SPAWN_NAME_KEBAB is never set and server
  name prompts have no pre-filled default on these two providers.

- Remove redundant register_cleanup_trap from DigitalOcean
  cloud_authenticate. shared/common.sh auto-registers the trap at
  source time (line 3696), making the explicit call dead code.

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 04:55:35 -05:00
A
9de4cecfea
fix: add spawn delete hint to post-session summaries (#1515)
After an SSH/exec session ends, the post-session summary warns users
their server is still running and directs them to the cloud dashboard.
It never mentions `spawn delete`, the CLI's own deletion command.

Add a "spawn delete" hint to both _show_post_session_summary (SSH
clouds) and _show_exec_post_session_summary (exec clouds) so users
discover the feature at the moment they most need it.

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 04:52:39 -05:00
Ahmed Abushagur
b5d174a472
fix: pin Codex to 0.94.0 + wire_api=chat for multi-turn stability (#1518)
* fix: switch Codex wire_api from "responses" to "chat" for multi-turn stability

The Responses API format causes "Invalid Responses API request" errors on
the second turn and beyond — conversation history items round-trip through
OpenRouter with null content fields and missing IDs that fail validation.
Chat Completions format is fully supported and avoids this issue.

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

* fix: pin Codex to 0.94.0 + wire_api=chat for multi-turn stability

OpenRouter's Responses API proxy drops required fields (id, content) from
conversation-history items on multi-turn requests, causing "Invalid
Responses API request" at input[6]+. Codex >=0.97.0 removed wire_api=chat
support (openai/codex#10157), so we pin to 0.94.0 — the last release where
Chat Completions format still works.

Tracking: https://github.com/openai/codex/issues/12114
TODO: unpin once OpenRouter /responses handles round-trip correctly.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 04:49:35 -05:00
A
50dd2f26ed
fix: repair Fly.io saved token loading (_load_token_from_config misuse) (#1513)
ensure_fly_token() called _load_token_from_config with only 1 argument
(config file path) but the function requires 3 (config_file, env_var_name,
provider_name). The empty env_var_name fails the security validation regex,
so the function always returns 1 silently. Users with saved Fly.io tokens
in ~/.config/spawn/fly.json were forced to re-authenticate every session.

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 03:54:41 -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
Ahmed Abushagur
95137ed2c7
fix: rewrite hetzner common.sh + fix token prompt bug (#1512)
* fix: rewrite hetzner common.sh + fix token prompt bug in shared/common.sh

Hetzner: rewrote from 621 to 224 lines. Removed hcloud CLI dual-path
fallback, server type validation/fallback chain (11 functions), and
duplicate CLI+API implementations. Now API-only like DigitalOcean.

Shared: fixed echo "" in _prompt_for_api_token, get_openrouter_api_key_manual,
and get_openrouter_api_key_oauth writing to stdout instead of stderr.
These functions are called inside $(...) command substitutions, so the
newlines got prepended to the captured token, causing "unable to
authenticate" errors when pasting tokens at the prompt.

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

* fix: rewrite daytona common.sh — API-only, drop CLI dependency

Rewrote from 312 to 174 lines. Removed daytona CLI dependency in
favor of direct REST API calls. Matches the same API-only pattern
used by Hetzner, DigitalOcean, and other clouds.

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

* fix: pass SSH port to control master exit in daytona interactive/destroy

The ssh -O exit command to close the multiplexed master was missing
the -p PORT flag when DAYTONA_SSH_PORT is set. This left the master
connection open, causing "mux_client: master did not respond" errors
when the interactive session tried to allocate a PTY.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 02:52:49 -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
b094accb93
fix: save connection info for all Sprite agents in cloud_provision (#1510)
5 of 6 Sprite agent scripts silently skipped saving connection info
for 'spawn list', because only sprite/claude.sh defined the
agent_save_connection hook. All other clouds save connection info in
their create_server() equivalent; move save_vm_connection into
cloud_provision() in sprite/lib/common.sh to match that pattern and
cover all agents uniformly. Remove now-redundant agent_save_connection
from sprite/claude.sh.

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 01:51:25 -05:00
A
3280a44c45
feat: add browser-based OAuth login for Fly.io + token sanitizer (#1506)
Replace the prompt-first auth flow with a browser-based CLI session
flow (same as `fly auth login`). The new auth chain is:

1. Environment variable (FLY_API_TOKEN)
2. Saved config file (~/.config/spawn/fly.json)
3. flyctl CLI (`fly auth token`)
4. Browser OAuth via Fly.io CLI Sessions API (NEW)
5. Manual token prompt (last resort fallback)

The browser flow creates a CLI session via POST /api/v1/cli_sessions,
opens the auth URL in the user's browser, then polls for the access
token. This is the same mechanism flyctl uses internally.

Also add _sanitize_fly_token() to handle the Fly dashboard copy button
which includes the display name before the token (e.g. "Deploy Token
FlyV1 fm2_..."). The sanitizer strips everything before "FlyV1" or
extracts bare "fm2_" tokens, and trims whitespace/newlines. Applied
at every token entry point (env var, config, manual prompt).

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 22:50:19 -08:00
L
d5690a8b11
feat: spawn name prompt + kebab resource naming across all clouds (#1507)
* feat: add spawn name prompt and project confirmation to GCP flow

Ask for spawn name upfront (before auth), derive kebab-case default for
VM naming, and confirm the current GCP project before using it.

New interaction order:
  1. Spawn name: "My Dev Box" → kebab "my-dev-box" exported as
     GCP_INSTANCE_NAME_KEBAB
  2. gcloud auth + project confirm: "Current project: X  Keep? [Y/n]"
     If no → project picker shown
  3. SSH key
  4. Machine type picker (existing)
  5. Zone picker (existing)
  6. Instance name prompt: "Instance name [my-dev-box]: "
     User can press Enter to accept or type a custom name

New functions:
  _to_kebab_case()         — lowercases, replaces non-alnum with hyphens
  _gcp_prompt_spawn_name() — prompts for display name, exports kebab default;
                             honours SPAWN_NAME env var set by CLI (--name flag)

Modified:
  _gcp_resolve_project()  — adds Y/n confirmation when project already set
  get_server_name()       — shows kebab default in prompt, accepts Enter
  cloud_authenticate()    — calls _gcp_prompt_spawn_name first

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

* feat: add spawn name prompt to all clouds via shared/common.sh

Move _to_kebab_case() and prompt_spawn_name() to shared/common.sh so all
clouds get upfront spawn name prompting and kebab-based resource naming.

shared/common.sh:
  + _to_kebab_case()    — "My Dev Box" → "my-dev-box"
  + prompt_spawn_name() — asks for display name, exports SPAWN_NAME_DISPLAY
                          and SPAWN_NAME_KEBAB; skips if already set;
                          honours SPAWN_NAME env var from CLI --name flag
  ~ get_resource_name() — replaces silent SPAWN_NAME fallback with a visible
                          prefilled default: "Enter server name [my-dev-box]: "

Per-cloud changes (cloud_authenticate gains prompt_spawn_name first):
  hetzner, fly, aws, daytona, digitalocean, sprite — one-line change each

gcp/lib/common.sh:
  - Remove _to_kebab_case()        (now in shared)
  - Remove _gcp_prompt_spawn_name() (now in shared as prompt_spawn_name)
  ~ cloud_authenticate: _gcp_prompt_spawn_name → prompt_spawn_name
  ~ get_server_name: simplified back to get_validated_server_name
    (shared get_resource_name now shows the kebab default in the prompt)

Result — every cloud shows this flow upfront:
  Spawn name (e.g. "My Dev Box"): My Claude Box
  ℹ Resource name: my-claude-box
  ...
  Enter server name [my-claude-box]: ⏎

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

* fix: use "Use project '...'?" instead of "Keep this project?" in GCP prompt

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 22:22:59 -08:00
L
ff261f3544
feat: add spawn pick to shared _display_and_select (Hetzner + all clouds) (#1505)
* feat: add spawn pick to _display_and_select in shared/common.sh

All clouds using interactive_pick (Hetzner, DigitalOcean, AWS, fly, etc.)
now get the arrow-key picker UI when the user runs via `spawn`.

Placement: between fzf (rarely installed) and numbered list (plain fallback).
Priority: fzf > spawn pick > numbered list.

Pipe-delimited items "id|field2|field3..." are converted to tab-delimited
"id\tid\tfield2 · field3 · ..." so spawn pick displays:
  > cx22  2 vCPU · 4.0 GB RAM · 40 GB disk · shared · $ 0.0057/hr
  > fsn1  Falkenstein · DE

The --default flag uses default_id when set, otherwise default_value,
so the correct item is pre-selected when the picker opens.

No 2>/dev/tty redirect (avoids the zsh 'file exists' failure that broke
the GCP picker; spawn pick opens /dev/tty internally via fs.openSync).

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

* refactor: replace custom _gcp_interactive_pick with shared interactive_pick

- Remove _gcp_interactive_pick (60 lines of custom picker logic)
- Convert option functions to pipe-delimited format (id|detail)
  to match what interactive_pick / _display_and_select expect
- Replace _gcp_pick_{machine_type,zone,project} with direct
  interactive_pick calls — same pattern as Hetzner
- _gcp_project_options: awk now outputs id|name instead of id\tid\tname

GCP now gets fzf → spawn pick → numbered list for free via the
shared helper, with no cloud-specific picker code.

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 21:59:00 -08: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
L
015446eee8
fix: remove 2>/dev/tty from spawn pick call in GCP picker (#1504)
The 2>/dev/tty redirect caused spawn pick to exit 1 on zsh/macOS
with 'file exists: /dev/tty', silently breaking the picker and
always falling through to the numbered-list fallback.

spawn pick renders its arrow-key UI by opening /dev/tty directly
via fs.openSync() — it never uses stderr for the UI — so the
redirect served no purpose and only caused failures.

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-02-19 21:44:01 -08:00
A
2a4e7ff983
feat: upgrade refresh-favicon skill to update-metadata (#1502)
Replace the icon-only refresh-favicon skill with a comprehensive
update-metadata skill using TypeScript + Bun. The script fetches
live GitHub stats (stars, license, language) and refreshes icons,
with metadata completeness validation.

- update.ts: runnable script (bun run .claude/skills/update-metadata/update.ts)
- Supports --agent, --dry-run, --icons-only, --stats-only flags
- Uses gh api for GitHub data, native fetch for icon downloads
- Validates all 12 metadata fields per agent

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:27:31 -08: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
9f172ffd12
fix: resolve 18 test/run.sh failures and expand sprite agent coverage (#1498)
- Add SPAWN_SKIP_API_VALIDATION=1 and SPAWN_SKIP_GITHUB_AUTH=1 to
  sprite test environment so verify_openrouter_key() doesn't make real
  HTTP calls with the fake test key (which gets 401, clears the key,
  and falls into OAuth — causing all sprite assertions to fail)
- Update agent iteration lists from stale "claude openclaw nanoclaw" to
  current "claude openclaw codex opencode kilocode zeroclaw"
- Remove dead nanoclaw case from _assert_agent_specific
- Remove 5 dead agent cases (nanoclaw, cline, gptme, plandex, continue)
  from _shared_agent_assertions.sh, add zeroclaw

Result: 108 passed, 0 failed (was: 48 passed, 18 failed)

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 00:06:06 -05:00
A
34b093fce0
fix: escape control characters in json_escape bash fallback (#1497)
The json_escape fallback (used when python3 is unavailable) only escaped
backslashes and double quotes, producing invalid JSON when input contained
newlines, tabs, or carriage returns. This could cause JSON injection in
API request bodies sent to cloud providers (Hetzner, DigitalOcean, Fly.io)
and corrupt credential config files.

Add escaping for \n, \r, and \t in the fallback path. The python3 primary
path (json.dumps) was already correct.

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 00:05:20 -05:00
L
64b8153377
fix: mark local/opencode as implemented in README matrix (#1500)
* fix: mark local/opencode as implemented in README matrix

* fix: update README agent/cloud counts and use claude.ai favicon

- Update README tagline: 10 agents/10 clouds/99 combos → 6/8/48 (accurate)
- Re-download claude icon from claude.ai/apple-touch-icon.png instead of
  Anthropic GitHub org avatar (62K vs 4.5K, higher quality source)
- Update assets/agents/.sources.json to reflect new claude icon source

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 21:02:40 -08:00
L
635d358ca3
feat: add agent icon assets and refresh-favicon skill (#1499)
Download favicon/icons for all 6 agents into assets/agents/:
- claude.png    — Anthropic GitHub org avatar (4.5K)
- openclaw.png  — openclaw.ai/apple-touch-icon.png (5.8K)
- zeroclaw.png  — zeroclaw-labs GitHub org avatar (11K)
- codex.png     — OpenAI GitHub org avatar (4.0K)
- opencode.svg  — opencode.ai/favicon.svg (612B)
- kilocode.png  — Kilo-Org GitHub org avatar (1.3K)

Update manifest.json icon fields to point to raw.githubusercontent.com
URLs for the local files (stable, CDN-served, versioned in repo).

Add assets/agents/.sources.json tracking each agent's canonical source
URL and extension for use by the refresh-favicon skill.

Add .claude/skills/refresh-favicon/SKILL.md — a skill that re-downloads
all agent icons from their source URLs, detects content types, updates
.sources.json, and syncs manifest.json icon fields.

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-02-19 20:55:13 -08:00
A
bb56302c67
fix: correct OAuth code validation regex end-of-string anchor (#1492) (#1496)
Remove backslash before $ in regex pattern so it anchors to end-of-string
rather than matching a literal dollar sign. This restores proper validation
of OAuth codes (16-128 alphanumeric chars only).

Agent: security-auditor

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-19 22:06:31 -05:00
Ahmed Abushagur
749ca907b7
fix: Hetzner reprompt for different API key on account limits (#1494)
When server creation fails with account-level errors (server limit
reached, insufficient funds, quota exceeded), offer to switch to a
different Hetzner API token and retry instead of just failing.

- Add _is_hetzner_account_error() to detect account-level issues
- Return exit code 2 from create_server() for account errors
- cloud_provision() catches code 2, prompts "Try a different account?"
- On yes: re-prompts for new API key, re-registers SSH keys, retries

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 20:40:20 -05:00
A
c3d251100b
fix: inline temp file cleanup in setup_shell_environment to preserve EXIT trap (#1489)
Replace both the trap-clobbering `trap 'rm -f ...' EXIT` calls and the
inline `rm -f` approach with `track_temp_file()` from shared/common.sh.
This registers temp files with the centralized cleanup handler that is
already set up on EXIT/INT/TERM, so:
- Temp files are cleaned up even on interrupt (not just success path)
- The calling script's EXIT trap is never clobbered
- _sprite_retry wrappers are preserved for transient error recovery

Agent: pr-maintainer

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 00:30:48 +00:00
Ahmed Abushagur
0e2750dfd9
fix: persist gh auth credentials for interactive sessions (#1491)
* fix: persist gh auth credentials to disk for interactive sessions

When GITHUB_TOKEN is in the environment, gh auth status returns success
(gh checks env vars first), so ensure_gh_auth() short-circuits before
gh auth login --with-token writes credentials to ~/.config/gh/hosts.yml.
The interactive session starts without GITHUB_TOKEN in env, so gh reports
"not logged into any GitHub hosts".

Fix: always run gh auth login --with-token when GITHUB_TOKEN is set,
persisting credentials to disk regardless of gh auth status.

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

* fix: unset GITHUB_TOKEN env var before gh auth login --with-token

gh refuses to store credentials when GITHUB_TOKEN is already set in
the environment: "The value of the GITHUB_TOKEN environment variable
is being used for authentication." Save the value, unset the env var,
pipe it to gh auth login, then re-export.

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

* fix: address security review — validate token format, skip if already persisted

- Add GITHUB_TOKEN format validation (ghp_, gho_, ghu_, ghs_, ghr_, github_pat_)
- Add fast path: check gh auth status with env var unset before persisting
- Document plaintext credential store behavior (standard gh CLI behavior)

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
2026-02-19 19:30:44 -05:00
Ahmed Abushagur
9e2f84adf0
fix: use native OpenRouter model_provider for Codex CLI config (#1490)
Codex CLI's OPENAI_BASE_URL env var approach causes "Invalid Responses
API request" errors because OpenRouter doesn't fully support the
Responses API wire format via base URL override. Switch all 8 codex
scripts to use ~/.codex/config.toml with model_provider="openrouter"
which uses the native OpenRouter integration.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 18:47:40 -05: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
A
4a6ec4fed7
fix: replace local -n namerefs in test/record.sh for bash 3.2 compat (#1488)
Why: test/record.sh used local -n (bash 4.3+ namerefs) which crashes
on macOS's default bash 3.2, breaking contributor workflow for recording
API fixtures. Fixes #1480.

Inlines the _export_env_vars_from_fields helper directly into
_load_multi_config_from_file, eliminating the nameref dependency while
preserving the security validation of env var names.

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:49:35 -05:00
Ahmed Abushagur
4d32923d5f
fix: add retry logic for transient Sprite API errors (#1487)
Sprite API calls intermittently fail with TLS handshake timeouts and
connection resets. Add _sprite_retry() wrapper that retries up to 3
times with 3s delay on transient errors.

Wrapped calls: sprite create, sprite exec (run_sprite), sprite exec
-file (upload_file_sprite, setup_shell_environment uploads).

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 17:49:29 -05:00
A
b29cf4a75d
fix: sync cloud READMEs with current agent list (#1486)
READMEs across all 8 clouds still referenced 5 removed agents
(NanoClaw, Cline, gptme, Plandex, Continue) and were missing
ZeroClaw. Users following these docs got 404 errors.

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 17:47:57 -05:00
Ahmed Abushagur
4378db760e
fix: opencode download URL — map x86_64 to x64, drop darwin→mac rename (#1485)
Release assets use x64 not x86_64 (opencode-linux-x64.tar.gz) and
darwin not mac (opencode-darwin-arm64.tar.gz). The arch mapping only
handled aarch64→arm64 but missed x86_64→x64, causing 404 on all
x86_64 servers.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 13:52:26 -08:00
Ahmed Abushagur
a063fe61cd
fix: sprite npm PATH resolution and gateway timeout (#1484)
* fix: sprite npm PATH resolution and gateway timeout

Sprites use nvm-managed node, so npm global bin is at
/.sprite/languages/node/nvm/.../bin/ which isn't in default PATH.
Dynamically resolve $(npm prefix -g)/bin in install, launch, and
gateway commands for all sprite agents.

Also increase openclaw gateway timeout from 30s to 60s — gateway
starts slowly on sprites but TUI connects once ready.

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

* fix: add opencode bin dir to PATH in sprite launch command

OpenCode installs to $HOME/.opencode/bin/ which isn't in the sprite's
default PATH or the npm prefix path.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 16:49:52 -05:00
A
87d6fdd240
feat: implement local/opencode script (#1481)
Why: local/opencode was listed as 'missing' in manifest.json — users
could not run OpenCode on their local machine via spawn.

- Add local/opencode.sh following the same pattern as other local scripts
  (sources lib/common.sh, uses opencode_install_cmd from shared/common.sh,
  injects OPENROUTER_API_KEY via generate_env_config)
- Update manifest.json matrix entry from 'missing' to 'implemented'

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 16:49:50 -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
Ahmed Abushagur
3b1f87e656
fix: pass -o org flag to all sprite CLI commands (#1479)
* fix: pass -o org flag to all sprite CLI commands

sprite create/exec/list/destroy fail with "authentication failed" when
the org isn't passed explicitly. Detect the selected org after login and
thread it through all sprite commands via _sprite_org_flags().

Also fix ensure_sprite_authenticated to fail loudly instead of
swallowing errors with || true.

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

* fix: sprite scripts fail when zsh is not available

setup_shell_environment overwrites .bashrc with `exec zsh`, but sprites
don't have zsh installed. This breaks PATH and causes all agent launch
commands that source .zshrc to fail.

- Only switch to zsh if it's actually available on the sprite
- Replace `source ~/.zshrc` with explicit PATH in all sprite agent
  launch commands (openclaw, opencode, codex, kilocode)
- Fix start_openclaw_gateway to use explicit PATH instead of .zshrc

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

* fix: openclaw not found on sprite — bashrc corruption from prior runs

On reused sprites, .bashrc still has `exec /usr/bin/zsh -l` from a prior
run. Sourcing it in the install command causes `&&` to short-circuit, so
`bun install -g openclaw` never runs.

- Clean up stale `exec zsh` lines from .bashrc at start of
  setup_shell_environment (fixes reused sprites)
- Use explicit PATH in openclaw install command instead of relying on
  .bashrc

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

* fix: use npm instead of bun for openclaw install on sprite

bun 1.3.9 on sprites fails with "connection closed" during dependency
resolution. Other sprite agents (codex, kilocode) already use npm
successfully.

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

* fix: openclaw install — npm+bun fallback, verify binary exists

Try npm first (more reliable on sprites), fall back to bun, then verify
the binary is actually in PATH before continuing.

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

* fix: persist npm global bin path to .spawnrc on sprites

npm installs openclaw successfully but its global bin dir isn't in the
sprite's default PATH. Detect the npm bin path after install, write it
to .spawnrc so gateway and launch commands (which source .spawnrc) find
the binary.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 15:47:47 -05: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
2e264d808d
fix: remove duplicate API key calls in spawn_agent + fix OVH server name (#1471)
PR #1462 removed duplicate get_or_prompt_api_key and get_model_id_interactive
calls in spawn_agent(). PR #1468 accidentally re-introduced them with incorrect
step numbering (two "4"s and two "5"s). This doubled API validation requests on
every deployment across all 130+ agent scripts.

Also fix OVH cloud_provision not exporting OVH_SERVER_NAME, causing
save_vm_connection to record an empty server name when the user types the name
at the interactive prompt instead of passing it via env var.

Agent: code-health

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-19 12:30:25 -05:00
A
d8785c3d0b
security: fix command injection in cline auth via remote env var expansion (#1473)
All 9 cline.sh scripts embedded OPENROUTER_API_KEY directly into the
cloud_run command string, allowing shell metacharacter injection on the
remote server. Fix by escaping the dollar sign (\${OPENROUTER_API_KEY})
so the variable is expanded on the remote machine where it's already
set via agent_env_vars()/generate_env_config, not locally before being
passed to cloud_run.

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-19 12:25:16 -05:00
A
e2d6aa1444
fix: use json_escape in save_vm_connection to prevent malformed JSON (#1470)
save_vm_connection built JSON via direct string interpolation, which
produces malformed output if any value contains quotes, backslashes,
or other JSON-special characters. This breaks spawn list/delete/history.

Changes:
- Use json_escape for all string fields in save_vm_connection
- Use json_escape for GCP zone/project metadata values
- Switch AWS, GCP, Daytona get_server_name to get_validated_server_name
  for consistency with Hetzner, DigitalOcean, Fly, OVH

Agent: code-health

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-19 16:23:27 +00: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
Ahmed Abushagur
8ee54d01a8
fix: harden agent reliability + security across all clouds (#1468)
* docs: add spawn delete command to README

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

* fix: harden openclaw across all clouds — validation, reliability, performance

Fixes multiple issues causing openclaw to break on most clouds:

Bugs fixed:
- Double-prefixed model ID (openrouter/openrouter/auto) in config generation
- AWS gateway starting without env vars (missing .zshrc source)
- DigitalOcean sourcing .spawnrc instead of .zshrc for gateway
- Destructive rm -rf ~/.openclaw on re-runs (now mkdir -p)

Validation added:
- API key checked against OpenRouter /auth/key endpoint with re-prompt on failure
- Model ID verified against OpenRouter model list with re-prompt loop
- openrouter/auto and openrouter/free bypass model check

Reliability improvements:
- Standardized gateway launch with </dev/null & disown across all 9 clouds
- Gateway log auto-displayed on startup timeout for diagnostics
- 2GB swap added to cloud-init to prevent OOM on small VMs
- Portable install timeout (10 min) with macOS gtimeout fallback

Performance:
- Reordered spawn_agent: OAuth runs while VM provisions (saves 30-60s)
- Fly.io: bumped to 2GB RAM + 2 shared CPUs for openclaw
- Fly.io: tries bun first (faster), falls back to npm

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

* fix: skip sudo in gh install when running as root (Fly.io containers)

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

* fix: address PR review — skip validation in tests, quote escaped cmd, escape model_id

- verify_openrouter_key and verify_openrouter_model skip network calls when
  SPAWN_SKIP_API_VALIDATION, BUN_ENV=test, or NODE_ENV=test is set
- install_agent timeout wrapper now quotes the escaped command for defense in depth
- model_id in openclaw JSON now uses json_escape() for consistency

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

* fix: remove double-escaping in install_agent that broke shell operators

install_agent() was wrapping commands with printf '%q' + bash -c before
passing them to the run callback. But run callbacks (run_server, run_sprite,
ssh_run_server) already handle escaping for remote transport. The double-
escaping turned && || > | into literal characters, causing 'source' to
treat the entire command as a single filename.

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

* fix: use local github-auth.sh instead of curling from main

When running from a local checkout, base64-encode the local
github-auth.sh and send it inline to the remote machine. This
ensures fixes (like the sudo skip for root) take effect immediately
without waiting for a merge to main.

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

* fix: handle github-auth errors gracefully instead of terminating

GitHub CLI setup is optional — failures should not abort the spawn
session. Guard both run_callback calls in offer_github_auth with
|| log_warn so the script continues even if gh install fails.

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

* fix: use GOOGLE_GEMINI_BASE_URL to route Gemini CLI through OpenRouter

Gemini CLI ignores OPENAI_BASE_URL — it uses GEMINI_API_KEY to talk
directly to Google's API. The OpenRouter key is not a valid Google
API key, so all requests fail with "API key not valid".

Use GOOGLE_GEMINI_BASE_URL to redirect Gemini CLI to OpenRouter's
endpoint. Fixes all 9 cloud gemini scripts + manifest.json.

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

* fix: guard optional spawn_agent hooks so failures don't kill the session

With set -eo pipefail, any unguarded failure terminates the script.
Several optional operations in spawn_agent were unguarded:

- agent_configure: config file uploads (agent works with defaults)
- agent_save_connection: convenience JSON for spawn list
- agent_pre_launch: gateway daemons, startup hooks
- agent_pre_provision: pre-provision prompts
- .spawnrc shell hooks: hooking env vars into .bashrc/.zshrc

These now log warnings and continue instead of aborting. Critical
steps (cloud_authenticate, agent_install, cloud_provision) still
exit on failure.

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

* fix: audit and fix env vars, escaping, and error handling across all agents

Audit findings from 3 parallel agents, fixes applied:

**Env vars (4 agents fixed across 9 clouds each = 36 scripts):**
- Amazon Q: remove fake OPENAI_* vars (Q uses AWS auth, can't use OpenRouter)
- Cline: replace OPENAI_* env vars with `cline auth -p openrouter` command
- Open Interpreter: drop OPENAI_* vars, use only OPENROUTER_API_KEY (native support via --model flag)
- NanoClaw: add ANTHROPIC_BASE_URL to .env file (was missing, requests went to Anthropic directly)

**Escaping:**
- execute_agent_non_interactive: replace printf '%q' with single-quote wrapping to avoid double-escaping on Fly.io

**Manifest updated** for amazonq, cline, interpreter entries.

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

* fix: use setsid to detach openclaw gateway daemon from SSH sessions

The gateway daemon launch (`nohup openclaw gateway ... & disown`) hangs
on all clouds because SSH/exec channels wait for child FDs to close.
setsid creates a new session, fully detaching the daemon so the channel
can close immediately. Falls back to nohup where setsid is unavailable.

Consolidates the daemon launch into a shared start_openclaw_gateway()
function used by all 9 cloud scripts.

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

* fix: configure npm global prefix for non-root clouds (AWS, GCP, OVH)

AWS Lightsail, GCP, and OVH SSH as non-root users (ubuntu/login user),
so `npm install -g` fails with EACCES on /usr/local/lib/node_modules/.

Fix: configure npm prefix to ~/.npm-global during cloud-init/setup and
add ~/.npm-global/bin to the SSH PATH prefix so agent install commands
find globally-installed npm binaries without sudo.

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

* fix: remove broken OpenRouter routing from Gemini CLI scripts

Gemini CLI uses Google's native API format (/v1beta/models/:streamGenerateContent),
not the OpenAI-compatible format (/v1/chat/completions). No base URL override can
bridge this — the request formats are fundamentally incompatible. Same situation
as Amazon Q (uses vendor-specific auth/API).

Removed GEMINI_API_KEY and GOOGLE_GEMINI_BASE_URL from all 9 scripts + manifest.

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

* fix: auto-install AWS CLI and gcloud SDK when missing

Instead of printing manual install instructions and exiting, both CLIs
now auto-install:

- AWS: downloads official .pkg (macOS) or .zip (Linux) installer
- GCP: uses brew cask on macOS, Google's tarball installer on Linux

Falls back to manual instructions if auto-install fails.

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

* fix: nanoclaw — install Docker on Linux, fix hardcoded /root/ path

Two issues broke NanoClaw on all clouds:

1. .env upload hardcoded /root/nanoclaw/.env — fails on non-root clouds
   (AWS=ubuntu, GCP=user, OVH=ubuntu). Now uses upload_config_file with
   $HOME which expands on the remote side.

2. NanoClaw requires a container runtime. On Linux it uses Docker, but
   Docker was never installed. Added Docker install via get.docker.com
   to all cloud scripts (with sudo where SSH user is non-root).

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

* fix: address security review findings from PR #1463

- Reject symlinked github-auth.sh before base64-encoding (falls back to remote URL)
- Hide API key from process list using curl -K - instead of -H in verify_openrouter_key

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

* fix: quote OPENROUTER_API_KEY in cline auth to prevent command injection

Unquoted variable in `cline auth -p openrouter -k ${OPENROUTER_API_KEY}`
allows shell metacharacters in the key to execute arbitrary commands on
the remote server. Wrapping in escaped double quotes prevents expansion.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 08:36:24 -05:00
A
b603e05043
fix: remove duplicate API key + model selection in spawn_agent() (#1462)
Steps 3-4 (get_or_prompt_api_key and model selection) were executed
twice in spawn_agent() -- once before provisioning and once after.
This caused redundant HTTP validation calls to openrouter.ai/api for
every agent deployment (~130+ scripts use spawn_agent). The duplicate
step numbering in comments (3,4,5 then 4,5,6) confirms this was
accidental.

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 06:23:55 -05:00
Ahmed Abushagur
be904cbe1c
fix: install_agent double-escaping + github-auth reliability (#1460)
* docs: add spawn delete command to README

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

* fix: harden openclaw across all clouds — validation, reliability, performance

Fixes multiple issues causing openclaw to break on most clouds:

Bugs fixed:
- Double-prefixed model ID (openrouter/openrouter/auto) in config generation
- AWS gateway starting without env vars (missing .zshrc source)
- DigitalOcean sourcing .spawnrc instead of .zshrc for gateway
- Destructive rm -rf ~/.openclaw on re-runs (now mkdir -p)

Validation added:
- API key checked against OpenRouter /auth/key endpoint with re-prompt on failure
- Model ID verified against OpenRouter model list with re-prompt loop
- openrouter/auto and openrouter/free bypass model check

Reliability improvements:
- Standardized gateway launch with </dev/null & disown across all 9 clouds
- Gateway log auto-displayed on startup timeout for diagnostics
- 2GB swap added to cloud-init to prevent OOM on small VMs
- Portable install timeout (10 min) with macOS gtimeout fallback

Performance:
- Reordered spawn_agent: OAuth runs while VM provisions (saves 30-60s)
- Fly.io: bumped to 2GB RAM + 2 shared CPUs for openclaw
- Fly.io: tries bun first (faster), falls back to npm

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

* fix: skip sudo in gh install when running as root (Fly.io containers)

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

* fix: address PR review — skip validation in tests, quote escaped cmd, escape model_id

- verify_openrouter_key and verify_openrouter_model skip network calls when
  SPAWN_SKIP_API_VALIDATION, BUN_ENV=test, or NODE_ENV=test is set
- install_agent timeout wrapper now quotes the escaped command for defense in depth
- model_id in openclaw JSON now uses json_escape() for consistency

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

* fix: remove double-escaping in install_agent that broke shell operators

install_agent() was wrapping commands with printf '%q' + bash -c before
passing them to the run callback. But run callbacks (run_server, run_sprite,
ssh_run_server) already handle escaping for remote transport. The double-
escaping turned && || > | into literal characters, causing 'source' to
treat the entire command as a single filename.

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

* fix: use local github-auth.sh instead of curling from main

When running from a local checkout, base64-encode the local
github-auth.sh and send it inline to the remote machine. This
ensures fixes (like the sudo skip for root) take effect immediately
without waiting for a merge to main.

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

* fix: handle github-auth errors gracefully instead of terminating

GitHub CLI setup is optional — failures should not abort the spawn
session. Guard both run_callback calls in offer_github_auth with
|| log_warn so the script continues even if gh install fails.

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

* fix: use GOOGLE_GEMINI_BASE_URL to route Gemini CLI through OpenRouter

Gemini CLI ignores OPENAI_BASE_URL — it uses GEMINI_API_KEY to talk
directly to Google's API. The OpenRouter key is not a valid Google
API key, so all requests fail with "API key not valid".

Use GOOGLE_GEMINI_BASE_URL to redirect Gemini CLI to OpenRouter's
endpoint. Fixes all 9 cloud gemini scripts + manifest.json.

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

* fix: guard optional spawn_agent hooks so failures don't kill the session

With set -eo pipefail, any unguarded failure terminates the script.
Several optional operations in spawn_agent were unguarded:

- agent_configure: config file uploads (agent works with defaults)
- agent_save_connection: convenience JSON for spawn list
- agent_pre_launch: gateway daemons, startup hooks
- agent_pre_provision: pre-provision prompts
- .spawnrc shell hooks: hooking env vars into .bashrc/.zshrc

These now log warnings and continue instead of aborting. Critical
steps (cloud_authenticate, agent_install, cloud_provision) still
exit on failure.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 05:21:55 -05:00
Ahmed Abushagur
159ad49fec
fix: harden openclaw across all clouds (#1456)
* docs: add spawn delete command to README

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

* fix: harden openclaw across all clouds — validation, reliability, performance

Fixes multiple issues causing openclaw to break on most clouds:

Bugs fixed:
- Double-prefixed model ID (openrouter/openrouter/auto) in config generation
- AWS gateway starting without env vars (missing .zshrc source)
- DigitalOcean sourcing .spawnrc instead of .zshrc for gateway
- Destructive rm -rf ~/.openclaw on re-runs (now mkdir -p)

Validation added:
- API key checked against OpenRouter /auth/key endpoint with re-prompt on failure
- Model ID verified against OpenRouter model list with re-prompt loop
- openrouter/auto and openrouter/free bypass model check

Reliability improvements:
- Standardized gateway launch with </dev/null & disown across all 9 clouds
- Gateway log auto-displayed on startup timeout for diagnostics
- 2GB swap added to cloud-init to prevent OOM on small VMs
- Portable install timeout (10 min) with macOS gtimeout fallback

Performance:
- Reordered spawn_agent: OAuth runs while VM provisions (saves 30-60s)
- Fly.io: bumped to 2GB RAM + 2 shared CPUs for openclaw
- Fly.io: tries bun first (faster), falls back to npm

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

* fix: skip sudo in gh install when running as root (Fly.io containers)

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

* fix: address PR review — skip validation in tests, quote escaped cmd, escape model_id

- verify_openrouter_key and verify_openrouter_model skip network calls when
  SPAWN_SKIP_API_VALIDATION, BUN_ENV=test, or NODE_ENV=test is set
- install_agent timeout wrapper now quotes the escaped command for defense in depth
- model_id in openclaw JSON now uses json_escape() for consistency

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

* fix: remove double-escaping in install_agent that broke shell operators

install_agent() was wrapping commands with printf '%q' + bash -c before
passing them to the run callback. But run callbacks (run_server, run_sprite,
ssh_run_server) already handle escaping for remote transport. The double-
escaping turned && || > | into literal characters, causing 'source' to
treat the entire command as a single filename.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 09:25:48 +00: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