Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 10 additions & 12 deletions .claude/skills/setup-agent-team/qa-e2e-prompt.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ You are a single-agent QA E2E tester for the spawn codebase.

## Mission

Run the Fly.io E2E test suite, investigate any failures, and fix broken provisioning scripts or test infrastructure.
Run the AWS E2E test suite, investigate any failures, and fix broken provisioning scripts or test infrastructure.

## Time Budget

Expand All @@ -21,8 +21,8 @@ cd WORKTREE_BASE_PLACEHOLDER

```bash
cd REPO_ROOT_PLACEHOLDER
chmod +x sh/e2e/fly-e2e.sh
./sh/e2e/fly-e2e.sh --parallel 6
chmod +x sh/e2e/aws-e2e.sh
./sh/e2e/aws-e2e.sh --parallel 6
```

Capture the full output. Note which agents passed and which failed.
Expand All @@ -35,22 +35,21 @@ If every agent passes, you're done. Log the results and exit. No PR needed.

For each failed agent, investigate the root cause. The failure categories are:

### Provision failure (app does not exist after provisioning)
### Provision failure (instance does not exist after provisioning)

1. Check the stderr log in the temp directory printed at the start of the run
2. Common causes:
- Missing env var for headless mode (e.g., `MODEL_ID` for openclaw)
- Fly.io API auth issues
- AWS API auth issues
- Agent-specific install script changed upstream
3. Read the agent's provisioning code: `packages/cli/src/fly/agents.ts` and `packages/cli/src/shared/agent-setup.ts`
3. Read the agent's provisioning code: `packages/cli/src/aws/aws.ts` and `packages/cli/src/shared/agent-setup.ts`
4. Read the E2E provision script: `sh/e2e/lib/provision.sh`

### Verification failure (app exists but checks fail)
### Verification failure (instance exists but checks fail)

1. SSH into the VM to investigate:
```bash
flyctl machines list -a APP_NAME --json | jq -r '.[0].id'
flyctl machine exec MACHINE_ID -a APP_NAME --timeout 30 "bash -c 'ls -la ~; cat ~/.spawnrc; echo ---; env'"
ssh -o StrictHostKeyChecking=no root@INSTANCE_IP "ls -la ~; cat ~/.spawnrc; echo ---; env"
```
2. Check if the binary path changed — read the agent's install script in `packages/cli/src/shared/agent-setup.ts`
3. Check if the env var names changed — read the agent's config in `manifest.json`
Expand All @@ -59,7 +58,6 @@ For each failed agent, investigate the root cause. The failure categories are:
### Timeout (provision took too long)

1. Check if `PROVISION_TIMEOUT` or `INSTALL_WAIT` need increasing
2. Check if the agent's install script has a new heavy dependency

## Step 4 — Fix

Expand All @@ -69,13 +67,13 @@ Make fixes in the worktree at WORKTREE_BASE_PLACEHOLDER. Fixes may be in:
- `sh/e2e/lib/verify.sh` — binary paths, config file locations, env var checks
- `sh/e2e/lib/common.sh` — API helpers, constants
- `sh/e2e/lib/teardown.sh` — cleanup logic
- `sh/e2e/lib/cleanup.sh` — stale app detection
- `sh/e2e/lib/cleanup.sh` — stale instance detection

After fixing:
1. Run `bash -n` on every modified `.sh` file
2. Re-run the E2E suite for the failed agent(s) only to verify the fix:
```bash
./sh/e2e/fly-e2e.sh AGENT_NAME
./sh/e2e/aws-e2e.sh AGENT_NAME
```

## Step 5 — Commit and PR
Expand Down
5 changes: 0 additions & 5 deletions .claude/skills/setup-agent-team/qa-fixtures-prompt.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,6 @@ curl -s -H "Authorization: Bearer ${DO_API_TOKEN}" "https://api.digitalocean.com
curl -s -H "Authorization: Bearer ${DO_API_TOKEN}" "https://api.digitalocean.com/v2/regions"
```

### Fly.io (needs FLY_API_TOKEN)
```bash
curl -s -H "Authorization: Bearer ${FLY_API_TOKEN}" "https://api.machines.dev/v1/apps?org_slug=personal"
```

For any other cloud directories found, read their TypeScript module in `packages/cli/src/{cloud}/` to discover the API base URL and auth pattern, then call equivalent GET-only endpoints.

## Step 4 — Save Fixtures
Expand Down
21 changes: 10 additions & 11 deletions .claude/skills/setup-agent-team/qa-quality-prompt.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,29 +122,28 @@ cd REPO_ROOT_PLACEHOLDER && git worktree remove WORKTREE_BASE_PLACEHOLDER/TASK_N

### Teammate 4: e2e-tester (model=sonnet)

**Task**: Run the Fly.io E2E test suite, investigate failures, and fix broken test infrastructure.
**Task**: Run the AWS E2E test suite, investigate failures, and fix broken test infrastructure.

**Protocol**:
1. Run the E2E suite from the main repo checkout (E2E tests provision live VMs — no worktree needed for the test runner itself):
```bash
cd REPO_ROOT_PLACEHOLDER
chmod +x sh/e2e/fly-e2e.sh
./sh/e2e/fly-e2e.sh --parallel 6
chmod +x sh/e2e/aws-e2e.sh
./sh/e2e/aws-e2e.sh --parallel 6
```
2. Capture the full output. Note which agents passed and which failed.
3. If all agents pass: report results and you're done. No PR needed.
4. If any agent fails, investigate the root cause. Failure categories:

**a) Provision failure** (app does not exist after provisioning):
**a) Provision failure** (instance does not exist after provisioning):
- Check the stderr log in the temp directory printed at the start of the run
- Common causes: missing env var for headless mode, Fly.io API auth issues, agent install script changed upstream
- Read: `packages/cli/src/fly/agents.ts`, `packages/cli/src/shared/agent-setup.ts`, `sh/e2e/lib/provision.sh`
- Common causes: missing env var for headless mode, AWS API auth issues, agent install script changed upstream
- Read: `packages/cli/src/aws/aws.ts`, `packages/cli/src/shared/agent-setup.ts`, `sh/e2e/lib/provision.sh`

**b) Verification failure** (app exists but checks fail):
**b) Verification failure** (instance exists but checks fail):
- SSH into the VM to investigate:
```bash
flyctl machines list -a APP_NAME --json | jq -r '.[0].id'
flyctl machine exec MACHINE_ID -a APP_NAME --timeout 30 "bash -c 'ls -la ~; cat ~/.spawnrc; echo ---; env'"
ssh -o StrictHostKeyChecking=no root@INSTANCE_IP "ls -la ~; cat ~/.spawnrc; echo ---; env"
```
- Check if binary paths or env var names changed in `manifest.json` or `packages/cli/src/shared/agent-setup.ts`
- Update verification checks in `sh/e2e/lib/verify.sh` if stale
Expand All @@ -161,9 +160,9 @@ cd REPO_ROOT_PLACEHOLDER && git worktree remove WORKTREE_BASE_PLACEHOLDER/TASK_N
- `sh/e2e/lib/verify.sh` — binary paths, config file locations, env var checks
- `sh/e2e/lib/common.sh` — API helpers, constants
- `sh/e2e/lib/teardown.sh` — cleanup logic
- `sh/e2e/lib/cleanup.sh` — stale app detection
- `sh/e2e/lib/cleanup.sh` — stale instance detection
7. Run `bash -n` on every modified `.sh` file
8. Re-run the E2E suite for the failed agent(s) only: `./sh/e2e/fly-e2e.sh AGENT_NAME`
8. Re-run the E2E suite for the failed agent(s) only: `./sh/e2e/aws-e2e.sh AGENT_NAME`
9. If changes were made: commit, push, open draft PR with title "fix(e2e): [description]"
10. Clean up worktree when done
11. Report: agents tested, passed, failed, fixed
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/setup-agent-team/qa.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ set -eo pipefail
# RUN_MODE=quality — agent team: test-runner + dedup-scanner + code-quality-reviewer + e2e-tester (reason=schedule/workflow_dispatch, 40 min)
# RUN_MODE=fixtures — single agent: collect API fixtures from cloud providers (reason=fixtures, 20 min)
# RUN_MODE=issue — single agent: investigate and fix a specific issue (reason=issues, 15 min)
# RUN_MODE=e2e — single agent: run Fly.io E2E tests, investigate failures (reason=e2e, 20 min)
# RUN_MODE=e2e — single agent: run AWS E2E tests, investigate failures (reason=e2e, 20 min)

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/cli-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Upload each cloud bundle as a separate release (fly-latest/fly.js, etc.)
# Upload each cloud bundle as a separate release (aws-latest/aws.js, etc.)
for bundle in packages/cli/*.js; do
name=$(basename "$bundle" .js)
[[ "$name" == "cli" ]] && continue # skip cli.js, already uploaded above
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
name: Build Fly Docker Images
name: Build Docker Images

on:
push:
branches: [main]
paths:
- "fly/docker/openclaw.Dockerfile"
- "sh/docker/**"
schedule:
# Daily: pick up new openclaw releases
- cron: "0 6 * * *"
Expand All @@ -29,6 +29,6 @@ jobs:
- uses: docker/build-push-action@v6
with:
context: .
file: fly/docker/openclaw.Dockerfile
file: sh/docker/openclaw.Dockerfile
push: true
tags: ghcr.io/openrouterteam/spawn-openclaw:latest
14 changes: 6 additions & 8 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,14 @@ Look at `manifest.json` → `matrix` for any `"missing"` entry. To implement it:

### 2. Add a new cloud provider (HIGH BAR)

We are currently shipping with **8 curated clouds** (sorted by price):
We are currently shipping with **7 curated clouds** (sorted by price):
1. **local** — free (no provisioning)
2. **hetzner** — ~€3.29/mo (CX22)
3. **fly** — free tier (3 shared-cpu VMs)
4. **aws** — $3.50/mo (nano)
5. **daytona** — pay-per-second sandboxes
6. **digitalocean** — $4/mo (Basic droplet)
7. **gcp** — $7.11/mo (e2-micro)
8. **sprite** — Fly.io managed VMs
3. **aws** — $3.50/mo (nano)
4. **daytona** — pay-per-second sandboxes
5. **digitalocean** — $4/mo (Basic droplet)
6. **gcp** — $7.11/mo (e2-micro)
7. **sprite** — managed cloud VMs

**Do NOT add clouds speculatively.** Every cloud must be manually tested and verified end-to-end before shipping. Adding a cloud that can't be tested is worse than not having it.

Expand Down Expand Up @@ -118,7 +117,6 @@ spawn/
github-auth.sh # Standalone GitHub CLI auth helper
key-request.sh # API key provisioning helpers (used by QA)
e2e/
fly-e2e.sh # Fly.io E2E test suite
lib/*.sh # E2E helper libraries
test/
macos-compat.sh # macOS compatibility test script
Expand Down
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Launch any AI agent on any cloud with a single command. Coding agents, research agents, self-hosted AI tools — Spawn deploys them all. All models powered by [OpenRouter](https://openrouter.ai). (ALPHA software, use at your own risk!)

**6 agents. 8 clouds. 48 working combinations. Zero config.**
**6 agents. 7 clouds. 42 working combinations. Zero config.**

## Install

Expand Down Expand Up @@ -160,14 +160,14 @@ If an agent fails to install or launch on a cloud:

## Matrix

| | [Local Machine](sh/local/) | [Hetzner Cloud](sh/hetzner/) | [Fly.io](sh/fly/) | [AWS Lightsail](sh/aws/) | [Daytona](sh/daytona/) | [DigitalOcean](sh/digitalocean/) | [GCP Compute Engine](sh/gcp/) | [Sprite](sh/sprite/) |
|---|---|---|---|---|---|---|---|---|
| [**Claude Code**](https://claude.ai) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| [**OpenClaw**](https://github.com/openclaw/openclaw) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| [**ZeroClaw**](https://github.com/zeroclaw-labs/zeroclaw) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| [**Codex CLI**](https://github.com/openai/codex) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| [**OpenCode**](https://github.com/sst/opencode) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| [**Kilo Code**](https://github.com/Kilo-Org/kilocode) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| | [Local Machine](sh/local/) | [Hetzner Cloud](sh/hetzner/) | [AWS Lightsail](sh/aws/) | [Daytona](sh/daytona/) | [DigitalOcean](sh/digitalocean/) | [GCP Compute Engine](sh/gcp/) | [Sprite](sh/sprite/) |
|---|---|---|---|---|---|---|---|
| [**Claude Code**](https://claude.ai) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| [**OpenClaw**](https://github.com/openclaw/openclaw) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| [**ZeroClaw**](https://github.com/zeroclaw-labs/zeroclaw) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| [**Codex CLI**](https://github.com/openai/codex) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| [**OpenCode**](https://github.com/sst/opencode) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| [**Kilo Code**](https://github.com/Kilo-Org/kilocode) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |

### How it works

Expand Down
1 change: 0 additions & 1 deletion assets/clouds/.sources.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
{
"hetzner": { "url": "https://www.hetzner.com/_resources/themes/hetzner/images/favicons/ms-icon-310x310.png", "ext": "png" },
"fly": { "url": "https://fly.io/phx/ui/images/favicon/android-chrome-512x512.png", "ext": "png" },
"aws": { "url": "https://a0.awsstatic.com/libra-css/images/site/touch-icon-ipad-144-smile.png", "ext": "png" },
"daytona": { "url": "https://avatars.githubusercontent.com/u/130513197?s=400&v=4", "ext": "png" },
"digitalocean": { "url": "https://www.digitalocean.com/_next/static/media/android-chrome-512x512.5f2e6221.png", "ext": "png" },
Expand Down
Binary file removed assets/clouds/fly.png
Binary file not shown.
9 changes: 0 additions & 9 deletions fixtures/fly/_metadata.json

This file was deleted.

13 changes: 0 additions & 13 deletions fixtures/fly/apps.json

This file was deleted.

16 changes: 0 additions & 16 deletions fixtures/fly/create_server.json

This file was deleted.

24 changes: 0 additions & 24 deletions manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -271,24 +271,6 @@
},
"icon": "https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/assets/clouds/hetzner.png"
},
"fly": {
"name": "Fly.io",
"description": "Deploy globally on Fly.io with free-tier VMs",
"url": "https://fly.io",
"type": "api+cli",
"auth": "FLY_API_TOKEN",
"provision_method": "POST /v1/apps + POST /v1/apps/{app}/machines",
"exec_method": "fly ssh console -C",
"interactive_method": "fly ssh console",
"defaults": {
"region": "iad",
"vm_size": "shared-cpu-1x",
"vm_memory": 1024,
"image": "ubuntu:24.04"
},
"notes": "Uses Machines API for provisioning and flyctl SSH for exec. Docker-based, pay-per-second pricing. Requires flyctl CLI.",
"icon": "https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/assets/clouds/fly.png"
},
"aws": {
"name": "AWS Lightsail",
"description": "Simple AWS instances starting at $3.50/mo",
Expand Down Expand Up @@ -381,12 +363,6 @@
"hetzner/codex": "implemented",
"hetzner/opencode": "implemented",
"hetzner/kilocode": "implemented",
"fly/claude": "implemented",
"fly/openclaw": "implemented",
"fly/zeroclaw": "implemented",
"fly/codex": "implemented",
"fly/opencode": "implemented",
"fly/kilocode": "implemented",
"aws/claude": "implemented",
"aws/openclaw": "implemented",
"aws/zeroclaw": "implemented",
Expand Down
1 change: 0 additions & 1 deletion packages/cli/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ dist/
aws.js
daytona.js
digitalocean.js
fly.js
gcp.js
hetzner.js
local.js
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/build-clouds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
//
// Usage:
// bun run cli/build-clouds.ts # build all clouds
// bun run cli/build-clouds.ts fly # build specific cloud
// bun run cli/build-clouds.ts aws # build specific cloud

import { readdirSync, existsSync } from "fs";
import path from "path";
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@openrouter/spawn",
"version": "0.10.26",
"version": "0.11.0",
"type": "module",
"bin": {
"spawn": "cli.js"
Expand Down
21 changes: 0 additions & 21 deletions packages/cli/src/__tests__/custom-flag.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,27 +135,6 @@ describe("GCP --custom prompts", () => {
});
});

describe("Fly --custom prompts", () => {
const savedCustom = process.env.SPAWN_CUSTOM;
const savedMemory = process.env.FLY_VM_MEMORY;

afterEach(() => {
restoreEnv("SPAWN_CUSTOM", savedCustom);
restoreEnv("FLY_VM_MEMORY", savedMemory);
});

it("should return defaults without --custom", async () => {
delete process.env.FLY_VM_MEMORY;
delete process.env.SPAWN_CUSTOM;
const { DEFAULT_VM_TIER } = await import("../fly/fly");
// The promptVmOptions is local to main.ts, so we test the behavior
// via the exported DEFAULT_VM_TIER and the env-var pattern
expect(DEFAULT_VM_TIER.cpuKind).toBeDefined();
expect(DEFAULT_VM_TIER.cpus).toBeGreaterThan(0);
expect(DEFAULT_VM_TIER.memoryMb).toBeGreaterThan(0);
});
});

describe("Hetzner --custom prompts", () => {
const savedCustom = process.env.SPAWN_CUSTOM;
const savedServerType = process.env.HETZNER_SERVER_TYPE;
Expand Down
Loading